自定义注解,将需要记录日志的方法进行标记
/*
* 常用注解说明:
* 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){
// 写入id
logMessage.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测试接口,数据返回无误
查看控制台日志信息,操作名,参数,结果和时间都被记录
附上参考代码,地址:https://www.cnblogs.com/softidea/p/6123307.html
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,这是返回结果的后缀