前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 中反射、注解、动态代理、AOP 之间的联系

Java 中反射、注解、动态代理、AOP 之间的联系

作者头像
菜皮日记
发布2023-12-18 14:32:14
1620
发布2023-12-18 14:32:14
举报
文章被收录于专栏:菜皮日记菜皮日记
AOP 和 Aspect 是什么?

AOP 即 Aspect Orient Programming 是以一种编程范式,在不同业务中横着切一刀形成一个切面,在此切面上做一些相同的事情。Aspect 就是切面。

规定了一些概念性的东西:

  • Pointcut:是一个(组)基于正则表达式的表达式,有点绕,就是说他本身是一个表达式,但是他是基于正则语法的。通常一个pointcut,会选取程序中的某些我们感兴趣的执行点,或者说是程序执行点的集合。
  • JoinPoint:通过pointcut选取出来的集合中的具体的一个执行点,我们就叫JoinPoint.
  • Advice:在选取出来的JoinPoint上要执行的操作、逻辑。关于5种类型,我不多说,不懂的同学自己补基础。
  • Aspect:就是我们关注点的模块化。这个关注点可能会横切多个对象和模块,事务管理是横切关注点的很好的例子。它是一个抽象的概念,从软件的角度来说是指在应用程序不同模块中的某一个领域或方面。又pointcut 和advice组成。
  • Weaving:把切面应用到目标对象来创建新的 advised 对象的过程。

这些都是概念性的东西。本意是期望通过抽离相同业务逻辑,通过将增强代码动态织入(将增强代理和原有代码结合到一起形成增强后的代码),实现一次编写到处使用。

动态代理和 AOP 关系

动态代理跟 AOP 的思想跟代理有相似之处,都是代码复用。一套增强代码复用到不同地方。区别在于指定复用的方式不同。

动态代理指定增强代码复用在哪里是通过手动编写被代理类来的,而 AOP 则是声明式的,之后通过其他方式自动创建出代理类。类似于一个是命令式,一个是声明式。AOP 是动态代理的一次简化,隐藏了实现细节,让编写和使用动态代理更加简便。

但无论怎么简化,根本都是要生成代理类,只不过这个过程是编译期做,还是在运行时做,是开发人员手动编写还是框架自动生成。

AspectJ 就是编译期生成,可以由开发人员手动执行命令,也可以放在 maven 等自动执行。

Spring AOP 则是使用 JDK 或 CGLib 动态代理,在运行时动态生成的。

AspectJ 和 Spring AOP 都是什么关系

AspectJ 是 eclipse 下的项目,是一个 AOP 的实现框架,是一种对 Java 语言的扩展语言,在编译器将原来的 Java 代码中织入增强代码,生成增强后的 class 文件。

AspectJ 分为

  • 编译时织入:通过 ajc 用编译生成 class 文件
  • 编译后织入:已经 javac 生成 class 文件后,通过处理 class 文件得到新的织入后的 class 文件
  • 加载时织入(LTW):通过java agent机制在内存中操作类文件,可以不需要ajc的支持做到动态织入。

AspectJ 有自己的语法和编译命令,pointcut 定义切点,after 定义通知等。

下面是编译时织入的例子

代码语言:javascript
复制

// 定义被增强类
public class App {

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

    public static void main(String[] args) {
        App app = new App();
        app.say();
    }
}

// 定义切面
public aspect AjAspect {

    // 切入点
    pointcut say():
            execution(* App.say(..));

    // 前置通知
    before(): say() {
        System.out.println("AjAspect before say");
    }
    // 后置通知
    after(): say() {
        System.out.println("AjAspect after say");
    }
}

AspectJ 语法是 Java 的扩展,所以 javac 无法编译,需要使用 AspectJ 提供的编译命令 ajc:

代码语言:javascript
复制

#!/usr/bin/env bash

ASPECTJ_TOOLS=/home/myths/.m2/repository/org/aspectj/aspectjtools/1.8.9/aspectjtools-1.8.9.jar
ASPECTJ_RT=/home/myths/.m2/repository/org/aspectj/aspectjrt/1.8.9/aspectjrt-1.8.9.jar

java -jar $ASPECTJ_TOOLS -cp $ASPECTJ_RT -sourceroots .

生成 class 文件:

代码语言:javascript
复制

// AjAspect 文件
import java.io.PrintStream;
import org.aspectj.lang.NoAspectBoundException;

public class AjAspect
{
  private static Throwable ajc$initFailureCause;
  public static final AjAspect ajc$perSingletonInstance;
  
  public static AjAspect aspectOf()
  {
    if (ajc$perSingletonInstance == null) {
      throw new NoAspectBoundException("AjAspect", ajc$initFailureCause);
    }
    return ajc$perSingletonInstance;
  }
  
  public static boolean hasAspect()
  {
    return ajc$perSingletonInstance != null;
  }
  
  private static void ajc$postClinit()
  {
    ajc$perSingletonInstance = new AjAspect();
  }
  
  static
  {
    try
    {
      
    }
    catch (Throwable localThrowable)
    {
      ajc$initFailureCause = localThrowable;
    }
  }
  
  public void ajc$before$AjAspect$1$682722c()
  {
    System.out.println("AjAspect before say");
  }
  
  public void ajc$after$AjAspect$2$682722c()
  {
    System.out.println("AjAspect after say");
  }
}
代码语言:javascript
复制

// App.class 文件
import java.io.PrintStream;

public class App
{
  public void say()
  {
    try
    {
      // 先调用 AjAspect 的 before,在执行原有的 say 方法内容
      AjAspect.aspectOf().ajc$before$AjAspect$1$682722c();System.out.println("App say");
    }
    catch (Throwable localThrowable)
    {
      // 最后调用 AjAspect 的 after
      AjAspect.aspectOf().ajc$after$AjAspect$2$682722c();throw localThrowable;
    }
    AjAspect.aspectOf().ajc$after$AjAspect$2$682722c();
  }
  
  public static void main(String[] args)
  {
    App app = new App();
    app.say();
  }
}

Spring AOP 常常与 AspectJ 混在一起,因为 Spring AOP 使用了 AspectJ 的注解,如:

代码语言:javascript
复制

package com.ywsc.fenfenzhong.aspectj.learn;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LogAspect {
     @After("execution(* com.ywsc.fenfenzhong.aspectj.learn.SayHelloService.*(..))")
     public void log(){
         System.out.println("记录日志 ...");
     }
}

代码中的 @Aspect、@After 都是 org.aspectj.lang.annotation 包中的注解。

虽然 spring 中使用了这些 AspectJ 的注解定义 AOP,但实际织入则用的是动态代理,是运行时动态执行的,而没用使用 AspectJ 在编译器织入。

Spring 中通过解析标注有 @Aspect 注解的类,通过反射和动态代理的方式生成代理类实现增强。

定义注解:

代码语言:javascript
复制

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    String value() default "";
}

编写 Aspect 切面:

代码语言:javascript
复制

@Aspect
@Component
public class SysLogAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(SysLogAspect.class);


    @Pointcut("@annotation(com.aldeo.common.annotation.Log)")
    public void logPointCut() {
    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        // 目标方法
        Object result = point.proceed();
        long time = System.currentTimeMillis() - beginTime;
      
      Log syslog = method.getAnnotation(Log.class);
        if (syslog != null) {
            // 注解上的描述
            LOGGER.info(syslog.value());
        }
      
        // 保存日志
        try {
            saveLog(point, time);
        } catch (Exception e) {
            LOGGER.error("==================================> saveSysLog.around.exception: " + e.getMessage(), e);
        }
        return result;
    }

在指定方法上标注注解,使用切面,实现 AOP 编程

代码语言:javascript
复制

@Log("测试自定义注解")
public String restPassword(){
    return "成功";
}

Springboot 中的 AOP 实现是基于 JDK 还是 CGLib?

默认是:

  • 如果代理对象有接口,就用 JDK 动态代理
  • 如果代理对象没有接口,那么就直接是 Cglib 动态代理。

SpringBoot 2.0 之后提供了一个配置项 spring.aop.proxy-target-class,他的自动配置文件中可以看到:

代码语言:javascript
复制

@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class, AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = false)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
	public static class JdkDynamicAutoProxyConfiguration {

	}

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = true)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
	public static class CglibAutoProxyConfiguration {

	}

}

如果此配置没有配置,或配置成了 true,都是用 CGlib,只有配置成 false 时才使用 JDK。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • AOP 和 Aspect 是什么?
  • 动态代理和 AOP 关系
  • AspectJ 和 Spring AOP 都是什么关系
  • Springboot 中的 AOP 实现是基于 JDK 还是 CGLib?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档