一、Spring生命周期
1、什么是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 注解的方法。
钩子函数:
@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容器的两种方式:BeanFactory、ApplicationContext
BeanFactory
BeanFactory接口是Spring容器的基本接口,提供了Bean的管理方法。BeanFactory既是生产Bean的工厂,同时也是管理Bean的仓库。
在BeanFactory中,所有的bean都是懒加载的,只有在使用时才会创建,因此它的启动速度比其他容器要快。
Spring在BeanFactory的基础上扩展了ApplicationContext。根据Spring官方文档,除了极少数情况下,开发者应该使用ApplicationContext而非BeanFactory。
ApplicationContext
ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能。
ApplicationContext的实现:
7、Processor和Aware
Processor(处理器)
Processor(处理器)是一种用于定制化和拓展Spring容器的机制。
BeanFactoryPostProcessor:是一个重要的Processor,它可以在容器实例化Bean之前读取Bean的定义(configuration metadata)并可以对Bean的定义进行一些自定义操作,如修改bean的定义、删除不必要的bean等。
BeanPostProcessor:是一种比较常用的Processor,它的主要作用是在Bean实例化后、初始化前或初始化后对Bean进行一些操作,如实现特定接口、执行特定操作等。通过实现BeanPostProcessor接口,开发者可以自定义一些逻辑,从而达到增强Bean功能的目的。
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的定义阶段具体包括的步骤如下:
1、Spring启动后,根据配置文件加载Bean的定义,包括处理@Bean和@Component 注解。
2、找到Bean定义后,将转换应用于Bean定义,也就是可以进一步修改处理Bean定义。
3、在处理Bean定义的过程中调用了一系列实现了BeanFactoryPostProcessor 接口的对象。
- 其中包括处理 @PropertySource、@Value 的PropertySourcesPlaceholderConfigurer
- 其中包括处理 @Configuration 的 ConfigurationClassPostProcessor
4、可以自行扩展 BeanFactoryPostProcesser 接口,参与Bean工厂后期处理功能。
Bean的实例化
Bean的实例化阶段具体包括的步骤如下:
1、首先查找Bean的依赖关系,解决创建Bean的先后次序问题,controller依赖service依赖mapper。
2、实例化Bean对象,也就是创建Bean对象,这里包含构造器注入过程
3、然后进行属性注入(属性注入,方法注入)
Bean的初始化
Bean的初始化阶段具体包括的步骤如下:
1、Bean的初始化由一系列的BeanPostProcessor对象完成
2、先执行 BPP 的前置处理方法postProcessBeforeInitialization
3、然后执行Bean的自定义初始化方法
4、再执行BPP的后置处理方法postProcessAfterInitialization
@Bean和 @Component
使用@Bean注解配置的Bean和使用@Component 注解配置的Bean的后处理方式是相同的。
二、Spring AOP
1、AOP概述
(1)关注点概述
关注点(Concerns)是程序中具有独立功能的代码片段,或称为功能的内聚区域。(例如controller层和service层的日志)
关注点按照功能的类型,可以分为核心关注点和横切关注点。
核心关注点(Core Concerns)指某项业务的核心处理逻辑,例如登录业务的核心逻辑是用户身份认证信息的比对。
横切关注点(cross-cutting concerns)指在多个业务中重复出现的,与业务核心逻辑关系不大的逻辑,例如输出运行日志。
(2)AOP
是一种编程范式(思想)。AOP旨在将横切关注点与业务主体进一步分离,把一些共同特性的类抽取出来。例如:三个Service的共性都是日志,将共性拿出来封装成一个切面
在没有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
(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;
}
}