【修炼内功】[spring-framework] [7] Spring Framework中的注解是如何运作的

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

spring-framework.jpg

林中小舍.png

微信关注“林中小舍”,林小二带你聊技术!

截止本篇,已经介绍了Spring中的ResourceBeanDefinitionReaderBeanFactoryApplicationContextAOP等,本篇重点介绍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

1. Spring中的注解编程模型

1.1 元注解(Meta Annotations)

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

1.2 构造形注解(Stereotype Annotations)

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

1.3 组合注解(Composed Annotations)

同时被多个(元)注解修饰,同时实现多个注解的能力,如上例中提到的@TransactionalService,等效于同时使用@Transactional@Service

1.4 属性别名及重写

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)将上述提到的元注解、组合注解、属性的别名及重写等信息整合,实现了一套类似注解类继承的机制,以此来提高注解复用、简化开发

ClassAnnotation

2. 基于注解的Bean装配

2.1 如何启用

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

2.1.1 基于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完成各种处理器的注册

2.1.2 基于AnnotationConfigApplicationContext

AnnotationConfigApplicationContext会创建两种注册器 AnnotatedBeanDefinitionReaderClassPathBeanDefinitionScanner

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

2.2 基于注解的Bean发现&注册

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

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

2.2.1 AnnotatedBeanDefinitionReader

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

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

registerBean

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

2.2.1.1 @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

2.2.1.2 @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

2.2.2 ClassPathBeanDefinitionScanner

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

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

scanBean

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

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

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

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

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

2.3 配置类的处理

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

2.3.1 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进行注册

processConfigBeanDefinitions

对于CONFIGURATION_CLASS_FULLCONFIGURATION_CLASS_LITE的解释会在下文

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

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

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

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

2.3.1.1 @PropertySource

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

Parse_PropertySource

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

2.3.1.2@ComponentScan

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

Parse_ComponentScan

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

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

2.3.1.3 @Import

Parse_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类似,只是会延迟执行

DeferredImportSelectorHandler.process

ImportBeanDefinitionRegistrar 的处理

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

ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars

2.3.1.4 @ImportResource

对于@ImportResource,Spring只是会做简单的解析并将注解中指定的locations暂存起来

@ImportResource中的locations指向需要加载的配置文件路径,如xml配置文件、groovy配置文件,甚至自定义的特殊的配置文件格式(需要同时配置@ImportResource中的reader,用于指定解析配置文件的BeanDefinitionReader)

Parse_ImportResource

具体的配置文件的解析加载逻辑在ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromImportedResources

ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromImportedResources

逻辑较为简单,不再细解

2.3.1.5 @Bean

@Bean主要用在配置类的方法上,方法必须返回一个用于注册的Bean实体,方法参数可以自动注入,可以使用@Qualifier等注解

所以,@Bean方法注册Bean的方式是不是跟factory method方式注册Bean非常相似(见Spring Framwork 3 Bean是如何被创建的 )?事实上也是如此

Parse_Bean

@Bean的解析逻辑也简单到不用多言,@Bean的注册逻辑在ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod

ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod

以上逻辑也并不复杂,主要使用了BeanDefinition中的factory-method,对于静态方法会设置bean-class,对于非静态方法会设置factory-bean,具体factory-method的相关介绍见Spring Framwork 3 Bean是如何被创建的

2.3.2 ConfigurationClassPostProcessor#postProcessBeanFactory

至此,完成了所有配置类的解析及其相关BeanDefinition的注册

上文有提到配置类分CONFIGURATION_CLASS_FULLCONFIGURATION_CLASS_LITE两种模式,只有被@Configuration修饰且将proxyBeanMethods设置为true才会标记为CONFIGURATION_CLASS_FULL,不同的模式有什么异同?

ConfigurationClassPostProcessor#postProcessBeanFactory中存在两个逻辑,增强Configuration类;注册ImportAwareBeanPostProcessor

ImportAwareBeanPostProcessor用于执行ImportAware接口实现的setImportMetadata方法,可用在类似通过@Import导入的Bean上,将原始Configuration类上的注解信息传入

以下着重介绍Configuration类的增强逻辑

enhanceConfigurationClasses

对于CONFIGURATION_CLASS_FULL模式的配置类,会使用ConfigurationClassEnhancer对配置类进行增强生成代理配置类并设置到原始BeanDefinition中,这就意味着在Bean的初始化阶段使用的是增强后的代理类

而对于CONFIGURATION_CLASS_LITE模式的配置类则不会做任何增强代理,在Bean的初始化阶段使用的是原始的目标配置类

为何需要对CONFIGURATION_CLASS_FULL模式(@Configuration修饰且将proxyBeanMethods设置为true)的配置类进行增强代理?Spring官方文档给出了一小段解释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)

3. 小结

  • 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,ImportSelectorImportBeanDefinitionRegistrar普通配置类(ConfigurationClass)

微信

知乎

阅读 176

推荐阅读
林中小舍
用户专栏

工作中的坑点及经验

51 人关注
41 篇文章
专栏主页