Spring 方法注入 非单例bean的调用

本文介绍了在Spring框架中如何解决singleton类型的bean调用prototype类型的bean的问题,重点讲解了两种方法:通过实现ApplicationContextAware接口以编程方式解决及使用Lookup方法注入。
Spring 方法注入 非单例bean的调用
在大部分情况下,容器中的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。看下面的例子。
Java代码
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。
Java代码
package com.flysnow.injection.command;

/**
* 一个命令接口
* @author 飞雪无情
*
*/
public interface Command {
/**
* 执行命令
* @return
*/
public Object execute();
}

package com.flysnow.injection.command; /** * 一个命令接口 * @author 飞雪无情 * */ public interface Command { /** * 执行命令 * @return */ public Object execute(); }
Java代码
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代码
<?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,然后在执行业务计算,代码中有注释,很简单。
测试类如下:
Java代码
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配置文件如下:
Java代码
public abstract class CommandManager {
//模拟业务处理的方法
public Object process(){
Command command=createCommand();
return command.execute();
}
//获取一个命令
protected abstract Command createCommand();
}

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

<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方法就成为被注入方法,他的定义形式必须为:
Java代码
<public|protected> [abstract] <return-type> theMethodName(no-arguments);

<public|protected> [abstract] <return-type> theMethodName(no-arguments);
被注入方法不一定是抽象的,如果被注入方法是抽象的,动态生成的子类(这里就是动态生成的CommandManager的子类)会实现该方法。否则,动态生成的子类会覆盖类里的具体方法。为了让这个动态子类得以正常工作,需要把CGLIB的jar文件放在classpath里,这就是我们引用cglib包的原因。还有,Spring容器要子类化的类(CommandManager)不能是final的,要覆盖的方法(createCommand)也不能是final的。
四:小结
Lookup方法注入干净整洁,易于扩展,更符合Ioc规则,所以尽量采用这种方式。
### Bean 的作用域 在 Spring 框架中,作用域(Singleton Scope)是默认的作用域。当一个 Bean 被定义为时,Spring 容器会确保该 Bean 在整个应用上下文中仅创建一个实[^3]。这意味着,无论有多少个组件请求该 BeanSpring 容器都会返回同一个实。 这种作用域适用于无状态的 Bean,即 Bean 的状态不会因为不同的调用而改变。如,服务层的组件(如 `UserService`)通常被定义为,因为它们通常不持有与特定请求相关的状态。 ### Bean 的实现原理 Spring 容器通过其核心的依赖注入(DI)机制来管理 Bean 的生命周期。具体来说,Spring 容器在启动时会读取配置(如 XML 配置文件或注解配置),并根据这些配置信息创建 Bean 的定义。对于作用域的 Bean,容器会在第一次请求该 Bean 时创建其实,并将其缓存起来。后续对该 Bean 的请求将直接返回缓存中的实,而不是重新创建一个新的实[^2]。 这种机制通过 Spring 的 `BeanFactory` 或其子接口 `ApplicationContext` 来实现。`BeanFactory` 提供了管理 Bean 生命周期的功能,而 `ApplicationContext` 在此基础上提供了更多的企业级功能,如国际化支持、事件传播等。 ### Bean 的线程安全性 需要注意的是,虽然 Spring 容器保证了 Bean 的唯一性,但这并不意味着 Bean 本身是线程安全的。如果 Bean 包含了可变的状态(即成员变量),那么在多线程环境下可能会出现并发访问的问题。因此,在设计 Bean 时,应尽量避免使用可变状态,或者采取适当的同步措施来保证线程安全[^4]。 ### 示代码 以下是一个简的示,展示了如何在 Spring 中定义一个 Bean: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; @Configuration public class AppConfig { @Bean @Scope("singleton") public UserService userService() { return new UserServiceImpl(); } } public interface UserService { void performAction(); } public class UserServiceImpl implements UserService { private int count = 0; @Override public void performAction() { count++; System.out.println("Action performed " + count + " times."); } } ``` 在这个示中,`userServiceImpl` 被定义为作用域的 Bean。无论有多少个组件请求 `userService`,Spring 容器都会返回同一个实。 ### Bean 的应用场景 Bean 适用于那些不需要为每次请求创建新实的场景。如: - **服务层组件**:如用户服务、订服务等,这些组件通常不持有与特定请求相关的状态。 - **工具类组件**:如日志记录器、配置管理器等,这些组件的功能是通用的,且不依赖于特定的请求上下文。 - **共享资源管理**:如数据库连接池、缓存管理器等,这些组件需要在整个应用中共享同一个实。 通过合理使用作用域,开发者可以更高效地管理对象的生命周期,减少不必要的资源消耗,同时提高应用的性能和可维护性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值