引用

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

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

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

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

可以通过控制台输出看到两次的输出借中的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. public String getAnotherBeanName() {
  6. return anotherBeanName;
  7. }
  8. public void setAnotherBeanName(String anotherBeanName) {
  9. this.anotherBeanName = anotherBeanName;
  10. }
  11. public void process() {
  12. log.info("process applicationContext " + applicationContext);
  13. AnotherBean anotherBean = createAnotheBean();
  14. anotherBean.doSth();
  15. }
  16. protected AnotherBean createAnotheBean() {
  17. return this.applicationContext.getBean(anotherBeanName, AnotherBean.class);
  18. }
  19. public void setApplicationContext(ApplicationContext applicationContext){
  20. log.info("setApplicationContext " + applicationContext);
  21. this.applicationContext = applicationContext;
  22. }
  23. }
  24. public class AnotherBean {
  25. protected static final Log log = LogFactory.getLog(AnotherBean.class);
  26. public String doSth(){
  27. log.info("AnotherBean.doSth");
  28. return "do something";
  29. }
  30. }
  31. <bean id="AnotherBean" class="com.test.spring.di.mtddi.AnotherBean"  scope="prototype"/>
  32. <bean id="ContextAwareBean" class="com.test.spring.di.mtddi.ContextAwareBean" >
  33. <property name="anotherBeanName" value="AnotherBean"/>
  34. </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. public void process() {
  4. AnotherBean anotherBean = createAnotheBean();
  5. anotherBean.doSth();
  6. }
  7. protected abstract AnotherBean createAnotheBean();
  8. }
  9. <bean id="AnotherBean" class="com.test.spring.di.mtddi.AnotherBean"  scope="prototype"/>
  10. <bean id="ReplacedBean" class="com.test.spring.di.mtddi.ReplacedBean" >
  11. <lookup-method name="createAnotheBean" bean="AnotherBean"/>
  12. </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. ctx = new ClassPathXmlApplicationContext("conf/mtddiAppcontext.xml");
  8. }
  9. /**
  10. * @param args
  11. */
  12. public static void main(String[] args) {
  13. /*不能通过bean factory的方式得到bean
  14. ContextAwareBean bean = (ContextAwareBean) factory.getBean("ContextAwareBean");
  15. bean.process();
  16. */
  17. //ContextAwareBean 只能从ApplicationContext获得bean
  18. //ContextAwareBean bean = (ContextAwareBean) ctx.getBean("ContextAwareBean");
  19. //bean.process();
  20. ReplacedBean bean1 = (ReplacedBean) factory.getBean("ReplacedBean");
  21. bean1.process();
  22. }
  23. }

*对于实现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. private BeanFactory beanFactory;
  5. public String getAnotherBeanName() {
  6. return anotherBeanName;
  7. }
  8. public void setAnotherBeanName(String anotherBeanName) {
  9. this.anotherBeanName = anotherBeanName;
  10. }
  11. public void process() {
  12. log.info("process beanFactory " + beanFactory);
  13. AnotherBean anotherBean = createAnotheBean();
  14. anotherBean.doSth();
  15. }
  16. protected AnotherBean createAnotheBean() {
  17. return this.beanFactory.getBean(anotherBeanName, AnotherBean.class);
  18. }
  19. @Override
  20. public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
  21. this.beanFactory = beanFactory;
  22. }
  23. }

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

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

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

Spring学习笔记(10)——方法注入的更多相关文章

  1. Spring学习笔记之方法注入

    public  abstract class ReplacedBean {protected static final Log log = LogFactory.getLog(ReplacedBean ...

  2. Spring学习笔记1—依赖注入(构造器注入、set注入和注解注入)

    什么是依赖注入 在以前的java开发中,某个类中需要依赖其它类的方法时,通常是new一个依赖类再调用类实例的方法,这种方法耦合度太高并且不容易测试,spring提出了依赖注入的思想,即依赖类不由程序员 ...

  3. Spring.Net学习笔记(6)-方法注入

    一.开发环境 系统:win10 编译器:VS2013 二.涉及程序集 Spring.Core.dll 1.3.1 Common.Logging.dll 三.开发过程 1.项目结构 2.编写Mobile ...

  4. Spring学习笔记二:注入方式

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6774608.html  我们说,IOC的实现方式是依赖注入,也就是把被依赖对象赋值到依赖对象的成员属性.怎么做 ...

  5. 【Spring学习笔记-4】注入集合类List、Set、Map、Pros等

    概要: 当java类中含有集合属性:如List.Set.Map.Pros等时,Spring配置文件中该如何配置呢? 下面将进行讲解. 整体结构: 接口 Axe.java  package org.cr ...

  6. Spring MVC 学习笔记10 —— 实现简单的用户管理(4.3)用户登录显示全局异常信息

    </pre>Spring MVC 学习笔记10 -- 实现简单的用户管理(4.3)用户登录--显示全局异常信息<p></p><p></p>& ...

  7. Spring学习笔记(七)模拟实际开发过程的调用过程XML版-Setter方式注入

    模拟实际开发过程的调用过程XML版-Setter方式注入 源码获取github [TOC] 1.项目结构 2.jar包跟上个一样 3.重写set方法 UserServiceImpl.java 1234 ...

  8. Spring 源码学习笔记10——Spring AOP

    Spring 源码学习笔记10--Spring AOP 参考书籍<Spring技术内幕>Spring AOP的实现章节 书有点老,但是里面一些概念还是总结比较到位 源码基于Spring-a ...

  9. Spring源码学习笔记9——构造器注入及其循环依赖

    Spring源码学习笔记9--构造器注入及其循环依赖 一丶前言 前面我们分析了spring基于字段的和基于set方法注入的原理,但是没有分析第二常用的注入方式(构造器注入)(第一常用字段注入),并且在 ...

  10. 不错的Spring学习笔记(转)

    Spring学习笔记(1)----简单的实例 ---------------------------------   首先需要准备Spring包,可从官方网站上下载.   下载解压后,必须的两个包是s ...

随机推荐

  1. 一、表单和ajax中的post请求&&后台获取数据方法

    一.表单和ajax中的post请求&&后台获取数据方法 最近要做后台数据接收,因为前台传来的数据太过于混乱,所以总结了一下前台数据post请求方法,顺便写了下相对应的后台接收方法. 前 ...

  2. categorical_crossentropy VS. sparse_categorical_crossentropy

    From:https://jovianlin.io/cat-crossentropy-vs-sparse-cat-crossentropy/ categorical_crossentropy 和 sp ...

  3. 系统调用的API以及汇编代码实现

    作者:严哲璟 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 系统调用过程为 ...

  4. AngularJs双向绑定

    模型数据(Data) 模型是从AngularJS作用域对象的属性引申的.模型中的数据可能是Javascript对象.数组或基本类型,这都不重要,重要的是,他们都属于AngularJS作用域对象. An ...

  5. 同步与异步,阻塞与非阻塞 bio,nio,aio

    BIO.NIO和AIO的区别(简明版) 同步异步,阻塞非阻塞: https://www.zhihu.com/question/19732473   转载请注明原文地址:http://www.cnblo ...

  6. openlayers学习笔记(十三)— 异步调用JSON数据画点、文字标注与连线

    使用Openlayers 3实现调用本地json数据在地图上添加点.文字标注以及连线. 生成底图地图 首先得有一个地图作为底图,代码如下: let vectorSource = new ol.sour ...

  7. map简单用法

    let familyNames = []; familyNames = res.Data.map(item=> { return item.ArgText });

  8. 【leetcode】1014. Capacity To Ship Packages Within D Days

    题目如下: A conveyor belt has packages that must be shipped from one port to another within D days. The  ...

  9. boost array

    boost::array is similar to std::array, which was added to the standard library with C++11. With boos ...

  10. UiAutomator、UiAutomator2、Bootstrap的关系

    很多同学经过一段时间的学习之后都明白了Appium的基本原理,但是越学习到后面发现出现的很多陌生名词无法弄清楚其具体作用,今天这篇文章的目的就是为了让大家来弄懂三个高频名词:UiAutomator.U ...