spring中自定义注解(annotation)与AOP中获取注解
一、自定义注解(annotation)
自定义注解的作用:在反射中获取注解,以取得注解修饰的类、方法或属性的相关解释。
package me.lichunlong.spring.annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; //自定义注解相关设置
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented public @interface LogAnnotation { //自定义注解的属性,default是设置默认值String desc() default "无描述信息";
}
二、自定义注解的使用
package me.lichunlong.spring.service;import me.lichunlong.spring.annotation.LogAnnotation;
import me.lichunlong.spring.jdbc.JdbcUtil;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserService {//与其它注解一样的使用@LogAnnotation(desc="this is UserService")public void add() {System.out.println("UserService add...");}
}
三、AOP中获取注解
// 环绕通知:类似与动态代理的全过程
// 携带参数ProceedingJoinPoint,且必须有返回值,即目标方法的返回@Around(value = "execution(* me.lichunlong.spring.service.*.*(..)) && @annotation(log)")public Object aroundMethod(ProceedingJoinPoint pjd, LogAnnotation log) {Object result = null;System.out.println(log.desc());try {System.out.println("前置通知");result = pjd.proceed();System.out.println("后置通知");} catch (Throwable e) {System.out.println("异常通知");}System.out.println("返回通知");return result;}
使用aspectj的@Around注解实现用户操作和操作结果日志
自定义注解,将需要记录日志的方法进行标记
/** 常用注解说明:* 1. RetentionPolicy(保留策略)是一个enum类型,有三个值* SOURCE -- 这个Annotation类型的信息只会保留在程序源码里,源码如果经过了编译后,Annotation的数据就会消失,并不会保留在编译好的.class文件里* CLASS -- 这个Annotation类型的信息保留在程序源码中,同时也会保留在编译好的.class文件里面,在执行的时候,并不会把这一些信息加载到虚拟 机(JVM)中去.注意一下,当你没有设定一个Annotation类型的Retention值时,系统默认值是CLASS。* RUNTIME -- 在源码、编译好的.class文件中保留信息,在执行的时候会把这一些信息加载到JVM中去的。** 2.ElementType @Target中的ElementType用来指定Annotation类型可以用在哪些元素上* TYPE(类型) -- 在Class,Interface,Enum和Annotation类型上* FIELD -- 属性上* METHOD -- 方法上* PARAMETER -- 参数上* CONSTRUCTOR -- 构造函数上* LOCAL_VARIABLE -- 局部变量* ANNOTATION_TYPE -- Annotation类型上* PACKAGE -- 包上** 3.Documented -- 让这个Annotation类型的信息能够显示在API说明文档上;没有添加的话,使用javadoc生成的API文件找不到这个类型生成的信息*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface TestAnnotation {//操作内容String operation() default "";
}
配置Aspect,创建规则和方法
package com.consumer.interceptor;import com.consumer.annotation.TestAnnotation;
import com.consumer.entity.LogMessage;
import com.consumer.entity.ReturnMessage;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;import org.springframework.stereotype.Component;import java.util.Date;/** 特别注意: Spring的配置文件中添加:** <aop:aspectj-autoproxy />* spring-mvc-dispatcher.xml中天机* <!--通知spring使用cglib而不是jdk的来生成代理方法 AOP可以拦截到Controller-->* <aop:aspectj-autoproxy proxy-target-class="true"/>** <aop:config>节点中proxy-target-class="true"不为true时。* 当登录的时候会报这个异常java.lang.NoSuchMethodException: $Proxy54.login(),*/
@Aspect
@Component
public class LogInterceptor {/*** 环绕通知 用于拦截指定内容,记录用户的操作* pj:ProceedingJoinPoint 是切入点对象* annotation:TestAnnotation 自定义的注解对象* object:Object 方法的第一个参数*/@Around(value = "@annotation(annotation) && args(object,..) ", argNames = "pj,annotation,object")public Object interceptorTest(ProceedingJoinPoint pj,TestAnnotation annotation, Object object) throws Throwable {System.out.println("执行方法 "+pj.getSignature().getName());// 初始化日志数据LogMessage logMessage = new LogMessage();// 获取操作的参数Object[] args = pj.getArgs();if(args.length>=1){// 写入idlogMessage.setManId(args[0].toString());}// 写入操作时间logMessage.setDate(new Date().getTime());// 写入操作名logMessage.setOperation(annotation.operation());// 执行操作,获取操作的返回结果ReturnMessage returnMessage = (ReturnMessage) pj.proceed();// 写入操作结果logMessage.setSuccess(returnMessage.getStatus());// 如果操作结果失败,写入失败原因if(!logMessage.isSuccess()){logMessage.setReason(returnMessage.getMsg());}//输出日志信息System.out.println(logMessage.toString());// 输出结束标识System.out.println("执行结束 "+pj.getSignature().getName());// 返回操作的原本结果return returnMessage;}
}
添加到配置文件xml
<context:annotation-config/>
<aop:aspectj-autoproxy />
<context:component-scan base-package="com.consumer" />
日志封装
操作结果封装
记录controller的日志记录,单纯返回固定的测试数据
@RequestMapping("aopTest")@ResponseBody@TestAnnotation(operation = "测试AOP日志记录")public ReturnMessage aopTest(@RequestParam(name = "manId")String manId){return new ReturnMessage(false, "草泥马", null);}
使用PostMan测试接口,数据返回无误
查看控制台日志信息,操作名,参数,结果和时间都被记录
import java.util.Arrays;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;@Aspect
public class AdviceTest {@Around("execution(* com.abc.service.*.many*(..))")public Object process(ProceedingJoinPoint point) throws Throwable {System.out.println("@Around:执行目标方法之前...");//访问目标方法的参数:Object[] args = point.getArgs();if (args != null && args.length > 0 && args[0].getClass() == String.class) {args[0] = "改变后的参数1";}//用改变后的参数执行目标方法Object returnValue = point.proceed(args);System.out.println("@Around:执行目标方法之后...");System.out.println("@Around:被织入的目标对象为:" + point.getTarget());return "原返回值:" + returnValue + ",这是返回结果的后缀";}@Before("execution(* com.abc.service.*.many*(..))")public void permissionCheck(JoinPoint point) {System.out.println("@Before:模拟权限检查...");System.out.println("@Before:目标方法为:" + point.getSignature().getDeclaringTypeName() + "." + point.getSignature().getName());System.out.println("@Before:参数为:" + Arrays.toString(point.getArgs()));System.out.println("@Before:被织入的目标对象为:" + point.getTarget());}@AfterReturning(pointcut="execution(* com.abc.service.*.many*(..))", returning="returnValue")public void log(JoinPoint point, Object returnValue) {System.out.println("@AfterReturning:模拟日志记录功能...");System.out.println("@AfterReturning:目标方法为:" + point.getSignature().getDeclaringTypeName() + "." + point.getSignature().getName());System.out.println("@AfterReturning:参数为:" + Arrays.toString(point.getArgs()));System.out.println("@AfterReturning:返回值为:" + returnValue);System.out.println("@AfterReturning:被织入的目标对象为:" + point.getTarget());}@After("execution(* com.abc.service.*.many*(..))")public void releaseResource(JoinPoint point) {System.out.println("@After:模拟释放资源...");System.out.println("@After:目标方法为:" + point.getSignature().getDeclaringTypeName() + "." + point.getSignature().getName());System.out.println("@After:参数为:" + Arrays.toString(point.getArgs()));System.out.println("@After:被织入的目标对象为:" + point.getTarget());}
}
触发
String result = manager.manyAdvices("aa", "bb");
System.out.println("Test方法中调用切点方法的返回值:" + result);
控制台结果
@Around:执行目标方法之前...
@Before:模拟权限检查...
@Before:目标方法为:com.abc.service.AdviceManager.manyAdvices
@Before:参数为:[改变后的参数1, bb]
@Before:被织入的目标对象为:com.abc.service.AdviceManager@1dfc617e
方法:manyAdvices
@Around:执行目标方法之后...
@Around:被织入的目标对象为:com.abc.service.AdviceManager@1dfc617e
@After:模拟释放资源...
@After:目标方法为:com.abc.service.AdviceManager.manyAdvices
@After:参数为:[改变后的参数1, bb]
@After:被织入的目标对象为:com.abc.service.AdviceManager@1dfc617e
@AfterReturning:模拟日志记录功能...
@AfterReturning:目标方法为:com.abc.service.AdviceManager.manyAdvices
@AfterReturning:参数为:[改变后的参数1, bb]
@AfterReturning:返回值为:原返回值:改变后的参数1 、 bb,这是返回结果的后缀
@AfterReturning:被织入的目标对象为:com.abc.service.AdviceManager@1dfc617e
Test方法中调用切点方法的返回值:原返回值:改变后的参数1 、bb,这是返回结果的后缀