在大部分情况下,容器中的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引用
- }
- }
下面定义Command接口和其实现类AsyncCommand。
- package com.flysnow.injection.command;
- /**
- * 一个命令接口
- * @author 飞雪无情
- *
- */
- public interface Command {
- /**
- * 执行命令
- * @return
- */
- public Object execute();
- }
- 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;
- }
- }
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>
以上主要是单例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());
- }
- }
可以通过控制台输出看到两次的输出借中的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();
- }
- <bean id="commandManager" class="com.flysnow.injection.CommandManager">
- <lookup-method name="createCommand" bean="asyncCommand"/>
- </bean>
运行测试,控制台打印出的两个Command的地址不一样,说明我们实现了。
<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的。
当一个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>
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>
客户端代码:
- 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();
- }
- }
*对于实现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;
- }
- }
两种方法的比较:理论上来讲,第二种方法更体现了IoC的思想,而且在bean类里面没有依赖到Spring,只是一个POJO.客户端在使用它的时候可以是依靠Spring配置(lookup-method)来使用,也可以通过提供实现类来完成调用.
四:小结
Lookup方法注入干净整洁,易于扩展,更符合Ioc规则,所以尽量采用这种方式。
相关推荐
在本篇“Spring.NET学习笔记16——事务管理Demo源码”中,我们将深入探讨Spring.NET的事务管理机制及其实际应用。 事务管理是软件开发中的关键部分,它确保数据库操作的一致性和完整性。Spring.NET通过其事务管理...
这份"Spring学习笔记+学习源码.zip"资源包含了深入学习Spring及其相关技术的知识点,以及实践代码,对提升Spring技能将大有裨益。 首先,我们来详细讨论Spring框架的主要组件和功能: 1. **依赖注入(Dependency ...
在本学习笔记中,我们将深入探讨JavaEE中的Spring框架,这是一个强大的、全面的企业级应用程序开发框架,它简化了Java开发并提供了丰富的功能。Spring的核心特性包括依赖注入(DI)、面向切面编程(AOP)以及对Java ...
《Spring 学习笔记三——深入理解Spring框架》 在我们的Spring学习旅程中,这篇笔记将带领大家更深入地探讨Spring框架的核心特性及其工作原理。Spring作为Java开发中广泛使用的轻量级框架,其强大的功能和灵活性...
《Spring 学习笔记五——深入理解Spring框架》 在软件开发领域,Spring框架是Java企业级应用开发的基石,其强大的功能和易用性使其成为开发者们的首选。本篇学习笔记将深入探讨Spring框架的核心概念和技术,帮助你...
在XML配置中,可以使用`<property>`标签指定需要注入的属性,通过`<ref>`标签引用其他bean,Spring容器会在适当的时候调用setter方法完成注入。 构造注入则是通过构造函数来设定依赖关系。在配置文件中,使用`...
7. **Spring学习笔记2——高级特性**: AOP(面向切面编程)、事件发布与监听、自定义拦截器、SpEL(Spring Expression Language)等Spring的高级特性可能会在这部分中被讲解。 8. **Spring学习笔记1——基础知识*...
在本篇"Spring学习笔记(四)"中,我们将深入探讨Spring框架的核心特性和使用方法,尤其是围绕源码解析和工具应用这两个主题。Spring是Java领域中最广泛应用的轻量级框架,它提供了丰富的功能来简化企业级应用的开发,...
【狂神说】Spring PDF学习总结笔记主要涵盖了Spring框架的核心概念、优点、组成部分以及相关扩展。Spring是一个由Rod Johnson创建的开源框架,旨在简化企业级应用开发的复杂性,它结合了众多现有技术,如SSH(Struct...
在本篇【原创】Mybatis学习笔记(一)——Spring集成Mybatis中,我们将探讨如何将流行的持久层框架Mybatis与Spring框架进行整合,以便在实际项目开发中实现灵活、高效的数据库操作。以下是对相关知识点的详细说明: ...
《MLDN学习笔记——Annotation》这篇博文主要探讨的是在编程领域中,特别是Java语言中,关于Annotation(注解)的深入理解和应用。Annotation是Java语言提供的一种元数据,它为程序提供了额外的信息,这些信息可以被...
在本篇“Spring学习笔记——HelloWorld”中,我们将探讨Spring框架的基础知识,以及如何创建一个简单的Spring应用程序。Spring是一个广泛使用的Java企业级应用开发框架,它提供了丰富的功能,包括依赖注入、AOP...
在本篇"spring学习笔记(六)"中,我们将深入探讨Spring框架的核心特性——自动装配(Autowired)。自动装配是Spring框架提供的一种方便的依赖注入方式,它能够自动为bean找到并设置其所需的依赖,极大地简化了应用的...
《Spring5学习笔记详解》 在当今的Java开发领域,Spring框架无疑是最为广泛使用的轻量级框架之一,尤其在Spring5版本发布后,其功能更加强大且易用。本篇文章将根据提供的文件名,深入探讨Spring5的核心概念、特性...
【Spring入门笔记】主要介绍了Spring框架的基础知识,包括Spring的核心概念、Bean的配置与作用域、依赖注入、SpringAop和SpringJdbc,以及事务声明。以下是对这些知识点的详细说明: 1. **什么是Spring?** Spring...
在本文中,我们将深入探讨Spring框架的核心概念——控制反转(Inversion of Control,IoC)和依赖注入(Dependency Injection,DI)。这些概念是Spring框架的基础,对于理解和掌握Spring的使用至关重要。 首先,让...
- 框架设计:Spring框架就大量使用反射来实现依赖注入。 - 动态代理:Java的`java.lang.reflect.Proxy`类可以利用反射创建动态代理对象。 - 测试工具:JUnit等测试框架利用反射来调用私有方法进行测试。 - ...
### Spring编程学习笔记知识点概述 #### 一、Spring框架简介 Spring框架是一个开源的轻量级Java开发框架,主要用于简化企业级应用的开发工作。它提供了全面的基础架构支持,包括但不限于依赖注入(Dependency ...
《Spring框架深度解析——基于传智播客左慈老师培训笔记》 在Java开发领域,Spring框架无疑是最具影响力和广泛使用的轻量级框架之一。它以其强大的功能、灵活的设计和丰富的生态系统,成为了企业级应用开发的首选。...
在本篇“Spring学习笔记(十五)——编程式事务例子”中,我们将深入探讨Spring框架中的编程式事务管理。在实际开发中,我们通常使用声明式事务管理,它基于AOP(面向切面编程)来简化事务处理。然而,有时为了更细...