【微服务】Spring Aop原理深入解析

目录


一、前言

spring 是一个流行的 Java 企业应用程序开发框架,其中的 aop(面向切面编程)是 spring 框架中一个非常重要的概念。可以说在spring框架以及生态体系下,随处可见aop编程思想的运用,借助这一编程思想,在很多特殊的业务场景下,aop的使用让编码变得易扩展、更优雅、更灵活,同时也能很好的解决通用的业务问题,提升开发效率。本文将详细介绍aop的核心技术和底层实现原理。

二、aop概述

2.1 什么是AOP

AOP,即面向切面编程,AOP是一种编程范式,用于在不修改原始代码的情况下向现有应用程序添加新功能。这种编程方式将应用程序分成许多独立的部分,称为切面。这些切面可以在应用程序的不同位置进行编写和维护,从而提高了应用程序的可重用性和可维护性

AOP是OOP(面向对象编程)的延续,主要用于实现横切关注点,比如:日志记录、性能统计、安全控制、事务处理等方面。简单来说,针对应用程序中那些可以预见的,以及归总为公共的处理业务的场景均可以考虑使用aop的编程实现。

2.2 AOP中的一些概念

为了后续更全面深入的掌握aop的使用,有必要了解下spring aop的相关概念。这些概念能够更好的指导我们在编码中对aop的原理的理解。

切面(Aspect)

AOP核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在Spring AOP中,切面通过带有@Aspect注解的类实现。

连接点(Join Point)

AOP核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在Spring AOP中,切面通过带有@Aspect注解的类实现。

连接点(Join Point)

哪些方法需要被AOP增强,这些方法就叫做连接点

通知(Advice)

AOP在特定的切入点上执行的增强处理

常用的通知类型有:

  • before,前置通知 ;
  • after,后置通知 ;
  • afterReturning,返回通知 ;
  • afterThrowing,异常通知 ;
  • around,环绕通知 ;

切入点(Pointcut)

实际真正被增强的方法,称为切入点

2.2.1 aop通知类型

通知(advice)是你希望在横切关注点具体的实现方式,即方法之前触发?执行后触发…,aop中的Advice主要有以下5种类型:

前置通知(Before Advice)

在连接点之前执行的Advice,即方法执行前,使用@Before注解使用这个Advice

返回之后通知(After Retuning Advice)

在连接点正常结束之后执行的Advice,即你的方法正常执行完成之后执行,通过 @AfterReturning注解使用它,前提是你的方法没有抛出异常

抛出(异常)后执行通知(After Throwing Advice)

如果方法执行抛异常的时候,这个Advice就会被执行,通过 @AfterThrowing注解来使用

后置通知(After Advice)

无论连接点通过什么方式退出(方法正常返回或者抛出异常)都会执行在结束后执行这些Advice,通过 @After注解使用

围绕通知(Around Advice)

围绕连接点执行的Advice,方法执行前可以拦截参数,方法执行后可以拿到执行结果,属于几种通知中比较灵活的一种,通过@Around注解使用

2.3 AOP实现原理

Spring AOP 的实现原理是基于动态代理字节码操作的。具体来说,

  • 在编译阶段, Spring 会使用 AspectJ 编译器将切面代码编译成字节码文件;
  • 在运行阶段, Spring 会使用 Java 动态代理或 CGLIB 代理生成代理类,这些代理类会在目标对象方法执行前后插入切面代码,从而实现AOP增强的功能;

Spring AOP 主要使用两种代理方式:JDK动态代理和 CGLIB 代理。如果目标对象实现了至少一个接口,则框架使用JDK动态代理;否则,使用 CGLIB 代理。

2.3.1 aop中的代理实现

基于接口情况下,使用JDK动态代理

JDK动态代理,创建一个接口实现类的代理对象,该接口实现类的代理对象会调用该接口的真实实现,并且在代理对象中调用真实的实现类的方法进行增强

没有接口情况下,使用 CGLIB 动态代理

CGLIB动态代理,创建一个类子类的代理对象,该子类的代理对象会去调用父类中的方法,并且在子类代理对象调用其父类方法后做增强

2.4 静态代理与动态代理

很多同学在理解aop的代理时,很容易对静态代理与动态代理这个概念搞混,顾名思义,静态代理,简单理解就是代理的类或方法定义已经提前定义好了,在需要使用的地方直接调用即可,而动态代理,关键是理解动态这个概念,从程序的角度来说,就是在程序运行过程中,动态生成了目标对象的代理对象。

2.4.1 静态代理实现

定义一个接口

public interface LoginService {
    void login(String userId);
}

接口实现

public class LoginServiceImpl implements LoginService {
    @Override
    public void login(String userId) {
        System.out.println("登录成功,userId:"+userId);
    }
}

实现了LoginService 接口的代理实现类

public class ProxyLoginService implements LoginService {

    private LoginServiceImpl loginServiceImpl;

    public ProxyLoginService(LoginServiceImpl loginServiceImpl){
        this.loginServiceImpl=loginServiceImpl;
    }

    @Override
    public void login(String userId) {
        System.out.println("执行登录之前操作");
        loginServiceImpl.login("user");
        System.out.println("执行登录方法后操作");
    }
}

测试方法

public static void main(String[] args) {
        //创建电影院(静态代理)
        ProxyLoginService proxyLoginService = new ProxyLoginService(new LoginServiceImpl());
        proxyLoginService.login("user1");
    }

静态代理优点

  • 使得真实角色处理的业务更加纯粹,不再去关注一些公共的事情 ;
  • 公共的业务由代理来完成—实现业务的分工;
  • 公共业务发生扩展时变得更加集中和方便 ;

缺点:

  • 每一个代理类都必须实现一遍真实实现类(也就是realMovie)的接口,如果接口增加方法,则代理类也必须跟着修改;
  • 每一个代理类对应一个真实实现类(委托类),如果真实实现(委托类)非常多,则静态代理类就非常臃肿,难以胜任;

三、 jdk动态代理与cglib代理

3.1 jdk动态代理

与静态代理不同,动态代理是根据代理对象,动态创建代理类。这样就可以避免静态代理中代理类接口过多的问题。jdk动态代理是通过反射来实现的,借助Java自带的java.lang.reflect.Proxy,通过固定的规则生成。

jdk动态代理的使用步骤如下:

  • 创建一个需要动态代理的接口,即Movie接口;
  • 创建一个需要动态代理接口的真实实现,即RealMovie类;
  • 创建一个动态代理处理器,实现InvocationHandler接口,并重写invoke方法去增强真实实现中的目标方法;
  • 在测试类中,生成动态代理的对象;

3.1.1 jdk代理示例

自定义一个类,实现InvocationHandler接口,并重写里面的invoke方法

public class ProxyInvocationHandler implements InvocationHandler {

    //需要动态代理接口的真实实现类
    private Object object;

    //通过构造方法去给需要动态代理接口的真实实现类赋值
    public ProxyInvocationHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //方法增强
        System.out.println("方法执行前操作");
        //object是真实实现,args是调用方法的参数
        //当代理对象调用真实实现的方法,那么这里就会将真实实现和方法参数传递过去,去调用真实实现的方法
        method.invoke(object,args);
        //方法增强
        System.out.println("方法执行后操作");
        return null;
    }
}

编写测试类,使用自定义的handler生成代理对象并调用目标方法

public class DynamicProxyTest {

    public static void main(String[] args) {
        //需要动态代理接口的真实实现
        LoginServiceImpl realMovie = new LoginServiceImpl();
        //动态代理处理类
        ProxyInvocationHandler handler = new ProxyInvocationHandler(realMovie);
        //获取动态代理对象
        //第一个参数:真实实现(被代理对象)的类加载器
        //第二个参数:真实实现类(被代理对象)它所实现的所有接口的数组
        //第三个参数:动态代理处理器
        LoginService loginService = (LoginService) Proxy.newProxyInstance(realMovie.getClass().getClassLoader(),
                realMovie.getClass().getInterfaces(),
                handler);
        loginService.login("user");
    }

}

执行结果

3.1.2 jdk动态代理模拟实现

由于jdk的代理是在运行期间动态产生字节码,所以很难看到运行期间代理产生的源码,我们可以模拟其原理进行一个简单的实现,代码如下:

定义一个接口,作为被代理的对象,实现类也一并贴出

    interface MinuService {
        int addMinu();

        void reduceMinu();
    }

    //目标对象实现类
    static class Target implements MinuService {

        @Override
        public int addMinu() {
            System.out.println("tager do addMinu");
            return 1;
        }

        @Override
        public void reduceMinu() {
            System.out.println("tager do reduceMinu");
        }
    }

定义一个handler,handler的作用可理解为作为代理对象中实际执行方法调用的一个入口

    interface MyInvocationHandler {
        Object invoke(Object proxy,Method method, Object[] args);
    }

定义代理类,代理类是核心实现,在代理类中主要做的事情如下:

  • 引用自定义handler;
  • 引用代理类中待执行的目标方法(该方法其实是运行期间动态生成的);
  • 调用handler中的invoke反射调用目标方法,并得到执行结果;
import java.lang.reflect.Method;

public class MyProxy implements JdkInner.MinuService {

    private JdkInner.MyInvocationHandler handler;

    public MyProxy(JdkInner.MyInvocationHandler handler) {
        this.handler = handler;
    }

    static Method addMinu;
    static Method reduceMinu;

    static {
        try {
            addMinu = JdkInner.MinuService.class.getMethod("addMinu");
            reduceMinu = JdkInner.MinuService.class.getMethod("reduceMinu");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    @Override
    public int addMinu() {
        Object invoke = null;
        invoke = handler.invoke(this, addMinu, new Object[0]);
        return Integer.parseInt(invoke.toString());
    }

    @Override
    public void reduceMinu() {
        handler.invoke(this, reduceMinu, new Object[0]);
    }
}

编写测试类,有没有发现这个写法和上述使用jdk的动态代理很像

 public static void main(String[] args) {

        MinuService minuService = new MyProxy(new MyInvocationHandler() {
            @Override
            public Object invoke(Object proxy,Method method, Object[] args) {
                System.out.println("before  invoke method");
                Object result = null;
                try {
                    result = method.invoke(new Target(), args);
                    System.out.println("result :" + result);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
                return result;
            }
        });
        System.out.println(minuService.getClass());
        minuService.addMinu();
    }

如果你想查看jdk动态代理时产生的源码,可以考虑使用arthars进行反编译查看

3.2 CGLIB 代理

CGLIB 代理是一个基于字节码操作的代理方式,它是一个强大的、高性能、高质量的 Code 生成类库,可以在运行期扩展 Java 类与实现 Java 接口,可以为没有实现接口的类创建代理对象。CGLIB 代理会在运行时生成一个目标对象的子类,并覆盖其中的方法,以实现AOP的功能。下面是 CGLIB 代理的实现代码:

public class CglibAopProxy implements AopProxy {
 
    private final AdvisedSupport advised;
 
    public CglibAopProxy(AdvisedSupport advised) {
        this.advised = advised;
    }
 
    @Override
    public Object getProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(advised.getTargetSource().getTargetClass());
        enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
        return enhancer.create();
    }
 
    private static class DynamicAdvisedInterceptor implements MethodInterceptor {
 
        private final AdvisedSupport advised;
 
        public DynamicAdvisedInterceptor(AdvisedSupport advised) {
            this.advised = advised;
        }
 
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            MethodInvocation methodInvocation = new CglibMethodInvocation(
                    advised.getTargetSource().getTarget(),
                    method,
                    args,
                    proxy,
                    advised.getMethodInterceptor(),
                    advised.getTargetSource().getTargetClass()
            );
            return methodInvocation.proceed();
        }
    }
}

3.2.1 cglib代理代码示例

下面是一段使用cglib进行代理的代码

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibTest {

    static class Target {
        public void get() {
            System.out.println("target get");
        }
    }

    public static void main(String[] args) {
        Target target = new Target();
        //拿到代理的类
        Target proxyTarget = (Target)Enhancer.create(Target.class, new MethodInterceptor() {
            @Override
            public Object intercept(Object proxyClass, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("before....");
                Object result = method.invoke(target, args);
                System.out.println("after...");
                return result;
            }
        });
        //执行代理类的方法
        proxyTarget.get();
    }

}

结果看起来和使用jdk代理类似

与jdk代理不同的是,jdk的代理仅能针对接口代理,而cglib生成的代理对象是一个子类,所以需要注意,使用cglib进行代理的时候,父类不能是final的,并且目标类中的代理方法也不能是final的。

3.2.2 cglib代理源码模拟实现

上面的案例了解了如何使用cglib进行代理以及代码的实现,下面来模拟一下其底层源码的实现

定义一个目标类

public class CglibTarget {

    public void add(){
        System.out.println("add()");
    }

    public void add(int num){
        System.out.println("add() :" + num);
    }

    public void reduce(int count){
        System.out.println("reduce() :" + count);
    }
}

定义代理类

通过上面的介绍了解到,cglib的代理对象是通过生成目标对象的子类实现的,所以代理类需要继承目标类

import org.springframework.cglib.proxy.MethodInterceptor;

import java.lang.reflect.Method;

public class CglibProxy extends CglibTarget {

    private MethodInterceptor methodInterceptor;

    public void setMethodInterceptor(MethodInterceptor methodInterceptor) {
        this.methodInterceptor = methodInterceptor;
    }

    static Method add_1;
    static Method add_2;

    static {
        try {
            add_1 = CglibTarget.class.getMethod("add");
            add_2 = CglibTarget.class.getMethod("add",int.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void add() {
        try {
            methodInterceptor.intercept(this,add_1,new Object[0],null);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

    public void add(int num){
        try {
            methodInterceptor.intercept(this,add_2,new Object[]{num},null);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

    public void reduce(int count){
        super.reduce(count);
    }

}

测类类

import com.congge.aop.cglib.CglibProxy;
import com.congge.aop.cglib.CglibTarget;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibTest {

    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        CglibTarget target = new CglibTarget();
        proxy.setMethodInterceptor(new MethodInterceptor() {
            @Override
            public Object intercept(Object targetObj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("before handle");
                Object result = method.invoke(target, args);
                System.out.println("after handle");
                return result;
            }
        });
        proxy.add(10);
    }
}

运行代码,得到如下效果

3.2.3 cglib代理补充说明

在使用cglib代码编码实现中,注意到在intercept方法参数中有一个Method的参数,这个参数是做什么用的呢?

不妨将代码修改成下面这样

public static void main(String[] args) {
        Target target = new Target();
        //拿到代理的类
        Target proxyTarget = (Target)Enhancer.create(Target.class, new MethodInterceptor() {
            @Override
            public Object intercept(Object proxyClass, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("before....");
                //Object result = method.invoke(target, args);
                Object result = methodProxy.invoke(target, args);
                System.out.println("after...");
                return result;
            }
        });
        //执行代理类的方法
        proxyTarget.get();
    }

再次执行,发现仍然可以得到正确的结果,为什么会这样呢?

cglib代理底层通过这种方式为调用者提供了多一种选择,当选择使用methodProxy的invoke方法时,将不反射,而是退化为直接使用原始目标对象去调用方法,某些情况下,可以获得比反射更高的性能。

四、spring aop源码探究

spring底层在aop的代理上是怎么处理的呢,接下来让我们通过源码一探究竟。

4.1 环境准备

4.1.1 引入aop依赖包

只需要引入aspectjweaver即可

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.3</version>
        </dependency>

4.1.2 自定义aop配置类

定义一个切面的配置类,这里扫描的是某个包路径下的所有方法,并且使用了环绕通知,在通知方法里,输出了方法实际执行耗时

@Component
@Aspect
public class AspectConfig {

    @Pointcut("execution(* com.congge.service.aop.*.*(..)))")
    private void pointcut(){

    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long start = System.currentTimeMillis();
        Object proceed = point.proceed();
        long end = System.currentTimeMillis();
        System.out.println("消耗时间:" + (end-start));
        return proceed;
    }

}

4.1.3 测试方法

编写测试方法,验证上述aop切面通知是否生效

@ComponentScan("com.congge.service.aop")
@EnableAspectJAutoProxy
public class SpringApp {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringApp.class);
        UserService userService = context.getBean(UserService.class);
        userService.getUser();
    }

}

运行之后效果如下,说明aop的环绕通知配置生效了

4.2 spring aop切点表达式

在实际开发中,aop编程的一般步骤是,定义切面类,然后定义具体的切点表达式,然后再在具体的通知方法上面引入切点表达式即可。下面列举几种常用的几种自定义切点表达式的写法。

4.2.1 语法结构

例1:对扫描包中的某个类的方法增强

execution(* com.xxx.xxxClass.方法名(..))

例2:对扫描包中的某个类的所有方法增强

 execution(* com.xxx.xxxClass.* (..))

例3:对扫描包中所有类,类中所有方法增强

 execution(* com.xxx.service.*.* (..))

例4:所有带有某个注解的方法或类

@annotation(com.xxx.annotation.xxxAnnotation)

上面举的例子都是去切入具体的某个方法、类,也可以切到某个包下所有的方法,也可以去切某包下带有某注解的方法等等。  

4.2.2 常见的切点表达式示例

下面列举常用的几种切点表达式示例,便于后续的参考

 所有方法执行

execution(public * *(..))

名以”XXX”开头的所有方法执行

execution(* XXX*(..))

XXX接口中所有方法执行

 execution(* com.xxx.XXX.*(..))

service包下所有方法执行

 execution(* com.xxx.service..*.*(..))

service包下的所有连接点

 (仅在Spring AOP中执行方法)

within(com.xxx.service..*)

代理实现XXX接口的任何连接点

 (仅在Spring AOP中执行方法)

this(com.xxx.service.XXXService)

所有带有@XXX注解的方法或类

@annotation(com.xxx.annotation.XXX)

4.3 spring aop源码分析过程

通过断点,在真正执行userService.getUser()之前,我们可以看到,userService已经是一个代理对象,而且不难看出,这是一个jdk动态代理产生的对象(userService接口存在实现类)

于是可以断定,在真正执行目标方法的时候,是代理对象在帮我们执行了,那么代理对象在哪里产生的呢?代理对象是什么时候产生的呢?这就是接下来需要搞清的重点所在。

从getBean入手,一路往下走,通过getBean方法,最终获取到了代理对象

public <T> T getBean(Class<T> requiredType) throws BeansException {
        this.assertBeanFactoryActive();
        return this.getBeanFactory().getBean(requiredType);
    }

通过getBean方法走到下面这个doGetBean方法里,在这个方法中,注意有一个关键的位置:getSingleton,很多同学在看spring源码的时候发现调用栈非常深,经常陷入一种不知道从何看起的状态,这里说过小技巧,你只需要关注你期望得到的目标即可。比如在doGetBean这里,为了快速获取到userService,可以在此处设置条件断点,如下:

以上述getSingleton为例来说,通过这个方法,可能会得到代理对象,需不需要进去看呢,可以先走一步,如果得到的是代理对象,那么说明getSingleton这个方法中是产生代理对象的地方,按照这个思路,我们走一步看看,发现此刻得到的对象就是一个代理对象,说明确实是在getSingleton这个方法中产生了代理对象;

以getSingleton继续深入,进入该方法之后发现,从singletonObjects中拿到的就是代理对象了,这里不禁冒出一个问题来了,singletonObjects中是什么时候将代理对象放进去的呢?

这里不得不说另一个源码的调试和阅读技巧,就是直接看debug中的调用栈,从下面的调用栈可以发现,上一步singletonObjects中存储的这个代理对象就是在其中的某一步产生并放到容器中的;

 这样一来,至少可以说明,产生代理对象的时机在当前这个getSingleton之前,从spring的bean的生命周期来看,要经历解析,创建,初始化,实例化等一些步骤,所以可以将目光定位到创建的过程,即createBean阶段,于是从getBean方法入手作为突破点即可,再次断点进入,来到getBean方法中

通过getBean一路来到getSingleton方法中,该方法即创建初始的userService的核心代码,而且第一次进入的时候发现userService的bean对象还未创建出来,一直来到该方法的如下位置,利用上面的调试技巧发现就是在这个singletonFactory.getObject()方法得到了代理对象

继续进入这个singletonFactory.getObject()方法,就来到创建bean的方法中,即createBean

沿着该方法继续往下走,当走到doCreateBean这里时,发现这个方法执行结束后就成了代理对象

继续进入doCreateBean方法中,当走完initializeBean方法之后,发现就产生了代理对象

那么initializeBean里面发生了什么呢?可以断定这个方法的某个地方最终产生了代理对象,于是来到下面这个方法

进入到applyBeanPostProcessorsAfterInitialization方法中

来到postProcessAfterInitialization里面之后,沿着wrapIfNecessary继续进入

 

历经千辛万苦,最终来到这里,这个方法就是最终产生代理对象的地方,核心创建代理对象的代码就是下面这段

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
            Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
            if (specificInterceptors != DO_NOT_PROXY) {
                this.advisedBeans.put(cacheKey, Boolean.TRUE);
                Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
                this.proxyTypes.put(cacheKey, proxy.getClass());
                return proxy;
            } else {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return bean;
            }
        } else {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
    }

 我们重点关注方法中createProxy这个方法即可

跳过中间的步骤,最终来到createAopProxy方法中,在这个方法中,展示了如何创建代理对象,以及采用哪种方式创建代理对象,即使用jdk动态代理呢?还是cglib方式呢?

从这段代码不难看出,这里做了一个判断,如果目标对象是接口,将采用jdk动态代理,如果目标对象是类,则使用cglib的代理,由于在前面的代码中目标对象是普通的类,所以将会产生一个cglib的代理对象

补充说明:  

在上面判断使用哪种代理方式时,有一个很重要的参数,即判断下面的这个参数的布尔值,通过源码点击进去发现,默认情况下该参数初始值为false

config.isProxyTargetClass()

该参数的意义在于,开发者可以通过强制指定这个参数的值,从而改变代理的方式强制使用cglib,如何修改呢,只需要将下面的注解中改为true即可

五、写在文末

本文通过较大的篇幅全面而深度的总结了aop代理相关的技术点,并且深入到源码层面解读了spring aop的完整过程,aop不仅是spring编程中的重要内容,也是日常开发中运用比较广泛的技术点,有必要对其做深入的理解和掌握。本篇到此结束,感谢观看。

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2023年12月19日
下一篇 2023年12月19日

相关推荐