Spring AOP中的代理

Spring AOP中的代理

大家好,我是栗子为

在之前的文章中,小为介绍过框架相关的知识点,和大家聊过bean的生命周期,这一次也是从Spring出发,咱们来聊聊Spring中最核心的一个部分-Spring AOP



01


什么是AOP?



AOP(Aspect Orient Programming),直译过来就是面向切面编程。这是一种编程思想,是对面向对象编程(OOP)的补充。面向对象编程将程序抽象成各个层次的对象,面向切面编程是将程序抽象成各个切面。



02


AOP代理分类


AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。

(1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)植入到Java字节码中,运行的时候就是增强之后的AOP对象。

(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

(3)静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的。静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。



03


Spring AOP动态代理方式



Spring AOP中的动态代理主要有两种方式,JDK动态代理CGLIB动态代理

JDK动态代理

JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。

Proxy类中的newProxyInstance()方法用来生成代理对象,有如下参数

  • Loader:类加载器,用来加载代理对象
  • Interfaces:被代理类实现的一些接口
  • h:实现了InvocationHandler接口的对象

调用这个代理对象的方法时,实际会调用实现InvocationHandler接口的类的invoke()方法,有如下参数:

  • Proxy:动态生成的代理类
  • method:与代理类对象调用的方法相对应
  • args:当前method方法的参数

JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。如果实现了接口,默认采用JDK动态代理,否则使用CGLIB动态代理

CGLIB动态代理

如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

一般是自定义MethodInterceptor并重写Intercept方法,intercept用于拦截增强被代理类的方法,和JDK中invoke方法类似,然后通过Enhancer类的create()创建代理类

举个🌰

创建一个接口Iplay.java

package com.zzw.aopdemo.test;

public interface IPlay {
  String play();
}

Tom和Jerry两个类实现该接口

Tom.java

package com.zzw.aopdemo.test;
import org.springframework.stereotype.Component;

@Component
public class Tom implements IPlay {
  @Override
  public String play() {
    System.out.println("Tom喜欢打篮球");
    return "success";
  }
}

Jerry.java

package com.zzw.aopdemo.test;
import org.springframework.stereotype.Component;

@Component
public class Jerry implements IPlay {
  @Override
  public String play() {
    System.out.println("Jerry喜欢逛街");
    return "success";
  }
}

配置文件AppConfig.java

package com.zzw.aopdemo.test;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackageClasses = {com.zzw.aopdemo.test.IPlay.class})
public class AppConfig 
{
}

测试类

package com.zzw.aopdemo.test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class AppTest {
  public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    Tom tom = context.getBean("tom",Tom.class);
    Jerry jerry = (Jerry) context.getBean("jerry");
    tom.play();
    jerry.play();
  }
}

结果

Tom喜欢打篮球
Jerry喜欢逛街

当需要对指定切面进行代码增强时,我们需要这样做:

  1. 项目中引入spring-aspects依赖

  2. 定义一个切面类PlayAspectJ.java

       import org.aspectj.lang.annotation.Aspect;
       import org.aspectj.lang.annotation.Before;
       import org.springframework.stereotype.Component;

       @Aspect
       @Component
       public class PlayAspectJ {
         // 指明该切面的切点是在com.zzw.aopdemo.test.IPlay上的play方法
         @Before("execution(* com.zzw.aopdemo.test.IPlay.play(..))")
         public void beforePlay()// 方法名不重要!!
           System.out.println("先说说自己的兴趣爱好");
         }
       }
  3. 在配置文件中启动AOP切面功能

    @Configuration
    @ComponentScan(basePackageClasses = {com.zzw.aopdemo.test.IPlay.class})
    @EnableAspectJAutoProxy(proxyTargetClass 
    true)
    public class AppConfig {
    }

    这里将proxyTargetClass参数设定为true,代理方式有接口使用jdk动态代理,如果没有接口使用cglib代理;设置为true时表示强制使用cglib代理

  4. 再次进行测试

结果如下

先说说自己的兴趣爱好
Tom喜欢打篮球
先说说自己的兴趣爱好
Jerry喜欢逛街

AOP中的5种通知类型

注解 通知
@Before 通知方法会在目标方法调用之前执行
@After 通知方法会在目标方法返回或异常后调用
@AfterReturning 通知方法会在目标方法返回后调用
@AfterThrowing 通知方法会在目标方法抛出异常后调用
@Around 通知方法会将目标方法封装起来

现在我们来修改一下PlayAspectJ.java

@Aspect
@Component
public class PlayAspectJ {

  @Before("execution(* com.zzw.aopdemo.test.IPlay.play(..))")
  public void beforePlay(){
    System.out.println("before");
  }

  @After("execution(* com.zzw.aopdemo.test.IPlay.play(..))")
  public void afterPlay(){
    System.out.println("after");
  }

  @AfterReturning("execution(* com.zzw.aopdemo.test.IPlay.play(..))")
  public void afterReturningPlay(){
    System.out.println("afterReturning");
  }

  @Around("execution(* com.zzw.aopdemo.test.IPlay.play(..))")
  public void around(ProceedingJoinPoint pj){
    try {
      System.out.println("Around前");
      pj.proceed();
      System.out.println("Around后");
    } catch (Throwable throwable) {
      throwable.printStackTrace();
    }
  }
}

为方便看效果,只看Tom类,结果如下

Around前
before
Tom喜欢打篮球
Around后
after
afterReturning

值得注意的是,使用@Around修饰的环绕通知类型,是将整个目标方法封装起来,在使用时会给它传入ProceedingJoinPoint类型的参数,这个对象是必须的,并且需要调用ProceedingJoinPointproceed()方法。如果没有调用该方法,执行结果为:

Around前
Around后
after
afterReturning

总结一下通知的顺序:

环绕前置、普通前置、目标方法执行、环绕正常返回/出现异常、环绕后置、普通后置、普通返回/出现异常



04


总结



关于Spring AOP的内容就介绍到这里,本文只介绍了注解的方式,其实对于切面配置还可以用xml的方式,这个大家可以自行去看一下,本文旨在带着大家了解AOP,熟悉在项目中这一技术的基本使用。那我们今天的内容就到这,最后提一句,勇士nb,库里yyds。

关注六只栗子,面试不迷路。


作者    栗子为

编辑   一口栗子  



原文始发于微信公众号(六只栗子):Spring AOP中的代理

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/88616.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!