一、Spring生命周期

1、什么是Spring生命周期

Spring生命周期

2、声明周期的管理方法

生命周期管理方法是对象在创建、初始化和销毁时自动调用的一些方法。这些方法提供了对象在不同生命周期阶段的行为。

生命周期管理方法在一些场景中也被称为“生命周期回调方法”,“生命周期钩子函数”等。

自定义对象的生命周期管理

但是当使用第三方组件和框架时,组件或框架中的对象的生命周期一般不是由开发者控制,而是按照特定的流程执行。、

第三方组件的生命周期管理

3、@PostConstruct

用来管理Spring Bean的生命周期,该方法将在Bean实例化后被调用。

  • 对于Scope为Singleton的Bean,默认在容器创建完成后进行实例化和初始化
  • 对于Scope为Prototype的Bean,在第一次获取该实例时进行实例化和初始化
@Service
public class ProductService {
    //安全的ArrayList集合CopyOnWriteArrayList 存储商品标签
    private final List<String> tags = new CopyOnWriteArrayList<>();

    /**
     * 为存储标签的集合中默认添加标签
     */
    @PostConstruct //Spring IOC容器在初始化该类的对象的时候 会自动调用这个方法
    public void initTags(){
        tags.add("应季");
        tags.add("爆款");
        tags.add("进口");
        tags.add("生鲜");
        tags.add("健身");
        System.out.println("标签初始化完成:"+tags.toString());
    }
    
     public List<String> getTags() {
        return tags;
    }
}

测试类:

class ProductServiceTest {
    private ApplicationContext context;

    @BeforeEach
    public void createContext(){
        context = new AnnotationConfigApplicationContext(ContextConfig.class);
    }

    @Test
    public void test1(){
        //获取ProductService对象
        ProductService productService = context.getBean(ProductService.class);
    }
}

测试结果:

标签初始化完成:[应季, 爆款, 进口, 生鲜, 健身]

4、@PreDestroy

在bean销毁之前被调用

  • 关闭容器时候会自动调用
  • 只有Scope为Singleton的Bean,销毁时候才会执行销毁方法
  • 这个方法不是绝对可靠,依赖JVM的关闭流程,直接终止进程时不会执行
@PreDestroy
public void destroy(){
    System.out.println("清除标签缓存:"+tags);
    tags.clear();
}

测试类:

@Test
public void test1(){
    //获取ProductService对象
    ProductService productService = context.getBean(ProductService.class);

    //为了看到效果 手动关闭容器
    ((AnnotationConfigApplicationContext) context).close();
}

@PreDestroy原理

1、Spring在Java虚拟机上挂了关闭钩子,虚拟机关闭时候会自动执行钩子, Spring的钩子会关闭Spring容器。

2、关闭容器的过程中,会销毁所有单例的Bean

3、销毁单例的Bean时,会调用添加了@PreDestroy 注解的方法。

PreDestory原理

Bean生命周期的销毁阶段

钩子函数:

@Test
public void test2() throws InterruptedException {
    /**
     * Runtime代表正在运行的java虚拟机  可以获取当前虚拟机的参数
     */
    Runtime runtime = Runtime.getRuntime();
    //获取当前虚拟机总内存
    long bytes = runtime.totalMemory();
    System.out.println("当前内存总量:" + bytes / 1024 / 1024 + "MB");
    //让系统挂上一个钩子函数 当虚拟机关闭的时候执行钩子函数
    runtime.addShutdownHook(new Thread(() -> {
        System.out.println("这是一个钩子函数");
    }));
    System.out.println("程序在运行");
    TimeUnit.SECONDS.sleep(3);
}

运行效果:在系统休眠结束后,关闭时才会调用这个钩子函数

标签初始化完成:[应季, 爆款, 进口, 生鲜, 健身]
当前内存总量:256MB
程序在运行
这是一个钩子函数

5、initMethod和destroyMethod

当程序中使用的Bean是第三方提供的类时,无法直接在源码中添加@PostConstruct或@PreDestroy注解,此时可以通过@Bean注解的属性实现。

创建user实例:

public class User {
    private Integer id;
    private String username;
    private String nickname;

    @PostConstruct
    public void initUser() {
        this.id = 1;
        this.username = "Tom";
        this.nickname = "汤姆";
        System.out.println("tom信息初始化完成");
    }

    public void destroy() {
        System.out.println("信息销毁");
    }

    public String getInfo() {
        return "id:" + id + ",username:" + username + ",nickname:" + nickname;
    }
}

在管理类中添加bean

@Bean(initMethod = "initUser", destroyMethod = "destroy")
public User user() {
    return new User();
}

测试类:

@Test
public void test3(){
    User user =context.getBean(User.class);
    System.out.println(user.getInfo());
    ((AnnotationConfigApplicationContext) context).close();
}

测试效果:

tom信息初始化完成
id:1,username:Tom,nickname:汤姆
信息销毁

6、认识Spring容器

实现spring容器的两种方式:BeanFactoryApplicationContext

Spring容器

BeanFactory

BeanFactory接口是Spring容器的基本接口,提供了Bean的管理方法。BeanFactory既是生产Bean的工厂,同时也是管理Bean的仓库。

在BeanFactory中,所有的bean都是懒加载的,只有在使用时才会创建,因此它的启动速度比其他容器要快。

Spring在BeanFactory的基础上扩展了ApplicationContext。根据Spring官方文档,除了极少数情况下,开发者应该使用ApplicationContext而非BeanFactory。

ApplicationContext

ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能。

ApplicationContext

ApplicationContext的实现:

applicationContext的实现

7、Processor和Aware

Processor(处理器)

Processor(处理器)是一种用于定制化和拓展Spring容器的机制。

BeanFactoryPostProcessor:是一个重要的Processor,它可以在容器实例化Bean之前读取Bean的定义(configuration metadata)并可以对Bean的定义进行一些自定义操作,如修改bean的定义、删除不必要的bean等。

BeanPostProcessor:是一种比较常用的Processor,它的主要作用是在Bean实例化后、初始化前或初始化后对Bean进行一些操作,如实现特定接口、执行特定操作等。通过实现BeanPostProcessor接口,开发者可以自定义一些逻辑,从而达到增强Bean功能的目的。

Processor

BeanFactoryPostProcessor
/**
 * 自定义Bean工厂后期处理器
 * 主要的作用是:对被管理的所有Bean做一些预处理的工作
 * 一般不建议自己做预处理,了解一下即可
 */
@Component
public class MyBeanFactoryProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        //获取被Spring Ioc容器管理所有Bean的名字的数组
        String[] beanNames = beanFactory.getBeanDefinitionNames();
        //遍历数组
        Arrays.stream(beanNames).forEach(
                name-> System.out.println("被管理的对象:"+name));
    }
}

测试结果:

被管理的对象:contextConfig
被管理的对象:myBeanFactoryProcessor
BeanPostProcessor
/**
 * Bean后期处理器
 * 创建Bean之后处理
 * postProcessBeforeInitialization在初始化之前执行
 * postProcessAfterInitialization在初始化之后执行
 */
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    //在初始化之后执行
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //创建bean之后 执行初始化之后执行
        System.out.println("Bean初始化之后执行,beanID=" + beanName + ",bean=" + bean);
        if (beanName == "user") {
            User user = (User) bean;
            user.setUsername("zhangsan");
            user.setNickname("张三");

            bean = user;
        }
        return bean;
    }

    //在初始化前执行
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //创建bean之后,执行初始化之前执行
        //方法的参数bean 就是创建的bean对象
        //方法的参数beanName 是bean标识


        System.out.println("Bean初始化之前执行,beanID=" + beanName + ",bean=" + bean);
        if (beanName == "name") {
            String name = (String) bean;
            name = "李四";
            bean = name;
        }
        return bean;
    }
}

测试类:

@Test
public void test4(){
    User user = context.getBean(User.class);
    //在bean初始化之后
    // 如果bean的标识是user 就把这个对象username改为zhangsan,nickname改为张三
    System.out.println(user.getInfo());

}

测试效果:

Bean初始化之前执行,beanID=contextConfig,bean=cn.highedu.ContextConfig$$SpringCGLIB$$0@43599640
Bean初始化之后执行,beanID=contextConfig,bean=cn.highedu.ContextConfig$$SpringCGLIB$$0@43599640
Bean初始化之前执行,beanID=productService,bean=cn.highedu.service.ProductService@55c53a33
标签初始化完成:[应季, 爆款, 进口, 生鲜, 健身]
Bean初始化之后执行,beanID=productService,bean=cn.highedu.service.ProductService@55c53a33
Bean初始化之前执行,beanID=name,bean=张三
Bean初始化之后执行,beanID=name,bean=李四
Bean初始化之前执行,beanID=user,bean=cn.highedu.entity.User@f14a7d4
tom信息初始化完成
Bean初始化之后执行,beanID=user,bean=cn.highedu.entity.User@f14a7d4
id:1,username:zhangsan,nickname:张三
CommonAnnotationBeanPostProcessor

是Spring框架中的一个BeanPostProcessor的实现类,其作用是识别和处理通用注解(Common Annotations)。这些通用注解包括@Resource、@PostConstruct、@PreDestroy等。

Aware

Aware接口是一组标记接口,用于将相应的资源注入到Bean中,以实现Bean对它们的感知(Awareness)

Spring框架提供了10余种Aware接口的子接口,每个子接口能获取到的信息不同,开发者可以根据实际情况组合这些接口。以下是其中一些较为常用的接口:

1、ApplicationContextAware:用于获取Spring容器上下文ApplicationContext对象。可以通过ApplicationContext对象获取到各种Spring容器管理的Bean实例,或者进行其他与容器相关的操作。

2、BeanNameAware:用于获取当前Bean的名称。这个Aware接口常用于在Bean内部需要获取自己的Bean名称时使用。

@Component
public class MyAware implements BeanNameAware, ApplicationContextAware {

    private ApplicationContext context;

    @Override
    public void setBeanName(String name) {
        //name表示被Spring IOC容器管理的Bean的标识
        System.out.println("MyAware获取到Bean:" + name);
    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        this.context = context;
    }

    public void getBean() {
        //获取到user这个Bean
        User user = (User) context.getBean("user");
        System.out.println(user.getInfo());
    }
}

测试类:

@Test
public void test5() {
    MyAware myAware =context.getBean(MyAware.class);
    myAware.getBean();
}

测试效果:

MyAware获取到Bean:myAware
id:1,username:zhangsan,nickname:张三

3、ResourceLoaderAware:用于获取ResourceLoader对象。ResourceLoader是Spring内置的一个用于加载资源(例如配置文件、图片、音频、视频等)的工具类,它可以支持多种资源的加载,包括从磁盘、网络、classpath以及URL等加载。

4、EnvironmentAware:用于获取当前应用程序所在的环境变量和系统属性。可以通过Environment对象获取当前应用程序运行的环境变量和系统属性,如当前应用程序所在的操作系统、JAVA_HOME路径、用户的home目录等。

8、Bean的创建过程

整体阶段划分

Bean的创建步骤可以划分为三个阶段,分别是:(1)加载并处理Bean的定义,(2)Bean的实例化和(3)Bean的初始化。

Bean的创建步骤

加载并处理Bean的定义

加载并处理Bean的定义阶段具体包括的步骤如下:

1、Spring启动后,根据配置文件加载Bean的定义,包括处理@Bean和@Component 注解。

2、找到Bean定义后,将转换应用于Bean定义,也就是可以进一步修改处理Bean定义。

3、在处理Bean定义的过程中调用了一系列实现了BeanFactoryPostProcessor 接口的对象。

  • 其中包括处理 @PropertySource、@Value 的PropertySourcesPlaceholderConfigurer
  • 其中包括处理 @Configuration 的 ConfigurationClassPostProcessor

4、可以自行扩展 BeanFactoryPostProcesser 接口,参与Bean工厂后期处理功能。

加载并处理Bean的定义

Bean的实例化

Bean的实例化阶段具体包括的步骤如下:

1、首先查找Bean的依赖关系,解决创建Bean的先后次序问题,controller依赖service依赖mapper。

2、实例化Bean对象,也就是创建Bean对象,这里包含构造器注入过程

3、然后进行属性注入(属性注入,方法注入)

Bean的实例化

Bean的初始化

Bean的初始化阶段具体包括的步骤如下:

1、Bean的初始化由一系列的BeanPostProcessor对象完成

2、先执行 BPP 的前置处理方法postProcessBeforeInitialization

3、然后执行Bean的自定义初始化方法

4、再执行BPP的后置处理方法postProcessAfterInitialization

Bean的初始化

@Bean和 @Component

使用@Bean注解配置的Bean和使用@Component 注解配置的Bean的后处理方式是相同的。

@Bean和 @Component

二、Spring AOP

1、AOP概述

(1)关注点概述

关注点(Concerns)是程序中具有独立功能的代码片段,或称为功能的内聚区域。(例如controller层和service层的日志)

关注点按照功能的类型,可以分为核心关注点和横切关注点。

核心关注点(Core Concerns)指某项业务的核心处理逻辑,例如登录业务的核心逻辑是用户身份认证信息的比对。

横切关注点(cross-cutting concerns)指在多个业务中重复出现的,与业务核心逻辑关系不大的逻辑,例如输出运行日志。

(2)AOP

是一种编程范式(思想)。AOP旨在将横切关注点与业务主体进一步分离,把一些共同特性的类抽取出来。例如:三个Service的共性都是日志,将共性拿出来封装成一个切面

AOP

在没有AOP的编程思想时创建service层:

public class UserServiceImpl implements UserService {

    @Override
    public void save() {
        System.out.println(new Date() + "日志:开始执行"); //横切关注点
        System.out.println("保存用户"); //核心关注点
        System.out.println(new Date()+"日志:结束执行");
    }

    @Override
    public void delete() {
        System.out.println(new Date() + "日志:开始执行");
        System.out.println("删除用户");
        System.out.println(new Date()+"日志:结束执行");
    }
}

如果客户要求添加日志(横切关注点),那么在方法中都需要添加,维护代码比较困难。

CGLIB代理,要求这个类不能用final修饰,必须有创建子类的能力。

(3)AOP与OOP

在oop中,模块化的核心单元室类,在AOP中,模块化的核心单元是切面,切面中封装了具体的逻辑。

AOP是对OOP的有效补充,可以达到降低代码的耦合度的目的,辅助OOP更好地实现业务逻辑的组织和管理。

2、Spring中使用AOP

(1)基于Spring实现AOP

基于Spring实现AOP

(2)Spring AOP编程步骤

1、导入AOP相关依赖。

2、创建切面组件,在切面组件中通过方法封装横切关注点代码,例如记录方法执行时长。

3、使用注解标注通知类型,例如@Before。

4、标注切入点,例如bean(userServiceImpl)。

导入依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
创建切面组件,在切面组件中通过方法封装横切关注点代码,例如记录方法执行时长。
使用注解标注通知类型,例如@Before。
标注切入点,例如bean(userServiceImpl)。
/**
 * 定义一个切面类
 * 一个横切关注点
 * 目的:统一处理日志功能
 */
@Component //交给Spring IOC容器管理
@Aspect //声明这是一个切面类
public class TimerAspect {
    private static final Logger logger = LoggerFactory.getLogger(TimerAspect.class);

    //定义一个环绕通知 说明当前的方法在什么时候通知
    //bean(userServiceImpl)表示一个切入点(在什么时候执行通知中的内容)
    //执行userServiceImpl这个bean的任何方法,都会执行当前通知
    @Around("bean(userServiceImpl)")
    public Object log(ProceedingJoinPoint point) throws Throwable {
        logger.info("执行" + point.getSignature().getName() + "操作");
        //调用目标方法
        Object result = point.proceed();
        return result;
    }
}
最后修改:2023 年 09 月 16 日
如果觉得我的文章对你有用,请随意赞赏