【问题标题】:inject prototype into singleton(java configuration + annotation)将原型注入单例(java配置+注解)
【发布时间】:2015-10-25 10:35:15
【问题描述】:

我被问到关于将原型注入单例的面试问题。我很难回答,现在我正在尝试研究这个。

我已经写了以下代码(pring boot)

豆 1:

@Service
@Scope(value = "prototype")
public class MyValidator {
}

豆 2:

@Service
public class ValidatorHolder {

    @Autowired
    MyValidator myValidator;

    public MyValidator getMyValidator() {
        return myValidator;
    }
}

配置:

@SpringBootApplication
@Configuration
@ComponentScan("com.example.domain")
public class DemoApplication {


    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
        ApplicationContext context = new AnnotationConfigApplicationContext(DemoApplication.class);
        ValidatorHolder validatorHolder1 = (ValidatorHolder) context.getBean("validatorHolder");
        ValidatorHolder validatorHolder2 = (ValidatorHolder) context.getBean("validatorHolder");
        System.out.println("=====================================");
        System.out.println(validatorHolder1.getMyValidator() == validatorHolder2.getMyValidator());
        System.out.println("=====================================");

    }
}

此代码返回 true。

正如我在阅读 article link 时所理解的那样 可以配置返回false。

如何在我的代码中执行? (无 xml)

附言

我尝试重写文章中的代码:

   <bean id="validatorHolder" class="com.example.domain.ValidatorHolder">
        <property name="myValidator" ref="validator"/>
    </bean>

    <bean id="validator" scope="prototype" class="com.example.domain.MyValidator">
        <!-- This instructs the container to proxy the current bean-->
        <aop:scoped-proxy/>
    </bean>

在 main 方法中,我编写了以下代码:

ApplicationContext xmlContext = new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
ValidatorHolder validatorHolder21 = (ValidatorHolder) xmlContext.getBean("validatorHolder");
ValidatorHolder validatorHolder22 = (ValidatorHolder) xmlContext.getBean("validatorHolder");
System.out.println("=====================================");
System.out.println(validatorHolder21.getMyValidator() == validatorHolder22.getMyValidator());
System.out.println("=====================================");

反正我看到true

附言2

让我们研究 Sean Patrick Floyd 的答案(范围代理,b))

我使用以下主要方法类:

@SpringBootApplication
@ComponentScan("com.example.domain")
public class DemoApplication {


    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);

        ApplicationContext context = new AnnotationConfigApplicationContext(DemoApplication.class);
        ValidatorHolder validatorHolder1 = (ValidatorHolder) context.getBean("validatorHolder");
        ValidatorHolder validatorHolder2 = (ValidatorHolder) context.getBean("validatorHolder");
        System.out.println("=====================================");
        System.out.println(validatorHolder1.getMyValidator() == validatorHolder2.getMyValidator());
        System.out.println("=====================================");         
}

当我运行应用程序时 - 我看到了

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'validatorHolder' defined in file [D:\freelance\demo\target\classes\com\example\domain\ValidatorHolder.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.domain.ValidatorHolder]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.example.domain.ValidatorHolder.<init>()
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1099)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1044)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:755)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:759)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:689)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:321)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:969)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:958)
    at com.example.DemoApplication.main(DemoApplication.java:20)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.domain.ValidatorHolder]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.example.domain.ValidatorHolder.<init>()
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:85)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1092)
    ... 20 common frames omitted
Caused by: java.lang.NoSuchMethodException: com.example.domain.ValidatorHolder.<init>()
    at java.lang.Class.getConstructor0(Class.java:3074)
    at java.lang.Class.getDeclaredConstructor(Class.java:2170)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:80)
    ... 21 common frames omitted

附言 3

P.S.2 问题与构造函数上错过的@Autowired 有关

修复此问题后

System.out.println(validatorHolder1.getMyValidator() == validatorHolder2.getMyValidator());

返回真

但如果有点替换 MyValidator 代码:

@Service
@Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class MyValidator {
    Object object = new Object();

    public Object getObject() {
        return object;
    }

    public void setObject(Object object) {
        this.object = object;
    }
}

 System.out.println(validatorHolder1.getMyValidator() == validatorHolder2.getMyValidator());

是的

 System.out.println(validatorHolder1.getMyValidator().getObject() == validatorHolder2.getMyValidator().getObject());

甚至

System.out.println(validatorHolder1.getMyValidator().getObject() == validatorHolder1.getMyValidator().getObject());

【问题讨论】:

  • @Sean Patrick Floyd 提供的链接有误吗?
  • 顺便说一句:如果你曾经需要在单例中注入原型,你几乎肯定应该改变你的设计。
  • 不,没有错。它推荐与手册相同的技术:查找方法注入
  • @Sean Patrick Floyd 我不明白如何在我的示例中应用这种技术

标签: java spring scope spring-boot spring-java-config


【解决方案1】:

您误解了方法注入技术。您需要使您的 bean 抽象才能工作:

public class MyValidator {}

public abstract class ValidatorHolder {
    public abstract MyValidator getMyValidator();
}

现在您可以按如下方式在 XML 中定义 bean:

<bean class="com.somepackage.MyValidator" scope="prototype" />
<bean class="com.somepackage.ValidatorHolder">
    <lookup-method name="getMyValidator" bean="myValidator" />
</bean>

在这种情况下,Spring 将创建一个ValidatorHolder 的匿名子类,它会在每个被调用的类型中返回原型 bean(一个新副本)。

使用带注释的服务类,查找方法注入是不可能的,但这是您可以使用@Configuration 类的方法:

@Configuration
public class MyConfiguration{

    @Bean
    @Scope("prototype")
    public MyValidator myValidator(){
        return new MyValidator();
    }

    @Bean
    public ValidatorHolder validatorHolder(){
        return new ValidatorHolder(){
            @Override public MyValidator getMyValidator(){
                return myValidator();
            }
        };
    }
}

在这种情况下,您正在自己创建ValidatorHolder 的子类,您可以清楚地看到发生了什么。

但任何一个版本都只有在你将 bean 和提供者方法抽象化时才有效。

最后,定义 spring bean 有三种不同的方式:

  • XML
  • 带有组件的注释类(例如@Service@Component) 扫描
  • @Configuration 类与 @Bean 方法。

在您的示例代码中,您混合了这三种样式,这几乎不是一个好主意。选择一种技术并坚持下去。


关于作用域代理,这可以在所有三种 bean 注册技术中实现。

a) XML

public class MyValidator {}

public class ValidatorHolder {
    private MyValidator myValidator;
    public void setMyValidator(MyValidator myValidator){
        this.myValidator = myValidator;}
    public MyValidator getMyValidator();
}

<bean class="com.somepackage.MyValidator" scope="prototype" />
<bean class="com.somepackage.ValidatorHolder">
    <aop:scoped-proxy />
</bean>

b) 带注释的服务类

@Service @Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class MyValidator {}

@Service
public class ValidatorHolder {
    @Autowired
    public ValidatorHolder(MyValidator myValidator){
        this.myValidator=myValidator;
    }
    private final MyValidator myValidator;
    public MyValidator getMyValidator(){ return myValidator; };
}

c) @Configuration 类,类似 XML 版本的 Bean 类

@Configuration
public class MyConfiguration{

    @Bean
    @Scope("prototype")
    public MyValidator myValidator(){
        return new MyValidator();
    }

    @Bean
    public ValidatorHolder validatorHolder(){
        return new ValidatorHolder(myValidator());
    }

}

请注意,所有代理解决方案将始终返回相同的对象,即代理。但底层功能将委托给不同的对象。通过将此代码添加到 MyValidator 来尝试一下:

private int counter = 1;
public int counter(){
    return counter ++;
}

现在,与您调用此代码的频率无关:

validatorHolder.getMyValidator().counter();

它总是会返回1

【讨论】:

  • 好的。你是对的,查找方法有效!但我对aop代理有误解。你能提供aop代理的完整例子吗?例如@Kalpesh Soni 不起作用
  • 我试过 b) 带注释的服务类 - 不起作用 - 主题中的详细信息
  • @gstackoverflow 是的,我错过了构造函数上的@Autowired 注释
  • 看起来很神奇。请澄清代理注入是如何工作的
  • 代理只是一个对象,它是实际 bean 的占位符
【解决方案2】:
@Service
public class ValidatorHolder {

@Autowired
ApplicatioContext context;

public MyValidator getMyValidator() {
    return context.getBean(MyValidator.class);
}
}

另请阅读http://shekhargulati.com/tag/method-injection/

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-13
  • 1970-01-01
  • 1970-01-01
  • 2017-02-14
相关资源
最近更新 更多