前言
Spring Bean的初始化方式就那么几种,但是初始化Bean的方式不同,Bean中静态代码块,构造代码块,构造器以及其他初始化方法的执行顺序如何,这也都是需要我们去掌握的,可能哪天碰到一道面试题,也就是类似于本文中提及到的初始化过程
此文描述的不是SpringBean的生命周期,后期我们会好好聊下它的生命周期,咱们今天阐述的是一个Bean内部方法的执行顺序
此文描述的不是SpringBean的生命周期,后期我们会好好聊下它的生命周期,咱们今天阐述的是一个Bean内部方法的执行顺序
初始化方式
SpringBean的初始化方式,相信大家并不陌生,我们今天对注解注入和配置文件注入进行讲解.我们先了解下注解注入和配置文件注入方式
注解
常用Spring的注解有: @Component,@Repository,@Service,@Controller,(@Configuration,@Bean等还有很多组合注入方式,不做一一解读)但每个注解有它不同的应用场景,不过你也可以用其中任意一个修饰你的表现层,服务层及数据层,但是如果用一个@Controller 修饰你的数据层及服务层,看到这样的代码你不会觉得恶心吗?每个各司其职,岂不是更好,下面我对每个注解简单的介绍下,我们后面的文章会详细解读
@Repository
Spring2.0 添加,一种封装存储、检索和搜索行为的机制,模拟对象集合,你可以标记在任何的类上,用来表明该类是用来执行与数据库相关的操作,并支持自动处理数据库操作产生的异常,其遵循 DDD:Domain-Driven Design(领域驱动设计)而设计的,简称DDD ,DDD 是一套综合软件系统分析和设计的面向对象建模方法,后期我们谈这个
@Service
Spring2.5 添加和他孪生兄弟还有@Component,@Controller,都是在Spring2.5 中产出.主要标记在服务层中,处理核心业务逻辑,如果存在DB的使用,可以使用@Repository注入的Bean,也同样遵循DDD而产出
@Controller
Spring2.5添加,上面已经讲到了,它的作用主要解释与表现层,作为web控制器使用,前端页面上的交互都与@Controller修饰的类进行交互,入参的判断及异常最终的处理,都会落在Controller中,不过也可以通过@ControllerAdvice处理全局异常,但是它是在Spring 3.2中添加的.
@Component
Spring2.5添加,为什么最后将@Component,因为@Component可以作用在任何类上面,可以回头看下@Repository,@Service,@Controller 都被@Component修饰,但凡类上面标记@Component注解,都可以被添加到容器中
配置文件
注解方式和配置文件的方式没有什么区别,只是注解不需要在xml自己配置bean
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">
<context:component-scan base-package="com.ijson"/>
<bean class="com.ijson.MyBeanService" init-method="initMethod" destroy-method="destroyMethod"/>
<context:annotation-config/>
<task:annotation-driven/>
</beans>
初始化过程
有了上面的铺垫,我们来讲下两种初始化方式他们的构造器,静态代码块,构造代码块的初始化过程,结合上面的xml我们看下一下代码,其执行顺序可参照代码中的注释
package com.ijson;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/**
* desc:
* author: cuiyongxu
* create_time: 2021/8/24-3:55 PM
**/
public class MyBeanService implements InitializingBean, DisposableBean {
static {
System.out.println("static");//1
}
static {
System.out.println("static-2");//2
}
{
System.out.println("{}-1");//3
}
{
System.out.println("{}-2");//4
}
public MyBeanService() {
System.out.println("MyBeanService");//5
}
@PostConstruct
public void init2() {
System.out.println("init2");//6
}
@PostConstruct
public void init() {
System.out.println("init");//7
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet");//8
}
public void initMethod() {
System.out.println("init-method");//9
}
/**
* bean 销毁顺序,终止容器时,销毁过程
*/
@PreDestroy
public void preDestroy() {
System.out.println("preDestroy");//1
}
@PreDestroy
public void preDestroy2() {
System.out.println("preDestroy2");//2
}
@Override
public void destroy() throws Exception {
System.out.println("destroy");//3
}
private void destroyMethod() {
System.out.println("destroy-method");//4
}
}
以下代码反编译,为啥要反编译?看后面总结
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.ijson;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class MyBeanService implements InitializingBean, DisposableBean {
public MyBeanService() {
System.out.println("{}-1");
System.out.println("{}-2");
System.out.println("MyBeanService");
}
@PostConstruct
public void init2() {
System.out.println("init2");
}
@PostConstruct
public void init() {
System.out.println("init");
}
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet");
}
public void initMethod() {
System.out.println("init-method");
}
@PreDestroy
public void preDestroy() {
System.out.println("preDestroy");
}
@PreDestroy
public void preDestroy2() {
System.out.println("preDestroy2");
}
public void destroy() throws Exception {
System.out.println("destroy");
}
private void destroyMethod() {
System.out.println("destroy-method");
}
static {
System.out.println("static");
System.out.println("static-2");
}
}
总结:
静态代码块:优先于各种代码块以及构造函数,它会在类初始化(可了解下JVM类加载过程)的时候执行一次,执行完成便销毁,如果一个类中有多个静态代码块,会按照书写顺序依次执行
构造代码块:在创建对象时被调用,创建对象就会调用一次,在咱们上面的示例中只会执行一次,因为加载容器中之后,不会被销毁,也就一直存在这个Bean.他优先于构造函数执行,需要注意,构造代码块不是优先于构造函数执行,而是依托于构造函数,也就是说,你不实例化对象,构造代码块是不会执行的,看下反编译后的MyBeanService的构造函数,我们看到构造代码块的代码被添加到了构造函数中,且在构造函数代码之前,即依托于构造函数
构造函数:构造函数的命名必须和类名完全相同,没有显式返回类型,当前类初始化时,就会自动调用构造函数
@PostConstruct: 优先要说的,它不是Spring的注解,它位于rt.jar中,源自Common Annotations 1.0,在jdk6发布时,被作为新特性添加到了jdk中,执行于构造函数之后,如果一个类中有多个@PostConstruct 则会按照书写顺序依次执行
InitializingBean:只包括afterPropertiesSet方法,继承该接口的类,在初始化bean的时候就会执行该方法.如果用户自行实现了安全策略,则优先执行安全管理器,再执行afterPropertiesSet方法,如果没有,则直接执行afterPropertiesSet,如下图源码所示
init-method: 只用在使用xml配置文件时才会有此项,在afterPropertiesSet之后执行,如下图源码所示
init-method: 想必看到上面的截图,应该就知道这个方法在谁的后面去执行了吧,这里就不做多说了
销毁过程
destroy-method: 可参照上图源码,可以看到会在destroy之后,执行一次