带你理解Spring AOP

本文深入讲解了面向切面编程(AOP)的概念,包括其在软件开发中的应用背景、AOP框架的发展历程,以及Spring框架中AOP的实现原理。探讨了AOP组件如joinpoint、pointcut、advice和aspect的作用,分析了SpringAOP如何通过动态代理技术实现织入,为读者提供了全面的AOP知识体系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

AOP概述

  • 在我们的日常开发中,除了正常业务逻辑外,还可能经常会需要在业务逻辑的特定位置加入日志,以便于调试和问题分析。但是这种插入日志的逻辑和业务逻辑间并不存在连续性和依赖性,这种逻辑侵入随着项目的不断发展,会导致项目越来越来臃肿,同时也更加难以管理。为了解决这个问题,优秀的前辈们推出了AOP(面向切面编程)理念以及很多优秀的AOP框架,其中比较有代表性的就AspectJ,AspectJ通过扩展java语言实现了AOP框架。当然我们的spring框架也实现了自己的AOP框架(Spring AOP),并在spring 2.0后完美的集成了ApectJ。
  • AOP有很多种实现方式,在最早的时候AOP的实现还是通过静态代理的方式实现,例如AspectJ通过自己的ajc编译器在程序编译阶段编译生成静态代理类的Class文件以完成相关切面逻辑,不过这种方式不够灵活,每次有新的接口或逻辑需要使用切面功能都需要修改AspectJ中的切面配置,然后重新启动项目,无疑这是很烦的。随着技术的发展,我们有了更多更好的技术实现,这里介绍两只中常用AOP实现技术,也是我们spring中使用的:
    (1)jdk动态代理:
    java在jdk 1.3后引入了jdk动态代理,可以在运行期间为实现了某个接口的对象动态的是生成代理对象,它是在运行期间通过反射实现的,灵活性很好了,但是较编译生成Class文件的实现方式,性能差了些,另外所有需要进行代理的对象都必须要实现某个接口,这是动态代理最大的硬伤了。
    (2)cglib动态字节码增强:
    大家都知道,jvm的类加载器并不在乎class文件如何生成,只要是满足规范的class文件都能加载运行。一般我们的java应用程序都是通过javac编译生成class文件,但是只要我们满足class文件规范,我们完全可以使用cglib等字节码工具在程序运行期间动态的构建Class文件。在我们从本地的class文件加载类文件并构建对象时,可以使用cglib动态的生成该对象的代理对象。

静态代理与动态代理

AOP的实现是基于代理模式的,所以我们需要通过了解代理模式来学习AOP,接下来我来介绍下静态代理和动态代理的实现。
1. 静态代理:
我们可以看下下面的代码:

public interface Subject {
    String getName();
}

public class RealSubject implements Subject {
    public String getName() {
        return ”东哥真帅!“;
    }
}

public class Proxy implements Subject {
    private RealSubject realSubject;
    public Proxy(Subject realSubject){
        this.realSubject = realSubject;
    }
    public String getName() {
        return realSubject.getName() + ”东哥真是666!“;
    }
}

public class Main {
    public static void main(String[] args) {
         Subject realSubject = new RealSubject();
         Subject proxy = new Proxy(realSubject);
         proxy. getName();
}

我们可以看到真的是灰常简单,代理对象和原始对象都实现了Subject,代理对象引用了原始对象,并在接口调用时利用了realSubject的getName()方法,只不过在realSubject返回数据的基础上加了些文字,没错这就是最简单的静态代理。但这种模式存在一个致命的问题:getName()函数可能并不仅仅只有Subject接口实现了,其他接口可能也实现了这个函数,例如我再有个Topic接口,它也有这个getName()方法。那么当我的切面需要切到所有实现getName的函数时,我们还需要在为Topic接口实现一套类似上面的代码,这要搞下去的话累都累死了,为了解决这个问题,我们引出了动态代理。我们接下来看下基于jdk和cglib实现的动态代理。

2. 动态代理:

(1)jdk动态代理代码:

public class RequestInvocationHandler implements InvocationHandler {
        private Object target;
        public RequestInvocationHandler(Object target){
            this.target = target;
        }
        
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if(method.getName().equals("getName")){
                return method.invoke(target, args) + "东哥真是666!";
            }
            return null;
        }
    }
    
    Subject subject = (Subject) Proxy.newProxyInstance(
            ProxyRunner.class.getClassLoader(),
            new Class[]{Subject.class},
            new RequestInvocationHandler(new RealSubject()));
    subject.getName();

    Topic topic = (Topic) Proxy.newProxyInstance(
            ProxyRunner.class.getClassLoader(),
            new Class[]{Topic.class},
            new RequestInvocationHandler(new RealTopic()));
    topic.getName();
    

jdk动态代理的实现主要依赖于java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。我们可以看出jdk动态代理是靠实现特定接口,我们在平时开发中不可能所有的对象都实现特定的接口,cglib为我们解决了这个问题。

(2)cglib实现动态代理代码:

    public class RequestInvocationHandler implements MethodInterceptor {
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            if (method.getName().equals("getName")) {
                return methodProxy.invokeSuper(o, objects) + "东哥真是666!";
            }
            return null;
        }
    }

    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Subject.class);
    enhancer.setCallback(new RequestInvocationHandler());

    Subject proxy = (Subject) enhancer.create();
    proxy.getName();
    

我们可以看到cglib通过继承目标对象,并覆写父类方法来实现动态代理,通过字节码生成技术在运行期间动态的自动帮我们构建代理对象。由于cglib采用继承覆写父类方法实现,所以也存在一定局限,当父类的方法为final、static、private类型时,cglib无法对方法进行代理,只能直接调用目标对象的方法。

AOP组件介绍

在具体了解整个AOP的运行流程前,我们先来看下AOP中几个比较重要的组件,当然这些术语都是参照ApectJ中的概念。

组件名概念
joinpointAOP功能模块需要织入的地方, 也有人称之为系统执行点,在ApectJ中有很多执行点类型,例如方法调用、方法执行、字段设置、类初始化等,但是在Spring Aop目前仅支持方法调用类型的切入点,不过已经能够我们大部分的业务开发了。
pointcutpointcut就是joinpoint的表述方式,通过pointcut的表达式我们就可以寻找到满足条件joinpoint。
adviceadvice是切入逻辑的载体,简单的说就是我们要织入的逻辑代码,他有很多种类型,包括Before Advice、After Advice等等,不过我们最常用的还是Around Advice,它同时包含了其他几种的功能。
aspect(advisor)aspect是将joinpoint和pointcut模块化封装的AOP概念实体,在我们SpringAop中也称为advisor,且一般一个advisor仅包含一个joinpoint和一个pointcut。
weaverweaver就是织入器,负责将逻辑织入到joinpoint的具体执行者。
target object被织入的对象

Spring AOP织入原理

我们先来看下伪代码:

// 新建织入者weaver
ProxyFactory weaver = new ProxyFactory(new Executable());
        
// 新建根据接口名匹配的advisor(aspect)
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
// 设置pointcut,在这里是需要织入的接口名,"execute"相当于pointcut
advisor.setMappedName("execute");
// 设置Advice,ExecutableAdvice中包含了代码逻辑
advisor.setAdvice(new ExecutableAdvice());
        
// 将advisor传递给weaver
weaver.addAdvisor(advisor);
// weaver通过ProxyFactor获取代理类
Executable proxyObject = weaver.getProxy();
        
proxyObject.execute();

上面的伪代码实际上已经揭示了AOP的完整流程:
(1)通过advisor包装pointcut和advice数据
(2)然后weaver利用advisor内的数据来获取代理对象
通过上面的逻辑分析,我们可以知道,对于SpringAOP的实现,最重要的有两点即pointcut和advice数据的获取代理对象的生成,那我们接下来就分为这两部分着重讲解下。

(一)pointcut和advice数据的获取

其实上面的伪代码是spring早期的实现方式,如上面代码所示,当时的pointcut只能是方法名或者对象类型(支持正则表达式),而advice需要通过实现特定的接口来实现,advisor也是通过继承特定的系统类实现的,最后还需要将bean的信息以xml的形式保存起来以支持IoC。
当然现在没那么麻烦了,我们看下目前我们的使用方式(基于注解的代理):

@Aspect
@Component
class Aspect {

    @Pointcut("@annotation(com.xxx.xxx.xxx)")
    public void servicePointcut() {
    }

    @Around("servicePointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        xxxxxxxxxxxxxxx;
        xxxxxxxxxxxxxxx;
        return xxx;
    }
}

现在我们有注解支持并且集成了AspectJ,可以使用AspectJ表达式,但底层的实现原理还是一样的。我们现在使用的weaver在注入到容器后,容器会自动寻找容器内所有标识了@Aspect的advisor(aspect)对象,获取到advisor(aspect)对象后,会通过反射获取@Pointcut内的表达式,然后通过AspectJ的类库解析生成pointcut对象,最后ProxyFactory便可以利用这些数据生成代理对象了,其实原理还是一样的,只是加了层封装,更加方便了我们的业务开发。

(二)代理对象的生成

接下来就是最重点的部分了,其实也很简单,需要大家有足够的IoC知识,可以看下我的IoC原理文章:深入了解spring IoC
在获取到了pointcut和advice数据后,weaver便开始了代理对象的构造了。我们在上面的代码中可以看到weaver的类型是ProxyFactory,ProxyFactory是最简单基本的一个织入器实现类,目前我们最经常使用的织入器实现类是AnnotationAwareAspectJAutoProxyCreator,不过实现原理都是一样的:都是基于BeanPostProcessor。我们在IoC原理中介绍过BeanPostProcessor,BeanPostProcessor可以干预Bean的实例化过程,它有点类似于责任链,任何一个bean的构建都需要经过这条链,只有执行完所有BeanPostProcessor后才会返回实例化的对象。AOP实现了BeanPostProcessor,他在目标对象实例化后利用反射或cglib生成了目标对象的动态代理对象(实现逻辑见上面:jdk动态代理代码实现/cglib实现动态代理),然后直接将代理对象返回给了使用方。这样使用方使用的便是经过动态代理后的对象,便实现了AOP的功能。很简单吧!🙃🙃🙃🙃

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

斜阳雨陌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值