Spring Framework框架起步,小白都看得懂(官翻版)!

写在开头

本篇章介绍Spring框架的完整的全部技术。写这篇文章的目的是,一方面为了给初入后端开发的才子一篇入门指导,另一方面是为了自己对于基础知识的查阅。

Spring不一定是最好的框架(虽然综合能力最强了),但是一定是生态最好的。社区,官方文档,更新迭代等,一直很有生力。我接触的框架虽说不多,但是学起来能给我带来那种惊艳,顺滑的框架,只有Spring和Netty了。所以,在考研完全放下开发之前,我打算写一篇文章,好好地说清楚Spring到底是什么。

本人偏爱Java Configuration(也是《Spring实战》(我的Spring入门书,挺不错的)推荐的方式),所以同样配置的情况下,将略去XML配置不表,请知悉。

官翻版本:Spring Framework Core 5.2.5.RELEASE

因为考虑到考研,项目开发等,所以文章更新会慢一些(其实主要是翻译总结是个体力活,我也有点懒…),如果你喜欢的话,也可以私信我提醒更新!

QQ: 2508826394
VX: d2508826394
Mail: 2508826394@qq.com/jokerblack2508826394@gmail.com
掘金: 直接私信我就看得到

本人能力有限,如果你觉得哪里叙述的有问题,请务必联系我,避免误导他人,非常感谢!对于我个人拿捏不准的翻译部分,我会直接附属原文。

需要的基础知识(操作):

Java SE(Version 11.0+)
Maven(Version 3.62+)
JetBrains IntelliJ IDEA(Version 2019.3.4+)
GitHub

全文大约2万字,请知晓!

出现的晦涩词汇的理解

配置元数据:配置源或配置文件的意思。

依赖:顾名思义,构建某个实例需要的其他实例,因为依赖于这个实例,所以叫“依赖”。

Bean:中文翻译“豆子,黄豆”,就是一个类实例,由框架进行生成和销毁。

DAO: 数据库访问类(Data Access Object),就是一类专门用来访问数据库的Bean

耦合:依赖的意思,A和B耦合性越高,说明它俩互相依赖性越强,软件设计讲究低耦合,是为了后期维护方便,免得“牵一发而动全身”。

属性(Property):属性,其实就是私有域。

POJO:中文翻译,简单Java对象。就是一个类,类似于Bean,作为数据载体,只有setter和getter方法,有时会覆写hashCode(), equals(), toString()方法等。

AoolicationContext:应用程序上下文,啥意思呢?都做过阅读理解吧?有一种题叫“请结合上下文分析XXXX”,没错,就是那个上下文,在Spring里面的意思是,整个程序所处的环境,你可以在ApplicationContext获取到Bean,配置文件,系统资源,启动项信息等。所以“上下文”的意思就是,程序的管理者,它拥有整个程序,可以渗透到各处进行管理操作。(我当初真的理解了好久!)

回调方法:就是在某个操作完成后,会自动的调用这个方法,在监听者设计模式中有详细的说明。

Post:中文翻译既有在…之后的意思,还有应用的意思,在后面的类名理解里可能会有用,所以特地拿出来。

Aware:Aware系的接口,带有入侵性,可以让实现它的Bean拥有从容器获取Bean基本信息(比如,名字,所属类)的能力,类似于给这个Bean开了VIP会员特权。

绪论

在Spring Framework中,最重要的莫过于Spring Framework的Inversion Of Control(IOC)控制反转。本文会先介绍IOC技术,之后是完整且全面的Spring Framework Aspect-Oriented Programming(AOP)面向切面编程。从概念上说,Spring的AOP是很好理解的,同时它也可以解决企业编程的80%的AOP问题。

Spring整合了AspectJ(一个当前特性最丰富,最成熟的Java企业级AOP实现)。(暗示Spring的AOP很牛逼)

IOC容器

Spring IOC容器和Bean介绍

IOC也作为Dependency Injection(DI)依赖注入而被人知晓。它是一个定义对象依赖(Dependency)的过程,对象从构造参数,工厂方法参数,属性值获取创建此对象需要的依赖(其实就是其他对象)。

容器(Container)就有点秀了!它在对象需要依赖时(需要其他对象时)主动地注入进去,这个过程本质上通过直接构造类或者类似服务定位模式的机制来取代bean实现对于依赖的实例化或定位过程,“接管了”对象对于依赖的控制,就像“反转一样”,所以叫控制反转(Inversion of Control)。

org.springframework.beans和org.springframework.context这两个包是Spring Framework IOC容器的基础包,其中:BeanFactory接口提供了一种高级配置机制,这种机制可以管理任何类型的对象。ApplicationContext是它的一个子接口,它拥有:

a)更简单地整合Spring AOP特性。
b)管理资源处理。
c)发布事件。
d)应用层次的特殊上下文(context),比如在Web应用中的WebApplicationContext。

简而言之,BeanFactory提供了配置框架和基础功能,ApplicationContext添加一些企业开发方面的功能。Application是BeanFactory的一个完全的超集,本章仅仅用它来描述Spring IOC容器,想要了解更多关于BeanFactory,详见这里

在Spring里,构成你应用骨干的对象实例被称为“bean”,bean是啥呢?是一个实例化了的,组装了的,同时被Spring IOC容器管理的对象。bean仅仅是你应用中的众多对象之一。所有的Bean以及他们所使用的依赖,都被容器使用配置元数据利用反射管理着。

容器概览

org.springframework.ApplicationContext接口代表了Spring IOC容器,它负责实例化,配置和装配bean。容器通过读取配置元数据来决定对哪个对象进行实例化,配置,和装配。元数据以XML, Java注解或Java代码形式。它让你可以清楚地指明构成你应用的对象,以及它们之间丰富的内部依赖关系。

下图展示了Spring是怎么工作的:

配置元数据

正如图所示的那样,Spring IOC容器使用一个配置元数据构成的表单。这个含有配置元数据的表单,代表着你,也就是此应用的开发者,希望Spring容器应该怎样实例化,配置和装配你应用中的Bean组件。

传统的设置配置元数据的方法是使用简单直观明了的XML形式,这也是本章主要的配置方法。

P.s.
XML并不是唯一的格式,Spring IOC容器已经从之解耦出来了,当下,越来越多的开发者使用基于Java注解的配置方法(比如我)。

想要了解更对其他配置格式?

*基于注解的配置(Spring 2.5引入)

*基于Java的配置(Spring 3.0引入,一些由Spring JavaConfig提供的特性逐渐成了Spring Framework的核心,所以你可以使用Java配置为你的应用提供外部Bean而不是使用XML文件,想使用这些新特性?试试@Configuration, @Bean, @Import, @DependsOn)

Spring配置考虑到容器至少管理一个(通常很多个)Bean定义,Java配置在@Configuration注解修饰的类里面使用@Bean注解来完成定义这些Bean。

这些Bean定义与实例对象联系起来然后修饰你的应用。通常情况下,你会定义业务层对象,比如数据库访问对象(DAO. Data Access Object),表示对象,例如Struts Action实例,基础结构对象,例如Hibernate SessionFactories,JMS队列,诸如此类。一般来说,加载域对象是DAO或业务逻辑的职责,所以容器里面不会有细粒化的域对象。不过,你可以使用Spring对于AspectJ的整合来配置IOC容器控制之外创建的对象。详见

简单用法:

public class Book
{
    // ...
}

@Configuration
public class WebConfig
{
    // ...
    @Bean(name = "book")
    public Book book()
    {
        return new Book();
    }
}

XML配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">  
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>

若需要在某个配置类引用其他的配置类,可以在类前面加上:

@Import(value = {ConfigA.class, ConfigB.class})

或XML形式:

<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>
使用容器

通过使用:

T getBean(String name, Class<T> requiredType)

方法,可以获取Bean实例。

但是,理想情况下,你不应该使用getBean()方法,因为这会破坏容器对于对象的自动管理,你可以使用诸如@Autowired的注解来在特殊Bean里面获取你需要的依赖。(说白了,别有事没事调用getBean(),测试除外,那不然,你还有IOC容器帮你管理干啥?直接自己整呗!弟弟!)

Bean概览

在IOC容器里面,Bean的定义以BeanDefinition的形式呈现。它们包含以下的元数据:

a)包限定名的类,包含了Bean的实现类。
b)Bean行为定义组件,定义了Bean在IOC容器里面的行为。
c)对于此Bean需要的其他Bean的引用,这些引用又被称为“合作者”或“依赖”。
d)其他用于设置新创建的Bean的配置,比如超时时间,数据库连接池数量等。

元数据被传递到一个配置集合中,以用来表达Bean定义。

具体的Bean配置集合,详见

ApplicationContext还允许用户在容器外面自定义Bean。通过ApplicationContext的getBeanFactory()方法获取,此方法返回BeanFactory的DefaultListableBeanFactory实现,DefaultListableBeanFactory支持通过registerSingleton(…)和registerBeanDefinition(…)来注册Bean。不过,通常情况下,应用只能和通过配置元数据定义的Bean协作。

命名Bean

一个Bean通常含有一个或多个唯一标识符。如果不指出bean名字,就会使用默认名(类名首字母小写),bean命名遵守Java命名规范,首字母小写+驼峰命名。当然还可以使用别名(说真的,一般没用过)。

<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
实例化Bean

对于XML,应该指出(强制地)其类型。在这种方式下,可以通过调用类的构造器创建,等同于new一个实例;还可以调用静态的工厂方法来创建。使用构造器创建时,容器会自己寻找构造器,所以你就只需要像编写普通类那样就好,这里指出,最好有一个无参构造器。

比方说:

<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>

Spring容器不仅仅可以管理实体类,虚拟类也可以被其管理,比如,不符合Java Bean规范的数据库连接池。

对于XML配置,使用静态工厂方法时,class指出工厂类,factory-method指出具体的静态方法。若是使用实例工厂方法,把class属性换成factory-bean并指向工厂类就好。

例如:

<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>

实例工厂方法:

<!-- 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"/>

依赖

典型的企业级应用通常不会包含单例对象,即使是最小的应用,它的某个对象也需要别的对象的嵌入才能运行。本节介绍如何通过定义Bean配置来实现把单个的对象组织起来,实现完全解耦合的应用。

依赖注入

Dependency Injection依赖注入(DI)指的是这样的一个过程:对象只能通过构造器参数,工厂方法参数,在对象实例被构造之后的属性值或工厂方法的返回值来定义它们的依赖。容器在创建这些Bean时主动注入这些依赖,这个过程称为对于Bean自己控制依赖的获取,定位这一职责的反转。

归功于DI的存在,代码更加简洁明了,解耦更加高效。Bean不在主动引入他们需要的依赖,所以测试更加容易。介于某些Bean是接口的实现类,所以抹除某些特性并加入新的来测试,模拟都变得那么简单!

  1. 基于构造器的DI

基于构造器的DI,由容器调用无参或有参构造器完成。调用静态工厂方法也可以起到相同的效果,以下展示一个构造器注入的例子:

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private MovieFinder movieFinder;

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

XML格式

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>

类型匹配:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

顺序匹配:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

名称匹配:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>
  1. 解析构造参数

这玩意属于XML的了,说白了就是,构造器参数匹配顺序出现歧义时,应显式地指明顺序。在Java Configuration里面,直接@Autowired就完事了,整这么多记不住的!

  1. 基于setter的DI

容器在调用过你的无参构造器或无参静态工厂方法后,调用你的setter方法完成依赖注入。来个例子看看:

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;

    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

除了以上两种注入,ApplicationContext还支持在构造器注入之后再次通过调用setter进行注入。

构造器注入和setter注入比较:

在setter方法上使用@Required注解可以使此属性成为必须注入的依赖。带有参数验证的控制器注入是更好的选择。

Spring 团队建议使用构造器注入,这样可以更大程度上减少组件构建时参数变化带来的风险,以及非空判定。长远角度来说,此方法构建的Bean通常是初始化状态的完美Bean。过多的构造参数,可能说明你的代码写得太垃圾了,重构吧!少年!

setter方法注入,一般适用于可选依赖,这种依赖一般可以被指定一个默认值,但是,还是得注意对于它的非空检查。setter注入的好处在于,后期可以再次配置或注入依赖,增加了变通性。

使用依赖注入,可以让第三方对象也更加方便的享受到容器管理的便捷,但是一旦第三方类没有setter方法,就只能通过构造器注入依赖了。

  1. 依赖解析过程

容器进行依赖解析过程如下:

a)通过配置元数据(比如xml文件,Java代码,注解)来创建和初始化ApplicationContext。
b)对于每个Bean,它的依赖项都以属性,构造器参数,静态工厂方法参数呈现出来,当Bean被创建时,依赖会自动添加进去。
c)每一个属性或构造器参数,都是容器里面的一个对于其他Bean的引用,或准备设置的值的实际定义。
d)每一个属性或构造器参数的值,都是从特殊类型转换到属性或构造器参数的实际类型之后的值。默认情况下,Spring可以把String类型的值转换成八大基本类型。

在容器创建时,Spring会验证每个Bean配置的合法性,Bean属性在Bean被创建之前,一直不会被设置。单例和预创建的Bean会在容器创建时一同被创建,其余情况均为需要创建时才会创建,因为每次创建都会引发连锁反应(Bean的依赖被创建,以来的依赖被创建…),所以对于不匹配的解析可能会在稍后出现。

注意避免循环依赖,比如A依赖B,B依赖C,C依赖A。对于这个种情况,要注意避免,比如通过基于setter的DI来降低依赖。

Spring容器会在加载期间推断潜在的问题(比如空指针,循环引用)。Spring尽可能迟地设置属性,这表明,可能一开始创建Bean正常,但过会就会抛异常。这也就是为什么ApplicationContext使用预创建作为默认方式的原因。在ApplicationContext创建时,以时间和内存换取更加可靠地系统(发现更多潜在的问题),而不是在需要创建时才发现问题,正是Spring采取的方式。当然,你可以覆写这个策略,实现单例创建(尽可能迟地创建)。

如果啥问题也么得,Spring就会开始注入Bean了,此时Spring会提前配置好需要创建的Bean所需要的依赖。这意味着,如果A需要B,那么Spring可能会在初始化A之后,然后配置B,再然后调用A的setter方法来完成DI。换句话说,一个Bean被创建,然后调用它的setter方法注入依赖,然后相关的生命周期方法会被调用。

  1. 依赖注入的例子
<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
依赖和细节配置

正如前面提到的那样,你可以通过引用其他被容器管理着的Bean会内置的值来实现对属性或构造器参数的设置。

  1. 直接值

就,字面意思,写在属性标签(<property/>)里面的字符串,可作为属性值进行注入,这属于XML文件了,典型的用法就是MyBatis数据库配置。

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="masterkaoli"/>
</bean>

p-namespace实例:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3306/mydb"
        p:username="root"
        p:password="masterkaoli"/>

</beans>

java.util.Properties实例:

<bean id="mappings"
    class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">

    <!-- typed as a java.util.Properties -->
    <property name="properties">
        <value>
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        </value>
    </property>
</bean>

idref标签:

<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean"/>
    </property>
</bean>

等同于下面这个(运行时)

<bean id="theTargetBean" class="..." />

<bean id="client" class="...">
    <property name="targetName" value="theTargetBean"/>
</bean>
  1. 引用其他的Bean

也是字面意思`,通过引用此容器管理的其他Bean来设置当前Bean的属性。

实例:

<bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
</bean>
  1. 内部Bean

为了创建此Bean而创建的一个匿名Bean,就像在类里面创建了匿名类一样;无法被容器管理,访问,注入,只是一个生成此Bean的辅助Bean。

实例:

<bean id="outer" class="...">
    <!-- instead of using a reference to a target bean, simply define the target bean inline -->
    <property name="target">
        <bean class="com.example.Person"> <!-- this is the inner bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>
  1. 集合

集合标签可用于设置Bean的集合类型,常见的集合均可使用。

看一个例子:

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.org</prop>
        </props>
    </property>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

对于集合合并

<beans>
    <bean id="parent" abstract="true" class="example.ComplexObject">
        <property name="adminEmails">
            <props>
                <prop key="administrator">administrator@example.com</prop>
                <prop key="support">support@example.com</prop>
            </props>
        </property>
    </bean>
    <bean id="child" parent="parent">
        <property name="adminEmails">
            <!-- the merge is specified on the child collection definition -->
            <props merge="true">
                <prop key="sales">sales@example.com</prop>
                <prop key="support">support@example.co.uk</prop>
            </props>
        </property>
    </bean>
<beans>
  1. Null和Empty的String值

空字符串就是属性值为"",若不设置属性,属性就为Null。

  1. 复合属性名

就也是字面意思,相当于属性的属性,给属性的属性设值。

比如:

<bean id="something" class="things.ThingOne">
    <property name="fred.bob.sammy" value="123" />
</bean>
使用depends-on

可以强制此Bean所使用的依赖在初始化此Bean钱被强制初始化。

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

// or
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
延迟初始化Bean

是一个属性,指出是否延迟初始化。

<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>

容器级别的延迟:

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>
自动织入协作器

通过设置<bean/>的autowire属性,即可设置自动织入模式。
自动织入的优点:

a)大大减少指定属性或构造器参数的需要。
b)当你的应用更新时,自动织入可以更新配置。这个特性,在开发方面非常有用。

自动织入有四个模式

a)no: 不开启自动织入,通过引用其他Bean实现引用依赖。
b)byName: 根据属性名自动织入
c)byType: 如果容器存在此属性类型,便织入。
d)constructor: 类似byType,但是只适用于构造器参数。

自动织入的局限性和不足:

暂略不表。

去除自动织入中的Bean

暂略不表。
方法注入

背景:大多时的场景,大多数的Bean都是单例的,当一个单例Bean需要和另一个单例Bean协作时,或一个非单例Bean需要和另一个非单例Bean合作时,通常你会通过把某个Bean定义成另一个(Bean)的属性来处理依赖问题。当Bean生命周期不一致时,嚯嚯,问题来了。假设在A的每个方法调用上,单例Bean A要使用非单例Bean B。容器只会创建A的实例一次,所以只有一次设置属性B的机会,但是容器可不能在每次需要B的时候都提供一个新的实例啊!这就造成了问题。

解决措施之一是,暂时取消一些IOC,你可以通过实现ApplicationContextAware接口来让A发现容器,并通过调用容器的getBean(“B”)方法来在每次需要B实例时,获取一个新的实例。来看一个例子:

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

以上代码耦合到了Spring框架,所以不是很推荐,但是这种方法确实可以让你写出干净的处理代码。

  1. 查找方法注入

查找方法注入是容器使用容器所管理的另一个Bean的方法来来重写当前Bean的某个方法的能力。查找通常调用原型Bean,Spring通过CGLIB的动态代理来动态地生成子类,借此实现这个方法的注入。

或者这么说(个人理解),对于抽象类的某个方法,没有实现,Spring会使用动态代理来覆写或添加其子类的方式来实现对于此抽象类的方法的实现,@Lookup就是告诉Spring,使用哪个类的同名方法来实现。

假设存在如下需要代理的类:

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

被代理的方法,应该具有以下方法签名:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

对于XML形式,有如下方法:

<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>

对于注解形式,可以这么声明查找方法:

public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}

或这样,一种更加简洁的方法,通过返回值查找Bean:

public abstract class CommandManager {

    public Object process(Object commandState) {
        MyCommand command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract MyCommand createCommand();
}
  1. 任意方法替换

另一种稍不如查找方法的使用方式,是用另一个方法实现,替换容器里面的Bean的任意方法。可以先跳过,待到日后需要再来看。
考虑如下类:

public class MyValueCalculator {

    public String computeValue(String input) {
        // some real code...
    }

    // some other methods...
}

然后有一个类,提供了此方法的新的实现,如下所示:

public class ReplacementComputeValue implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it, and return a computed result
        String input = (String) args[0];
        ...
        return ...;
    }
}

可能是通过类名来确定唯一替换的…

类名格式:Replacement+需要替换的方法名+implement MethodReplacer。

XML如下所示:

<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
    <!-- arbitrary method replacement -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

Bean作用域

当你创建一个Bean定义时,相当于你也创建了一个基于此Bean定义的规则,此规则指出了如何创建类的实例,是单例还是原型Bean。这是一个重要的思想,介于此,你可以为某个类创建多个基于同一创建规则的对象实例。

你不仅能控制注入到从一个Bean定义所创建的对象的各种依赖和配置的值;你还可以控制从一个确切的Bean定义所创建的对象的作用域。这种方法是强有力且灵活的,因为你可以通过配置来选定对象的作用域而不是在Java类级别上重新选定对象作用域。Bean可以用于其中之一的作用域,Spring Framework拥有六中作用域,只有当你编写Web-Aware ApplicationContext时,后四个才可用。当然,亦可自定义作用域。

看一个描述表格:

作用域描述
单例(singleton)对于每个IOC容器的所有同一Bean的引用,都共享同一个Bean实例。
原型(prototype)对于每一个引用,都创建一个新的实例。
请求(request)每一个HTTP请求都有自己的实例对象引用。
会话(session)把范围限定为一个HTTP Session生命周期内。
应用(application)把范围限定为ServletContext生命周期内
WebSocket(websocket)把范围限定为WebSocket声明周期内。
线程(thread)仅限定Spring 3.0+,参见详见
单例Bean

咳咳,啥叫单例Bean呢?就是在容器里面,只有一个被共享的Bean实例被容器管理着,这种情况就叫"单例Bean"。所有对于此Bean的引用,请求,都会得到同一个实例。

当你定义一个单例Bean时,Spring IOC容器只会根据Bean定义,创建一个此Bean的实例,然后缓存在单例Bean缓存池里面(里面全是单例的Bean),然后所有对于此Bean的请求全部都会得到此单例Bean的引用。

…官方文怎么说的那么瘪嘴呢?(大雾?)。说白了,就是所有的Bean对象引用共享一个实例;不同于new那种,每个对象都能有自己的实例;全局变量懂吧?大家都用它的那种!那就是单例Bean。

来个图康康

对于基于注解的配置,如下使用方法:

@Scpoe(ConfigurableListableBeanFactory.XXX)
@Component
public class ClassA
{
    // ...
}

XML用法:

<bean id="accountService" class="com.something.DefaultAccountService"/>

<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
原型Bean

原型Bean不同于单例Bean,容器会为每个请求都创建一个实例对象,你应该知道,对于有状态的Bean,应该使用原型Bean,对于无状态的Bean,建议使用单例Bean。

来张图康康:

那啥,DAO组件通常应定义为单例的,因为它不具备对话状态。

来个原型Bean设置样例看看:

<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>

不同于单例的Bean,原型Bean仅由容器创建,而容器并不负责其完整的生命周期,所以,原型Bean的资源释放,要手动处理。或者通过使用自定义的bean post-processor。

在某种程度上来说,Spring 容器之于原型Bean就像Java的new一样,除了创建之外的所有生命周期方法应该由编写者处理。

含有原型Bean依赖的单例Bean

倘若某个单例Bean需要原型Bean作为依赖,请记住,对于依赖的每次注入,都是一个全新的实例,不同属性之间没法共享同一个依赖实例,如果想共享,那就不妨考虑一下方法注入

Request, Session, Application和WebSocket作用域

在使用之前,通常需要做一些初始化步骤,但是并不是必须的。如果你用的是Spring MVC的话,DispatcherServlet会帮你做足准备工作,某些老旧的Servlet容器可能需要你自己配置某些起步需要。

  1. 请求域

先说怎么创建一个请求域的Bean

<bean id="loginAction" class="com.something.LoginAction" scope="request"/>

或Java配置

@RequestScope
@Component
public class LoginAction {
    // ...
}

Request类型的Bean生命周期仅为当前request请求,请求结束被创建,请求完成被销毁。

  1. 会话域

创建:

<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

Java配置

@SessionScope
@Component
public class UserPreferences {
    // ...
}

生命周期同一个HTTP Session一致,会话被创建,此类Bean也会被创建,会话被销毁,此类Bean也会被销毁。

  1. 应用域

创建:

<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>

Java配置:

@ApplicationScope
@Component
public class AppPreferences {
    // ...
}

类似于单例Bean,因为整个Web应用都共享同一个实例,这个Bean是作为ServletContext的一个属性来设置的,所以会一致存在于整个Web中。同单例Bean的区别在于:它是属于ServletContext而不是ApplicationContext的;二是它是作为ServletContext属性来使用的。

  1. 使用被限定的Bean作为依赖。

对于使用HTTP-Request范围的Bean作为依赖注入到一个长生命周期的Bean中去,可能需要代理,一种方法是使用AOP代理。

这个用的不多,先跳过,原文链接放这

  1. 选择要创建的代理类型

同上,先略过。

自定义作用域
  1. 创建一个自定义作用域

想把你自己的scope整合到Spring容器里面,你需要实现org.springframework.beans.factory.config.Scope接口。查看Java Doc以便了解更多。

Scpoe接口有四个方法来获取,移除,销毁对象。

Object get(String name, ObjectFactory<?> objectFactory)

Object remove(String name)

void registerDestructionCallback(String name, Runnable destructionCallback)

String getConversationId()
  1. 使用一个自定义作用域

你需要把Scope注册到Spring容器,通过ConfigurableBeanFactory的registerScope方法来注册,而ConfigurableBeanFactory可以通过ApplicationContext的BeanFactory属性获取。

使用示例:

Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);

自定义Bean属性

Spring提供了一系列接口,用以实现自定义Bean属性。

生命周期回调

为了使你自己的Bean与容器互动,你要实现InitializingBean接口和DisposableBean接口,容器会在前阶段调用afterPropertiesSet()方法,后阶段调用destroy()方法,来使你的Bean在初始化和销毁时产生某一行为。

在Spring内部,Spring框架会使用BeanPostProcessor实现类来处理它所能找到的回调接口,并调用合适的方法。如果你想自定义某个Spring没有默认提供的行为,通过实现BeanPostProcessor即可,详细信息在这里

为了初始化和销毁回调,Spring管理着的对象可能还需要实现Lifecycle接口,以至于这些对象可以进行实际上的“开启”和“关闭”操作,正如被容器自己的生命周期所驱动着那样(言外之意,和容器生命周期一致)。

  1. 初始化回调

org.springframework.beans.factory.InitializingBean接口让Bean在容器为其添加全部依赖后拥有初始化行为的能力。方法声明如下:

void afterPropertiesSet() throws Exception;

但是我们不建议你使用此接口,因为这会把代码耦合到Spring。我们推荐使用@PostConstruct注解,或者为POJO指定一个初始化方法。指定初始化方法样例:

XML配置

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>

Java配置

public class BeanOne {

    public void init() {
        // initialization logic
    }
}

public class BeanTwo {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

注意,初始化方法,应该是一个无参方法。

以上方法等效于下面这个:

public class AnotherExampleBean implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
        // do some initialization work
    }
}
  1. 销毁回调

org.springframework.beans.factory.DisposableBean接口提供了在Bean被销毁时可以采取的措施,方法声明如下:

void destroy() throws Exception;

同样,Spring不建议使用此接口,而是使用@PreDestroy注解或指定销毁方法。先上样例看看:

<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>

Java配置

public class BeanOne {

    public void init() {
        // initialization logic
    }
}

public class BeanTwo {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

销毁方法应该也是无参的。

以上方法等同于下面这个:

public class AnotherExampleBean implements DisposableBean {

    @Override
    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}
  1. 默认初始化和销毁方法

除了通过指定初始化和销毁方法外,Spring还提供按名字查找的方式来主动地调用初始化或销毁方法,这就是默认情况。默认按照名字来查找并调用,但是这些默认方法,名字必须符合规范,比如初始化方法叫"init()",销毁方法叫"destroy()",此时它们就会被当做默认方法,供Spring容器调用。

比如:

public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

    public void setBlogDao(BlogDao blogDao) {
        this.blogDao = blogDao;
    }

    // this is (unsurprisingly) the initialization callback method
    public void init() {
        if (this.blogDao == null) {
            throw new IllegalStateException("The [blogDao] property must be set.");
        }
    }
}

对于XML格式

<beans default-init-method="init">

    <bean id="blogService" class="com.something.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>

</beans>

默认方法应声明在顶级<beans/>标签里,当然,也可以通过在<bean/>标签指定初始化或销毁方法来覆写默认的方法。

  1. 组合生命周期机制

Spring提供了多种机制,现在是时候去总结他们了。

可以产生初始化行为的操作:

a)用@PostConstruct注解标注的方法
b)InitializingBean接口的afterPropertiesSet()方法
c)自定义的init()方法

可以产生销毁行为的操作:

a)@PreDestroy注解标注的方法
b)DisposableBean接口的destroy()方法
c)自定义的destroy()方法
  1. 开启和关闭(生命周期)回调

Lifecycle接口为任何拥有自己的生命周期需求的对象定义了几个潜在的方法。看看接口:

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

任何由Spring管理的对象都有可能实现此接口,当ApplicationContext自己被启动或销毁时,它会按层级结构似的触发它所管理的Bean的启动(start),关闭(stop)方法。当然,它是通过把此过程代理给LifecycleProcessor接口来实现的,看定义:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

此接口除了继承自Lifecycle的三个方法之外,还添加了两个自己的方法,用于当ApplicationContext刷新和关闭时也对其所管理的Bean执行相同的操作。

注意:Lifecycle是一个普通的协议,仅适用于显式地启动与停止,但是不适合刷新阶段的自启动,如果想实现自启动,考虑org.springframework.context.SmartLifecycle接口。当然,你也得知道停止信号并不保证会在销毁方法调用时通知到位。在普通停止阶段,停止信号会先于销毁方法传递给相关的Bean,但是在热刷新或终止刷新尝试阶段,只有销毁方法会被调用。

关闭和启动的顺序尤为重要(废话),某个Bean应该在它的依赖被启动后才能启动,并应该先于它的依赖执行关闭操作。但是很多时候,仅能根据类型进行判断先后顺序,这是不够的,我们需要更加细粒化的方法,org.springframework.context.SmartLifecycle接口继承自Phased接口提供了这样的选择。看看代码:

public interface Phased {

    int getPhase();
}

再来看看SmartLifecycle:

public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

当容器启动时,拥有最小值的对象启动最早,不过也关闭最晚。意味着,若把phase设置为Integer.MIN_VALUE,那么此对象会最先启动;
当phase设置为Integer.MAX_VALUE时,对象会最后启动,最先关闭。

由SmartLifecycle接口定义的停止方法会接收一个回调,任何实现了此接口的类必须在关闭过程结束了后,调用回调方法的run()方法。它可以保证在必要的时候进行异步关闭,因为LifecycleProcessor默认实现类DefaultLifecycleProcessor会在每个阶段等待超时时间到来以进行调用那个回调。默认时限是30s,当然,你可以自己修改此时间。你可以通过实现名字为"lifecycleProcessor"的Bean来实现对LifecycleProcessor的覆写,当然,这种方式可以设置超时时间。

对于LifecycleProcessor接口,他也同样定义了对于ApplicationContext的刷新和关闭操作。后者只有在stop()被显式地调用时才会执行关闭操作;但同样会发生在ApplicationContext被关闭时。当ApplicationContext被刷新时,默认lifecycle处理器会检查每个SmartLifecycle对象的isAutoStartup()返回值以判定是否对其进行重启操作。phase属性和depends-on所定义的依赖,会根据前面所阐述的规则,来判断启动顺序。

  1. 优雅地在非web应用中关闭Spring IOC容器

如果你使用的是非web应用的话,记得绑定一个关闭操作到JVM上,这样可以释放单例Bean所占有的资源。通过把关闭操作注册到JVM上,即可实现“优雅地”关闭应用,用法如下:

调用ConfigurableApplicationContext的registerShutdownHook()方法。

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...
    }
}
ApplicationContextAware和BeanNameAware

当ApplicationContext创建了一个实现了org.springframework.context.ApplicationContextAware接口的实例对象,这个实例对象就获得了一个指向ApplicationContext的引用,如下是此接口的定义:

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

因此,Bean可以程序化的人为管理ApplicationContext,通过把ApplicationContext接口强制转换到此接口的子类,可以获得更多的用法。其中一个典型的用法莫过于获取其他Bean。有时此功能是有用的,但是你应该避免使用它,因为它破坏了IOC的特性。ApplicationCOntext的其他方法提供了诸如访问文件资源,发布应用事件,访问消息资源的能力,详见更多用法,参考这里

自动织入是另一个获取ApplicationContext的方法,传统的方法是使用基于类型对ApplicationContext进行注入到构造器或setter方法里面。一种更加灵活的做法是,使用自动织入特性对域或方法参数进行织入。这是使用@Autowired注解进行织入的方法,也是我喜欢的方式。

当一个Bean实现org.springframework.beans.factory.BeanNameAware接口时,就被提供了一个可以修改它的Bean定义中Bean名字的方法,如下描述:

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}
其他Aware接口

除了ApplicationContextAware和BeanNameAware之外,Spring还提供了一系列Aware接口供Bean使用,来向容器表明他们所需要的某些基础依赖。

看个表:

(原文不太好翻译,为了防止产生歧义,直接放原文)

NameInjected DependencyExplained in…
ApplicationContextAwareDeclaring ApplicationContext.ApplicationContextAware and BeanNameAware
ApplicationEventPublisherAwareEvent publisher of the enclosing ApplicationContext.Additional Capabilities of the ApplicationContext
BeanClassLoaderAwareClass loader used to load the bean classes.Instantiating Beans
BeanFactoryAwareDeclaring BeanFactory.ApplicationContextAware and BeanNameAware
BeanNameAwareName of the declaring bean.ApplicationContextAware and BeanNameAware
BootstrapContextAwareResource adapter BootstrapContext the container runs in. Typically available only in JCA-aware ApplicationContext instances.JCA CCI
LoadTimeWeaverAwareDefined weaver for processing class definition at load time.Load-time Weaving with AspectJ in the Spring Framework
MessageSourceAwareConfigured strategy for resolving messages (with support for parametrization and internationalization).Additional Capabilities of the ApplicationContext
NotificationPublisherAwareSpring JMX notification publisher.Notifications
ResourceLoaderAwareConfigured loader for low-level access to resources.Resources
ServletConfigAwareCurrent ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext.Spring MVC
ServletContextAwareCurrent ServletContext the container runs in. Valid only in a web-aware Spring ApplicationContext.Spring MVC

Bean定义的继承原则

一个Bean定义可以包含许多配置信息,包括构造器参数,属性值,和特定于容器的信息,比如初始化方法,静态工厂方法名,诸如此类。一个子Bean定义(不是子Bean,定义,而是子,Bean定义;请注意断句)会继承来自父Bean定义的配置元数据。子定义可以覆写某些属性值,或者按需添加新的定义。使用父子Bean定义可以省下好多重复的编写。实际上,这是模板的一种形式。

如果程序化地使用ApplicationContext的话,子Bean定义由ChildBeanDefinition呈现。如果是XML形式的,看这里:

<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">  
    <property name="name" value="override"/>
    <!-- the age property value of 1 will be inherited from parent -->
</bean>

如果未指定子定义,那么子Bean定义会使用父Bean定义,但是也依旧可以覆写它,后者这种情况,子Bean类必须与父Bean类兼容。

子Bean定义会继承作用域,构造器参数值,属性值,和方法替换,同时拥有添加新值的能力。任何你指定的域,初始化方法,销毁方法,或静态工厂方法设置,都会覆写父定义中与之对应的设置。

下面的例子显式地把父类声明成了抽象的,若子定义没有显式地覆写这一设置,那么子定义生成的Bean也会是抽象的

<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name" value="override"/>
    <!-- age will inherit the value of 1 from the parent bean definition-->
</bean>

一般来说,把一个类声明为抽象的,就是为了作为模板,让它的子定义来覆写,任何形式的对于此抽象父定义定义的Bean的引用都会得到一个错误。

P.s.个人理解,父Bean定义就像抽象类似的,作为模板规范了子Bean定义,自身没法实例化,只能实例化它的子Bean定义定义的Bean。

注意:对于任何你只想作为模板来使用的定义,一定声明为抽象的(abstract),不然IOC容器会提前把他们实例化了。

容器的延展知识

通常,开发人员不需要专门编写ApplicationContext实现类的子类,通过注入几个特殊的组合接口的实现类就好。

通过BeanPostProcessor自定义Bean

BeanPostProcessor提供了回调方法,实现它们,你可以提供你自己的初始化逻辑。如果你想在IOC容器实例化,配置或初始化Bean之后实现一些自定义逻辑,你可以注入一个或多个BeanPostProcessor实现类。

看一下BeanPostProcessor的定义:

public interface BeanPostProcessor {
    
    // Apply this BeanPostProcessor to the given new bean instance before any bean initialization callbacks (like InitializingBean's afterPropertiesSet or a custom init-method).
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    
    // Apply this BeanPostProcessor to the given new bean instance after any bean initialization callbacks (like InitializingBean's afterPropertiesSet or a custom init-method).
    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

你也可以定义多个BeanPostProcessor,并通过设置order顺序来控制它们被执行的顺序。只有在BeanPostProcessor实现了Ordered接口后才可以设置order属性。所以,如果你实现自己的BeanPostProcessor接口的话,不妨顺便实现Ordered接口。详细信息见JavaDoc

注意:
a)BeanPostProcessor实例用来操作Bean实例,这表明,Bean创建由Spring IOC容器负责,然后剩余的工作由BeanPostProcessor完成。
b)BeanPostProcessor只能作用域一个容器或这个容器的子容器里,即使是继承同一容器的不同子容器,也没法共享BeanPostProcessor。
c)通过使用BeanFactoryPostProcessor来更改实际的Bean定义。

org.springframework.beans.factory.config.BeanPostProcessor实际上只包含两个回调方法,当这样的一个类被注册到了容器后,对于容器里面的每个Bean,post-processor(后处理器)会在容器初始化方法(比如,InitializingBean.afterPropertiesSet(),或者任何被声明的init()方法)被调用之前和任何Bean初始化回调之后会获取到一个回调方法。post-processor(后处理器)会对Bean实例采用任何行为,包括完全地忽略回调。一个Bean后处理器通常会检查回调接口,或者会通过一个代理来包装一个Bean。某些Spring AOP基础类使用后处理器的方式来提供代理逻辑。

ApplicationContext会自动推断实现了BeanPostProcessor接口的,且定义在配置元数据里面的Bean,这些Bean会被注册到容器里面,当Bean创建时,它们会被调用,BeanPostProcessor可以像普通Bean那样部署在容器里面。

当你使用@Bean注解在配置类里面声明BeanPostProcessor时,记得把返回值设置成这个类自身或,至少为BeanPostProcessor接口,显式地表明这个Bean的"后处理器属性"。否则,在完全创建它之前,ApplicationContext没法通过类型识别来推断出BeanPostProcessor。由于BeanPostProcessor需要被更早地实例化以用来处理其他的Bean的实例化,所以这点注意事项是很重要的。

注意:
程序化地注册BeanPostProcessor。虽然我们推荐的注册方式是通过ApplicationContext自动推断,但是你也可以使用ConfigurableBeanFactory的addBeanPostProcessor方法来完成注册。这种方式在你需要评估注册前的条件环境逻辑和在继承的容器之间复制Bean后处理器显得十分好用。但是这种方式没法指定执行顺序,还有就是,此方式注册的后处理器要先于自动推断注册的处理器执行。

实现BeanPostProcessor接口的类是特殊的,并且容器对它们的处理方式有所不同。作为ApplicationContext特殊启动阶段的一部分,在启动时会实例化它们直接引用的所有BeanPostProcessor实例和Bean。接下来,以排序方式注册所有BeanPostProcessor实例,并将其应用于容器中的所有其他Bean。因为AOP自动代理是作为BeanPostProcessor本身实现的,所以BeanPostProcessor实例或它们直接引用的bean都没有资格进行自动代理,因此没有织入的方面。

对于使用@Autowired注解注入的Bean,Spring可能会根据类型来找取,但是可能会找到某些意料之外的Bean,因此可能会让这些Bean没有被自动代理或后处理的资格。

接下来,来看一个使用BeanPostProcessor的例子:

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}

Spring附带的RequiredAnnotationBeanPostProcessor在允许把注解里面的值注入到属性上面的同时,还允许使用后处理器的特性。

通过BeanFactoryPostProcessor自定义配置元数据

BeanFactoryPostProcessor不同于BeanPostProcessor的地方在于,它是在配置元数据层面进行操作的,它可以读取配置元数据并在IOC容器实例化除了BeanFactoryPostProcessor类型的Bean之前更改它们, 详见

看一下定义:

public interface BeanFactoryPostProcessor {
    // Modify the application context's internal bean factory after its standard initialization.
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
}

你可以设置多个BeanFactoryPostProcessor,当然也可以通过设置order属性完成对于执行顺序的控制。前提同BeanPostProcessor一样,你得实现Ordered接口。

也可以在BeanFactoryPostProcessor中使用Bean实例,但这会导致过早地实例化,违反了标准的容器生命周期,所以不推荐。

当一个Bean工厂后处理器在ApplicationContext上下文里声明后。Spring会自动执行它。为了应用对容器内配置元数据的更改,Spring提供了一系列预声明的Bean工厂后处理器,例如:PropertyOverrideConfigurer和PropertySourcesPlaceholderConfigurer。你也可以使用自定义BeanFactoryPostProcessor,例如,注册自定义属性编辑器。

ApplicationContext会自动推断部署在容器里面,并实现了BeanFactoryPostProcessor接口的Bean,它会在合适的时候把这些Bean用作Bean工厂后处理器,你也可以像部署其他Bean那样部署Bean工厂后处理器。

这里有一篇文章,更加详细地介绍了BeanPostProcessor和BeanFactoryPostProcessor的区别和用法

通过FactoryBean自定义初始化逻辑

可以为本身就是工厂的对象实现org.springframework.beans.factory.FactoryBean接口

看一下定义:

public interface FactoryBean {
    // 一个可以被设置在BeanDefinition上的属性名,用于当无法通过工厂Bean类来推断出对象类型时,可以通过这种方式识别出来。
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
    // Return an instance (possibly shared or independent) of the object managed by this factory.
    T getObject() throws Exception;
    // Return the type of object that this FactoryBean creates, or null if not known in advance.
    Class<?> getObjectType();
    // Is the object managed by this factory a singleton? That is, will getObject() always return the same object (a reference that can be cached)?
    default boolean isSingleton() {
        return true;
    }
}

FactoryBean也是可以对Spring IOC容器实例化逻辑进行插拔操作的一个体现。如果你编写了复杂而不是冗余的XML配置,你可以考虑使用FactoryBean来进行构建或实现初始化逻辑。

Spring框架大量使用了此接口,随赠的类就有50+。当你想获取FactoryBean实例而不是它产生的Bean时,只要在getBean()方法的参数前面添加一个’&'连字符就行。比如,某个FactoryBean的id为"myBean",仅需这样做:getBean("&myBean")即可获得FactoryBean实例。

基于注解的容器配置

基于注解的配置和XML配置,究竟哪个更好呢?Spring给出的答案是:那取决于实际使用情况,二者各有优缺点。

@Required

@Required注解用于设置Bean属性的方法,例如:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

上述表明,这个属性必须在配置期间被填充值,可能通过Bean定义时的显式地设置它的值,或自动织入它的值。

注意:这个属性在Spring5.1之后就不推荐使用了,对于想强制填充的值,考虑使用构造器注入或InitializingBean.afterPropertiesSet()进行注入。

使用@Autowired

此注解可被用于构造器,如下所示

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

在Spring Framework4.3之后,如果Bean定义只有一个构造器的话,可以省略此注解,但是吧,如果不止一个,那么需要添加此注解,以指明哪个构造器应该被使用。

当然,@Autowired也可以用在传统setter方法上,例如:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

也可以应用到任意名字的方法,以及任意数量的参数上:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

亦或,应用到私有域和构造器上:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

通过添加到数组形式上,可以让Spring使用ApplicationContext注入所有的此类型的Bean:

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

对于集合,也可以这么用:

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

映射也可以,不过映射的键只能是String,代表Bean名字:

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

@Autowired默认必须拥有可以注入的Bean,但是设置required属性,可以进行"非强制"注入:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

介于Spring特殊的构造器算法,@Autowired的required属性可能会在构造器或工厂方法参数里略有不同。比如,对于只有一个构造器的场景,数组,Set,Map这类,即使@Autowired默认为强制注入,但是这几个可以为空实例。对于所有依赖项都声明在唯一的一个多参构造器里的场景,这是可行的。

当然,也可以使用JDK8的java.uti.Optional来表示"非强制"注入:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}

对于Spring Framework5.0,还可以使用@Nullable来表示"非强制"注入:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}

此注解也可用于那些众所周知的可解决依赖项:BeanFactory, ApplicationContext, Environment, ResourceLoader, ApplicationEventPublisher, MessageSource, 这些类的子类也会自动解析,他们都不需要特殊的启动设置:

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

注意:@Autowired, @Inject, @Value, @Resource都没法用于BeanPostProcessor,因为BeanPostProcessor是处理这些注解的类。所以对于你自己的BeanPostProcessor和BeanFactoryPostProcessor,你必须显式地通过XML或@Bean方法来注册。

使用@Primary微调基于注解的自动织入

对于基于类型注入的@Autowired来说,这种方式还是有些宽泛,于是乎,需要@Primary来指出众多待选Bean中,究竟哪个更合适。比如:

@Configuration
public class MovieConfiguration {

    @Bean
    // 同样注入MovieCatalog,这个会优先注入。
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}
使用Qualifiers微调基于注解的自动织入

对于特定的Bean注入,还可以使用@Qualifier来进行特定的注入:把某个值与Bean进行绑定。看一个例子:

public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...
}

对应的注入:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

对于备选匹配,一般来说,Bean名称默认为匹配的值,但是有时候并不适用,所以你应当自己指出某些特定的值来进行唯一的标识,比如:“main”, “EMEA”, “persistent”。

对于集合类型,@Qualifier可以起到过滤器作用,比方说:从一组备选项中过滤出某些特定名称的Bean。

对于没有其他解析指示符的情况,Spring会使用注入点名称(属性名)与Bean名字进行匹配。

如果你只是想利用名字作为筛选而不是那么的在意类型的话,可以试试@Resource,它不同于@Autowired的地方是,@Autowired优先选取类型匹配,再选取名字匹配,它优先选取名字匹配,类型就显得不那么强制匹配了。

介于@Resource的匹配算法,对于本身就是Map或数组的Bean,使用@Resource来进行名称匹配会是一个不错的选择。当然,对于@Autowired来说,确保返回类型一致或返回类型继承了同一父类,然后用唯一标示的"value"属性来指明就好。

在某个配置类里面调用它的@Bean的结果给另一个方法,属于"自引用"的一种。这并不推荐,解决措施之一是,把@Bean方法声明为静态的,以避免它和配置类的生命周期混在一起;或者延迟它的解析。再不然,把他们设置在不同的配置类里面,尽量不在同一个配置里面。

@Autowired适用于私有域,构造器和多参方法,允许使用在参数阶段使用唯一标识来细粒化注入。作为对比,@Resource仅支持私有域和单参的Bean属性setter方法。所以,如果你想注入构造器或多参方法,请务必使用@qualifier来进行唯一注入。

当然,你也可以创建自己的qualifier注解,仅需提供@Qualifier就好:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {

    String value();
}

然后就可以像这样使用:

public class MovieRecommender {

    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;

    private MovieCatalog comedyCatalog;

    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }

    // ...
}

有时,不带名字的qualifier就可以了,他们默认以类型进行匹配。

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {

}

使用:

public class MovieRecommender {

    @Autowired
    @Offline 
    private MovieCatalog offlineCatalog;

    // ...
}

当然,还可以设置多个属性,在匹配时必须满足全部属性匹配。

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {

    String genre();

    Format format();
}

public enum Format {
    VHS, DVD, BLURAY
}

用起来,也必须都得满足:

public class MovieRecommender {

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Action")
    private MovieCatalog actionVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Comedy")
    private MovieCatalog comedyVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.DVD, genre="Action")
    private MovieCatalog actionDvdCatalog;

    @Autowired
    @MovieQualifier(format=Format.BLURAY, genre="Comedy")
    private MovieCatalog comedyBluRayCatalog;

    // ...
}
使用泛型作为自动织入的Qualifiers

你可以使用泛型类型作为唯一标识符,比如,有如下Bean定义:

@Configuration
public class MyConfiguration {

    @Bean
    public StringStore stringStore() {
        return new StringStore();
    }

    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }
}

那么可以这么用:

@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean

@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean

当然,泛型标识符也可以用于List,Map实例和数组:

// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;
使用CustomAutowireConfigurer

CustomAutowireConfigurer是一个BeanFactoryPostProcessor,它可以让你注册自己的自定义qualifier注解类型,即使他们没有使用Spring的@Qualifier注解进行标注。来看一个用法:

<bean id="customAutowireConfigurer"
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>
使用@Resource进行注入

Spring也支持使用@javax.annotation.Resource进行对私有域或Bean属性setter方法进行注入。

@Resource自带一个name属性,默认情况下,Spring把它解释成Bean名字。它适用于以名字进行注入的情景:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder") 
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

如果不进行指定name属性的值,那么默认为准备注入的私有域属性的名字或setter参数的名字。且在这种情况下,类似于@Autowired,比如:

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

上述注入,会优先寻找一个名为:“customerPreferenceDao”,若找不到,就会寻找类型为CustomerPreferenceDao的Bean(以类型匹配来寻找)。

使用@Value

@Value通常用于注入外在属性,也就是那些写在配置文件里面的属性。看个例子:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}

搭配下面的配置类:

@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }

配置文件如下所示:

catalog.name=MovieCatalog

Spring提供了一个宽松的值解析器,倘若需要的值没有被找到,属性名就会被当成默认值进行注入。不过,你可以自定义一个PropertySourcesPlaceholderConfigurerBean来进行更加严格的控制:

@Configuration
public class AppConfig {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
       return new PropertySourcesPlaceholderConfigurer();
    }
}

注意,当声明PropertySourcesPlaceholderConfigurerBean时,方法必须是静态的。

上述措施确保了占位符未被找到时的失败,不过,也可以通过setPlaceholderPrefix(),setPlaceholderSuffix()和setValueSeparator()来自定义占位符。

Spring Boot提供了PropertySourcesPlaceholderConfigurer的默认实现来加载来自application.properties或application.yml文件的属性。

Spring还提供了内置的对基本类型的转换,同时还可以把基于逗号’,'分隔符的属性值转换成String数组形式。

当然,还可以通过如下方式设定默认值:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}

某个Spring的BeanPostProcessor使用内置的ConversionService来进行把属性值转换成需要的类型,所以,你可以实现你自己的ConversionService来进行转换:

@Configuration
public class AppConfig {

    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}

当@Value包含SpEL表达式时,会在运行时动态的计算属性值:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }
}

SpEL包含了对于更加复杂的数据结构的支持:

@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

    public MovieRecommender(
            @Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
        this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
    }
}
使用@PostConstruct和@PreDestroy

javax.annotation.PostConstruct和javax.annotation.PreDestroy可以被CommonAnnotationBeanPostProcessor扫描到并进行生命周期的处理,如下所示:

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // populates the movie cache upon initialization...
    }

    @PreDestroy
    public void clearMovieCache() {
        // clears the movie cache upon destruction...
    }
}

路径扫描和组件管理

前几节主要讲述使用XML来进行Bean定义和Bean注册,虽然上一章讲述了使用注解完成源码层面的Bean定义和注册,但那仅仅只是依赖注入而已,对于"基础Bean"的定义,以及底层组件的装配,还是得在XML文件中完成。所以,本章会介绍怎么利用筛选机制来推断出潜在的基础配置类并把它们注册进容器以完成Bean装配(其实就是告诉你怎么完全脱离XML使用纯Java代码的配置)。

@Component和模板注解(Stereotype Annotation)

@Repository是用来标注DAO组件的一个注解,通常肩负着异常翻译的职责。

Spring提供了几个模板注解:@Component,@Service和@Controller。@Component也常作为其他任何Spring管理的组件的通用模板注解,@Repository,@Service和@Controller均是特例化的@Component,为了适用于更加特殊的情景(比如持久层,服务层,呈现层等),而@Component适用于一般场景。所以,你可以用@Component来装饰你的类,但是也可以使用刚刚说的那些特例化的Bean来装饰,这会让你的类更加专注于某些具体的业务处理。所以如果可以使用更加具体化的注解,就不要使用@Component

package org.springframework.stereotype;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	String value() default "";

}

继承层次:

使用元注解和组合注解

Spring提供的一些注解可以在你的代码里面被用作元注解,元注解就是可以应用到其他注解的注解,比如@Component就是一个元注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component 
public @interface Service {

    // ...
}

你还可以通过组合元注解来创建"组合注解",比如Spring MVC的@RestController就是@Controller和@ResponseBody的组合注解。组合注解可以重写元注解的属性,这是很有用的,因为通过这种方式,你可以仅定义父注解的一个属性子集,其他的给出默认值就好。比如:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

    /**
     * Alias for {@link Scope#proxyMode}.
     * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
     */
    @AliasFor(annotation = Scope.class)
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}

然后可以不声明proxyMode就进行使用:

@Service
@SessionScope
public class SessionScopedService {
    // ...
}

当然可以指定proxyMode:

@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
    // ...
}

关于proxyMode属性:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {

	/**
	 * Specifies whether a component should be configured as a scoped proxy
	 * and if so, whether the proxy should be interface-based or subclass-based.
	 * Defaults to ScopedProxyMode.DEFAULT, which typically indicates
	 * that no scoped proxy should be created unless a different default
	 * has been configured at the component-scan instruction level.
	 * Analogous to <aop:scoped-proxy/> support in Spring XML.
	 */
	ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;

}
自动化地推断类和注册Bean定义

Spring可以自动推断模板类(stereotyped classes)并向ApplicationContext中注册相应的Bean定义实例。例如:

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}

上面两个类均符合自动推断的标准。

为了正确的推断出组件类,你可以在@Configuration注解标注的类上添加@ComponentScan注解来扫描,通过设置它的basePackages属性的值就可以了,这个属性的值是组件类的父包,当然还可以是基于’,’(逗号),’;’(分号),’ '(空格)分隔的多个包。比如:

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}

注意:有时为了简洁,可能直接使用value属性进行设置,效果一样的:@ComponentScan(“org.example”)

使用过滤器完成自动化扫描

默认情况下,@Component, @Repository, @Service, @Controller, @Configuration或你自定义的注解声明的类,只能推断候选组件。然而,你可以通过应用自定义过滤器来完成更加复杂的筛选,使用@ComponentScan的includeFilters和excludeFilters属性即可,看一个表:

过滤器类型表达形式举例描述
annotationorg.example.SomeAnnotation目标组件的类型级别上的用来呈现或元呈现的注解
assignableorg.example.SomeClass目标组件可分配给(扩展或实现)的类(或接口)。
aspectjorg.example…*Service+与目标组件匹配的一个AspectJ类型的表达式
regexorg\.example\.Default.*与目标组件类名匹配的一个正则表达式
customorg.example.MyTypeFilter一个对于org.springframework.core.type.TypeFilter接口的自定义实现

下面这个例子展示了忽略所有@Repository标注的组件并使用"stub"持久层进行替代:

@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    ...
}
在组件里定义Bean元数据

Spring组件也可以向容器添加Bean定义,仅需在@Configuration注解的类里面添加@Bean注解就好:

@Component
public class FactoryMethodComponent {

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    public void doWork() {
        // Component method implementation omitted
    }
}

上述类拥有特定于应用的代码——doWork()方法体里面的代码。同时,它还有类似工厂方法的publicInstance()方法,用来给出Bean定义,除了可以使用@Qualifier来进行修饰外,任何作用于方法上的注解均可用于修饰此类方法。Bean名字默认为方法名。

自动织入的域和方法也可以使用:

@Component
public class FactoryMethodComponent {

    private static int i;

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    // use of a custom qualifier and autowiring of method parameters
    @Bean
    protected TestBean protectedInstance(
            @Qualifier("public") TestBean spouse,
            @Value("#{privateInstance.age}") String country) {
        TestBean tb = new TestBean("protectedInstance", 1);
        tb.setSpouse(spouse);
        tb.setCountry(country);
        return tb;
    }

    @Bean
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }

    @Bean
    @RequestScope
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }
}

还有一种通过对工厂方法设置InjectionPoint参数来完成创建Bean的方式,这种方式对于设置原型Bean来说很有用,此方式仅适用于Bean实例的创建,而不适用于现有实例的注入。

原文:

As of Spring Framework 4.3, you may also declare a factory method parameter of type InjectionPoint (or its more specific subclass: DependencyDescriptor) to access the requesting injection point that triggers the creation of the current bean. Note that this applies only to the actual creation of bean instances, not to the injection of existing instances. As a consequence, this feature makes most sense for beans of prototype scope. For other scopes, the factory method only ever sees the injection point that triggered the creation of a new bean instance in the given scope (for example, the dependency that triggered the creation of a lazy singleton bean). You can use the provided injection point metadata with semantic care in such scenarios. The following example shows how to do use InjectionPoint:

@Component
public class FactoryMethodComponent {

    @Bean
    @Scope("prototype")
    public TestBean prototypeInstance(InjectionPoint injectionPoint) {
        return new TestBean("prototypeInstance for " + injectionPoint.getMember());
    }
}

@Bean注解通常不同于@Configuration标注的类里面的其他注解,不同之处在于@Component不支持CGLIB对于方法和私有域调用的拦截。CGLIB是一种,在@Configuration类里面的@Bean方法里面调用方法和私有域来创建对协作对象的bean元数据引用的方式。这并不是简单是Java语法上的那种调用而是,通过容器来提供一般的具有完整生命周期的Bean管理,以及对于Spring Bean的代理,甚至是用于程序化的对于其他@Bean方法的调用。作为对比,在空白的@Component修饰的类里,在@Bean方法里面调用方法或域仅仅是Java语义上的,而没有特殊的CGLIB的处理或约束的限制。

原文:

The @Bean methods in a regular Spring component are processed differently than their counterparts inside a Spring @Configuration class. The difference is that @Component classes are not enhanced with CGLIB to intercept the invocation of methods and fields. CGLIB proxying is the means by which invoking methods or fields within @Bean methods in @Configuration classes creates bean metadata references to collaborating objects. Such methods are not invoked with normal Java semantics but rather go through the container in order to provide the usual lifecycle management and proxying of Spring beans, even when referring to other beans through programmatic calls to @Bean methods. In contrast, invoking a method or field in a @Bean method within a plain @Component class has standard Java semantics, with no special CGLIB processing or other constraints applying.

注意:

你可能会把@Bean方法声明成静态的,这是可以的,因为这意味着可以在不实例化它们所包含的配置类而直接调用他们。这在定义后处理器Bean时是很有用的(例如,BeanFactoryPostProcessor和BeanPostProcessor类型),这类Bean会在容器生命周期早期阶段完成初始化而不必触发配置的其他部分。

对于静态@Bean方法的调用不会被容器拦截,即使在@Configuration类里面配置的也是如此,因为CGLIB的技术限制,它只能拦截非静态方法。所以,对于@Bean方法的调用会产生直接的Java语义上的结果——一个新的实例由工厂方法产生并返回。

Java语言对于@Bean方法的可见性不会对Spring容器的最终Bean定义产生直接影响。你可以根据需要自由地在非@Configuration类里面声明你需要的工厂方法,或者在任意地方声明静态方法。然而,有一点是,如果是在@Configuration类里面声明的@Bean方法,那么此方法应该是可以被覆写的,也就是,它不能是private或final。

还可以在给定组件或配置类的基类上以及在由组件或配置类实现的接口中声明的Java 8默认方法上扫描到@Bean方法。这为组合复杂的配置排版提供了很大的灵活性,从Spring 4.2开始,通过Java 8默认方法甚至可以进行多继承。

最后,单个类可以保留多个@Bean方法,这取决于运行时的依赖关系,进而可以使用多个工厂方法。这种算法和在配置类选取"最贪婪"的构造器和工厂方法时的算法相同:拥有最多可满足的依赖的那个会被选取,类似于容器在多个@Autowired注解标注的构造器里面选取最合适的那个。

原文:

You may declare @Bean methods as static, allowing for them to be called without creating their containing configuration class as an instance. This makes particular sense when defining post-processor beans (for example, of type BeanFactoryPostProcessor or BeanPostProcessor), since such beans get initialized early in the container lifecycle and should avoid triggering other parts of the configuration at that point.

Calls to static @Bean methods never get intercepted by the container, not even within @Configuration classes (as described earlier in this section), due to technical limitations: CGLIB subclassing can override only non-static methods. As a consequence, a direct call to another @Bean method has standard Java semantics, resulting in an independent instance being returned straight from the factory method itself.

The Java language visibility of @Bean methods does not have an immediate impact on the resulting bean definition in Spring’s container. You can freely declare your factory methods as you see fit in non-@Configuration classes and also for static methods anywhere. However, regular @Bean methods in @Configuration classes need to be overridable — that is, they must not be declared as private or final.

@Bean methods are also discovered on base classes of a given component or configuration class, as well as on Java 8 default methods declared in interfaces implemented by the component or configuration class. This allows for a lot of flexibility in composing complex configuration arrangements, with even multiple inheritance being possible through Java 8 default methods as of Spring 4.2.

Finally, a single class may hold multiple @Bean methods for the same bean, as an arrangement of multiple factory methods to use depending on available dependencies at runtime. This is the same algorithm as for choosing the “greediest” constructor or factory method in other configuration scenarios: The variant with the largest number of satisfiable dependencies is picked at construction time, analogous to how the container selects between multiple @Autowired constructors.

命名自动推断的组件

当一个组件被扫描过程自动推断出之后,它的Bean名字由扫描器已知的BeanNameGenerator策略生成,默认情况下,@Component,@Repository,@Service,@Controller这些注解的value属性规定的值会作为Bean名称。

如果没有指定这个属性,Bean名字默认为类名首字母小写。

你还可以自定义自己的BeanNameGenerator来设置自己的命名策略,记得,别忘了添加一个无参构造器。看一下BeanNameGenerator的定义:

public interface BeanNameGenerator {

	/**
	 * Generate a bean name for the given bean definition.
	 * @param definition the bean definition to generate a name for
	 * @param registry the bean definition registry that the given definition
	 * is supposed to be registered with
	 * @return the generated bean name
	 */
	String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);

}

如果为了解决类名冲突(比如,不同包下的同名类),可以使用FullyQualifiedAnnotationBeanNameGenerator来处理,看一下定义:

public class FullyQualifiedAnnotationBeanNameGenerator extends AnnotationBeanNameGenerator {

	@Override
	protected String buildDefaultBeanName(BeanDefinition definition) {
		String beanClassName = definition.getBeanClassName();
		Assert.state(beanClassName != null, "No bean class name set");
		return beanClassName;
	}

}

不过一般情况下,在定义Bean时直接指定它的名称未尝不是更简单的方法,若是Bean是通过容器来进行织入的话,BeanNameGenerator也是不错的方法。

为自动推断的组件提供作用域

Spring容器管理的Bean一般情况下都是单例的,如果你想为自己的Bean定义指定作用域,可以添加@Scope注解并指定其value属性。比如:

@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

注意,@Scope仅作用于具体的Bean类,或工厂方法(被@Bean修饰的方法),且不具备继承层次。

当然,你也可以自定义范围注解,仅需组合Spring提供的元注解即可,比方说,可以把@Scope和代理模式组合起来。

为了提供自定义的作用域解决方案,你可以实现ScopeMetadataResolver接口,看一下定义:

public interface ScopeMetadataResolver {

	/**
	 * @param definition the target bean definition
	 * @return the relevant scope metadata; never {@code null}
	 */
	ScopeMetadata resolveScopeMetadata(BeanDefinition definition);

}

public class ScopeMetadata {

	private String scopeName = BeanDefinition.SCOPE_SINGLETON;

	private ScopedProxyMode scopedProxyMode = ScopedProxyMode.NO;


	/**
	 * Set the name of the scope.
	 */
	public void setScopeName(String scopeName) {
		Assert.notNull(scopeName, "'scopeName' must not be null");
		this.scopeName = scopeName;
	}

	/**
	 * Get the name of the scope.
	 */
	public String getScopeName() {
		return this.scopeName;
	}

	/**
	 * Set the proxy-mode to be applied to the scoped instance.
	 */
	public void setScopedProxyMode(ScopedProxyMode scopedProxyMode) {
		Assert.notNull(scopedProxyMode, "'scopedProxyMode' must not be null");
		this.scopedProxyMode = scopedProxyMode;
	}

	/**
	 * Get the proxy-mode to be applied to the scoped instance.
	 */
	public ScopedProxyMode getScopedProxyMode() {
		return this.scopedProxyMode;
	}

}

当使用某一非单例作用域时,你应该为被限制作用域的对(scoped object)象提供一个代理,介于此,应该设置@ComponentScan的scopedProxy属性。可选的值有:ScopedProxyMode.INTERFACES, ScopedProxyMode.NO, ScopedProxyMode.TARGET_CLASS,比如,下面的就是一个启用标准JDK动态代理的例子:

@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    // ...
}
使用注解提供Qualifier元数据

正如前面讨论的那样,@Qualifier可以提供更加细粒化的控制,但是那些qualifier的元数据都是在Bean定义上提供的,所以对于基于路径扫描的方法来说,你可以在候选类上提供类型级别的qualifier元数据,如下所示:

@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}

@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}

@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
    // ...
}

注意:基于路径扫描配置在组件类上的qualifier元数据没法被共享,这点不同于使用XML时的配置。

生成备选组件的索引

如果类路径扫描很快的话,不妨直接在启动阶段进行扫描,比如创建一个静态的备选组件列表。要实现此功能,需要导入spring-context-indexer包。

记得把spring-context-indexer注册为注解处理器。

使用JSR-330标准注解

使用@Inject和@Named进行依赖注入
@Component的标准等价注解:@Named和@ManagedBean
JSR-330标准注解的局限性

基于Java的容器配置

本节主要介绍如何在你的Java代码里面使用注解来配置Spring容器。

基本概念:@Bean和@Configuration

Spring的新的Java配置的支持的核心是基于@Configuration类和基于@Bean的方法。

@Bean注解表明这个方法会实例化,配置,并初始化一个由Spring IOC容器管理的Bean。这个注解通常和@Configuration搭配使用,虽然也可以和任意的@Component一起使用。

@Configuration标注的类表明此类的主要作用作为Bean定义的一个源。它还可以通过调用类里面的其他@Bean方法来定义Bean之间的依赖关系。最简单的用法如下:

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

完整的@Configuration模式和“精简的”@Bean模式的对比:

当一个@Bean方法未在@Configuration类里面声明时,那么可以称这个@Bean方法的模式是精简的模式,声明在@Component组件类或普通的类里面的@Bean方法都可以被称作“精简”模式。某些服务组件可能会在每个可用的组件类上,通过一个附加的@Bean方法把管理视图暴露给容器,在这种情况下,@Bean方法更像是工厂方法。

不同于@Configuration,精简模式的@Bean没法定义Bean之间的依赖关系。取而代之的是,它们在其包含组件的内部状态上进行操作,或者是它们声明的参数。这样的@Bean方法不用当调用其他任何的@Bean方法,这样的方法,从字面上来看,应该只是特定Bean引用的工厂方法。而没有任何运行时的语义状态。负面影响就是,无法在运行时应用CGLIB代理,所以类的设计也就没有限制(比方说,定义为final)。

通常情况下,@Bean方法应该在@Configuration类里面定义,确保可以使用"完整的"模式。这样的话,跨方法引用可以被重新导向至容器的生命周期管理。这样可以防止不经意间地常规Java调用方法,对于同一@Bean方法的调用,这有助于减少在“精简”模式下运行时难以跟踪的细微bug。

使用AnnotationConfigApplicationContext实例化Spring容器

AnnotationConfigApplicationContext在Spring3.0之后引入,这个丰富的类,既可以把@Configuration作为输入源,也可以把普通的@component类作为输入,或者用JSR-330注解标注的类也可以。

当@Configuration作为输入时,@Configuration类自己会作为Bean定义注册,而且类里面的所有@Bean方法也会作为Bean定义注册。

当@Component和JSR-330标准注解标注的类作为输入时,这些类会作为Bean定义注册,并且假定类的内部会在必要的时候使用诸如@Autowired和@Inject的注解。

  1. 简单构造

正如XML那样,你可以使用@Configuration标注的类作为输入来实例化AnnotationConfigApplicationContext,比如:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

AnnotationConfigApplicationContext的部分源码:

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
    /**
	 * Create a new AnnotationConfigApplicationContext that needs to be populated
	 * through {@link #register} calls and then manually {@linkplain #refresh refreshed}.
	 */
	public AnnotationConfigApplicationContext() {
	    // ...
	}

	/**
	 * Create a new AnnotationConfigApplicationContext with the given DefaultListableBeanFactory.
	 * @param beanFactory the DefaultListableBeanFactory instance to use for this context
	 */
	public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {
	    // ...
	}

	/**
	 * Create a new AnnotationConfigApplicationContext, deriving bean definitions
	 * from the given component classes and automatically refreshing the context.
	 * @param componentClasses one or more component classes &mdash; for example,
	 * {@link Configuration @Configuration} classes
	 */
	public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
	    // ...
	}
	
	/**
	 * Create a new AnnotationConfigApplicationContext, scanning for components
	 * in the given packages, registering bean definitions for those components,
	 * and automatically refreshing the context.
	 * @param basePackages the packages to scan for component classes
	 */
	public AnnotationConfigApplicationContext(String... basePackages) {
	    // ...
	}
}
  1. 使用register(Class<?> …)程序化地构建容器

你还可以使用AnnotationConfigApplicationContext的无参构造器,构造一个实例,然后调用其register(class<?> …)方法,如下所示:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
  1. 使用scan(String …)启用组件扫描

AnnotationConfigApplicationContext使用scan(String …)方法来启用对于包含组件类的包的扫描,如下所示:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}

有一点需要注意的是,@Configuration是以@Component作为元注解组成的,所以它们是扫描的候选项,在上面的例子中,假设AppConfig是一个声明在com.acme包下的类,那么它会在scan()被调用时被选取,当执行到refresh()时,它的所有的@Bean方法都会被注册到容器里面去。

  1. AnnotationConfigApplicationContext对Web应用的支持

AnnotationConfigApplicationContext是WebApplicationContext的一个变种,它的实现类——AnnotationConfigWebApplicationContext可以用于操作Web应用,比如配置SpringContextLoaderListener和Spring MVC DispatcherServlet等。

使用@Bean注解

废话不多说,看看源码先:

public @interface Bean {

    @AliasFor("name")
    String[] value() default {};
    
    @AliasFor("value")
    String[] name() default {};
    
    @Deprecated
    Autowire autowire() default Autowire.NO;
    
    boolean autowireCandidate() default true;
    
    String initMethod() default "";
    
    String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}
  1. 声明一个Bean

通过在一个方法体前面注上@Bean注解,你可以使这个方法向ApplicationContext里面注入一个Bean定义,Bean类型为方法返回值类型,Bean名称默认为方法名。如下所示:

@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

也可以把返回类型设置为接口类型,而返回的实际值是接口的实现类:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

这么做的话,结果就是隐藏了实际类型,可能会在自动织入或获取Bean时产生一些冲突。如果实现类实现了不止一个接口,那么可能会产生不安全隐患,所以最好还是指出特定的返回类型。

  1. Bean依赖

对于@Bean注解的方法,可以通过方法参数的形式指明构建此Bean所需要的依赖。类似于基于构造器的依赖注入。

  1. 接收生命周期回调

所有使用@Bean定义的类都可以使用普通的生命周期回调,也可以使用,JSR-250注解——@PostContruct和@PreDestroy。

同时也支持标准的Spring生命周期回调,比如:InitializingBean,DisposableBean,Lifecycle。

标准的*Aware接口(比如:BeanFactoryAware,BeanNameAware,MessageSourceAware,ApplicationContextAware等)也被很好地支持。

@Bean也支持指定任意方法为初始化方法或销毁方法:

public class BeanOne {

    public void init() {
        // initialization logic
    }
}

public class BeanTwo {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

注意:

使用Java配置的,且拥有public的close()或shutdown()方法的Bean,会自动导入一个销毁方法回调,如果你不希望在容器关闭时,这个销毁方法被调用的话,请这样设置:@Bean(destroyMethod="")。这种措施对于数据库数据源很有用。

上面例子的初始化方法可以这么使用:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }

    // ...
}

当你直接使用Java形式来设计你的代码时,你可以在不依赖于容器生命周期的情况下编写任意你想要的代码。

  1. 限定Bean作用域

直接使用@Scope注解就行。比如指定是单例还是原型:

@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}

使用代理的形式:

// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
    return new UserPreferences();
}

@Bean
public Service userService() {
    UserService service = new SimpleUserService();
    // a reference to the proxied userPreferences bean
    service.setUserPreferences(userPreferences());
    return service;
}
  1. 自定义Bean名称

使用@Bean属性的name属性,就能替换掉默认为方法名的Bean名字:

@Configuration
public class AppConfig {

    @Bean(name = "myThing")
    public Thing thing() {
        return new Thing();
    }
}
  1. Bean别名

@Bean的name属性接收一个字符数组的时候,就算设置了Bean的别名了。

@Configuration
public class AppConfig {

    @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}
  1. Bean描述

有时,为一个Bean添加描述是很有意义的,比方说对于具有监视意义的Bean,仅需简单地添加@Description注解:

@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Thing thing() {
        return new Thing();
    }
}
使用@Configuration注解

@Configuration定义了它所标注的类为一个Bean定义源。里面的@Bean方法定义了具体的Bean定义,同时,不同@Bean方法之间的引用也定义了Bean之间的依赖关系。

  1. 注入内部Bean依赖

当Bean依赖于其他的某个Bean时,表达依赖关系仅需简单地引用其他@Bean方法就行:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

注意:

这种定义依赖关系的方式,仅适用于在@Configuration定义的类内部使用,而不适用于@Component标注的普通类。

  1. 查找方法注入

查找方法注入是一种高级特性,我们建议尽可能少的使用。在单例Bean使用原型Bean作为依赖时,这种方式是很好用的。而使用Java配置提供了天然的便利来使用这种模式。如下所示:

public abstract class CommandManager {
    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

然后某个类继承了CommandManager,重写了createCommand()方法,比方说这个方法会查找一个新的command对象。如下所示:

@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // inject dependencies here as required
    return command;
}

@Bean
public CommandManager commandManager() {
    // return new anonymous implementation of CommandManager with createCommand()
    // overridden to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}
  1. 更多的关于基于Java配置的内部工作机制

考虑下面这个例子,@Bean方法被调用了两次:

@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}

Spring容器管理的Bean默认为单例的,所以这里可能有问题,因为任何@Configuration类都会被CGLIB子类化,子类在构造Bean之前,会先检查容器有没有需要的Bean缓存(作用域),然后创建一个新的实例。

当然,如果你的Bean作用域不同的话,这又是另一种结果了。

组合基于Java的配置
  1. 使用@Import注解

@Import允许导入其他的含有@Bean方法的@Configuration类。如下所示:

@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();
    }
}

这样,在实例化ApplicationContext时,仅需ConfigB.class就行了:

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);
}

在Spring Framework4.2之后,可以直接Import普通组件类,这个作用类似于AnnotationConfigApplicationContext.register()方法,这样就不用扫描基础包了,还是挺有用的。

P.s. 个人建议,不妨试着写一个总配置类,导入所有其他的配置类,然后用这个总配置类实例化ApplicationContext就行。

把依赖注入到被导入的@Bean定义里面(注意,是被导入的,不是导入的),可以这么用:

@Configuration
public class ServiceConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {

    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

还有一种方法,就是直接把@Configuration作为组件@Autowired注入到另一个@Configuration类里面去。

警告:

尽可能的以简单化地方法进行注入依赖,因为@Configuration会在上下文初始化时尽可能早地被处理。所以,就像上面的例子那样,尽可能使用基于参数的注入(因为基于参数不会那么早地就要求注入依赖)。

同样,在通过@Bean处理BeanPostProcessor和BeanFactoryPostProcessor时,你应当相当小心。这些@Bean方法应当被声明为static @Bean方法,这样就不会触发他们所在的@Configuration类的实例化。否则的话,@Autowired和@Value可能会不顶用,因为@Configuration类自身可能会比AutowiredAnnotationBeanPostProcessor更早地被创建为Bean实例。

看一个使用自动织入的例子:

@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {

    private final DataSource dataSource;

    public RepositoryConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

小小的附加的知识

  1. 有条件地导入@Configuration类和@Bean方法

有时基于系统状态,启用或禁用某一个配置类或@Bean方法是很有用的,一个很常见的例子就是@Profile注解,用来指定需要激活的环境。

@Profile注解是通过更加灵活的@Conditional注解实现的,@Conditional注解指明,在注册一个Bean之前,应该参考org.springframework.context.annotation.Condition的实现。

然后org.springframework.context.annotation.Condition提供了matches()方法,以供进行匹配项处理,看一个例子:

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    // Read the @Profile annotation attributes
    MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
    if (attrs != null) {
        for (Object value : attrs.get("value")) {
            if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                return true;
            }
        }
        return false;
    }
    return true;
}
  1. 组合Java配置和XML配置

有两种方式,以XML为中心的和以配置类为中心的,直接看示例趴!

  1. 以XML为中心

配置类:

@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }
}

XML文件

<!--使用普通的Bean注入来引入配置类-->
<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="com.acme.AppConfig"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

<!--使用包扫描来引入配置类-->
<beans>
    <!-- picks up and registers AppConfig as a bean definition -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

使用:

public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
  1. 以配置类为中心

XML文件:

<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>

Java配置类:

@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }
}

用法:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

环境抽象

Environment接口是容器内的一个集成抽象,它对应用环境的两个关键层面建立模型:profile和property(配置描述和配置属性)。

profiles是一组逻辑化的,命名了的Bean定义,如果所给的profile被激活(或处于活跃状态),那么这个profile所包含的Bean定义会注册到容器里面。profile在XML和注解里均可设置,Environment的作为profile的决定者出现的,它会决定哪个profile会被使用,默认的profile是哪个。

properties在几乎所有应用里都扮演着很重要的角色,property可能从各种源里生成,比如:配置文件,JVM系统参数,系统环境变量,JNDI,servlet上下文参数,ad-hoc配置对象,Map对象,等等。Environment之于property的意义在于,提供了一个接口,通过这个接口,可以操作配置源,包括修改和读取。

Bean定义配置文件

Bean定义描述提供了一个包含在核心容器的机制,通过这个机制,允许在不同的环境注册不同的Bean,这在开发时是很有用的,考虑如下场景:

a) 在开发中针对内存中的数据源进行工作,而不是在进行QA或生产时从JNDI查找相同的数据源。
b) 仅在性能模式下注册资源监视组件。
c) 针对部署时和开发时不同Bean实现的切换。
  1. 使用@Profile

@Profile注解会在它所指定的profile被激活时向容器内注册相应的Bean:

@Configuration
@Profile("development")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}
@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean(destroyMethod="")
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

@Profile的值可以包含表达式,比如,’!‘代表“非”;’&‘代表“且”;’|'代表“或”。在组合使用时,记得加小括号"()"。当然,你也可以使用@Profile作为元注解来编写你自己的注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}

如果一个配置类被@Profile标注了,那么它所包含的@Bean方法和与其关联的@Import注解都不会发生作用,直到profile处于激活状态。

当然,也可以标注在方法级别,用来指定某一特定Bean是否被激活:

@Configuration
public class AppConfig {

    @Bean("dataSource")
    @Profile("development")
    public DataSource standaloneDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }

    @Bean("dataSource")
    @Profile("production")
    public DataSource jndiDataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

注意:

对于使用@Profile注解的@Bean方法,如果两个方法名一样,仅根据profile的不同而返回同一类型的Bean的话,请记得,方法参数必须一致(都是无参或只有n个类型顺序均一致的参数)。否则会出错,如果你想完成profile不同,且参数也不同的话,记得使用@Bean的name属性加不一样的方法名。

  1. 在XML文件中使用profile

暂略不表,原文在这

  1. 激活一个profile

言简意赅地说,用Environment接口的setActiveProfiles(String… profiles)就能指定需要激活的profile。看看码:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
  1. 默认profile

指定当没有profile被选中时,默认使用的profile:

@Configuration
@Profile("default")
public class DefaultDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .build();
    }
}

也可以使用setDefaultProfiles(String… profiles)方法来设置默认profile。

配置源抽象

Spring的Environment基于可配置的继承层次提供了搜索配置源的能力。例如:

ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);

对于配置源,会抽象出一个集合,这个集合包含了所有的配置源。配置源会以键值对的形式存储在这个集合里面(以:配置名-配置值的形式)。想找到某一配置源,在集合里面,从前向后地搜索即可。Spring的StandardEnvironment由两个配置源对象组成,一个是JVM系统配置(System.getProperties()),一个是应用系统变量(System.getenv())。

对于Web应用,还有一个StandardServletEnvironment可以使用,通过它,可以获取你需要的servlet config和servlet context parameter。

对于配置源的搜索的层次化的。默认情况下,系统级配置拥有比环境配置更高的优先级,还有,同名属性不会合并,系统级会覆盖掉环境级的。对于一个普通的StandardServletEnvironment,它的搜索层次大致如下所示:

ServletConfig参数

ServletContext参数

JNDI环境变量

JVM系统参数

JVM系统环境

更重要的是,整个机制都是可配置的。如果你想添加一个自定义的配置源,可以试着向下面这么做:

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());

此时MyPropertySource拥有最高优先级。MutablePropertySources提供了一些方法,用来精确操纵配置源集合。

使用@PropertySource注解

@PropertySource提供了和通过上面的add()方法向Spring Environment添加数据源的等价的方法。看一个例子,其中app.properties是一个包含键值对的配置源:

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

任何在@PropertySource里面的占位符都是针对环境中已经注册的配置进行解析的:

@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

有一个需要注意的,就是,@PropertySource是可以重复的,但是,仅限同一层面,什么意思呢?就是,要么仅作为单一注解使用,要么仅作为你自定义注解的元注解使用,不能既是元注解还是单一注解。

语句中的占位符

介于历史原因,语句中的占位符仅限JVM系统参数和环境变量,但是,现在来说,么得这个问题了!只要属性是注册在Environment里面就够了,Spring会自动搜寻并把占位符替换成属性值。

注册一个LoadTimeWeaver

LoadTimeWeaver是用于当类被加载进JVM时,Spring动态地进行类转换时使用的。为了启用它,可以在你的任何一个配置类前面加上@EnableLoadTimeWeaving:

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}

或XML文件:

<beans>
    <context:load-time-weaver/>
</beans>

如果你觉得对你有帮助,可以赏几个钱子儿吗?(用小钱钱买大开心!)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值