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

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值