利用Spring AOP统一处理日志和异常

第一步:

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 里面的参数顺序必须和接口参数顺序保持一致!!!


猜你喜欢

转载自blog.csdn.net/dai_haijiao/article/details/80365753