1调用静态工厂方法创建Bean
问题:
你打算调用一个静态工厂方法在Spring IoC容器中创建一个Bean,静态工厂方法的目的是在静态方法中封装对象创建过程。
解决方案:
Spring支持调用一个静态工厂方法创建Bean,这个方法应该在factory-method属性中指定。
工作原理:
factory-method
public class ProductCreator {
public static Product createProduct(int productId){
if(1 == productId){
return new Product("xiaomei", 16);
}else if(2 == productId){
return new Product("xiaolang", 15);
}
throw new IllegalArgumentException("Unknown product");
}
}
<bean id="productCreator1" class="com.partner4java.spring.factorymethod.ProductCreator"
factory-method="createProduct">
<constructor-arg value="1" />
</bean>
<bean id="productCreator2" class="com.partner4java.spring.factorymethod.ProductCreator"
factory-method="createProduct">
<constructor-arg value="2" />
</bean>
@Test
public void testFactoryMethod(){
System.out.println(applicationContext.getBean("productCreator1"));
System.out.println(applicationContext.getBean("productCreator2"));
}
2调用一个实例工厂方法创建Bean
问题:
你打算调用一个实例工厂方法在Spring IoC容器中创建一个Bean,目的是在另一个对象实例的一个方法中封装对象创建过程。
请求对象的客户可以简单地调用这个方法,不需要了解创建的细节。
解决方案:
Spring支持调用实例工厂方法创建Bean。Bean实例在factory-bean属性中指定,而工厂方法应该在factory-method属性中指定。
工作原理:
factory-bean
package com.partner4java.spring.factorybean;
import java.util.Map;
import com.partner4java.spring.factorymethod.Product;
public class ProductCreator {
private Map<String, Product> products;
public void setProducts(Map<String, Product> products) {
this.products = products;
}
public Product createProduct(String productId){
Product product = products.get(productId);
if(product != null){
return product;
}
throw new IllegalArgumentException("Unknown product");
}
}
<bean id="productCreator" class="com.partner4java.spring.factorybean.ProductCreator">
<property name="products">
<map>
<entry key="gaofumei">
<bean class="com.partner4java.spring.factorymethod.Product">
<constructor-arg value="gaofumei" name="name"/>
<constructor-arg value="100" name="price"/>
</bean>
</entry>
<entry key="xiaoneinv">
<bean class="com.partner4java.spring.factorymethod.Product">
<constructor-arg value="xiaoneinv" name="name"/>
<constructor-arg value="200" name="price"/>
</bean>
</entry>
</map>
</property>
</bean>
<bean id="gaofumei" factory-bean="productCreator" factory-method="createProduct">
<constructor-arg value="gaofumei"/>
</bean>
<bean id="xiaoneinv" factory-bean="productCreator" factory-method="createProduct">
<constructor-arg value="xiaoneinv"/>
</bean>
@Test
public void testFactoryMethod(){
System.out.println(applicationContext.getBean("gaofumei"));
System.out.println(applicationContext.getBean("xiaoneinv"));
}
3从静态字段中声明bean
问题:
你打算从一个静态字段中声明Spring IoC容器中的一个Bean。在Java中,常量值往往声明为静态字段。
解决方案:
为了从静态字段中声明Bean,你可以使用内建的工厂Bean FieldRetrievingFactoryBean,或者Spring 2.X中的<util:contant>标记。
工作原理:
public class ProductConstant {
public static Product gaofumei = new Product("gaofumei", 100);
public static Product xiaoneinv = new Product("xiaoneinv", 200);
}
<util:constant id="gaofumei"
static-field="com.partner4java.spring.constant.ProductConstant.gaofumei" />
<util:constant id="xiaoneinv"
static-field="com.partner4java.spring.constant.ProductConstant.xiaoneinv" />
@Test
public void testFactoryMethod(){
System.out.println(applicationContext.getBean("gaofumei"));
System.out.println(applicationContext.getBean("xiaoneinv"));
}
4从对象属性中声明bean
问题:
你打算从一个对象属性或者嵌套的属性(也就是属性路径)中声明Spring IoC容器中的一个Bean。
解决方案:
为了从一种对象属性或者属性路径中声明Bean,可以使用内建的工厂Bean PropertyPathFactoryBean或者Spring 2.X中的<util:property-path>标记。
工作原理:
public class ProductProperty {
private Product gaofumei;
private Product xiaonennv;
public Product getGaofumei() {
return gaofumei;
}
public void setGaofumei(Product gaofumei) {
this.gaofumei = gaofumei;
}
public Product getXiaonennv() {
return xiaonennv;
}
public void setXiaonennv(Product xiaonennv) {
this.xiaonennv = xiaonennv;
}
}
<bean id="productProperty" class="com.partner4java.spring.property.ProductProperty">
<property name="gaofumei">
<bean class="com.partner4java.spring.factorymethod.Product">
<constructor-arg name="name" value="gaofumei"/>
<constructor-arg name="price" value="100.1"/>
</bean>
</property>
<property name="xiaonennv">
<bean class="com.partner4java.spring.factorymethod.Product">
<constructor-arg name="name" value="xiaonennv"/>
<constructor-arg name="price" value="200.2"/>
</bean>
</property>
</bean>
<util:property-path id="gaofumei" path="productProperty.gaofumei"/>
<util:property-path id="xiaonennv" path="productProperty.xiaonennv"/>
5使用Spring表达式语言
问题:
你希望动态的评估一些条件或者属性,并且将其作为IoC容器中的配置值使用。
你也可能因为自定义范围的情况,必须将某些估值从设计时延迟到运行时。
或者你只是需要一种行为自己的应用添加强大的表达式语言。
解决方案:
使用Spring 3.0的Spring表达式语言(SpEL),这种语言提供了与JSF和JSP中的Unified EL或者对象图形导航语言(OGNL)相似的功能。
SpEL提供了易用的基础架构,可以在Spring容器之外使用。
工作原理:
6设置Bean作用域
问题:
当你在配置文件中声明Bean时,实际上定义了Bean创建的一个模板,而不是实际的Bean实例。
当getBean()方法或者其他的Bean的一个引用请求Bean时,Spring将根据Bean作用域(Scope)确定应该返回的Bean实例。
有时候,你必须为Bean设置正确的作用域而不是默认的作用域。( By default, a bean will be a singleton)
解决方案:
作用域 描述
singleton
在每个Spring IoC容器中一个bean定义对应一个对象实例。
prototype
一个bean定义对应多个对象实例。
request
在一次HTTP请求中,一个bean定义对应一个实例;即每次HTTP请求将会有各自的bean实例, 它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。
session
在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
global session
在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于web的Spring ApplicationContext情形下有效。
7自定义Bean初始化和析构
问题 :
许多现实世界中的组件在使用之前必须进行某种初始化任务。
在组件的声明周期结束时,也必须要执行相应的任务。
解决方案:
除了注册Bean之外,Spring IoC容器还负责管理Bean的声明周期,允许你在他们的生命期特定时点执行自定义任务。
你的任务应该封装在回调方法中,由Spring IoC容器在核实的时候调用。
Spring IoC容器管理Bean周期的步骤:
1、构造程序或者工厂方法创建Bean实例。
2、向Bean属性设置值和Bean引用。
3、调用初始化回调方法。
4、Bean就绪。
5、容器关闭时,调用析构回调方法。
工作原理:
方法一:实现约定接口
public class Work implements InitializingBean,DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("离职");
}
public void vork(){
System.out.println("working");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("入职");
}
}
<bean id="work" class="com.partner4java.spring.initdes.Work"
scope="prototype" />
方法二:配置文件声明
public class Work1 {
public void destroy() throws Exception {
System.out.println("离职");
}
public void vork(){
System.out.println("working");
}
public void afterPropertiesSet() throws Exception {
System.out.println("入职");
}
}
<bean id="work1" class="com.partner4java.spring.initdes.Work1"
init-method="afterPropertiesSet" destroy-method="destroy" scope="prototype" />
方法三:注解声明
public class Work2 {
@PreDestroy
public void destroy() throws Exception {
System.out.println("离职");
}
public void vork(){
System.out.println("working");
}
@PostConstruct
public void afterPropertiesSet() throws Exception {
System.out.println("入职");
}
}
<context:annotation-config/>
<bean id="work2" class="com.partner4java.spring.initdes.Work2"
scope="prototype" />
8用Java Config简化XML配置
问题:
你欣赏DI容器的能力,但是希望覆盖一些配置,或者只是希望将更多的配置从XML格式中转移到Java中,可以更好的从重构和类型安全性中获益。
解决方案:
你可以使用Java Config。
工作原理:
Java Config支持强大的,代表了与其他通过XML或者注解的配置选项完全不同的工作方式。
重要的是,Java Config可以与现有方式混合使用。
启用Java配置的最简单方法是使用简单的XML配置文件。
@Configuration标记类:Spring将在类中寻找@Bean定义,指定这个标记@Bean的方法为一个Bean。(也就是指定某个方法返回为一个Bean)
@Lazy:将Bean的构造推迟到必须满足依赖或者应用上下文中显示的访问时。
@DependsOn:指定一个Bean的创建必须在其他Bean创建之后。
@Primary:指定相同接口的多个Bean。
@Import、@Value...
9使Bean感知容器
问题:
一个精心设计的组件应该没有对容器的直接依赖。
但是,有时候Bean有必要了解容器的资源。
解决方案:
Spring将通过一些接口定义的设置方法将对应资源注入到你的Bean中。
Spring中的常见感知接口
感知接口 目标资源
BeanNameAware IoC容器中配置的实例的Bean名称
BeanFactoryAware 当前的Bean工厂,通过它你可以调用容器的服务。
ApplicationContextAware* 当前应用上下文,通过他你可以调用容器的服务。
MessageSourceAware 消息资源,通过他可以解析文本消息。
ApplicationEventPublisherAware 应用事件发布者,通过他你可以发布应用事件。
ResourceLoaderAware 资源装载器,通过他可以加载外部资源。
你可以实现这些接口,然后试试看你都能获得什么。
工作原理:
public class HelloBeanNameAware implements BeanNameAware {
private String name;
@Override
public void setBeanName(String name) {
this.name = name;
}
@Override
public String toString() {
return "HelloBeanNameAware [name=" + name + "]";
}
}
public class HelloBeanFactoryAware implements BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public String toString() {
return "HelloBeanFactoryAware [beanFactory=" + beanFactory.getClass().getSimpleName() + "]";
}
}
public class HelloApplicationContextAware implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public String toString() {
return "HelloApplicationContextAware [applicationContext="
+ applicationContext.getClass().getSimpleName() + "]";
}
}
<bean id="helloBeanNameAware" class="com.partner4java.spring.aware.HelloBeanNameAware" />
<bean id="helloBeanFactoryAware" class="com.partner4java.spring.aware.HelloBeanFactoryAware" />
<bean id="helloApplicationContextAware" class="com.partner4java.spring.aware.HelloApplicationContextAware" />
@Test
public void testAware() throws InterruptedException{
System.out.println(applicationContext.getBean("helloBeanNameAware"));
System.out.println(applicationContext.getBean("helloBeanFactoryAware"));
System.out.println(applicationContext.getBean("helloApplicationContextAware"));
// 后台打印:
// HelloBeanNameAware [name=helloBeanNameAware]
// HelloBeanFactoryAware [beanFactory=DefaultListableBeanFactory]
// HelloApplicationContextAware
// [applicationContext=ClassPathXmlApplicationContext]
}
10加载外部资源
问题:
有时候,你的应用可能需要从不同位置(例如文件系统、classpath或者URL)读取外部资源(例如文本文件、XML文件、属性文件或者图像文件)。
通常,你必须处理用于从不同位置加载资源的不同API。
解决方案:
Spring的资源装载器提供统一的getResoure()方法,按照资源路径读取外部资源。你可以为路径指定不同的前缀从不同位置加载资源。
为了从文件系统加载资源,使用file前缀。
从classpath加载资源则使用classpath前缀。
你还可以在资源路径中指定一个URL。
Resource是Spring中代表外部资源的通用接口。Spring提供Resource接口的多个实现。
资源装载器的getResource()方法根据资源路径决定实例化哪一个Resource实现。
工作原理:
方式一:实现接口ResourceLoaderAware(感知接口)
public class HelloResourceLoader implements ResourceLoaderAware {
private ResourceLoader resourceLoader;
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public void showResource() throws IOException {
Resource resource = resourceLoader.getResource("file:D:/cc.txt");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
String s = null;
while((s = bufferedReader.readLine()) != null){
System.out.println(s);
}
}
}
<bean id="helloResourceLoader" class="com.partner4java.spring.resource.HelloResourceLoader"
init-method="showResource" />
获取bean,会注入感知接口的资源,并执行初始化方法。
方式二:
简单的指定这个Resource属性的资源路径,Spring将使用预先注册属性编辑器ResourceEditor将这个属性转换为一个Resource对象,然后注入你的Bean中。
public class HelloSimpleResourceLoader {
private Resource resource;
public void setResource(Resource resource) {
this.resource = resource;
}
public void showResource() throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
String s = null;
while((s = bufferedReader.readLine()) != null){
System.out.println(s);
}
}
}
<bean id="helloSimpleResourceLoader"
class="com.partner4java.spring.resource.HelloSimpleResourceLoader"
init-method="showResource">
<property name="resource">
<value>classpath:com/partner4java/spring/App.class</value>
</property>
</bean>
11创建Bean后处理器
问题:
你希望在Spring IoC容器中注册自己的插件,在构造期间处理Bean实例。
解决方案:
Bean后处理器允许在初始化回调方法前后进行附加的Bean处理。
Bean后处理器的主要特性是逐个处理IoC容器中的所有Bean实例,而不是单个Bean实例。
一般,Bean后处理器用于检查Bean属性有效性,或者根据特殊条件修改Bean属性。
Bean后处理器的基本要求是实现BeanPostProcessor接口。
你可以实现postProcessBeforeInitialization()和postProcessAfterInitialization()方法,在初始化回调方法前后处理所有Bean。
然后,Spring将在调用初始化回调方法前后向这两个方法传递每个Bean实例。
步骤如下:
1、构造程序或者工厂方法创建Bean实例。
2、为Bean属性设置值和Bean引用。
3、调用感知接口中定义的设置方法。
4、将Bean实例传递给每个Bean前置处理器中的postProcessBeforeInitialization方法。
5、调用初始化回调方法。
6、讲Bean实例传递给每个Bean后处理器中的postProcessAfterInitialization方法。
7、Bean准备就绪,可以使用。
8、容器关闭时,调用析构回调方法。
使用Bean工厂为IoC容器时,Bean后处理器只能编程注册,更准确的讲是通过addBeanPostProcessor()方法注册。
但是,如果你使用一个应用上下文,注册将很简单,只要在Bean配置文件中声明处理器实例,他就会自动注册。
工作原理:
public class LogBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
System.out.println(beanName + " say hello world!");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
System.out.println(beanName + " say good buy!");
return bean;
}
}
<!-- 要在应用上线文中注册一个Bean后处理器,只要在Bean配置文件中声明他的一个实例就可以了。 应用上下文能够自动检测谁实现了BeanPostProcessor接口,并且注册他一处理容器中的所有其他Bean实例 -->
<bean class="com.partner4java.spring.postprocess.LogBeanPostProcessor" />
<bean id="helloResourceLoader" class="com.partner4java.spring.resource.HelloResourceLoader"
init-method="showResource" />
如果以配置文件的格式设置init-method,对BeanPostProcesser的执行没有什么威胁,BeanPostProcesser还是会先执行。
但是如果,以@PreDestroy和@PostConstruct的形式,BeanPostProcesser讲不能正常工作,因为BeanPostProcesser的默认优先级低于CommonAnnotationBeanPostProcesser。
不过可以同时实现PriorityOrdered接口来指定执行顺序。
12外部化Bean配置
问题:
在配置文件中配置Bean时,你必须记住,讲部署细节如文件路径、服务器地址、用户名和密码与Bean配置混在一起是不好的做法。
通常,Bean配置由应用开发人员编写,而部署细节则是部署人员或者系统管理员的事情。
解决方案:
Spring有一个名为PropertyPlaceholderConfigurer的Bean工厂后处理器,
用来将部分Bean配置外部化为一个属性文件。你可以在Bean配置文件中使用${var}形式的变量,
PropertyPlaceholderConfigurer讲从属性文件中加载属性并且用他们替代变量。
Bea工厂后处理器与Bean后处理器之间的不同在他的目标是IoC容器--Bean工厂或者应用上下文,而不是Bean实例。
Bean工厂后处理器将在IoC容器加载Bean配置之后、Bean实例创建之前生效,他的典型作用是在Bean实例化之前修改Bean配置。
Spring有多个Bean工厂后处理器供你使用。在实战中,你很少与必要编写自己的Bean工厂后处理器。
工作原理:
方式一:
具体指定外部Bean配置类
DBConfig.properties:
DB.name=test
public class DBConfig {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "DBConfig [name=" + name + "]";
}
}
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<value>classpath:DBConfig.properties</value>
</property>
</bean>
<bean id="dbConfig" class="com.partner4java.spring.config.DBConfig">
<property name="name" value="${DB.name}"/>
</bean>
第二种:
简单的方式
<context:property-placeholder location="classpath:DBConfig.properties"/>
13解析文本消息
问题:
对于支持国际化的应用来说,为不同地区解析文本消息的能力是必要的。
解决方案:
Spring应用上下文能够按照关键字为目标地区解析文本消息。一般来说,一个地区的消息应该存储在一个独立的属性文件中,这个属性文件称作资源包(Resource bundle)。
MessageSource是定义多种消息解析方法的接口。ApplicationContext接口扩展了这个接口,使用所有应用上下文能够解析文本消息。
应用上下文将消息解析委派给名为messageSource的Bean。ResourceBundleMessageSource最常见的MessageSource实现,他从资源包中解析不同地区的消息。
工作原理:
方式一:
message_en_US.properties:
shopping.wife=gaofumei beatu
shopping.age={0} oh
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename">
<value>message</value>
</property>
</bean>
@Test
public void testAware() throws InterruptedException{
System.out.println(applicationContext.getMessage("shopping.wife", null, Locale.US));
System.out.println(applicationContext.getMessage("shopping.age", new Object[]{18}, Locale.US));
}
后台打印:
gaofumei beatu
18 oh
14使用应用事件进行通信
问题:
在组件之间的典型通信模式中,发送者必须定位接受者,以便调用接受者之上的方法。
在这种情况下,发送者组件必须了解接收者组件。这种通信直接而简单,但是发送者和接受者组件紧密耦合。
使用IoC容器时,你的组件可以通过接口而不是实现进行通信。这种通信模式有助于减少耦合。
但是,只有在发送者组件必须与一个接受者通信时有效。当发送者必须与多个接受者通信时,必须逐个调用接收者。
解决方案:
Spring的应用上下文支持基于事件的Bean间通信。
在基于事件的通信模式中,发送者组件只要发布一个事件而不需要知道接收者。
实际上,可以有多于一个接收者组件。
而且,接收者不需要知道是谁发布了事件,可以同时监听不同发送者的多个事件。
这样,发送者和接收者组件是低耦合的。
在Spring中,所有事件类都必须扩展ApplicationEvent类。这样,任何Bean都可以调用应用时间发布者的publishEvent()方法,发布一个事件 。
对于监听某些事件的Bean来说,必须实现ApplicationListener接口,并在onApplicationEvent()方法中处理事件。
实际上,Spring将通知所有事件的监听者,这样你必须自己过滤事件。但是,如果使用类属,Spring将只分发匹配类属参数的消息。
工作原理:
(jdk里面的观察者模式也挺成熟的)
15在Spring中注册属性编辑器
问题:
属性编辑器是JavaBeans API的一项功能,用于属性值与文本值互相转换。
每个属性编辑器仅用于某一类属性。你可以希望采用属性编辑器来简化Bean配置。
解决方案:
Spring IoC容器支持使用属性编辑器帮助Bean配置。
例如,使用java.net.URL类型的属性编辑器,可以指定用于URL类型属性的URL字符串。
Spring会自动的将这个URL字符串转换为一个URL对象,注入你的属性中。
Spring自带多种用于转换常见类型Bean属性的属性编辑器。
一般来说,你应该在Spring IoC容器中注册属性编辑器,然后才能使用它。
CustomEditorConfigurer是作为Bean工厂后处理器来实现的,用于在任何Bean实例化之前注册你的自定义属性编辑器。
工作原理:
就是先写一个编辑器,然后注册,然后自动使用。
除了CustomDateEditor之外,Spring自带多个转换常见数据类型的属性编辑器,
例如CustomNumberEditor、ClassEditor、FileEditor、LocaleEditor、StringArrayPropertyEditor和URLEditor。
可查看包:org.springframework.beans.propertyeditors
public class User {
private String username;
private Date birthday;
...
<bean id="dateEditor"
class="org.springframework.beans.propertyeditors.CustomDateEditor">
<!-- DateFormat对象作为其第一个构造程序参数 -->
<constructor-arg>
<bean class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd" />
</bean>
</constructor-arg>
<!-- 表示该编辑器是否允许空值 -->
<constructor-arg value="true"/>
</bean>
<!-- 必须进行注册 -->
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="java.util.Date">
<ref local="dateEditor"/>
</entry>
</map>
</property>
</bean>
<bean id="user" class="com.partner4java.spring.editor.User">
<property name="username" value="gaofumei"/>
<property name="birthday" value="2012-10-10"/>
</bean>
16创建自定义属性编辑器
问题:
除了注册内建的属性编辑器之外,你可能希望编写自定义的属性编辑器,转换你自定义数据类型。
解决方案:
你可以实现java.beans.PropertyEditor实例或者扩展便利的支持类java.beans.PropertyEditorSupport,编写自定义的属性编辑器。
工作原理:
首先实现自己的编辑器,然后注册编辑器
public class UserEditor extends PropertyEditorSupport {
@Override
public String getAsText() {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
User user = (User) getValue();
return user.getClass().getName() + "," + user.getUsername() + ","
+ dateFormat.format(user.getBirthday());
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
String[] parts = text.split(",");
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
User user = (User)Class.forName(parts[0]).newInstance();
user.setUsername(parts[1]);
user.setBirthday(dateFormat.parse(parts[2]));
setValue(user);
} catch (Exception e) {
e.printStackTrace();
}
}
}
<!-- 必须进行注册 -->
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="com.partner4java.spring.editor.User">
<bean class="com.partner4java.spring.editor.UserEditor"/>
</entry>
</map>
</property>
</bean>
<bean id="userChild" class="com.partner4java.spring.editor.UserChild">
<property name="user">
<value>com.partner4java.spring.editor.User,gaofumei,2012-01-02</value>
</property>
</bean>
@Test
public void testAware() throws InterruptedException{
System.out.println(applicationContext.getBean("userChild"));
}