Spring Boot利用自定义注解记录请求或方法执行日志
首先,定义日志注解,注解字段可自行扩展
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Log {
/**操作名称*/
String value() default "";
/**模块名*/
String moduleName() default "";
}
主要思路是:
利用AOP拦截被注解标注的方法,进行相关参数获取。为了防止日志保存影响正常的业务执行,因此利用Spring的事件机制,发送事件给监听器,监听器收到事件后,异步保存日志。
注:在本示例中,使用Lombok注解,请自行了解Lombok注解作用。
下面是Aop的实现类,拿到拦截参数后,关键的一步是通过spring 的事件机制,将事件广播出去。
LogAop.java
@Slf4j
@Aspect
@Component
public class LogAop {
@Pointcut(value = "@annotation(com.chillax.boot.core.common.annotation.Log)")
public void cutService() {
}
@Around("cutService()")
public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {
long startTime = System.currentTimeMillis();
//先执行业务
Object result = point.proceed();
long endTime = System.currentTimeMillis();
try {
handle(point, endTime - startTime, null);
} catch (Exception e) {
log.error("日志记录出错!", e);
}
return result;
}
@AfterThrowing(pointcut = "cutService()", throwing = "e")
public void doAfterThrowing(JoinPoint point, Throwable e) {
try {
handle(point, null, e);
} catch (Exception ex) {
log.error("日志记录出错!", ex);
}
}
private void handle(JoinPoint point, Long methodProcessTime, Throwable e) throws Exception {
//获取拦截的方法名
Signature sig = point.getSignature();
MethodSignature msig = null;
if (!(sig instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
msig = (MethodSignature) sig;
Object target = point.getTarget();
Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
String methodName = currentMethod.getName();
//获取拦截方法的参数
Object[] params = point.getArgs();
//获取操作名称
Log annotation = currentMethod.getAnnotation(Log.class);
String moduleName = annotation.moduleName();
String operationName = annotation.value();
//这里根据自己的业务需求,封装自己的业务类
SysLog sysLog = new SysLog();
SpringContextUtil.publishEvent(new SysLogEvent(sysLog));
}
}
日志事件类,用于事件间的传递。可以扩展此类用做其他用途。
SysLogEvent.java
public class SysLogEvent extends ApplicationEvent {
public SysLogEvent(Object source) {
super(source);
}
}
Spring容器工具类,在项目启动时,注入Spring上下文,然后封装一下事件发送方法及其他常用方法。
SpringContextUtil.java
@Slf4j
@Service
@Lazy(false)
public class SpringContextUtil implements ApplicationContextAware, DisposableBean {
private static ApplicationContext applicationContext;
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
public static <T> T getBean(String name, Class<T> requiredType) {
return applicationContext.getBean(name, requiredType);
}
public static boolean containsBean(String name) {
return applicationContext.containsBean(name);
}
public static boolean isSingleton(String name) {
return applicationContext.isSingleton(name);
}
public static Class<? extends Object> getType(String name) {
return applicationContext.getType(name);
}
public static void publishEvent(ApplicationEvent event) {
if (applicationContext != null) {
applicationContext.publishEvent(event);
}
}
public static void clearHolder() {
if (log.isDebugEnabled()) {
log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
}
applicationContext = null;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
@Override
public void destroy() throws Exception {
SpringContextUtil.clearHolder();
}
}
最后,编写事件监听器。
@Async 标注此方法执行为异步,需要使用**@EnableAsync**注解开启此功能
@Order标记此监听器为最低级别加载
@EventListener(SysLogEvent.class) 标注监听的事件
比较重要的是,监听器需要将它加入到Spring容器中。通过event.getSource() 获取到发送事件时,传递的对象。
SysLogListener.java
@AllArgsConstructor
@Slf4j
@Component
public class SysLogListener {
private final ISysLogService sysLogService;
@Async
@Order
@EventListener(SysLogEvent.class)
public void saveSysLog(SysLogEvent event) {
SysLog sysLog = (SysLog) event.getSource();
sysLogService.save(sysLog);
}
}