在日常開發工作中,我們免不了要打印很多log。而大部分需要輸出的log又是重復的(例如傳入參數,返回值)。
因此,通過AOP方式來進行日志管理可以減少很多代碼量,也更加優雅。
Springboot通過AOP方式(@Aspect)和Javassist優雅地進行日志輸出管理。
CREATE TABLE `sys_operation_log` ( `log_id` varchar(32) NOT NULL DEFAULT '', `table_name` varchar(50) DEFAULT '' COMMENT '表名', `biz_name` varchar(50) DEFAULT '' COMMENT '業務名稱', `biz_id` varchar(50) DEFAULT '' COMMENT '業務主鍵值', `biz_type` smallint(1) DEFAULT '0' COMMENT '業務類型(1:添加 2:修改 3:刪除)', `create_time` datetime DEFAULT NULL COMMENT '操作時間', `creator` varchar(50) DEFAULT '' COMMENT '操作人', `remark` varchar(200) DEFAULT '' COMMENT '備注', PRIMARY KEY (`log_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系統操作日志';
主要使用技術:Aspect,Javassist
package com.xinyartech.erp.system.aop; import java.lang.reflect.Modifier; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import com.alibaba.fastjson.JSON; import com.xinyartech.erp.core.util.Util; import javassist.ClassClassPath; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import javassist.NotFoundException; import javassist.bytecode.CodeAttribute; import javassist.bytecode.LocalVariableAttribute; import javassist.bytecode.MethodInfo; /** * 通過spring aop實現service方法執行時間監控 * * @author Lynch * */ @Aspect @Component public class WebLogAop { private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(WebLogAop.class); private static ThreadLocal<String> threadLocal = new ThreadLocal<String>(); //public static final String POINT = "execution (* com.xinyartech.erp.*.web.*.*(..))";
@Pointcut("(execution (* com.xinyartech.erp.*.web.*.*(..)))") public void webLog(){ } /** * 前置通知 * @param joinPoint 切點 * @throws Throwable 異常 */ @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { String uuid = Util.getUUID(); threadLocal.set(uuid); String classType = joinPoint.getTarget().getClass().getName(); Class<?> clazz = Class.forName(classType); String clazzName = clazz.getName(); log.info(String.format("[%s] 類名:%s", uuid, clazzName)); String methodName = joinPoint.getSignature().getName(); log.info(String.format("[%s] 方法名:%s", uuid, methodName)); String[] paramNames = getFieldsName(this.getClass(), clazzName, methodName); Object[] args = joinPoint.getArgs(); for(int k=0; k<args.length; k++){ log.info("[" + uuid + "] 參數名:" + paramNames[k] + ",參數值:" + JSON.toJSONString(args[k])); } } /** * 后置通知 * 打印返回值日志 * @param ret 返回值 * @throws Throwable 異常 */ @AfterReturning(returning = "ret", pointcut = "webLog()") public void doAfterReturning(JoinPoint joinPoint, Object ret) throws Throwable { String uuid = threadLocal.get(); String classType = joinPoint.getTarget().getClass().getName(); Class<?> clazz = Class.forName(classType); String clazzName = clazz.getName(); log.info(String.format("[%s] 類名:%s", uuid, clazzName)); String methodName = joinPoint.getSignature().getName(); log.info(String.format("[%s] 方法名:%s", uuid, methodName)); log.info(String.format("[%s] 返回值 : %s", uuid, JSON.toJSONString(ret))); log.info("*****************************************"); } /** * 得到方法參數的名稱 * @param cls 類 * @param clazzName 類名 * @param methodName 方法名 * @return 參數名數組 * @throws NotFoundException 異常 */
private static String[] getFieldsName(Class<?> cls, String clazzName, String methodName) throws NotFoundException { ClassPool pool = ClassPool.getDefault(); ClassClassPath classPath = new ClassClassPath(cls); pool.insertClassPath(classPath); CtClass cc = pool.get(clazzName); CtMethod cm = cc.getDeclaredMethod(methodName); MethodInfo methodInfo = cm.getMethodInfo(); CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag); String[] paramNames = new String[cm.getParameterTypes().length]; int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1; for (int i = 0; i < paramNames.length; i++){ paramNames[i] = attr.variableName(i + pos); //paramNames即參數名
} return paramNames; } }