在Spring将原型Bean注入到Singleton Bean中
本文展示了在Spring中将原型Bean注入到Singleton Bean中的不同方法,这样,每次Singleton Bean需要它时,都会创建原型范围的Bean的新实例。
Singleton bean与原型bean合作时出现的问题
假设单例作用域的bean对原型作用域的bean有依赖性。 Spring IOC容器仅创建一次Singleton Bean,因此只有一次机会来设置属性。每次需要一个单例Bean时,就不能将其作用域范围内注入原型原型的Bean(新的Bean实例)。
这是一个在将原型bean注入单例bean中时理解问题的示例。有两个类MsgManager和MsgHandler。 MsgManager配置为单例bean,其中MsgHandler是通过原型范围定义的。
消息管理器
在MsgManager类中,有一个对MsgHandler实例的依赖关系,该实例随后用于调用方法。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class MsgManager { @Autowired private MsgHandler msgHandler; public void handleRequest(){ msgHandler.handleMessage(); } }
消息处理程序
MsgHandler配置为具有原型范围。
import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component @Scope("prototype") public class MsgHandler { MsgHandler(){ System.out.println("In MsgHandler Constructor"); } public void handleMessage(){ System.out.println("Handling message"); } }
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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.theitroad" /> </beans>
我们可以将以下类与main方法一起使用以读取配置并调用bean方法。
public class App { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("appcontext.xml"); MsgManager bean1 = context.getBean("msgManager", MsgManager.class); // calling method two times bean1.handleRequest(); MsgManager bean2 = context.getBean("msgManager", MsgManager.class); bean2.handleRequest(); context.close(); } }
输出:
19:43:15.557 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'msgManager' In MsgHandler Constructor Handling message Handling message
如我们所见,"在MsgHandler构造函数中"仅显示一次,这意味着仅创建了一个MsgHandler实例,即使具有原型范围,也不会按预期的那样创建两个实例。
将原型bean注入到singleton bean中
现在,当我们看到一个问题时,在Singleton bean属性中仅设置一次,因此具有原型范围的bean也仅设置一次,并且每次使用相同实例而不是创建新实例,让我们将重点转移到解决方案上在Spring框架中将原型bean注入到singleton bean中。
1.通过实现ApplicationContextAware接口
获取新bean的一种方法是实现ApplicationContextAware接口,并使用该上下文在类中获取bean。
通过ApplicationContextAware接口的实现,MsgManager类的更新如下。
@Component public class MsgManager implements ApplicationContextAware{ private ApplicationContext applicationContext; public void handleRequest(){ getMsgHandler().handleMessage(); } // This method returns instance public MsgHandler getMsgHandler() { return applicationContext.getBean("msgHandler", MsgHandler.class); } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
现在,当我们运行App类时,我们将获得以下输出:
In MsgHandler Constructor Handling message In MsgHandler Constructor Handling message
但是,这不是一个好的解决方案,因为业务代码知道并耦合到了Spring Framework。
2.使用查找方法注入
查找方法注入是容器重写容器管理的Bean上的方法的能力,以返回容器中另一个命名Bean的查找结果。 Spring框架通过使用从CGLIB库生成字节码来动态生成覆盖该方法的子类来实现此方法注入。
@Component public class MsgManager{ private ApplicationContext applicationContext; private MsgHandler msgHandler; public void handleRequest(){ msgHandler = getMsgHandler(); msgHandler.handleMessage(); } @Lookup public MsgHandler getMsgHandler() { return null; } }
Spring框架通过扩展MsgManager类来动态生成子类,并实现带有@Lookup注释的注释方法以返回查找结果。
输出:
In MsgHandler Constructor Handling message In MsgHandler Constructor Handling message
如我们所见,构造函数被调用了两次,这意味着每次都会创建一个MsgHandler的新实例。
3.使用作用域代理
将原型bean注入单例bean的另一种方法是使用范围代理。
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.stereotype.Component; @Component @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) public class MsgHandler { MsgHandler(){ System.out.println("In MsgHandler Constructor"); } public void handleMessage(){ System.out.println("Handling message"); } }
进行此更改后,容器将创建用于连接的MsgHandler的代理对象。该代理对象从定义的作用域机制(原型,请求,会话等)中获取真实的MsgHandler类对象。
有两种创建代理的模式
ScopedProxyMode.TARGET_CLASS –创建基于类的代理(使用CGLIB)。
ScopedProxyMode.INTERFACES –创建一个JDK动态代理,以实现目标对象的类公开的所有接口。
消息管理器
@Component public class MsgManager{ @Autowired private MsgHandler msgHandler; public void handleRequest(){ msgHandler.handleMessage(); } }
输出:
In constructor of ClassB In MsgHandler Constructor Handling message In MsgHandler Constructor Handling message
4.使用ObjectFactory接口
还有一个功能接口ObjectFactory,它定义一个工厂,该工厂在调用时可以返回Object实例(共享或者独立)。使用此接口,我们可以封装一个通用工厂,该工厂在每次调用时返回某个目标对象的新实例(原型)。
@Component public class MsgManager{ @Autowired private ObjectFactory<MsgHandler> msgHandlerObjectFactory; public void handleRequest(){ msgHandlerObjectFactory.getObject().handleMessage(); } }
在此,每次调用msgHandlerObjectFactory.getObject()方法都会返回MsgHandler bean的新实例(具有原型范围)。
@Component @Scope(value = "prototype") public class MsgHandler { MsgHandler(){ System.out.println("In MsgHandler Constructor"); } public void handleMessage(){ System.out.println("Handling message"); } }
输出:
In MsgHandler Constructor Handling message In MsgHandler Constructor Handling message