本文已收录【修炼内功】跃迁之路
微信搜索 林中小舍,林小二带你聊技术
截止本篇,已经介绍了Spring中的Resource、 BeanDefinitionReader 、 BeanFactory 、 ApplicationContext 、AOP等,本篇重点介绍Spring Framework中基于注解的Bean装配原理
注解的使用大大简化了配置的过程,也更能表现代码与配置之间的直接关系,但同时也失去了部分配置统一管理的能力,对代码也有一定的侵入性
这似乎并不影响开发者对注解使用的高涨热情,使用注解还是XML进行配置开发并没有统一的定论,以何种方式进行开发还是需要看具体的项目适合什么
The introduction of annotation-based configuration raised the question of whether this approach is “better” than XML. The short answer is “it depends.” The long answer is that each approach has its pros and cons, and, usually, it is up to the developer to decide which strategy suits them better. Due to the way they are defined, annotations provide a lot of context in their declaration, leading to shorter and more concise configuration. However, XML excels at wiring up components without touching their source code or recompiling them. Some developers prefer having the wiring close to the source while others argue that annotated classes are no longer POJOs and, furthermore, that the configuration becomes decentralized and harder to control.
Spring Framework提供了众多配置类注解,但不知道各位在使用过程中是否有类似于如下的疑问
@Service、 @Repository 等注解本身还被 @Component 修饰,为什么要这样?有什么作用?
// Service @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component // 被@Component修饰 public @interface Service { @AliasFor(annotation = Component.class) String value() default ""; } // Repository @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component // 被@Component修饰 public @interface Repository { ... }
@Service中的 @AliasFor 是做什么用的?为什么设置 @Service 的value,效果与直接设置 @Component 的value是一样的(都可以指定bean-name)?
@Service("myService") public class MyService { ... } @Component("myService") pulbic class MyService { ... }
自定义注解 @TransactionalService 同时被 @Transactional 及 @Service 修饰,为什么直接使用 @TransactionalService 与同时使用 @Transactional 及 @Service 的效果是一样的?
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Transactional @Service public @interface TransactionalService { ... } @TransactionalService public class MyService { ... } @Service @Transactional pulbic class MyService { ... }
是否从以上的示例中能够看出一些端倪?似乎Spring实现了注解的“继承”,并且可以对“父注解”中的属性进行重写
在深入介绍之前,有必要先了解一下Spring中的注解编程模型 Spring Annotation Programming Model
用于修饰其他注解的注解,如 @Service 中的 @Documented 、 @Component 等,这里完全可以将元注解理解为被修饰注解的“父类”
用于声明Spring组件的注解,如 @Component 、 @Service 、 @Repository 、 @Controller 等等,仔细观察的话会发现,除了 @Component 之外,所有直接/间接被 @Component 修饰的注解均为构造形注解(均可用于声明Spring组件),如果将这种关系看做是继承就比较容易理解了
同时被多个(元)注解修饰,同时实现多个注解的能力,如上例中提到的 @TransactionalService ,等效于同时使用 @Transactional 及 @Service
Spring引入了新的注解 @AliasFor 用以实现属性的别名(同一个注解内部不同属性间)及属性的重写(元注解中的属性)
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Service @Transactional(propagation = Propagation.NESTED) // 设置默认值 public @interface TransactionalService { // 重写@Service#value @AliasFor(annotation = Service.class) String value() default ""; // 重写@Transactional#rollbackFor @AliasFor(annotation = Transactional.class, attribute = "rollbackFor") Class<? extends Throwable>[] transFor() default {}; }
@TransactionalService(value = "myTransactionalService", transFor = RuntimeException.class) public class MyService {} // 构造MergedAnnotations MergedAnnotations mergedAnnotations = MergedAnnotations.from(MyService.class); // 构造 MergedAnnotation<Component> mergedComponent = mergedAnnotations.get(Component.class); MergedAnnotation<Service> mergedService = mergedAnnotations.get(Service.class); MergedAnnotation<Transactional> mergedTransactional = mergedAnnotations.get(Transactional.class); System.out.println("Component#value: " + mergedComponent.getString("value")); System.out.println("Service#value: " + mergedService.getString("value")); System.out.println("Transactional#rollbackFor: " + Arrays.toString(mergedTransactional.getClassArray("rollbackFor"))); System.out.println("Transactional#propagation: " + mergedTransactional.getEnum("propagation", Propagation.class));
输出
Component#value: myTransactionalService Service#value: myTransactionalService Transactional#rollbackFor: [class java.lang.RuntimeException] Transactional#propagation: NESTED
在Spring的注解体系中会发现大量如上的使用方式,Spring使用 MergedAnnotation(s) 将上述提到的元注解、组合注解、属性的别名及重写等信息整合,实现了一套类似注解类继承的机制,以此来提高注解复用、简化开发
如何让Spring识别到注解并注册/装配?
以xml配置的方式使用Spring时,会有两个标签 <context:annotation-config>
及 <context:component-scan>
来开启注解的识别
前者用于注册一些处理器,用于处理容器中已注册Bean上的注解,如配置类 @Configuration 、 @Import 、 @Autowired 等
后者用于扫描指定package中被构造形注解( @Component 及所有直接/间接被 @Component 修饰的注解)修饰的类,并将其注册到容器中,除此之外 <context:component-scan>
还会同时完成 <context:annotation-config>
的功能
两种配置的解析器分别对应 AnnotationConfigBeanDefinitionParser 及 ComponentScanBeanDefinitionParser ,其分别在 ContextNamespaceHandler 中注册
xml自定义标签的逻辑在Spring Framework 2 BeanDefinitionReader中有介绍
AnnotationConfigApplicationContext会创建两种注册器 AnnotatedBeanDefinitionReader 及 ClassPathBeanDefinitionScanner
前者用于指定BeanClass(es)进行注册,后者用于完成构造形注解修饰类的发现并注册,同时两者均会调用 AnnotationConfigUtils.registerAnnotationConfigProcessors 完成各种处理器的注册
至此可以看到,基于注解的Bean发现、注册、装配集中在三个地方
AnnotatedBeanDefinitionReader可以指定一个或者多个class进行注册,具体的注册逻辑在 AnnotatedBeanDefinitionReader#registerBean ,其大体的流程为
流程图中每一个被框起来的部分都是一个比较独立的逻辑块,下文中会频繁出现(以下不再对各逻辑块的细节进行展开),这里需要对其中的几个逻辑块展开讨论下
Spring可以通过 @Conditional 中指定的 Condition 实现类来判断,是否需要忽略当前class的bean注册,所以需要在 @Conditional 中指定一个或多个 Condition 的实现类,只要有一个Condition不满足条件则会忽略本次bean的注册
@FunctionalInterface public interface Condition { /** * Determine if the condition matches. */ boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
Condition的接口定义十分简单,从ConditionContext中可以获取 BeanDefinitionRegistry 、 ConfigurableListableBeanFactory 、 Environment 、 ResourceLoader 、 ClassLoader 等信息, AnnotatedTypeMetadata 则包含了目标类上的注解信息,通过以上各种信息可以方便的写出Condition逻辑
ProfileCondition是一个比较典型的例子,其获取目标class上的 @Profile 注解,判断 @Profile 指定的值是否包含当前系统所指定的 profile ( -Dspring.active.profiles ),以决定是否忽略bean的注册,以此便可以将特殊Bean的注册做环境隔离,只有在指定的环境中才会注册特殊的Bean(比如应用到线上、线下环境注册不同的Service实现)
一般而言,Condition实现类均会配合一个对应的自定义注解来结合使用
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(ProfileCondition.class) // 元注解为@Conditional,并指定Condition为ProfileCondition public @interface Profile { String[] value(); } class ProfileCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // 获取目标class上的 @Profile 注解,判断 @Profile 指定的值是否包含当前系统所指定的 profile } }
@Profile中使用了 @Conditional 作为元注解,并指定 Condition 为 ProfileCondition ,同时 ProfileCondition 又通过 @Profile 中的参数来判断环境是否匹配, @Profile 与 ProfileCondition 相辅相成
类似的,在SpringBoot中还可以找到很多类似的 Annotation + Condition 组合,如@ConditionalOnClass、@ConditionalOnProperty等等
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnClassCondition.class) public @interface ConditionalOnClass { Class<?>[] value() default {}; String[] name() default {}; } @Order(Ordered.HIGHEST_PRECEDENCE) class OnClassCondition extends FilteringSpringBootCondition { // 获取目标class上的 @ConditionalOnClass 注解,判断 @ConditionalOnClass 指定的类是否在classpath中 }
其他 @ConditionalXxx 不再列举,可以在 org.springframework.boot.autoconfigure.condition 包中查看
Q: 如何创建符合自己业务含义的 Annotation + Condition
在Spring Framwork 3 Bean是如何被创建的一文中介绍过Scope的概念,Spring中可以通过 @Scope 设置Bean的Scope,Bean的生命周期由对应的 Scope 实现类决定
除此之外 @Scope 中还有一个重要的参数 proxyMode , @Scope 修饰的Bean为什么还需要代理?
这里以SessionScope为例( WebApplicationContext.SCOPE_SESSION ),Bean在Session创建的时候生成、在Session销毁的时候销毁
@Service @Scope(scopeName = WebApplicationContext.SCOPE_SESSION) public class SessionScopeService { public String hello() { return "Hello"; } }
如果此时需要在Controller中注入该SessionScopeService会发生什么?
@Controller public class ScopesController { @Autowired SessionScopeService sessionScopeService; }
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionScopeService': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton;
在Spring初始化过程中需要注入SessionScopeService时,Session并没有生成(没有请求进来),无法创建SessionScopeService类型的Bean,为了解决此问题可以为SessionScopeService设置scope proxyMode,提前生成一个代理类(由ScopedProxyFactoryBean生成),该代理类会延迟目标Bean的创建(延迟getBean的调用)
如果在注入之后立即调用方法会发生什么?
@RestController public class TestController { private SessionScopeService sessionScopeService; @Autowired public void setSessionScopeService(SessionScopeService sessionScopeService) { this.sessionScopeService = sessionScopeService; sessionScopeService.hello(); } }
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.sessionScopeService': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton;
设置@Scope的proxyMode可以解决Spring初始化过程中提前注入的问题,但 <u>使用还是要在Scope的生命周期内</u>
@Scope的使用示例可以参见Quick Guide to Spring Bean Scopes
AnnotatedBeanDefinitionReader可以将指定的一个或者多个class注册到容器中(不要求目标class被构造形注解修饰),而 ClassPathBeanDefinitionScanner 可以在指定的packages中扫描所有被构造形注解修饰的class并注册到容器中
构造形注解:@Component、@ManagedBean、@Named或被以上注解修饰(以元注解形式存在)的注解
ClassPathBeanDefinitionScanner的逻辑较为简单(如上图),不再做过多的展开,上图中高亮的部分的细节在 AnnotatedBeanDefinitionReader 一节中都有详细介绍
至此, AnnotatedBeanDefinitionReader 及 ClassPathBeanDefinitionScanner 仅仅完成了bean的注册,对于配置类(@Configuration)的解析,@Bean、@Import、@PropertySource等注解的处理并没有在以上逻辑中完成
带着以上疑问,我们来看 AnnotationConfigUtils 都做了什么
AnnotationConfigUtils#registerAnnotationConfigProcessors并没有十分特别的逻辑,只是注册了几个Bean
对于 AutowiredAnnotationBeanPostProcessor 及 CommonAnnotationBeanPostProcessor 是否还有印象?在Spring Framwork 3 Bean是如何被创建的一文中有介绍 @Autowoired、@Value、@Inject及@Resource、@WebServiceRef、@EJB等注解的解析逻辑,但在该文中仅介绍了具体的解析过程,并没有介绍以上两个处理的注册逻辑
EventListenerMethodProcessor及 DefaultEventListenerFactory ,借助 SmartInitializingSingleton (Spring Framework 4 ApplicationContext给开发者提供了哪些(默认)扩展一问中有介绍)在所有Singleton Beans被初始化后,寻找 @EventListener 注解修饰的方法,并将其封装为 ApplicationListener 添加到 ApplicationContext 的 applicationListeners 中(ApplicationListener的处理见Spring Framework 4 ApplicationContext给开发者提供了哪些(默认)扩展)
接下来重点分析 ConfigurationClassPostProcessor (处理配置类 @Configuration、@Bean、@Import等)
由上了解到,配置类的处理在 ConfigurationClassPostProcessor 中,其实现了 BeanDefinitionRegistryPostProcessor (BeanDefinitionRegistryPostProcessor的触发时机在Spring Framework 4 ApplicationContext给开发者提供了哪些(默认)扩展一文有介绍),需要关注其中的两个方法 ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry 及 ConfigurationClassPostProcessor#postProcessBeanFactory,其均在BeanDefinition注册完成后且Bean实例化之前执行
该步骤的流程图如下,看似繁琐,但主要的只有三点
三种情况会将已注册的BeanDefinition定义为配置类
但只有被@Configuration修饰且将proxyBeanMethods设置为true才会标记为CONFIGURATION_CLASS_FULL,其余则会被标记为CONFIGURATION_CLASS_LITE,关于这两种标记的作用在下文会做解释
在配置类中,会依次对以下注解进行解析
对于 CONFIGURATION_CLASS_FULL
及 CONFIGURATION_CLASS_LITE
的解释会在下文
所以对配置类的解析及Bean注册主要分为两部分
接下来对配置类相关的各种注解解析及注册逻辑依次进行展开
(以下,Spring在查找注解的时候,均会在所有父子类中进行查找)
Spring会获取目标类上所有 @PropertySource 中的 value ,并得到所有对应的的Resource,将其封装为PropertySource后逐一添加到Environment的propertySources中
这里并没有对 ConfigurationClassParser#addPropertySource 中的逻辑详细展开,感兴趣的可以查看Spring源码,会对Environment中的propertySources的结构会有比较深的理解
Spring对@ComponentScan的解析本质上使用了 ClassPathBeanDefinitionScanner ,对于 ClassPathBeanDefinitionScanner 的介绍见上文
@ComponentScan注解中的参数可以对 ClassPathBeanDefinitionScanner 进行一些定制,比较常用的有
@Import注解接受三种类型的class, ImportSelector 、 ImportBeanDefinitionRegistrar 、 普通配置类(ConfigurationClass)
ImportSelector的作用在于返回需要import的类 importClasses ( ImportSelector#selectImports ),之后会递归调用 processImports 方法依次解析 importClasses ,所以 ImportSelector#selectImports 可以返回 ImportSelector 、 ImportBeanDefinitionRegistrar 、 ConfigurationClass 三种中的任意类型
ImportSelector的子类,对于该类型会被加入到 ConfigurationClassParser#deferredImportSelectorHandler 的 deferredImportSelectors 中,延迟到配置类中所有注解解析完成后且注解配置的Bean注册前执行( ConfigurationClassParser#deferredImportSelectorHandler.process() )
借助 ImportBeanDefinitionRegistrar 的 registerBeanDefinitions 方法可以直接动态的注册Bean,但注册的逻辑被放入 ConfigurationClassParser#importBeanDefinitionRegistrars 中,延迟到配置类中所有注解解析完成后执行( ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars )
对于不是以上两种情况的,会被当做配置类,递归调用 ConfigurationClassParser#processConfigurationClass 进行配置类的解析,除此之外通过@Import导入的ConfigurationClass均会被注册为Bean,无论目标类是否被构造形注解修饰
所以对于 ImportSelector 及 ConfigurationClass 都会以递归的方式直接解析,对于 DeferredImportSelector 及 ImportBeanDefinitionRegistrar 则会被暂存起来延迟执行
DeferredImportSelector 的处理
DeferredImportSelector的处理逻辑与 ImportSelector 类似,只是会延迟执行
ImportBeanDefinitionRegistrar 的处理
这里没有特殊的逻辑,会依次调用解析到的 ImportBeanDefinitionRegistrar 的 registerBeanDefinitions 方法
![ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars](/img/bVbIL1u) #### @ImportResource 对于@ImportResource,Spring只是会做简单的解析并将注解中指定的locations暂存起来 @ImportResource中的locations指向需要加载的配置文件路径,如xml配置文件、groovy配置文件,甚至自定义的特殊的配置文件格式(需要同时配置@ImportResource中的reader,用于指定解析配置文件的BeanDefinitionReader) ![Parse_ImportResource](/img/bVbIL1b) 具体的配置文件的解析加载逻辑在**ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromImportedResources** ![ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromImportedResources](/img/bVbIL1c) 逻辑较为简单,不再细解 #### @Bean @Bean主要用在配置类的方法上,方法必须返回一个用于注册的Bean实体,方法参数可以自动注入,可以使用@Qualifier等注解 所以,@Bean方法注册Bean的方式是不是跟`factory method`方式注册Bean非常相似(见[Spring Framwork 3 Bean是如何被创建的][Spring Framwork 3 Bean是如何被创建的] )?事实上也是如此 ![Parse_Bean](/img/bVbIL1e) @Bean的解析逻辑也简单到不用多言,@Bean的注册逻辑在**ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod** ![ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod](/img/bVbIL1p) 以上逻辑也并不复杂,主要使用了BeanDefinition中的`factory-method`,对于静态方法会设置`bean-class`,对于非静态方法会设置`factory-bean`,具体factory-method的相关介绍见[Spring Framwork 3 Bean是如何被创建的][Spring Framwork 3 Bean是如何被创建的] ### ConfigurationClassPostProcessor#postProcessBeanFactory 至此,完成了所有配置类的解析及其相关BeanDefinition的注册 上文有提到配置类分`CONFIGURATION_CLASS_FULL`及`CONFIGURATION_CLASS_LITE`两种模式,只有被**@Configuration**修饰且将***proxyBeanMethods***设置为***true***才会标记为`CONFIGURATION_CLASS_FULL`,不同的模式有什么异同? 在**ConfigurationClassPostProcessor#postProcessBeanFactory**中存在两个逻辑,增强Configuration类;注册**ImportAwareBeanPostProcessor** **ImportAwareBeanPostProcessor**用于执行**ImportAware**接口实现的***setImportMetadata***方法,可用在类似通过**@Import**导入的Bean上,将原始Configuration类上的注解信息传入 以下着重介绍Configuration类的增强逻辑 ![enhanceConfigurationClasses](/img/bVbIL1q) 对于`CONFIGURATION_CLASS_FULL`模式的配置类,会使用ConfigurationClassEnhancer对配置类进行增强生成代理配置类并设置到原始BeanDefinition中,这就意味着在Bean的初始化阶段使用的是增强后的代理类 而对于`CONFIGURATION_CLASS_LITE`模式的配置类则不会做任何增强代理,在Bean的初始化阶段使用的是原始的目标配置类 为何需要对`CONFIGURATION_CLASS_FULL`模式(**@Configuration**修饰且将***proxyBeanMethods***设置为***true***)的配置类进行增强代理?Spring官方文档给出了一小段解释[Full @Configuration vs “lite” @Bean mode][Full @Configuration vs “lite” @Bean mode] >When `@Bean` methods are declared within classes that are not annotated with `@Configuration`, they are referred to as being processed in a “lite” mode. Bean methods declared in a `@Component` or even in a plain old class are considered to be “lite”, with a different primary purpose of the containing class and a `@Bean` method being a sort of bonus there. For example, service components may expose management views to the container through an additional `@Bean` method on each applicable component class. In such scenarios, `@Bean` methods are a general-purpose factory method mechanism. > >Unlike full `@Configuration`, lite `@Bean` methods cannot declare inter-bean dependencies. Instead, they operate on their containing component’s internal state and, optionally, on arguments that they may declare. Such a `@Bean` method should therefore not invoke other `@Bean` methods. Each such method is literally only a factory method for a particular bean reference, without any special runtime semantics. The positive side-effect here is that no CGLIB subclassing has to be applied at runtime, so there are no limitations in terms of class design (that is, the containing class may be `final` and so forth). > >In common scenarios, `@Bean` methods are to be declared within `@Configuration` classes, ensuring that “full” mode is always used and that cross-method references therefore get redirected to the container’s lifecycle management. This prevents the same `@Bean` method from accidentally being invoked through a regular Java call, which helps to reduce subtle bugs that can be hard to track down when operating in “lite” mode. 简单而言,`CONFIGURATION_CLASS_FULL`模式的配置类中,对同一@Bean方法的多次调用只会执行一次,且每次调用返回的Bean一致
public class MyComponent {
private static int initCount = 0; public MyComponent() { // 统计创建次数 MyComponent.initCount++; } public static int initCount() { return MyComponent.initCount }
}
public class MyServiceA {
private MyComponent myComponent; public MyServiceA(MyComponent myComponent) { this.myComponent = myComponent; System.out.println("In MyServiceA, MyCompoent Init Count " + myComponent.initCount + " times.") }
}
public class MyServiceB {
private MyComponent myComponent; public MyServiceB(MyComponent myComponent) { this.myComponent = myComponent; System.out.println("In MyServiceB, MyCompoent Init Count " + myComponent.initCount + " times.") }
}
@Configuration
public class MyConfiguration {
@Bean public MyComponent myComponent() { return new MyComponent(); } @Bean @DependsOn("myComponent") public MyServiceA myServiceA() { // 调用内部方法,myComponent方法只会调用一次 MyComponent myComp = myComponent(); // 输出 In MyServiceA, MyCompoent Init Count 1 times. return new MyServiceA(myComp); } @Bean @DependsOn("myServiceA") public MyServiceB myServiceB() { // 调用内部方法,myComponent方法只会调用一次 MyComponent myComp = myComponent(); // 输出 In MyServiceB, MyCompoent Init Count 1 times. return new MyServiceB(myComp); }
}
但是,如果将上述配置逻辑放入`CONFIGURATION_CLASS_LITE`模式中,对同一@Bean方法的多次调用则会实际多次执行,且每次调用返回的Bean均为不同的新对象
@Configuration(proxyBeanMethods = false)
public class MyConfiguration {
@Bean public MyComponent myComponent() { return new MyComponent(); } @Bean @DependsOn("myComponent") public MyServiceA myServiceA() { // 调用内部方法,myComponent方法每次都会执行 MyComponent myComp = myComponent(); // 输出 In MyServiceA, MyCompoent Init Count 2 times. return new MyServiceA(myComp); } @Bean @DependsOn("myServiceA") public MyServiceB myServiceB() { // 调用内部方法,myComponent方法每次都会执行 MyComponent myComp = myComponent(); // 输出 In MyServiceB, MyCompoent Init Count 3 times. return new MyServiceB(myComp); }
}
或
@Component
public class MyConfiguration {
@Bean public MyComponent myComponent() { return new MyComponent(); } @Bean public MyServiceA myServiceA() { // 调用内部方法,myComponent方法每次都会执行 MyComponent myComp = myComponent(); // 输出 In MyServiceA, MyCompoent Init Count 2 times. return new MyServiceA(myComp); } @Bean @DependsOn("myServiceA") public MyServiceB myServiceB() { // 调用内部方法,myComponent方法每次都会执行 MyComponent myComp = myComponent(); // 输出 In MyServiceB, MyCompoent Init Count 3 times. return new MyServiceB(myComp); }
}
但如果使用注入模式(非直接调用@Bean方法),则依旧遵循Spring的默认规则
@Component
public class MyConfiguration {
@Bean public MyComponent myComponent() { return new MyComponent(); } @Bean public MyServiceA myServiceA(MyComponent myComp) { // 输出 In MyServiceA, MyCompoent Init Count 1 times. return new MyServiceA(myComp); } @Bean @DependsOn("myServiceA") public MyServiceB myServiceB(MyComponent myComp) { // 输出 In MyServiceB, MyCompoent Init Count 1 times. return new MyServiceB(myComp); }
}
> 注:通过配置类进行Bean的注册,在执行逻辑上也是有明确先后顺序的 > > @Import(ImportSelector) -> @Import(DeferredImportSelector) -> @Import(ConfigClass) -> @Bean -> @ImportResource -> @Import(ImportBeanDefinitionRegistrar) # 小结 - Spring中的注解编程模型实现了注解类的“继承” - 基于注解的Bean装配可以通过**AnnotationConfigApplicationContext**或者**ClassPathXmlApplicationContext**中的`<context:annotation-config>`及`<context:component-scan>`标签来开启 - **ClassPathBeanDefinitionScanner**可以在指定的package中扫描Bean类并注册,支持所有被**@Component**、**@ManagedBean**、**@Named**或被以上注解修饰(以元注解形式存在)的构造形注解,如**@Component**、**@ManagedBean**、**@Named**、**@Repository**、**@Service**、**@Configuration**、**@Controller**、等等 - **@Conditional**注解用于在满足一定条件的情况下才对目标类进行Bean注册,通常会以 *Annotation + Condition*的形式出现,可以在**org.springframework.boot.autoconfigure.condition**包中查看更多示例 - 三种情况会将Bean定义为配置类 - 被**@Configuration**修饰 - 被**@Components**、**@ComponentScan**、**@Import**、**@ImportResource**任意修饰 - 存在被**@Bean**修饰的方法 - 配置类中支持的注解处理包括**@PropertySource**、**@ComponentScan**、**@Import**、**@ImportResource**、**@Bean** - **@Import**支持三种类型的class,***ImportSelector***、***ImportBeanDefinitionRegistrar***、***普通配置类(ConfigurationClass)*** [Are annotations better than XML for configuring Spring?]: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-annotation-config [Spring Annotation Programming Model]: https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model [Spring Framework 2 BeanDefinitionReader]: https://segmentfault.com/a/1190000021458901 [Quick Guide to Spring Bean Scopes]: https://www.baeldung.com/spring-bean-scopes [Spring Framwork 3 Bean是如何被创建的]: https://segmentfault.com/a/1190000022309143 [Spring Framework 4 ApplicationContext给开发者提供了哪些(默认)扩展]: https://segmentfault.com/a/1190000022425759 [Full @Configuration vs “lite” @Bean mode]: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-java-basic-concepts ----------