第一步:
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;
/**
* 自定义注解类(方法描述)
*/
@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;
/**
* 自定义注解类(接口描述信息)
*/
@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 里面的参数顺序必须和接口参数顺序保持一致!!!