0
点赞
收藏
分享

微信扫一扫

JavaEE进阶 - Spring AOP - 细节狂魔

爱读书的歌者 2022-09-01 阅读 214

文章目录

1.什么是 Spring AOP?


2、为什么要使用 AOP?

这个其实在前面已经讲得很清楚了。
直白来说:使用 AOP 的主要原因:
1、有些代码通用性较强,并且使用频繁,冗余度高。使用 AOP 可以降低使用的成本。
2、处于对 业务的安全性来考虑,不得不做一个安全的校验。类似的业务有很多,于是 使用 AOP 这种思想,是非常好的。既能降低代码量,又能保证 安全性。

基于这两个主要的原因,所以我们要使用 AOP:在一个统一的位置,进行统一的处理。让后面写代码的时候,程序员没有后顾之优,就是不需要担心安全 性问题。而且,由于是在同一个位置实现的,所以,不会影响到其它代码的执行。

除了统⼀的⽤户登录判断之外,AOP 还可以实现:
1、统⼀⽇志记录

2、统⼀⽅法执⾏时间统计

3、统⼀的返回格式设置

4、统⼀的异常处理

5、事务的开启和提交

也就是说使⽤ AOP 可以扩充多个对象的某个能⼒,所以 AOP 可以说是 OOP(Object Oriented Programming,⾯向对象编程)的补充和完善。


Spring AOP 应该怎么学习呢?


AOP 组成

切⾯(Aspect)

切⾯是包含了:通知、切点和切⾯的类,相当于 AOP 实现的某个功能的集合。

连接点(Join Point)

切点(Pointcut)

切点:就是定义 AOP 拦截的规则。
前面说到:切面 是有 切点 和 通知组成的。
也就是说:切面 不止有一个切点 和 通知。

另外,切面是一个类,类里面具体要实现什么方法,是切面说的算的!
切面,就像公司的总经理,负责发布任务,切点,就是中层领导,规划任务和人员,制定计划。
不同的人,负责工作内容是不一样的,每一个人就是一个 连接点。
通知,告诉每个人负责工作的具体内容是什么,然后他去实现。

通知(Advice)

通知:定义了切⾯是什么,何时使⽤,其描述了切⾯要完成的⼯作,还解决何时执⾏这个⼯作的问题。

Spring 切⾯类中,可以在⽅法上使⽤以下注解,设置⽅法为通知⽅法,在满⾜条件后会通知本⽅法进⾏调⽤:
前置通知使⽤ @Before:通知⽅法会在⽬标⽅法调⽤之前执⾏。
后置通知使⽤ @After:通知⽅法会在⽬标⽅法返回或者抛出异常后调⽤。

环绕通知使⽤ @Around:通知包裹了的⽅法(集合中的连接点),在被通知的⽅法收到通知之前和调⽤之后执⾏⾃定义的⾏为。

AOP 整个组成部分的概念如下图所示,以多个⻚⾯都要访问⽤户登录权限为例:

在这里插入图片描述


Spring AOP 实现

Spring AOP 的实现步骤如下:
1、 添加 Spring AOP 框架⽀持。
2、 定义切⾯和切点。
3、 定义通知。

接下来我们使⽤ Spring AOP 来实现⼀下 AOP 的功能,完成的⽬标是拦截所有 UserController ⾥⾯的⽅法,每次调⽤ UserController 中任意⼀个⽅法时,都执⾏相应的通知事件。


1、 添加 Spring AOP 框架⽀持。

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.7.1</version>
</dependency>

2、定义切⾯

在这里插入图片描述


3、 定义切点


4、定义通知。

环绕通知使⽤ @Around:通知包裹了的⽅法(集合中的连接点),在被通知的⽅法收到通知之前和调⽤之后执⾏⾃定义的⾏为。

实现通知方法:在什么时机执行什么方法。
下面,我们以前置方法为例,演示一下。


前置通知


验收阶段


小结

AspectJ语法 详解

在这里插入图片描述


继续演示定义相关通知


1、前置通知 - 已演示


2、后置通知


后置通知:等目标方法执行完成之后,被调用

下面,我们来看一下效果。
在这里插入图片描述
我们可以的出一个结论:
@AfterReturning 修饰的方法执行的优先级 比 @After 修饰的方法执行的优先级更高。


后置通知:如果目标方法在执行期间,抛出异常,会被调用


练习:计算一个方法的执行时间。 - 前篇

那么,问题来了!
前面我不是说: AOP 可以统⼀⽅法执⾏时间的统计嘛。
但是,遇到问题了、
那么,我们该怎么做呢?

下面,我们就来先了解一下 环绕通知。


3、环绕通知

下面我们先来实现一个环绕通知。
在这里插入图片描述
下面我们再来具体看一下环绕通知的执行流程
在这里插入图片描述


练习(环绕通知):计算一个方法的执行时间。 - 后篇


Spring AOP 实现原理 - 升华

Spring AOP ⽀持 JDK Proxy 和 CGLIB ⽅式实现动态代理。

那么,问题来了: 为什么 Spring AOP 动态代理的实现,会有两种方式呢?’
使用 官方的 JDK Proxy 不好吗?为什么还有再加一个 CGLIB。

默认情况下,实现了接⼝的类,使⽤ AOP 会基于 JDK ⽣成代理类,没有实现接⼝的类,会基于 CGLIB ⽣成代理类。

总结:Spring AOP 实现 动态代理的方式,“主力” 为 CGLIB Proxy。“替补” 为 JDK Proxy。
理由: CGLIB Proxy 的性能更高。
“替补” JDK Proxy 上场情况: 目标对象 为 最终类的时候,也就是不满足 CGLIB Proxy 的执行条件的时候,JDK Proxy 才会 “上场”。
在这里插入图片描述


织⼊(Weaving):代理的生成时机

无论是通过哪种方式生成的 动态代理,都会涉及到 代理的生成时机。

在⽬标对象的⽣命周期⾥有多个点可以进⾏织⼊,一共三个点。
也就是说: 我们动态代理生成的时机分为3个部分:

编译期:

类加载器:

运⾏期:

那么,问题来了。
Spring AOP 的动态代理 生成的时机 是在哪一个时期呢?
Spring AOP 的动态代理 生成的时机 是在运行期
(“懒汉模式”:用到的时候,才会去生成。)


JDK 动态代理实现(依靠反射实现) - 了解即可

import org.example.demo.service.AliPayService;
import org.example.demo.service.PayService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//动态代理:使⽤JDK提供的api(InvocationHandler、Proxy实现),此种⽅式实现,要求被代理类必须实现接⼝
public class PayServiceJDKInvocationHandler implements InvocationHandler
{
    //⽬标对象即就是被代理对象
    private Object target;
    public PayServiceJDKInvocationHandler( Object target) {
        this.target = target;
    }
    
    //proxy代理对象,method 执行的目标方法,args 执行方法所需的参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //1.安全检查
        System.out.println("安全检查");
        
        //2.记录⽇志
        System.out.println("记录⽇志");
        
        //3.时间统计开始
        System.out.println("记录开始时间");
        
        
        //通过反射调⽤被代理类的⽅法 - 重点
        // invoke 就是实例反射的意思,把 目标对象 target 和 响应的参数args,传进去
        Object retVal = method.invoke(target, args);
        
        
        //4.时间统计结束
        System.out.println("记录结束时间");
        return retVal;
    }
    public static void main(String[] args) {
    // PayService 它是一个接口,但对接的类 需要根据实际情况来决定
    // 下面就是 对应着 阿里的支付服务的实体类
        PayService target= new AliPayService();
        
       //⽅法调⽤处理器
        InvocationHandler handler =
                new PayServiceJDKInvocationHandler(target);
                
        //创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
        PayService proxy = (PayService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class[]{PayService.class},
                handler
        );
        // 调用 代理类
        proxy.pay();
    }
}

CGLIB 动态代理实现 - 了解即可

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.example.demo.service.AliPayService;
import org.example.demo.service.PayService;
import java.lang.reflect.Method;
public class PayServiceCGLIBInterceptor implements MethodInterceptor {
    //被代理对象
    private Object target;
    public PayServiceCGLIBInterceptor(Object target){
        this.target = target;
    }
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
       //1.安全检查
        System.out.println("安全检查");
       //2.记录⽇志
        System.out.println("记录⽇志");
        //3.时间统计开始
        System.out.println("记录开始时间");
        
        //通过cglib的代理⽅法调⽤
        Object retVal = methodProxy.invoke(target, args);
        
        //4.时间统计结束
        System.out.println("记录结束时间");
        return retVal;
    }
    public static void main(String[] args) {
        PayService target= new AliPayService();
        PayService proxy= (PayService) Enhancer.create(target.getClass(),
                new PayServiceCGLIBInterceptor(target));
        proxy.pay();
    }
}

JDK 和 CGLIB 实现的区别


总结

Spring AOP 是通过动态代理的⽅式,在运⾏期将 AOP 代码织⼊到程序中的,它的实现⽅式有两种:

举报

相关推荐

0 条评论