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

写在开头

本篇章介绍Spring框架的完整的所有技术。html

本人偏心Java Configuration(也是Spring实战推荐的方式),因此一样配置的状况下,将略去XML配置不表,请知悉。java

官翻版本:Spring Framework Core 5.2.5.RELEASEmysql

本文会尽可能遵照原文排版,某些我以为不过重要的,我会只加标题,而不加具体内容,并放上连接,供感兴趣的读者自行阅读。web

由于考虑到考研,项目开发等,因此文章更新会慢一些,若是你喜欢的话,也能够私信我提醒更新!算法

须要的基础知识:spring

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

出现的专业词汇讲解

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

依赖:顾名思义,构建某个实例须要的其余实例,由于依赖于这个实例,因此叫“依赖”。数据库

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

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

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

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

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

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

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

绪论

在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)依赖注入而被人知晓。它是一个定义对象依赖的过程,对象从构造参数,工厂方法参数,属性值获取建立此对象须要的依赖(其实就是其余对象)。

容器就有点秀了!它在对象须要依赖时(须要其余对象时)主动地注入进去,这个过程本质上经过直接构造类或者相似服务定位模式的机制来取代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是怎么工做的:

![](https://user-gold-cdn.xitu.io/2020/3/21/170fb8fe8facbbda?w=498&h=296&f=png&s=3788)

配置元数据

正如图所示的那样,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

暂略不表。

<span id="method-injection">方法注入<span/>

背景:大多时的场景,大多数的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...
    }
}

<span id="applicationContextAndBeanNameAware">ApplicationContextAware和BeanNameAware</span>

当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使用,来向容器代表他们所须要的某些基础依赖。

看个表:

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

Name Injected Dependency Explained in…
ApplicationContextAware Declaring ApplicationContext. ApplicationContextAware and BeanNameAware
ApplicationEventPublisherAware Event publisher of the enclosing ApplicationContext. Additional Capabilities of the ApplicationContext
BeanClassLoaderAware Class loader used to load the bean classes. Instantiating Beans
BeanFactoryAware Declaring BeanFactory. ApplicationContextAware and BeanNameAware
BeanNameAware Name of the declaring bean. ApplicationContextAware and BeanNameAware
BootstrapContextAware Resource adapter BootstrapContext the container runs in. Typically available only in JCA-aware ApplicationContext instances. JCA CCI
LoadTimeWeaverAware Defined weaver for processing class definition at load time. Load-time Weaving with AspectJ in the Spring Framework
MessageSourceAware Configured strategy for resolving messages (with support for parametrization and internationalization). Additional Capabilities of the ApplicationContext
NotificationPublisherAware Spring JMX notification publisher. Notifications
ResourceLoaderAware Configured loader for low-level access to resources. Resources
ServletConfigAware Current ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext. Spring MVC
ServletContextAware Current 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 after any bean initialization callbacks (like InitializingBean's afterPropertiesSet or a custom init-method).
    default Object postProcessBeforeInitialization(Object bean, String beanName)
    // Apply this BeanPostProcessor to the given new bean instance before any bean initialization callbacks (like InitializingBean's afterPropertiesSet or a custom init-method).
    default Object postProcessBeforeInitialization(Object bean, String beanName)
}

你也能够定义多个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...
    }
}

[下一篇]()

若是你以为对你有帮助,能够赏几个钱子儿吗?(用小钱钱买大开心!)

相关文章
相关标签/搜索