Java series 012] all roads lead to Rome

Posted by tarlejh on Sun, 17 May 2020 12:25:09 +0200

Hello! Today is Saturday. Today we talk about the different methods of Singleton bean to get a new instance of prototype bean from Spring container.

In the previous section, we learned about the scope of spring beans, and also learned that if the scope is not used correctly, it will cause data confusion. After we adjusted the scope of TransferService to prototype, we solved the problem. What are the "big ways" to solve the problem? Let's take a look at the other three ways to solve the problem and what are their advantages and disadvantages.

ApplicationContextAware interface

Familiar with the bean lifecycle in ApplicationContext, you should know that ApplicationContextAware is a lifecycle interface. This interface defines a single method setApplication, which provides an instance of the ApplicationContext object for the implementation bean. That is, if a bean implements the interface, the setApplication method will be called at the appropriate time in the bean's life cycle. Let's look at the following code:

 1@Slf4j
 2public class TransferServiceImpl implements TransferServiceApplicationContextAware {
 3
 4    private TransferDao transferDao;
 5    private ApplicationContext applicationContext;
 6
 7    public TransferServiceImpl(TransferDao transferDao) {
 8        this.transferDao = transferDao;
 9        log.info("initialization 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}

Of course, you need to modify the scope of TransferService to Singleton in bean configuration. After execution, I will see the following figure:

We can see that the TransferRecords can be initialized twice, compared with the previous one, which shows that we can achieve the goal by implementing the ApplicationContextAware interface.

In the application context, the ApplicationContextAware interface is useful if a bean needs to access other beans. However, the disadvantage of implementing this interface is that it couples bean classes with the Spring Framework. This "path" follows, but at a cost, so we can achieve decoupling through injection technology in the following two ways.

< lookup method > element

When a bean class defines a bean lookup method, which can be concrete or abstract, and its return type represents a bean, Spring will provide an implementation for this method according to the instructions of the < lookup method > element, that is, get the bean instance from the Spring container and return it. Let's see how the code is implemented.

We implement the following code in an abstract way.

 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("initialization 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}

In the transferBeans.xml file, add the following configuration to the bean of transferService.

1 <lookup-method bean="transferRecords" name="getTransferRecordsBean" />

Let's see if the execution result is the same as the above figure?

Obviously, the result is the same, which shows that this "road" can also achieve the goal. However, it should be noted that bean classes and bean lookup methods cannot be defined as final.

In addition, you can use @ Lookup annotation, which is the annotation version of the < Lookup method > element.

@Lazy notes

Let's review the scope of beans. If beans in the singleton scope are initialized immediately, that is, instantiate them when creating Spring containers. The prototype scope is initialized when the bean is requested. If you want singleton beans to be created late, you can use the @ Lazy annotation. Of course, we use @ Lazy annotation here to solve the problem that a singleton bean requests a prototype bean, and the bean can only be initialized once.

We add an applicationContext.xml file, as follows:

1<?xml version="1.0" encoding="UTF-8"?>
2
3<beans xmlns="http://www.springframework.org/schema/beans"
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   <context:component-scan base-package="com.bj.spring"></context:component-scan>
9</beans>

Then the adjusted codes of TransferDao, TransferRecords and TransferServiceImpl are as follows:

 1@Repository
 2public class TransferDao {
 3    public TransferDao(){
 4        log.info("initialization 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("initialization 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}

Again, we modify the main method.

 1public static void main(String[] args) {
 2        ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
 3        log.info("Start request transferService,@Lazy annotation");
 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("request transferService end,@Lazy annotation");
10    }

After the adjustment is completed, we will see if we can achieve the following two goals:

  • TransferRecords is not initialized at startup.

  • The TransferRecords instances of Calling are not the same.

Pay attention to the two red boxes, as well as the previous logs, and we find that the expected purpose has been achieved, which shows that this "path" can also complete our single ton bean request prototype bean, and there will not be only "Singleton" beans.

Another interesting thing here is that if we remove the @ Lazy annotation of TransferRecords, after execution, let's look at the results below. You will find that when a singleton bean requests a prototype bean, there is really only a "Singleton" bean. This is the problem of "money" mentioned in the previous lecture.

In addition to using @ Lazy annotation to initialize the bean delay, you can also use the Lazy init feature of the < bean > element. Using @ Lazy annotation can reduce the intrusion of code, and the way of annotation is simpler than xml configuration. However, the implementation of xml configuration can help developers understand the principle of spring container and bean initialization.

summary

In this paper, we use the ApplicationContextAware life cycle interface, < lookup method > and @ Lazy annotation to tell you how to achieve the purpose of "different methods for Singleton bean to get a new instance of prototype bean from Spring container". Of course, we also mentioned the other two methods. Here, we are just trying to attract talents. Interested friends can try to achieve it by themselves. Today's code has been uploaded to GitHub.

Thinking and discussion

1. As mentioned above, there are five "roads" to achieve the goal. Is there any other plan besides this?

2. When we talk about the initialization delay of beans, in fact, it is the Spring container that delays the automatic assembly of dependencies. How is the automatic assembly of beans implemented?

Welcome to share and correct with me! You are also welcome to share this article with your friends or colleagues.

Thank you for reading. See you next time!

Scan the code to follow us and work with you

Topics: Programming Spring xml encoding github