深入解析Spring AOP核心原理

一 Spring-AOP

1.对SpringAOP理解

AOP是OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生泛型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低提高程序的可重用性,同时提高了开发的效率。

AOP(Aspect-Oriented Programming:面向切面编程): 将哪些与业务无关,却为业务模块所共同调用的逻辑(例如事务处理,日志管理,权限控制)封装抽成一个可重用的模块,这个模块被命名为"切面",便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可拓展性和可维护性。

SpringAOP基于动态代理实现:

  • 如果被代理的对象,已经实现某个接口,则SpringAOP会使用JDK Proxy(反射),基于接口的方式,创建代理对象(JDK动态代理的核心是InvocationHandler接口和Proxy类);
  • 如果被代理的对象,没有实现某个接口,就无法使用JDK Proxy去处理代理了,这时候Spring会使用Cglib,基于继承的方式,生成一个被代理类对象的子类来作为代理(Cglib动态代理的核心是MethodInterceptor接口和Enhancer类)。

2.AOP通知

概念:AOP将抽取出来的共性功能称为通知;

通知类型:以通知在上下文中的具体位置作为划分

解释:通知就是需要增强的方法内容以及执行位置的结合。

前置通知-before,返回通知-after-returning,异常通知-after-throwing,后置通知-after,环绕通知-around.

复制代码
<!--    aop配置-->
    <aop:config>
<!--        配置切面-->
        <aop:aspect id="mian" ref="logger">
<!--            配置切点-->
            <aop:pointcut id="cut" expression="execution(public * com.itheima.service.*.*(..))"></aop:pointcut>
<!--            配置通知-->
<!--            前置通知-->
            <aop:before method="beforeLog" pointcut-ref="cut"></aop:before>
<!--            后置通知-->
            <aop:after method="afterLog" pointcut-ref="cut"></aop:after>
<!--            返回通知-->
            <aop:after-returning method="afterReturningLog" pointcut-ref="cut"></aop:after-returning>
<!--            异常通知-->
            <aop:after-throwing method="afterThrowingLog" pointcut-ref="cut"></aop:after-throwing>
            <aop:around method="aroundLog" pointcut-ref="cut"></aop:around>
        </aop:aspect>
    </aop:config>

3.AOP连接点

AOP将所有的方法都视为连接点,不管是接口里面的抽象方法,还是实现类里面的重写方法,都是连接点。

解释:具备添加通知能力的方法位置,就是连接点,也就是所有类的所有方法

4.AOP切点

AOP将可能被抽取共性功能的方法称为切入点,切入点是连接点的子集。

解释:成功添加了通知方法的位置,就是切点。

复制代码
<aop:pointcut id="dian" expression="execution(public * com.apesource.service.*.*(..))"/>

5.AOP目标对象

就是挖掉功能的方法对应的类生的对象,这种对象是无法直接完成最终工作的

解释:被代理对象,就是目标对象

6.AOP织入

就是将挖掉的功能回填的动态过程

解释:将通知添加到切点的过程ing,就是织入。

补充:AOP切面:切点+通知

7.SpringAOP+AspectJ实现

1.坐标

复制代码
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>

2.配置

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="accountSErviceImp" class="com.itheima.service.AccountServiceImp"></bean>
    <bean id="logger" class="com.itheima.util.Logger"></bean>
<!--    aop配置-->
    <aop:config>
<!--        配置切面-->
        <aop:aspect id="mian" ref="logger">
<!--            配置切点-->
            <aop:pointcut id="cut" expression="execution(public * com.itheima.service.*.*(..))"></aop:pointcut>
<!--            配置通知-->
<!--            前置通知-->
            <aop:before method="beforeLog" pointcut-ref="cut"></aop:before>
<!--            后置通知-->
            <aop:after method="afterLog" pointcut-ref="cut"></aop:after>
<!--            返回通知-->
            <aop:after-returning method="afterReturningLog" pointcut-ref="cut"></aop:after-returning>
<!--            异常通知-->
            <aop:after-throwing method="afterThrowingLog" pointcut-ref="cut"></aop:after-throwing>
            <aop:around method="aroundLog" pointcut-ref="cut"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>

切点表达式配置语法

execution(修饰符 返回值 包名称.类名称.方法名称(参数列表))

1.修饰符可以省略代表任意

execution(返回值 包名称.类名称.方法名称(参数列表))

2.返回值可以使用"*"代表任意

execution(* 包名称.类名称.方法名称(参数列表))

3.包名可以使用"*"代表任意名称

execution(* *.*.*.类名称.方法名称(参数列表))

eg:execution(void *.*.*.ServiceImp.findAll())

4.包名可以使用".."代表任意个数

execution(* *..类名称.方法名称(参数列表))

eg:execution(void *..ServiceImp.findAll())

5.类名与方法名可以使用"*"代表任意

execution(* *...*.*(参数列表))

6.参数列表可以使用".."代表任意个数任意类型

execution(* *...*.*(..))

如果有参数

int======>int

String===>java.lang.String

二 Spring中bean的一生

1.bean实例化的基本流程-底层

Spring容器在进行初始化时,会将xml配置的信息封装成一个BeanDefinition 对象,所有的BeanDefinition存储到一个名为beanDefinitionMap 的Map集合中去,Spring框架在对该Map进行遍历,使用反射创建bean实例对象,创建号的Bean对象存储在一个名为singletonObject的Map集合中,当调用getBean方法时,则最终从该Map集合中取出Bean实例对象返回。

Bean 实例化的基本流程

加载xml配置文件,解析获取配置中的每个的信息,封装成一个个的BeanDefinition对象;

BeanDefinition 存储在一个名为beanDefinitionMap的Map中;

ApplicationContext底层遍历beanDefinitionMap ,创建Bean实例对象; 创建好的Bean实例对象,被存储到一个名为singletonObjects的Map中;

当执行applicationContext.getBean(beanName)时,从singletonObjects去匹配Bean实例返回。

2.Spring 还有其他类型的 "后处理器"

后处理器接口 作用阶段 核心功能
BeanFactoryPostProcessor Bean 定义加载后,Bean 实例化前 修改 BeanDefinition(Bean 的元信息),例如: - 动态修改 Bean 的属性值、作用域 - 新增 Bean 定义(如 Spring 的PropertyPlaceholderConfigurer用于解析${}占位符)
BeanDefinitionRegistryPostProcessor BeanFactoryPostProcessor 的增强版,更早执行 向容器中注册新的 BeanDefinition(比BeanFactoryPostProcessor有更高的优先级),例如: - 动态扫描并注册 Bean(Spring Boot 的 @ComponentScan 底层用到类似机制)
InstantiationAwareBeanPostProcessor Bean 实例化前后 (比BeanPostProcessor更早) 干预 Bean 的实例化过程,例如: - 阻止默认实例化,返回自定义 Bean 对象 - 处理属性注入前的逻辑(如 @Autowired 的依赖查找)
DestructionAwareBeanPostProcessor Bean 销毁前 处理 Bean 销毁前的逻辑,例如: - 资源释放、状态清理等

Spring的后处理器是Spring对外开放的重要扩展点,允许我们介入到Bean的整个实例化流程中来,以达到动态注册BeanDefinition,以及动态修改Bean的作用。

Spring主要有两种后处理器

  • BeanFactoryPostProcessor:Bean工厂后处理器,在BeanDefinitionMap填充完毕,Bean实例话之前执行;
  • BeanPostProcessor:Bean后处理器,一般在Bean实例化之后,填充到单例池singletonObjects之前执行;

3.Bean工厂后处理器 -- BeanFactoryPostProcessor

BeanFactoryPostProcessor是一个接口规范,实现了该接口的类只要交由Spring容器管理的话,那么 Spring就会 回调该接口的方法,用于对BeanDefinition注册和修改的功能。

修改

  1. 创建BeanFactoryPostProcessor实现类并重写方法

  2. 注入实现类

注册

  1. 创建BeanFactoryPostProcessor实现类并重写方法

  2. 注入实现类

Spring 提供了一个BeanFactoryPostProcessor 的子接口BeanDefinitionRegistryPostProcessor专门用 于注册BeanDefinition操作

复制代码
public class MyBeanFactoryPostProcessor2 implements 
BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory 
configurableListableBeanFactory) throws BeansException {}
    
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry 
beanDefinitionRegistry) throws BeansException {
 BeanDefinition beanDefinition = new RootBeanDefinition();
 beanDefinition.setBeanClassName("com.apesource.pojo.Student");
 beanDefinitionRegistry.registerBeanDefinition("stu",beanDefinition);
 }
}

4.Bean后处理器-BeanPostProcessor

Bean被实例化后,到最终缓存到名为singletonObjects 单例池之前,中间会经过Bean的初始化过程。

复制代码
public class MyBeanPostProcessor implements BeanPostProcessor{
    public Object postProcessBeforeInitialization(Object bean, String beanName){
        System.out.println("初始化之前执行");
        return bean;
    }
    public Object postProcessAfterInitialization(Object bean, String beanName){
        System.out.println("初始化之后执行");
        return bean;
    }

5.spring-bean的生命周期

从Bean实例化之后,及通过反射创建出对象之后,到Bean成为一个完整对象最终存储到单例池中,这个过程被成为SPringBean的生命周期。

大致分为三个阶段

  • Bean的实例化阶段:spring框架会取出BeanDefinition的信息进行判断当前bean的范围是否是singleton的,是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化。
  • Bean的初始化阶段:Bean创建之后还仅仅是个"半成品",还需要对Bean的实例进行填充,执行一些aware接口方法,执行BeanPostProcessor方法,执行InitializingBean接口的初始化方法。执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段。
  • Bean的完成阶段:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池sinletonObjects去了,及完成了SPringBean的整个生命周期。

6.Spring Bean的初始化过程涉及如下几个过程:

  • Bean实例的属性填充

  • Aware接口属性注入

  • BeanPostProcessor 的before()方法回调

  • InitializingBean接口的初始化方法回调 自定义初始化方法init回调

  • BeanPostProcessor 的after()方法回调

8.常用的Aware接口

Aware 接口是一种框架辅助属性注入的一种思想,其他框架中也可以看到类似的接口。框架具备高度封装性,我们接 触到的一般都是业务代码,一个底层功能API不能轻易的获取到,但是这不意味着永远用不到这些对象,如果用到了 ,就可以使用框架提供的类似Aware的接口,让框架给我们注入该对象。

三 循环依赖-解决

1. Spring在进行属性注入时,会分为如下几种情况:

注入普通属性,String、int或存储基本类型的集合时,直接通过set方法的反射设置进去;

注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作;

注入双向对象引用属性时,就比较复杂了,涉及了循环引用(循环依赖)。

循环依赖

含义:多个实体之间相互依赖并形成闭环的情况就叫做"循环依赖",也叫做"循环引用"。

Spring提供了 三级缓存 存储完整Bean实例和半成品Bean实例,用于解决循环引用问题

DefaultListableBeanFactory 的上四级父类DefaultSingletonBeanRegistry中提供如下三个Map

复制代码
public class DefaultSingletonBeanRegistry ... {
    //1、最终存储单例Bean成品的容器,即实例化和初始化都完成的Bean,称之为"一级缓存"    

Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    //2、早期Bean单例池,缓存半成品对象,且当前对象已经被其他对象引用了,称之为"二级缓存"    

Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
    //3、单例Bean的工厂池,缓存半成品对象,对象未被引用,使用时在通过工厂创建Bean,称之为"三级缓存"    
Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
}

注:将对象保存至三级缓存的时候,会包装成ObjectFactory对象录入,未来通过此接口对应的get方法再次提取对象.

UserService和UserDao循环依赖的过程结合上述三级缓存描述一下

  1. UserService 实例化对象,但尚未初始化,将UserService 存储到三级缓存
  2. UserService 属性注入,需要UserDao ,从缓存中获取,没有UserDao
  3. UserDao 实例化对象,但尚未初始化,将UserDao存储到到三级缓存;
  4. UserDao属性注入,需要UserService ,从三级缓存获取UserService,UserService 从三级缓存移入二级缓存;
  5. UserDao执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存; UserService 注入UserDao;
  6. UserService执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存。
相关推荐
rabbit_pro1 分钟前
Java 文件上传到服务器本地存储
java·服务器·python
q_191328469514 分钟前
基于Springboot2+Vue2的旅游景点购票系统
java·vue.js·spring boot·后端·mysql·毕业设计·计算机毕业设计
哈哈哈笑什么14 分钟前
基于RabbitMQ的企业级订单系统设计与实现
后端
LSTM9715 分钟前
使用 Java 实现条形码生成与识别
后端
哈哈哈笑什么15 分钟前
如何防止恶意伪造前端唯一请求id
前端·后端
哈哈哈笑什么15 分钟前
Spring Cloud 微服务架构下幂等性的 业务场景、解决的核心问题、完整实现方案及可运行代码
后端
XL's妃妃16 分钟前
Java 基准测试工具 JMH 详细介绍
java·开发语言·测试工具
Z3r4y16 分钟前
【代码审计】RuoYi-4.7.1&4.8.1 Thymeleaf模板注入分析
java·web安全·ruoyi·代码审计·thymeleaf
PieroPC17 分钟前
飞牛Nas-通过Docker的Compose 安装WordPress
后端
元直数字电路验证26 分钟前
Jakarta EE (原 Java EE) 技术栈概览
java·java-ee