首先,定義日誌註解,註解字段可自行擴展java
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Log { /**操做名稱*/ String value() default ""; /**模塊名*/ String moduleName() default ""; }
主要思路是:spring
利用AOP攔截被註解標註的方法,進行相關參數獲取。爲了防止日誌保存影響正常的業務執行,所以利用Spring的事件機制,發送事件給監聽器,監聽器收到事件後,異步保存日誌。app
注:在本示例中,使用Lombok註解,請自行了解Lombok註解做用。異步
下面是Aop的實現類,拿到攔截參數後,關鍵的一步是經過spring 的事件機制,將事件廣播出去。ide
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)); } }
日誌事件類,用於事件間的傳遞。能夠擴展此類用作其餘用途。ui
SysLogEvent.javadebug
public class SysLogEvent extends ApplicationEvent { public SysLogEvent(Object source) { super(source); } }
Spring容器工具類,在項目啓動時,注入Spring上下文,而後封裝一下事件發送方法及其餘經常使用方法。日誌
SpringContextUtil.javacode
@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); } }