java 012_Java系列012】条条大路通罗马

8a198938097f3fa9ab41f9ccbce8b9f2.png

上一节中,我们学习了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,之后我执行后看下图:

63671c9ddfacf9453067768b3b198224.png

我们可以看到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增加下面配置。

我们来看看执行结果是不是和上图一样呢?

ea5132b7b1b9816babffe8f7fa7b4cce.png

很明显结果是一样的,这说明这条“路”也是能达到目的。但是需要注意的是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实例都不一样。

59aedfbe4302236b02194f70587ec38e.png

留意两个红色框,以及前部分日志,我们发现已经达到了预期的目的,这说明此“路”也是能完成我们singleton bean请求prototype bean时,不会出现只有“单例”的bean。

这里还有一个有意思的就是,如果我们把TransferRecords的@Lazy注解去掉,执行后,我们看看下图的结果,你会发现,singleton bean请求prototype bean时,真的只有“单例”的bean。这就是上一讲中提到的“钱”不对的问题。

2c531a6abb0fc140f7f370ebae2a204f.png

除了使用@Lazy注解能使bean延迟被初始化之外,还能使用元素的lazy-init特性。使用@Lazy注解可以减少代码的侵入性,同时注解的方式相比xml配置来说更为简洁,但是xml配置实现可以让开发者更利于理解spring容器、bean初始化原理。

总结

文中我们使用了ApplicationContextAware生命周期接口、和@Lazy注解分别向你讲述了如何达到“Singleton bean从Spring容器中获取一个prototype bean新实例的不同方法”这个目的。当然我们也额外提到另外两种方法,在这里只是抛砖引玉,有兴趣的朋友可以自己尝试实现。今天的代码已上传到GitHub。

思考和讨论

1、上文说了有5条“大路”可以达到目的,除了此之外还有没有其他方案呢?

2、我们说到bean的初始化延迟,其实就是Spring容器延迟自动装配依赖项,那bean的自动装配是如何实现的呢?

欢迎留言与我分享和指正!也欢迎你把这篇文章分享给你的朋友或同事,一起交流。

感谢您的阅读,我们下节再见!

扫码关注我们,与君共进

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值