In-depth Spring principles-4.Aware interface, initialization and destruction execution sequence, Scope domain

  • About the author: Hello everyone, I am Tudou Ni who loves cheese. I am a Java contestant in the 24th class of school admissions. Nice to meet you all.
  • Series of columns: Spring source code, JUC source code
  • If you feel that the blogger’s article is not bad, please support the blogger three times in a row.
  • The blogger is working hard to complete the 2023 plan: source code tracing to find out.
  • Contact information: nhs19990716, add me to the group, let’s learn together, make progress together, and fight against the Internet winter together

Article directory

    • Aware interface
    • Execution order of initialization and destruction
    • Scope

Aware interface

In fact, in the life cycle, the Aware interface also participates, as shown in the figure:

For example, the third step during initialization is actually calling the Aware related interface.

Take the common Aware interface as an example:

1.BeanNameAware mainly injects the name of the Bean

2.BeanFactoryAware is mainly used to inject the BeanFactory container

3.ApplicationContextAware mainly injects the ApplicationContext container

Next, let’s analyze it in the form of a piece of code.

GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("myBean", MyBean.class);

public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean {<!-- -->

    private static final Logger log = LoggerFactory.getLogger(MyBean.class);

    @Override
    public void setBeanName(String name) {<!-- -->
        // Call back the BeanNameAware interface before initialization
        log.debug("Current bean " + this + " name is:" + name);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {<!-- -->
        log.debug("Current bean " + this + " Container is:" + applicationContext);
    }

    @Override
    public void afterPropertiesSet() throws Exception {<!-- -->
        log.debug("Current bean " + this + "Initialization");
    }
}

context.refresh();
context.close();

Output:

[DEBUG] 11:36:41.083 [main] com.itheima.a06.MyBean - The current bean com.itheima.a06.MyBean@130161f7 is named: myBean
[DEBUG] 11:36:41.102 [main] com.itheima.a06.MyBean - The current bean com.itheima.a06.MyBean@130161f7 container is: org.springframework.context.support.GenericApplicationContext@de3a06f, started on Tue Oct 24 11:36:41 CST 2023
[DEBUG] 11:36:41.103 [main] com.itheima.a06.MyBean - Current bean com.itheima.a06.MyBean@130161f7 initialized

Different from the post-processing we introduced in the previous chapter, we do not need to add any post-processor, we only need to implement the corresponding Aware interface, and when running, the corresponding implementation method will be executed.

The above is the initial understanding of Aware. Let’s look at the use of post-processors.

context.registerBean("myConfig1", MyConfig1.class);
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
context.registerBean(CommonAnnotationBeanPostProcessor.class);

@Configuration
public class MyConfig1 {<!-- -->

    private static final Logger log = LoggerFactory.getLogger(MyConfig1.class);

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {<!-- -->
        log.debug("Inject ApplicationContext");
    }

    @PostConstruct
    public void init() {<!-- -->
        log.debug("Initialization");
    }
}

Output:

[DEBUG] 11:41:48.451 [main] com.itheima.a06.MyConfig1 - Inject ApplicationContext
[DEBUG] 11:41:48.456 [main] com.itheima.a06.MyConfig1 - Initialization

In fact, this is the same as what was introduced before, because with the addition of the corresponding post-processor, the @Autowired and @PostConstruct annotations can be parsed.

In fact, at this point, it can be understood that the Aware interface is very similar to the post-processor, both of which can intervene in the life cycle, and can be clearly seen in the life cycle diagram.

But there is still something essential to do, simply put:

  • The parsing of @Autowired requires the use of bean post-processor, which is an extended function
  • The Aware interface is a built-in function and can be recognized by Spring without any extension.

In fact, it is very clear to say this, but there is another big difference: the built-in injection and initialization are not affected by the extension function and will always be executed, while the extension function may fail due to certain circumstances.

Or add a bean in Myconfig1

@Bean // beanFactory post-processor
    public BeanFactoryPostProcessor processor1() {<!-- -->
        return beanFactory -> {<!-- -->
            log.debug("execute processor1");
        };
    }

When I run it again, I find that @Autowired and @PostConstruct are not executed.

[INFO] 11:45:21.941 [main] o.s.c.a.ConfigurationClassEnhancer - @Bean method MyConfig1.processor1 is non-static and returns an object assignable to Spring's BeanFactoryPostProcessor interface. This will result in a failure to process annotations such as @Autowired, @Resource and @PostConstruct within the method's declaring @Configuration class. Add the 'static' modifier to this method to avoid these container lifecycle issues; see @Bean javadoc for complete details.
[DEBUG] 11:45:21.952 [main] com.itheima.a06.MyConfig1 - Execute processor1

What causes @Autowired to fail?

In fact, there is a default initialization sequence in the Context.refresh() method.

1.beanfactory post-processor

2.bean post-processor

3.Initialize the singleton

Let’s use a picture to describe it:

The Java configuration class contains BeanFactoryPostProcessor. Therefore, to create the BeanFactoryPostProcessor, you must create the Java configuration class in advance. At this time, the BeanPostProcessor is not ready yet, causing @Autowired and other annotations to become invalid.

Based on the above analysis, another code example is given:

@Configuration
public class MyConfig2 implements InitializingBean, ApplicationContextAware {<!-- -->

    private static final Logger log = LoggerFactory.getLogger(MyConfig2.class);

    @Override
    public void afterPropertiesSet() throws Exception {<!-- -->
        log.debug("Initialization");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {<!-- -->
        log.debug("Inject ApplicationContext");
    }



    @PostConstruct
    public void init() {<!-- -->
        log.debug("Initialization");
    }


    @Bean // beanFactory post-processor
    public BeanFactoryPostProcessor processor2() {<!-- -->
        return beanFactory -> {<!-- -->
            log.debug("execute processor2");
        };
    }
}

Output:

[DEBUG] 11:51:22.230 [main] com.itheima.a06.MyConfig2 - Inject ApplicationContext
[DEBUG] 11:51:22.235 [main] com.itheima.a06.MyConfig2 - Initialization
[INFO ] 11:51:22.237 [main] o.s.c.a.ConfigurationClassEnhancer - @Bean method MyConfig2.processor2 is non-static and returns an object assignable to Spring's BeanFactoryPostProcessor interface. This will result in a failure to process annotations such as @Autowired , @Resource and @PostConstruct within the method's declaring @Configuration class. Add the 'static' modifier to this method to avoid these container lifecycle issues; see @Bean javadoc for complete details.
[DEBUG] 11:51:22.245 [main] com.itheima.a06.MyConfig2 - Execute processor2

This can be analyzed and summarized:

  • The Aware interface provides a [built-in] injection method, which can inject BeanFactory, ApplicationContext
  • The InitializingBean interface provides a [built-in] initialization method
  • Built-in injection and initialization are not affected by extension functions and will always be executed, so they are commonly used by classes within the Spring framework.

Execution sequence of initialization and destruction

If you want to verify the execution sequence of initialization and destruction, the most direct way is to print it out. In fact, we have introduced the execution sequence of the post-processor and the Aware interface in detail. Here, add @Bean Comprehensive comparison of specified initialization methods:

ConfigurableApplicationContext context = SpringApplication.run(A.class, args);
context.close();


    @Bean(initMethod = "init3")
    public Bean1 bean1() {<!-- -->
        return new Bean1();
    }

    @Bean(destroyMethod = "destroy3")
    public Bean2 bean2() {<!-- -->
        return new Bean2();
    }

public class Bean1 implements InitializingBean {<!-- -->
    private static final Logger log = LoggerFactory.getLogger(Bean1.class);

    //Extend
    @PostConstruct
    public void init1() {<!-- -->
        log.debug("Initialization 1");
    }


    // built-in
    @Override
    public void afterPropertiesSet() throws Exception {<!-- -->
        log.debug("Initialization 2");
    }

    public void init3() {<!-- -->
        log.debug("Initialization 3");
    }
}

public class Bean2 implements DisposableBean {<!-- -->
    private static final Logger log = LoggerFactory.getLogger(Bean2.class);

    @PreDestroy
    public void destroy1() {<!-- -->
        log.debug("Destroy 1");
    }

    @Override
    public void destroy() throws Exception {<!-- -->
        log.debug("Destruction 2");
    }

    public void destroy3() {<!-- -->
        log.debug("Destruction 3");
    }
}

Output:

[DEBUG] 14:52:57.353 [main] com.itheima.a07.Bean1 - Initialization 1
[DEBUG] 14:52:57.353 [main] com.itheima.a07.Bean1 - Initialization 2
[DEBUG] 14:52:57.354 [main] com.itheima.a07.Bean1 - Initialization 3

...

[DEBUG] 14:52:57.758 [main] com.itheima.a07.Bean2 - Destroy 1
[DEBUG] 14:52:57.758 [main] com.itheima.a07.Bean2 - Destroy 2
[DEBUG] 14:52:57.758 [main] com.itheima.a07.Bean2 - Destroy 3

Its initialization sequence is:

  1. @PostConstruct annotated initialization method
  2. Initializing method of InitializingBean interface
  3. @Bean(initMethod) specified initialization method

The destruction order is:

  1. @PreDestroy annotated destruction method
  2. Destruction method of DisposableBean interface
  3. The destruction method specified by @Bean(destroyMethod)

Scope

In the current version of Spring and Spring Boot programs, there are five scopes, singleton, prototype, request, session, application (globalSession has been abandoned)

  • singleton, created when the container starts (no delay is set), destroyed when the container is shut down
  • prototype, created each time it is used, will not be automatically destroyed and needs to be destroyed by calling DefaultListableBeanFactory.destroyBean(bean)
  • request, created every time this bean is used in a request, and destroyed when the request ends.
  • session, created when this bean is used in each session, and destroyed when the session ends
  • Application, web container creates this bean when it is used, and destroys it when the container stops.

Among them, the first two are the most common scopes. I won’t go into details here. I mainly introduce the last three, taking a Springboot long beard as an example.

SpringApplication.run(A.class, args);

@Scope("request")
@Component
public class BeanForRequest {<!-- -->
    private static final Logger log = LoggerFactory.getLogger(BeanForRequest.class);

    @PreDestroy
    public void destroy() {<!-- -->
        log.debug("destroy");
    }

}

@Scope("session")
@Component
public class BeanForSession {<!-- -->
    private static final Logger log = LoggerFactory.getLogger(BeanForSession.class);

    @PreDestroy
    public void destroy() {<!-- -->
        log.debug("destroy");
    }
}

@Scope("application")
@Component
public class BeanForApplication {<!-- -->
    private static final Logger log = LoggerFactory.getLogger(BeanForApplication.class);

    @PreDestroy
    public void destroy() {<!-- -->
        log.debug("destroy");
    }
}

@RestController
public class MyController {<!-- -->


    @Lazy
    @Autowired
    private BeanForRequest beanForRequest;

    @Lazy
    @Autowired
    private BeanForSession beanForSession;

    @Lazy
    @Autowired
    private BeanForApplication beanForApplication;

    @GetMapping(value = "/test", produces = "text/html")
    public String test(HttpServletRequest request, HttpSession session) {<!-- -->
        ServletContext sc = request.getServletContext();
        String sb = "<ul>" +
                    "<li>" + "request scope:" + beanForRequest + "</li>" +
                    "<li>" + "session scope:" + beanForSession + "</li>" +
                    "<li>" + "application scope:" + beanForApplication + "</li>" +
                    "</ul>";
        return sb;
    }

}

Start springboot and visit http://localhost:8080/test to find:

For a request, these three scopes will be involved. When the page is refreshed,

You can find that the corresponding BeanForRequest has changed. This is the scope of the request, which changes with each request.

In the same way, the corresponding session corresponds to a session. Here you can modify the session time or restart a new browser to visit again.

It was found that the corresponding BeanForSession also changed.

The above is the scope of the scope domain, but careful students can actually find that in the Controller, because Spring’s beans are singletons by default, our @Autowired is not a singleton, and even changes with the change of the scope. The @Lazy annotation is added respectively, so what is the function of this annotation?

Remove the annotation and retest, and found that no matter how we refresh, the request and session will not change. Why is this?

In fact, this is a typical problem of singleton injection into other scopes failing.

Take the example of injecting multiple instances into a single instance

There is a singleton object E

@Component
public class E {<!-- -->
    private static final Logger log = LoggerFactory.getLogger(E.class);

    private F f;

    public E() {<!-- -->
        log.info("E()");
    }

    @Autowired
    public void setF(F f) {<!-- -->
        this.f = f;
        log.info("setF(F f) {}", f.getClass());
    }

    public F getF() {<!-- -->
        return f;
    }
}

The object F to inject is expected to be a multi-instance

@Component
@Scope("prototype")
public class F {<!-- -->
    private static final Logger log = LoggerFactory.getLogger(F.class);

    public F() {<!-- -->
        log.info("F()");
    }
}

test

E e = context.getBean(E.class);
F f1 = e.getF();
F f2 = e.getF();
System.out.println(f1);
System.out.println(f2);

output

com.itheima.demo.cycle.F@6622fc65
com.itheima.demo.cycle.F@6622fc65

Found that they are the same object, not the expected multi-instance object

For singleton objects, dependency injection only occurs once, and the multi-instance F is not used subsequently, so E always uses the F of the first dependency injection.

solve:

Generate proxy using @Lazy

Although the proxy object is still the same, every time any method of the proxy object is used, a new f object is created by the proxy.

Therefore, the problem of singleton injection into other scopes can be effectively solved.