前言
使用spring框架时,当在单例对象中注入一个原生对象是,注入的原生会变成单例对象,本文就这一问题介绍几种 解决方案。
在Spring Framework官方文档的第一章第四节方法注入中,最后一个点方法注入,详细描绘了这个问题,也为此提出了解决方案。截图如下:
1、还原问题
1.1 创建原生对象User
User类代码:
@Component
@Scope("prototype")
public class User {
public User() {
System.out.println ("Constrctor of user");
}
public void query(){
System.out.println ("useer `s hashCode"+this.hashCode ());
}
}
把User类交给spring IOC容器管理,将其设置为原生对象,创建无参构造方法,打印一行文字,再添加一个普通的方法,打印当前对象的哈希值。
1.2 测试原生对象
测试类代码:
public class MainTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext (AppConfig.class);
annotationConfigApplicationContext.getBean (User.class).query ();
annotationConfigApplicationContext.getBean (User.class).query ();
annotationConfigApplicationContext.getBean (User.class).query ();
annotationConfigApplicationContext.getBean (User.class).query ();
}
}
测试结果:
通过运行结果可以看出,user的构造方法执行了五次,第一次是spring IoC容器创建时调用的,其他四次是在测试类中获取user对象时调用的。user对象的哈希值打印了四次,四次打印的值都不同,所以可以证明四次实行的对象不是同一个对象,在测试类中使用annotationConfigApplicationContext对象的getBean方法获取一次对象spring IOC容器就会创建一个user对象。
1.3 创建一个单例类,引入原生对象
创建一个ServiceImpl类,注入User类,添加一个方法,调用user类的query方法。
ServiceImpl类代码:
@Component
public class ServiceImpl {
@Autowired
User user;
public void query() {
user.query ();
}
}
1.4 测试单例类
测试类代码:
public class MainTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext (AppConfig.class);
annotationConfigApplicationContext.getBean (ServiceImpl.class).query ();
annotationConfigApplicationContext.getBean (ServiceImpl.class).query ();
annotationConfigApplicationContext.getBean (ServiceImpl.class).query ();
annotationConfigApplicationContext.getBean (ServiceImpl.class).query ();
}
}
测试结果:
通过测试结果可以看出,user类的构造方法值执行了一次,通过serviceImpl对象打印四次user对象的哈希值都相同,证明四次调用的对象都是同一对象。
结果分析:以为ServiceImpl类是单例的,所以在Spring IoC容器创建该对象时只创建了一个ServiceImpl实例对象,同时创建了一个user实例对象,只后将user实例对象注册到ServiceImpl实例对象中。因为ServiceImpl类是单例的,所以在测试方法中使用getBean方法获取ServiceImpl对象时,annotationConfigApplicationContext对象直接从Spring IoC容器中获取,得到的ServiceImpl对象由于已经在初始化过程中已注入了user对象,所以可直接使用,整个过程中没有通过annotationConfigApplicationContext对象获取user对象,因此ServiceImpl对象中user的原生属性失效。
2、解决方案
2.1 实现 ApplicationContextAware
接口直接在Spring 容器中获取原生对象
在spring framework官方文档中,1.4.6的方法注入中给了我们关于这个问题的解决方案。如图:
可以是单例对对象的类实现 ApplicationContextAware接口,该接口提供了setApplicationContext方法,传入了applicationContext对象,及Spring上下文对象,程序员可以通过该对象直接获取原型类对象,来解决上述问题。
具体解决方法为,在ServiceImpl中引入一个ApplicationContext对象,继承ApplicationContextAware接口,重写setApplicationContext方法,将该方法传入的ApplicationContext对象赋值给ServiceImpl类中的ApplicationContext对象。最后在query方法中使用ApplicationContext对象获取user对象。修改后代码如下:
@Component
public class ServiceImpl implements ApplicationContextAware {
ApplicationContext applicationContext;
// @Autowired
User user;
public void query() {
applicationContext.getBean (User.class).query ();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
运行测试类结果为:
可以看到已经解决了上述问题。这种解决方案的弊端在于必须实现ApplicationContextAware接口,spring framework的侵入性较强。
这种方法的核心在于使用applicationContext对象重新获取原生对象,因为applicationContext对象本身就在Spring IoC容器中,所以可以直接将applicationContext对象注入到单例对象中。修改后的ServiceImpl来对象为:
@Component
public class ServiceImpl {
@Autowired
ApplicationContext applicationContext;
// @Autowired
User user;
public void query() {
applicationContext.getBean (User.class).query ();
}
}
2.2 Lookup方法注入
将单例对象类修改成抽象了类,加入一个返回值类型为原型类的抽象方法,将该方法加上一个Lookup注解,这样spring framework就会利用CGLIB代理方法实现这个类,返回一个原生对象。spring官网中的截图如下:
修改后的ServiceImpl抽象类代码如下:
@Component
public abstract class ServiceImpl {
// @Autowired
User user;
@Lookup
public abstract User getPrototypUser();
public void query() {
getPrototypUser ().query ();
}
}
因为spring framework是用代理的方法实现这个类,程序员也可以将上述的抽象方法改成返回值为空的方法,也是可以同样的效果。这种方法在官网上没有提到,但博主经过试验也可是可以,希望大家谨慎采用。更改后的ServiceImpl的代码如下:
@Component
public class ServiceImpl {
// @Autowired
User user;
@Lookup
public User getPrototypUser(){
return null;
}
public void query() {
getPrototypUser ().query ();
}
}