引用
在大部分情况下,容器中的bean都是singleton类型的。如果一个singleton bean要引用另外一个singleton bean,或者一个非singleton bean要引用另外一个非singleton bean时,通常情况下将一个bean定义为另一个bean的property值就可以了。不过对于具有不同生命周期的bean来说这样做就会有问题了,比如在调用一个singleton类型bean A的某个方法时,需要引用另一个非singleton(prototype)类型的bean B,对于bean A来说,容器只会创建一次,这样就没法在需要的时候每次让容器为bean A提供一个新的的bean B实例
对于上面的问题Spring提供了三种解决方案:
-
放弃控制反转。通过实现ApplicationContextAware接口让bean A能够感知bean 容器,并且在需要的时候通过使用getBean(“B”)方式向容器请求一个新的bean B实例。
-
Lookup方法注入。Lookup方法注入利用了容器的覆盖受容器管理的bean方法的能力,从而返回指定名字的bean实例。
-
自定义方法的替代方案。该注入能使用bean的另一个方法实现去替换自定义的方法。
这里只说前两种方案的实现,第三种方案因为不常用,略过不提,有兴趣的可以了解一下。
一:实现环境
-
Eclipse3.4
-
JDK1.5
-
Spring3.0.3
-
Junit 4测试框架
-
依赖jar有log4j-1.2.16.jar,commons-logging-api-1.1.1.jar,cglib-nodep-2.2.jar。
二:通过实现ApplicationContextAware接口以编程的方式实现
ApplicationContextAware和BeanFactoryAware差不多,用法也差不多,实现了ApplicationContextAware接口的对象会拥有一个ApplicationContext的引用,这样我们就可以已编程的方式操作ApplicationContext。看下面的例子。
-
package com.flysnow.injection;
-
-
import org.springframework.beans.BeansException;
-
import org.springframework.context.ApplicationContext;
-
import org.springframework.context.ApplicationContextAware;
-
-
import com.flysnow.injection.command.Command;
-
-
/**
-
* 命令管理器
-
* @author 飞雪无情
-
*
-
*/
-
public class CommandManager implements ApplicationContextAware {
-
//用于保存ApplicationContext的引用,set方式注入
-
private ApplicationContext applicationContext;
-
//模拟业务处理的方法
-
public Object process(){
-
Command command=createCommand();
-
return command.execute();
-
}
-
//获取一个命令
-
private Command createCommand() {
-
return (Command) this.applicationContext.getBean(“asyncCommand”); //
-
}
-
-
public void setApplicationContext(ApplicationContext applicationContext)
-
throws BeansException {
-
this.applicationContext=applicationContext;//获得该ApplicationContext引用
-
}
-
-
}
<span style="font-size:18px;">package com.flysnow.injection;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import com.flysnow.injection.command.Command;
/**
* 命令管理器
* @author 飞雪无情
*
*/
public class CommandManager implements ApplicationContextAware {
//用于保存ApplicationContext的引用,set方式注入
private ApplicationContext applicationContext;
//模拟业务处理的方法
public Object process(){
Command command=createCommand();
return command.execute();
}
//获取一个命令
private Command createCommand() {
return (Command) this.applicationContext.getBean("asyncCommand"); //
}
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext=applicationContext;//获得该ApplicationContext引用
}
}
</span>
下面定义Command接口和其实现类AsyncCommand。
-
package com.flysnow.injection.command;
-
-
/**
-
* 一个命令接口
-
* @author 飞雪无情
-
*
-
*/
-
public interface Command {
-
/**
-
* 执行命令
-
*
@return
-
*/
-
public Object execute();
-
}
<span style="font-size:18px;">package com.flysnow.injection.command;
/**
* 一个命令接口
* @author 飞雪无情
*
*/
public interface Command {
/**
* 执行命令
* @return
*/
public Object execute();
}
</span>
-
package com.flysnow.injection.command;
-
-
/**
-
* 一个异步处理命令的实现
-
* @author 飞雪无情
-
*
-
*/
-
public class AsyncCommand implements Command {
-
-
/* (non-Javadoc)
-
* @see com.flysnow.lookup.command.Command#execute()
-
*/
-
public Object execute() {
-
//返回自身实例,是为了测试的时候好看出每次返回的不是同一个实例
-
return this;
-
}
-
-
}
<span style="font-size:18px;">package com.flysnow.injection.command;
/**
* 一个异步处理命令的实现
* @author 飞雪无情
*
*/
public class AsyncCommand implements Command {
/* (non-Javadoc)
* @see com.flysnow.lookup.command.Command#execute()
*/
public Object execute() {
//返回自身实例,是为了测试的时候好看出每次返回的不是同一个实例
return this;
}
}
</span>
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.0.xsd”>
-
<!– 通过scope=”prototype”界定该bean是多例的 –>
-
<bean id=”asyncCommand” class=”com.flysnow.injection.command.AsyncCommand” scope=”prototype”></bean>
-
<bean id=”commandManager” class=”com.flysnow.injection.CommandManager”>
-
</bean>
-
</beans>
<span style="font-size:18px;"><?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.0.xsd">
<!-- 通过scope="prototype"界定该bean是多例的 -->
<bean id="asyncCommand" class="com.flysnow.injection.command.AsyncCommand" scope="prototype"></bean>
<bean id="commandManager" class="com.flysnow.injection.CommandManager">
</bean>
</beans>
</span>
以上主要是单例Bean commandManager的process()方法需要引用一个prototype(非单例)的bean,所以在调用process的时候先通过createCommand方法从容器中取得一个Command,然后在执行业务计算,代码中有注释,很简单。
测试类如下:
-
package com.flysnow.injection;
-
-
import org.junit.Before;
-
import org.junit.Test;
-
import org.springframework.context.ApplicationContext;
-
import org.springframework.context.support.ClassPathXmlApplicationContext;
-
-
import com.flysnow.injection.CommandManager;
-
-
public class TestCommandManager {
-
private ApplicationContext context;
-
@Before
-
public void setUp() throws Exception {
-
context=new ClassPathXmlApplicationContext(“beans.xml”);
-
}
-
-
@Test
-
public void testProcess() {
-
CommandManager manager=context.getBean(“commandManager”, CommandManager.class);
-
System.out.println(“第一执行process,Command的地址是:”+manager.process());
-
System.out.println(“第二执行process,Command的地址是:”+manager.process());
-
}
-
-
}
<span style="font-size:18px;">package com.flysnow.injection;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.flysnow.injection.CommandManager;
public class TestCommandManager {
private ApplicationContext context;
@Before
public void setUp() throws Exception {
context=new ClassPathXmlApplicationContext("beans.xml");
}
@Test
public void testProcess() {
CommandManager manager=context.getBean("commandManager", CommandManager.class);
System.out.println("第一执行process,Command的地址是:"+manager.process());
System.out.println("第二执行process,Command的地址是:"+manager.process());
}
}
</span>
可以通过控制台输出看到两次的输出借中的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和bean配置文件如下:
-
public abstract class CommandManager {
-
//模拟业务处理的方法
-
public Object process(){
-
Command command=createCommand();
-
return command.execute();
-
}
-
//获取一个命令
-
protected abstract Command createCommand();
-
}
<span style="font-size:18px;">public abstract class CommandManager {
//模拟业务处理的方法
public Object process(){
Command command=createCommand();
return command.execute();
}
//获取一个命令
protected abstract Command createCommand();
}
</span>
-
<bean id=”commandManager” class=”com.flysnow.injection.CommandManager”>
-
<lookup-method name=”createCommand” bean=”asyncCommand”/>
-
</bean>
<span style="font-size:18px;"><bean id="commandManager" class="com.flysnow.injection.CommandManager">
<lookup-method name="createCommand" bean="asyncCommand"/>
</bean>
</span>
运行测试,控制台打印出的两个Command的地址不一样,说明我们实现了。
<lookup-method>标签中的name属性就是commandManager Bean的获取Command实例(AsyncCommand)的方法,也就createCommand方法,bean属性是要返回哪种类型的Command的,这里是AsyncCommand。
这里的createCommand方法就成为被注入方法,他的定义形式必须为:
-
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
<span style="font-size:18px;"><public|protected> [abstract] <return-type> theMethodName(no-arguments);</span>
被注入方法不一定是抽象的,如果被注入方法是抽象的,动态生成的子类(这里就是动态生成的CommandManager的子类)会实现该方法。否则,动态生成的子类会覆盖类里的具体方法。为了让这个动态子类得以正常工作,需要把CGLIB的jar文件放在classpath里,这就是我们引用cglib包的原因。还有,Spring容器要子类化的类(CommandManager)不能是final的,要覆盖的方法(createCommand)也不能是final的。
当一个Bean依赖的Bean和自己生命周期不同的时候:如Bean A依赖Bean B,Bean A 是singleton,如果需要在Bean A每次用到Bean B的时候都用一个Bean B的新的实例,通过在配置文件中通过 property或者 contructor-arg是不能实现的.这时候只能在Bean A中用Bean B的时候动态得到.通常的做法有两种:
1,Bean A实现 ApplicationContextAware, Spring初始化的时候会将 ApplicationContext 传给Bean A,Bean A通过getBean(“BeanB”)方法每次得到Bean B.(“BeanB”最好不要hardcode,通过property传入)例:
-
public class ContextAwareBean implements ApplicationContextAware {
-
protected static final Log log = LogFactory.getLog(AnotherBean.class);
-
private String anotherBeanName;
-
private ApplicationContext applicationContext;
-
-
public String getAnotherBeanName() {
-
return anotherBeanName;
-
}
-
public void setAnotherBeanName(String anotherBeanName) {
-
this.anotherBeanName = anotherBeanName;
-
}
-
public void process() {
-
log.info(“process applicationContext ” + applicationContext);
-
AnotherBean anotherBean = createAnotheBean();
-
anotherBean.doSth();
-
-
}
-
protected AnotherBean createAnotheBean() {
-
-
return this.applicationContext.getBean(anotherBeanName, AnotherBean.class);
-
}
-
public void setApplicationContext(ApplicationContext applicationContext){
-
log.info(“setApplicationContext ” + applicationContext);
-
this.applicationContext = applicationContext;
-
}
-
}
-
public class AnotherBean {
-
protected static final Log log = LogFactory.getLog(AnotherBean.class);
-
public String doSth(){
-
log.info(“AnotherBean.doSth”);
-
return “do something”;
-
}
-
}
-
-
<bean id=”AnotherBean” class=”com.test.spring.di.mtddi.AnotherBean” scope=”prototype”/>
-
-
<bean id=”ContextAwareBean” class=”com.test.spring.di.mtddi.ContextAwareBean” >
-
<property name=”anotherBeanName” value=”AnotherBean”/>
-
</bean>
<span style="font-size:18px;">public class ContextAwareBean implements ApplicationContextAware {
protected static final Log log = LogFactory.getLog(AnotherBean.class);
private String anotherBeanName;
private ApplicationContext applicationContext;
public String getAnotherBeanName() {
return anotherBeanName;
}
public void setAnotherBeanName(String anotherBeanName) {
this.anotherBeanName = anotherBeanName;
}
public void process() {
log.info("process applicationContext " + applicationContext);
AnotherBean anotherBean = createAnotheBean();
anotherBean.doSth();
}
protected AnotherBean createAnotheBean() {
return this.applicationContext.getBean(anotherBeanName, AnotherBean.class);
}
public void setApplicationContext(ApplicationContext applicationContext){
log.info("setApplicationContext " + applicationContext);
this.applicationContext = applicationContext;
}
}
public class AnotherBean {
protected static final Log log = LogFactory.getLog(AnotherBean.class);
public String doSth(){
log.info("AnotherBean.doSth");
return "do something";
}
}
<bean id="AnotherBean" class="com.test.spring.di.mtddi.AnotherBean" scope="prototype"/>
<bean id="ContextAwareBean" class="com.test.spring.di.mtddi.ContextAwareBean" >
<property name="anotherBeanName" value="AnotherBean"/>
</bean></span>
2,方法注入:在Bean A中定义一个方法,返回类型是Bean B,在配置文件中通过”lookup-method”告诉Spring动态覆盖该方法,并返回Bean B的一个实例:
-
public abstract class ReplacedBean {
-
protected static final Log log = LogFactory.getLog(ReplacedBean.class);
-
-
public void process() {
-
-
AnotherBean anotherBean = createAnotheBean();
-
anotherBean.doSth();
-
-
}
-
protected abstract AnotherBean createAnotheBean();
-
-
}
-
-
<bean id=”AnotherBean” class=”com.test.spring.di.mtddi.AnotherBean” scope=”prototype”/>
-
-
<bean id=”ReplacedBean” class=”com.test.spring.di.mtddi.ReplacedBean” >
-
<lookup-method name=”createAnotheBean” bean=”AnotherBean”/>
-
</bean>
<span style="font-size:18px;">public abstract class ReplacedBean {
protected static final Log log = LogFactory.getLog(ReplacedBean.class);
public void process() {
AnotherBean anotherBean = createAnotheBean();
anotherBean.doSth();
}
protected abstract AnotherBean createAnotheBean();
}
<bean id="AnotherBean" class="com.test.spring.di.mtddi.AnotherBean" scope="prototype"/>
<bean id="ReplacedBean" class="com.test.spring.di.mtddi.ReplacedBean" >
<lookup-method name="createAnotheBean" bean="AnotherBean"/>
</bean></span>
客户端代码:
-
public class MtddiClient {
-
private static BeanFactory factory;
-
private static ApplicationContext ctx;
-
static {
-
Resource resource = new ClassPathResource(“conf/mtddiAppcontext.xml”);
-
factory = new XmlBeanFactory(resource);
-
-
ctx = new ClassPathXmlApplicationContext(“conf/mtddiAppcontext.xml”);
-
}
-
/**
-
* @param args
-
*/
-
public static void main(String[] args) {
-
/*不能通过bean factory的方式得到bean
-
ContextAwareBean bean = (ContextAwareBean) factory.getBean(“ContextAwareBean”);
-
bean.process();
-
*/
-
//ContextAwareBean 只能从ApplicationContext获得bean
-
//ContextAwareBean bean = (ContextAwareBean) ctx.getBean(“ContextAwareBean”);
-
//bean.process();
-
-
ReplacedBean bean1 = (ReplacedBean) factory.getBean(“ReplacedBean”);
-
bean1.process();
-
}
-
}
<span style="font-size:18px;">public class MtddiClient {
private static BeanFactory factory;
private static ApplicationContext ctx;
static {
Resource resource = new ClassPathResource("conf/mtddiAppcontext.xml");
factory = new XmlBeanFactory(resource);
ctx = new ClassPathXmlApplicationContext("conf/mtddiAppcontext.xml");
}
/**
* @param args
*/
public static void main(String[] args) {
/*不能通过bean factory的方式得到bean
ContextAwareBean bean = (ContextAwareBean) factory.getBean("ContextAwareBean");
bean.process();
*/
//ContextAwareBean 只能从ApplicationContext获得bean
//ContextAwareBean bean = (ContextAwareBean) ctx.getBean("ContextAwareBean");
//bean.process();
ReplacedBean bean1 = (ReplacedBean) factory.getBean("ReplacedBean");
bean1.process();
}
}</span>
*对于实现ApplicationContextAware的Bean,必须用 ApplicationContext的getBean方法.对于方法注入(
lookup-method方式
):用BeanFactory和ApplicationContext的getBean都可以.如果要用BeanFactory,应该实现BeanFactoryAware:
-
public class BeanFactoryAwareBean implements BeanFactoryAware {
-
protected static final Log log = LogFactory.getLog(BeanFactoryAwareBean.class);
-
private String anotherBeanName;
-
-
private BeanFactory beanFactory;
-
-
public String getAnotherBeanName() {
-
return anotherBeanName;
-
}
-
public void setAnotherBeanName(String anotherBeanName) {
-
this.anotherBeanName = anotherBeanName;
-
}
-
public void process() {
-
log.info(“process beanFactory ” + beanFactory);
-
AnotherBean anotherBean = createAnotheBean();
-
anotherBean.doSth();
-
}
-
protected AnotherBean createAnotheBean() {
-
-
return this.beanFactory.getBean(anotherBeanName, AnotherBean.class);
-
}
-
-
@Override
-
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
-
this.beanFactory = beanFactory;
-
}
-
}
<span style="font-size:18px;">public class BeanFactoryAwareBean implements BeanFactoryAware {
protected static final Log log = LogFactory.getLog(BeanFactoryAwareBean.class);
private String anotherBeanName;
private BeanFactory beanFactory;
public String getAnotherBeanName() {
return anotherBeanName;
}
public void setAnotherBeanName(String anotherBeanName) {
this.anotherBeanName = anotherBeanName;
}
public void process() {
log.info("process beanFactory " + beanFactory);
AnotherBean anotherBean = createAnotheBean();
anotherBean.doSth();
}
protected AnotherBean createAnotheBean() {
return this.beanFactory.getBean(anotherBeanName, AnotherBean.class);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}</span>
两种方法的比较:理论上来讲,第二种方法更体现了IoC的思想,而且在bean类里面没有依赖到Spring,只是一个POJO.客户端在使用它的时候可以是依靠Spring配置(lookup-method)来使用,也可以通过提供实现类来完成调用.
四:小结
Lookup方法注入干净整洁,易于扩展,更符合Ioc规则,所以尽量采用这种方式。
转载于:https://my.oschina.net/sfsimon/blog/659437