在Spring将原型Bean注入到Singleton Bean中

时间:2020-01-09 10:44:29  来源:igfitidea点击:

本文展示了在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