精通Spring Framework (3) 深入掌握Spring AOP(一)

spring framework 两个特性:
1.依赖翻转
2.面向切面

DI 有助于应用对象之间的解耦,AOP可以实现横切关注点域他们所影响对象之间的解耦。

  • AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用 。*

aop可以理解为模块化编程,日志模块独立出来做成一个切面

一、Spring AOP

spring 提供了四种aop支持

  • 基于代理的经典AOP
  • 纯POJO 切面
  • @AspectJ 注解驱动切面
  • 注入式AspectJ 切面(适用于spring 个版本)

书中介绍 前三种 是Spring AOP实现变体, spring AOP构建在动态代理基础上。
所以 spring 对于aop 的支持仅限于拦截方法。

spring aop 基于动态代理实现,AOP的拦截功能是由java中的动态代理来实现的。在目标类的基础上增加切面逻辑,生成增强的目标类(该切面逻辑或者在目标类函数执行之前,或者目标类函数执行之后,或者在目标类函数抛出异常时候执行。不同的切入时机对应不同的Interceptor的种类,如BeforeAdviseInterceptor,AfterAdviseInterceptor以及ThrowsAdviseInterceptor等)。

动态 代理有两种实现,基于jdk反射的动态代理,被代理对象的子类,要求被代理类继承接口,
cglib 实现既可以是类也可以是接口, 针对字节码处理。

分析:在编写纯净的springframework 项目时,想使用spring AOP

需要额外引入:

org.aspectj:aspectjweaver:1.9.5
org.assertj:assertj-core:3.16.1

支持切入点表达式和 切面注解

如果 缺少引入则会报错

Caused by: java.lang.IllegalStateException: Failed to introspect Class [org.springframework.aop.aspectj.AspectJAfterReturningAdvice] from ClassLoader [jdk.internal.loader.ClassLoaders$AppClassLoader@e73f9ac]
	at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:481)
	at org.springframework.util.ReflectionUtils.doWithLocalMethods(ReflectionUtils.java:321)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(AutowiredAnnotationBeanPostProcessor.java:267)
	... 33 more
Caused by: java.lang.NoClassDefFoundError: org/aspectj/lang/JoinPoint
	at java.base/java.lang.Class.getDeclaredMethods0(Native Method)
	at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3166)
	at java.base/java.lang.Class.getDeclaredMethods(Class.java:2309)
	at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:463)
	... 35 more
Caused by: java.lang.ClassNotFoundException: org.aspectj.lang.JoinPoint
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
	... 39 more

找不到切点,分析日志发现找不到切点。
可以推测spring-aop 仅仅是对aop的动态代理支持。

@interface Aspect 注解和 xml 中 切面的配置需要引入上述两个包

1.1 通知 advice

公共的逻辑的抽象,模块化的执行代码,例如要记录登录用户的信息,登录方式多种,那么针对登录的Controller 或者Service 可以统一执行 一个advice的方法记录 用户IP 地址,用户登录时间,用户登录Client的信息。

通知定义了切面是什么何时使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题

spring的切面可以应用5中类型的通知

  • 前置通知 Before 目标方法被调用之前
  • 后置通知 After 目标方法完成之后,此时不关系输出的是什么
  • 返回通知 After-returning 目标方法成功执行之后
  • 异常通知 After-throwing 目标方方法抛出异常后调用
  • 环绕通知 Around 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义行为

环绕通知

前四种通知容易理解和实践,重点分析一下环绕通知

环绕通知(@Around):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型,环绕通知可以在方法调用前后完成自定义的行为,它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行

public class XmlAudienceAround {
    
    
    public Object aroundPringLog(ProceedingJoinPoint pjp) {
    
    
        Object rtValue = null;
        try {
    
    
            Object[] args = pjp.getArgs();//得到方法执行所需的参数

            if (args != null) {
    
    
                for (Object o : args) {
    
    
                    if (o instanceof String) {
    
    
                        System.out.println("String: "+o.toString());
                    }
                    if (o instanceof Computer) {
    
    
                        System.out.println("age: " + ((Computer) o).getAge());
                    }

                }
            }

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");

            rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");

            return rtValue;
        } catch (Throwable t) {
    
    
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
            throw new RuntimeException(t);
        } finally {
    
    
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
        }
    }

环绕通知内可以获取 切点的入参,根据入参写对应的逻辑处理,来决定是否执行切点还是执行 advice

可以修改入参

环绕通知可以影响切点代码的执行。

ProceedingJoinPoint is only supported for around advice

环绕通知可以获取连接点入参 出参

后置通知和返回通知的区别

返回通知可以获取到连接点方法返回参数

public void finish(JoinPoint joinPoint, Computer computer) {
        joinPoint.getArgs();
        System.out.println(computer.toString());
        System.out.println("4.finish");
    }
<aop:aspect id="aspects" ref="monitor">
            <aop:pointcut id="pointCutAfterReturning" expression=
                    "execution(* com.fancv.scan.Person.watch(..))"/>
            <aop:after-returning method="all" pointcut-ref="pointCutAfterReturning"/>
            <!--  <aop:around method="finish" pointcut-ref="pointCutAfterReturning"/>-->
            <aop:before method="take" pointcut-ref="pointCutAfterReturning"/>
            <aop:after-returning method="finish" pointcut-ref="pointCutAfterReturning" returning = "computer"/>
        </aop:aspect>

1.2 切点 pointcut

业务层代码中某个点,根据AOP不同的实现技术 这个点的形态不同,基于Spring AOP

动态代理的AOP 精确到类中方法

1.3 连接点 joinpoint

应用执行过程中插入的一个点,例如调用方法时,抛出异常时,修改字段时。

连接点是切面里的一段逻辑,处理 切点触发前后 环绕时的逻辑。

1.4 切面 Aspect

切面是通知和切点的结合

1.5 引入 Introduction

  <aop:declare-parents types-matching="com.fancv.aop.LiLei"
                                 implement-interface="com.fancv.aop.UserValidator" default-impl="com.fancv.aop.UserValidatorImpl"></aop:declare-parents>

给类Lilei 引入了UserValidatorImpl 的方法

具体原理猜测是动态代理

 Chinese lilei = (Chinese)context.getBean("lilei");
        lilei.Say();



        MyPerson person = (MyPerson) context.getBean("person");
        person.st();
        Computer c = new Computer("level", 12, true);
        UserValidator u=(UserValidator)context.getBean("lilei");
        u.validate(c);

1.6 织入 Weaving

一个过程,可以理解为 spring aop 动态代理的过程

编译期 AspectJ 采用的方式

类加载期 特殊的类加载器

运行期 spring aop 采用此方式

二、 AspectJ 编译实现AOP

下一篇继续

参考资料:

  1. 《spring 实战》(第四版)
  2. idea中配置 aspect编译
  3. spring aop 动态代理原理
  4. spriing aop 获取连接点入参出参

猜你喜欢

转载自blog.csdn.net/keep_learn/article/details/115559315