将原型 Bean 注入单例 Bean

10

我是Spring的新手,正在尝试理解"将原型bean注入到单例bean中"的概念。据我理解,在单例模式下,每个Spring IoC容器只有一个实例,无论你检索多少次。

在下面的示例中,我开发了一个单例bean,并在其中引用原型bean:

validator.validate(requestId);因为private RequestValidator validator还没有被实例化。
<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;
    }
}

请求验证器.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;
    }
}

现在当我调用主方法时,我看到以下输出: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,因此构造函数不会再次被调用。所以原型作用域的validator只充当单例模式。

对我来说:我期望每当我从应用程序上下文中获取requestProcessor时,它都将与new validator连接,因为我们声明了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" />

如果我调用我的主方法,我会看到以下的输出和错误

这里执行了代码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个回答

8

仅在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实例。
查找方法是每次需要原型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实例。

您可以在文档中找到更多详细信息。


谢谢。现在我至少理解了这个概念,但我看不到你的代码正在工作。它产生了错误“Exception in thread "main" java.lang.AbstractMethodError: com.injection.testing.RequestProcessor.createValidator()Lcom/injection/testing/RequestValidator; at com.injection.testing.RequestProcessor.handleRequest(RequestProcessor.java:12) at com.injection.testing.MainDemo.main(MainDemo.java:13)”。 - user5778069
我为这个问题创建了一个单独的线程:http://stackoverflow.com/questions/41460585/lookup-method-injecting-a-prototype-bean-into-a-singleton-bean-issue。请在此回复。 - user5778069
@user4567570 你必须使用抽象方法createValidator作为查找方法,而不是getValidator - Ken Bekov

0

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

因为任何对RequestProcessor Bean的后续引用都将访问上下文中相同的RequestProcessor实例 - 并且它已经完全构造 - 因此永远不会调用创建RequestValidator的新实例。尽管您的验证器的作用域是原型 - 在上面的示例中 - 您只需要一个副本 - 当您创建RequestProcessor单例时。

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


那么这意味着 RequestValidator 的原型作用域不是原型,而是单例。对吗?你能否在查找方法上回答一下,我发现它的工作不正常。 - user5778069
1
不,RequestValidator的范围是原型。这意味着每次请求RequestValidator Bean的实例时,您都会获得一个唯一的实例。在上面的示例中,您只请求了一个实例 - 在RequestProcessor Bean中。如果您创建另一个Bean定义 - 比如RequestBlahBean - 并注入一个RequestValidator,那么那将是RequestValidator Bean的第二个实例 - 与注入在RequestProcessor单例中的实例不同。 - Bjorn Loftis
请确认 - 由于requestProcessorSingleton,而validatorprototype,因此当Spring为每个注入创建新的原型bean时(这将只发生一次,因为requestProcessorSingleton),但原型bean不会在每次调用其方法时都被创建。对吗?如何解决第二个查询的错误? - user5778069
确认。Ken的例子说明了您第二个查询的解决方案。AbstractMethodError来自bean定义和抽象方法名称之间的名称不匹配,例如“...lookup-method getValidator...”和抽象方法的名称,例如createValidator()。 - Bjorn Loftis
非常感谢。Scoped Proxies也是解决的方法吗? - user5778069

0
在大多数应用场景中,容器中的大多数bean都是单例的。当一个单例bean需要与另一个单例bean或非单例bean进行协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖关系。当bean的生命周期不同时,就会出现问题。假设单例bean A 需要在每次调用A方法时使用非单例(原型)bean B。容器仅创建一次单例bean A,因此只有一次机会设置属性。容器无法在每次需要时为bean A提供bean B的新实例。
解决方案是放弃一些控制反转。您可以通过实现ApplicationContextAware接口并通过向容器发出getBean("B")调用来使bean A意识到容器,以便在每次bean A需要它时请求(通常是新的)bean B实例。
采用这种方法,我们的业务逻辑与Spring容器(应用程序上下文)耦合在一起,这看起来并不是一个好的解决方案。这种情况的替代方案是查找方法注入

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配置支持,您可以创建CommandManager的子类,在该子类中覆盖抽象的createCommand()方法,以便以查找新的(原型)命令对象的方式进行:
@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();
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接