`
liyixing1
  • 浏览: 971459 次
  • 性别: Icon_minigender_1
  • 来自: 江西上饶
社区版块
存档分类
最新评论

不同作用域与依赖的冲突,方法注入

阅读更多

在大部分情况下,容器中的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实例。

上述问题的一个解决办法就是放弃控制反转。通过实现BeanFactoryAware接口让bean A能够感知bean 容器,并且在需要的时候通过使用getBean("B")方式
向容器请求一个新的bean B实例
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// lots of Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

public class CommandManager implements BeanFactoryAware {

   private BeanFactory beanFactory;

   public Object process(Map commandState) {
      // grab a new instance of the appropriate Command
      Command command = createCommand();
      // set the state on the (hopefully brand new) Command instance
      command.setState(commandState);
      return command.execute();
   }

   // the Command returned here could be an implementation that executes asynchronously, or whatever
   protected Command createCommand() {
      return (Command) this.beanFactory.getBean("command"); // notice the Spring API dependency
   }

   public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
      this.beanFactory = beanFactory;
   }
}
上面的例子显然不是最好的,因为业务代码和Spring Framework产生了耦合。方法注入,作为Spring IoC容器的一种高级特性,可以以一种干净的方法来处理这种情况。

Lookup方法注入

这究竟是不是方法注入……

有点像Tapestry 4.0的页面,写上abstract属性,Tapestry会在运行时用具体实现将其覆盖。
Lookup方法注入利用了容器的覆盖受容器管理的bean方法的能力,从而返回指定名字的bean实例。在上述场景中,Lookup方法注入适用于原型bean。Lookup方法注入的内部机制是Spring利用了CGLIB库在运行时生成二进制代码功能,通过动态创建Lookup方法bean的子类而达到复写Lookup方法的目的。

如果你看下上个代码段中的代码(CommandManager类),Spring容器动态覆盖了createCommand()方法的实现。你的CommandManager类不会有一点对Spring的依赖,在下面这个例子中也是一样的:

package fiona.apple;

// no more Spring imports!

public abstract class CommandManager {

   public Object process(Object commandState) {
      // grab a new instance of the appropriate Command interface
      Command command = createCommand();
      // set the state on the (hopefully brand new) Command instance
      command.setState(commandState);
      return command.execute();
   }

    // okay... but where is the implementation of this method?
   protected abstract Command createCommand();
}
在包含被注入方法的客户类中(此处是CommandManager),此方法的定义必须按以下形式进行:

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


如果方法是抽象的,动态生成的子类会实现该方法。否则,动态生成的子类会覆盖类里的具体方法。让我们来看个例子:

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
  <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
  <lookup-method name="createCommand" bean="command"/>
</bean>
在上面的例子中,标识为commandManager的bean在需要一个新的command bean实例时,会调用createCommand方法。重要的一点是,必须将command部署为prototype。当然也可以指定为singleton,如果是这样的话,那么每次将返回相同的command bean实例!

我做了个例子
目录结构






需要asm.jar和cglib.jar,cglib的版本会依赖对应的版本号,如果发现了asm和cglib的错误,可以换版本试试
package com.liyixing.spring.service;

public interface IAccountService {
public void getAccount();
}

package com.liyixing.spring.service;

public interface IDownloadService {
public void getDownload();
}
package com.liyixing.spring.service.imp;

import com.liyixing.spring.service.IDownloadService;

public class DownloadService implements IDownloadService {


public void getDownload() {
System.out.println("getDownload()");
}

}

package com.liyixing.spring.service.imp;

import com.liyixing.spring.service.IAccountService;
import com.liyixing.spring.service.IDownloadService;

public abstract class AccountService implements IAccountService {
private IDownloadService downloadService;

public IDownloadService getDownloadService() {
return downloadService;
}

public void setDownloadService(IDownloadService downloadService) {
this.downloadService = downloadService;
}

public void getAccount() {
downloadService = look();
downloadService.getDownload();
System.out.println("getAccount()");
System.out.println("down is " + downloadService);
System.out.println();
}

public abstract IDownloadService look();

}
这个类是抽象类,它的look方法也是抽象方法;

beans.xml
<?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-2.5.xsd">
<bean id="accountService" class="com.liyixing.spring.service.imp.AccountService">
<lookup-method name="look" bean="downloadService"/>
</bean>
<bean id="downloadService" class="com.liyixing.spring.service.imp.DownloadService"
scope="prototype">
</bean>
</beans>
<lookup-method name="look" bean="downloadService"/>这里设置了方法,并设置的bean属性这个属性表示将会以在look方法,返回downloadService这个类,类似的在look的动态生成的代码会有
beanFactory.getBean("downloadService");方法存在。



请注意,为了让这个动态子类得以正常工作,需要把CGLIB的jar文件放在classpath里。另外,Spring容器要子类化的类不能是final的,要覆盖的方法也不能是final的。同样的,要测试一个包含抽象方法的类也稍微有些不同,你需要自己编写它的子类提供该抽象方法的桩实现。最后,作为方法注入目标的bean不能是序列化的(serialized)。

现ServiceLocatorFactoryBean(在org.springframework.beans.factory.config包里)的用法和ObjectFactoryCreatingFactoryBean的有些相似,不同的是它允许你指定自己的lookup接口,不一定非要用Spring的lookup接口,比如ObjectFactory。要详细了解这种方法请参考ServiceLocatorFactoryBean的Javadocs(它的确减少了对Spring的耦合)。


能够在HTTP request或者Session(甚至自定义)作用域中定义bean固然很好,但是Spring IoC容器除了管理对象(bean)的实例化,同时还负责协作者(或者叫依赖)的实例化。如果你打算将一个Http request范围的bean注入到另一个bean中,那么需要注入一个AOP代理来替代被注入的作用域bean。也就是说,你需要注入一个代理对象,该对象具有与被代理对象一样的公共接口,而容器则可以足够智能的从相关作用域中(比如一个HTTP request)获取到真实的目标对象,并把方法调用委派给实际的对象。

<aop:scoped-proxy/> 不能和作用域为singleton或prototype的bean一起使用。为singleton bean创建一个scoped proxy将抛出BeanCreationException异常。


让我们看一下将相关作用域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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <!-- a HTTP Session-scoped bean exposed as a proxy -->
    <bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
         
          <!-- this next element effects the proxying of the surrounding bean -->
<aop:scoped-proxy/>
    </bean>
   
    <!-- a singleton-scoped bean injected with a proxy to the above bean -->
    <bean id="userService" class="com.foo.SimpleUserService">
   
        <!-- a reference to the proxied
'userPreferences' bean -->
        <property name="userPreferences" ref="userPreferences"/>

    </bean>
</beans>

要创建这样的代理,只需要在Bean作用域定义中增加一个<aop:scoped-proxy/>子元素(为了让容器可以有效的使用基于类(而不是接口)的代理,你需要在classpath中加入CGLIB包, 并且要使用Appendix A, XML Schema-based configuration配置方式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
这引入的xsi的aopxsd文件
)。为什么在request,session, globalSession 和 '自定义作用域' 需要<aop:scoped-proxy/>元素?在下面配置片段中可以找到解释(注意下面 'userPreferences' Bean定义是不完整的):

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
从上述配置中可以很明显的看到singleton bean userManager被注入了一个指向HTTP Session作用域bean userPreferences的引用。singleton userManager bean会被容器仅实例化一次,并且其依赖(即userPreferences bean)也仅被注入一次。这意味着,userManager在理论上只会操作同一个userPreferences对象,即原先被注入的那个bean。而注入一个HTTP Session作用域的bean作为依赖,有违我们的初衷。因为我们想要的只是一个userManager对象,在它进入一个HTTP Session生命周期时,我们希望去使用一个HTTP Session的userPreferences对象。

当注入某种类型对象时,该对象实现了和UserPreferences类一样的公共接口(即UserPreferences实例)。并且不论我们底层选择了何种作用域机制(HTTP request、Session等等),容器都会足够智能的获取到真正的 UserPreferences对象,因此我们需要将该对象的代理注入到userManager bean中, 而userManager bean并不会意识到它所持有的是一个指向UserPreferences引用的代理。在本例中,当UserManager实例调用了一个使用UserPreferences对象的方法时,实际调用的是代理对象的方法。随后代理对象会从HTTP Session获取真正的UserPreferences对象,并将方法调用委派给获取到的实际的UserPreferences对象。

这就是当把request-, session-, 和 globalSession-scoped beans 注入到协作对象中时,需要以下的正确而完整的配置:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
   
<aop:scoped-proxy/>

</bean>

<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

选择创建代理的类型

默认情况下,当一个bean有<aop:scoped-proxy/>标记时,Spring容器将为它创建一个基于CGLIB的类代理,这意味着你需要 将CGLIB库添加到应用的classpath中。

注意:CGLIB代理仅仅拦截public方法的调用!对于非public的方法调用,不会对目标对象产生委托。

你可以将<aop:scoped-proxy/>的属性'proxy-target-class'设置为'false'来选择标准JDK推荐的基于接口的代理,这样就不需要在应用的classpath中增加额外的库。但是,这就意味着类必须实现至少一个接口。并且所有的协作者必须通过某一个 接口来引用bean。
  • 大小: 52.5 KB
  • 大小: 7.4 KB
分享到:
评论

相关推荐

    关于angularJs指令的Scope(作用域)介绍

    2. scope: true —— 创建一个新的隔离作用域,这个作用域会继承父作用域的属性和方法。不过,这个继承是单向的,即父作用域中的数据变化可以影响到子作用域,而子作用域中的数据变化不会影响到父作用域。这种配置...

    spring注解实现注入.zip

    在Spring框架中,注解注入是一种非常常用且强大的依赖注入(Dependency Injection,简称DI)方式。依赖注入是Spring的核心特性之一,它允许开发者在不直接创建对象的情况下,将依赖关系从对象代码中分离出来,提高了...

    build_your_own_angularjs

    它主要利用了几个核心概念来构建动态网页应用:双向数据绑定、依赖注入、指令、作用域(Scopes)、过滤器和模块等。 ####作用域(Scopes) - **作用域的概念:** 作用域是模型视图之间的桥梁,是应用数据和视图...

    AngularJS入门教程之XHR和依赖注入详解

    所有AngularJS内置的服务、作用域方法以及其他API都有一个'$'前缀。这是一个约定俗成的规则,旨在避免命名冲突。因此,开发者在命名自定义服务和模型时,应避免使用'$'前缀,以防止与AngularJS的内置API发生命名冲突...

    flex框架Pasley的学习手册

    基本用法包括定义绑定源和目标,同时避免冲突,例如通过指定作用域,以防止多个组件监听相同的数据。Pasley提供了全局、局部等多种作用域,以便于管理和组织绑定。 1. **持久化属性**:发布(publish)的属性可以...

    Spring面试题(最新版)-重点

    Spring的配置方式有XML和注解两种,XML配置中,可以定义Bean的属性、作用域、依赖关系等;注解配置则更简洁,通过在类或方法上添加注解实现配置。 依赖注入(Dependency Injection,DI)是Spring的重要特性,它允许...

    AngularJS学习笔记

    总的来说,这份AngularJS学习笔记系统地介绍了AngularJS的基本概念和特性,从基本的视图绑定、依赖注入、作用域,到高级的自定义模块、指令、路由管理等,为学习者提供了一份全面的学习材料。这份笔记记录了学习者的...

    AngularJs concepts详解及示例代码

    AngularJS的概念众多,包括组件、服务、依赖注入、作用域、数据绑定等。下面详细解释这些AngularJS的核心概念,并通过示例代码进行展示。 一、组件(Components) 组件是AngularJS应用程序中的基本构建块,它们负责...

    iOS代替单例的方法

    使用Block或Closure可以局部地管理对象,将对象的创建和使用限制在特定的作用域内。这有助于避免全局状态,减少潜在的冲突。 8. **KVO(Key-Value Observing)与通知中心(NotificationCenter)** 通过KVO或...

    Java面试题精选.docx

    Java是一种广泛使用的面向对象的... - 全局作用域(静态导入):在整个程序中都可见,但不推荐,可能导致命名冲突。 理解这些Java面试题的关键知识点有助于深入掌握Java开发和系统架构,并在面试中表现出专业水平。

    AngularJS中transclude用法详解

    这需要仔细的规划,以确保作用域的继承与内容的transclude不会相互冲突。 在AngularJS中使用transclude功能,不仅需要理解其基本概念,还需要掌握在实际应用中的技巧和注意事项。虽然transclude为AngularJS增加了...

    Spring中的自动装配有哪些限制?.docx

    对于复杂的应用,显式装配(如使用@Bean或@Autowired注解)可以提供更细粒度的控制,允许你指定bean之间的依赖关系,包括依赖的顺序、作用域等。 6. **性能影响**: 自动装配需要Spring容器进行额外的工作来识别和...

    spring面试总结

    Spring框架是Java开发中不可或缺的一部分,它以其强大的依赖注入(DI)和面向切面编程(AOP)功能闻名。在面试中,理解Spring的核心概念和技术是至关重要的。以下是对Spring框架的一些关键知识点的详细说明: 1. ...

    简述AngularJS相关的一些编程思想

    5. 服务和工厂:AngularJS的服务和工厂是管理和封装应用中数据和功能的组件,它们是依赖注入系统中的重要角色,允许在不同的控制器和指令间共享数据和方法。 接下来,详细解释上述提到的几个编程思想: - 数据绑定...

    spring注解开发--Bean加载控制.zip

    这与@Autowired配合使用,帮助解决多bean注入的冲突。 7. **@Profile** `@Profile`注解允许你在不同环境(如开发、测试、生产)中使用不同的bean配置。你可以指定bean应该在哪些配置环境下激活。 8. **@...

    spring注解大全和应用

    若请求参数名与方法参数名不一致,可以通过`value`属性指定映射关系。 7. **@ModelAttribute**:多用途注解,可标记在方法上,用于在处理请求前预填充模型数据,或者标记在方法参数上,从模型中提取数据。在控制器...

    第3章 前端面试技能拼第3章 前端面试技能拼

    - **作用域与闭包**:掌握变量的作用域规则,理解函数作用域和块级作用域,以及闭包的原理和应用。 - **原型与继承**:了解JavaScript的原型链机制,以及如何实现对象的继承。 - **事件与DOM操作**:熟悉DOM树的...

    spring framework reference 3.0

    - **Bean 概述**:Bean 是 Spring 容器管理的对象,具有一定的生命周期和作用域。 - **命名 Bean**:可以通过 ID 或名称来唯一标识一个 Bean。 - **别名**:可以在容器中为 Bean 定义别名,以便更容易地引用它。 ...

    (源码)基于webComponent的微前端框架简易实现.zip

    2. 利用自定义元素microapp作为容器,实现微应用的样式隔离和JS作用域隔离。 3. 采用发布订阅模式,实现主应用与微应用之间的数据交互。 ### 功能特点 1. 渲染子应用加载远程HTML,解析DOM获取JS、CSS静态资源完成...

    Spring面试题(2022最新版)-重点

    - **setter 方法注入**:通过 setter 方法注入依赖项。 - **字段注入**:直接在字段上使用注解来注入依赖项。 ### Spring Beans #### Spring Beans 定义 - **什么是 Spring Beans?** - Spring Beans 是由 ...

Global site tag (gtag.js) - Google Analytics