zoukankan      html  css  js  c++  java
  • Spring学习笔记(10)——方法注入

    引用

    在大部分情况下,容器中的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。看下面的例子。 
     
    1. package com.flysnow.injection;  
    2.   
    3. import org.springframework.beans.BeansException;  
    4. import org.springframework.context.ApplicationContext;  
    5. import org.springframework.context.ApplicationContextAware;  
    6.   
    7. import com.flysnow.injection.command.Command;  
    8.   
    9. /** 
    10.  * 命令管理器 
    11.  * @author 飞雪无情 
    12.  * 
    13.  */  
    14. public class CommandManager implements ApplicationContextAware {  
    15.     //用于保存ApplicationContext的引用,set方式注入  
    16.     private ApplicationContext applicationContext;  
    17.     //模拟业务处理的方法  
    18.     public Object process(){  
    19.         Command command=createCommand();  
    20.         return command.execute();  
    21.     }  
    22.     //获取一个命令  
    23.     private Command createCommand() {  
    24.         return (Command) this.applicationContext.getBean("asyncCommand"); //  
    25.     }  
    26.   
    27.     public void setApplicationContext(ApplicationContext applicationContext)  
    28.             throws BeansException {  
    29.         this.applicationContext=applicationContext;//获得该ApplicationContext引用  
    30.     }  
    31.   
    32. }  


    下面定义Command接口和其实现类AsyncCommand。 

     
    1. package com.flysnow.injection.command;  
    2.   
    3. /** 
    4.  * 一个命令接口 
    5.  * @author 飞雪无情 
    6.  * 
    7.  */  
    8. public interface Command {  
    9.     /** 
    10.      * 执行命令 
    11.      * @return  
    12.      */  
    13.     public Object execute();  
    14. }  
     
    1. package com.flysnow.injection.command;  
    2.   
    3. /** 
    4.  * 一个异步处理命令的实现 
    5.  * @author 飞雪无情 
    6.  * 
    7.  */  
    8. public class AsyncCommand implements Command {  
    9.   
    10.     /* (non-Javadoc) 
    11.      * @see com.flysnow.lookup.command.Command#execute() 
    12.      */  
    13.     public Object execute() {  
    14.         //返回自身实例,是为了测试的时候好看出每次返回的不是同一个实例  
    15.         return this;  
    16.     }  
    17.   
    18. }  


    Bean配置文件如下: 

     
    1. <?xml version="1.0" encoding="UTF-8"?>  
    2. <beans xmlns="http://www.springframework.org/schema/beans"  
    3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    4.     xsi:schemaLocation="http://www.springframework.org/schema/beans  
    5.         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">  
    6.         <!-- 通过scope="prototype"界定该bean是多例的 -->  
    7.         <bean id="asyncCommand" class="com.flysnow.injection.command.AsyncCommand" scope="prototype"></bean>  
    8.         <bean id="commandManager" class="com.flysnow.injection.CommandManager">  
    9.         </bean>  
    10. </beans>  


    以上主要是单例Bean commandManager的process()方法需要引用一个prototype(非单例)的bean,所以在调用process的时候先通过createCommand方法从容器中取得一个Command,然后在执行业务计算,代码中有注释,很简单。 
    测试类如下: 

     
    1. package com.flysnow.injection;  
    2.   
    3. import org.junit.Before;  
    4. import org.junit.Test;  
    5. import org.springframework.context.ApplicationContext;  
    6. import org.springframework.context.support.ClassPathXmlApplicationContext;  
    7.   
    8. import com.flysnow.injection.CommandManager;  
    9.   
    10. public class TestCommandManager {  
    11.     private ApplicationContext context;  
    12.     @Before  
    13.     public void setUp() throws Exception {  
    14.         context=new ClassPathXmlApplicationContext("beans.xml");  
    15.     }  
    16.   
    17.     @Test  
    18.     public void testProcess() {  
    19.         CommandManager manager=context.getBean("commandManager", CommandManager.class);  
    20.         System.out.println("第一执行process,Command的地址是:"+manager.process());  
    21.         System.out.println("第二执行process,Command的地址是:"+manager.process());  
    22.     }  
    23.   
    24. }  


    可以通过控制台输出看到两次的输出借中的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配置文件如下: 

     
    1. public abstract class CommandManager {  
    2.     //模拟业务处理的方法  
    3.     public Object process(){  
    4.         Command command=createCommand();  
    5.         return command.execute();  
    6.     }  
    7.     //获取一个命令  
    8.     protected abstract Command createCommand();  
    9. }  
     
    1. <bean id="commandManager" class="com.flysnow.injection.CommandManager">  
    2.             <lookup-method name="createCommand" bean="asyncCommand"/>  
    3.         </bean>  


    运行测试,控制台打印出的两个Command的地址不一样,说明我们实现了。 
    <lookup-method>标签中的name属性就是commandManager Bean的获取Command实例(AsyncCommand)的方法,也就createCommand方法,bean属性是要返回哪种类型的Command的,这里是AsyncCommand。 
    这里的createCommand方法就成为被注入方法,他的定义形式必须为: 

     
    1. <public|protected> [abstract] <return-type> theMethodName(no-arguments);  


    被注入方法不一定是抽象的,如果被注入方法是抽象的,动态生成的子类(这里就是动态生成的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传入)例:

     
    1. public class ContextAwareBean implements ApplicationContextAware {  
    2.     protected static final Log log = LogFactory.getLog(AnotherBean.class);  
    3.     private String anotherBeanName;  
    4.     private ApplicationContext applicationContext;  
    5.      
    6.     public String getAnotherBeanName() {  
    7.         return anotherBeanName;  
    8.     }  
    9.     public void setAnotherBeanName(String anotherBeanName) {  
    10.         this.anotherBeanName = anotherBeanName;  
    11.     }  
    12.     public void process() {  
    13.         log.info("process applicationContext " + applicationContext);  
    14.         AnotherBean anotherBean = createAnotheBean();  
    15.         anotherBean.doSth();  
    16.          
    17.     }  
    18.     protected AnotherBean createAnotheBean() {  
    19.      
    20.     return this.applicationContext.getBean(anotherBeanName, AnotherBean.class);  
    21.     }  
    22.     public void setApplicationContext(ApplicationContext applicationContext){  
    23.         log.info("setApplicationContext " + applicationContext);  
    24.         this.applicationContext = applicationContext;  
    25.     }  
    26. }  
    27.  public class AnotherBean {  
    28.     protected static final Log log = LogFactory.getLog(AnotherBean.class);  
    29.     public String doSth(){  
    30.         log.info("AnotherBean.doSth");  
    31.         return "do something";  
    32.     }  
    33. }  
    34.   
    35. <bean id="AnotherBean" class="com.test.spring.di.mtddi.AnotherBean"  scope="prototype"/>  
    36.      
    37.     <bean id="ContextAwareBean" class="com.test.spring.di.mtddi.ContextAwareBean" >  
    38.         <property name="anotherBeanName" value="AnotherBean"/>  
    39.     </bean>  

    2,方法注入:在Bean A中定义一个方法,返回类型是Bean B,在配置文件中通过"lookup-method"告诉Spring动态覆盖该方法,并返回Bean B的一个实例:

     
    1. public  abstract class ReplacedBean {  
    2. protected static final Log log = LogFactory.getLog(ReplacedBean.class);  
    3.      
    4.     public void process() {  
    5.          
    6.         AnotherBean anotherBean = createAnotheBean();  
    7.         anotherBean.doSth();  
    8.          
    9.     }  
    10.     protected abstract AnotherBean createAnotheBean();  
    11.      
    12. }  
    13.   
    14. <bean id="AnotherBean" class="com.test.spring.di.mtddi.AnotherBean"  scope="prototype"/>  
    15.   
    16. <bean id="ReplacedBean" class="com.test.spring.di.mtddi.ReplacedBean" >  
    17.         <lookup-method name="createAnotheBean" bean="AnotherBean"/>  
    18.     </bean>  

    客户端代码:

     
    1. public class MtddiClient {  
    2.     private static BeanFactory factory;  
    3.     private static ApplicationContext ctx;  
    4.     static {  
    5.         Resource resource = new ClassPathResource("conf/mtddiAppcontext.xml");  
    6.         factory = new XmlBeanFactory(resource);  
    7.          
    8.         ctx = new ClassPathXmlApplicationContext("conf/mtddiAppcontext.xml");  
    9.     }  
    10.     /** 
    11.      * @param args 
    12.      */  
    13.     public static void main(String[] args) {  
    14.         /*不能通过bean factory的方式得到bean 
    15.         ContextAwareBean bean = (ContextAwareBean) factory.getBean("ContextAwareBean"); 
    16.         bean.process(); 
    17.         */  
    18.         //ContextAwareBean 只能从ApplicationContext获得bean  
    19.         //ContextAwareBean bean = (ContextAwareBean) ctx.getBean("ContextAwareBean");  
    20.         //bean.process();  
    21.                   
    22.         ReplacedBean bean1 = (ReplacedBean) factory.getBean("ReplacedBean");  
    23.         bean1.process();  
    24.     }  
    25. }  

    *对于实现ApplicationContextAware的Bean,必须用 ApplicationContext的getBean方法.对于方法注入(lookup-method方式):用BeanFactory和ApplicationContext的getBean都可以.如果要用BeanFactory,应该实现BeanFactoryAware:

     
    1. public class BeanFactoryAwareBean implements BeanFactoryAware {  
    2.     protected static final Log log = LogFactory.getLog(BeanFactoryAwareBean.class);  
    3.     private String anotherBeanName;  
    4.      
    5.     private BeanFactory beanFactory;  
    6.      
    7.     public String getAnotherBeanName() {  
    8.         return anotherBeanName;  
    9.     }  
    10.     public void setAnotherBeanName(String anotherBeanName) {  
    11.         this.anotherBeanName = anotherBeanName;  
    12.     }  
    13.     public void process() {  
    14.         log.info("process beanFactory " + beanFactory);  
    15.         AnotherBean anotherBean = createAnotheBean();  
    16.         anotherBean.doSth();        
    17.     }  
    18.     protected AnotherBean createAnotheBean() {  
    19.      
    20.     return this.beanFactory.getBean(anotherBeanName, AnotherBean.class);  
    21.     }  
    22.      
    23.     @Override  
    24.     public void setBeanFactory(BeanFactory beanFactory) throws BeansException {  
    25.         this.beanFactory = beanFactory;         
    26.     }  
    27. }  

    两种方法的比较:理论上来讲,第二种方法更体现了IoC的思想,而且在bean类里面没有依赖到Spring,只是一个POJO.客户端在使用它的时候可以是依靠Spring配置(lookup-method)来使用,也可以通过提供实现类来完成调用.

    四:小结 
    Lookup方法注入干净整洁,易于扩展,更符合Ioc规则,所以尽量采用这种方式。

    http://blog.csdn.net/u011225629/article/details/45460047

  • 相关阅读:
    WSGI-mini-web框架服务器
    pymsql链接数据库报错2003的解决过程记录
    官方案例--Survival Shoot(一)
    Python-Day5修改haproxy配置文件
    Python-Day4实现简单的shell sed替换功能
    Python-列表嵌套字典-名片管理系统(适合刚学习完字典和列表的同学练手)
    Python-列表的运用-名字管理系统
    Vi 编写一个剪刀石头布游戏
    Python-Day3 购物系统
    Python-Day2三级菜单
  • 原文地址:https://www.cnblogs.com/itommy/p/10610345.html
Copyright © 2011-2022 走看看