深入解析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,存储到一级缓存,删除二三级缓存。
相关推荐
阿萨德528号2 小时前
ZooKeeper Java客户端与分布式应用实战
java·zookeeper·java-zookeeper
死也不注释2 小时前
【Unity UGUI 交互组件——Dropdown(TMP版本)(10)】
java·unity·交互
波波烤鸭3 小时前
Spring Boot 原理与性能优化实战
spring boot·后端·性能优化
shellvon3 小时前
从抓包到攻防:解锁API安全设计的秘密
后端·安全
狼爷3 小时前
凌晨 4 点的线上 CPU 告警:一场历时 4 小时的故障排查与架构优化全记录
java
言之。3 小时前
Django REST Framework响应类Response详解
后端·python·django
渣哥3 小时前
Java 线程池中的 submit 和 execute 有何不同
java
电商API_180079052473 小时前
淘宝商品视频批量自动化获取的常见渠道分享
java·爬虫·自动化·网络爬虫·音视频
IT乐手3 小时前
java 里 Consumer 和 Supplier 用法
java