一、学习背景

摸鱼的时候继续复刻demo,没错,同之前一篇文章,在写aop时又发现自己对aop只停留在面试阶段,甚至还不如,完全不会实践,所以在此记录复刻aop用到的的一些且自己已经遗忘的知识。
那么复刻的一个需求点我以最简单来说对于此次要学习的内容:就是通过joinPoint获取方法上的特定注解。
注解代码如下:

import com.uum.common.core.enums.BusinessType;

import java.lang.annotation.*;

/**
 * 自定义注解--操作日志记录
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Operation {

    /**
     * 模块
     */
     String description() default "";

    /**
     * 功能
     */
     BusinessType businessType() default BusinessType.OTHER;

    /**
     * 是否保存请求的参数
     */
     boolean isSaveRequestData() default true;

    /**
     * 是否保存返回结果
     */
    boolean isSaveResponseData() default true;
}

那么这个就是系统操作日志的注解,主要到时候用aop的joinPoint获取标注在方法上该注解联合方法的相关内容将其转化为系统日志并进行存储。那么问题来了,什么是joinPoint?

二、joinPoint对象

2.1、joinPoint

  • 在AOP中,我们知道描述切面的术语有通知(advice),切点(pointcut),连接点(join point)
  • 连接点(joinPoint)就是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
  • 这个类的主要作用就是可以让我们在Advice中获取被增强方法相关的所有信息。通过JoinPoint可以获取被代理方法的各种信息,如方法参数,方法所在类的class对象,然后执行反射操作
    它的主要方法及相关解释如下表格:
方法名功能
Signature getSignature();获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
Object[] getArgs();获取传入目标方法的参数对象
Object getTarget();获取被代理的对象
Object getThis();获取代理对象
其中关键方法就是这个getSignature(),该方法返回Signature对象,而通过该对象我们可以获得被增强方法的信息。

2.2、ProceedingJoinPoint

ProceedingJoinPoint继承JoinPoint,是它的子接口,在joinPoint的基础上对其增强功能。其实主要是在JoinPoint的基础上暴露出 proceed 这个方法。proceed很重要,这个是aop代理链执行的方法。

public interface ProceedingJoinPoint extends JoinPoint {  
       public Object proceed() throws Throwable;  
       public Object proceed(Object[] args) throws Throwable;  
 } 

环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的

  • 暴露出这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到JoinPoint,,这也是环绕通知和前置、后置通知方法的一个最大区别。这跟切面类型有关), 能决定是否走代理链还是走自己拦截的其他逻辑。建议看一下 JdkDynamicAopProxy的invoke方法,了解一下代理链的执行原理。
  • 注:ProceedingJoinPoint is only supported for around advice
  • 典型的用法如下:
 public Object around(ProceedingJoinPoint point) throws Throwable {
        Signature signature = point.getSignature();
        // AopUtils.getTargetClass(point.getTarget())获取原始对象,例如对于Mapper而言,它获取的是具体代理的Mapper如com.b.mapper.DefaultDsMapper(如果前者继承了后者的话)而不是定义该方法的Mapper如com.b.base.BaseMapper<Info, InfoExample, InfoKey>,如下图
        Type[] types = AopUtils.getTargetClass(point.getTarget()).getGenericInterfaces(); // getGenericInterfaces方法能够获取类/接口实现的所有接口
        Annotation nologgingAnno = ((Class)types[0]).getAnnotation(Nologging.class); // type是所有类型的父接口
        MethodSignature methodSignature = (MethodSignature)signature;
        Method targetMethod = methodSignature.getMethod();
        }

三、Signature对象

3.1、Signature

此接口通常用于跟踪或记录应用程序以获取有关连接点的反射信息,下面是官方给的一个使用的例子

 aspect Logging {
       Logger logger = Logger.getLogger("MethodEntries");
   
       before(): within(com.bigboxco..*) && execution(public * *(..)) {
           Signature sig = thisJoinPoint.getSignature();
           logger.entering(sig.getDeclaringType().getName(),
                           sig.getName());
       }
   }

源码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
 
package org.aspectj.lang;
 
public interface Signature {
    String toString();
    //返回此签名的缩写字符串表示形式
    String toShortString();
    //返回此签名的扩展字符串表示形式
    String toLongString();
    //返回此签名的标识符部分。对于方法,这将返回方法名称。
    String getName();
    //返回表示为 int 的此签名上的修饰符。使用 java.lang.reflect.Modifier 上定义的常量和辅助方法来操作它,
    // 检查此签名是否公开 java.lang.reflect.Modifier.isPublic(sig.getModifiers());
    //  打印出修饰符 java.lang.reflect.Modifier.toString(sig.getModifiers());
    int getModifiers();
     //返回一个 java.lang.Class 对象,表示声明此成员的类、接口或方面。对于成员内声明,这将是声明成员的类型,而不是按词法写入声明的类型。使用 SourceLocation.getWithinType() 获取在词法上出现声明的类型。
    //为了与 java.lang.reflect.Member 保持一致,这个方法应该被命名为 getDeclaringClass()
    Class getDeclaringType();
    //返回声明类型的完全限定名称。这等效于调用 getDeclaringType().getName(),但是为了更高的效率缓存了结果
    String getDeclaringTypeName();
}

HttpClient请求

GET http://localhost:8080/test/testAnno?name=Indian frends
Accept: application/json

运行结果

I'm fine ,thank you
toString; String com.mz.testSpring.controler.testSpringControler.sayHello(String)
toShortString; testSpringControler.sayHello(..)
toLongString; public java.lang.String com.mz.testSpring.controler.testSpringControler.sayHello(java.lang.String)
getName; sayHello
getModifiers; 1
getDeclaringType; com.mz.testSpring.controler.testSpringControler
getDeclaringTypeName; com.mz.testSpring.controler.testSpringControler

可以看出,Signature 也是为了获取原始方法的各种信息而存在的,并且信息更加的简洁,方便我们截取,他与JoinPoint合作可以让我们更方便的获取原始方法的各种信息

3.2、MethodSignature

当然我们通过Signature上面的方法还是不能很方便达成我们获取方法上的注解等操作。因为我们获取不了方法本身这个Method类。
那么这时MethodSignature就出现了,顾名思义,它就是方法签名,可以看下它的源码:

public interface MethodSignature extends CodeSignature {
    Class getReturnType();      /* name is consistent with reflection API */
	Method getMethod();
}

它就提供了两个方法,其中关键点就在于getMethod()方法,它可以获取被增强的方法本身Method这个类,通过该类就类似反射操作了,可以通过它的API完成获取注解等操作。
那怎么通过Signature得到MethodSignature呢
其实很简单,可以看一下它的依赖关系图:
在这里插入图片描述
所以你应该就明白了,强转即可。
那么我们就可以完成通过Signature获取注解的操作,方法如下:

 private Optional<Operation> getAnnotationLog(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        if(method != null){
            return Optional.ofNullable(method.getAnnotation(Operation.class));
        }
        return Optional.empty();
    }

四、参考链接

https://blog.csdn.net/M_amazing/article/details/121747188
https://blog.csdn.net/qq_46940224/article/details/125960508
https://blog.csdn.net/qq_15037231/article/details/80624064

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐