修炼内功springframework-7-Spring-Framework中的注解是如何运作的

38次阅读

共计 21432 个字符,预计需要花费 54 分钟才能阅读完成。

本文已收录【修炼内功】跃迁之路

微信搜索 林中小舍,林小二带你聊技术


截止本篇,已经介绍了 Spring 中的 Resource、BeanDefinitionReader、BeanFactory、ApplicationContext、AOP 等,本篇重点介绍 Spring Framework 中基于注解的 Bean 装配原理

注解的使用大大简化了配置的过程,也更能表现代码与配置之间的直接关系,但同时也失去了部分配置统一管理的能力,对代码也有一定的侵入性

这似乎并不影响开发者对注解使用的高涨热情,使用注解还是 XML 进行配置开发并没有统一的定论,以何种方式进行开发还是需要看具体的项目适合什么

Are annotations better than XML for configuring Spring?

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 提供了众多配置类注解,但不知道各位在使用过程中是否有类似于如下的疑问

  1. @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 {...}
  2. @Service中的 @AliasFor 是做什么用的?为什么设置 @Service 的 value,效果与直接设置 @Component 的 value 是一样的(都可以指定 bean-name)?

    @Service("myService")
    public class MyService {...}
    
    @Component("myService")
    pulbic class MyService {...}
  3. 自定义注解 @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

Spring 中的注解编程模型

元注解(Meta Annotations)

用于修饰其他注解的注解,如 @Service 中的 @Documented@Component 等,这里完全可以将元注解理解为被修饰注解的“父类”

构造形注解(Stereotype Annotations)

用于声明 Spring 组件的注解,如 @Component@Service@Repository@Controller 等等,仔细观察的话会发现,除了 @Component 之外,所有直接 / 间接被 @Component 修饰的注解均为构造形注解(均可用于声明 Spring 组件),如果将这种关系看做是继承就比较容易理解了

组合注解(Composed Annotations)

同时被多个(元)注解修饰,同时实现多个注解的能力,如上例中提到的 @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) 将上述提到的元注解、组合注解、属性的别名及重写等信息整合,实现了一套类似注解类继承的机制,以此来提高注解复用、简化开发

基于注解的 Bean 装配

如何启用

如何让 Spring 识别到注解并注册 / 装配?

基于 ClassPathXmlApplicationContext

以 xml 配置的方式使用 Spring 时,会有两个标签 <context:annotation-config><context:component-scan>来开启注解的识别

前者用于注册一些处理器,用于处理容器中已注册 Bean 上的注解,如配置类 @Configuration@Import@Autowired

后者用于扫描指定 package 中被构造形注解(@Component及所有直接 / 间接被 @Component 修饰的注解)修饰的类,并将其注册到容器中,除此之外 <context:component-scan> 还会同时完成 <context:annotation-config> 的功能

两种配置的解析器分别对应 AnnotationConfigBeanDefinitionParserComponentScanBeanDefinitionParser,其分别在 ContextNamespaceHandler 中注册
xml 自定义标签的逻辑在 Spring Framework 2 BeanDefinitionReader 中有介绍

  • AnnotationConfigBeanDefinitionParser会调用 AnnotationConfigUtils.registerAnnotationConfigProcessors 完成各种处理器的注册
  • ComponentScanBeanDefinitionParser会创建一个 ClassPathBeanDefinitionScanner,并调用其doScan 方法完成构造形注解修饰类的发现并注册,同时会调用 AnnotationConfigUtils.registerAnnotationConfigProcessors 完成各种处理器的注册

基于 AnnotationConfigApplicationContext

AnnotationConfigApplicationContext会创建两种注册器 AnnotatedBeanDefinitionReaderClassPathBeanDefinitionScanner

前者用于指定 BeanClass(es)进行注册,后者用于完成构造形注解修饰类的发现并注册,同时两者均会调用 AnnotationConfigUtils.registerAnnotationConfigProcessors 完成各种处理器的注册

基于注解的 Bean 发现 & 注册

至此可以看到,基于注解的 Bean 发现、注册、装配集中在三个地方

  • AnnotatedBeanDefinitionReader#register
  • ClassPathBeanDefinitionScanner#scan
  • AnnotationConfigUtils#registerAnnotationConfigProcessors

AnnotatedBeanDefinitionReader

AnnotatedBeanDefinitionReader可以指定一个或者多个 class 进行注册,具体的注册逻辑在AnnotatedBeanDefinitionReader#registerBean,其大体的流程为

  1. 将目标 class 封装为 AnnotatedGenericBeanDefinition
  2. 根据 @Condition 注解判断是否忽略
  3. 依据目标 class 上的其他注解设置 BeanDefinition 的属性
  4. 将 BeanDefinition 注册到容器中

流程图中每一个被框起来的部分都是一个比较独立的逻辑块,下文中会频繁出现(以下不再对各逻辑块的细节进行展开),这里需要对其中的几个逻辑块展开讨论下

@Conditional

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 中可以获取 BeanDefinitionRegistryConfigurableListableBeanFactoryEnvironmentResourceLoaderClassLoader 等信息,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 作为元注解,并指定 ConditionProfileCondition,同时 ProfileCondition 又通过 @Profile 中的参数来判断环境是否匹配,@ProfileProfileCondition 相辅相成

类似的,在 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

@Scope

在 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

ClassPathBeanDefinitionScanner

AnnotatedBeanDefinitionReader可以将指定的一个或者多个 class 注册到容器中(不要求目标 class 被构造形注解修饰),而 ClassPathBeanDefinitionScanner 可以在指定的 packages 中扫描所有被构造形注解修饰的 class 并注册到容器中

构造形注解:@Component、@ManagedBean、@Named 或被以上注解修饰(以元注解形式存在)的注解

ClassPathBeanDefinitionScanner的逻辑较为简单(如上图),不再做过多的展开,上图中高亮的部分的细节在 AnnotatedBeanDefinitionReader 一节中都有详细介绍

至此,AnnotatedBeanDefinitionReaderClassPathBeanDefinitionScanner 仅仅完成了 bean 的注册,对于配置类(@Configuration)的解析,@Bean、@Import、@PropertySource 等注解的处理并没有在以上逻辑中完成

带着以上疑问,我们来看 AnnotationConfigUtils 都做了什么

AnnotationConfigUtils

AnnotationConfigUtils#registerAnnotationConfigProcessors并没有十分特别的逻辑,只是注册了几个 Bean

  • ConfigurationClassPostProcessor
  • AutowiredAnnotationBeanPostProcessor
  • CommonAnnotationBeanPostProcessor
  • PersistenceAnnotationBeanPostProcessor(如果 classpath 中存在 javax.persistence.EntityManagerFactory
  • EventListenerMethodProcessor
  • DefaultEventListenerFactory

对于 AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor是否还有印象?在 Spring Framwork 3 Bean 是如何被创建的一文中有介绍 @Autowoired、@Value、@Inject 及 @Resource、@WebServiceRef、@EJB 等注解的解析逻辑,但在该文中仅介绍了具体的解析过程,并没有介绍以上两个处理的注册逻辑

EventListenerMethodProcessorDefaultEventListenerFactory,借助SmartInitializingSingleton(Spring Framework 4 ApplicationContext 给开发者提供了哪些(默认) 扩展一问中有介绍)在所有 Singleton Beans 被初始化后,寻找 @EventListener 注解修饰的方法,并将其封装为 ApplicationListener 添加到 ApplicationContextapplicationListeners中(ApplicationListener 的处理见 Spring Framework 4 ApplicationContext 给开发者提供了哪些 (默认) 扩展)

接下来重点分析ConfigurationClassPostProcessor(处理配置类 @Configuration、@Bean、@Import 等)

配置类的处理

由上了解到,配置类的处理在 ConfigurationClassPostProcessor 中,其实现了 BeanDefinitionRegistryPostProcessor(BeanDefinitionRegistryPostProcessor 的触发时机在 Spring Framework 4 ApplicationContext 给开发者提供了哪些(默认) 扩展一文有介绍),需要关注其中的两个方法 ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry 及 ConfigurationClassPostProcessor#postProcessBeanFactory,其均在 BeanDefinition 注册完成后且 Bean 实例化之前执行

ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

ConfigurationClassPostProcessor#processConfigBeanDefinitions

该步骤的流程图如下,看似繁琐,但主要的只有三点

  • 三种情况会将已注册的 BeanDefinition 定义为配置类

    • 被 @Configuration 修饰
    • 被 @Components、@ComponentScan、@Import、@ImportResource 任意修饰
    • 存在被 @Bean 修饰的方法
但只有被 @Configuration 修饰且将 proxyBeanMethods 设置为 true 才会标记为 CONFIGURATION_CLASS_FULL,其余则会被标记为 CONFIGURATION_CLASS_LITE,关于这两种标记的作用在下文会做解释
  • 在配置类中,会依次对以下注解进行解析

    • @PropertySource(s)
    • @ComponentScan(s)
    • @Import
    • @ImportResource
    • @Bean
  • 同时会递归父类进行上述解析
  • 对上述注解解析完成之后,会依次按照注解的内容对 BeanDefinition 进行注册

对于 CONFIGURATION_CLASS_FULLCONFIGURATION_CLASS_LITE的解释会在下文

所以对配置类的解析及 Bean 注册主要分为两部分

  • 图中蓝色部分,仅解析注解的内容
  • 如钟红色部分,对注解解析出来的内容进行 BeanDefinition 的注册

接下来对配置类相关的各种注解解析及注册逻辑依次进行展开

(以下,Spring 在查找注解的时候,均会在所有父子类中进行查找)

@PropertySource

Spring 会获取目标类上所有 @PropertySource 中的value,并得到所有对应的的 Resource,将其封装为 PropertySource 后逐一添加到 Environment 的 propertySources 中

这里并没有对 ConfigurationClassParser#addPropertySource 中的逻辑详细展开,感兴趣的可以查看 Spring 源码,会对 Environment 中的 propertySources 的结构会有比较深的理解

@ComponentScan

Spring 对 @ComponentScan 的解析本质上使用了 ClassPathBeanDefinitionScanner,对于ClassPathBeanDefinitionScanner 的介绍见上文

@ComponentScan注解中的参数可以对 ClassPathBeanDefinitionScanner 进行一些定制,比较常用的有

  • userDefaultFilters 可以决定是否配置默认的 TypeFilter,对于默认的 TypeFilter 注册见上文(ClassPathScanningCandidateComponentProvider#registerDefaultFilters
  • includeFilters 及 excludeFilters 可以添加自己的包含及排除 TypeFilter
  • 扫描 packages 可以通过 basePackages 及 basePackageClasses 指定,对于后者 Spring 会转换为指定 classes 所在的 packages
  • 如果没有指定任何扫描 packages,Spring 则会使用目标 class 所在的 package

@Import

@Import 注解接受三种类型的 class,ImportSelectorImportBeanDefinitionRegistrar普通配置类(ConfigurationClass)

  • ImportSelector

    ImportSelector的作用在于返回需要 import 的类 importClassesImportSelector#selectImports),之后会递归调用processImports 方法依次解析 importClasses,所以ImportSelector#selectImports 可以返回 ImportSelectorImportBeanDefinitionRegistrarConfigurationClass 三种中的任意类型

    • DeferredImportSelector

      ImportSelector的子类,对于该类型会被加入到 ConfigurationClassParser#deferredImportSelectorHandlerdeferredImportSelectors中,延迟到配置类中所有注解解析完成后且注解配置的 Bean 注册前执行(ConfigurationClassParser#deferredImportSelectorHandler.process()

  • ImportBeanDefinitionRegistrar

    借助 ImportBeanDefinitionRegistrarregisterBeanDefinitions方法可以直接动态的注册 Bean,但注册的逻辑被放入 ConfigurationClassParser#importBeanDefinitionRegistrars 中,延迟到配置类中所有注解解析完成后执行(ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars

  • ConfigurationClass

    对于不是以上两种情况的,会被当做配置类,递归调用 ConfigurationClassParser#processConfigurationClass 进行配置类的解析,除此之外通过 @Import 导入的 ConfigurationClass 均会被注册为 Bean,无论目标类是否被构造形注解修饰

所以对于 ImportSelectorConfigurationClass都会以递归的方式直接解析,对于 DeferredImportSelectorImportBeanDefinitionRegistrar则会被暂存起来延迟执行

DeferredImportSelector 的处理

DeferredImportSelector的处理逻辑与 ImportSelector 类似,只是会延迟执行

ImportBeanDefinitionRegistrar 的处理

这里没有特殊的逻辑,会依次调用解析到的 ImportBeanDefinitionRegistrarregisterBeanDefinitions方法

![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



----------

正文完
 0