第一步:
1. 增加spring AOP相关配置到spring-context.xml:
<!-- 启动对@AspectJ注解的支持 --> <!-- proxy-target-class等于true是强制使用cglib代理,proxy-target-class默认是false,如果你的类实现了接口就走JDK代理,如果没有,走cglib代理 --> <!-- 注:对于单利模式建议使用cglib代理,虽然JDK动态代理比cglib代理速度快,但性能不如cglib --> <!--如果不写proxy-target-class="true"这句话也没问题 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
2. pom.xml增加相关依赖:
<!-- AOP --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.11</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.6.11</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.1_3</version> </dependency> <dependency> <groupId>javassist</groupId> <artifactId>javassist</artifactId> <version>3.12.1.GA</version> </dependency>
第二布:
创建相关自定义注解类,InterfaceParam.java 和 JzInterface.java
package com.abc.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; /** * 自定义注解类(方法描述) * * @author DaiHaijiao * */ @Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface JzInterface { /** * 方法描述:接口名称 * * @return String */ String value() default ""; /** * 方法描述:参数列表 * * @return InterfaceParam[] */ InterfaceParam[] params() default {}; }
package com.abc.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; /** * 自定义注解类(接口描述信息) * * @author DaiHaijiao * */ @Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface InterfaceParam { /** * 方法描述:参数名称 * * @return String */ String name() default ""; /** * 方法描述:参数说明 * * @return String */ String desc() default ""; }
第三步:
创建操作日志实体对象,属性字段如下:
// 请求状态 public static final int STATUS_FAIL = 0; // 失败 public static final int STATUS_SUCCESS = 1; // 成功 private ObjectId _id; private String userId; // 用户ID private String integerfaceDesc; // 接口描述 private String receptionParams; // 前台参数 private String backstageParams; // 后台参数 private int status; // 请求状态 private String menuKey; // 菜单key private Boolean deleted; // 删除标记 private Date createdAt; // 创建时间 private Date updatedAt; // 更新时间
第四部:
编写AOP拦截类:
package com.abc.aop; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.shiro.SecurityUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import com.abc.annotation.InterfaceParam; import com.abc.annotation.JzInterface; import com.abc.entity.OperationLog; import com.abc.entity.User; import com.abc.service.OperationLogService; import javassist.ClassClassPath; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import javassist.Modifier; import javassist.NotFoundException; import javassist.bytecode.CodeAttribute; import javassist.bytecode.LocalVariableAttribute; import javassist.bytecode.MethodInfo; /** * 采用AOP的方式处理日志和异常问题 * */ @Aspect @Component public class BindAop { @Autowired private OperationLogService operationLogService; private final Logger log = LoggerFactory.getLogger(this.getClass()); // 接口描述 private String interfaceDescribe = ""; // 接口参数 InterfaceParam[] param = null; // 接口详细参数字符串map Map<String, String> parameContentMap = null; OperationLog operationLog = null; @Pointcut("execution(* com.abc.controller.*.*(..))") public void aopMethod() { } /** * 环绕通知 * * @param joinPoint * @return Object * @throws Throwable */ @Around("aopMethod()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { log.info("before method invoking!"); Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); JzInterface jzInterface = method.getAnnotation(JzInterface.class); if (null != jzInterface) { String classType = joinPoint.getTarget().getClass().getName();// 接口所在的包名类名 // Class<?> clazz = Class.forName(classType); // String clazzName = clazz.getName();// 接口所在的类名 // String clazzSimpleName = clazz.getSimpleName();// 接口所在的类名 String methodName = joinPoint.getSignature().getName();// 被请求的接口名 String[] paramNames = getFieldsName(this.getClass(), classType, methodName);// 被请求的接口的参数的key parameContentMap = appendParameInfo(paramNames, joinPoint, jzInterface.params()); interfaceDescribe = jzInterface.value(); } else { interfaceDescribe = ""; } return joinPoint.proceed(); } /** * 后置通知 * * * @param joinPoint */ @After("aopMethod()") public void after(JoinPoint joinPoint) { if (!"".equals(interfaceDescribe)) {// 获取接口的描述 String parame = parameContentMap.get("parame"); String parameInfo = parameContentMap.get("parameInfo"); // 接口访问成功(加入日志操作记录) operationLog = new OperationLog(); // operationLog对象set相关属性值 operationLog.setIntegerfaceDesc(interfaceDescribe); operationLog.setReceptionParams(parameInfo); operationLog.setBackstageParams(parame); operationLog.setStatus(OperationLog.STATUS_SUCCESS);// 成功 operationLog.setUserId(((User) SecurityUtils.getSubject().getPrincipal()).get_id() + "");//此处用户id根据自己项目实际情况获取 operationLog.setDeleted(false); operationLog.setMenuKey("-"); // 操作日志入库 operationLog = operationLogService.add(operationLog); } } /** * 异常通知 * * @param joinPoint * @param e */ @AfterThrowing(pointcut = "aopMethod()", throwing = "e") public void doAfterThrowing(JoinPoint joinPoint, Throwable e) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String url = request.getRequestURI(); if (!"".equals(interfaceDescribe)) { String parame = parameContentMap.get("parame"); String parameInfo = parameContentMap.get("parameInfo"); // 接口访问失败(修改日志操作记录) operationLog.setIntegerfaceDesc(interfaceDescribe); operationLog.setReceptionParams(parameInfo); operationLog.setBackstageParams(parame); operationLog.setStatus(OperationLog.STATUS_FAIL);// 失败 operationLog.setUserId(((User) SecurityUtils.getSubject().getPrincipal()).get_id() + ""); operationLog.setDeleted(false); operationLog.setMenuKey("-"); // 修改刚才插入数据库的数据 operationLogService.edit(operationLog); } // 错误信息 log.error("访问" + url + " 发生错误, 错误信息:" + e.getMessage()); // TODO ... 错误信息保存数据库 ... } /** * 拼接参数key以及参数value * * @param paramNames * @param joinPoint * @param param * @return String */ private Map<String, String> appendParameInfo(String[] paramNames, JoinPoint joinPoint, InterfaceParam[] param) { Map<String, String> map = new HashMap<String, String>(); Object[] args = joinPoint.getArgs(); StringBuilder sb = new StringBuilder(); StringBuilder sb1 = new StringBuilder(); sb.append("{"); sb1.append("{"); int length = args.length; if (param.length == length) { int lastArgsNum = args.length - 1; for (int k = 0; k < length; k++) { Object arg = args[k]; sb.append("\"" + paramNames[k] + "\""); sb1.append("\"" + param[k].desc() + "\""); if (k == lastArgsNum) { sb.append(":\"" + arg + "\""); sb1.append(":\"" + arg + "\""); } else { sb.append(":\"" + arg + "\","); sb1.append(":\"" + arg + "\","); } } } sb.append("}"); sb1.append("}"); map.put("parame", sb.toString()); map.put("parameInfo", sb1.toString()); return map; } /** * 得到方法参数的名称 * * @param cls * @param clazzName * @param methodName * @return String[] * @throws NotFoundException */ @SuppressWarnings("rawtypes") private 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); if (attr == null) { // exception } 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; } }
第五步:
使用:
1. eg,接口需要将调用日志添加到数据库,则如下:
/** * 新建角色 * * @param name * @param desc * @param menuKeys * @return Result */ @JzInterface(value = "新建角色", params = { @InterfaceParam(name = "name", desc = "角色名称"), @InterfaceParam(name = "desc", desc = "备注"), @InterfaceParam(name = "menuKeys", desc = "角色权限") }) @RequestMapping(value = "add.do") public @ResponseBody Result add(String name, String desc, String menuKeys) { //TODO ... // ... // ... return null; } 2. eg,接口不需要将调用日志添加到数据库,则如下: /** * 新建角色 * * @param name * @param desc * @param menuKeys * @return Result */ @RequestMapping(value = "add.do") public @ResponseBody Result add(String name, String desc, String menuKeys) { //TODO ... // ... // ... return null; } 3. 说明,当需要添加日志,但是接口中又没有参数时,注解中的 params 直接 = {} 即可。当接口参数中包含有request、response、session或其他对象时,注解中的params也同样需要写入该参数,eg:
@JzInterface(value = "获取人员数据列表", params = { @InterfaceParam(name = "page", desc = "页码"), @InterfaceParam(name = "limit", desc = "条数"), @InterfaceParam(name = "temp", desc = "0:初始化页面,不是0说明不是初始化页面"), @InterfaceParam(name = "session", desc = "HttpSession对象") }) @RequestMapping(value = "IMOS_page_list.do") public @ResponseBody Map<String, Object> person_page_list(Integer page, Integer limit, String temp, HttpSession session) { Map<String, Object> map = new HashMap<String, Object>(); //TODO ... // ... ... // ... ... return map; }
最后一点,要注意:params 里面的参数顺序必须和接口参数顺序保持一致!!! --------------------- 原文:https://blog.csdn.net/Dai_Haijiao/article/details/80365753