0%

Spring IoC容器功能大全 4.0.0.RELEASE

《代码整洁之道》第十章:系统里面提到:将系统的构造与使用分开,您可以使用工厂,或者依赖注入(Dependency Injection,控制反转IoC的一种应用手段)来实现。IoC将第二权责从对象中拿出来,转移到另一个专注于此的对象中,从而遵循单一职责原则

在Spring中,控制反转IoC(Inversion of Control):由Spring IoC 容器来负责对象生命周期和对象之间的关系。

1、为什么需要控制反转?

DI**(依赖注入)**其实就是IOC的另外一种说法。控制的什么被反转了?就是:获得依赖对象的方式反转了。

最好以依赖项注入(DI)的方式编写大多数应用程序代码,这种代码是从Spring IoC容器中提供的,具有在创建时由容器提供的自己的依赖项,并且完全不需要了解容器本身。

把依赖项的构造与使用分开,从而达到构造与使用的解耦,可以更好的对依赖项进行配置,类似Spring的配置文件中进行简单配置即可替换掉依赖注入项的实现。否则,你只能修改源代码。

2、IoC容器基本使用方法:

2.1、元数据配置实现DI:

基于XML配置:

基本格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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>

基于注解配置:

注解注入在XML注入之前执行,因此对于通过两种方法注入属性,XML配置将覆盖注解配置。

2.2、实例化容器

1
2
ApplicationContext context =
new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});

2.3、使用容器

通过调用T getBean(String name, Class<T> requiredType)方法,你可以检索到bean的实例。

1
2
3
4
5
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

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

2.4、Bean配置概览

一个Bean配置会解析成BeanDefinition对象,该对象包含以下内容:

  • 包限定的类名:通常是所定义的Bean的实际实现类;
  • Bean行为配置元素,用于声明Bean在容器中的行为(作用域,生命周期回调等);
  • 引用其他bean完成其工作所需的bean;这些引用也称为协作者或依赖项;
  • 要在新创建的对象中设置的其他配置设置,例如,用于管理连接池的Bean中使用的连接数,或该池的大小限制。

具体配置方式参考:beans Table 4.1. The bean definition

2.5、IoC容器具体配置相关用法

2.5.1、基于构造函数的DI

1
2
3
4
5
6
7
8
9
package x.y;

public class Foo {

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

}
1
2
3
4
5
6
7
8
9
10
<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>

构造参数类型匹配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package examples;

public class ExampleBean {

// No. of years to the calculate the Ultimate Answer
private int years;

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

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

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

2.5.2、基于Setter的DI

通过<property/>指定setter的参数

1
2
3
4
5
6
7
8
9
10
11
<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"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
}

}

2.5.3、基于静态工厂的DI

静态工厂的参数通过<constructor-arg/>标签指定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
}

}
1
2
3
4
5
6
7
8
<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"/>

2.5.4、更多标签用法

1
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>

可以使用p-namespace命名空间简化

1
2
3
4
5
6
7
8
9
10
<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 ...
p:driverClassName="com.mysql.jdbc.Driver"
...
</beans>

2.5.5、Inner Beans

直接定义需要注入的Bean,而不是通过ref标签引用:

1
2
3
4
5
6
7
8
9
<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>

2.5.6、集合注入

<list/><set/><map/>,和<props/>元素分别对应Java集合的List、Set、Map以及Properties,使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<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>

2.5.7、Autowiring自动装配

您可以允许Spring通过检查ApplicationContext的内容来为您的bean自动解析协作者(其他bean)。

自动z装配模式

Mode Explanation
no (Default) No autowiring. Bean references must be defined via a ref element. Changing the default setting is not recommended for larger deployments, because specifying collaborators explicitly gives greater control and clarity. To some extent, it documents the structure of a system.
byName Autowiring by property name. Spring looks for a bean with the same name as the property that needs to be autowired. For example, if a bean definition is set to autowire by name, and it contains a master property (that is, it has a setMaster(..) method), Spring looks for a bean definition named master, and uses it to set the property.
byType Allows a property to be autowired if exactly one bean of the property type exists in the container. If more than one exists, a fatal exception is thrown, which indicates that you may not use byType autowiring for that bean. If there are no matching beans, nothing happens; the property is not set.
constructor Analogous to byType, but applies to constructor arguments. If there is not exactly one bean of the constructor argument type in the container, a fatal error is raised.

2.5.8、方法注入

假如A是单例的Bean,依赖于非单例的B,需要每次执行某个方法,都需要创建一个B的新的实例,如何配置呢?

这个时候可以使用方法注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package fiona.apple;

// no more Spring imports!

public abstract class CommandManager {

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

// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
1
2
3
4
5
6
7
8
9
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" 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="command"/>
</bean>

2.5.9、Bean作用域范围

Scope Description
singleton (Default) Scopes a single bean definition to a single object instance per Spring IoC container.
prototype Scopes a single bean definition to any number of object instances.
request Scopes a single bean definition to the lifecycle of a single HTTP request; that is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
session Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
global session Scopes a single bean definition to the lifecycle of a global HTTP Session. Typically only valid when used in a portlet context. Only valid in the context of a web-aware Spring ApplicationContext.
prototype

要使Spring容器释放由prototype作用域的bean占用的资源,请尝试使用自定义bean后处理器,该处理器包含对需要清理的bean的引用。

Singleton的beans引用prototype beans

如果在运行时每次都需要原型bean的新实例,请使用“方法注入”。

Request, session, and global session scopes

这几种作用域只能用于对web环境的ApplicationContext,如XmlWebApplicationContext。

如果你使用Servlet 2.4+的web容器,但不是使用Spring Web MVC(用到DispatcherServlet和DispatcherPortlet),那么,为了支持这几种作用域,您需要做一下初始化配置:

1
2
3
4
5
6
7
8
9
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>

如果你使用的是Servlet 2.3,你需要挺servlet Filter实现:

1
2
3
4
5
6
7
8
9
10
11
12
<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>
Scoped beans as dependencies

在request,session,globalSession和custom-scope级别的作用域需要使用<aop:scoped-proxy />元素。

1
2
3
4
5
6
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>

2.6、自定义bean的行为

为了与容器对bean生命周期的管理过程进行交互,您可以实现Spring InitializingBean和DisposableBean接口。 容器调用前者的afterPropertiesSet() 并调用后者的destroy(),以允许Bean在初始化和销毁Bean时执行某些自定义的操作。

2.6.1、生命周期回调

初始化回调

org.springframework.beans.factory.InitializingBean 接口提供了在bean必备的属性都设置完成之后,做一些其他初始化工作的回调方法:

1
void afterPropertiesSet() throws Exception;

不建议使用InitializingBean,这使得代码与Spring耦合。可以尝试使用 @PostConstruct 注解,或者提供一个具有init-method方法的POJO。

1
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
销毁回调

org.springframework.beans.factory.DisposableBean 接口提供了在bean销毁的时候做一些其他工作的方法:

1
void destroy() throws Exception;

不建议使用 DisposableBean,这使得代码与Spring耦合。可以尝试使用 @PreDestroy 注解,或者提供一个具有destroy-method方法的bean:

1
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
默认的初始化和销毁方法

您可以在beans标签中指定默认的初始化和销毁方法,所有bean都使用该名称的方法作为初始化和销毁方法。

1
2
3
4
5
6
7
<beans default-init-method="init">

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

</beans>

2.6.2、ApplicationContextAware 和 BeanNameAware

通过实现ApplicationContextAware方法,可以获取到ApplicationContext对象的引用。

1
2
3
4
5
public interface ApplicationContextAware {

void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

}

获取到ApplicationContext之后,您可以转换为已知的子类型如ConfigurableApplicationContext,以操作更多的方法。

最常用的是通过ApplicationContext检索bean,也可以方位Resources,发布application events或者访问MessageSource。但是应当尽量避免直接使用ApplicationContext,因为这导致代码和Spring耦合,不遵循IoC原则。

您也可以通过自动装配 @Autowired 来获取ApplicationContext。

实现org.springframework.beans.factory.BeanNameAware可以获取到定义的bean的名称:

1
2
3
4
5
public interface BeanNameAware {

void setBeanName(string name) throws BeansException;

}

该回调方法在bean的属性装配完成之后,初始化回调执行之前执行。

2.6.3、其他Aware接口

aware-list

注意:使用这些接口将会导致你的接口与Spring API绑定在一起,不遵循IoC原则。所以,应该用于底层的需要编程式访问容器的Beans。

2.7、Bean定义继承

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="inheritedTestBean" abstract="true"
class="org.springframework.beans.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>

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

以下配置只能在子Bean中配置,无法继承:depends on, autowire mode, dependency check, singleton, scope, lazy init.

2.8、容器扩展点

一般我们不会继承ApplicationContext去扩展,而是使用Spring IoC容器的插件式的方式去实现特定的集成接口。

2.8.1、使用BeanPostProcessor自定义beans

BeanPostProcessor接口提供了一个可自定义实现的回调。如果写了多个实现类,可以通过Ordered接口指定执行顺序。该回调在容器的初始化方法之前执行(如IntializingBean的afterPropertiesSet或者任何定义的初始化方法)。

Spring的一些底层AOP类也是通过post-processor织入代理逻辑。

ApplicationContext自动检测识别配置元数据中实现了BeanPostProcessor接口的bean,作为post-processor。当然,也可以通过使用ConfigurableBeanFactoryaddBeanPostProcessor方法编程式的去配置。通过编程式不支持Ordered接口,而是根据注册顺序来执行的,并且编程式注册的post-processor在通过元数据配置的之前执行。

  • BeanPostProcessors以及被其引用的bean与普通的bean不一样,在ApplicationContext容器启动阶段的时候就初始化了;
  • 所有的BeanPostProcessors按顺序注册到容器中,然后会应用到容器中后面所有加载的bean;
  • 因为AOP代理是通过BeanPostProcessor实现了,所以BeanPostProcessor以及被其引用的bean中不会自动织入代理。

下面这个里面在bean创建的时候,调用了其toString()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

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

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

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang
http://www.springframework.org/schema/lang/spring-lang.xsd">

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

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

</beans>

Spring的依赖注入注解就是通过RequiredAnnotationBeanPostProcessor这个BeanPostProcessor实现的。

2.8.2、使用BeanFactoryPostProcessor自定义配置元数据

org.springframework.beans.factory.config.BeanFactoryPostProcessor接口与BeanPostProcessor接口类似,不同点在于:BeanFactoryPostProcessor在Spring IoC容器读取配置元数据时对配置元数据进行自定义的处理。

注意:你的BeanFactoryPostProcessor必须实现Ordered接口才可以对属性进行修改。

Spring内置了一些bean factory post-processor,如PropertyOverrideConfigurerPropertyPlaceholderConfigurer

PropertyPlaceholderConfigurer允许从标准的Java Properties格式的文件读取bean的属性值。

1
2
3
4
5
6
7
8
9
10
11
<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>

以下是一个比较有用的例子:通过使用PropertyPlaceholderConfigurer,在运行时获取具体的实现类:

1
2
3
4
5
6
7
8
9
10
<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}"/>

2.8.3、通过使用FactoryBean自定义实例化逻辑

通过实现org.springframework.beans.factory.FactoryBean接口可以让bean变成工厂。这对于一些不能够通过xml来配置的实例化逻辑很有帮助,主要方法:

  • Object getObject(): 返回bean工厂的实例;
  • boolean isSingleton(): 返回bean是否为单例;
  • getObjectType(): 返回实例的类型。

可以通过getBean("&myBean")获取FactoryBean,使用getBean("myBean")获取FactoryBean的实例。

2.9、基于注解的容器配置

这是一种通过字节码元数据来配置容器的方法。

基于注解的注入会在基于XML的注入之前执行,所以可以通过XML配置覆盖注解配置。

通过下面的注解,可以隐性地注入这些注解的BeanPostProcessor:

1
<context:annotation-config/>

这个配置会自动注册一下的post-processor: AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor, 还有前面提到的 RequiredAnnotationBeanPostProcessor

2.9.1、@Required

这个注解应用于Bean属性的setter方法,来注入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
public class SimpleMovieLister {

private MovieFinder movieFinder;

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

// ...

}

2.9.2、@Autowired

可以通过@Autowired注解给setter方法注入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
public class SimpleMovieLister {

private MovieFinder movieFinder;

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

// ...

}

也可以应用于任意名称(非setXxx)多个参数的方法,或者构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MovieRecommender {

@Autowired
private MovieCatalog movieCatalog;

private CustomerPreferenceDao customerPreferenceDao;

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

// ...

}

也支持数组注入:

1
2
3
4
5
6
7
8
public class MovieRecommender {

@Autowired
private MovieCatalog[] movieCatalogs;

// ...

}

集合或者Map类型的注入。Map类型的key为bean的名称:

1
2
3
4
5
6
7
8
9
10
11
12
public class MovieRecommender {

private Map<String, MovieCatalog> movieCatalogs;

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

// ...

}

@Autowired注解也可以用于注入: BeanFactory, ApplicationContext, Environment, ResourceLoader, ApplicationEventPublisher, 和 MessageSource类型的bean,这些接口和他们的子类,无需做特殊的配置,会自动的通过类型查找进行装配。

1
2
3
4
5
6
7
8
9
10
11
public class MovieRecommender {

@Autowired
private ApplicationContext context;

public MovieRecommender() {
}

// ...

}

@Autowired, @Inject, @Resource以及@Value注解是通过BeanPostProcessor实现的,所以你不可以在你自己的BeanPostProcessor或者BeanFactoryPostProcessor中使用这些注解,而应该通过XML或者@Bean注解来配置。

2.9.3、使用@Qualifier指定需要注入的bean

当使用类型注入的时候,如果一个类型有多个bean,那么就需要使用@Qualifier注解了,该注解用于找到实际需要注入的bean。

可以在属性上面使用,也可以在勾走函数或方法的参数里面指定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}

// ...

}

对应配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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>
  • 如果你趋向于使用bean名称来配置基于注解驱动的注入,请尽量不要使用@Autowired,虽然可通过@Qualifier来指定bean的名称。你应该尝试使用@Resource,该注解只会通过bean的名称而不是类型来寻找依赖;
  • @Autowired应用于属性、构造函数以及多参函数,配合@Qualifier一起使用可以定位bean;而@Resource只支持属性、单参数的属性setter方法。所以如果你的配置目标是构造函数或者多参方法,请使用@Autowired和@Qualifier。
自定义Qualifier注解:
1
2
3
4
5
6
7
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {

String value();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MovieRecommender {

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

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

// ...

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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>

另外,你也可以不提供qualifier的value值,当成一个更加通用的bean来处理。或者添加更多的用于定位bean的字段:

1
2
3
4
5
6
7
8
9
10
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {

String genre();

Format format();

}
1
2
3
public enum Format {
VHS, DVD, BLURAY
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MovieRecommender {

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

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

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

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

// ...

}

详细例子:Fine-tuning annotation-based autowiring with qualifiers

2.9.4、使用泛型作为自动装配限定

如果bean是一个泛型类型,在注入的时候,会寻找到对应类型的bean:

1
2
3
4
// 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;

2.9.5、CustomAutowireConfigurer

CustomAutowireConfigurer是一个BeanFactoryPostProcessor,可以通过它注册你自己的限定注解类型,即使该注解不包含@Qualifier注解:

1
2
3
4
5
6
7
8
<bean id="customAutowireConfigurer"
class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifier</value>
</set>
</property>
</bean>

2.9.6、@Resource

@Resource包含一个name属性,该属性默认为bean名称。如果name属性未指定,那么默认会取属性的名称或者setter方法中的bean属性名称。

与@Autowired类似,在没有指定显式name的时候,@Resource通过类型匹配而不是特定名称的bean获取到这些可解决的依赖项:BeanFactory,ApplicationContext,ResourceLoader,ApplicationEventPublisher, 和MessageSource接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MovieRecommender {

// 先查找customerPreferenceDao名称的bean,找不到,则查找CustomerPreferenceDao类型的bean
@Resource
private CustomerPreferenceDao customerPreferenceDao;

// 基于ApplicationContext类型进行查找
@Resource
private ApplicationContext context;

public MovieRecommender() {
}

// ...

}

2.9.7、@PostConstruct和PreDestroy

CommonAnnotationBeanPostProcessor除了处理@Resource注解,也处理JSR-250生命周期注解。

假设CommonAnnotationBeanPostProcessor已在Spring ApplicationContext中注册,则在生命周期中与相应的Spring生命周期接口方法或者显式声明的回调方法会被调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class CachingMovieLister {

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

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

}

2.10、类路径扫描与托管组件

通过扫描类路径可以发现bean组件。这节主要是提供一种不用XML文件配置bean的方法。主要通过注解、AspectJ 类型表达式,或者自定义的拦截规则,来判断哪些类被定义为Bean组件并且需要注入到容器中。

从Spring 3.0依赖,Spring JavaConfig项目作为Spring框架核心部分,提供了很多新的特性,这写特性允许你通过Java定义bean,而不是XML。可以看看相关的注解:@Configuration,@Bean,@Import以及@DependsOn。

2.10.1、@Component

@Component是Spring的一个泛化的组件管理注解,可以作用在任何层次的类中。同时针对不同的场景,延伸出了以下的注解:

  • @Repository:用于将数据访问层的类标识为Spring Bean,该注解能将锁标注的类中抛出的数据访问异常封装为Spring的数据方位异常类型。Spring本身提供了一个丰富并且与持久层框架技术无关的数据访问异常结构,用于封装持久层框架抛出的异常,使得异常独立于底层框架;
  • @Service:作用在业务层。目前该注解与@Component相同;
  • @Controller:作用在控制层,目前该注解与@Componet相同。

通过在类上使用这些注解,Spring会自动创建相应的BeanDefinition对象,并且注册到ApplicationContext中,成为托管于Spring的组件。

2.10.2、元注解

元注解的意思就是用这个注解标注于其他注解类上面。Spring中内置的很多注解都可以作为元注解使用。

2.10.3、自动检测类以及注册Bean Definitions

为了能够自动检测并注册Bean Definitions,需要在Spring配置文件中添加注解:

1
2
3
4
5
6
7
8
9
10
11
12
<?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>
  • context:component-scan标签会自动引入context:annotation-config标签;同时会自动引入AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor。
  • 扫描的包需要在classpath中。

2.10.4、使用过滤器自定义扫描规则

默认的情况下,自动扫描注册的组件包括通过使用 @Component, @Repository, @Service, @Controller以及自定义注解(使用到了@Component元注解)这些注解的类,但是,你可以通过include-filter或者exclude-filter来自定义过滤规则:

1
2
3
4
5
6
7
8
<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>

2.10.5、通过组件定义bean元数据

Spring组件里面也可以给定义bean的元数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Component
public class FactoryMethodComponent {

private static int i;

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

// use of a custom qualifier and autowiring of method parameters

@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(tb);
tb.setCountry(country);
return tb;
}

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

@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}

}

在@Component中使用@Bean注解和在Configuration中是不同的。使用了@Component注解的类使用方法或者字段时不会使用CGLIB增强,所以使用了@Bean注解的方法和字段则是普通的Java语义,不会经过CGLIB处理。而在使用了@Configuration注解的类中使用方法或字段时则使用CGLIB创建代理对象,所以当调用@Bean注解的方法的时候,会从容器中拿到被Spring代理或依赖于其他Bean的对象引用。

在ConfigurationClassPostProcessor中的postProcessBeanDefinitionRegistry方法中,会检查所有的bean,如果bean是被@Configuration、@Component、@ComponentScan、@Import、@ImportResource其中一个标注的,那么此类就会被视为Configuration类。在postProcessBeanDefinition方法中,会把@Configuration类动态代理为一个新类,使用CGLIB的enhancer来增强Configuration类。

@Component和@Configuration作为配置类的差别

2.10.6、命名自动检测组件

在组件扫描的过程中,bean的名称通过BeanNameGenerator识别。默认的,任何Spring的stereotype注解( @Component, @Repository, @Service, 和 @Controller)都会提供一个name属性用于设置bean的名称。缺省BeannameGenerator会返回首字母小写的非限定类目。

你可以通过实现BeanNameGenerator接口自定义一个bean-naming策略,然后配置到扫描器中:

1
2
3
4
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>

2.10.7、为自动检测组件提供作用域

默认d 情况下,Spring托管的组件会使用singleton作用域。如果你需要自己制定作用域,可以使用@Scope注解:

1
2
3
4
5
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}

如果要提供自定义的作用域解析策略,请实现ScopeMetadataResolver。然后配置到扫描器中:

1
2
3
4
<beans>
<context:component-scan base-package="org.example"
scope-resolver="org.example.MyScopeResolver" />
</beans>

使用非singleton作用域时,可能有必要为作用域对象生成代理。 为此,在component-scan元素上可以使用scoped-proxy属性。 三个可能的值是:no,interfaces和targetClass。原因参考: the section called “Scoped beans as dependencies”

2.10.8、通过注解提供qualifier元数据

基于XML格式的qualifier配置参考: Section 4.9.3, “Fine-tuning annotation-based autowiring with qualifiers”

1
2
3
4
5
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
1
2
3
4
5
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
1
2
3
4
5
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}

2.11、使用JSR 330标准注解

从Spring 2.0开始,支持JSR-330标准依赖注解。为了这用这些注解,引入javax.inject依赖即可:

1
2
3
4
5
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>

2.11.1、@Inject和Named依赖注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import javax.inject.Inject;
import javax.inject.Named;

public class SimpleMovieLister {

private MovieFinder movieFinder;

@Inject
public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}

// ...

}

2.11.2、@Named:与@Component注解等效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import javax.inject.Inject;
import javax.inject.Named;

@Named("movieListener")
public class SimpleMovieLister {

private MovieFinder movieFinder;

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

// ...

}

同时,需要配置扫描标签:

1
2
3
<beans>
<context:component-scan base-package="org.example"/>
</beans>

2.11.3、标准注解的局限性

Spring javax.inject.* javax.inject restrictions / comments
@Autowired @Inject @Inject has no required attribute
@Component @Named -
@Scope("singleton") @Singleton The JSR-330 default scope is like Spring’s prototype. However, in order to keep it consistent with Spring’s general defaults, a JSR-330 bean declared in the Spring container is a singleton by default. In order to use a scope other than singleton, you should use Spring’s @Scope annotation.javax.inject also provides a @Scope annotation. Nevertheless, this one is only intended to be used for creating your own annotations.
@Qualifier @Named -
@Value - no equivalent
@Required - no equivalent
@Lazy - no equivalent

2.12、基于Java的容器配置

2.12.1、基本注解:@Bean @Configuration

Spring基于Java的配置新特性主要是通过@Bean和@Configuration注解来实现的。

@Bean注解用于指定一个方法来实例化,配置以及初始化一个新的Spring IoC容器托管对象。这与标签类似。你可以配合任何Spring @Component注解一起使用@Bean,最常用的是与@Configuration注解的bean配合使用。

用@Configuration注释类表示该类的主要目的是作为Bean定义的来源。此外,@ Configuration类允许通过简单地调用同一类中的其他@Bean方法来定义Bean之间的依赖关系。

1
2
3
4
5
6
7
8
9
@Configuration
public class AppConfig {

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

}

等同于:

1
2
3
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

2.12.2、使用AnnotationConfigApplicationContext初始化Spring容器

在传统的XML配置方式中,可以使用ClassPathXmlApplicationContext类来加载外部XML上下文文件。而使用基于Java的配置时,需要用到Spring 3.0中的新增功能-Spring的AnnotationConfigApplicationContext类。这种通用的ApplicationContext实现不仅能够注册@Configuration类,而且还可以注册普通的@Component类和带有JSR-330元数据注释的类。

当提供@Configuration类作为输入时,@ Configuration类本身将注册为Bean definition,并且该类中所有已声明的@Bean方法也将注册为Bean定义。

当提供@Component和JSR-330的类作为输入时,它们将注册为bean definition,这些类中可以使用诸如@Autowired或@Inject之类的作为DI元数据配置。

例子:

实例化y一个ClassPathXmlApplicationContext的时候,需要提供Spring XML文件,而实例化一个AnnotationConfigApplicationContext的时候,需要提供一个@Configuration注解的配置类(或者任何@Component或者JSR-330注解类),从而完全不用XML来配置容器:

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

编程式地使用register(Class<?>…)构建容器

1
2
3
4
5
6
7
8
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();
}

使用scan(String…)启用组件扫描

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

让AnnotationConfigWebApplicationContext支持web应用

使用子类:AnnotationConfigWebApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<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>

2.12.3、使用@Bean注解

1
2
3
4
5
6
7
8
9
@Configuration
public class AppConfig {

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

}
接收生命周期回调

在被@Bean定义的类中,你可以使用JSR-250的@PostConstruct和@PreDestroy注解接收生命周期回调;

你也可以通过实现InitializingBean、DisposableBean或者Lifecycle接口来接收生命周期回调;

*Aware接口如BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContextAware接口也支持回调。

另外,你也可以使用以下方式让bean支持回调,这有点类似XML配置中的init-method和destroy-method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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() {
return new Foo();
}

@Bean(destroyMethod = "cleanup")
public Bar bar() {
return new Bar();
}

}

上面i的初始化方法配置与下面的代码等效:

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class AppConfig {
@Bean
public Foo foo() {
Foo foo = new Foo();
foo.init();
return foo;
}

// ...

}
指定Bean的作用域
使用@Scope注解
1
2
3
4
5
6
7
8
9
10
@Configuration
public class MyConfiguration {

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

}
@Scope和scoped-proxy

类似于<aop:scoped-proxy/>注解,你可 以给 @Scope注解指定代理模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
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命名
1
2
3
4
5
6
7
8
9
@Configuration
public class AppConfig {

@Bean(name = "myFoo")
public Foo foo() {
return new Foo();
}

}
bean别名
1
2
3
4
5
6
7
8
9
@Configuration
public class AppConfig {

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

}
bean描述

当把bean导出来监控的时候(如通过JMX),这个描述信息显得很重要。

1
2
3
4
5
6
7
8
9
10
Configuration
public class AppConfig {

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

}

2.12.4、使用@Configuration注解

这是类级别的注解,表明该类是一个bean definition。@Configuration类通过@Bean注解方法定义Bean。

内部bean注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class AppConfig {

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

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

}

注入,这种 配置方法在@Component类中无效

查找方法注入

如前面讲到的,针对在一个singleton的bean中依赖一个prototypebean这种场景,查找方法注入非常有用,代码可能这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
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();
}

使用基于Java的配置,你可以创建一个CommandManager子类,让每次都返回一个新的 AsyncCommand对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
AsyncCommand command = new AsyncCommand();
// inject dependencies here as required
return command;
}

@Bean
public CommandManager commandManager() {
// return new anonymous implementation of CommandManager with command() overridden
// to return a new prototype Command object
return new CommandManager() {
protected Command createCommand() {
return asyncCommand();
}
}
}
更多关于基于Java的配置其内部是如何工作的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration
public class AppConfig {

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

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

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

}

在上面d 例子中,clientService1和clientService2都是引用了同一个clientDao。所有@Configuration类在启动的时候都通过CGLIB创建了一个子类,在子类中,字方法会先检查容器中是否有缓存bean,如果没有然后在调用父类去创建一个新的实例。

2.12.5、组织基于Java的配置

使用@Import注解

类似于xml中的<import/>标签,@Import注解可以从另一个配置类中加载bean definition。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@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();
}

}
注入通过import引入的bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Configuration
public class ServiceConfig {

@Autowired
private AccountRepository accountRepository;

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

}

@Configuration
public class RepositoryConfig {

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

这种用法,你很难找到这些依赖是从哪个Configuration类引入的,为此,你可以直接通过Configuration类来引入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Configuration
public class ServiceConfig {

@Autowired
private RepositoryConfig repositoryConfig;

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

@Configuration
public interface RepositoryConfig {

@Bean
AccountRepository accountRepository();

}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {

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

}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config!
public class SystemTestConfig {

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

}

public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
有条件的引入@Configuration类或者@Beans

有时候我们想要某个@Configuration类或者@Beans不生效,如根据不同的环境启用同的@Configuration配置。@Profile正是这样的注解。

@Profile注解使用灵活的@Conditional实现,@Conditional注解作用是让@Bean在注册之前,判断是否符合特定的条件(org.springframework.context.annotation.Condition的实现)。

下面是一个Condition接口的实现类的部分代码,核心是这个matches方法,该实现类可以用于@Profile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
// Read the @Profile annotation attributes
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
}
return true;
}
合并Java和XML配置

Spring的@Configuration配置类并不能完全的取代XML配置。诸如Spring XML名称空间之类的某些功能仍然是配置容器的理想方法,你可以选择:

  • 基于XML如ClassPathXmlApplicationContext实例化容器;
  • 基于Java的AnnotationConfigApplicationContext实例化容器,并通过@ImportResource注解导入需要的XML。
基于XML的@Configuration配置类

记住:@Configuration配置类实际是容器里面的一个Bean definition。下面是一个具体的基于XML的@Configuration配置类的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public class AppConfig {

@Autowired
private DataSource dataSource;

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

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

}
1
2
3
4
5
6
7
8
9
10
11
12
13
<beans>
<!-- enable processing of annotations such as @Autowired and @Configuration -->
<context:annotation-config/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

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

<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
1
2
3
4
5
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}

因为@Configuration带有元注解@Component,@Configuration注解的类也可以用于组件扫描:

1
2
3
4
5
6
7
8
9
10
11
<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>

<context:component-scan/>会引入<context:annotation-config/>标签的功能。

基于@Configuration配置类导入XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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);
}

}
1
2
3
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
1
2
3
4
5
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}

2.13、Bean definition profiles和环境抽象

Bean definition profiles允许不同的环境注册不同的beans。更多资料: Environment, XML Profiles@Profile annotation

2.14、PropertySource抽象

Spring的环境抽象提供了可配置属性源层次结构上的搜索操作。更多资料: Unified Property Management, PropertySource class 以及 @PropertySource annotation

2.15、注册一个LoadTimeWeaver

LoadTimeWeaver用于在类加载到JVM的时候进行动态的转换。

启用load-time编织:

1
2
3
4
5
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {

}

XML中的同等配置:

1
2
3
<beans>
<context:load-time-weaver/>
</beans>

一旦为ApplicationContext配置了这个,该ApplicationContext中的任何bean都将实现LoadTimeWeaverAware接口,从而获得load-time实例的引用。这对于Spring的JPA支持来说非常有用,JPA类转换会用到load-time编织。查阅 LocalContainerEntityManagerFactoryBean Java文档获取更多内容。更多关于AspectJ load-time 编织的内容:Section 8.8.4, “Load-time weaving with AspectJ in the Spring Framework”

2.16、更多关于ApplicationContext招式

org.springframework.beans.factory包提供了管理和操纵beans的功能更。

org.springframework.context包添加了ApplicationContext接口,继承BeanFactory接口,另外继承了其他的接口,以面向框架的形式提供了更多的功能。

context包也提供给了一下的功能来增强BeanFactory的框架能力:

  • 通过MessageSource接口访问i18n格式的消息;
  • 通过ResourceLoader接口访问类似URL和文件这样的资源;
  • 通过ApplicationEventPublisher接口在ApplicationContext中发布事件;
  • 加载多个(分层)上下文,从而允许每个上下文通过HierarchicalBeanFactory接口聚焦在一个特定层上,例如应用程序的Web层;

2.16.1、使用MessageSource国际化

2.16.2、标准和自定义事件

你也可以创建自定义事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BlackListEvent extends ApplicationEvent {

private final String address;
private final String test;

public BlackListEvent(Object source, String address, String test) {
super(source);
this.address = address;
this.test = test;
}

// accessor and other methods...

}

使用ApplicationEventPublisher的publishEvent()方法发布自定义事件。这通常可以通过实现ApplicationEventPublisherAware接口让其注册为bean来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class EmailService implements ApplicationEventPublisherAware {

private List<String> blackList;
private ApplicationEventPublisher publisher;

public void setBlackList(List<String> blackList) {
this.blackList = blackList;
}

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

public void sendEmail(String address, String text) {
if (blackList.contains(address)) {
BlackListEvent event = new BlackListEvent(this, address, text);
publisher.publishEvent(event);
return;
}
// send email...
}

}

在配置时期,Spring容器将对解析到实现了ApplicationEventPublisherAware的bean,并且自动调用setApplicationEventPublisher()方法。实际上,传入的参数将是Spring容器本身。您只需通过其ApplicationEventPublisher接口与应用程序上下文进行交互。

要接收自定义的的ApplicationEvent,请创建一个实现ApplicationListener的类,并将其注册为Spring Bean:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

private String notificationAddress;

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

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

}
1
2
3
4
5
6
7
8
9
10
11
12
13
<bean id="emailService" class="example.EmailService">
<property name="blackList">
<list>
<value>known.spammer@example.org</value>
<value>known.hacker@example.org</value>
<value>john.doe@example.org</value>
</list>
</property>
</bean>

<bean id="blackListNotifier" class="example.BlackListNotifier">
<property name="notificationAddress" value="blacklist@example.org"/>
</bean>

请注意,ApplicationListener通常使用自定义事件BlackListEvent的类型进行参数化。 这意味着onApplicationEvent()方法可以保持类型安全,从而避免了任何向下转换的需求。 您可以根据需要注册任意数量的事件侦听器,但是请注意,默认情况下,事件侦听器会同步接收事件。 这意味着publishEvent()方法将阻塞,直到所有侦听器都已完成对事件的处理为止。 这种同步和单线程方法的一个优点是,当侦听器接收到事件时,如果有可用的事务上下文,它将在发布者的事务上下文内部进行操作。 如果有必要采用其他发布事件的策略,请参阅 Spring的ApplicationEventMulticaster接口的JavaDoc。

Spring的事件机制旨在在同一应用程序上下文内在Spring bean之间进行简单的通信。 但是,对于更复杂的企业集成需求,单独维护的Spring Integration项目为基于著名的Spring编程模型构建轻量级,面向模式,事件驱动的架构提供了完整的支持。

2.16.3、便利的访问低级别的资源

为了掌握最佳用法和理解ApplicationContext,用户通常应该熟悉Spring的Resource抽象,如第5章“资源”中所述。

应用程序上下文是一个ResourceLoader,可用于加载Resource。 Resource本质上是JDK java.net.URL类的功能更丰富的版本,实际上,Resource的实现在适当的地方包装了java.net.URL的实例。 Resource可以以透明的方式从几乎任何位置获取低级别的资源,包括从类路径,文件系统位置,可使用标准URL描述的任何位置以及一些其他变体。 如果资源位置字符串是没有任何特殊前缀的简单路径,则这些资源的来源是特定的,并且适合于实际的应用程序上下文类型。

您可以配置部署到应用程序上下文中的bean,以实现特殊的回调接口ResourceLoaderAware,以便在初始化时自动调用,并将应用程序上下文本身作为ResourceLoader传入。 您还可以公开Resource类型的属性,以用于访问静态资源。 它们将像其他任何属性一样注入其中。 您可以将那些Resource属性指定为简单的String路径,并依赖于上下文自动注册的特殊JavaBean PropertyEditor,以在部署Bean时将这些文本字符串转换为实际的Resource对象。

提供给ApplicationContext构造函数的一个或多个位置路径实际上是资源字符串,对于特定的上下文实现,将以简单的形式对其进行适当处理。 ClassPathXmlApplicationContext将简单的位置路径视为类路径位置。不管实际的上下文类型如何,您也可以使用带有特殊前缀的位置路径(资源字符串)来强制从类路径或URL中加载定义。

2.16.4、web应用程序的便捷实例化ApplicationContext

2.16.5、将Spring ApplicationContext部署为J2EE RAR文件

2.17、BeanFactory

2.17.1、BeanFactory还是ApplicationContext

优先使用ApplicationContext,除非你有其他原因不这样做。

2.17.2、胶水代码和邪恶的单例

最好以依赖项注入(DI)的方式编写大多数应用程序代码,这种代码是从Spring IoC容器中提供的,具有在创建时由容器提供的自己的依赖项,并且完全不不用了解容器本身。

References

The IoC container

The IoC Container 5.2.0.RELEASE

@repository注解

透过现象看原理:详解Spring中Bean的this调用导致AOP失效的原因

欢迎关注我的其它发布渠道