目录
从 Spring 3.0 开始,Spring JavaConfig 项目提供的许多特性都是 Spring 框架核心的一部分。可以允许你使用 Java 代替传统的 XML 文件来定义bean。这些注解有:@Configuration、@Bean、@Import 和 @DependsOn 注解等。
Spring 提供了很多组件管理的注解,如:@Component,@Service,@Controller 和 @Repository 等。其中 @Component 注解是 Spring 组件管理的通用原型,其他注解都是对 @Component 的引申,表示对于更具体组件的管理。
虽然 @Component 注解可以替换所以有的其他组件注解,但是 Spring 还是推荐按层使用这些注解,因为 @Service,@Controller 和 @Repository 等注解在 Spring 框架的后续版本中可能会添加额外的语义,同时这些注解也让组件管理有更明确的标记。
Spring 提供的很多注解都可以作为用户自定义注解的元注解来使用。所谓元注解,就是可以用在其他注解上的注解,比如前边提到的 @Service 就是使用 @Component 作为元注解来定义的:
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Component
- public @interface Service {
-
- // ...
- }
此外,Spring MVC 中的 @RestController 注解也是由 @Controller 和 @ResponseBody 组成的一个组合注解。
组合注解支持通过重新声明元注解的属性来进行属性自定义。这个特性在用户只想公开元注释属性的一个子集时,会特别的有用。示例如下:// 组合注解的自定义
- @Target({ElementType.TYPE, ElementType.METHOD})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Scope(WebApplicationContext.SCOPE_SESSION)
- public @interface SessionScope {
-
- /**
- * 屏蔽@Scope的其他属性,只保留proxyMode属性可以被用户定义
- * Alias for {@link Scope#proxyMode}.
- *
Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
- */
- @AliasFor(annotation = Scope.class)
- ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
-
- }
如果要自动检测类并注册相应的 bean,需要将 @ComponentScan 添加到 @Configuration 配置类中,其中 basePackages 属性设置的是所有类的公共父包。(或者,指定一个逗号或分号或空格分隔的列表,其中包括每个类的父包。)// 指定配置文件去哪一个包下扫描配置中的类
- @Configuration
- @ComponentScan(basePackages = "org.example")
- public class AppConfig {
- // ...
- }
默认情况下,@Component, @Repository, @Service, @Controller,@Configuration 等注解只能检测候选组件,不过,你可以通过自定过滤器来改变这些默认行为。在 @ComponentScan 注解中通过配置 includeFilters 和 excludeFilters 属性来实现自定义。每个过滤的元素都需要包含过滤类型 type 和过滤表达式 expression 两个属性,更详细的介绍,请点击这里。// 自定义扫描的文件
下面的例子展示了忽略所有的 @Repository 注解并使用以 ”Stub“ 结尾的包名下以 ”Repository“ 命名结尾的文件进行替换:
- @Configuration
- @ComponentScan(basePackages = "org.example",
- includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
- excludeFilters = @Filter(Repository.class))
- public class AppConfig {
- // ...
- }
你还可以禁止使用默认过滤器,通过设置 @ComponentScan 注解中的 useDefaultFilters=false 来实现,设置这个属性将禁止自动检测带注解的类和相关的注解,比如:@Component, @Repository,@Service,@Controller,@RestController,或 @Configuration 等 // 禁用过滤器
Spring 组件还可以向容器提供 bean 定义的元数据。你可以像使用 @Configuration + @Bean 一样,使用 @Component + @Bean 来完成此操作。下面的例子展示了如何做到这一点:
- @Component
- public class FactoryMethodComponent {
-
- @Bean
- @Qualifier("public")
- public TestBean publicInstance() {
- return new TestBean("publicInstance");
- }
-
- public void doWork() {
- // 组件方法实现省略
- }
- }
上边类是一个包含了 doWork() 方法的 Spring 组件,同时,它还包含了一个 publicInstance() 的工厂方法,该方法提供了一个 bean 定义。@Bean 注解标识了工厂方法和其他 bean 定义的属性,比如通过 @Qualifier 注解指定限定符的值。此外,还可以指定其他方法级别的注解,比如:@Scope, @Lazy,和自定义的限定符注解等。
@Component 组件支持方法和属性的自动装配,同时也支持 @Bean 工厂方法的自动装配,下边的例子展示如何做到这一点:// @Configuration 不就是 @Component 的包装?
- @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
- @Bean
- protected 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;
- }
-
- @Bean
- private TestBean privateInstance() {
- return new TestBean("privateInstance", i++);
- }
-
- @Bean
- @RequestScope
- public TestBean requestScopedInstance() {
- return new TestBean("requestScopedInstance", 3);
- }
- }
从 Spring Framework 4.3 开始,你也可以声明一个类型为 InjectionPoint 的工厂方法参数(或者它更具体的子类:DependencyDescriptor),该参数是触发当前 bean 创建的请求注入点。需要注意的是,该方法只能用于 bean 实例的新的创建,而不是对现有实例进行注入。该方法对非单例 bean 的创建非常有用。对于其他作用域,只有当触发创建指定作用域的 bean 的新实例时,工厂方法才能访问该 bean 的请求注入点。这种场景下,你可以使用通过注入点提供的数据。下面的例子展示了如何使用 InjectionPoint:// 通过注入点配置元数据
- @Component
- public class FactoryMethodComponent {
-
- @Bean @Scope("prototype")
- public TestBean prototypeInstance(InjectionPoint injectionPoint) {
- return new TestBean("prototypeInstance for " + injectionPoint.getMember());
- }
- }
被 @Bean 标注的方法在 @Component 类和 @Configuration 类中有不同的处理方式。不同之处在于 @Component 类在调用方法和字段时没有使用 CGLIB 进行增强。CGLIB 代理是一种通过调用 @Configuration 类中 @Bean 注解的方法来创建对协作对象的 bean 的方式。这些方法不是通过正常的 Java 语义调用的,而是通过 Spring 容器进行调用,从而为 Spring bean 提供生命周期管理和代理功能,甚至在通过对 @Bean 方法的编程调用去引用其他 bean 时也是如此。相反,在普通的 @Component 类中调用 @Bean 方法或字段时具有标准的 Java 语义,没有经过 CGLIB 特殊处理或其他的约束。// 区别在于是否使用了 CGLIB 进行增强
可以将 @Bean 方法声明为 static 方法,允许这些方法在它们的配置类实例没创建之前就可以被调用。这种方式运用在一些特殊的场景,比如,在定义一些后置处理器的 beans 时(BeanFactoryPostProcessor or BeanPostProcessor),这些 bean 会在容器的生命周期中被提前进行初始化,所以不会碰触到配置类中其他部分的 beans。
声明为 static 的 @Bean 方法不会被 Spring 容器进行拦截,即使是被定义在 @Configuration 类中也一样。所以声明为 static 的 @Bean 方法也不能被 CGLIB 代理,该方法的调用具备标准的 Java 语义,调用该工厂方法后,会从该方法返回一个独立的实例。
Java 对 @Bean 方法的可见性不会对 Spring 容器中生成的 bean 产生直接影响。你可以灵活的在非 @Configuration 类中声明静态或者非静态的工厂方法。但是,配置在 @Configuration 类中 @Bean 方法需要能够被覆盖(支持重写),所以,@Configuration 类中 @Bean 方法不能被声明为 private 或者 final 类型。
@Bean 方法可以定义在 Spring 的 @Component 类或 @Configuration 类中,同样也可以定义在由 @Component 类或 @Configuration 类实现的 Java 8 接口的默认方法当中。这使得在组合复杂的配置关系时具备了很大的灵活性,在 Spring 4.2 版本中,甚至可以通过 Java 8 的默认方法实现多个继承。// 默认方法打破了Java 的单继承?
最后,在一个单独的 class 中可能为同一个 bean 声明了多个不同参数的 @Bean 方法。这些工厂方法在运行时被选择的算法和选择构造器的方式是一样的:能够满足最多参数注入的方法将会最终被选中。
虽然类路径扫描非常快,但是可以通过在编译时创建静态候选列表来提高大型应用程序的启动性能。在该模式下,所有作为组件扫描目标的模块都必须使用这种机制。// 提高程序启动性能
要生成索引,需要向每个包含组件扫描指令目标组件的模块添加额外的依赖项。下面的例子展示了如何使用 Maven 实现这一点:
- <dependencies>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context-indexer</artifactId>
- <version>5.3.23</version>
- <optional>true</optional>
- </dependency>
- </dependencies>
spring-context-indexer 会生成一个包含在 jar 文件中的 META-INF/spring.components 文件。更过细节请点击这里。