Spring注解实现日志记录

2/10/2017来源:ASP.NET技巧人气:1377

之前总结写了一篇通过xml配置的方式,切面编程实现日志记录的功能demo

http://blog.csdn.net/weiweiai123456/article/details/38561085

可参考http://blog.csdn.net/heirenheiren/article/details/36634497 ,讲的是注解实现

现在实现一个通过注解方式实现的样例:

一:准备

xml中需要开启CGLIB动态代理

<!-- 启用CGliB -->
	<aop:aspectj-autoPRoxy proxy-target-class="true"/>切面编程----AOP,依赖的是代理,即JDK代理和CGLIB代理,而代理的实现依靠的是反射。

maven配置省...

二:注解类

SaveSysLog.java

package com.cooya.partner.metadata.entity.baseConfig;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 
 * Description: 保存系统日志注解接口
 *
 * @author suoww
 * @date 2017-2-8
 *
 */
@Retention(RetentionPolicy.RUNTIME)  //注解会在class中存在,运行时可通过反射获取
@Target(ElementType.METHOD) //注解到方法
public @interface SaveSysLog {

    //调用方      1:嗨赚客户端   2:支付宝   3:微信       4:钱宝     5:其他第三方
    int send() default 1; 
    
    //接口url(从二级目录记起)
    String url();
    
    //接口类型(前台,后台)
    int type();
}定义三个成员,这里只能定义八种基本数据类型,分别是

byte-->Byte

short-->Short

int-->Integer

long-->Long

float-->Float

double-->Double

char-->Character

boolean-->Boolean

注意:只能是上述这些8种类型的变量

三:切面类

SysLogAspect.java

package com.cooya.partner.service.baseConfig;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Httpsession;

import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
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.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.alibaba.fastjson.JSONObject;
import com.cooya.partner.constant.InterfaceTypeConst;
import com.cooya.partner.metadata.entity.baseConfig.PartnerSystemLog;
import com.cooya.partner.metadata.entity.baseConfig.SaveSysLog;
import com.cooya.partner.metadata.entity.user.PartnerUser;
import com.cooya.partner.metadata.mapper.baseConfig.PartnerSystemLogMapper;
import com.cooya.partner.permission.dto.ShiroUser;

/**
 * 
 * Description: 切面类记录接口调用失败日志信息
 *
 * @author suoww
 * @date 2017-2-8
 *
 */
@Aspect
@Component
public class SysLogAspect {
    
    public static final int CODE_SUCCESS = 0;
    
    private Logger logger = LoggerFactory.getLogger(SysLogAspect.class);
    
    @Resource
    private PartnerSystemLogMapper partnerSystemLogMapper;
    
    /**
     * 
     * Description: 定义切点名controllerAspect,此方法需要为空,只是标识切点和切面关系
     *
     * @author suoww
     * @date 2017-2-8
     */
    @Pointcut("@annotation(com.cooya.partner.metadata.entity.baseConfig.SaveSysLog)")
    public void controllerAspect(){}
    
    /**
     * 
     * Description:织入后增强 
     *
     * @param join
     * @author suoww
     * @throws Exception 
     * @date 2017-2-8
     */
    @AfterReturning(pointcut = "controllerAspect()", returning = "res")
    public void doAfter(JoinPoint joinPoint, Object res) throws Exception{
        //获取反射参数
        logger.debug("---------------AfterReturning开始--------------");
        if(null == res){
            return;
        }
        Map<String, Object> map = Obj2Map(res);
        int code = (Integer)map.get("code");
        if(code == CODE_SUCCESS){
            return;
        }
        String message = (String)map.get("message");
        //类名
        String targetName = joinPoint.getTarget().getClass().getSimpleName();
        //得到方法名
        String methodName = joinPoint.getSignature().getName();
        MethodSignature ms = (MethodSignature) joinPoint.getSignature();
        //入参key
        String[] parameterNames = ms.getParameterNames();
        //入参value
        Object[] arguments = joinPoint.getArgs();
        Method method = ms.getMethod();
        //方法的注解对象
        SaveSysLog logParam = method.getAnnotation(SaveSysLog.class);  
        /* logger.debug("SaveSysLog注解参数send:" + logParam.send());  
        logger.debug("SaveSysLog注解参数url:" + logParam.url()); 
        logger.debug("SaveSysLog注解参数type:" + logParam.type()); 
        logger.debug("targetName:" + targetName);
        logger.debug("methodName:" + methodName);
        logger.debug("ms:" + ms);
        logger.debug("arguments:" + JSONObject.toJSONString(arguments));
        logger.debug("parameterNames:" + JSONObject.toJSONString(parameterNames));
        logger.debug("method:" + JSONObject.toJSONString(method));*/
        
        //拼参数
        PartnerSystemLog sysLog = new PartnerSystemLog(); 
        //获取用户
        if(logParam.type() == InterfaceTypeConst.InterfaceType.APP){
            sysLog.setUserId(getAppUserId());
        }else{
            sysLog.setUserId(getMgrUserId());
        }
        sysLog.setSend(logParam.send());
        sysLog.setUrl(logParam.url());
        sysLog.setType(logParam.type());
        //入参字符串
        StringBuffer jsonParamSb = new StringBuffer();
        for(int i = 0;i < parameterNames.length;i++){
            jsonParamSb.append(parameterNames[i]).append("=").append(JSONObject.toJSONString(arguments[i]));
            if(i != (parameterNames.length - 1)){
                jsonParamSb.append("&");
            }
        }
        //截取返回json
        if(jsonParamSb.toString().length() <= 1000){
            sysLog.setJsonParam(jsonParamSb.toString());
        }else{
            sysLog.setJsonParam(jsonParamSb.toString().substring(0, 1000));
        }
        //出参
        sysLog.setJsonResult(JSONObject.toJSONString(res));
        StringBuffer remarkSb = new StringBuffer();
        remarkSb.append(targetName).append(".").append(methodName).append("报错信息:").append(message);
        //截取remark
        if(remarkSb.toString().length() <= 1000){
            sysLog.setRemark(remarkSb.toString());
        }else{
            sysLog.setRemark(remarkSb.toString().substring(0, 1000)); 
        }
        sysLog.setCreateTime(new Date());
        sysLog.setUpdateTime(new Date());
        handleLog(sysLog);
        logger.debug("---------------AfterReturning结束--------------");
    }
    
    /**
     * 
     * Description: 异步记录接口调用失败的日志
     *
     * @param systemLog
     * @author suoww
     * @date 2017-2-8
     */
    @Async
    public void handleLog(PartnerSystemLog sysLog){
        //写日志
        int row = partnerSystemLogMapper.insertSelective(sysLog);
        logger.debug("------日志写入行数:" + row);
    } 
    
    /**
     * 
     * Description: 对象转map
     *
     * @param obj
     * @return
     * @throws Exception
     * @author suoww
     * @date 2017-2-8
     */
    public Map<String,Object> Obj2Map(Object obj) throws Exception{
        Map<String,Object> map=new HashMap<String, Object>();
        Field[] fields = obj.getClass().getDeclaredFields();
        for(Field field:fields){
            field.setaccessible(true);
            map.put(field.getName(), field.get(obj));
        }
        return map;
    }
    
    /**
     * 
     * Description: 获取APP用户ID
     *
     * @return
     * @author suoww
     * @date 2017-2-8
     */
    protected Long getAppUserId() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpSession session = request.getSession();
        if (null == session) {
            return null;
        }
        PartnerUser user = (PartnerUser) session.getAttribute("userInfo");
        if (null == user) {
            return null;
        }
        return user.getId();
    }
    
    /**
     * 
     * Description: 获取Mgr的用户ID
     *
     * @return
     * @author suoww
     * @date 2017-2-8
     */
    protected Long getMgrUserId(){
        ShiroUser user = (ShiroUser) SecurityUtils.getSubject().getPrincipal();
        return user.getId();
    }
}

@Aspect和@Component分别表示这是一个切面类、Spring要帮我实例化对象并管理

@Pointcut(XX) :使用SaveSysLog作为注解(annotation)的将作为切点,对应切面controllerAspect

1.@AfterReturning 表示切点后增强,即切入点的方法执行结束后,即执行切面中的增强代码,但是不会改变原切入点方法返回值。下面具体说明

2.pointcut="controllerAspect()" ,returning="res" 表示切点和切面对应关系,一个方法上可以有多个切面,指定顺序

参考:http://blog.csdn.net/rainbow702/article/details/52185827

3.下面的是反射获取的参数,类名,方法名,入参key,参数value,注解对象,方法返回值

四:调用

controller中调用

 /**
     * 
     * Description: 接口:查询场次下商品
     *
     * @param channelId
     * @return
     * @author suoww
     * @date 2017-1-13
     */
    @RequestMapping("/queryGoodsUnderChannel")
    @ResponseBody
    @SaveSysLog(send=InterfaceTypeConst.SendType.HZ, url="/goods/api/queryGoodsUnderChannel.html", type=InterfaceTypeConst.InterfaceType.APP)
    public AjaxResult queryGoodsUnderChannel(@RequestParam(value = "channelId", required = true)Long channelId){
        try{
            List<PartnerChannelGoodsDto> list = partnerGoods2ChannelService.getChannelGoodsDto(channelId);
            logger.info("根据channelId:" + channelId + "获取到的商品集合为" + JSONObject.toJSONString(list));
            return AjaxResult.success(list, "成功获取频道下商品");
        }catch(ResultCodeException e){
            e.printStackTrace();
            return AjaxResult.failed("校验失败:" + e.getMessage());
        }catch(Exception e){
            e.printStackTrace();
            return AjaxResult.failed("系统异常:" + e.getMessage());
        }
    }
@SaveSysLog(send=InterfaceTypeConst.SendType.HZ, url="/goods/api/queryGoodsUnderChannel.html", type=InterfaceTypeConst.InterfaceType.APP)

这里对应注解接口三个成员,send,url,type

queryGoodsUnderChannel 这个方法将整体作为一个切入点,结合@AfterReturning 在queryGoodsUnderChannel ()执行结束会,会进入到SysLogAspect.doAfter 执行一段代码,记录日志

五:测试

输入http://localhost:8080/partner-app/goods/api/queryGoodsUnderChannel.html?channelId=17

  入参中channelId=17

参数可以对应。

总结:权限控制,日志记录应该使用AOP,注解方式实际使用时候比XML配置方式要省事很多。