将原型注入单例(java配置+注解)
inject prototype into singleton(java configuration + annotation)
我被问到关于将原型注入单例的面试问题。我很难回答,现在我正在努力研究这个问题。
我写了下面的代码(弹簧启动)
豆子 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("=====================================");
}
}
此代码为真。
据我阅读时的理解article link
可以配置为 return false.
我可以在我的代码中做什么? (没有 xml)
P.S.
我尝试重写文章中的代码:
<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
P.S.2
让我们研究 Sean Patrick Floyd 的答案(范围代理,b))
我使用以下主要方法class:
@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.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
P.S.3
P.S.2 问题与构造函数
上遗漏的 @Autowired
有关
解决这个问题后
System.out.println(validatorHolder1.getMyValidator() == validatorHolder2.getMyValidator());
return是真的
但如果稍微替换一下 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());
错误
@Service
public class ValidatorHolder {
@Autowired
ApplicatioContext context;
public MyValidator getMyValidator() {
return context.getBean(MyValidator.class);
}
}
你误解了方法注入技术。您需要将 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
的匿名子 class,return 是原型 bean(一个新副本)它被调用的每个类型.
使用带注释的服务 classes,查找方法注入是不可能的,但这是使用 @Configuration
可以做到的 classes:
@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
的子class,您可以清楚地看到发生了什么。
但是只有当您将 bean 和提供程序方法抽象化时,这两个版本才有效。
最后一点,可以通过三种不同的方式定义 spring beans:
- XML
- 注释 classes(例如
@Service
、@Component
)与一个组件
扫描
@Configuration
class 使用 @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) 注释服务 class
@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
classes,Bean classes 就像 XML 版本
@Configuration
public class MyConfiguration{
@Bean
@Scope("prototype")
public MyValidator myValidator(){
return new MyValidator();
}
@Bean
public ValidatorHolder validatorHolder(){
return new ValidatorHolder(myValidator());
}
}
请注意所有代理解决方案总是return同一个对象,即代理。但是底层功能将委托给不同的对象。通过将此代码添加到 MyValidator 进行尝试:
private int counter = 1;
public int counter(){
return counter ++;
}
现在,与您调用此代码的频率无关:
validatorHolder.getMyValidator().counter();
它将永远return 1
。
我被问到关于将原型注入单例的面试问题。我很难回答,现在我正在努力研究这个问题。
我写了下面的代码(弹簧启动)
豆子 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("=====================================");
}
}
此代码为真。
据我阅读时的理解article link 可以配置为 return false.
我可以在我的代码中做什么? (没有 xml)
P.S.
我尝试重写文章中的代码:
<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
P.S.2
让我们研究 Sean Patrick Floyd 的答案(范围代理,b))
我使用以下主要方法class:
@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.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
P.S.3
P.S.2 问题与构造函数
上遗漏的@Autowired
有关
解决这个问题后
System.out.println(validatorHolder1.getMyValidator() == validatorHolder2.getMyValidator());
return是真的
但如果稍微替换一下 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());
错误
@Service
public class ValidatorHolder {
@Autowired
ApplicatioContext context;
public MyValidator getMyValidator() {
return context.getBean(MyValidator.class);
}
}
你误解了方法注入技术。您需要将 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
的匿名子 class,return 是原型 bean(一个新副本)它被调用的每个类型.
使用带注释的服务 classes,查找方法注入是不可能的,但这是使用 @Configuration
可以做到的 classes:
@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
的子class,您可以清楚地看到发生了什么。
但是只有当您将 bean 和提供程序方法抽象化时,这两个版本才有效。
最后一点,可以通过三种不同的方式定义 spring beans:
- XML
- 注释 classes(例如
@Service
、@Component
)与一个组件 扫描 @Configuration
class 使用@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) 注释服务 class
@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
classes,Bean classes 就像 XML 版本
@Configuration
public class MyConfiguration{
@Bean
@Scope("prototype")
public MyValidator myValidator(){
return new MyValidator();
}
@Bean
public ValidatorHolder validatorHolder(){
return new ValidatorHolder(myValidator());
}
}
请注意所有代理解决方案总是return同一个对象,即代理。但是底层功能将委托给不同的对象。通过将此代码添加到 MyValidator 进行尝试:
private int counter = 1;
public int counter(){
return counter ++;
}
现在,与您调用此代码的频率无关:
validatorHolder.getMyValidator().counter();
它将永远return 1
。