Spring IOC官方文档简翻

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/guanhang89/article/details/79521304

Spring官方文档简翻之IOC

Version 5.0.2.RELEASE,地址:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-introduction

有人觉得学好Spring需要深入源码,但是我认为,除非你需要借鉴和利用Spring中的设计开发元素来完成自己的框架开发,不然没必要深入源码来学习Spring的使用(很多时候这些东西看了也很快遗忘)。自我感觉我对Spring的了解还不够,尝试简单翻译一下官方文档,仅供参考。

IOC

beans和context两个包是IOC的基础,BeanFactory接口提供了管理bean的机制,而 ApplicationContext是BeanFactory的一个子接口,它增加了AOP的整合,资源国际化,事件发布,以及应用层的context,比如WebApplicationContext。

简单点的说,BeanFactory提供了配置框架和基本功能,ApplicationContext增加了企业开发需要的特性,Spring的IOC容器一般也就是指ApplicationContext。

IOC概述

ApplicationContext有不同的实现,像ClassPathXmlApplicationContext,FileSystemXmlApplicationContext。可以使用xml,注解以及java代码来配置bean(xml是一种传统的方式)

IOC依赖某种形式的配置元数据,这个配置的元数据(一般是xml,注解,或者java代码)告诉IOC容器怎么初始化,配置以及装配对象。这所说的java代码配置方式是指使用@Bean,@Configureation来配置的bean

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
        http://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>

容器概述:

多个配置文件用逗号隔开

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.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>

另外路径要注意的是:

不要使用../来指定文件路径,可能会导致依赖当前应用之外的文件

特别是不要加上classpath来使用,比如:classpath:../services.xml

运行时会从最近配置的classpath中的父目录查找,classpath配置不同,导致的结果也不同

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

spring的bean的定义将会转化为BeanDefinition类

可以通过DefaultListableBeanFactory.registerSingleton()或者registerBeanDefinition来注册容器之外产生的bean

一、bean的命名

bean的标识符必须唯一,一般情况下只有一个标识符,但可以有多个名称

在xml配置中,id, name 都是指的标识符,bean可以定义多个名称,在name属性中指定(逗号,分号或者空格分隔多个别名)

如果一个bean没有定义ID,则将会以它的simple name作为名字(首字母小写,如果多个大写字母开头,则保持原样)

定义别名:

实际上name属性中定义的名字也算别名

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

二、初始化bean

配置内部类,要加上$符号,也就是编译后的内部类的名称

配置静态工厂方法产生的类:

不用指定生成的类的类型,只要工厂类有对应的静态方法

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

配置实例工厂方法产生的类:

指定生成类的方法名,注意实例工厂使用的是factory-bean而不是class

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

依赖注入

DI主要有两种形式:构造器注入和setter注入

一、构造器注入

需要有一个有参的构造函数

public class Foo {

   public Foo(Bar bar, Baz baz) {
       // ...
   }
}

如果bar和baz没有继承关系,则在构造参数中不需指明索引或类型

   <beans>
       <bean id="foo" class="x.y.Foo">
           <constructor-arg ref="bar"/>
           <constructor-arg ref="baz"/>
       </bean>

       <bean id="bar" class="x.y.Bar"/>

       <bean id="baz" class="x.y.Baz"/>
   </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>

由于在运行时参数名称是不可见的(编译时未打开debug flag,即javac –g:vars),这时候需要使用JDK注解来说明参数名称

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

二、setter注入

<bean id="id" class="com.loster.li.Id">  
    <property name="id" value="123"></property>  
    <property name="name" value="xiaoli"></property>  
</bean>  

setter注入是在调用一个无参的构造函数,或者无参的静态工厂方法之后返回bean后的注入。基于setter和基于构造器可以混用,混用原则:

构造器注入适合强依赖,setter注入适合选择性依赖。同时在setter方法上加上@Required注解可以表示这个注入是必须的

构造器注入是不能为null的,不然会报错,所以spring推荐用构造器注入,换句话说setter注入可以是null的。同时通过构造器注入的bean是处于完全初始化的状态

对于属性赋值,spring支持字符串转int,long,String,boolean等

三、循环依赖问题

默认情况下,spring容器在启动的时候创建bean。lazy-init属性可以让bean在访问的时候再创建

如果类A使用构造器注入类B,类B又使用构造器注入类A,在运行的时候,Spring会抛出BeanCurrentlyInCreationException

可行的解决办法:让某些类使用setter注入,而不是构造器注入,或者只用setter注入。因此哪怕不推荐,你也可以使用setter注入来构造循环依赖

spring会尽量晚的进行属性注入和解决依赖问题,这表示有可能在容器正确加载之后也可能抛异常。比如类属性不存在或者不合法,这也是为什么spirng默认是预先初始化,把发现异常的时间提前。

如果bean A依赖bean B,spring容器在初始的时候会先初始化B

四、依赖注入的案例

setter注入:

<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"/>
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 id="exampleBean" class="examples.ExampleBean">
    <!-- 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 id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
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. 静态工厂返回的bean可以并不是该静态工厂方法所在的类,静态工厂配置的id是工厂生成的类的ID,class是静态工厂所在的class
  2. 非静态的实例工厂方法的使用方法类似,只不过把静态工厂中配置的class改成factory-bean,因为非静态的,需要工厂实例存在
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
    <constructor-arg ref="anotherExampleBean"/>
    <constructor-arg ref="yetAnotherBean"/>
    <constructor-arg value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
    // a private constructor
    private ExampleBean(...) {
        ...
    }
    // a static factory method; the arguments to this method can be
    // considered the dependencies of the bean that is returned,
    // regardless of how those arguments are actually used.
    public static ExampleBean createInstance (
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        ExampleBean eb = new ExampleBean (...);
        // some other operations...
        return eb;
    }
}

依赖的详细配置

一、直接的赋值:

使用value属性

<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命名空间来简化赋值

<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
    http://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.beans.factory.config.PropertyPlaceholderConfigurer">

    <!-- 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使用

可以使用在constructor-arg和property标签上,注意它不是引用

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

推荐用上面,因为在运行之前就能够发现问题。常用在配置代理拦截类,防止拼写错误

二、引用其他类

在constructor-arg和property标签上可以使用ref来引用其他类

最简单的引用方式:

可以用来引用当前容器或者父容器的bean(bean的赋值内容可以是某个的name属性)

<ref bean="someBean"/>

引用父容器中的bean

<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
    <!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<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>

三、直接在property和constructor-arg内部配置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>

内部bean定义不需要id和name,内部bean是匿名的,而且随着外部bean的创建而创建,所以它会忽略scope配置

四、配置集合属性

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">[email protected]</prop>
            <prop key="support">[email protected]</prop>
            <prop key="development">[email protected]</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>

Map的key和value,以及set的value,都可以使用在下面这些元素上

bean | ref | idref | list | set | map | props | value | null

集合的继承使用:

子集合可以覆盖父集合,类似于继承,同样适用于list,map,set

xml的merge属性需要作用于子集合上

<beans>
    <bean id="parent" abstract="true" class="example.ComplexObject">
        <property name="adminEmails">
            <props>
                <prop key="administrator">[email protected]</prop>
                <prop key="support">[email protected]</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">[email protected]</prop>
                <prop key="support">[email protected]</prop>
            </props>
        </property>
    </bean>
<beans>

最后的结果:

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

对于list说,因为需要维护顺序,所以父集合的顺序自带子集合之前

指定类型的集合

java5之后自带的

public class Foo {
    private Map<String, Float> accounts;
    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}

赋的值都会转成Fload

<beans>
    <bean id="foo" class="x.y.Foo">
        <property name="accounts">
            <map>
                <entry key="one" value="9.99"/>
                <entry key="two" value="2.75"/>
                <entry key="six" value="3.99"/>
            </map>
        </property>
    </bean>
</beans>

五、空字符串以及null

空字符串

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

null:

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

六、p命名空间的使用

两种方法的对比:

用来取代property标签

<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
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="[email protected]"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="[email protected]"/>
</beans>
<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
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="john-classic" class="com.example.Person">
        <property name="name" value="John Doe"/>
        <property name="spouse" ref="jane"/>
    </bean>

    <bean name="john-modern"
        class="com.example.Person"
        p:name="John Doe"
        <!--注意这里-->
        p:spouse-ref="jane"/>

    <bean name="jane" class="com.example.Person">
        <property name="name" value="Jane Doe"/>
    </bean>
</beans>

七、c命名空间的使用

用于构造参数

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

    <bean id="bar" class="x.y.Bar"/>
    <bean id="baz" class="x.y.Baz"/>

    <!-- traditional declaration -->
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
        <constructor-arg value="[email protected]"/>
    </bean>

    <!-- c-namespace declaration -->
    <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="[email protected]"/>

</beans>

如果不清楚构造函数的名称是什么,可以使用参数的位置:

<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>

八、嵌套赋值

注意,当bean初始化后,这里的fred和bob不能为null,不然会报空指针异常

<bean id="foo" class="foo.Bar">
    <property name="fred.bob.sammy" value="123" />
</bean>

使用depends-on

在xml配置中,常用ref来表示一个bean要依赖另一个bean;但有时候依赖并不是直接的,这时候就需要depends-on属性,让被依赖的bean在之前初始化,同时销毁的时候,要依赖的bean先销毁

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

多个依赖bean的配置:

可以用逗号,分号,空格隔开

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

lazy-init:懒初始化

对于单利的bean,是在ApplicationContext初始化的时候就会创建。也可以将bean配置成懒初始化,也就是在bean在第一次访问的时候创建

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

自动匹配

可以在bean元素中使用autowire来自动匹配bean,有四种匹配模式:

模式 解释
No 无自动匹配,需要用ref来定义
byName 根据名称匹配。比如一个bean有一个mater属性(即有setMaster()方法),spring将会找名字为master的bean,然后注入
byType 根据类型匹配,如果有多个类型匹配,则会出错(除数组、集合、Map之外)
constructor 和byType类似,但是是作用在构造方法的参数上

当使用byType或者constructor模式,可以自动装配数组和类型化的集合,容器会从所有的候选bean中选取匹配的类注入。同时如果是自动装配一个map,map的key是String类型的,那么map的值会包括所有类型匹配的实例,而且map的key将会是对应的bean的名字。

缺陷:

不推荐一个项目只有很少的类使用autowire,应该保持风格的一致性

不能autowire原始类型,String,Classes

autowire 没有显示配置准确

排除不自动匹配的bean:

在bean标签中加入autowire-candidate=false可以避免该bean被拿来匹配,如果设置为false,@Autowired也不会匹配上

限制配置模式:

default-autowire-candidates=”*Repository”,表示只会让Repository结尾的类匹配,多个模式用都好隔开。注意,autowire-candidate的优先级要高于这个

方法注入(Method injection)

当一个单例的bean A中的方法在调用的使用需要依赖非单例的bean B,如果只像前面介绍的那样配置一下会出现问题,因为容器只会初始化A一次,因此只会设置一次bean B。当A的方法被调用时,不能给A提供新的B的实例。

这时候就需要我们手动的getBean来设置:

// 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!
        //注意,这里依赖了spring的api,不是很推荐
        return this.applicationContext.getBean("command", Command.class);
    }

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

一、查找方法注入(Lookup method)

上面的方式由于和Spring耦合,并不推荐。

Lookup 方法注入能够重写容器管理的bean,然后返回对另一个bean的查找结果,通常用在有非单例bean的场景,类似于上面。Spring采用CGLIB重写Lookup method

一般是定义了一个抽象类,用来继承重写,也可以是实体类

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?
    //查找方法,spring会代理重写这个类,查找返回期望类型的原型(prototype)bean
    protected abstract Command createCommand();
}

查找方法的写法:

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

spring的配置:

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<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>

commandManager需要myCommand类的一个新实例时,可以调用createCommand()方法。需要注意的是myCommand要配置成prototype,不然没意义,每次返回的都是同一个

也可以是用注解的方式配置:

public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }
    //括号里的名字如果不加,会按照类型解析
    @Lookup("myCommand")
    protected abstract Command createCommand();
}

二、任意方法的替换

也是方法注入的一种,只不过用的很少

public class MyValueCalculator {
    public String computeValue(String input) {
        // some real code...
    }
    // some other methods...
}
/**
 * 用来替换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 ...;
    }
}

Spring配置:

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

<arg-type/>可以使用多个 ,String也可以简写成:

java.lang.String
String
Str

bean的范围

这里范围指的是一个bean definition的作用范围,或者说由一个bean definition创建的一个对象的作用范围。一个bean definition可以创建多个实例

bean的范围目前可以使用的有六种,其中四种用于web环境

范围 描述
单例(singleton) 默认的,容器只创建一个实例
原型(prototype) 多例的
request 一个http request生命周期一个实例
session 一个session 生命周期一个实例
application 一个 ServletContext生命周期一个实例
websocket 一个WebSocket生命周期一个实例

单例

默认的范围。该bean的bean definition只会生成一个实例,作用于整个IOC容器的使用。

注意,不是一个ClassLoader一个实例,而是一个IOC容器一个实例

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

<!-- 同上 -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>     

原型(prototype)范围

  1. 原型类似于原型模式,通过一个原型能够复制出多个实例
  2. 一个prototype适用于状态变化的bean,而singleton适用于无状态的bean。如果一个bean是prototype,每一次使用(注入)都是一个新的bean
  3. 需要注意的时候,容器不会完全管理多例bean的生命周期,调用方需要合理的销毁bean,比如释放占用的资源

依赖的注意

当使用prototype bean时,相关的依赖解析是在运行时才进行的

Request,session,application,Websocket范围

在web环境下才会使用这些范围,如果你使用在ClassPathXmlApplicationContext中,将会报异常

一、初始化web配置

使用这些范围要初始化相关的web环境,这取决于使用的是什么web容器。在Spring MVC中,请求通过DispatcherServlet进行处理,不需要进行特别的设置。

如果使用的是Servlet2.5的web容器,并且请求其他的方法进行处理(比如Struts),这是就需要注册org.springframework.web.context.request.RequestContextListener.ServletRequestListener,对于Servlet3.0+,可以通过编程继承实现WebApplicationInitializer接口,或者使用下面的xml配置:

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

如果配置监听器有问题,可以使用Spring的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>

DispatcherServlet,RequestContextListener,以及RequetContextFilter都是做的同样一件事,就是将HTTP request绑定到请求线程。

二、Request 范围

通过下面的方式配置:

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

代码配置:

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

三、Session范围

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
@SessionScope
@Component
public class UserPreferences {
    // ...
}

四、Application范围

也就是ServletContext级别,作为ServletContext一个属性保存

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

五、依赖问题

如果将HTTP request注入到一个生命周期更长的一个作用域(此时request可能已经不存在),可以采用注入一个AOP代理类的方式来实现,spring提供了如下的配置方式:

<?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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- an HTTP Session-scoped bean exposed as a proxy -->
    <!--该bean是有范围的,实例只在session范围中-->
    <bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
        <!-- instructs the container to proxy the surrounding bean -->
        <aop:scoped-proxy/>
    </bean>

    <!--该bean是单例的,在IOC容器中起作用-->
    <!-- a singleton-scoped bean injected with a proxy to the above bean -->
    <bean id="userService" class="com.foo.SimpleUserService">
        <!-- a reference to the proxied userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>

原因:

如果不采用上面的代理形式,由于单例的bean只会初始化一次,它的依赖也只会初始化一次,因此userService操作的userPreferences都是一开始注入的那个,这显然不是我们想要的结果

采用上面的代理形式后,每当调用代理类的方法时,代理类会从当前的HTTP Session中获取UserPreferences对象,然后代理这个对象

使用aop:scoped-proxy标签时,将会使用CGLIB创建代理对象,CGLIB只会代理public方法,对于非public方法,代理无效。

也可以使用JDK的基于接口的代理,只需要将proxy-target-class设置为false

<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.foo.DefaultUserPreferences" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>

<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

注意:aop:scoped-proxy对于FactoryBean的实现类不起作用,返回的仍是factory bean本身,而不是从getObject拿到的返回值

自定义范围

自定义自己的范围,需要实现org.springframework.beans.factory.config.Scope接口:

主要有四个方法:

从scope中获取对象的方法

从scope中移除对象

注册销毁的时候的回调函数

获取会话标识符

Object get(String name, ObjectFactory objectFactory)
Object remove(String name)
void registerDestructionCallback(String name, Runnable destructionCallback)
String getConversationId()

向Spring容器注册一个Scope(该方法来源于ConfigurableBeanFactory接口):

第一个参数中的scope名称是唯一的,第二个参数是Scope的实现类

void registerScope(String scopeName, Scope scope);

也可以声明式的注册Scope:

<?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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="thread">
                    <bean class="org.springframework.context.support.SimpleThreadScope"/>
                </entry>
            </map>
        </property>
    </bean>
    <bean id="bar" class="x.y.Bar" scope="thread">
        <property name="name" value="Rick"/>
        <aop:scoped-proxy/>
    </bean>
    <bean id="foo" class="x.y.Foo">
        <property name="bar" ref="bar"/>
    </bean>
</beans>

自定义bean的特性

生命周期回调

如果想介入容器管理的bean的生命周期,可以实现InitializingBean以及DisposableBean接口,对于前者,容器会调用afterPropertiesSet方法,对于后者会调用destroy方法。

注意:

Spring推荐使用@PostConstruct和@PreDistory注解来实现生命周期的回掉,或者使用init方法或者destroy方法来实现

在Spring的内部是使用BeanPostProcessor的实现类来处理任何的实现的生命周期的回调(对内的接口,功能更多),因此,可以实现该接口来自定义生命周期的回调处理

除了初始化和销毁的回调,Spring管理的bean同时也实现了LifeCycle的接口,从而能够参与容器本身的创建和销毁的声明周期(跟容器挂钩而不是跟bean的生命周期挂钩)

一、初始化回调

InitializingBean接口允许bean来实现初始化工作(相关的属性都设置之后),该接口含有一个方法:

void afterPropertiesSet() throws Exception;     

不推荐使用该接口,因为会和spring耦合。可以使用@PostConstruct注解或自己实现一个POJO的初始化方法。如果是使用xml来配置spring的,使用init-method属性来配置初始化方法名(void型,无参的方法)

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

    public void init() {
        // do some initialization work
    }
}

上面的代码等同于:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {

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

二、销毁回调

实现DisposableBean接口就能实现销毁回调方法,该接口只有一个方法:

void destroy() throws Exception;

同上实现该方法同在xml配置中配置destroy-method的效果一样

destroy-method的值也可以指向一个类,该类实现了java.lang.AutoCloseable 或者java.io.Closeable接口,这样在销毁的时候会调用接口中的close或者shutdown方法

三、默认初始化和销毁方法(全局的)

如果不使用spring提供的接口来实现初始化和销毁回调,仅仅在类中写了名如init(),initialize(),dispose()等方法,并且在一个项目中都按这种方式命名,这时我们可以配置让容器去“查找”这些初始化和销毁方法并作用在每个bean上,下面是例子:

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.");
        }
    }
}                                                                
<beans default-init-method="init">
    <bean id="blogService" class="com.foo.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>
</beans>

同样,销毁回调使用default-destroy-method属性来配置

spring是在bean满足初始化依赖后才会进行相关的初始化工作,因此初始化回调作用在原生的bean上,对于AOP来说,此时是不生效的。只有当bean完全初始化后才会创建代理对象。

四、生命周期组合机制

spring提供了三种控制bean生命周期的方式:

InitializingBea 和DisposableBean接口

自定义init()和destroy()方法

使用@PostConstruct或者@PreDestroy注解

如果多个生命周期的方法名称重复,该方法只会执行一次,如果有多个,会按照一定的顺序执行,执行顺序如下:

@PostConstruct

InitializingBean接口

自定义的init()方法

销毁方法顺序同上

五、启动和停止回调

Lifecycle提供了一些方法来满足对象声明周期的要求(比如启动或者停止一些后台程序)

public interface Lifecycle {
    void start();
    void stop();
    boolean isRunning();
}

当IOC接收到启动或者停止信号时,就会依次调用这些方法。spring通过委托LifecycleProcessor来实现。

由于bean之间存在依赖,因此启动和停止的顺序应该和依赖关系一致,但是有时这些依赖并不能直接知道,这时候spring提供了SmartLifecycle接口来解决,解决思路:

public interface SmartLifecycle extends Lifecycle, Phased {
    boolean isAutoStartup();
    void stop(Runnable callback);
}

通过实现Phase接口的getPhase方法,返回一个int值,值越小表示启动优先级越高,销毁优先级越低,只实现普通LifeCycle的类的phase是0,因此如果你定义的类的phase是负数,那么表示会在这些普通类之前初始化

注意上面的SmartLifecycle提供了一个run方法,这是在关闭过程结束后新建线程异步运行的。这个异步动作默认现实30s,可以自己配置。

六、非web环境关闭IOC容器

ApplicationContext是基于web环境的,在非web,比如桌面应用开发环境,可以注册一个JVM hook来实现关闭IOC

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
        // add a shutdown hook for the above context...
        ctx.registerShutdownHook();
        // app runs here...
        // main method exits, hook is called prior to the app shutting down...
    }
}

ApplicationContextAware和BeanNameAware

  1. ApplicationContextAware类持有一个ApplicationContext引用,在ApplicationContext启动后,可以用该引用获取相应的bean,但是不推荐这种方法,因为违背了IOC的规则

    spring2.5之后,也可以采用autowire这只ApplicationContext引用(构造函数注入和set注入,或者使用@AutoWire的方式注入到构造函数,类的字段,或者其他的方法中)

  2. BeanNameAware则接口提供了获取实现类在IOC中的id的能力,注意该接口的回调是在初始化方法之前完成

其他的Aware接口详见官方文档(使用这些Aware会和spring代码耦合)

Bean definition的继承

一个bean definition(就是xml中bean的定义)包含很多的配置信息,包括构造参数,属性值,以及容器管理的初始化方法,静态工厂方法名等。一个子的bean definition会继承父的配置信息,子的可以覆盖父的某些配置信息,或者增加其他的配置信息。例子如下:

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

但是下面定义会总是从子定义中获取:depends on, autowire mode, dependency check, singleton, lazy init

如果父定义没有指定实现类,则abstract属性必须有

容器扩展点

正常情况下不会用到这些知识,但是如果需要使用到spring的内部机制就需要这些类了

BeanPostProcessor

是一个回调接口,用来写bean的初始化逻辑,依赖解决逻辑等。可以实例化多个该类,并能设置优先级。该类的scope是整个容器。spring AOP中创建代理类用到了该接口。

案例:

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

配置方法很简单,都不需要写id

<bean class="scripting.InstantiationTracingBeanPostProcessor"/>

BeanFactoryPostProcessor

可以在初始化其他bean之前能够读取配置元数据(在bean初始化之前),并且能够更改这些数据,因此通过该接口自可以自定义配置元数据 。同样也可以实例化多个实现类并设置优先级。同样scope也是容器级别的。spring提供了一些该接口的实现类,比如:PropertyPlaceholderConfigurer(placeholder中文为占位符的意思)和PropertyOverrideConfigurer。下面是案例:动态修改连接池的相关信息

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        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>

${}占位符的内容来自于配置的jdbc.properties文件

另一种类似的配置方式,使用context命名空间:

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

该类另一种使用方法是在运行时动态替换class的名字

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

<bean id="serviceStrategy" class="${custom.strategy.class}"/>

另一个实现类PropertyOverrideConfigurer的用法:用来覆盖属性值,同样也是通过properties文件来配置,而且可以配置多层的属性

配置方式:

beanName.property=value

如:

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

同样,使用context来更方便的配置:

<context:property-override location="classpath:override.properties"/>

使用FactoryBean自定义初始化逻辑

自定义的FactoryBean的实现类是可以被IOC使用的(可插拔),该接口控制了bean的初始化逻辑。获取FactoryBean本体类的方法:getBean(“&myBean”),注意到前面多了个&符号,表示是获取FactoryBean的实现类,而不是从IOC容器获取某个类。

容器的注解方式的配置

注解方式比XML方式的配置更好吗 答案是it depends

注解的优点:配置方便 缺点:和代码耦合, 需要重新编译

xml的优缺点和上面相反

注意:

注解注入是在xml注入之前进行的,因此后者的相关配置会覆盖前者的配置

使用注解一般要配置BeanPostProcessor:

<?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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>

上面的配置注册了一系列post-processors 包括:

AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor,RequiredAnnotationBeanPostProcessor

@Required

表示属性必须注入,避免运行时报空指针

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

@Autowired

下面的案例可以使用@Inject注解来代替@Autowired

作用在构造方法上:

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

注意:

如果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 {

    @Autowired
    private MovieCatalog[] movieCatalogs;

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

如果希望容器中的元素是有顺序的,可以相关类上添加@Order或者@Priority注解

也可以注入到Map中,只要map的key是String就行,注入的时候会存bean的名字

public class MovieRecommender {

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

当没有匹配的类可以注入时,默认注入时失败的,当然可以通过下面的方式修改这一特性:

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

一个类里面只有一个构造方法可以被标记为required

使用required属性比@Required注解更好,@Required要求更强

也可以对Spring内部提供的一些类进行注解依赖:

public class MovieRecommender {
    @Autowired
    private ApplicationContext context;
}

@Autowired @Inject @Resource @Value是通过BeanPostProcessor来处理的,如果自定义了自己的实现类,这些注解就不会起作用,只能通过xml来注入

通过@Primary设置注入的优先级

通过byType的注入方式可能要面临多个候选类的情况,这时候可以@Primary来调节注入的优先级

@Configuration
public class MovieConfiguration {
    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }
    @Bean
    public MovieCatalog secondMovieCatalog() { ... }
}

firstMovieCatalog将会优先被注入,用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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/>
    <bean class="example.SimpleMovieCatalog" primary="true">
        <!-- inject any dependencies required by this bean -->
    </bean>
    <bean class="example.SimpleMovieCatalog">
        <!-- inject any dependencies required by this bean -->
    </bean>
    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

使用@Qualifier来注入

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的名字是默认的qualifier值,因此可以使用该注解实现byName的注入

注意使用qualifier只会收窄匹配范围,前提还是按类型匹配

qualifier可以不唯一,因此也可以注入到集合中去

如果目的是想按byName注入,Spring更推荐使用@Resource注解,@Resource可以实现类型不相干注入

@Autowired能作用在构造方法,方法参数以及属性,而@Resource只能作用在属性和setter方法上

一个类也可以自己注入自己,但是这个注入处理优先级最低

相当于:

<?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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://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 id="movieRecommender" class="example.MovieRecommender"/>

</beans>

也可以自定义自己的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;
    }
}
<?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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/>
    <bean class="example.SimpleMovieCatalog">
        <qualifier type="Genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>
    <bean class="example.SimpleMovieCatalog">
        <qualifier type="example.Genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>
    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

泛型注入

假设有两个实现了Store<泛型>接口的类

@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

泛型注入可以作用在Lsit, Map和Array上

// 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自定义注解

参考官方文档

@Resource

可以用来注入属性和setter方法,name指的是bean的name(id)

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

如果不指定名字,会默认使用属性的名字和setter方法中参数的名字,如果找不到名字,@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扫描以及注入

@Componen等其他注解注入

Spring提供@Component, @Service, @Controller,@Repository注解注入

@Component适用于任何注入的类,其他几个作用一样,只不过带有特殊的含义。

Spring提供的这些注解也可以用在元注解上,比如@Service注解的定义如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // Spring will see this and treat @Service in the same way as @Component
public @interface Service {

    // ....
}

不仅可以添加一个,也可以组合添加使用,比如Spring MVC的@Controller就是@Controller和@ResponseBody组成的

使用注解扫描自动注入相关类

使用@ComponentScan(可以使用逗号,分号和空格分隔,basePackages可以省略,这样会使用默认的value属性)

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

等同于:

<?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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

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

</beans>

注意,使用component-scan默认开启了annotation-config,所以没有必要手动再添加:\

同时,开启注解扫描也默认包含了AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor,因此可以识别这些注解

@ComponentScan中使用过滤器

有以下的过滤类型:

Filter Type Example Expression Description
annotation (default) org.example.SomeAnnotation An annotation to be present at the type level in target components.
assignable org.example.SomeClass A class (or interface) that the target components are assignable to (extend/implement).
aspectj org.example..*Service+ An AspectJ type expression to be matched by the target components.
regex org\.example\.Default.* A regex expression to be matched by the target components class names.
custom org.example.MyTypeFilter A custom implementation of the org.springframework.core.type .TypeFilter interface

使用:

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

可以在注解中使用useDefaultFilters=false和在xml中context:component-scan的属性中设置use-default-filters=”false”来屏蔽前面讲过的四个注解的注入作用(实际是取消默认过滤器的作用)

Component中提供bean的配置元数据

@Component类中可以使用@Bean来提供bean definition元数据,并供其他bean definition autowire(待确定)

@Qualifier起作用的前提是有@Bean,publicInstance方法提供了一个bean definition,bean的名字就是publicInstance(一般用在@Configuration注解的配置类中)

@Component
public class FactoryMethodComponent {

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

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

    private static int i;

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

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

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

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

InjectionPoint表示注入点(让该创建bean的方法运行的注入点)

@Component
public class FactoryMethodComponent {

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

@Bean在@Component和在@Configuration的区别:

在@Configuration中,@Bean作用的工厂方法返回的对象是经过CGLIB代理的,而@Component中的@Bean注解的工厂方法返回的是原生Java对象

参考博客:https://blog.csdn.net/ttjxtjx/article/details/49866011

注意:@Bean的方法可以使静态的,适用于定义PostProcessor,因为它们初始化的优先级高,但是这时返回的类不会被Spring代理,因为是静态的。

@Configuration中的@Bean的方法的位置可以随意放,但是方法不能是private和final的

注入类的名称

前面介绍的四个注解都有一个name属性,给注入的类提供名称,不注明name属性,则用首字母小写的方式

@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

自定义命名规则:实现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

默认是singleton的,也可以显示定义:

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

同时也可以自定义scope的resolve类:

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

对于web中的不同的scope可能会产生依赖,这时候要传递代理类,也可以指定代理的方式(不代理,接口方式,目标类方式)

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

使用注解提供qualifier元数据

前面也讨论过这些qualifier,只不过这里使用注解方式

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

加快classpath扫描:创建索引

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

使用JSR 330标准注解

这里简略带过,主要有以下几个注解:

@Inject @Name @Singleton @Provider

基于java代码的容器配置

使用@Bean和@Configuration

主要使用到@Bean(提供bean definition 和xml中的bean标签一样)和@Configuration(提供配置元数据)两个注解

@Configuration
public class AppConfig {

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

前面我们看到可以在@Component中使用@Bean注解, 这种@Bean不和@Configuration一起使用的方式,是一种简化模式

使用AnnotationConfigApplicationContext初始化容器

AnnotationConfigApplicationContext能够处理@Configuration、@Component以及JSR-330注解的类

使用:

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

任何@Component和JSR-330的注解类都可以作为输入

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

使用register显示注册配置类:

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) {
    //@Configuration含有@Component的作用,因此可以自动扫描
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}

web环境下的使用:

web环境要使用AnnotationConfigWebApplicationContext来配置ContextLoaderListener监听器,Spring的DispatcherServlet等,例如:

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

使用@Bean注解

@Configuration
public class AppConfig {
    //Bean的名字默认和方法名字一致,返回的类型也可以是接口类型
    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

使用构造方法注入:

@Configuration
public class AppConfig {

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

指定初始化和销毁的方法

public class Foo {

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

    public void cleanup() {
        // destruction logic
    }
}
@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public Foo foo() {
        //实际上如果不指定init方法,也可以手动在这处调用init方法达到同样的效果
        return new Foo();
    }
    @Bean(destroyMethod = "cleanup")
    public Bar bar() {
        return new Bar();
    }
}

采用java代码配置方式,默认会识别close和shutdown来执行初始化或销毁回调,如果类中有该方法,但是不想让他们作为回调方法,可以使用下面的方式屏蔽,(DataSource一般都要屏蔽):

@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
    return (DataSource) jndiTemplate.lookup("MyDS");
}

指定Scope

@Configuration
public class MyConfiguration {

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

Scope的代理:

// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
//上面的注解可以指定ScopeProxyMode
public UserPreferences userPreferences() {
    return new UserPreferences();
}

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

指定Bean的名字:

@Configuration
public class AppConfig {
    //指定多个别名
    @Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}

添加Bean的描述:

@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Foo foo() {
        return new Foo();
    }
}

使用@Configuration注解

该注解表示该类的作用是提供bean的定义的

注入bean的依赖(直接调用方法):

@Configuration
public class AppConfig {

    @Bean
    public Foo foo() {
        return new Foo(bar());
    }

    @Bean
    public Bar bar() {
        return new Bar();
    }
}

look-up注入:解决单例bean依赖原型bean的问题:

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();
}
@Bean
@Scope("prototype")//依赖的原型bean
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // inject dependencies here as required
    return command;
}

@Bean
//单例bean
public CommandManager commandManager() {
    // return new anonymous implementation of CommandManager with command() overridden
    // to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            //用来返回原型bean
            return asyncCommand();
        }
    }
}

@Bean的调用次数问题:

@Configuration
public class AppConfig {

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

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }
    //虽然上面注入了两次这个Bean,实际只new了一次,因为bean注解的类会被spring代理
    //从而可以拦截不必要的new动作(对于单例来说)
    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}

组合Config类:

使用import

一个配置类可以导入其他的配置类,Spring4.2之后也可以导入component类

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

这时候只需要显示的配置ConfigB的类就行了,因为B会依赖A:

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

实际使用中经常会遇到的案例:多个配置文件互相依赖

@Configuration
public class ServiceConfig {

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

    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        return new JdbcAccountRepository(dataSource);
    }
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

对BeanPostProcessor 或者BeanFactoryPostProcessor使用@Bean注解时,一般采用静态方法,这样不会过早初始化配置类,不然的话,使用@Autowired或者@Value作用在配置类上会失效

或者使用Autowire:

@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

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

@Configuration
public class RepositoryConfig {

    private final DataSource dataSource;

    @Autowired
    //这个autowire可以省略,因为只有一个构造方法
    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");
}

有时候并不知道使用Autowire的依赖类是在哪个配置类,这时候可以显示依赖配置类:

缺点:使配置文件耦合起来

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        // navigate 'through' the config class to the @Bean method!
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}

其他注解:

@Lazy表示懒加载

@DependsOn添加显示依赖

有选择性的添加@Configuration类和@Bean方法:

Profile注解(参见官方文档)

组合xml和java配置

在xml配置文件中加入config配置类:

@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

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

    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }
}
<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
    <!--当成一个类注入,不用指明id,因为没有其他类可以用-->
    <bean class="com.acme.AppConfig"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=

因为Configuration本身就使用了@Component,所以上面的xml配置文件又可以改为:

注意:component-scan已经开启了annotation-config,不用再写

<beans>
    <!-- picks up and registers AppConfig as a bean definition -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

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

在以java代码配置为主的配置中加入xml配置:使用@ImportResource

@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的主要涉及到两个方面:profiles和properties

profile用来表示bean definitions在逻辑上的区分,properties表示配置文件

Bean definition的profile

@Profile

案列:datasource的分环境配置

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

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

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

Profile注解也可以作用在注解上:

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

这样案例中的Profile注解可以用@Production代替

@Profile({“p1”,”p2”}):在profile p1和p2激活的时候才会生效

@Profile({“p1”,”!p2”}):p1激活,p2没激活的时候才会生效

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

xml中的写法:

<beans profile="development"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xsi:schemaLocation="...">

    <jdbc:embedded-database id="dataSource">
        <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"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>

或者:

<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 id="dataSource">
            <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 id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
    </beans>
</beans>

激活Profile

常见的方式:

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

可以激活多个:

ctx.getEnvironment().setActiveProfiles("profile1", "profile2");

在测试中可以使用:@ActiveProfiles

通过配置的方式:配置spring.profiles.active属性:

-Dspring.profiles.active="profile1,profile2"

默认Profile

在没有指定profile的情况下会默认开启的(默认profile的名字可以改)

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

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

PropertySource的抽象

Environment的另一个部分

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

Environment会从一系列PropertySource对象中寻找。Spring的StandardEnvironment配置了两个该类:一个表示JVM系统参数(System.getProperties()),另一个表示系统的环境变量(System.getenv())。系统属性会在环境变量之前被查找。除了StandardEnvironment还有StandardServletEnvironment,它里面包含了servlet的一些配置信息

添加自己的PropertySource

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

@PropertySource

可以通过@PropertySource方便的添加该类

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

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        //该属性包含在app.properties
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

@PropertySource中可以使用已有的property:

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

这里面假设my.placeholder已经在注册的property resource里面了,比如系统属性或者环境变量里面。如果没有将会使用default/path。

同时下面这个占位符不关心customer属性在哪定义了,只要在Environment中就能够被读到:

<beans>
    <import resource="com/bank/service/${customer}-config.xml"/>
</beans>

ApplicationContext的其他能力

ApplicationContext继承了BeanFactory接口,并提供了一下的能力;

通过MessageSource访问国际化信息

通过ResourceLoader接口访问资源

通过使用ApplicationEventPublisher向ApplicationListener的实现类发布事件

加载多个contexts

使用MessageSource国际化

当ApplicationContext加载后,会自动查找MessageSource的类。Spring提供了两个Message实现类:ResourceBundleMessageSource和StaticMessageSource。下面是案例:

list的每个value代表一个resource bundle

<beans>
    <bean id="messageSource"
            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!

使用:

public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", null);
    System.out.println(message);
}

另一个使用了占位符的案例:

<beans>

    <!-- this MessageSource is being used in a web application -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="exceptions"/>
    </bean>

    <!-- lets inject the above MessageSource into this POJO -->
    <bean id="example" class="com.foo.Example">
        <property name="messages" ref="messageSource"/>
    </bean>

</beans>
public class Example {

    private MessageSource messages;

    public void setMessages(MessageSource messages) {
        this.messages = messages;
    }

    public void execute() {
        String message = this.messages.getMessage("argument.required",
            new Object [] {"userDao"}, "Required", null);
        System.out.println(message);
    }
}
# in exceptions.properties
argument.required=The {0} argument is required.

MessageSource的实现类都带有JDK ResourceBundle的功能:

下面是按照约定定义的针对British的properties国际化文件:

# in exceptions_en_GB.properties
argument.required=Ebagum lad, the {0} argument is required, I say, required.

使用:

public static void main(final String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("argument.required",
        new Object [] {"userDao"}, "Required", Locale.UK);
    System.out.println(message);
}

web环境下ApplicationContext初始化

ContextLoaderListenerh会注册一个ApplicationContext,如果不指定contextConfigLocation的值,默认使用“/WEB-INF/applicationContext.xml”

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

猜你喜欢

转载自blog.csdn.net/guanhang89/article/details/79521304