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;
}
}
静态和实例工厂配置(和构造函数类似):
注意:
- 静态工厂返回的bean可以并不是该静态工厂方法所在的类,静态工厂配置的id是工厂生成的类的ID,class是静态工厂所在的class
- 非静态的实例工厂方法的使用方法类似,只不过把静态工厂中配置的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">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>
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">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
最后的结果:
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="foo@bar.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="foo@bar.com"/>
</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="foo@bar.com"/>
</bean>
<!-- c-namespace declaration -->
<bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>
</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)范围
- 原型类似于原型模式,通过一个原型能够复制出多个实例
- 一个prototype适用于状态变化的bean,而singleton适用于无状态的bean。如果一个bean是prototype,每一次使用(注入)都是一个新的bean
- 需要注意的时候,容器不会完全管理多例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
ApplicationContextAware类持有一个ApplicationContext引用,在ApplicationContext启动后,可以用该引用获取相应的bean,但是不推荐这种方法,因为违背了IOC的规则
spring2.5之后,也可以采用autowire这只ApplicationContext引用(构造函数注入和set注入,或者使用@AutoWire的方式注入到构造函数,类的字段,或者其他的方法中)
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>