SpringAOP的实现与原理

目录

一.初步了解何为AOP:

二.实现AOP所用的代理模式:

三.动态代理的实现以及所用技术:

(1)动态代理的技术实现:

1.JDK动态代理:

2.CGLIB动态代理:

(2)JDK动态代理与CGLIB动态代理的区别:

(3)JDK代理与CGLIB代理的默认调用:

四.SpringAOP的组成、实现方式以及通知类型:

(1)SpringAOP的组成:

(2)SpringAOP的实现方式:

(3)切点表达式:

1.execution表达式:

2.自定义注解:

(4)SpringAOP的通知类型: 

五.总结:


一.初步了解何为AOP:

AOP和IOC一样,都是一种思想,IOC是控制反转,而AOP则是通过代理模式实现的思想 (面向切面编程——通过引入横切关注点,将其与核心业务逻辑分离,并以模块化的方式进行管理。它通过切面来描述横切关注点,切面是对横切关注点的封装。切面定义了在何处、何时和如何应用横切关注点。在 AOP 中,切面可以横跨多个对象,独立于核心业务逻辑)。什么是代理模式呢?这里简单理解就类似于,我们买房子,按照直观来看,就是我们和房东之间的一场交易,但是,现实中,我们往往不会和房东直接进行联系,而是会先找到个房屋中介,再经由中介来帮我们联系房东实现一系列的后续操作等,而这个中介就类似于代理模式,代理模式,就是来控制方法的调用的。

了解了代理模式,再来了解一下AOP的优点与常用情景。首先是AOP的优点,就像中介一样,我们的代码,也不会直接的影响到其它的类或方法了,所以,之就直接的降低了我们代码的耦合性,其次,通过代理,我们可以对目标方法进行补充和增强,就像现实生活中,中介也会包给我们许多的后续服务一样,可以更加完善我们的一些基础功能。以上呢,就是AOP的一些基本优势。接下来就是它的使用场景了。在实际的开发中,我们可能会遇到一种情况——频繁的出现相同的代码,且不能避免,并且调用时机等还比较随机的,当出现以上情况时,就可以用AOP来很好地解决了,我们可以通过注解等手段,在目标方法上进行注明,这样一来,就可以极大的降低代码的重复率,并且,由于将代理交给了Spring,我们也就无需担心调用时机的问题,这就是AOP的常用环境——统一管理。

二.实现AOP所用的代理模式:

上面简单了解了代理模式,这里就来详细的讲解一下,实现AOP用的代理模式是哪些。

代理模式是有很多种的,比如远程代理、虚拟代理、保护代理等等,而实现AOP所用的只是其中几种而已,其中常见的就是静态代理和动态代理。接下来,就来简单介绍一下两种代理的含义。

静态代理——静态代理是一种在编译时就已经确定代理关系的代理方式。在静态代理中,代理类和被代理类都要实现同一个接口或继承同一个父类,代理类中包含了被代理类的实例,并在调用被代理类的方法前后执行相应的操作。静态代理的优点是实现简单,易于理解和掌握,但是它的缺点是需要为每个被代理类编写一个代理类,当被代理类的数量增多时,代码量会变得很大。

动态代理——动态代理是一种在运行时动态生成代理类的代理方式。在动态代理中,代理类不需要实现同一个接口或继承同一个父类,而是通过 Java 反射机制动态生成代理类,并在调用被代理类的方法前后执行相应的操作。动态代理的优点是可以为多个被代理类生成同一个代理类,从而减少了代码量,但是它的缺点是实现相对复杂,需要了解 Java 反射机制和动态生成字节码的技术。

说了这么多,那在实际开发中,和SpringAOP中使用的又是哪一种实现技术呢?其实这里可以很容易的得出结论,那必然是动态代理,毕竟,相较于静态代理,动态代理才更符合开发的需求,那么新的问题产生了,动态代理又是怎么实现的,它又使用了哪些技术呢?

三.动态代理的实现以及所用技术:

(1)动态代理的技术实现:

实现动态代理所用的技术有两种,分别是JDK动态代理和CGLIB动态代理,二者有所区别,后面会提到,这里先来看一下他们每种的实现代码,重点在于理解,因为正常情况下这个不需要我们来写。

1.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代理对象
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        System.out.println("安全检查");
        System.out.println("记录日志");
        System.out.println("记录开始时间");

        //通过反射调用被代理类的方法
        Object retVal = method.invoke(target, args);

        System.out.println("记录结束时间");
        return retVal;
    }

    public static void main(String[] args) {

        PayService target=  new AliPayService();
        //方法调用处理器
        InvocationHandler handler = 
            new PayServiceJDKInvocationHandler(target);
        //创建一个代理类:通过被代理类、被代理实现的接口、方法调用处理器来创建
        PayService proxy = (PayService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class[]{PayService.class},
                handler
        );
        proxy.pay();
    }
}

上述代码就是JDK动态代理的实现,JDK 动态代理是一种使用 Java 标准库中的java.lang.reflect.Proxy 类来实现动态代理的技术。在 JDK 动态代理中,被代理类必须实现一个或多个接口,并通过 InvocationHandler 接口来实现代理类的具体逻辑。具体来说,当使用 JDK 动态代理时,需要定义一个实现 InvocationHandler 接口的类,并在该类中实现代理类的具体逻辑。然后,通过 Proxy.newProxyInstance() 方法来创建代理类的实例。该方法接受三个参数:类加载器、代理类要实现的接口列表和 InvocationHandler 对象,JDK动态代理的优点在于实现简单,易于理解,缺点在于,JDK的被代理类必须是实现了一个或多个接口的目标类。

2.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 {
        System.out.println("安全检查");
        System.out.println("记录日志");
        System.out.println("记录开始时间");

        //通过cglib的代理方法调用
        Object retVal = methodProxy.invoke(target, args);

        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();
    }
}

CGLIB 动态代理是一种使用 CGLIB 库来实现动态代理的技术。在 CGLIB 动态代理中,代理类不需要实现接口,而是通过继承被代理类来实现代理。 具体来说,当使用 CGLIB 动态代理时,需要定义一个继承被代理类的子类,并在该子类中实现代理类的具体逻辑。然后,通过 Enhancer.create() 方法来创建代理类的实例。该方法接受一个类作为参数,表示要代理的类。CGLIB的优点在于相较于JDK动态代理来说,CGLIB动态代理不仅可以代理接口类,还可以代理普通类,代理更加全面,而它的缺点过于复杂,需要了解CGLIB库。

(2)JDK动态代理与CGLIB动态代理的区别:

 通过对于代码实现的理解,我们可以看出第一个区别——JDK动态代理只能代理接口类,而CGLIB不仅可以代理接口类,还可以代理普通类,并且,二者出自不同的库。那么除此之外他们还有没有别的不同呢?答案当然是有的,下面来一一补充一下:

1.JDK 动态代理使用 java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler 来生成代理对象;CGLIB 动态代理使用 CGLIB 库来生成代理对象。

2.JDK 动态代理生成的代理对象是目标对象的接口实现;CGLIB 动态代理生成的代理对象是目标对象的子类。

3.CGLIB动态代理的代理对象一定不能是被final修饰的,包括它代理的方法也不能是final的,而JDK动态代理则没有这个限制。

4.据未经本人亲自试验过的,从别处听说来的说法,JDK动态代理比CGLIB动态代理更加高效,生成代理对象的速度更快,貌似是说,在JDK8以上的版本中,对JDK动态代理进行了优化,导致它的效率更高于CGLIB动态代理。

上面就是二者的区别,那么,Spring所用的是哪一种呢?答案是两者均有,那么,什么时候用JDK的,什么时候用CGLIB呢?下面就来说明一下。

(3)JDK代理与CGLIB代理的默认调用:

在Spring2以前,默认使用的是JDK动态代理,而到了Spring2之后,就默认使用的CGLIB动态代理了。不过,这个优先选用哪种代理是可以通过我们来进行手动指定的,方法如下:

通过设置spring.aop.proxy-target-class的值来实现,当值为false时,若被代理类未实现接口,则会使用CGLIB动态代理,反之则使用JDK动态代理,值为true时,就是我们之前说的默认情况了。

四.SpringAOP的组成、实现方式以及通知类型:

前面了解过了AOP的原理和各种实现,接下来,就来了解一下SpringAOP的组成和实现方式等。

(1)SpringAOP的组成:

前面提到了AOP是一种面向切面编程思想,不过,粗略的一谈也是毫无实际意义的,那么,什么叫切面呢?切面又包括什么呢?下面将会一一列举出来:

1.连接点——简单来讲就是被代理的方法,仔细的来说就是程序在执行时的某个特定点,不过这个概念有些模糊,简单的理解成被代理的方法即可。

2.切点——顾名思义,就是要进行切割的点,也就是要织入切面的位置,用来定义哪些连接点被切面关注。以上概念也许不好理解,不过不要紧,后续会在代码中再次提到,就会很直观了。

3.切面——切面是横切关注点的模块化单元,它将通知和切点组合在一起,描述了在何处、何时和如何应用横切关注点。

4.通知——就是代理逻辑,代理类的实际代码内容。官方的来说就是,切面在特定切点上执行的代码,包括在连接点之前、之后或周围执行的行为。

以上就是AOP思想的组成,也是看懂代码的重要一步。

(2)SpringAOP的实现方式:

SpringAOP的组成主要有两个部分,第一个是aspectj,第二个就是Spring,那么,具体的体现在哪里呢?下面来看一个代码片段

上述代码片段中,先不要看其他的,先看@Aspect这个注解,@Aspect注解就是出自aspectj的,这也就是为什么,SpringAOP的实现方式中有aspectj,而Spring呢,则是因为一个内部原因,我们所使用的这个@Aspect注解,它内部的实现逻辑,其实并不是aspectj的@Aspect注解。这句话大概听起来有点怪,但先别懵,其实,这里就只是用了@Aspect这么个名字而已,SpringAOP使用了这个名字,所以需引入aspectj,但是,SpringAOP又并不想使用aspectj给@Aspect实现的逻辑,于是,Spring就自己又重新实现了一下@Aspect注解的内部逻辑,这也就导致了,为什么SpringAOP的实现方式有两个部分。这里再看一下这个代码段,已经很清晰的标识出了切点,切面,通知等,结合上面的概念,也是不难理解的,不过这里并没有提到连接点,这里正好用代码来补充一下,在上述代码中,@Around注解后的()中,有一个execution,而execution中的内容就是连接点(不过这里说的其实还是挺不准确的,下面介绍execution表达式的时候,将会准确的进行说明)。

(3)切点表达式:

在上述代码段中,提到了一个叫做execution表达式的东西,而这个东西,就是接下来要概括的切点表达式,但是之一。

1.execution表达式:

这里这次再来看连接点,就会更直观了,也更准确,这里连接点就是类名.后的方法,这个才是真正意义上的连接点。说回正题,execution表达式,是用于方法的表达式,execution是根据方法名来进行匹配的。剩下的,在上图中已经清晰的表明了,直接看就好了。

2.自定义注解:

对于execution表达式,它仅适用于规则的代理,什么是规则的代理呢,就是代理一个类下的目标方法,当需要代理多个类中的几个目标方法时,execution表达式就显得麻烦了,而这时,我们就可以使用自定义注解来进行代理了,而自定义注解,就要用到一个标签——@annotation,通过这个标签,就可以来指定,当前方法逻辑对哪个注解有效,就相当于,在外部实现了自定义注解的注解逻辑(在内部实现逻辑的话不用)。@annotation注解是根据括号中的注解来进行匹配的。具体代码可以自行去找,也很好理解。另外,这里在补充一下,自定义注解必备的两个注解,也是自定义注解必须遵守的规则之一

除了这两个,在自定义注解中还有其他的一些标签,这里就不一一罗列了,可以自行查找。

(4)SpringAOP的通知类型: 

SpringAOP是一种代理思想,在实现的代理类中,会有自己的代理逻辑,那么这些代理逻辑的执行和多个不同代理逻辑间的执行顺序是怎样的呢?这里用两张图片进行了总结。

这里只要掌握了切面执行的优先级和通知执行的先后顺序即可。 

五.总结:

抽象的概念很多,重点在于掌握AOP的原理,理明AOP的思想还有一些细碎的点,并且,此处可拓展出的问题有很多,可以多多进行思考。

  • 34
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值