全方位解析Spring IoC:(四)Bean

由于掘金文章存在字数限制,本文拆开了各个章节分开发布,全文可点击以下链接进行阅读:blog.omghaowan.com/archives/sp...

通过上述步骤即把配置读取到容器中,然后容器会再进一步生成相应的bean实例。无论是通过哪种方式,对于注册到容器中的bean都会被定义为BeanDefinition,其主要包含以下属性:

属性 描述
Class Class属性表示当前bean的类型。
Name Name属性表示当前bean的名称。
Scope Scope属性表示当前bean的作用域。
Constructor arguments Constructor arguments属性表示当前bean的构造参数。
Properties Properties属性表示当前bean的属性。
Autowiring mode Autowiring mode属性表示当前bean的自动装配模式。
Lazy initialization mode Lazy initialization mode属性表示当前bean是否延迟初始化。
Initialization method Initialization method属性表示当前bean的初始化方法。
Destruction method Destruction method属性表示当前bean的销毁方法。

关于BeanDefinition,它主要有三种实现,分别是RootBeanDefinitionChildBeanDefinitionGenericBeanDefinition:

BeanDefinition实现 描述
RootBeanDefinition RootBeanDefinition是一个标准的BeanDefinition,在配置阶段中可作为一个独立的BeanDefinition
ChildBeanDefinition ChildBeanDefinition是一个具有继承关系的BeanDefinition,它会继承父BeanDefinition的属性和覆盖父BeanDefinition方法。例如,对于init方法、destroy方法和静态工厂方法ChildBeanDefinition是会覆盖继承的父BeanDefinition(如有),而depends onautowire modedependency checksingletonlazy init等属性则是直接忽略父BeanDefinition,直接以ChildBeanDefinition的为准。
GenericBeanDefinition GenericBeanDefinition是一个具有一站式功能的标准BeanDefinition。除了与其他BeanDefinition具有相同的功能外,它还能通过parentName属性灵活/动态地设置其父BeanDefinition。在大多数场景中,GenericBeanDefinition都可以有效地替代ChildBeanDefinition

Spring 2.5引入了GenericBeanDefinition后,我们应该将GenericBeanDefinition作为以程序方式注册bean的首选类。因为通过GenericBeanDefinition我们可以动态地定义父BeanDefinition(通过parentName属性),而不是将角色"硬编码"为RootBeanDefinition或者ChildBeanDefinition。当然,如果能提前确定父/子关系的话,我们也可以使用RootBeanDefinition/ChildBeanDefinition

这样,IoC容器完成了将Metadata配置转换为(含以上属性(不限于))BeanDefinition的过程。在完成转换后,IoC容器就可以根据BeanDefinition的定义来创建并初始化bean实例了。

不难看出,BeanDefinition实际上类似于Class,我们对bean的所有配置都已经定义到BeanDefinition里,然后再通过BeanDefinition来创建对象实例。

Bean实例化

对于Bean的实例化,我们可以通过构造器和工厂方法两种方式进行创建,而不论是构造器还是工厂方法都可以通过XMLJava进行声明。

一般,IoC容器会通过BeanDefinition中的class属性(强制)来找到需要实例化的类。但是,class属性所标识的类不一定就是需要实例化的bean,我们可以将它看作是用于实例化bean的类,可以表示bean本身的类型,也可以表示bean工厂方法所在的类。

通过构造器实例化

通过XML&&构造器配置实例

XML配置中,如果没有特别指定,容器就会通过构造器来创建bean实例。

xml 复制代码
<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
通过Java&&构造器配置实例

Java配置中,通过@Configuration@Component注解及其衍生注解(典型的有: @Controller@Service@Repository)声明的类会被当作bean注册到容器中。对于这种两种方式,IoC也会使用它们的构造器来创建bean实例。

java 复制代码
@Configuration
public class MyConfig {}

@Configuration一般用于标识bean定义的来源类。

java 复制代码
@Component
public class MyComponent {}

@Component一般用于标识Spring的通用组件类。为了起到区分作用,Spring通过元注解组合@Component的方式衍生出三个更专用的注解,分别是持久层的@Repository、服务层的@Service 和表示层的@Controller。通过这种方式的区分,我们可以对业务层级进行更针对性地处理,例如对不同层级作增强处理(AOP)。另外,官方可能也会在未来的发行版中对相应的注解添加额外的语义(例如,对于@Repository注解提供持久层异常自动转换的支持(已支持))。

通过工厂方法实例化

通过XML&&工厂方法配置实例

XML配置中,我们可以在<bean>标签中设置factory-method属性来指定创建bean实例的工厂方法,其中工厂方法又分为静态工厂方法和实例工厂方法。

  • 静态工厂方法

    对于静态工厂方法,我们需要在<bean>标签中设置class属性来指定工厂方法的所在类,并通过factory-method属性来指定工厂方法。

    xml 复制代码
    <bean id="clientService"
        class="examples.ClientService"
        factory-method="createInstance"/>
    java 复制代码
    public class ClientService {
        private static ClientService clientService = new ClientService();
        private ClientService() {}
    
        public static ClientService createInstance() {
            return clientService;
        }
    }
  • 实例工厂方法

    对于实例工厂方法,我们需要在<bean>标签中设置factory-bean属性来指定bean实例,并通过factory-method属性来指定工厂方法(注意,class属性需留空)。

    上述使用实例工厂方法创建bean实例,可以简单的概括为使用现有bean实例的非静态方法来创建bean实例。

    xml 复制代码
    <!-- the factory bean, which contains a method called createInstance() -->
    <bean id="serviceLocator" class="examples.DefaultServiceLocator">
        <!-- inject any dependencies required by this locator bean -->
    </bean>
    
    <!-- the bean to be created via the factory bean -->
    <bean id="clientService"
        factory-bean="serviceLocator"
        factory-method="createClientServiceInstance"/>
    java 复制代码
    public class DefaultServiceLocator {
    
        private static ClientService clientService = new ClientServiceImpl();
    
        public ClientService createClientServiceInstance() {
            return clientService;
        }
    }

    通过这种方式,我们也可以在一个工厂类中声明多个实例工厂方法,如下例所示:

    xml 复制代码
    <bean id="serviceLocator" class="examples.DefaultServiceLocator">
        <!-- inject any dependencies required by this locator bean -->
    </bean>
    
    <bean id="clientService"
        factory-bean="serviceLocator"
        factory-method="createClientServiceInstance"/>
    
    <bean id="accountService"
        factory-bean="serviceLocator"
        factory-method="createAccountServiceInstance"/>
    java 复制代码
    public class DefaultServiceLocator {
    
        private static ClientService clientService = new ClientServiceImpl();
    
        private static AccountService accountService = new AccountServiceImpl();
    
        public ClientService createClientServiceInstance() {
            return clientService;
        }
    
        public AccountService createAccountServiceInstance() {
            return accountService;
        }
    }
通过Java&&工厂方法配置实例

对于Java配置的方式(使用工厂方法来创建bean实例),在Spring官方文档中并没有明确展开。但,实际上笔者在阅读源码时发现了通过Java的方式也能使用工厂方法来创建bean实例,并且它也区分了静态工厂方法和实例工厂方法。

  • 实例工厂方法

    Java配置的方式中,Spring会把@Configuration@Component注解类中的标识了@Bean的实例方法当作是实例工厂方法来处理。即,使用这种方式创建的bean实例都是通过实例工厂方法来完成的。

    java 复制代码
    @Configuration
    public class MyConfig {
        @Bean
        public MyService myService() {
            return new MyService();
        }
    }
    java 复制代码
    @Component
    public class MyComponent {
        @Bean
        public MyService myService() {
            return new MyService();
        }
    }
  • 静态工厂方法

    与实例工厂方法类似,Spring会把@Configuration@Component注解类中的标识了@Bean的静态方法当作是静态工厂方法来处理。即,使用这种方式创建的bean实例都是通过静态工厂方法来完成的。

    java 复制代码
    @Configuration
    public class MyConfig {
        @Bean
        public static MyService myService() {
            return new MyService();
        }
    }
    java 复制代码
    @Component
    public class MyComponent {
        @Bean
        public static MyService myService() {
            return new MyService();
        }
    }

抛开静态工厂方法与实例工厂方法的术语,对于上述通过@Bean来声明bean实例的方式有两个关键的区别:

  1. @Configuration类与@Component类中声明@Bean方法的区别

    @Bean方法声明在@Configuration类中时,我们称之为Full模式;而当@Bean方法声明在@Component类中时,我们称之为Lite模式。在Full模式下,@Bean方法之间的相互调用会被重定向到IoC容器中,从而避免bean实例的重复创建,这也被称为inter-bean references;而在Lite模式下,@Bean方法之间的相互调用并不支持inter-bean references,它们之间的相互调用仅仅会被当作是普通的工厂方法调用(标准的Java调用),即会再次创建对象实例。

    需要注意,因为Full模式是通过CGLIB动态代理的方式来完成的,所以在配置@Bean工厂方法时不能将其设置为privatefinal。与此相反,Lite模式由于不需要使用CGLIB动态代理,不但去除了这样的限制,而且也降低了服务的启动时间和加载时间。

    对于Lite模式,除了可以将@Bean方法声明在@Component注解类中实现外,我们还可以使用@Component@ComponentScan@Import@ImportResource或者将@Bean方法声明在普通Java类(无任何注解标注)中来实现。而在Spring5.2后新增了属性proxyBeanMethodsproxyBeanMethods表示是否开启代理,默认为true表示开启),当proxyBeanMethods=false时也表示Lite模式,即@Configuration(proxyBeanMethods = false)

    关于Full模式和Lite模式的更多详情可以阅读以下资料:

  2. 静态@Bean方法与实例@Bean方法的区别

    当使用静态@Bean方法时,它可以在它所在的@Configuration@Component类尚未实例化前完成实例化,这对我们通过@Bean来声明后置处理器(BeanFactoryPostProcessorBeanPostProcessor)时特别有用,因为这避免了实例化后置处理器时提前触发了其他部分的实例化从而导致不可预知的异常。

    需要注意,由于Full模式是通过CGLIB动态代理的方式来完成的(只对实例方法有效),所以对于@Bean静态方法的调用并不会被IoC容器所拦截(即使在@Configuration类中也不会),即对@Bean静态方法的调用仅仅被当作是标准的Java方法(直接返回一个独立的对象实例)。从另一方面来看,由于@Bean静态方法属于Lite模式,无需遵守Full模式下的相关限制(例如,不能是privatefinal),所以我们可以在任何我们觉得合适的地方声明@Bean静态方法。

    关于静态@Bean方法与实例@Bean方法的更多详情可阅读以下资料:

通过FactoryBean实例化

另外,对于一些构建起来比较复杂的bean实例我们可以通过FactoryBean来完成。对于FactoryBean,在使用上我们需要将它声明为bean,这样Spring就会将FactoryBean及其FactoryBean#getObject方法返回的对象都注册到IoC容器中。其中,对于FactoryBean指定或默认生成的名称是其FactoryBean#getObject所创建的bean实例名称,而FactoryBean实例本身的名称则需在指定或默认生成的名称前加上前缀&,即&beanName

java 复制代码
public class MyServiceFactoryBean implements FactoryBean {
    /**
     * 表示当前FactoryBean生成对象是否为单例 
     */
    @Override
    public boolean isSingleton() {
        return true;
    }

    /**
     * 表示当前FactoryBean生成对象类型
     */
    @Override
    public Class getObjectType() {
        return MyService.class;
    }

    /**
     * 方法返回FactoryBean需要创建的对象实例
     */
    @Override
    public Object getObject() {
        return new MyServiceImpl();
    }
}

需要注意,对于FactoryBean#isSingleton方法标识当前FactoryBean#getObject生成对象是否为单例的用法需要与FactoryBean实例本身的作用域结合使用。例如,如果需要令FactoryBean#getObject所生成的对象为单例作用域,那么我们不但需要让FactoryBean#isSingleton方法返回true,而且还需让FactoryBean实例本身作用域为单例作用域;否则Spring都会按照多例作用域Prototype来处理。

关于FactoryBean的更多细节可阅读以下资料:

Bean依赖

Spring中,我们可以使用依赖注入(dependency injection,简称DI)的方式定义bean依赖(推荐),这样IoC容器就会在创建bean的时候将这些依赖注入到实例中,即使用构造器参数、工厂方法参数或实例属性(对象实例化后被设置)对其依赖完成注入。

在软件工程中,依赖注入(dependency injection)是一种设计模式,通过分离对象构造和对象使用来实现程序上的松耦合,即一个对象(或方法)对于它所需要(依赖)的其它对象(或方法)应该采用接收的方式,而不是在内部创建它们。这样,对象(或方法)本身就不需要关注它所需要的对象(或方法)是如何构造的了。

关于DI的更多详情可阅读以下资料:

依赖注入

在使用上,我们可以通过构造器参数、工厂方法参数、Setter方法等完成bean的依赖注入。而由于构造器注入与工厂方法注入几乎等价,所以下面将注入方式分为两种,即基于构造器注入和基于Setter方法注入。

通过构造器注入依赖

对于构造器注入,我们首先需要将相应的依赖声明为构造器参数,即:

java 复制代码
public class SimpleBean {

    private final OneInjectBean oneInjectBean;
    private final TwoInjectBean twoInjectBean;

    public SimpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) {
        this.oneInjectBean = oneInjectBean;
        this.twoInjectBean = twoInjectBean;
    }
}

然后,我们再将对应的bean实例及其依赖进行标记,以让IoC容器完成实例化和依赖注入。下面我们分别从XML的方式和Java的方式展开描述。

通过XML&&构造器配置依赖

XML配置中,我们可以使用标签<constructor-arg>来标记构造参数及需要被注入的依赖。

xml 复制代码
<beans>
    <bean id="simpleBean" class="com.example.SimpleBean">
        <constructor-arg ref="oneInjectBean"/>
        <constructor-arg ref="twoInjectBean"/>
    </bean>

    <bean id="oneInjectBean" class="com.example.OneInjectBean"/>
    <bean id="twoInjectBean" class="com.example.TwoInjectBean"/>
</beans>

需要注意,此处仅使用了ref属性来引用对应的依赖是因为它们之间并没有引起歧义,如果参数之间存在歧义或者构造参数顺序与XML配置不一致时,就需要添加type属性或index属性。

更多详情可阅读:

通过Java&&构造器配置依赖

Java配置中,我们可以使用注解@Autowired@Inject来标记需要使用依赖注入的构造器。

java 复制代码
@Component
public class SimpleBean {

    private final OneInjectBean oneInjectBean;
    private final TwoInjectBean twoInjectBean;

    @Autowired
    public SimpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) {
        this.oneInjectBean = oneInjectBean;
        this.twoInjectBean = twoInjectBean;
    }
}

需要注意:

  • Spring4.3起,如果需要实例化的bean只定义了一个构造器,则可以不用在构造器上添加@Autowired注解或@Inject注解。但是,如果需要实例化的bean存在多个构造器,且其中没有默认构造器(无参),这时我们就需要至少在一个构造器上标记@Autowired注解。
  • Spring4.3起,Spring才开始支持@Configuration类的构造器注入。

而对于使用工厂方法来实例化bean(即通过@Bean注解标记的方法)则只需要在方法参数上指定需要注入的依赖即可。

java 复制代码
@Configuration
public class MyConfig {

    @Bean
    public SimpleBean simpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) {
        return new SimpleBean(oneInjectBean, twoInjectBean);
    }
}

更多详情可阅读:

通过Setter注入依赖

对于Setter方法注入,我们首先需要在bean中定义对应依赖的Setter方法,即:

java 复制代码
public class SimpleBean {

    private OneInjectBean oneInjectBean;
    private TwoInjectBean twoInjectBean;

    public void setOneInjectBean(OneInjectBean oneInjectBean) {
        this.oneInjectBean = oneInjectBean;
    }

    public void setTwoInjectBean(TwoInjectBean twoInjectBean) {
        this.twoInjectBean = twoInjectBean;
    }

}

这样,Spring就可以在bean实例化后通过调用对应的Setter方法来完成依赖注入。同样的,对于Setter方法的依赖注入也可以分为XMLJava两种方式进行配置,下面笔者将各自展开描述。

通过XML&&Setter配置依赖

XML配置中,我们可以在<bean>标签中添加<property>标签来标记需要注入的属性,这样IoC容器就会在bean实例化后调用属性相应的Setter方法完成依赖注入。

xml 复制代码
<beans>
    <bean id="simpleBean" class="com.example.SimpleBean">
        <property name="oneInjectBean" ref="oneInjectBean"/>
        <property name="twoInjectBean" ref="twoInjectBean"/>
    </bean>

    <bean id="oneInjectBean" class="com.example.OneInjectBean"/>
    <bean id="twoInjectBean" class="com.example.TwoInjectBean"/>
</beans>

与构造器注入类似,<property>标签也支持通过typevalue属性来指定对应的属性类型和属性值。

更多详情可阅读:

通过Java&&Setter配置依赖

Java配置中,我们可以使用注解@Autowired@Inject@Resource在属性对应的Setter方法上进行标记,以完成Setter方法的依赖注入。

java 复制代码
@Component
public class SimpleBean {

    private OneInjectBean oneInjectBean;
    private TwoInjectBean twoInjectBean;

    @Autowired
    public void setOneInjectBean(OneInjectBean oneInjectBean) {
        this.oneInjectBean = oneInjectBean;
    }

    @Autowired
    public void setTwoInjectBean(TwoInjectBean twoInjectBean) {
        this.twoInjectBean = twoInjectBean;
    }

}

更多详情可阅读:

构造器注入 vs. Setter注入

Spring团队提倡我们应该更多地通过构造器注入,因为通过构造器注入你可以将依赖对象声明为不可变对象,并且可以确保注入的依赖不为空。另外,如果通过这种方式进行注入而造成大量的构造参数也可以提前提醒我们这里存在bad code smell,因为这意味着当前类可能存在太多的职责,我们应该进行重构来适当的分离类的职责以更好地解决问题。而对于Setter方法的注入,Spring团队则建议将它用于一些可选的/可切换的依赖项上,这样我们就可以很轻易地对其依赖进行重新配置或重新注入。另外,在配置这些可选依赖项时可在类中分配合理的默认值,否则就必须在使用依赖项的地方进行非空检查。总的来说,我们可以混合使用构造器注入与Setter方法注入,其中对于一些必须的依赖项通过构造器来强制注入,而对于一些可选依赖项则通过Setter方法来进行注入。

更多详情可阅读:

扩展特性

自动装配

上文提及用于依赖注入的@Autowired注解实际上是用来标记Spring的自动装配模式的,所谓自动装配即Spring通过查询IoC容器(ApplicationContext)中的bean来帮我们自动解析并装配相应的bean依赖。

对于bean的自动装配,Spring提供了四种不同的模式让我们选择:

模式 说明
no (默认)没有自动装配。在这种模式下bean依赖必须通过ref属性来指定。
byName 通过属性名字自动装配。Spring会查询与属性名字相同的bean进行自动装配。例如,当bean被配置为通过byName模式完成自动装配,那么对于它名为master的属性,Spring会从IoC容器中查询出同样名为masterbean,若存在则将它自动装配到对应属性中。
byType 通过属性类型自动装配。Spring会查询与属性类型相同的bean进行自动装配,但是前提是容器中正好存在一个与它属性类型相匹配的bean。如果容器中存在多个类型相匹配的bean则会抛出异常;而如果没有类型相匹配的bean则不会发生任何事情(即属性未被设置)。
constructor 类似于byType模式,但适用于构造器参数。与byType模式不同的是,如果容器中没有一个bean与构造器参数类型相匹配的话,则会抛出异常。

其中,在byTypeconstructor自动装配的模式下,我们可以自动装配数组和集合。在这种情况下IoC容器中所有与之类型相匹配的bean都会被注入到对应的数组或集合中。

同样在使用上我们可以使用XML的方式和Java的方式进行配置,即:

  • 通过XML配置自动装配

    XML配置中,我们可以在<bean/>标签中指定autowire属性来设置bean的自动装配模式,例如:

    java 复制代码
    public class SimpleBean {
    
        private OneInjectBean oneInjectBean;
        private TwoInjectBean twoInjectBean;
    
        public SimpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) {
            this.oneInjectBean = oneInjectBean;
            this.twoInjectBean = twoInjectBean;
        }
    
        public void setOneInjectBean(OneInjectBean oneInjectBean) {
            this.oneInjectBean = oneInjectBean;
        }
    
        public void setTwoInjectBean(TwoInjectBean twoInjectBean) {
            this.twoInjectBean = twoInjectBean;
        }
    
    }

    如果我们使用byName模式,则可以像如下这样配置:

    xml 复制代码
    <beans>
        <bean id="simpleBean" class="com.example.SimpleBean" autowire="byName"/>
    
        <bean id="oneInjectBean" class="com.example.OneInjectBean"/>
        <bean id="twoInjectBean" class="com.example.TwoInjectBean"/>
    </beans>

    如果我们使用byType模式,则可以像如下这样配置:

    xml 复制代码
    <beans>
        <bean id="simpleBean" class="com.example.SimpleBean" autowire="byType"/>
    
        <bean id="oneInjectBean" class="com.example.OneInjectBean"/>
        <bean id="twoInjectBean" class="com.example.TwoInjectBean"/>
    </beans>

    如果我们使用constructor模式,则可以像如下这样配置:

    xml 复制代码
    <beans>
        <bean id="simpleBean" class="com.example.SimpleBean" autowire="constructor"/>
    
        <bean id="oneInjectBean" class="com.example.OneInjectBean"/>
        <bean id="twoInjectBean" class="com.example.TwoInjectBean"/>
    </beans>
  • 通过Java配置自动装配

    XML配置不同,通过Java配置我们只需要在构造器、Setter方法或者属性上标记@Autowired注解即可,例如:

    需要注意,通过这种方式只能使用byType模式进行自动装配。

    如果我们以构造器的方式配置自动装配,则可以像如下这样配置:

    java 复制代码
    @Component
    public class SimpleBean {
    
        private OneInjectBean oneInjectBean;
        private TwoInjectBean twoInjectBean;
    
        @Autowired
        public SimpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) {
            this.oneInjectBean = oneInjectBean;
            this.twoInjectBean = twoInjectBean;
        }
    }

    如果我们以Setter方法的方式自动装配,则可以像如下这样配置:

    java 复制代码
    @Component
    public class SimpleBean {
    
        private OneInjectBean oneInjectBean;
        private TwoInjectBean twoInjectBean;
    
        @Autowired
        public void setOneInjectBean(OneInjectBean oneInjectBean) {
            this.oneInjectBean = oneInjectBean;
        }
    
        @Autowired
        public void setTwoInjectBean(TwoInjectBean twoInjectBean) {
            this.twoInjectBean = twoInjectBean;
        }
    
    }

    如果我们以属性的方式自动装配,则可以像如下这样配置:

    java 复制代码
    @Component
    public class SimpleBean {
        @Autowired
        private OneInjectBean oneInjectBean;
        @Autowired
        private TwoInjectBean twoInjectBean;
    }

在自动装配的使用上,Spring团队推荐:

  • 对于自动装配应该在整个项目中一致使用才能达到最佳的效果。如果在普遍不使用自动装配的应用中,对某一两个bean使用自动装配可能会让人感到困惑。
  • 在大规模的应用中,更多的是使用默认配置(即no模式)。因为明确的指定依赖可以提供更好的控制性和清晰性,并且在某种程度上它记录了一个系统的整体结构。

另外,我们还可以通过将beanautowire-candidate属性设置为false来使它不会成为其他bean依赖的自动装配候选者(但这并不意味着排除了bean本身不能使用自动装配对其依赖进行配置,而是它不能是其他bean自动装配的候选者)。其中,需要注意的是autowire-candidate属性只能影响到基于类型的自动装配(即byType或者constructor),它并不会影响到基于名称的自动装配(即byName),即使指定的bean并没有成为自动装配候选者也会得到解析。与上述其它属性类似,对于非自动装配候选者的配置也存在XML的方式和Java方式,具体如下所示:

  • 通过XML配置bean为非自动装配候选者

    XML配置中,要将bean配置为非自动装配候选者,则需要在bean标签中将autowire-candidate属性设置为false,即:

    xml 复制代码
    <beans>
        <bean id="simpleBean" class="com.example.SimpleBean" autowire-candidate="false"/>
    </beans>

    除此之外,对于XML配置我们还可以在顶级<beans/>元素的default-autowire-candidates属性上指定一个或多个匹配模式(多个模式之间用逗号分隔),使得它包含的所有的<bean>只有在模式匹配的前提下才能成为自动装配的候选者。需要注意的是,<bean>autowire-candidate属性(需显式设置true或者false)的优先级是比其父标签<beans/>元素的default-autowire-candidates属性要高的。

    xml 复制代码
    <beans default-autowire-candidates="*Repository"> 
        <bean id="simpleBean" class="com.example.SimpleBean"/>
        <bean id="simpleRepository" class="com.example.SimpleRepository"/>
    </beans>

    此处的default-autowire-candidates="*Repository"表示只有名称以Repository结尾的<bean>(在父标签<beans>中)才能成为自动装配的候选者。

  • 通过Java配置bean为非自动装配候选者

    Java配置中,要将bean配置为非自动装配候选者,则需要在@Bean注解中将autowireCandidate属性设置为false,即:

    java 复制代码
    @Configuration
    public class MyConfig {
    
        @Bean(autowireCandidate = false)
        public SimpleBean simpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) {
            return new SimpleBean(oneInjectBean, twoInjectBean);
        }
    }

关于自动装配的优/劣势:

  • 优势:
    1. 自动装配可以显著减少属性或构造器参数配置的需要。
    2. 自动装配可以随着对象变化而更新配置。例如,如果需要向bean添加依赖,自动装配可以在无需修改配置的前提下完成。
  • 劣势:
    1. 自动装配中属性和构造器参数的配置会被显式依赖项所覆盖。
    2. 自动装配不适用于简单属性及其类型数组,例如Primitive(原始类型)、StringClass
    3. 自动装配精确性没有显式装配高,并且它会影响到IoC容器中对象关系的记录。
    4. 自动装配使得我们无法在IoC容器文档生成工具中获得装配信息。
    5. 自动装配可能会在多个bean与依赖项相匹配的时候抛出异常。如果依赖项是数组、集合或Map等类型时可能并不会存在问题,但是对于期望单个值的依赖项则会因为没有得到唯一的bean实例而抛出异常。而对于这种情况我们可以有以下几种选择:
      1. 放弃自动装配转而使用显式装配。
      2. 通过将beanautowire-candidate属性设置为false来避免自动装配。
      3. 通过将bean设置为primary来指定某个beanprimary候选者。
      4. 基于注解实现更细粒度的控制。

更多详情可阅读一下资料:

延迟初始化

在默认情况下,IoC容器在初始化时会对所有单例bean进行实例化。除此之外,对于一些特殊的需要我们也可以将单例bean的实例化进行延迟,这样它就不会在容器初始化时被创建,而是在第一次向IoC容器请求bean时才进行实例化。

一般我们是推荐bean跟着容器初始化一起创建的,因为这可以在启动的时候立即发现配置或环境出现问题而引发的错误,而不是在运行过程中才发现问题。

同样的,在使用上我们可以使用XML的方式和Java的方式进行配置,即:

  • 通过XML的方式配置延迟初始化

    XML配置中,我们可以将<bean/>标签上的lazy-init属性设置truefalse来控制是否延长初始化。

    xml 复制代码
    <beans>
        <!-- 通过指定lazy-init属性为true即可让SimpleBean不会在容器启动时进行实例化 -->
        <bean id="simpleBean" class="com.example.SimpleBean" lazy-init="true"/>
    </beans>

    另外,我们还可以通过在<beans/>元素上使用default-lazy-init属性来让它所包含的<bean>都实现延迟初始化。

    xml 复制代码
    <beans default-lazy-init="true">
        <!-- no beans will be pre-instantiated... -->
    </beans>
  • 通过Java的方式配置延迟初始化

    Java配置中,我们可以通过@Lazy注解来达到与lazy-init同样的效果(即延迟初始化)。其中,@Lazy存在以下几种不同的声明方式:

    通过将@Lazy注解声明在@Component及其衍生类(@Controller@Service@Repository)上以让它实现延迟初始化。

    java 复制代码
    @Lazy
    @Component
    public class SimpleBean {
    }

    通过将@Lazy注解声明在@Bean方法上以让它实现延迟初始化。

    java 复制代码
    @Configuration
    public class MyConfig {
        @Lazy
        @Bean
        public SimpleBean simpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) {
            return new SimpleBean(oneInjectBean, twoInjectBean);
        }
    }

    通过将@Lazy注解声明在@Configuration类上以让它所包含的所有@Bean方法都被延迟初始化。另外,我们也可以在@Configuration中的@Bean上加上@Lazy(value=false)(显式设置为不延迟初始化)来覆盖类上指定的默认行为(延迟初始化)。

    java 复制代码
    @Lazy
    @Configuration
    public class MyConfig {
        
        @Bean
        public SimpleBean1 simpleBean1(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) {
            return new SimpleBean1(oneInjectBean, twoInjectBean);
        }
    
        @Lazy(value=false)
        @Bean
        public SimpleBean2 simpleBean2(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) {
            return new SimpleBean2(oneInjectBean, twoInjectBean);
        }   
    
    }

    通过将@Lazy注解声明在依赖的@Autowired@Inject注解上以让依赖延迟注入。通过这种方式,Spring会首先会对@Lazy的依赖注入一个lazy-resolution代理,在依赖被调用时才会去检索或创建依赖实例(这可能会由于依赖不存在而抛出异常)。

    java 复制代码
    @Component
    public class SimpleBean {
        @Lazy
        @Autowired
        private OneInjectBean oneInjectBean;
        @Lazy
        @Autowired
        private TwoInjectBean twoInjectBean;
    }
    java 复制代码
    @Component
    public class SimpleBean {
        private OneInjectBean oneInjectBean;
        private TwoInjectBean twoInjectBean;
        @Lazy
        @Autowired
        public SimpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) {
            this.oneInjectBean = oneInjectBean;
            this.twoInjectBean = twoInjectBean;
        }
    }
    java 复制代码
    @Component
    public class SimpleBean {
        private OneInjectBean oneInjectBean;
        private TwoInjectBean twoInjectBean;
        @Lazy
        @Autowired
        public void setOneInjectBean(OneInjectBean oneInjectBean) {
            this.oneInjectBean = oneInjectBean;
        }
        @Lazy
        @Autowired
        public void setTwoInjectBean(TwoInjectBean twoInjectBean) {
            this.twoInjectBean = twoInjectBean;
        }
    }

    除此之外,对于依赖的延迟注入除了可以使用@Lazy注解我们还可以使用org.springframework.beans.factory.ObjectFactoryjakarta.inject.Providerorg.springframework.beans.factory.ObjectProvider来代替,即将它们作为注入点。

    java 复制代码
    @Component
    public class SimpleBean {
        @Autowired
        private ObjectProvider<OneInjectBean> oneInjectBean;
        @Autowired
        private ObjectProvider<TwoInjectBean> twoInjectBean;
    }
    java 复制代码
    @Component
    public class SimpleBean {
        private ObjectProvider<OneInjectBean> oneInjectBean;
        private ObjectProvider<TwoInjectBean> twoInjectBean;
        @Autowired
        public SimpleBean(ObjectProvider<OneInjectBean> oneInjectBean, ObjectProvider<TwoInjectBean> twoInjectBean) {
            this.oneInjectBean = oneInjectBean;
            this.twoInjectBean = twoInjectBean;
        }
    }
    java 复制代码
    @Component
    public class SimpleBean {
        private ObjectProvider<OneInjectBean> oneInjectBean;
        private ObjectProvider<TwoInjectBean> twoInjectBean;
        @Autowired
        public void setOneInjectBean(ObjectProvider<OneInjectBean> oneInjectBean) {
            this.oneInjectBean = oneInjectBean;
        }
        @Autowired
        public void setTwoInjectBean(ObjectProvider<TwoInjectBean> twoInjectBean) {
            this.twoInjectBean = twoInjectBean;
        }
    }

需要注意,当延迟初始化bean是普通单例bean(非延迟初始化)的依赖时,容器在启动期间还是会创建这个延迟初始化的bean,这是因为单例bean仅仅会在容器启动时被创建,之后它的依赖将无法再被注入进去了。对于这种情况,我们可以通过将依赖也声明为lazy(例如,在依赖属性上加上注解@Lazy)来解决。

更多详情可阅读一下资料:

依赖显式化

对于存在依赖关系的bean(使用了Java(通过@Autowired等注解)或XML(通过ref属性或标签等)的方式声明了依赖项),IoC容器会在初始化时会根据它们的依赖关系(顺序)逐个进行实例化。然而,对于一些没那么直接的依赖关系(例如,数据库驱动程序的注册)通过这种方式则无法实现。因此,Spring提供了depends-on属性让我们可以显示地指定bean的依赖关系,以此来强制它们的初始化顺序。

需要注意,通过这种方式来指定初始化顺序仅仅适用于单例bean

同样的,在使用上我们可以使用XML的方式和Java的方式进行配置,即:

  • 通过XML方式配置depneds-on

    XML配置中,我们可以在<bean/>标签中将depends-on属性设置为需要依赖的bean名称来显式指定它们依赖关系的,以此强制它们的初始化顺序。

    xml 复制代码
    <beans>
        <bean id="simpleBean1" class="com.example.SimpleBean1" depends-on="simpleBean2"/>
        <bean id="simpleBean2" class="com.example.SimpleBean2"/>
    </beans>

    如果bean对多个bean存在依赖,则可以通过分隔符(逗号、空格和分号都是有效的分隔符)来间隔多个bean名称。

    xml 复制代码
    <beans>
        <bean id="simpleBean1" class="com.example.SimpleBean1" depends-on="simpleBean2,simpleBean3"/>
        <bean id="simpleBean2" class="com.example.SimpleBean2"/>
        <bean id="simpleBean3" class="com.example.SimpleBean3"/>
    </beans>
  • 通过Java方式配置depneds-on

    Java配置中,我们可以在@Component及其衍生类(@Controller@Service@Repository)中或者@Bean方法上使用注解@DependsOn来达到目的,其中对于@DependsOn注解的用法与XMLdepends-on属性的用法相同。

    java 复制代码
    @DependsOn(value={"simpleBean2","simpleBean3"})
    @Component
    public class SimpleBean1 {
    }
    
    @Component
    public class SimpleBean2 {
    }
    
    @Component
    public class SimpleBean3 {
    }
    java 复制代码
    @Configuration
    public class MyConfig {
    
        @DependsOn(value={"simpleBean2","simpleBean3"})
        @Bean
        public SimpleBean1 simpleBean1() {
            return new SimpleBean1();
        }
    
        @Bean
        public SimpleBean2 simpleBean2() {
            return new SimpleBean2();
        }   
    
        @Bean
        public SimpleBean3 simpleBean3() {
            return new SimpleBean3();
        }   
    }

需要注意,depends-on属性不但可以指定依赖间的初始化顺序,对于单例bean它还会根据其初始化顺序间接指定其销毁的顺序。即,在给定bean销毁前必须先销毁依赖它的所有bean

更多详情可阅读一下资料:

常见问题

自引用

Spring 4.3@Autowired注解开始支持自引用(即,自己引用自己)。然而,实际上@Autowired的自引用在Spring中充当的是一种后备策略,即一般情况下@Autowired的自引用并不会在依赖注入候选者的名单中,只有在其它候选者都不满足的情况下它才会作为一种后备被注入到依赖中。可选地,我们也可以使用其它解决方案来实现自引用,即可以使用@Resource注解指定唯一bean名称的方式来实现自引用。

除此之外,将以@Bean方法声明的bean注入到同一配置类中实际上也是一种自引用的场景。对于这种情况,我们可以将相应的@Bean方法声明为@Lazystatic,目的是让@Bean方法的生命周期与配置类本身进行分离,否则容器只会在兜底阶段才考虑这些bean(而是选择其他配置类中相匹配的bean作为主要候选者(如果存在))。

更多详情可阅读一下资料:

循环依赖

IoC容器启动或者第一次请求bean时会触发相应bean(默认情况下单例bean会在容器启动时被创建)以及其依赖项、依赖项的依赖项(等等)的创建。在这过程中可能会触发bean的循环依赖,例如:

java 复制代码
@Component
public class Component1 { 

    private Component2 component2;

    @Autowired
    public Component1(Component2 component2){
        this.component2 = component2;
    }
}

@Component
public class Component2 {
    
    private Component1 component1;

    @Autowired
    public Component2(Component1 component1) {
        this.component1 = component1;
    }
}

对于像上述这样使用构造器注入的情况,在发生循环依赖时IoC会在运行时检测出来,并且抛出BeanCurrentlyInCreationException异常。在发生循环依赖时抛出异常可能并不是我们想要的,所以Spring也提供了其他的注入方式来解决循环依赖的问题(不但检测出循环依赖,而且也解决循环依赖),例如在以Setter方式配置依赖注入时,若发生循环依赖,它会迫使其中一个bean在完全初始化之前注入到另一个bean中(经典的鸡和蛋场景),具体用法如下:

java 复制代码
@Component
public class Component1 { 
 
    private Component2 component2;

    @Autowired
    public void setComponent2(Component2 component2){
        this.component2 = component2;
    }
}

@Component
public class Component2 {
    
    private Component1 component1;

    @Autowired
    public void setComponent1(Component1 component1){
        this.component1 = component1;
    }
}

更多详情可阅读一下资料:

Bean作用域

Spring中,IoC容器中的每个bean都会被指定一个作用域(默认是singleton),通过这个作用域我们就可以指定每次生成bean实例的存活时间。其中,Spring(默认)提供了以下六种可选值(有四种只有在Web应用中可以使用):

作用域 描述
singleton (默认)将单个bean的作用域限定为每个容器只有单个对象实例。
prototype 将单个bean的作用域限定为任意数量的对象实例。
request 将单个bean的作用域限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有自己的bean实例(仅在Web应用的上下文中有效)。
session 将单个bean的作用域限定为HTTP会话的生命周期(仅在Web应用的上下文中有效)。
application 将单个bean的作用域限定为ServletContext的生命周期(仅在Web应用的上下文中有效)。
websocket 将单个bean的作用域限定为WebSocket的生命周期(仅在Web应用的上下文中有效)。

作用域的可选值

Singleton

bean的作用域为singleton时,它仅会在容器中存在一个共享实例,并且所有与它ID匹配的请求容器都会返回指定的bean。换句话说,当您定义了一个作用域为singletonbeanIoC容器会在bean实例首次创建后将它存储到单例bean的缓存中,并且后续对它的所有请求和引用都会返回缓存中的实例。

下图展示了singleton作用域的工作原理:

Spring概念中的单例beanGang of Four (GoF)设计模式书中定义的单例模式有所不同。GoF中的singleton是对象作用域的硬编码,每个ClassLoader只创建特定类的一个实例;而Springsingleton作用域则是每个容器只创建特定类的一个实例,即每个IoC容器仅会创建特定类(通过BeanDefinition定义的类)的一个实例(在Spring中,singleton作用域是bean的默认作用域)。

更多详情可阅读一下资料:

Prototype

bean的作用域为prototype(非单例)时,它会在每次对容器请求bean时(通过bean注入或者调用getBean())都会创建一个新的bean实例。

通常,我们应该对所有有状态的bean使用prototype作用域,而对无状态的bean使用singleton作用域。

下图说明了Springprototype作用域的工作原理:

与其他bean作用域相比,Spring并不会管理prototype作用域bean的完整生命周期。对于prototype作用域的bean,容器会在实例化、配置和组装后直接将其传递给客户端,而没有像单例模式那样进行缓存。也正因如此,容器并不会在prototype作用域的bean销毁时执行其所配置的销毁方法(bean生命周期回调)。所以,在prototype作用域的bean销毁时我们必须显式地清理对象及释放它所持有的资源。

需要注意,当将prototype作用域的bean作为依赖注入到singleton作用域的bean时,prototype作用域的bean仅仅会singleton作用域的bean初始化时进行创建和注入。之后如果再次请求prototype作用域的bean(新)实例时,容器是不会再次将它注入到singleton作用域的bean中的,这是因为对于singleton作用域的bean仅仅是会在容器实例化bean时完成依赖项的解析和注入。而对于这种情况我们可以通过方法注入Method Injection的方式或作用域代理ScopedProxyMode的方式来实现重复注入。

更多详情可阅读一下资料:

Web Scope

对于Request, Session, ApplicationWebsocket等作用域只有在Web应用时才能生效,如果我们在普通容器中使用这些作用域则抛出IllegalStateException异常。

Request

bean的作用域为request时,它会在每次HTTP请求创建新的bean实例。也就是说,对于request作用域的bean生命周期会被限制在一次HTTP请求,在请求处理完成时request作用域的bean将会被销毁。我们可以根据需要改变实例的内部状态,因为通过其他HTTP请求生成的bean实例并不能看见这些改变。

更多详情可阅读一下资料:

Session

bean的作用域为session时,它会在每次HTTP会话创建新的bean实例。也就是说,对于session作用域的bean生命周期会被限制在一次HTTP会话,在HTTP会话被销毁时session作用域的bean也将会被销毁。我们可以根据需要改变实例的内部状态,因为通过其他HTTP会话生成的bean实例并不能看见这些改变。

更多详情可阅读一下资料:

Application

bean的作用域为application时,它会在整个Web应用程序创建新的bean实例。也就是说,对于application作用域的bean生命周期会被限制在ServletContext,并作为常规ServletContext属性存储。这有点类似于Spring的单例bean,但在两个重要方面有所不同:

  • Application作用域的bean是每个ServletContext的单例,而不是每个ApplicationContext(在任何给定的Web应用程序中一个ServletContext可能有多个ApplicationContext)。
  • Application作用域的bean是公开的,可作为ServletContext属性被访问。

更多详情可阅读一下资料:

WebSocket

bean的作用域为WebSocket时,它会与WebSocket会话的生命周期相关联,适用于跨WebSocket应用。

更多详情可阅读一下资料:

作用域的配置

对于bean作用域,我们可以分别通过XML的方式和Java的方式进行配置.

  • 通过XML方式配置

    在基于XML的配置中,我们可以使用<bean/>标签的scope属性来指定其作用域。

    xml 复制代码
    <beans>
        <!-- 默认是singleton作用域 -->
        <bean id="simpleBean" class="com.example.SimpleBean"/>
        <!-- singleton作用域 -->
        <bean id="simpleBean" class="com.example.SimpleBean" scope="singleton"/>
        <!-- prototype作用域 -->
        <bean id="simpleBean" class="com.example.SimpleBean" scope="prototype"/>
        <!-- request作用域 -->
        <bean id="simpleBean" class="com.example.SimpleBean" scope="request"/>
        <!-- session作用域 -->
        <bean id="simpleBean" class="com.example.SimpleBean" scope="session"/>
        <!-- application作用域 -->
        <bean id="simpleBean" class="com.example.SimpleBean" scope="application"/>
    </beans>
  • 通过Java方式配置

    在基于Java的配置中,我们可以在@Component类或者@Bean方法上使用@Scope注解并设置其scopeName属性来声明bean的作用域。而对于WebScope作用域,Spring还通过元注解组合的方式新增了对应的注解@RequestScope@SessionScope@ApplicationScope

    java 复制代码
    // 默认为singleton
    @Scope
    @Component
    public class SimpleBean {
    }
    java 复制代码
    @Scope(scopeName="singleton")
    @Component
    public class SimpleBean {
    }
    java 复制代码
    @Scope(scopeName="prototype")
    @Component
    public class SimpleBean {
    }
    java 复制代码
    // 等价于@Scope(scopeName="request")
    @RequestScope
    @Component
    public class SimpleBean {
    }
    java 复制代码
    // 等价于@Scope(scopeName="session")
    @SessionScope
    @Component
    public class SimpleBean {
    }
    java 复制代码
    // 等价于@Scope(scopeName="application")
    @ApplicationScope
    @Component
    public class SimpleBean {
    }

更多详情可阅读一下资料:

作用域的依赖

由于作用域的不同,IoC容器中bean存活的生命周期也有所差别。如果需要将生命周期较短的bean注入到生命周期更长的bean,直接的依赖注入可能会产生问题。对于这种情况,我们可以使用AOP代理来代替不同作用域的bean,即我们需要注入的是一个(与作用域对象具有相同接口的)代理对象,它会从对应的作用域中检索出真正的目标实例,并将方法的调用委托给它。

  • 通过XML方式配置注入代理

    XML配置中,我们可以在<bean>中加入子标签<aop:scoped proxy/>以表示对该bean生成代理对象。

    xml 复制代码
    <beans>
        <!-- a singleton-scoped bean injected with a proxy to the above bean -->
        <bean id="simpleBean" class="com.example.SimpleBean">
            <property name="oneInjectBean" ref="oneInjectBean"/>
            <property name="twoInjectBean" ref="twoInjectBean"/>
        </bean>
    
        <!-- a prototype-scoped bean exposed as a proxy -->
        <bean id="oneInjectBean" class="com.example.OneInjectBean" scope="prototype">
            <!-- instructs the container to proxy the surrounding bean -->
            <aop:scoped-proxy/> 
        </bean>
        <!-- a prototype-scoped bean exposed as a proxy -->
        <bean id="twoInjectBean" class="com.example.TwoInjectBean" scope="prototype">
            <!-- instructs the container to proxy the surrounding bean -->
            <aop:scoped-proxy/> 
        </bean>
    </beans>
    • singleton bean上使用<aop:scoped proxy/>,对bean引用使用的是一个可序列化的代理对象,因此可以通过反序列化来重新获取实例。
    • prototype bean上使用<aop:scoped proxy/>,对注入代理每个方法的调用都会导致bean实例重新创建,然后再将调用转发到该实例中。

    默认情况下,IoC容器会使用CGLIB为标记有<aop:scoped proxy/>bean创建代理对象。而如果将<aop:scoped proxy/>元素的proxy-target-class属性设置falseIoC容器则会通过JDK动态代理(基于JDK接口的标准代理)来为bean生成代理对象。

    • 根据JDK动态代理的定义,在使用时我们不需要添加额外的库来生成代理对象,但是需要为bean实现至少一个接口。另外,需要注意对这种bean依赖的注入必须通过其接口之一来引用。
    • 根据CGLIB动态代理的定义,由于这种代理只会截获public方法的调用,所以我们不能在这种代理上调用非public方法,它们是不会被委托给目标实例的。
    xml 复制代码
    <beans>
        <!-- a singleton-scoped bean injected with a proxy to the above bean -->
        <bean id="simpleBean" class="com.example.SimpleBean">
            <property name="oneInjectBean" ref="oneInjectBean"/>
            <property name="twoInjectBean" ref="twoInjectBean"/>
        </bean>
    
        <!-- a prototype-scoped bean exposed as a proxy -->
        <bean id="oneInjectBean" class="com.example.DefaultOneInjectBean" scope="prototype">
            <!-- instructs the container to proxy the surrounding bean -->
            <aop:scoped-proxy proxy-target-class="false"/> 
        </bean>
        <!-- a prototype-scoped bean exposed as a proxy -->
        <bean id="twoInjectBean" class="com.example.DefaultTwoInjectBean" scope="prototype">
            <!-- instructs the container to proxy the surrounding bean -->
            <aop:scoped-proxy proxy-target-class="false"/> 
        </bean>
    </beans>

    需要注意,如果将<aop:scoped proxy/>放在FactoryBean实现的<bean>时,作用域是FactoryBean本身,而不是从FactoryBean#getObject()返回的对象。

    另外,如果需要设置大量的代理对象,我们可以在component-scan上使用scoped-proxy属性来指定。其中,可选值为nointerfacestargetClass

    xml 复制代码
    <beans>
        <context:component-scan base-package="org.example" scoped-proxy="targetClass"/>
    </beans>
  • 通过Java方式配置注入代理

    Java配置中,我们可以在@Scope注解上通过指定proxyMode()属性来设置其作用域代理,它的可选值可在ScopedProxyMode枚举中找到,默认为不创建代理(即ScopedProxyMode.DEFAULT或者ScopedProxyMode.NO)。

    ScopedProxyMode 描述
    DEFAULT 默认,等价于ScopedProxyMode.NO。可通过在component-scan上设置ScopedProxyMode属性进行变更。
    NO 此类型表示不创建代理。
    INTERFACES 此类型表示通过JDK动态代理创建代理类。
    TARGET_CLASS 此类型表示通过CGLIB动态代理创建代理类。
    java 复制代码
    @Scope(scopeName="singleton")
    @Component
    public class SimpleBean1 {
        @Autowired
        private SimpleBean2 simpleBean2;
    }
    
    @Scope(scopeName="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
    @Component
    public class SimpleBean2 {
    }

    如果需要设置大量的代理对象,我们可以在@ComponentScan注解上使用scopedProxy属性来指定。其中,可选值为nointerfacestargetClass

    java 复制代码
    @Configuration
    @ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.TARGET_CLASS)
    public class AppConfig {
    }

    另外,除了通过代理类的方式将生命周期较短的bean注入到生命周期更长的bean外,我们还可以通过将注入点(即构造参数、Setter参数或自动装配字段)声明为ObjectFactory<MyTargetBean>来实现,这样每次调用ObjectFactory#getObject()来获取依赖对象就会按需检索依赖实例(无需保留或单独存储实例)。

    ObjectFactory<MyTargetBean>作为一个扩展变量,你还可以声明ObjectProvider<MyTargetBean>,它实现了ObjectFactory<MyTargetBean>并提供了几个额外的访问方法,比如getIfAvailable()getIfUnique()。另外,对于JSR-330规范它也有相同作用的变体Provider,类似的可以通过Provider<MyTargetBean>的方法Provider#get()来检索依赖实例。

更多详情可阅读一下资料:

Bean生命周期

Lifecycle接口

InitializationDestruction

bean实现了InitializingBeanDisposableBean接口时,IoC容器就会在bean初始化和销毁时分别调用InitializingBean#afterPropertiesSet方法(hook)和DisposableBean#destroy方法(hook)让bean能在特定的生命周期上执行特定的操作。

java 复制代码
@Component
public class SimpleBean implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
        // do some initialization work
    }
}

IoC容器会在设置完所有必要的属性后调用org.springframework.beans.factory.InitializingBean接口类(如有)来指定初始化工作,其中InitializingBean接口只有一个方法,即:

java 复制代码
void afterPropertiesSet() throws Exception;
java 复制代码
@Component
public class SimpleBean implements DisposableBean {

    @Override
    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}

IoC容器会在bean被销毁时调用org.springframework.beans.factory.DisposableBean接口类(如有)来指定销毁工作,其中DisposableBean接口只有一个方法,即:

java 复制代码
void destroy() throws Exception;

实际上,除了实现相应的接口方法外Spring更推荐我们使用JSR-250标准中@PostConstruct注解(初始化)和@PreDestroy注解(注销)来标记bean的初始化方法(hook)和销毁方法(hook),因为通过注解的方式可以让bean无需耦合Spring指定的接口(如果不想使用JSR-250注解但是又不想耦合Spring指定的接口,可以考虑在bean配置中指定init-method属性和destroy-method属性)。具体用法如下所示:

  • 通过XML配置的方式

    XML中,我们可以通过<bean/>标签的init-method属性和destroy-method属性分别指定初始化方法和销毁方法。

    xml 复制代码
    <bean id="simpleBean" class="com.example.SimpleBean" init-method="init" destroy-method="cleanup"/>
    java 复制代码
    public class SimpleBean {
    
        public void init() {
            // do some initialization work
        }
    
        public void cleanup() {
            // do some destruction work (like releasing pooled connections)
        }
    
    }
  • 通过Java配置的方式

    Java中,我们可以通过@Bean注解的initMethod属性和destroyMethod属性分别指定初始化方法和销毁方法。

    java 复制代码
    @Configuration
    public class MyConfig {
        @Bean(initMethod="init", destroyMethod="cleanup")
        public SimpleBean simpleBean() {
            return new SimpleBean();
        }
    }
    java 复制代码
    public class SimpleBean {
    
        public void init() {
            // do some initialization work
        }
    
        public void cleanup() {
            // do some destruction work (like releasing pooled connections)
        }
    }

    而在Spring 2.5后,我们可以通过@PostConstruct注解和@PreDestroy注解分别指定初始化方法和销毁方法。

    java 复制代码
    @Configuration
    public class MyConfig {
        @Bean
        public SimpleBean simpleBean() {
            return new SimpleBean();
        }
    }
    java 复制代码
    // 直接用@Component也可以
    public class SimpleBean {
        @PostConstruct
        public void init() {
            // do some initialization work
        }
    
        @PreDestroy
        public void cleanup() {
            // do some destruction work (like releasing pooled connections)
        }
    }

实际上,Spring是使用BeaPostProcessor来处理任何生命周期相关的接口的,如果需要自定义更多特性或其他Spring没有提供的生命周期行为,我们可以为此实现一个专用的BeanPostProcessor。另外,需要注意Spring会在bean完成所有依赖注入后立即调用配置的初始化方法,这意味着在bean执行初始化方法时AOP拦截器尚未应用于bean

Spring建议我们即使不使用InitializingBeanDisposableBean等接口来执行初始化方法和销毁方法,对于生命周期的接口方法命名也应该是整个项目统一的(存在一个标准,例如init()initialize()dispose()等名称编写方法),这样我们就可以在需要时低成本地使用XML<beans>default-init-method属性和default-destroy-method属性来指定它当中所有<bean>的初始化方法和销毁方法。

xml 复制代码
<beans default-init-method="init" default-destroy-method="cleanup">
    <bean id="simpleBean" class="com.example.SimpleBean"/>
</beans>

当然,我们也可以在bean中设置init-methoddestroy-method属性来覆盖其父级的默认方法(default-init-methoddefault-destroy-method)。

总的来说,我们可以通过三种方式来控制bean生命周期行为(从Spring2.5):

  • InitializingBeanDisposableBean接口
  • init()destroy()方法(自定义)
  • @PostConstruct@PreDestroy注解.

对于上述三种方式我们可以组合使用。如果一个bean配置了多种生命周期机制,并且每种机制都配置了不同的方法名,那么每个配置的方法都会按照以下顺序执行:

  • 对同一个bean的多种初始化方法执行顺序:
    1. @PostConstruct注解方法
    2. InitializingBean#afterPropertiesSet()方法
    3. init()方法(自定义配置)
  • 对同一个bean的多种销毁方法执行顺序:
    1. @PreDestroy注解方法
    2. DisposableBean#destroy()方法
    3. destroy()方法(自定义配置)

其中,如果对不同的方式都配置了相同的方法名称,则该方法将运行一次。

更多详情可阅读一下资料:

StartupShutdown

除了上述提及在特定生命周期触发的钩子方法外,Spring还提供了定义了生命周期中用于启动和停止的Lifecycle接口(可用于启动和停止某些进程)。

java 复制代码
public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

Spring中,任何实现了Lifecycle接口的(被Spring管理的)对象都会在ApplicationContextIoC容器)收到启动和停止信号时被级联触发。在实现上,它是通过LifecycleProcessor来实现这一点:

java 复制代码
/**
 * LifecycleProcessor继承了Lifecycle接口,并且添加了两种额外的方法,用于响应刷新和关闭的上下文。
 */ 
public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

需要注意Spring并没有保证在bean销毁前一定会有停止通知发出,一般来说Lifecycle实现会在接收到销毁方法(destruction)的回调前先收到停止的通知(Lifecycle#stop),但是对于在上下文生命周期内的热更新或者在停止刷新的间隔内它们只会收到销毁方法(destruction)的回调。

虽然Lifecycle接口定义了用于启动和停止的方法,但是这并不意味着它会在上下文刷新时自动启动。如果需要更细粒度地控制自动启动和特定bean的优雅停止,可以考虑继承SmartLifecycle接口。

java 复制代码
public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

SmartLifecycle除了扩展Lifecycle外,还增加了Phased的扩展。对于两个存在依赖关系的对象它们启动和停止的顺序是十分重要的,比如依赖方在其依赖之后开始,并在其依赖之前停止。但是,在开发过程中我们并不一定能知道两个具体对象的直接依赖关系,而是只知道某种类型的对象应该在另一种类型的对象之前开始,对于这种情况我们就需要用到Phased的扩展了:

java 复制代码
public interface Phased {

   int getPhase();
}

Phased接口主要用于指定对象的启动相位,即在启动时相位最低的对象首先启动,在停止时则按照相反的顺序进行停止。因此对于getPhase()方法返回Integer.MIN_VALUE的对象将是最先启动和最后停止的对象;在频谱的另一端,Integer.MAX_VALUEphase值则表示该对象应该最后启动并首先停止。另外,默认情况下任何未实现SmartLifecycle的"正常"Lifecycle对象的phase都是0,所以任何负phase值表示对象应该在这些标准组件之前开始(并在他们之后停止),反之亦然。

SmartLifecycleLifecycle的基础上增加了可接收Runnable参数的stop方法,默认会在执行完Lifecycle#stop方法后立即调用Runnable#run方法(同一调用线程)。在加入这个方法后,我们可以很轻易的实现SmartLifecycle组件的异步关闭(通过回调的方式实现)以支持功能的需要,例如LifecycleProcessor的默认实现DefaultLifecycleProcessor会在执行组件停止时对每个phase对象组的执行时间(含Runnable回调的执行)设置有超时时间,对于这种情况我们就可以启动异步关闭了(如有需要)。

默认情况下每个phase组的超时时间为30秒。我们也可以通过在上下文中定义一个名为lifecycleProcessorbean来覆盖默认的生命周期处理器实例。而如果要修改超时时间,添加以下配置即可:

xml 复制代码
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
    <!-- timeout value in milliseconds -->
    <property name="timeoutPerShutdownPhase" value="10000"/>
</bean>

另外,LifecycleProcessor接口还定义了刷新回调方法onRefresh()hook)和关闭回调方法onClose()hook),其中前者会在上下文刷新时(在所有对象都被实例化和初始化之后)被触发,默认情况下处理器会检查每个SmartLifecycleisAutoStartup()方法,当它返回true时当前实例就会在该执行点被自动启动,而不是等待上下文的start方法被显式调用来启动(与上下文刷新不同,在标准的上下文实现中start是不会自动发生的);而后者则会在上下文关闭时被触发,效果与显式调用stop方法类似,只不过时机是发生在上下文被关闭。

对于在非WEB应用环境下使用IoC容器,如果想要对每个单例bean优雅地执行关闭,则需要配置和实现相对应的destroy方法(释放资源),并且需要想向JVM注册一个shutdownhook方法,即调用ConfigurableApplicationContext接口的registerShutdownHook()方法(在基于Web应用环境的ApplicationContext实现已经存在相关代码,所以无需额外配置)。

java 复制代码
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Boot {
    
    public static void main(final String[] args) throws Exception {
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

        // add a shutdown hook for the above context...
        ctx.registerShutdownHook();

        // app runs here...

        // main method exits, hook is called prior to the app shutting down...
    }
}

更多详情可阅读一下资料:

Aware接口

Spring提供了各种可感知的回调接口,让bean可以向容器表明它们需要某种基础设施依赖。下面我们来看看几个比较常见的Aware接口:

  • ApplicationContextAware

    ApplicationContextIoC容器)创建一个org.springframework.context.ApplicationContextAware对象实例时,该实例就会通过ApplicationContextAware#setApplicationContext提供这个ApplicationContext的实例引用给我们。

    java 复制代码
    public interface ApplicationContextAware {
    
        void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
    }

    在获取到ApplicationContext后我们就可以通过它(以编程的方式)来创建和获取更多的bean了。虽然有时候这种能力很有用,但是我们更应该避免以这种方式来操作bean,因为这会导致业务代码耦合到Spring并且它也不遵循IoC风格。

    除此之外,我们还可以通过自动装配的方式来获取ApplicationContext。即,通过构造参数、Setter方法参数或字段属性等自动装配的方式来对ApplicationContext进行注入。

  • BeanNameAware

    ApplicationContextIoC容器)创建一个org.springframework.beans.factory.BeanNameAware对象实例时,该实例就会通过BeanNameAware#setBeanName提供所关联bean实例的名称给我们。

    java 复制代码
    public interface BeanNameAware {
    
        void setBeanName(String name) throws BeansException;
    }

    需要注意,BeanNameAware#setBeanName方法会在普通bean属性填充后初始化方法回调(InitializingBean#afterPropertiesSet方法或者指定init-method方法)前被调用。

  • 更多Aware接口

    除了ApplicationContextAwareBeanNameAware外,Spring还提供了广泛的Aware回调接口:

    Name Injected Dependency
    ApplicationContextAware Declaring ApplicationContext.
    ApplicationEventPublisherAware Event publisher of the enclosing ApplicationContext.
    BeanClassLoaderAware Class loader used to load the bean classes.
    BeanFactoryAware Declaring BeanFactory.
    BeanNameAware Name of the declaring bean.
    LoadTimeWeaverAware Defined weaver for processing class definition at load time.
    MessageSourceAware Configured strategy for resolving messages (with support for parameterization and internationalization).
    NotificationPublisherAware Spring JMX notification publisher.
    ResourceLoaderAware Configured loader for low-level access to resources.
    ServletConfigAware Current ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext.
    ServletContextAware Current ServletContext the container runs in. Valid only in a web-aware Spring ApplicationContext.

更多详情可阅读一下资料:

Bean加载

在完成对bean的配置后,我们就可以使用Spring提供的方式对bean进行加载了,例如,通过将配置文件或配置类设置到容器ApplicationContext中进行加载;或者也可以通过@ComponentScan注解扫描指定路径上的配置文件进行加载,又或者通过@Import注解直接导入配置类的方式进行加载。

通过ApplicationContext加载

对于传统Java编程的方式,我们可以将配置文件或者配置类设置到ApplicationContext中来加载bean,其中根据不同的文件类型可分为FileSystemXmlApplicationContextClassPathXmlApplicationContextAnnotationConfigApplicationContext

  • 通过XML配置的方式

    对于XML配置的方式,我们可以通过将文件系统路径和classpath路径分别配置到FileSystemXmlApplicationContextClassPathXmlApplicationContext中进行bean的加载。

    java 复制代码
    ApplicationContext classPathContext = new ClassPathXmlApplicationContext("beans.xml");
    MyService service = classPathContext.getBean("myService", MyService.class);
    java 复制代码
    ApplicationContext fileSystemContext = new FileSystemXmlApplicationContext("resources/beans.xml");
    MyService service = fileSystemContext.getBean("myService", MyService.class);
  • 通过Java配置的方式

    对于Java配置的方式,我们可以将@Configuration@Component等配置类设置到AnnotationConfigApplicationContext中进行bean的加载。

    java 复制代码
    ApplicationContext annotationConfigContext = new AnnotationConfigApplicationContext(MyConfig.class);
    MyService service = annotationConfigContext.getBean("myService", MyService.class);

    除此之外,我们也可以在ApplicationContext中调用getBeanFactory()获取BeanFactory(默认实现为DefaultListableBeanFactory),然后调用BeanFactory(默认实现为DefaultListableBeanFactory)的registerSingleton()registerBeanDefinition()方法来注册容器外的对象以创建对应的bean实例。

更多详情可阅读一下资料:

通过@ComponentScan加载

除了使用传统Java编程的方式,Spring还提供了自动检测的方式来加载bean,即通过使用@ComponentScan注解类完成对指定范围内bean的自动检测(扫描),并最终将对应BeanDefinition实例注册到容器中(例如ApplicationContext)。

当使用@ComponentScan进行自动检测时AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor都会被隐式包含在内。如果是通过它的XML方式进行配置(使用<context:component-scan>),则可以通过将annotation-config属性设置为false来禁用AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor的注册。

java 复制代码
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
    // ...
}

默认情况下,@Component@Repository@Service@Controller@Configuration注解及其衍生注解都会被检测到,并且我们还可以通过在@ComponentScan注解上设置includeFiltersexcludeFilters属性配置自定义过滤器修改和扩展此行为,其中每个过滤器都需要配置typeexpression属性。下表描述了过滤的选项:

Filter Type Example Expression Description
annotation (default) org.example.SomeAnnotation 在目标组件的类级别上存在相应的注解。
assignable org.example.SomeClass 目标组件可分配给所指定的类(或接口)。
aspectj org.example..*Service+ 目标组件相匹配的AspectJ类型表达式。
regex org\.example\.Default.* 目标组件相匹配的正则表达式(类名)。
custom org.example.MyTypeFilter org.springframework.core.type.TypeFilter接口的自定义实现。

如果需要禁用默认的扫描过滤策略,我们可以通过在注解上设置useDefaultFilters=false或在<component-scan/>标签上设置use-default-filters'false'来禁用(默认过滤器)。这可以有效地禁用了对@Component@Repository@Service@Controller@RestController@Configuration注解及其衍生注解的自动检测。

更多详情可阅读一下资料:

通过@Import加载

另外,我们还可以通过@Import注解来添加额外的配置类(例如,@Configuration类)。

本章节主要讨论如何使用@Import注解来添加额外的配置类,而对于使用<import>标签(若以XML的方式配置)或者使用@ImportResource注解(若以Java的方式配置)来引用其他的XML配置文件在这里就不展开讨论了,有兴趣的读者可进一步阅读相关资料进行了解。

java 复制代码
/**
 * Indicates one or more <em>component classes</em> to import &mdash; typically
 * {@link Configuration @Configuration} classes.
 *
 * <p>Provides functionality equivalent to the {@code <import/>} element in Spring XML.
 * Allows for importing {@code @Configuration} classes, {@link ImportSelector} and
 * {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component
 * classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}).
 *
 * <p>{@code @Bean} definitions declared in imported {@code @Configuration} classes should be
 * accessed by using {@link org.springframework.beans.factory.annotation.Autowired @Autowired}
 * injection. Either the bean itself can be autowired, or the configuration class instance
 * declaring the bean can be autowired. The latter approach allows for explicit, IDE-friendly
 * navigation between {@code @Configuration} class methods.
 *
 * <p>May be declared at the class level or as a meta-annotation.
 *
 * <p>If XML or other non-{@code @Configuration} bean definition resources need to be
 * imported, use the {@link ImportResource @ImportResource} annotation instead.
 *
 * @see Configuration
 * @see ImportSelector
 * @see ImportBeanDefinitionRegistrar
 * @see ImportResource
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

    /**
     * {@link Configuration @Configuration}, {@link ImportSelector},
     * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
     */
    Class<?>[] value();

}

通过@Import注解我们不但可以加载一些典型的配置类(例如@Configuration@Component注解类及其衍射注解的类),而且还可以还可以加载ImportSelectorImportBeanDefinitionRegistrar类进行更灵活的配置。

我们可以将@Import作为一个元注解(meta-annotation)衍射出更多灵活的用户,典型的就是添加相应的@EnableXxxx注解来使相应的第三方组件生效。而对于@Import中指定ImportSelectorImportBeanDefinitionRegistrar则更是众多第三方框架整个Spring的关键所在。

  • 常规配置类

    对于常规配置类(例如@Configuration@Component注解类及其衍射注解的类)的导入会将配置类本身及其所包含的bean配置注册和加载到IoC容器中。

    java 复制代码
    @Configuration
    public class ConfigA {
    
        @Bean
        public A a() {
            return new A();
        }
    }
    
    @Configuration
    @Import(ConfigA.class)
    public class ConfigB {
    
        @Bean
        public B b() {
            return new B();
        }
    }
    
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
    
        // now both beans A and B will be available...
        A a = ctx.getBean(A.class);
        B b = ctx.getBean(B.class);
    }
  • ImportSelector

    对于ImportSelector的导入则可以更灵活地进行bean的注册和加载。

    java 复制代码
    /**
     * Interface to be implemented by types that determine which @{@link Configuration}
     * class(es) should be imported based on a given selection criteria, usually one or
     * more annotation attributes.
     *
     * <p>An {@link ImportSelector} may implement any of the following
     * {@link org.springframework.beans.factory.Aware Aware} interfaces,
     * and their respective methods will be called prior to {@link #selectImports}:
     * <ul>
     * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
     * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}</li>
     * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}</li>
     * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}</li>
     * </ul>
     *
     * <p>Alternatively, the class may provide a single constructor with one or more of
     * the following supported parameter types:
     * <ul>
     * <li>{@link org.springframework.core.env.Environment Environment}</li>
     * <li>{@link org.springframework.beans.factory.BeanFactory BeanFactory}</li>
     * <li>{@link java.lang.ClassLoader ClassLoader}</li>
     * <li>{@link org.springframework.core.io.ResourceLoader ResourceLoader}</li>
     * </ul>
     *
     * <p>{@code ImportSelector} implementations are usually processed in the same way
     * as regular {@code @Import} annotations, however, it is also possible to defer
     * selection of imports until all {@code @Configuration} classes have been processed
     * (see {@link DeferredImportSelector} for details).
     *
     * @author Chris Beams
     * @author Juergen Hoeller
     * @since 3.1
     * @see DeferredImportSelector
     * @see Import
     * @see ImportBeanDefinitionRegistrar
     * @see Configuration
     */
    public interface ImportSelector {
    
        /**
         * Select and return the names of which class(es) should be imported based on
         * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
         * @return the class names, or an empty array if none
         */
        String[] selectImports(AnnotationMetadata importingClassMetadata);
    
        /**
         * Return a predicate for excluding classes from the import candidates, to be
         * transitively applied to all classes found through this selector's imports.
         * <p>If this predicate returns {@code true} for a given fully-qualified
         * class name, said class will not be considered as an imported configuration
         * class, bypassing class file loading as well as metadata introspection.
         * @return the filter predicate for fully-qualified candidate class names
         * of transitively imported configuration classes, or {@code null} if none
         * @since 5.2.4
         */
        @Nullable
        default Predicate<String> getExclusionFilter() {
            return null;
        }
    
    }

    ImportSelector的实现类中,我们需要在selectImports方法中返回需要加载的@Configuration类名称(通过Class.getName()获取),这样Spring容器会从selectImports方法的返回值获取需要加载的@Configuration类及其所包含的bean。需要注意,IoC容器在获取的同时会通过getExclusionFilter()方法进一步执行过滤。除此之外,我们还可以通过ImportSelector的衍生类DeferredImportSelector来延迟加载@Configuration类。DeferredImportSelector会在其他所有@Configuration类加载后再进行加载,而不同DeferredImportSelector之间则是通过Ordered接口或@Order注解来指定其执行顺序的。

    对于selectImports方法的AnnotationMetadata参数则表示@Import注解所修饰类的元信息。

  • ImportBeanDefinitionRegistrar

    ImportSelector相似,将ImportBeanDefinitionRegistrar传入@Import同样可以对额外的bean进行注册和加载。与之相比,ImportBeanDefinitionRegistrar更接近底层,是直接通过构建BeanDefinition注册到容器中的。

    ImportBeanDefinitionRegistrar除了可以配置到@Import外,还能配置到ImportSelector#selectImports方法中。

    java 复制代码
    /**
     * Interface to be implemented by types that register additional bean definitions when
     * processing @{@link Configuration} classes. Useful when operating at the bean definition
     * level (as opposed to {@code @Bean} method/instance level) is desired or necessary.
     *
     * <p>Along with {@code @Configuration} and {@link ImportSelector}, classes of this type
     * may be provided to the @{@link Import} annotation (or may also be returned from an
     * {@code ImportSelector}).
     *
     * <p>An {@link ImportBeanDefinitionRegistrar} may implement any of the following
     * {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
     * methods will be called prior to {@link #registerBeanDefinitions}:
     * <ul>
     * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
     * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
     * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
     * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
     * </ul>
     *
     * <p>Alternatively, the class may provide a single constructor with one or more of
     * the following supported parameter types:
     * <ul>
     * <li>{@link org.springframework.core.env.Environment Environment}</li>
     * <li>{@link org.springframework.beans.factory.BeanFactory BeanFactory}</li>
     * <li>{@link java.lang.ClassLoader ClassLoader}</li>
     * <li>{@link org.springframework.core.io.ResourceLoader ResourceLoader}</li>
     * </ul>
     *
     * <p>See implementations and associated unit tests for usage examples.
     *
     * @author Chris Beams
     * @author Juergen Hoeller
     * @since 3.1
     * @see Import
     * @see ImportSelector
     * @see Configuration
     */
    public interface ImportBeanDefinitionRegistrar {
    
        /**
         * Register bean definitions as necessary based on the given annotation metadata of
         * the importing {@code @Configuration} class.
         * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
         * registered here, due to lifecycle constraints related to {@code @Configuration}
         * class processing.
         * <p>The default implementation delegates to
         * {@link #registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)}.
         * @param importingClassMetadata annotation metadata of the importing class
         * @param registry current bean definition registry
         * @param importBeanNameGenerator the bean name generator strategy for imported beans:
         * {@link ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR} by default, or a
         * user-provided one if {@link ConfigurationClassPostProcessor#setBeanNameGenerator}
         * has been set. In the latter case, the passed-in strategy will be the same used for
         * component scanning in the containing application context (otherwise, the default
         * component-scan naming strategy is {@link AnnotationBeanNameGenerator#INSTANCE}).
         * @since 5.2
         * @see ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR
         * @see ConfigurationClassPostProcessor#setBeanNameGenerator
         */
        default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
                BeanNameGenerator importBeanNameGenerator) {
    
            registerBeanDefinitions(importingClassMetadata, registry);
        }
    
        /**
         * Register bean definitions as necessary based on the given annotation metadata of
         * the importing {@code @Configuration} class.
         * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
         * registered here, due to lifecycle constraints related to {@code @Configuration}
         * class processing.
         * <p>The default implementation is empty.
         * @param importingClassMetadata annotation metadata of the importing class
         * @param registry current bean definition registry
         */
        default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        }
    
    }

    ImportBeanDefinitionRegistrar中,我们可以在registerBeanDefinitions方法上构建相应的BeanDefinition,将它注册到BeanDefinitionRegistry,并最终加载到IoC容器。不过需要注意的是,由于@Configuration相关生命周期的限制BeanDefinitionRegistryPostProcessor是不可以在这里被注册的。

    对于registerBeanDefinitions方法的AnnotationMetadata参数则表示@Import注解所修饰的类的元信息。

更多详情可阅读一下资料:

相关推荐
冰淇淋烤布蕾11 分钟前
EasyExcel使用
java·开发语言·excel
拾荒的小海螺17 分钟前
JAVA:探索 EasyExcel 的技术指南
java·开发语言
Jakarta EE33 分钟前
正确使用primefaces的process和update
java·primefaces·jakarta ee
马剑威(威哥爱编程)42 分钟前
哇喔!20种单例模式的实现与变异总结
java·开发语言·单例模式
java—大象1 小时前
基于java+springboot+layui的流浪动物交流信息平台设计实现
java·开发语言·spring boot·layui·课程设计
ApiHug2 小时前
ApiSmart x Qwen2.5-Coder 开源旗舰编程模型媲美 GPT-4o, ApiSmart 实测!
人工智能·spring boot·spring·ai编程·apihug
杨哥带你写代码2 小时前
网上商城系统:Spring Boot框架的实现
java·spring boot·后端
camellias_2 小时前
SpringBoot(二十一)SpringBoot自定义CURL请求类
java·spring boot·后端
布川ku子2 小时前
[2024最新] java八股文实用版(附带原理)---Mysql篇
java·mysql·面试
向阳12182 小时前
JVM 进阶:深入理解与高级调优
java·jvm