- 放弃控制反转。通过实现ApplicationContextAware接口让bean A能够感知bean 容器,并且在需要的时候通过使用getBean("B")方式向容器请求一个新的bean B实例。
- Lookup方法注入。Lookup方法注入利用了容器的覆盖受容器管理的bean方法的能力,从而返回指定名字的bean实例。
- 自定义方法的替代方案。该注入能使用bean的另一个方法实现去替换自定义的方法。
这里只说前两种方案的实现,第三种方案因为不常用,略过不提,有兴趣的可以了解一下。
一、通过实现ApplicationContextAware接口以编程的方式实现
ApplicationContextAware和BeanFactoryAware差不多,用法也差不多,实现了ApplicationContextAware接口的对象会拥有一个ApplicationContext的引用,这样我们就可以已编程的方式操作ApplicationContext。看下面的示例:
定义Command接口和其实现类AsyncCommand。
package org.luosijin.springTest.applicationContextAware;
/**
* 一个命令接口
* @author luosijin
*
*/
public interface Command
{
public Object execute();
}
package org.luosijin.springTest.applicationContextAware;
/**
* 一个异步处理命令的实现
* @author luosijin
*
*/
public class AsyncCommand implements Command {
/* (non-Javadoc)
* @see org.luosijin.springTest.applicationContextAware.Command#excute()
*/
@Override
public Object execute() {
//返回自身实例,是为了测试的时候好看出每次返回的不是同一个实例
return this;
}
}
写一个singleton例
package org.luosijin.springTest.applicationContextAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* 命令管理器
* @author luosijin
*
*/
public class CommandManager implements ApplicationContextAware {
//用于保存ApplicationContext的引用,采用set方式注入
private ApplicationContext applicationContext;
/* (non-Javadoc)
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public Object process()
{
Command command = createCommand();
return command.execute();
}
//创建一个命令
private Command createCommand()
{
return (Command)applicationContext.getBean("asyncCommand");
}
}
配置Bean文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd ">
<bean id="personServiceImpl" class="org.luosijin.springTest.service.impl.PersonServiceImpl"/>
<bean id="bankSecurity" class="org.luosijin.springTest.service.impl.BankSecurityServiceImpl">
<property name="bankSecurityDao" ref="bankSecurityDao"/>
<property name="totalMoney" >
<bean id="bankSecurityDao.money" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
</property>
</bean>
<bean id="bankSecurityDao" class="org.luosijin.springTest.dao.BankSecurityDao">
<property name="money" value="10000"/>
</bean>
<bean id="bankSecurity2" class="org.luosijin.springTest.service.impl.BankSecurityServiceImpl2"/>
<!-- 通过scope="prototype"界定该bean是多例的 -->
<bean id="asyncCommand" class="org.luosijin.springTest.applicationContextAware.AsyncCommand" scope="prototype" />
<bean id="commandManger" class="org.luosijin.springTest.applicationContextAware.CommandManager"/>
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
</beans>
以上主要是单例Bean commandManager的process()方法需要引用一个prototype(非单例)的bean,所以在调用process的时候先通过createCommand方法从容器中取得一个Command,然后在执行业务计算,
写Junit测试类
import org.luosijin.springTest.applicationContextAware.CommandManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author luosijin
* 测试类
*
*/
public class TestCommandManager
{
private ApplicationContext applicationContext;
@Before
public void setUP() throws Exception
{
applicationContext = new ClassPathXmlApplicationContext(new String[]{"beans.xml"});
}
@Test
public void testProcess()
{
CommandManager commandManager = applicationContext.getBean("commandManger", CommandManager.class);
System.out.println("第一执行process,Command的地址是:"+commandManager.process());
System.out.println("第二执行process,Command的地址是:"+commandManager.process());
}
}
运行结果如下:
2013-1-5 10:28:28 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@9664a1: startup date [Sat Jan 05 10:28:28 CST 2013]; root of context hierarchy
2013-1-5 10:28:28 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [beans.xml]
2013-1-5 10:28:29 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@92bbba: defining beans [personServiceImpl,bankSecurity,bankSecurityDao,bankSecurity2,asyncCommand,commandManger,org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#0]; root of factory hierarchy
第一执行process,Command的地址是:org.luosijin.springTest.applicationContextAware.AsyncCommand@1d86fd3
第二执行process,Command的地址是:org.luosijin.springTest.applicationContextAware.AsyncCommand@958bb8
通过控制台输出看到两次的输出借中的Command的地址是不一样的,因为我们为asyncCommand配置了scope="prototype"属性,这种方式就是使得每次从容器中取得的bean实例都不一样。通过这样方式我们实现了单例bean(commandManager)中的方法(process方法)引用非单例的bean(asyncCommand)。虽然我们实现了,但是这不是一种好的方法,因为我们的业务代码和Spring Framework产生了耦合。下面介绍Spring提供的另外一种干净的实现方式,就是Lookup方法注入。
二、通过Lookup方法注入来实现
使用这种方式很简单,因为Spring已经为我们做了很大一部分工作,我们要做的就是bean配置和业务类。
在上面的例子中:
- 首先修改CommandManager类为abstract的,修改createCommand方法也为abstract的。
- 去掉ApplicationContextAware的实现及相关set方法和applicationContext变量定义
- 修改bean配置文件,在commandManager Bean中增加<lookup-method name="createCommand" bean="asyncCommand"/>。
- 其他保持不变
修改后的CommandManager类代码如下:
package org.luosijin.springTest.applicationContextAware;
/**
* 命令管理器
* @author luosijin
*
*/
public abstract class CommandManager{
public Object process()
{
Command command = createCommand();
return command.execute();
}
//创建一个命令
protected abstract Command createCommand();
}
bean配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd ">
<bean id="personServiceImpl" class="org.luosijin.springTest.service.impl.PersonServiceImpl"/>
<bean id="bankSecurity" class="org.luosijin.springTest.service.impl.BankSecurityServiceImpl">
<property name="bankSecurityDao" ref="bankSecurityDao"/>
<property name="totalMoney" >
<bean id="bankSecurityDao.money" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
</property>
</bean>
<bean id="bankSecurityDao" class="org.luosijin.springTest.dao.BankSecurityDao">
<property name="money" value="10000"/>
</bean>
<bean id="bankSecurity2" class="org.luosijin.springTest.service.impl.BankSecurityServiceImpl2"/>
<!-- 通过scope="prototype"界定该bean是多例的 -->
<bean id="asyncCommand" class="org.luosijin.springTest.applicationContextAware.AsyncCommand" scope="prototype"/>
<bean id="commandManger" class="org.luosijin.springTest.applicationContextAware.CommandManager">
<lookup-method name="createCommand" bean="asyncCommand"/>
</bean>
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
</beans>
运行结果如下:
2013-1-5 11:00:56 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@9664a1: startup date [Sat Jan 05 11:00:56 CST 2013]; root of context hierarchy
2013-1-5 11:00:56 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [beans.xml]
2013-1-5 11:00:56 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@92bbba: defining beans [personServiceImpl,bankSecurity,bankSecurityDao,bankSecurity2,asyncCommand,commandManger,org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#0]; root of factory hierarchy
第一执行process,Command的地址是:org.luosijin.springTest.applicationContextAware.AsyncCommand@19b1de
第二执行process,Command的地址是:org.luosijin.springTest.applicationContextAware.AsyncCommand@ec6b00
<lookup-method>标签中的name属性就是commandManager Bean的获取Command实例(AsyncCommand)的方法,也就createCommand方法,bean属性是要返回哪种类型的Command的,这里是AsyncCommand。 这里的createCommand方法就成为被注入方法,他的定义形式必须为:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
被注入方法不一定是抽象的,如果被注入方法是抽象的,动态生成的子类(这里就是动态生成的CommandManager的子类)会实现该方法。否则,动态生成的子类会覆盖类里的具体方法。为了让这个动态子类得以正常工作,需要把CGLIB的jar文件放在classpath里,这就是我们引用cglib包的原因。还有,Spring容器要子类化的类(CommandManager)不能是final的,要覆盖的方法(createCommand)也不能是final的。
Lookup方法注入干净整洁,易于扩展,更符合Ioc规则,所以尽量采用这种方式。