上一节中,我们学习了Spring bean的作用域,也了解了如果作用域使用不正确会带来数据错乱问题,我们将TransferService的作用域调整为prototype后解决了该问题,那我们还有哪些“大路”可以解决这个问题呢?下面我们一起来看看另外三种方式是如何解决该问题的,各有什么优缺点。
ApplicationContextAware接口
熟悉ApplicationContext中bean生命周期的你应该知道ApplicationContextAware是一个生命周期接口。该接口定义了单一的方法setApplication,它为实现bean提供了一个ApplicationContext对象的实例。也就是如果某个bean实现了该接口则setApplication方法会在bean的生命周期内适当的时候被调用。我们来看下面一段代码:
1@Slf4j
2public class TransferServiceImpl implements TransferService, ApplicationContextAware{
3
4 private TransferDao transferDao;
5 private ApplicationContext applicationContext;
6
7 public TransferServiceImpl(TransferDao transferDao){
8 this.transferDao = transferDao;
9 log.info("初始化transferService bean... ...");
10 }
11
12 @Override
13 public void tranfer(String accountId, String anotherAccountId){
15 TransferRecords transferRecords = applicationContext.getBean(TransferRecords.class);
16 transferRecords.setAccountId(accountId);
17 transferRecords.setAnotherAccountId(anotherAccountId);
18 transferDao.saveRecords(transferRecords);
19 }
20
21 @Override
22 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException{
23 this.applicationContext = applicationContext;
24 }
25}
当然记得要在bean配置中修改TransferService的作用域为Singleton,之后我执行后看下图:
我们可以看到TransferRecords已经可以被初始化2次,相比上一讲我们看到的只能被初始化1次,这说明我们通过实现ApplicationContextAware接口是可以达到目的。
在应用程序上下文中,如果一个bean需要访问其他的bean,那么ApplicationContextAware接口是有用的。但是,实现该接口的缺点在于它将bean类与Spring Framework相耦合。此“路”随通,但要付出代价,因此我们可以通过下面两种方法通过注入技术来达到解耦。
元素
当一个bean类定义了一个bean lookup方法,该方法可以是具体的方法或抽象的,其返回类型表示一个bean时,Spring将根据元素的指示为此方法提供实现,即从Spring容器中获取bean的实例并返回。我们一起来看看代码是如何实现的。
我们使用抽象方式实现如下代码。
1@Slf4j
2public abstract class TransferServiceImpl implements TransferService{
3 private TransferDao transferDao;
4
5 public TransferServiceImpl(TransferDao transferDao){
6 this.transferDao = transferDao;
7 log.info("初始化transferService bean... ...");
8 }
9
10 public abstract TransferRecords getTransferRecordsBean();
11
12 @Override
13 public void tranfer(String accountId, String anotherAccountId){
14 TransferRecords transferRecords = getTransferRecordsBean();
15 transferRecords.setAccountId(accountId);
16 transferRecords.setAnotherAccountId(anotherAccountId);
17 transferDao.saveRecords(transferRecords);
18 }
19}
transferBeans.xml文件中给transferService的bean增加下面配置。
1
我们来看看执行结果是不是和上图一样呢?
很明显结果是一样的,这说明这条“路”也是能达到目的。但是需要注意的是bean类和bean查找方法不能被定义为final。
另外,也可以通过@Lookup注解,该注解是元素的注解版本。
@Lazy注解
我们回顾下bean的作用域,如果是singleton范围的bean会被即时初始化,也就是创建Spring容器时实例化他们。而prototype范围的则在bean被请求时才初始化。如果想让singleton bean被延迟创建,则可以使用@Lazy注解。当然在这我们使用@Lazy注解是为了解决singleton bean请求prototype bean,bean只能被初始化一次的问题。
我们新增一个applicationContext.xml文件,如下:
1<?xml version="1.0" encoding="UTF-8"?>
2
3
4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
5 xmlns:context="http://www.springframework.org/schema/context"
6 xsi:schemaLocation="http://www.springframework.org/schema/beans
7 http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
8
9
而后TransferDao和TransferRecords、TransferServiceImpl调整后的代码如下:
1@Repository
2public class TransferDao{
3 public TransferDao(){
4 log.info("初始化transferDao bean... ...");
5 }
6}
7
8@Component
9@Lazy
10@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
11public class TransferRecords{
12 .....
13 public TransferRecords(){
14 log.info("初始化transferRecords bean... ...");
15 }
16}
17
18@Service
19public class TransferServiceImpl implements TransferService{
20 @Autowired
21 @Lazy
22 private TransferRecords transferRecords;
23 @Autowired
24 private TransferDao transferDao;
25
26 .....
27 @Override
28 public void initTransferRecord(){
29 log.info("@Lazy--->" + transferRecords);
30 }
31}
同样,我们修改main方法。
1public static void main(String[] args){
2 ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
3 log.info("开始请求transferService,@Lazy注解");
4 TransferService transferService_1 = context.getBean(TransferServiceImpl.class);
5 log.info("Calling --> transferRecords");
6 transferService_1.initTransferRecord();
7 log.info("Calling again --> transferRecords");
8 transferService_1.initTransferRecord();
9 log.info("请求transferService结束,@Lazy注解");
10 }
调整完成后,我们执行看看能否达到下面2个目的:
启动时,TransferRecords并不会初始化。
两次Calling的TransferRecords实例都不一样。
留意两个红色框,以及前部分日志,我们发现已经达到了预期的目的,这说明此“路”也是能完成我们singleton bean请求prototype bean时,不会出现只有“单例”的bean。
这里还有一个有意思的就是,如果我们把TransferRecords的@Lazy注解去掉,执行后,我们看看下图的结果,你会发现,singleton bean请求prototype bean时,真的只有“单例”的bean。这就是上一讲中提到的“钱”不对的问题。
除了使用@Lazy注解能使bean延迟被初始化之外,还能使用元素的lazy-init特性。使用@Lazy注解可以减少代码的侵入性,同时注解的方式相比xml配置来说更为简洁,但是xml配置实现可以让开发者更利于理解spring容器、bean初始化原理。
总结
文中我们使用了ApplicationContextAware生命周期接口、和@Lazy注解分别向你讲述了如何达到“Singleton bean从Spring容器中获取一个prototype bean新实例的不同方法”这个目的。当然我们也额外提到另外两种方法,在这里只是抛砖引玉,有兴趣的朋友可以自己尝试实现。今天的代码已上传到GitHub。
思考和讨论
1、上文说了有5条“大路”可以达到目的,除了此之外还有没有其他方案呢?
2、我们说到bean的初始化延迟,其实就是Spring容器延迟自动装配依赖项,那bean的自动装配是如何实现的呢?
欢迎留言与我分享和指正!也欢迎你把这篇文章分享给你的朋友或同事,一起交流。
感谢您的阅读,我们下节再见!
扫码关注我们,与君共进