SpringAOP如何获取方法参数上的注解

网友投稿 284 2022-12-20

SpringAOP如何获取方法参数上的注解

SpringAOP获取方法参数上的注解

一、示例

① 如下代码,自定义一个参数注解@Test,并将其使用到方法参数上,用于标注需要检验的参数

/**

* 自定义注解,用于参数

*/

@Target(PARAMETER)

@Documented

@Retention(RetentionPolicy.RUNTIME)

public @interface Test{

}

/**

* 接口层,使用使用@Test注解标记参数

*/

@RestController

@RequestMapping("/v1/test")

public class TestController {

@PostMapping(value = "/email", produces = "application/json")

public String send(@RequestBody @Test MailSendDTO mailSendDTO) {

//TODO 业务处理

return "SUCCESS";

}

}

② 通过切面拦截该方法,从连接点获取signature,并将signature强转为MethodSignature,从而从MethodSignature对象可以获取拦截的方法对象以及方法参数注解

@Aspect

@Configuration

public class ValidateAop {

/**

* 切点配置,表达式, 在com.laoxi.test.controller包下,所有的类public的任意参数的方法

*/

@Pointcut("execution(public * com.laoxi.test.controller.*.*(..))")

public void validate(){}

@Before("validate()")

public void doBefore(JoinPoint joinPoint){

Object[] params = joinPoint.getArgs();

if(params.length == 0){

return;

}

//获取方法,此处可将signature强转为MethodSignature

MethodSignature signature = (MethodSignature) joinPoint.getSignature();

Method method = signature.getMethod();

//参数注解,1维是参数,2维是注解

Annotation[][] annotations = method.getParameterAnnotations();

for (int i = 0; i < annotations.length; i++) {

Object param = params[i];

Annotation[] paramAnn = annotations[i];

//参数为空,直接下一个参数

if(param == null || paramAnn.length == 0){

continue;

}

for (Annotation annotation : paramAnn) {

//这里判断当前注解是否为Test.class

if(annotation.annotationType().equals(Test.class)){

//校验该参数,验证一次退出该注解

//TODO 校验参数

break;

}

}

}

}

}

二、debug

通过debug代码:

可发现连接点实际为MethodInvocationProceedingJoinPoint对象,连接点中的signature则为MethodSignatureImpl对象,是MethodInvocationProceedingJoinPoint的内部类。

三、类图及源码

MethodInvocationProceedingJoinPoint类图,顶级实现了JoinPoint(以后再使用切面的时候,可以看看其他类里面都扩展了哪些方法可以直接使用)

MethodSignaturezntpjIhImpl类图,顶级实现了Signature(以后再使用切面的时候,可以看看其他类里面都扩展了哪些方法可以直接使用)

用AOP拦截自定义注解并获取注解属性与上下文参数(基于Springboot框架)

AOP可以用于日志的设计,这样话就少不了要获取上下文的信息,博主在设计日志模块时考虑了一下此法,整理了一下如何用AOP来拦截你自定义的注解。

自定义注解

首先先自定义一个注解

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface Axin {

/**

* 所属模块

* @return

*/

String module() default "日志模块";

/**

* 动作描述

* @return

*/

String desc() default "无动作";

}

@Documented:注解表明制作javadoc时,是否将注解信息加入文档。如果注解在声明时使用了@Documented,则在制作javadoc时注解信息会加入javadoc。

@Target:用来说明该注解可以被声明在那些元素之前

@Target(ElementType.TYPE) //接口、类、枚举、注解

@Target(ElementType.FIELD) //字段、枚举的常量

@Target(ElementType.METHOD) //方法

@Target(ElementType.PARAMETER) //方法参数

@Target(ElementType.CONSTRUCTOR) //构造函数

@Target(ElementType.LOCAL_VARIABLE)//局部变量

@Target(ElementType.ANNOTATION_TYPE)//注解

@Target(ElementType.PACKAGE) ///包

@Retention:用来说明该注解类的生命周期。

@Retention(RetentionPolicy.SOURCE) —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略

@Retention(RetentionPolicy.CLASS) —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略

@Retention(RetentionPolicy.RUNTIME) —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用.

定义切面

/**

* @author Axin

*/

@Aspect

@Component

public class AxinAspect {

/**

* 这里定义了一个总的匹配规则,以后拦截的时候直接拦截log()方法即可,无须去重复写execution表达式

*/

@Pointcut("@annotation(Axin)")

public void log() {

}

@Before("log()&&@annotation(axin)")

public void doBefore(JoinPoint joinPoint,Axin axin) {

System.out.println("******拦截前的逻辑******");

System.out.println("目标方法名为:" + joinPoint.getSignature().getName());

System.out.println("目标方法所属类的简单类名:" + joinPoint.getSignature().getDeclaringType().getSimpleName());

System.out.println("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName());

System.out.println("目标方法声明类型:" + Modifier.toString(joinPoint.getSignature().getModifiers()));

//获取传入目标方法的参数

Object[] args = joinPoint.http://getArgs();

for (int i = 0; i < args.length; i++) {

System.out.println("第" + (i + 1) + "个参数为:" + args[i]);

}

System.out.println("被代理的对象:" + joinPoint.getTarget());

System.out.println("代理对象自己:" + joinPoint.getThis());

System.out.println("拦截的注解的参数:");

System.out.println(axin.module());

System.out.println(axin.desc());

}

@Around("log()&&@annotation(axin)")

public Object doAround(ProceedingJoinPoint proceedingJoinPoint,Axin axin) throws Throwable {

System.out.println("环绕通知:");

System.out.println(axin.module());

System.out.println(axin.desc());

Object result = null;

result = proceedingJoinPoint.proceed();

return result;

}

@After("log()")

public void doAfter() {

System.out.println("******拦截后的逻辑******");

}

}

匹配规则:

//匹配AOP对象的目标对象为指定类型的方法,即DemoDao的aop的代理对象

@Pointcut("this(com.hhu.DemaoDao)")

public void thisDemo() {

...

}

通知类别:

前置通知(Before advice)- 在目标方便调用前执行通知

后置通知(After advice)- 在目标方法完成后执行通知

返回通知(After returning advice)- 在目标方法执行成功后,调用通知

异常通知(After throwing advice)- 在目标方法抛出异常后,执行通知

环绕通知(Around advice)- 在目标方法调用前后均可执行自定义逻辑

获取上下文信息JoinPoint

JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象. 注意:这用于非环绕通知

方法名

功能

Signature getSignature();

获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息

Object[] getArgs();

获取传入目标方法的参数对象

Object getTarget();

获取被代理的对象

Object getThis();

获取代理对象

方法使用模板:

public void doBefore(JoinPoint joinPoint) {

System.out.println("******拦截前的逻辑******");

System.out.println("目标方法名为:" + joinPoint.getSignature().getName());

System.out.println("目标方法所属类的简单类名:" + joinPoint.getSignature().getDeclaringType().getSimpleName());

System.out.println("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName());

System.out.println("目标方法声明类型:" + Modifier.toString(joinPoint.getSignature().getModifiers()));

//获取传入目标方法的参数

Object[] args = joinPoint.getArgs();

for (int i = 0; i < args.length; i++) {

System.out.println("第" + (i + 1) + "个参数为:" + args[i]);

}

System.out.println("被代理的对象:" + joinPoint.getTarget());

System.out.println("代理对象自己:" + joinPoint.getThis());

}

ProceedingJoinPoint

ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中

方法名

功能

Object proceed() throws Throwable

执行目标方法

Object proceed(Object[] var1) throws Throwable

传入的新的参数去执行目标方法

定义测试方法

//Service接口

public interface AxinService {

String axinRun(String arg1, User user);

}

//实现类

/**

* @author Axin

*/

@Component

public class AxinServiceImpl implements AxinService {

@Axin(module = "print",desc = "打印")

@Override

public String axinRun(String arg1, User user) {

String res = arg1 + user.getName() + user.getAge();

return res;

}

public String axinRun(String arg1, Person person) {

String res = arg1 + person.getName() + person.getAge();

return res;

}

}

//控制类

/**

* @author Axin

*/

@RestController

public class HelloController {

@Autowired

AxinService axinService;

@RequestMapping("/hello")

public String hello() {

User user = new User();

user.setAge(10);

user.setName("张三");

String res = axinService.axinRun("Test:", user);

return "Hello Spring Boot!
"+res;

}

}

测试结果

环绕通知:

print

打印

******拦截前的逻辑******

目标方法名为:axinRun

目标方法所属类的简单类名:AxinService

目标方法所属类的类名:com.axin.springboot.service.AxinService

目标方法声明类型:public abstract

第1个参数为:Test:

第2个参数为:User(id=null, name=张三, age=10, date=null)

被代理的对象:com.axin.springboot.service.AxinServiceImpl@ac2ddcc

代理对象自己:com.axin.springboot.service.AxinServiceImpl@ac2ddcc

拦截的注解的参数:

print

打印

******拦截后的逻辑******

小结

通过上述的代码演示,我们可以自定义一个注解,然后配置切面来拦截有注解的方法,同时也可以获得方法传入的参数来完成你的业务需求。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:解决springboot 启动找不到主类的问题
下一篇:Java数组的运用详解
相关文章

 发表评论

暂时没有评论,来抢沙发吧~