将原型bean注入单例bean

时间:2017-01-04 06:43:42

标签: spring

我是Spring的新手并试图理解这个概念" 将原型bean注入单个bean "。根据我的理解在单例中,每个Spring IoC容器只有一个实例,无论你检索它多少次validator.validate(requestId);,因为仍未private RequestValidator validator实例化。 我开发了以下示例,在单例bean中,我给出了如下所示的原型bean的引用:

<bean id="requestProcessor" class="com.injection.testing.RequestProcessor">
        <property name="validator" ref="validator" />
</bean>

<bean id="validator" class="com.injection.testing.RequestValidator" scope="prototype" />

RequestProcessor.java

public class RequestProcessor {
    private RequestValidator validator;

    public RequestProcessor(){
        System.out.println("Constructor:: RequestProcessor instance created!");
    }

    public void handleRequest(String requestId){
        System.out.println("Request ID : "+ requestId);
        validator.validate(requestId);
    }

    public RequestValidator getValidator() {
        return validator;
    }

    public void setValidator(RequestValidator validator) {
        this.validator= validator;
    }
}

RequestValidator.java

public class RequestValidator {
    private List<String> errorMessages = new ArrayList<String>();

    public RequestValidator() {
        System.out.println("Constructor:: RequestValidator instance created!");
    }

    // Validates the request and populates error messages
    public void validate(String requestId){
        System.out.println("RequestValidator :"+requestId);
    }

    public List<String> getErrorMessages() {
        return errorMessages;
    }
}

现在当我调用main方法时,我看到以下输出: 的 MainDemo.java

public class MainDemo {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

        RequestProcessor processor = (RequestProcessor) context.getBean("requestProcessor");
        processor.handleRequest("1212");
        System.out.println("------------------------");
        processor.handleRequest("1213");
    }
}

输出是:

Constructor:: RequestProcessor instance created!
Constructor:: RequestValidator instance created!
Request ID : 1212
RequestValidator :1212
------------------------
Request ID : 1213
RequestValidator :1213

现在查看输出,看起来第二次调用processor.handleRequest("1213"); bean没有实例化,而是已经实例化的bean被使用,这就是为什么构造函数不会被再次调用。所以原型bean validator仅作为单例bean使用。

对我来说:我希望当我从应用程序上下文中获取requestProcessor时,它将与new validator连接,因为我们声明验证器bean是原型范围。但这不会发生。

如何解决?我的理解是否正确?

另一种方式:

<!-- Lookup way  -->
    <bean id="requestProcessor" class="com.injection.testing.RequestProcessor" >
        <lookup-method name="getValidator" bean="validator" />
    </bean>

    <bean id="validator" class="com.injection.testing.RequestValidator" scope="prototype" />

如果我调用我的main方法,我会在下面看到输出+错误:这里执行代码validator.validate(requestId);private RequestValidator validator;没有实例化,为什么会出现空指针异常。

我已在以下代码中显示:

public class MainDemo {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

        RequestValidator requestValidator = (RequestValidator) context.getBean("validator");

        RequestProcessor processor = (RequestProcessor) context.getBean("requestProcessor");
        processor.handleRequest("1212");
        System.out.println("------------------------");
        processor.handleRequest("1213");
    }
}

现在我看到以下错误:

Constructor:: RequestProcessor instance created!
Constructor:: RequestValidator instance created!
Request ID : 1212
Exception in thread "main" java.lang.NullPointerException
    at com.injection.testing.RequestProcessor.handleRequest(RequestProcessor.java:12)
    at com.injection.testing.MainDemo.main(MainDemo.java:14)

4 个答案:

答案 0 :(得分:6)

当Spring上下文启动时,注入只发生一次。如果bean具有prototype范围,Spring将为每次注入创建新的原型bean。但每次调用其方法时都不会创建原型bean。让我们考虑下一个例子:

<bean id="firstRequestProcessor" class="com.injection.testing.RequestProcessor">
        <property name="validator" ref="validator" />
</bean>

<bean id="secondRequestProcessor" class="com.injection.testing.RequestProcessor">
        <property name="validator" ref="validator" />
</bean>


<bean id="validator" class="com.injection.testing.RequestValidator" scope="prototype" />

在这种情况下,两个RequestProcessor bean都有自己的RequestValidator bean实例。

Lookup方法是方法,每次需要原型bean的新实例时都应该调用。最好使这个方法abstract,因为无论如何Spring会自动覆盖这个方法。例如:

public class abstract RequestProcessor {

    public void handleRequest(String requestId){
        System.out.println("Request ID : "+ requestId);
        RequestValidator validator = createValidator(); //here Spring will create new instance of prototype bean
        validator.validate(requestId);
    }

    protected abstract RequestValidator createValidator();
}

注意,createValidator返回RequestValidator的实例并且没有任何参数。此外,您不需要私有类变量validator。在这种情况下,bean的配置如下所示:

<bean id="requestProcessor" class="com.injection.testing.RequestProcessor" >
    <lookup-method name="createValidator" bean="validator" />
</bean>

<bean id="validator" class="com.injection.testing.RequestValidator" scope="prototype" />

现在每次调用createValidator方法时,Spring都会创建validator bean的新实例。

您可以在documentation中找到更多详情。

答案 1 :(得分:0)

当spring创建上下文时,它将实例化RequestProcessor的一个实例。在该实例的实例化期间,创建RequestValidator的实例并将其注入RequestProcessor中。

因为对RequestProcessor Bean的任何后续引用都将在上下文中访问RequestProcessor的同一实例 - 并且它已经完全构造 - 所以永远不会调用创建RequestValidator的新实例。虽然你的Validator的范围是原型 - 在上面的例子中 - 你只需要一个副本 - 当你创建RequestProcessor单例时。

对于问题的第二部分 - 您误解了查找方法的使用。 Spring允许您覆盖RequestProcessor上的方法 - 因此当您调用requestProcessor.getValidator()时,spring将返回该RequestValidator的新实例。但是 - 在构造requestProcessor实例期间 - 您从未初始化验证器字段。 spring在实例化期间也没有注入验证器。因此NullPointerException。

答案 2 :(得分:0)

  

查找方法注入

     

如前所述,查找方法注入是一项高级功能,   您应该很少使用。在单例作用域的情况下很有用   bean依赖于原型作用域的bean。为此使用Java   配置类型提供了实现此目标的自然方法   模式。

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();
}
  

使用Java配置支持,您可以创建以下子类   覆盖抽象createCommand()方法的CommandManager   以某种方式查找新的(原型)命令对象:

@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // inject dependencies here as required
    return command;
}

@Bean
public CommandManager commandManager() {
    // return new anonymous implementation of CommandManager with command() overridden
    // to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}

答案 3 :(得分:0)

在大多数应用场景中,容器中的大多数bean是单例的。当单例Bean需要与另一个单例Bean协作或非单例Bean需要与另一个非单例Bean协作时,通常可以通过将一个Bean定义为另一个Bean的属性来处理依赖性。当bean的生命周期不同时会出现问题。假设单例bean A需要使用非单例(原型)bean B,也许在对A的每个方法调用上都使用它。容器仅创建一次单例bean A,因此只有一次机会来设置属性。每次需要一个容器时,容器都无法向Bean A提供新的Bean B实例。

一个解决方案是放弃某些控制反转。您可以通过实现ApplicationContextAware接口,并通过对容器进行getBean(“ B”)调用来使bean A知道该容器,每次豆A需要它时都请求一个(通常是新的)bean B实例。

通过这种方法,我们的业务逻辑与Spring conatiner(应用程序上下文)结合在一起,这似乎不是一个好的解决方案。替代方法是查找方法注入