Spring Framework 学习笔记——核心技术之Spring IOC Spring Framework 官网文档学习笔记——核心技术之Spring IOC

目录

官方文档

spring-framework-5.3.9

1. Spring Framework 核心技术

1.1 IOC 容器( Spring’s Inversion of Control (IoC) container)

Spring 控制反转容器。

一般上, 一个类在使用另一个类的功能时,会调用另一个类的构造方法进行实例化,然后进行使用。
控制反转,就是另一个类的实例化动作不需要自己来完成,通过依赖注入的方式注入进来,直接使用。

Spring IOC 就是管理 Bean 的整个生命周期(实例化,初始化,销毁),在实例化对象的时候,根据Bean构造方法或 getter/Setter 方法,将依赖的Bean 注入进去供其使用。

org.springframework.beansorg.springframework.context 包是Spring IOC 容器的基础。


BeanFactory

BeanFactory 提供了配置框架和基础功能。

BeanFactory 的实现应该尽可能支持标准的生命周期接口。
所有初始化方法的标准排序,如下:

  1. BeanNameAware's setBeanName
  2. BeanClassLoaderAware's setBeanClassLoader
  3. BeanFactoryAware's setBeanFactory
  4. EnvironmentAware's setEnvironment
  5. EmbeddedValueResolverAware's setEmbeddedValueResolver
  6. ResourceLoaderAware's setResourceLoader (only applicable when running in an application context)
  7. ApplicationEventPublisherAware's setApplicationEventPublisher (only applicable when running in an application context)
  8. MessageSourceAware's setMessageSource (only applicable when running in an application context)
  9. ApplicationContextAware's setApplicationContext (only applicable when running in an application context)
  10. ServletContextAware's setServletContext (only applicable when running in a web application context)
  11. postProcessBeforeInitialization methods of BeanPostProcessors
  12. InitializingBean's afterPropertiesSet
  13. a custom init-method definition
  14. postProcessAfterInitialization methods of BeanPostProcessors

Bean factory 关闭时会用到以下方法:

  1. postProcessBeforeDestruction methods of DestructionAwareBeanPostProcessors
  2. DisposableBean's destroy
  3. a custom destroy-method definition

ApplicationContext

ApplicationContextBeanFactory子类,添加了企业级功能:

  • 更容易和 Spring AOP 特性集成
  • 继承自 ListableBeanFactory Benan 工厂方法访问应用组件.
  • 继承自 the ResourceLoader interface 加载文件资源.
  • 继承自 the ApplicationEventPublisher interface 发布事件到注册的监听器上的能力.
  • 继承自 the MessageSource interface 处理信息,支持国际化能力
  • 继承 a parent context. a single parent context 能够在整个 web 应用, 然而每个 servlet 也能够拥有独立各自的子Context。

ApplicationContext 接口代表了 Spring IOC容器。负责实例化,配置,和组装 Beans。容器通过读取配置元数据获得关于要实例化、配置和组装哪些对象的指令。配置元数据 通过 XML,Java 注解和Java 代码表示。

ApplicationContext 接口的实现:

  • ClassPathXmlApplicationContext
  • FileSystemXmlApplicationContext

在 ApplicationContext 创建和初始化完成后,应用里的类才和 配置元数据 结合, 等待使用:
Spring Framework 学习笔记——核心技术之Spring IOC
Spring Framework 官网文档学习笔记——核心技术之Spring IOC


Configuration Metadata (配置元数据)

配置元数据表示告诉 Spring 容器如何去实例化,配置和组装对象。

  • Spring 2.5 提供了基于注解的配置
  • Spring 3.0 开始提供了基于Java的配置,可以使用Java替换xml进行Bean的配置。

基于XML配置的Bean 通过使用<beans> <bean></bean></beans> 标签进行配置;基于Java配置的Bean通过在标记 @Configuration 类里使用 @Bean注解进行配置。

XML 配置Bean 的例子:

<?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 >  
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean >
        <!-- collaborators and configuration for this bean go here -->
    </bean>

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

</beans>
  • id 同一</beans>标签下Bean定义的唯一标识
  • class 指定Bean 的类型。

内部类怎么配置?

<!-- OtherThing 是 SomeThing的静态内部类 -->
class="com.example.SomeThing$OtherThing"
或 
class="com.example.SomeThing.OtherThing"

实例化 IOC 容器

ApplicationContext 构造器可以支持从本地路径,CLASSPATH等加载配置元数据。

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

Spring's Resource 抽象提供了一个很方便的机制从 URI 读取流。

services.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">

    <!-- services -->

    <bean >
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

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

</beans>

daos.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 
        class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <bean >
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for data access objects go here -->

</beans>

使用 <import>标签导入其他xml配置的Bean:

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

    <bean />
    <bean />
</beans>

使用 IOC 容器

ApplicationContext 通过以下方式读取Bean定义并且访问他们:

// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List<String> userList = service.getUsernameList();

更灵活的变体:GenericApplicationContext

GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();

Bean

在容器中,BeanDefinition 对象代表 Bean定义。BeanDefinition对象包含了以下元数据:

  • 类的全限定名
  • Bean行为配置元素(scope, lifecycle callback)
  • 其他Bean的引用
  • 其他的一些配置

元数据被翻译成一组属性值。

  • Class 类
  • Name Bean的名字
  • Scope 范围
  • Constructor arguments 构造参数
  • Properties 属性值
  • Autowiring mode 自动装配
  • Lazy initialization mode 延迟加载
  • Initialization method 初始化方法
  • Destruction method 容器销毁回调方法

ApplicationContext’s BeanFactory 通过 getBeanFactory() 方法返回一个 DefaultListableBeanFactory 类。这个类可以使用registerSingleton(..)或registerBeanDefinition(..)方法注册 BeanDefinition。

Bean 命名约定:

  • 小驼峰格式命名

XML 配置中 name 属性可以给Bean指定多个名字(以comma (,), semicolon( ; ), or white space 分隔)。

使用 <alias></alias> 标签可以给名字为 fromName起别名 toName:

<alias name="fromName" alias="toName"/>

如果使用 Javaconfiguration @Bean注解提供了别名的配置

实例化 Bean

XML配置的 Bean 有一个Class属性指定了Bean的类型,使用 Class 进行实例化有两种方式:

  • 反射调用 Class 属性的构造器进行实例化
  • 通过静态工厂方法实例化Bean(可以是自己的静态工厂方法或者)

通过构造器实例化 Bean 的配置:

<bean />

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

通过静态工厂方法实例化 Bean 的配置:

<bean 
    class="examples.ClientService"
    factory-method="createInstance"/>
public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}

通过一个类的实例静态工厂方法:

<!-- the factory bean, which contains a method called createInstance() -->
<bean >
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean 
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

...

Dependencies(依赖)

DI(Dependency Injection)

官网说,依赖注入 (DI) 是一个过程,其中对象仅通过构造函数参数、工厂方法的参数或在对象实例被构造或从工厂方法返回。然后容器在创建 bean 时注入这些依赖项。这个过程基本上是 bean 本身的逆过程(因此得名,控制反转),通过使用类的直接构造或服务定位器模式自行控制其依赖项的实例化或位置。

DI 存在两种变体:基于构造器的依赖注入和基于Setter 方法的依赖注入。

基于构造器的依赖注入
代码1:

package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}
<beans>
    <bean >
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

    <bean />

    <bean />
</beans>

注意: ThingTwo 和 ThingThree 必须没有继承关系才能正常工作。

如果是构造参数的值是简单类型,Spring 无法确定其类型,不能自动匹配类型。需要增加 type 指定参数值得类型,这样 Spring 根据type 属性进行类型匹配。

package examples;

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private final int years;

    // The Answer to Life, the Universe, and Everything
    private final String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

构造参数按类型匹配赋值:

<bean >
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

构造参数按参数下标赋值:

<bean >
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

下标从 0 开始

构造参数按参数名赋值:

<bean >
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

注意,必须开启 debug 进行编译,或者使用 @ConstructorProperties 注解

package examples;

public class ExampleBean {

    // Fields omitted

    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

基于构造器的依赖注入

基于set方法的依赖注入

Setter-based DI 有技巧的,在容器调用无参构造器初始化Bean后或无参静态工厂方法实例化对象后。
下面这个例子只能通过set 方法注入,这个类没有依赖容器声明的接口,基础类或注解:

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 也为管理的 Bean 支持基于构造器注入和set方法注入。
ApplicationContext 也支持,构造器注入后,在进行set方法注入。

构造器注入和set方法注入选哪个?

构造器注入用于强制性依赖,set方法注入适用于选择性的依赖。set方法上标注 @Required 注解则 也会变成强制依赖。
构造器注入确保依赖不能为null;但是构造器的参数不能过多。
set方法注入适用于选择性的依赖,一个优点是重新注入对象。

ApplicationContext 通过以下决定如何执行Bean的依赖:

  • ApplicationContext 被创建后,使用 配置元数据初始化。
  • 每一个Bean 的依赖被表达成 属性,构造方法参数或静态工厂方法参数。
  • 每一个属性或者构造参数,是一个确切的值定义或容器中其他Bean的引用。
  • 每一个属性或者构造参数被转换为真实的类型。

当container 创建后,单例 Bean 就会被创建。依赖的Bean会优先于当前Bean创建。

Circular dependencies
循环依赖:类A通过构造函数注入需要类B的一个实例,类B通过构造函数注入需要类A的一个实例。

构造器注入时循环依赖会抛出BeanCurrentlyInCreationException异常。
set 方法注入支持循环依赖。

代码示例:
set 方法注入:

<bean >
    <!-- 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 />
<bean />
public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }

    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }
}

构造器依赖注入:

<bean >
    <!-- constructor injection using the nested ref element -->
    <constructor-arg>
        <ref bean="anotherExampleBean"/>
    </constructor-arg>

    <!-- constructor injection using the neater ref attribute -->
    <constructor-arg ref="yetAnotherBean"/>

    <constructor-arg type="int" value="1"/>
</bean>

<bean />
<bean />

public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public ExampleBean(
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }
}

依赖配置详细说明

  1. 标签的 value 属性制定了 属性的值(字符串表示)。
<!-- 使用 property 标签配置属性 -->
<bean >
    <!-- 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="misterkaoli"/>
</bean>

<!-- 使用p:命名空间配置属性 -->
<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 
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3306/mydb"
        p:username="root"
        p:password="misterkaoli"/>

</beans>
  1. idref 标签使用其他 bean定义的id进行依赖
<bean />

<bean >
    <property name="targetName">
        <idref bean="theTargetBean"/>
    </property>
</bean>

等用于:

<bean  />

<bean >
    <property name="targetName" value="theTargetBean"/>
</bean>

  1. ref 标签引用其他 Bean
<ref bean="someBean"/>
  1. 内部类
<bean >
    <!-- 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. Collections
    , , , and 标签给java 的List, Set, Map, and Properties 类型赋值
<bean >
    <!-- 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>
  1. 集合合并
<beans>
    <bean >
        <property name="adminEmails">
            <props>
                <prop key="administrator">administrator@example.com</prop>
                <prop key="support">support@example.com</prop>
            </props>
        </property>
    </bean>
    <bean >
        <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>

结果:

administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk

局限性:不能合并不同 collection 类型。

  1. Null 和空字符串
    空字符串:
<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>

Null

<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>

使用 depends-on 标签

<bean >
    <property name="manager" ref="manager" />
</bean>

<bean  />
<bean  />

该depends-on属性可以指定初始化时依赖项,并且仅在单例bean的情况下,还可以指定相应的销毁时依赖项。depends-on在给定的 bean 本身被销毁之前,首先销毁与给定 bean定义关系的依赖 bean 。这样,depends-on也可以控制关机顺序。

** Lazy-initialized Beans(延迟初始化Bean) **
ApplicationContext 的实现类在初始化的时候就创建并配置好了所有单例 Bean 。

lazy-init="true" 告诉容器不要在容器启动时就创建实例,等到第一次请求时在创建。

<bean />
<bean name="not.lazy" class="com.something.AnotherBean"/>

default-lazy-init属性全局设置延迟加载:

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>

自动装配
Spring 容器可以自动装配合作关系的Bean。

<bean>标签的 autowire 属性可以设置自动装配模式

4 中自动装配模式:

  • no: 必须使用ref 元素定义 bean 引用
  • byName:根据属性名进行自动装配
  • byType:根据类型进行自动装配
  • constructor: 和byType类似,但是用在构造参数。

自动装配的局限和劣势:

  • property 和 constructor-arg 设置的显式依赖可能会覆盖自动装配
  • 自动装配不如显式装配精确
  • 容器内的多个 bean 定义可能与要自动装配的 setter 方法或构造函数参数指定的类型相匹配

你可以有几个选择:

  1. 放弃自动装配,使用显示依赖
  2. 避免自动装配一个autowire-candidate="false"的bean
  3. 指定一个Bean为主候选人,设置 primary 属性
  4. 使用基于注解的配置实现更细粒度的控制

从自动装配中排除Bean:
<bean>标签的autowire-candidate属性设置为"false" 即可。

注意 : autowire-candidate属性仅仅对按照类型自动装配生效。

<beans/>标签的default-autowire-candidates属性可以设置多个样式(用逗号分隔)。


方法注入
如果一个单例Bean,有一个非单例Bean 的属性,那么就会有这样的问题,单例Bean只实例化一次,对应非单例Bean的属性也就只创建1个对象,无法在每次用到非单例Bean属性时获取不同的对象。

一个解决方案是 实现ApplicationContextAware接口,将ApplicationContext注入进来后,使用applicationContext.getBean获取非单例Bean的实例。

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

查找方法注入


package fiona.apple;

// no more Spring imports!

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();
}
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean >
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean >
    <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") //查找名字为“myCommand”的原型Bean的一个实例并返回
    protected abstract Command createCommand();
}

ServiceLocatorFactoryBean 类的使用,参考:https://yanbin.blog/spring-servicelocator-pattern/

任一方法替换
用的很少,建议跳过。

public class MyValueCalculator {

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

    // some other methods...
}

/**
 * meant to be used to override the existing computeValue(String)
 * implementation in MyValueCalculator
 */
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 ...;
    }
}
<bean >
    <!-- arbitrary method replacement -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean />

Bean Scopes

Bean 范围

范围 说明
singleton 默认都是单例。作用范围在Spring IOC容器
prototype 一个Bean定义有多个对象实例 (Spring 不会管理完整的生命周期)
request 作用范围在一次 HTTP 请求 (web)
session 作用范围在一个HTTP会话 (web)
application 作用范围在一个ServletContext (web)
websocket 作用范围在一个WebSocket(web)

单例:
Spring Framework 学习笔记——核心技术之Spring IOC
Spring Framework 官网文档学习笔记——核心技术之Spring IOC
原型:
Spring Framework 学习笔记——核心技术之Spring IOC
Spring Framework 官网文档学习笔记——核心技术之Spring IOC

和其他范围相比 Spring 不会管理原型Bean完整的生命周期

request,session,application,websocket
这几个范围是在Spring ApplicationContext 的 web 实现中可用。

初始化 web 配置:

  • Spring Web MVC:使用 DispatcherServlet 处理请求即可。
  • Servlet 2.5 : 需要注册 org.springframework.web.context.request.RequestContextListener
  • Servlet 3.0+:WebApplicationInitializer 接口编程式完成

web.xml 中添加:

<web-app>
    ...
    <listener>
        <listener-class>
            org.springframework.web.context.request.RequestContextListener
        </listener-class>
    </listener>
    ...
</web-app>

<!-- 如果监听器设置有问题,则可以使用 RequestContextFilter -->
<web-app>
    ...
    <filter>
        <filter-name>requestContextFilter</filter-name>
        <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>requestContextFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
</web-app>

Request scope
scope="request" 或注解 @RequestScope

<bean />
@RequestScope
@Component
public class LoginAction {
    // ...
}

Session Scope
scope="session" 或 @SessionScope

<bean />
@SessionScope
@Component
public class UserPreferences  {
    // ...
}

Application Scope

scope="application" 或 @ApplicationScope

<bean />
@ApplicationScope
@Component
public class AppPreferences {
    // ...
}

Scoped Beans as Dependencies
如果你想把一个HTTP request 范围的 Bean 注入到一个更长生命周期的 Bean 中,那么你应该选择使用 aop 代理。

使用 aop:scoped-proxy/ 标签即可:

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

    <!-- an HTTP Session-scoped bean exposed as a proxy -->
    <bean >
        <!-- 命令容器去代理 userPreferences Bean -->
        <aop:scoped-proxy/> 
    </bean>

    <!-- a singleton-scoped bean injected with a proxy to the above bean -->
    <bean >
        <!-- a reference to the proxied userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>

<aop:scoped-proxy/> 默认使用CGLib进行动态代理。如果只想使用JDK动态代理则应该这么配置 :<aop:scoped-proxy proxy-target-class="false"/>

自定义范围

自定义范围集成到Spring 容器中时,需要实现org.springframework.beans.factory.config.Scope接口

示例:

// 使用 new 创建自定义范围SimpleThreadScope对象,注入到beanFactory
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);

定制 Bean 的特性

Spring 提供了很多接口供定制Bean 的特性。分为3组:

  • 生命周期回调
  • ApplicationContextAware and BeanNameAware
  • 其他 Aware 接口
1.生命周期回调

Spring 支持的生命周期回调有3种:

  1. 实现 InitializingBean 接口或 DisposableBean 注解
  2. JSR-250 @PostConstruct 或 @PreDestroy 注解
  3. xml 配置 init-method 或 destroy-method 属性。
  4. 实现Lifecycle 接口(可参与容器的启动和停止处理)

Spring 使用 BeanPostProcessor 接口的实现类去调用任何回调接口的对应方法。

Initialization 回调
Initialization 接口只有1个方法:

void afterPropertiesSet() throws Exception;

Spring建议不要用 实现 InitializingBean 接口进行初始化动作(因为没有必要跟Spring 代码进行关联)。
推荐使用 @PostConstruct注解或在XML配置init-method属性指定初始化方法或在@Bean注解设置 initMethod 属性 。

Destruction 回调

DisposableBean 接口只有1个方法:

void destroy() throws Exception;

Spring建议不要用 实现 DisposableBean 接口进行初始化动作(因为没有必要跟Spring 代码进行关联)。
推荐使用 @PreDestroy注解或在XML配置destroy-method属性指定销毁方法 。

混合生命周期回调

如果一个 Bean 的一个初始化方法使用了 @PostConstruct标注并且 init-method属性也指定相同的方法,那么 初始化方法。

一个配置了多生命周期机制Bean多个初始化方法,调用顺序如下:

  1. Methods annotated with @PostConstruct
  2. afterPropertiesSet() as defined by the InitializingBean callback interface
  3. A custom configured init() method

一个配置了多生命周期机制Bean多个销毁方法,调用顺序如下:

  1. Methods annotated with @PreDestroy
  2. destroy() as defined by the DisposableBean callback interface
  3. A custom configured destroy() method

总结一下: JSR注解排第一,Spring接口实现排第二,自定义排最后。

Startup and Shutdown Callbacks

当 ApplicationContext 收到 start和stop信号时,会级联调用所有的 Lifecycle 接口实现类。通过委派LifecycleProcessor接口去实现。

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

实现SmartLifecycle 接口可以实现更加细粒度的控制

public interface Phased {

    int getPhase();
}

public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

当启动时,getPhase() 返回的值越小,越早启动也越晚停止。getPhase()的值和 “depends-on”关系决定了启动顺序。

非 web 应用的优雅停机

使用 ConfigurableApplicationContext#registerShutdownHook() 方法增加 Hook方法。

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

当 ApplicationContext 创建 ApplicationContextAware 接口的一个实例时,会将这个 ApplicationContext 引用设置给它:

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

获取一个 ApplicationContext 引用的方式:

  • 实现 ApplicationContextAware 接口(不推荐)
  • 使用自动装配

当 ApplicationContext 创建 BeanNameAware 接口的一个实例时,会将这个 Bean 的名字的设置给它:

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

注意: 此回调发生在 生命周期回调之前(InitializingBean, afterPropertiesSet, or a custom init-method.)

3.其他 Aware 接口

比较重要的 Aware 接口

接口名 注入依赖
ApplicationContextAware 声明的 ApplicationContext
ApplicationEventPublisherAware 封装的ApplicationContext的事件发布者。
BeanClassLoaderAware 类加载器
BeanFactoryAware 声明的 BeanFactory
BeanNameAware Bean 的名字
LoadTimeWeaverAware 加载时处理类定义的编织者
MessageSourceAware 处理Message 的策略
NotificationPublisherAware Spring JMX notification publisher.
ResourceLoaderAware 更低等级访问资源的加载器
ServletConfigAware ServletConfig
ServletContextAware ServletContext

Bean Definition 继承

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

<bean 
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">  
    <property name="name" value="override"/>
    <!-- the age property value of 1 will 从 parent 继承 -->
</bean>

如果 有一个Bean 你只想让它做个模板,不希望容器去实例化它。那么你必须设置 abstract="false" 。

容器扩展点

  • BeanPostProcessor :定制Bean
  • BeanFactoryPostProcessor:定制配置元数据
  • FactoryBean:定制实例化逻辑
1.使用 BeanPostProcessor 定制 Bean

BeanPostProcessor 接口定义了2个回调方法,你可以自己实现实例化逻辑,依赖处理逻辑等。可以配置多个 BeanPostProcessor 实例,可以根据他们的 order 属性排序(必须实现Order 接口才能设置 order 属性值 )。

BeanPostProcessor 实例是在Bean 的实例上操作的。也就是, Spring IoC 容器实例化一个Bean然后BeanPostProcessor的实例才开始工作。

BeanPostProcessor 只处理当前容器的Bean的实例。

BeanPostProcessor 有两个回调方法:

package org.springframework.beans.factory.config;

import org.springframework.beans.BeansException;
import org.springframework.lang.Nullable;

public interface BeanPostProcessor {

	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
}
  • postProcessBeforeInitialization 在生命周期回调方法调用前执行
  • postProcessAfterInitialization 在生命周期回调方法调用后执行

ApplicationContext 会自动探测配置元数据里实现 BeanPostProcessor 接口的任何一个Bean。ApplicationContext 把这些Bean注册为后置处理器。

注意: 如果使用工厂方法声明 BeanPostProcessor ,那么返回类型必须是它本身类型或 BeanPostProcessor类型。

ConfigurableBeanFactory#addBeanPostProcessor 方法可编程式注册 BeanPostProcessor实例。

BeanPostProcessor instances and AOP auto-proxying

AOP 自动代理是作为 BeanPostProcessor 的实现。所以不能对这些类进行AOP自动代理。

例子1:打印所有Bean

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;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        https://www.springframework.org/schema/lang/spring-lang.xsd">

    <lang:groovy 
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>

    <!--
    when the above bean (messenger) is instantiated, this custom
    BeanPostProcessor implementation will output the fact to the system console
    -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>

运行

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = ctx.getBean("messenger", Messenger.class);
        System.out.println(messenger);
    }

}

结果:

Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961

例子2:AutowiredAnnotationBeanPostProcessor

2.使用 BeanFactoryPostProcessor 定制 Bean Definition

BeanFactoryPostProcessor 和 BeanPostProcessor 主要的不同是:BeanFactoryPostProcessor处理的是Bean的配置元数据。

Spring IoC 允许 BeanFactoryPostProcessor 读取并修改Bean的配置元数据。

可以配置多个 BeanFactoryPostProcessor 实例,可以通过实现 Order接口进行排序。

BeanFactoryPostProcessor 实例是容器隔离的。

预定义了几个Bean工厂后置处理器 :

  • PropertyOverrideConfigurer (覆盖属性的默认值)
  • PropertySourcesPlaceholderConfigurer(支持外部化配置)

容器会自动探测BeanFactoryPostProcessor的实例。

Bean(Factory)PostProcessor 设置懒加载会失效。

PropertySourcesPlaceholderConfigurer 示例:

dataSource 这个bean 的属性值,由一个外部 properties 提供。
PropertySourcesPlaceholderConfigurer 会从 jdbc.properties 读取属性信息并替换掉 dataSource的属性值。

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

<bean 
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

context 命名空间也可以指定:

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

PropertySourcesPlaceholderConfigurer 不仅可以从properties 文件中读取。默认,如果在 properties 文件没找到属性,那么就会从 Environment 或 System 读取

PropertySourcesPlaceholderConfigurer 替换类名:


<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
    <property name="locations">
        <value>classpath:com/something/strategy.properties</value>
    </property>
    <property name="properties">
        <value>custom.strategy.class=com.something.DefaultStrategy</value>
    </property>
</bean>

<bean />

PropertyOverrideConfigurer
如果 Properties 配置文件没有找到 Bean 的属性值,那么就会用默认上下文。

多个PropertyOverrideConfigurer 会以最后一个覆盖为准。

Spring 2.5以后可以这么配置:

<context:property-override location="classpath:override.properties"/>
2.使用 FactoryBean 定制实例化逻辑

你可以实现 org.springframework.beans.factory.FactoryBean 接,创建一个 对象工厂。

如果你想从 容器中获取 FactoryBean 的实例,那么需要在getBean时, Bean id 增加前缀 &

基于注解容器配置

Spring 提供了基于注解的配置,通过字节码元数据替换XML里的配置。

Spring 2.0 :@Required,支持JSR-250 @PostConstruct和@PreDestroy
Spring 2.5:@Autowired,支持JSR-330 @Inject 和@Named

注解注入,早于 XML 注入。因此 XML 配置会覆盖 注解配置。

你可以像以往一样每个 BeanPostProcesser 配置一个,也可使用 <context:annotation-config/> 注册多个BeanPostProcesser:


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

    <context:annotation-config/>

</beans>

<context:annotation-config/> 注册了以下几个 BeanPostProcesser:

  • ConfigurationClassPostProcessor
  • AutowiredAnnotationBeanPostProcessor
  • CommonAnnotationBeanPostProcessor
  • PersistenceAnnotationBeanPostProcessor
  • EventListenerMethodProcessor

context:annotation-config/ 只会在同一个 application Conttext 中查找beans 上的注解。

1. @Required 注解(Spring 5.1 已过时)

@Required 作用在方法上。表明set方法必须注入值,如果没有则抛异常。

必须注册 RequiredAnnotationBeanPostProcessor 类到容器中,才能保证 @Required生效。

注意: 5.1 以后此注解和RequiredAnnotationBeanPostProcessor 都已过时。

2. @Autowired 注解

@Inject 可以替代 @Autowired

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

从 Spring Frameword 4.3 开始,如果 MovieRecommender 只有1个构造方法,那么没必要标注 @Autowired;如果 MovieRecommender 有多个构造方法且没有默认构造器,那么需要标注 @Autowired告诉容器哪个构造器去实例化。

@Autowired 注解它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。
注意事项:
  在使用@Autowired时,首先在容器中查询对应类型的bean

  • 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据
  • 如果查询的结果不止一个,那么@Autowired会根据名称(@Qualifier注解)来查找。
  • 如果查询的结果为空,那么会抛出异常。解决方法时,使用required=false
3.@Primary 注解微调 @Autowired

按照类型自动装配,可能会匹配到多个候选人,那么@Primary 注解指明哪个是Bean 是需要提供的。

4.@Qualifier 注解微调 @Autowired

@Qualifier 注解让你在自动装配时可以选择哪个Bean进行装配。

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

    // ...
}

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

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/> 

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/> 

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean />

</beans>

Bean 名称是默认的限定符。

@Autowired适用于字段、构造函数和多参数方法,允许在参数级别通过限定符注释进行缩小。@Resource 仅支持带有单个参数的字段和 bean 属性设置器方法。

自定义限定符

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

    // ...
}

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

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Action"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Comedy"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="DVD"/>
        <meta key="genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="BLURAY"/>
        <meta key="genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

</beans>

5.使用 CustomAutowireConfigurer

CustomAutowireConfigurer 是 BeanFactoryPostProcessor,让你注册定制的限定符类型

<bean 
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>

AutowireCandidateResolver 由以下条件决定自动装配候选:

  • 每个Bean定义的 autowire-candidate 属性值
  • <beans>标签 的 default-autowire-candidates 属性
  • @Qualifier注解或CustomAutowireConfigurer自定义 限定符
6.@Resource 注解(javax.annotation.Resource)

Spring 2.5 之后也支持 @Resource 注解通过属性或set方法注入。
@Resource 注解获取一个 name 属性。默认情况下 Spring 将那个name属性值作为 Bean 的名字。

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

如果没有指定name属性值,那么如果 @Resource 在setter方法上,则取属性值作为Bean的名字进行注入;如果 @Resource 修饰在filed上,则取filed那么作为Bean的名字进行注入。

下面这个例子,优先查找名字是“customerPreferenceDao”的Bean,然后才会查找 CustomerPreferenceDao 类型的 primary type

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; 

    public MovieRecommender() {
    }

    // ...
}
7.@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 { }

application.properties :

catalog.name=MovieCatalog

声明一个 PropertySourcesPlaceholderConfigurer 去处理不存在的值。

@Configuration
public class AppConfig {

	//注意:如果使用 JavaConfig配置 PropertySourcesPlaceholderConfigurer,方法必须是 static
     @Bean
     public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
           return new PropertySourcesPlaceholderConfigurer();
     }
}

Spring boot 默认配置了 PropertySourcesPlaceholderConfigurer 。

8. @PostConstruct 注解和 @PreDestroy 注解

CommonAnnotationBeanPostProcessor 可以认出 @Resource注解, @PostConstruct 注解和 @PreDestroy 注解。

public class CachingMovieLister {

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

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

Classpath 扫描和管理 Components

1. @Component 和更多的模板注解

Spring 提供的更多模板注解:@Component,@Repository,@Service 和 @Controller。@Repository,@Service 和 @Controller这三个是@Component的特殊化用于更多明确的使用场景。

2. 使用元注解和组合注解

@Service 注解标注了@Component:

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

    // ...
}
// The Component causes @Service to be treated in the same way as @Component.

@RestController 注解就是 @Controller 和 @ResponseBody 组合。

3. 自动探测 Class 和注册 BeanDefinition

@ComponentScan 或 context:component-scan/可以扫描标记了模板注解的类并注册到容器中。

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

    <context:component-scan base-package="org.example"/>

</beans>

context:component-scan/ 隐式的开启了 context:annotation-config。如果想禁止注册AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor,那么将 context:component-scan/ 标签的 annotation-config 属性设置为false。

使用 过滤器去定制 Scan Bean:

默认, 自动探测标注@Component, @Repository, @Service, @Controller, @Configuration 注解和 @Component注解的派生注解的类为。

@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    ...
}
<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>

过滤器类型:

type expression
annotation (default) org.example.SomeAnnotation
assignable(指定) org.example.SomeClass
aspectj(切面表达式) org.example..*Service+
regex (正则表达式) org.example.Default.*
custom (自定义TypeFilter) org.springframework.core.type.TypeFilter 接口的一个实例

使用 标签禁止默认过滤器

Spring components 可以提供BeanDefinition给 容器

@Component
public class FactoryMethodComponent {

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

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

@Component标注的class中 @Bean 注解的方法,不会被 CGLIB 拦截.

CGLIB subclassing can override only non-static methods.

自动探测组件命名
如果一个组件被自动扫描到,它的 BeanName 是由 BeanNameGenerator 生成。
如果 @Component, @Repository, @Service, @Controller 注解指定value属性,那么value就是Bean 名字;如果未指定,Bean 名字就是 首字母小写的类名。这是模式规则。

如果不喜欢默认规则,可以实现 BeanNameGenerator 接口定制名字生成规则。

@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example"
        name-generator="org.example.MyNameGenerator" />
</beans>

自动探测组件范围

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

可以实现 ScopeMetadataResolver 接口实现自定义范围处理:

@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>

对标注了 @Scope注解的Bean生成代理:

@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>

@Qualifier注解


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

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

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

为候选组件生成下标

引入 jar 包:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>5.3.9</version>
        <optional>true</optional>
    </dependency>
</dependencies>

The spring-context-indexer artifact generates a META-INF/spring.components file that is included in the jar file.

使用 JSR 330 注解

引入jar包

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

Spring 注解和JSR-330 注解对比

Spring javax.inject.* JSR330的限制
@Autowired @Inject @Inject has no 'required' attribute. Can be used with Java 8’s Optional instead.
@Component @Named / @ManagedBean JSR-330 does not provide a composable model, only a way to identify named components.
@Scope("singleton") @Singleton The JSR-330 default scope is like Spring’s prototype. However, in order to keep it consistent with Spring’s general defaults, a JSR-330 bean declared in the Spring container is a singleton by default. In order to use a scope other than singleton, you should use Spring’s @Scope annotation. javax.inject also provides a @Scope annotation. Nevertheless, this one is only intended to be used for creating your own annotations.
@Qualifier @Qualifier / @Named javax.inject.Qualifier is just a meta-annotation for building custom qualifiers. Concrete String qualifiers (like Spring’s @Qualifier with a value) can be associated through javax.inject.Named.
@Value
@Required
@Lazy
ObjectFactory Provider javax.inject.Provider is a direct alternative to Spring’s ObjectFactory, only with a shorter get() method name. It can also be used in combination with Spring’s @Autowired or with non-annotated constructors and setter methods.

基于Java代码容器配置

介绍了怎么通过使用标注注解的java代码配置 Spring 容器。

1. @Bean 和 @Configuration

@Bean指明一个方法实例化,配置,初始化一个新的对象交给 Spring IOC 容器管理。
@Configuration 指明一个类是 bean definitions 的来源。
使用:

@Configuration
public class AppConfig {

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

Full @Configuration Vs 轻量级 @Bean 模式 :

@Bean 方法没在 @Configuration 类中,成为轻量级模式。
@Component 类中 @Bean 方法相当于工厂方法。@Component 类中@Bean 方法不能通过方法调用方式使用另个@Bean 方法的Bean。

@Configuration 类中@Bean 方法可以通过方法调用方式使用另个@Bean 方法的Bean。

** Java-based Configuration 内部使用**:


@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();
    }
}
2. 使用 AnnotationConfigApplicationContext 实例化 Spring IOC

可以使用 @Configuration 注解标注的AppConfig.class作为实例化AnnotationConfigApplicationContext的输入:

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

AnnotationConfigApplicationContext 也支持 @Component 和 JSR-330 注解。

编程式构建容器:

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

开启组件扫描:


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

扫描 com.acme 包下 @Component 类,并注册为 BeanDefinition。

支持 Web 应用的 AnnotationConfigWebApplicationContext

web.xml 示例:

<web-app>
    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
        instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration locations must consist of one or more comma- or space-delimited
        fully-qualified @Configuration classes. Fully-qualified packages may also be
        specified for component-scanning -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Declare a Spring MVC DispatcherServlet as usual -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
            instead of the default XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- Again, config locations must consist of one or more comma- or space-delimited
            and fully-qualified @Configuration classes -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- map all requests for /app/* to the dispatcher servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

3. 组合Java-based Configuration

使用 @Import 或 标签,加载另一个配置类里的 Bean 定义:

@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

Spring Framework 4.2 @Import 支持 @Component 类

@Bean 方法提供 BeanPostProcessor and BeanFactoryPostProcessor definitions 时 一定要是 static 方法!
@Autowired and @Value 在 @Configuration 类中可能不生效。

@Configuration 类正确使用 @Autowired:

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

使用 @Conditional 注解:
@Profile 是 @Conditional 实现的:

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

使用 @ImportResource 注解导入xml配置的Bean definition:

@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);
    }
}
<!-- properties-config.xml -->
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>

Environment 抽象

Environment 接口是集成在容器中,模拟了应用环境的2个关键方面:profiles 和 properties.

profile 是一组 Bean Definition 。只有提供的 profile激活时,才注册到容器里。

使用 @Profile

当一个或多个profile激活时,@Profile 指明一个满足条件的组件可以注册。

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

表达式:

  • !: A logical “not” of the profile
  • &: A logical “and” of the profiles
  • |: A logical “or” of the profiles

注意: production & us-east | eu-central 不允许这么写;必须写成 production & (us-east | eu-central)

@Profile 使用在方法层面:


@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");
    }
}
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <!-- other bean definitions -->

    <beans profile="development">
        <jdbc:embedded-database >
            <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
            <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
        </jdbc:embedded-database>
    </beans>

    <beans profile="production">
        <jee:jndi-lookup />
    </beans>
</beans>

激活profile

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();

还可以使用ystem environment variables, JVM system properties, servlet context parameters in web.xml, or even as an entry in JNDI 设置 spring.profiles.active属性。

PropertySource

Environment 对象在一套 PropertySource 对象中执行查找。

StandardEnvironment 被配置了2个 PropertySource 对象:

  • System.getProperties()
  • System.getenv()

StandardServletEnvironment 比 多了2个默认属性源:servlet config and servlet context parameters

StandardServletEnvironment 数据源优先级(由高到低):

  1. ServletConfig parameters
  2. ServletContext parameters (web.xml context-param entries)
  3. JNDI environment variables (java:comp/env/ entries)
  4. JVM system properties (-D command-line arguments)
  5. JVM system environment (operating system environment variables)

定制属性源顺序:

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
使用 @PropertySource 注解

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

注册一个 LoadTimeWeaver

LoadTimeWeaver 动态转换类

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}

XML 配置:

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

ApplicationContext 额外的能力

  • 国际化
  • 资源加载
  • 事件发布
  • 上下文继承
国际化MessageSource

ApplicationContext 接口扩展了 MessageSource 接口。

Spring 提供了 3 个 MessageSource 接口实现:

  • ResourceBundleMessageSource
  • ReloadableResourceBundleMessageSource
  • StaticMessageSource

ResourceBundleMessageSource 示例:

<beans>
    <bean 
            class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>format</value>
                <value>exceptions</value>
                <value>windows</value>
            </list>
        </property>
    </bean>
</beans>
  # in format.properties
    message=Alligators rock!
  # in exceptions.properties
    argument.required=The {0} argument is required.

if you want to resolve messages against the British (en-GB) locale, you would create files called format_en_GB.properties, exceptions_en_GB.properties, and windows_en_GB.properties

标准和定制事件
  • ContextRefreshedEvent
  • ContextStartedEvent
  • ContextStoppedEvent
  • ContextClosedEvent
  • RequestHandledEvent
  • ServletRequestHandledEvent

继承 ApplicationEvent 实现定制事件:

// 自定义事件
public class BlockedListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

    public BlockedListEvent(Object source, String address, String content) {
        super(source);
        this.address = address;
        this.content = content;
    }

    // accessor and other methods...
}

// 发布事件
public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blockedList;
    private ApplicationEventPublisher publisher;

    public void setBlockedList(List<String> blockedList) {
        this.blockedList = blockedList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String content) {
        if (blockedList.contains(address)) {
            publisher.publishEvent(new BlockedListEvent(this, address, content));
            return;
        }
        // send email...
    }
}

// 通知者
public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlockedListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

基于注解的事件监听器

public class BlockedListNotifier {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    @EventListener
    public void processBlockedListEvent(BlockedListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

基于注解的事件异步监听器

@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
    // BlockedListEvent is processed in a separate thread
}


@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress...
}

@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    // ...
}
Application Startup Tracking

Application 接口管理着 Spring 应用的生命周期和围绕组件丰富的编程模型。

AnnotationConfigApplicationContext 检测例子:

// create a startup step and start recording
StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan");
// add tagging information to the current step
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
// perform the actual phase we're instrumenting
this.scanner.scan(basePackages);
// end the current step
scanPackages.end();
Web 应用实例化ApplicationContext

使用 ContextLoaderListener 注册 ApplicationContext

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 如果没配置 contextConfigLocation 默认加载 /WEB-INF/applicationContext.xml  -->

BeanFactory

Feature BeanFactory ApplicationContext
Bean instantiation/wiring Yes Yes
Integrated lifecycle management No Yes
Automatic BeanPostProcessor registration No Yes
Automatic BeanFactoryPostProcessor registration No Yes
Convenient MessageSource access (for internalization) No Yes
Built-in ApplicationEvent publication mechanism No Yes