2019独角兽企业重金招聘Python工程师标准>>>
7.10 Classpath scanning and managed componets
文档地址: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#beans-classpath-scanning 本章中大部分例子是用xml来指定配置元数据,以生成容器里的每个bean定义.上个部分描述了如何通过资源注解来提供大量的配置元数据.但是,这很多例子中,很多bean定义是在xml文件中完成的,而注解只负责依赖注入.本节描述如何通过扫描路径来检测可选组件.候选组件都是一些能匹配一些条件的类或者在容器中注册了相应的bean定义.这移除了使用xml注册bean的必要,因此你可用使用注解,AspectJ类型表达,又或者你的自定义拦截条件 来选择容器中注册的bean定义.
从spring3.0,spring的java配置项目提供了的很多功能已经是spring核心功能的一部分.这允许你使用java而不是xml文件来定义beans.学习@Configuration,@Bean,@Import,@Dependson注解,并学习如何使用.
7.10.1 @Component and further sterertype annotations
@Repository注解是一个任何能完成repository(也就是DAO)角色或模板的类的标志.使用这些标志可以自动转译异常,如Section 20.2.2,"Exception translation";
spring提供了很多固定注解:@Component,@Service,@Controller,@Repository.@Component是一个spring管理组件的基本模板类型.@Repository,@Service,@Controller是对@Componet的特殊化,应用于特定的场合,例如,他们分别在持久化,服务层,表现层中使用.因此,你可以用@Component来标志你的组件,但你最好用@Repository,@Service,@Controller来替换,因为这样可以使这些类更好的而配合工作操作或者和切面协同.例如,这些模板注解都是切入点的理想目标.spring框架在以后的发布版中@Repository,@Service,@Controller可能会携带额外的语法.因此,当你考虑用@Service或@Component来标注你的服务层是,@Service是更好的选择.同上文所示,@Repository已被当为了一个支持持久层自动异常转译的标志.
7.10.2 Meta-annotations (元注解)
很多spring提供的注解可以作为元注解在你的代码里使用.元注解是指可以在其他注解中使用的注解.例如,上文中提到的@Service注解中就有@Component元注解.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // Spring will see this and treat @Service in the same way as @Component
public @interface Service {/ / ....
}
元注解可以被聚合生成组合注解.例如,spring mvc的@RestController注解就是@Controller和@ResponseBody组合而成的.
另外,组合注解可以重新申明元注解的属性来允许用户自定义.当你只打算暴露一部分元注解属性时这将非常有用.例如,spring的@SessionScope以硬编码的方式控制scope的名字为session,但扔允许自定义proxyMode的值.
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {/*** Alias for {@link Scope#proxyMode}.* <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.*/@AliasFor(annotation = Scope.class)ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;}
@SessionScope不申明proxyMode属性仍可以如下使用
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {// ...
}
更多细节,请查看spring annotaion programming Model
7.10.3 Automatically detectin classes and registering bean definitions(自动检测类并注册bean定义)
spring 可以自动检测模板类并通过ApplicationContext来注册相应的bean定义.例如,一下两个类将会代理以实现自动检测.
@Service
public class SimpleMovieLister {private MovieFinder movieFinder;@Autowiredpublic SimpleMovieLister(MovieFinder movieFinder) {this.movieFinder = movieFinder;}}@Repository
public class JpaMovieFinder implements MovieFinder {// implementation elided for clarity
}
要自动检测这些类并注册相应的bean,你需要在你的@Configuration类中添加@ComponentScan注解,@ComponentScan中的basePackages属性应该是两个类的共同父包.(另外,你可以制定一个逗号,分号,空白分割的列表来包含每个类的父包);
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {...
}
为了简化,上面的可以直接使用注解你的value属性,如 @ComponentScan("org.example")
以下是相应的xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="org.example"/></beans>
-
context:component-scan的隐式的包含了context:annotation-cofig的功能,所以你在使用context:component-scan时不需要使用context:annotation-config;
-
类路径包的扫描需要相应的目录实体在类路径中真实存在.当你用Ant构建JARS时,要保证你没有开启你的文件转化jar包任务.另外,在一些环境里,依据安全策略类路径目录将无法暴露.例如,jdk1.7中单独的应用.
还有,当你使用component-scan元素时,AutowiredAnnotaionBeanPostProcessor,CommonAnnotationBeanPostProcessor都是隐式包含的.这意味着这两个组件会自动检测被一起注册,不需要在xml里提供任何bean配置元数据.
另,你也可以不注册AutowiredAnnotaionBeanPostProcessor,CommonAnnotationBeanPostProcessor,这需要你在annotation-config元素中将值设为false;
7.10.4 使用过滤器来自定义扫描
一般而言,用@Component,@Repository,@Service,,@Controller,或其他本身标记了@Component注解的注解才能被检测为可选组件.但是,你可以简单的通过使用自定义过滤器来修改并扩展这个行为.将他们作为includeFilters或excludeFilters的参数添加到@ComponentScan注解里.下面的表将描述拦截选项.
拦截器类型
annotaion,assignable,aspectj,regex,custom五种. 下面的例子表明所有的@Repository注解都会被排除,并使用以stub结尾的repository替代.
@Configuration
@ComponentScan(basePackages = "org.example",includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),excludeFilters = @Filter(Repository.class))
public class AppConfig {...
}
等同于
<beans><context:component-scan base-package="org.example"><context:include-filter type="regex"expression=".*Stub.*Repository"/><context:exclude-filter type="annotation"expression="org.springframework.stereotype.Repository"/></context:component-scan>
</beans>
@Filter,属性时type,pattern,默认是annotation;你也可以通过设置@ComponentScan的useDefaultFileters=false或设置<component-scan>元素中的use-default-filters="false"来禁用默认过滤器.而受影响的类是标注了@Componnet,@Repository,@Service,@Controller,或者@Configuration的类.
7.10.5 Defining bean medata within components(在components中定义bean的元数据)
spring 的components也可以向容器提供bean定义.你可以像在@Configuration注解类中一样使用@Bean注解来定义bean的元数据.下面是一个简单的例子:
@Component
public class FactoryMethodComponent {@Bean@Qualifier("public")public TestBean publicInstance() {return new TestBean("publicInstance");}public void doWork() {// Component method implementation omitted}}
这个类是一个spring组件类,在它的doWork()方法里有应用特定的代码.然,它也提供了一个bean定义,用一个工厂方法来指向方法publicInstance().@Bean注解标记着工厂方法或其他bean定义属性.例如通过@Qualifier提供的一个qualifier值.其他方法级别的注解可以被@Scope,@Lazy或其他自定义注解等来指定.
- 可以使用@Lazy同@Autowired,@Inject标志懒加载.在本上下文中,它将注入懒加载代理.
自动装配的字段和方法支持上文已经讨论了,另外讨论的是对@Bean方法自动装配的支持:
@Component
public class FactoryMethodComponent {private static int i;@Bean@Qualifier("public")public TestBean publicInstance() {return new TestBean("publicInstance");}// use of a custom qualifier and autowiring of method parameters@Beanprotected TestBean protectedInstance(@Qualifier("public") TestBean spouse,@Value("#{privateInstance.age}") String country) {TestBean tb = new TestBean("protectedInstance", 1);tb.setSpouse(spouse);tb.setCountry(country);return tb;}@Beanprivate TestBean privateInstance() {return new TestBean("privateInstance", i++);}@Bean@RequestScopepublic TestBean requestScopedInstance() {return new TestBean("requestScopedInstance", 3);}}
上面的例子会自动装配字符串类型的方法参数country的值为一个名为privateInstance的bean的Age属性.一个spring表达式语言元素通过#{<expression>}的形式定义了表达式的值.对于@Value注解,一个表达式解析器会会再解析表达文本之前前查看bean的名称. 在spring的Component里的@Bean方法不同于@Configuration类里的@Bean方法的处理.这个不同在于@Component的类没有通过CGLIB来拦截方法或属性的调用.CGLIB代理是通过在@Configueration类中定义了指向其他协作对象的bean的元数据@Bean方法来调用相应的方法或字段;这些方法并没有通过正常的java语义而是通过容器调用的,为的就是当通过调用@Bean方法来引用其他beans时可以提供正常的生命周期管理和spring的代理.相反的,在一个简单的@Component类里调用@Bean方法的字段或方法会有正常的java语法,而不是一个特殊的CGLIB处理或其他限制条件.
如果你吧一个@bean的方法宣布为static,这可以允许你在bean未实例化之后调用它们.当你定义了后处理器类时,这会非常有意义.因为这些类会再容器生命周期中很早启动,这样会避免在此时触发容器配置的其他部分.
调用@bean的静态方法不会被容器拦截,即使当你在@Configuration类里调用.这主要是技术的限制:CGLIB子类只能重写非静态的方法.结果,直接调用其他的@Bean方法拥有java标准语法,所以工厂方法自身会返回一个独立的实例.
java语言中@Bean方法的存在并不会对spring容器里的bean定义造成直接的影响.你可以自由的在你认为合适的非@Configuration类里申明工厂方法,也可以将之设置为静态方法.但是,在@Configuration类里的常规@Bean方法可能需要重写,所以 他们不能命名为private或final.
@Bean方法可以在一个指定的组件类或配置类里发现,和java 8 发现组件类和配置类中实现接口申明的默认方法一样.这就允许在组装复杂的配置组合时有很大的灵活性.即使通过java8的默认方法使混合继承成为可能.
最后,记住一个单个的class可能对于同一个bean持有多个混合的@Bean方法,混合工厂方法的安排取决于运行时那些依赖是可获得.这个算法同其他配置场景里选择最贪婪的工厂方法和构造器一样:可适依赖数量最多的种类在构造期间获取,同容器如何选择混合@Autowired构造器一样.
7.10.6 命名自动检测组件 Naming autodected components
当一个组件作为扫描过程的一部分进行自动检测时,它的bean的名称是通过BeanNameGenerator策略来告知扫描器的.默认的,spring任何固定类型注解(@Componnet,@Repository,@Service,@Controller)包含一个那么值,并将它提供给对于的bean定义.
如果一个注解没有包含value值或其他可检测的组件(可以被自定义拦截器拦截的bean).默认的bean名称生成器返回一个小写字母开头的非限制的类名称.例如,如果以下两个组件被检测,那么它们的名称应该是myMovieListener或movieFinderImpl;
@Service("myMovieLister")
public class SimpleMovieLister {// ...
}@Repository
public class MovieFinderImpl implements MovieFinder {// ...
}
如果你不想依赖默认的bean命名策略,你可以提供一个自定义命名策略.首先,实现BeanNameGenerator接口,并确保它有一个无参构造器.另外,在配置扫描器时提供一个全匹配符的类名称.
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {...
}
或者
<beans><context:component-scan base-package="org.example"name-generator="org.example.MyNameGenerator" />
</beans>
首先,思考指定注解的值,因为其他组件可能会引用它.另一方面,当容器进行注入时,名称自动生成策略总是差强人意的.
7.10.7 Providing a scope for autodetected components(为自动检测组件提供作用域)
一般而言,spring管理的组件默认和通用的自动检测组件的作用域一般是单例.但是,有时你需要使用@Scope来定义其他的作用域.使用注解提供作用域的名称来实现.
@Scope("prototype")
@Repostitory
public class MovieFinderImpl implements MovieFinder{
}
web特定的作用域细节,可查看7.4.5章,Request,session,global session,application,and WebSocket scopes
如果你想使用自定义作用域策略而不是使用基于注解方法,你需要实现ScopeMetadataResolver接口,并保证该实现类有一个无参构造器.另外,在配置扫描器时提供一个类的全路径名称.
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {...
}
xml配置方法
<beans><context:component-scan base-package="org.example"scope-resolver="org.example.MyScopeResolver" />
</beans>
当你使用特定非单例作用域,它可能需要为作用域里的对象提供代理. 这个原因在"scoped beans as dependencies"一节里已经描述.为实现这个目标,component-scan元素提供了一个scoped-proxy元素,它有三个值:no,interfacce,targerClass.例如,下面的配置将会是标准的JDK动态代理:
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {...
}<beans><context:component-scan base-package="org.example"scoped-proxy="interfaces" />
</beans>
7.10.8 通过注解提供匹配符元数据
我们在7.9.4这节里考虑过,'Fine-tuning annotation-based autowiring with qualifier'. 本节的例子说明在你要解决自动注入条件问题时,你可以使用@Qualifier注解或自定义注解来获得更好的控制.因为这些例子都是基于xml的bean定义,这个通配符元数据通过是通过候选的bean定义使用xml里bean元素的qualifier或meta子元素来定义.当你使用类路径扫描或组件自动检测,你需要提供在候选类里通过类级别的注解来提供匹配符元数据.以下三个例子说明了这个技术:
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {// ...
}@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {// ...
}@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {// ...
}
作为大部分基于xml的可替换项,要记住注解元数据是基于类定义自身的,而使用xml可以为同一类型的不同bean定义提供它们相应的配置元数据.因为注解是类级别的,而xml里的bean则是实例级别的.