欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

记我的第一次spring aop项目实践 博客分类: Java aopspringaspectjspring aop日志记录 

程序员文章站 2024-02-12 15:55:04
...

一、需求背景。

 

1、刚来公司1个多月,接到一个新的项目需求,需要开发喜力啤酒的app相关接口,因为相对比较独立,具体功能领导安排我一个人完成。

 

然后就愉快的撸起了代码,撸啊撸,撸啊撸,时间过得真快,过了1周,就撸完了.屁颠屁颠跑过去给领导说撸完了,可以上线了。

 

领导思考了片刻,为保障上线需要,需要监控喜力所有接口的日志部分,包括请求参数和返回参数的情况。让我把喜力所有接口的相关调用日志也加上!

 

Oh,我滴个乖乖,这个咋整啊,难道要去每个接口加参数,那么多接口,都猴年马月去了。后来请教了一下公司的前辈,问他们怎么做,他们让我用spring aop来弄,只需要在需要监控的方法加上相关注解就可以了。于是开始了我的spring aop之旅。

 

 

二、希望实现的效果。

 

1、哪个接口需要记录日志,就在哪个日志前加一个注解,这样能做到比较灵活。毕竟不是所有接口都要加。

 

2、不改变已经写好了的接口的任何业务逻辑,不穿插代码。

 

3、对调用者调用服务端的接口依然和修改前调用方式一样,让client无感监控动作。

 

三、结论:

1、能满足第二点提到的要求,用spring aop再合适不过了。

 

2、首先我们为什么需要做日志管理,在现实的上线中我们经常会遇到系统出现异常或者问题。这个时候就马上打开CRT或者SSH连上服务器拿日子来分析。受网络的各种限制。于是我们就想为什么不能直接在管理后台查看报错的信息呢。于是日志管理就出现了。

         其次个人觉得做日志管理最好的是Aop,有的人也喜欢用拦截器。都可以,在此我重点介绍我的实现方式。

         Aop有的人说拦截不到Controller。有的人说想拦AnnotationMethodHandlerAdapter截到Controller必须得拦截org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter。

首先Aop可以拦截到Controller的,这个是毋容置疑的其次须拦截AnnotationMethodHandlerAdapter也不是必须的。最起码我没有验证成功过这个。我的Spring版本是4.0.3。

         Aop之所以有的人说拦截不到Controller是因为Controller被jdk代理了。我们只要把它交给cglib代理就可以了。

 

 

四、操作步骤。

 

第一步:定义注解:

 

package com.biz.core.util.annotation;

import java.lang.annotation.*;

//@Documented
//@Retention(RetentionPolicy.RUNTIME)
//@Target({ElementType.METHOD})

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebMapControllerLog {
    String module()  default "";
    String methods()  default "";
}

 

 

第二步:定义一个切点类

 

package com.biz.web.controller.util;


import com.alibaba.fastjson.JSONObject;
import com.biz.core.bean.MessageObject;
import com.biz.core.bean.enums.MessageType;
import com.biz.core.entity.WebMapLog;
import com.biz.core.util.annotation.HeineKenControllerLog;
import com.biz.core.util.annotation.WebMapControllerLog;
import com.biz.core.util.mq.MqUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

@Aspect
@Component
public class WebMapControllerLogAspect {

    //本地异常日志记录对象
    private static final Logger logger = LoggerFactory.getLogger(WebMapControllerLogAspect.class);


    /**
     * 切入点,加了注解SfaMethod
     */
    // @Pointcut(value = "@annotation(com.biz.core.util.gatewaySupport.annotation.SfaMethod)")
    //@Pointcut(value = "@annotation(com.biz.core.util.annotation.HenineKenAnnotation)")
    @Pointcut("@annotation(com.biz.core.util.annotation.WebMapControllerLog)")
    public void serviceAspect() {
        System.out.println("我是一个切入点");
    }


    /**
     *     * 前置通知:目标方法执行之前执行以下方法体的内容      * @param jp    
     */
//    @Before("execution(* com.biz.service.rest.henineken.impl.*.*(..))")
// @Before(value = "serviceAspect()")
//    public void beforeMethod(JoinPoint joinPoint) {
//        String methodName = joinPoint.getSignature().getName();
//    begintime = System.currentTimeMillis();
//
//        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//        HttpSession session = request.getSession();
//
//        String url = request.getRequestURL().toString();
//        String method = request.getMethod();
//        String uri = request.getRequestURI();
//        String a = request.getQueryString();
////        request.
//
//        Map<String,String[]> ss = request.getParameterMap();
//
//        Enumeration<String> s1 = request.getParameterNames();
//
//                //Map<String,Object> pramMap = request.getParameterMap();
//
//        logger.info(a);
//
//
//
////        //读取session中的用户 等其他和业务相关的信息,比如当前用户所在应用,以及其他信息, 例如ip
////        String ip = request.getRemoteAddr();
////
////
////        JSONObject jdata = JSONObject.parseObject(data);
////        try {
////            logger.info("doBefore enter。 任何时候进入连接点都调用");
////            logger.info("method requested:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
////            logger.info("method description:" + getServiceMethodDescription(joinPoint));
////            logger.info("remote ip:" + ip);
////            //日志存入数据库
////
////            logger.info("doBefore end");
////        }  catch (Exception e) {
////            logger.info("doBefore exception");
////            logger.info("exceptionMsg={}", e.getMessage());
////        }
//
//        System.out.println("【前置通知】the method 【" + methodName + "】 begins with " + JSONObject.toJSONString(joinPoint.getArgs()));
//    }
//
//    /**
//     *     * 返回通知:目标方法正常执行完毕时执行以下代码     * @param jp     * @param result    
//     */
////    @AfterReturning( value = "execution(* com.biz.service.rest.henineken.impl.*.*(..))", returning = "result")
// @AfterReturning(pointcut = "serviceAspect()", returning = "result")
//    public void afterReturningMethod(JoinPoint jp, Object result) {
//        String methodName = jp.getSignature().getName();
//
//        System.out.println("【返回通知1】the method 【" + methodName + "】 ends with 【" + result + "】");
//
//    CrmReqLog crmReqLog=new CrmReqLog();
//    crmReqLog.setContent(JSONObject.toJSONString(result));
//    crmReqLog.setInfo("格式化请求内容");
//    crmReqLog.setUri("workCircleSummaryLikeService.1111");
//    crmReqLog.setTimes(System.currentTimeMillis()-begintime);
//
//    crmReqLog.setSuccess(true);
//    MqUtil.sendMessage(MessageObject.getLogType(MessageType.crm_req, crmReqLog));
// }
    @Around("serviceAspect()   && @annotation(oauth)")
    public Object around(ProceedingJoinPoint pjp, WebMapControllerLog oauth) throws Throwable {


        logger.info("进入环绕通知");
        Object[] args = pjp.getArgs(); //7

        String uri = getRequestURI();





        HttpServletRequest request = (HttpServletRequest) args[0];
        Map<String, Object> param = getParameterMap(request);


        long beginTime = System.currentTimeMillis();
        Object objectResult = pjp.proceed();
        logger.info("执行完方法调用");
        WebMapLog webMapLog = new WebMapLog();
        webMapLog.setUri(uri);
        webMapLog.setReq_param(JSONObject.toJSONString(param));
        String content = JSONObject.toJSONString(objectResult);
        //当前报文长度
        int length = content.length();
        webMapLog.setLengths(length);
        webMapLog.setReturn_param(content);
        webMapLog.setTimes(System.currentTimeMillis() - beginTime);
        webMapLog.setMethod(oauth.methods());
        webMapLog.setModule(oauth.module());
        //日志
        webMapLog.setSuccess(true);
        webMapLog.setSno("sno");
        MqUtil.sendMessage(MessageObject.getLogType(MessageType.web_map, webMapLog));
        logger.info("this is after around advice normal end");


//        String sno = (String) param.get("sno");
//        String lockName = RedisLockUtil.getLockName(uri, sno);
//
//        long beginTime = System.currentTimeMillis();
//        Object objectResult = null;
//        boolean isok = false;
//        try {
//            if (StringUtils.isNotEmpty(lockName) && StringUtils.isNotEmpty(sno)) {
//                //判断是否有处理成功的
//                LockStatus lockStatus = RedisLockUtil.getLockStatus(lockName);
//                //处理成功的直接返回
//                if (LockStatus.success.equals(lockStatus)) {
//                    objectResult = JSONResult.successByMsg("该接口已成功处理过");
//                    isok = true;
//                } else if (LockStatus.loading.equals(lockStatus)) {
//                    //处理中
//                    objectResult = JSONResult.error(Exceptions.Global.SERVICE_LOADING.getCode(), Exceptions.Global.SERVICE_LOADING.getDescription());
//                } else {
//                    //获取锁,开始处理
//                    boolean successLock = RedisLockUtil.getLock(lockName, 2, 24 * 60 * 60);
//                    //获得锁,处理
//                    if (successLock) {
//                        //成功的不再处理(必须二次判断)
//                        if (LockStatus.success.equals(RedisLockUtil.getLockStatus(lockName))) {
//                            objectResult = JSONResult.successByMsg("该接口已成功处理过");
//                            isok = true;
//                        }else{
//                            objectResult = pjp.proceed();
//                            isok = true;
//                            //解锁
//                            RedisLockUtil.setLockStatus(lockName, LockStatus.success);
//                            //同时关闭同步锁
//                            RedisLockUtil.unLock(lockName);
//                        }
//                    } else {
//                        //没有获得锁也是处理中
//                        objectResult = JSONResult.error(Exceptions.Global.SERVICE_LOADING.getCode(), Exceptions.Global.SERVICE_LOADING.getDescription());
//                    }
//                }
//            } else {
//                //sno不存在
//                objectResult = pjp.proceed();
//                isok = true;
//            }
//        } catch (Throwable t) {
//            t.printStackTrace();
//            logger.error(t.getMessage());
//            //失败了
//            isok = false;
//            JSONResult jerror=null;
//            if (null != t.getCause()) {
//                if (t.getCause().getClass().equals(CRMException.class)) {
//                    CRMException crmException = (CRMException) t.getCause();
//                    jerror = crmException.getResult();
//                } else {
//                    jerror = JSONResult.error(Exceptions.Global.ERROR.getCode(), "服务器正忙,请稍后再试");
//                }
//            }
//            //错误配置
//            if (null == jerror || StringUtils.isEmpty(jerror.getMsg())) {
//                jerror = JSONResult.error(Exceptions.Global.INTERFACE_SERVER_EXCEPTION.getCode(), Exceptions.Global.INTERFACE_SERVER_EXCEPTION.getDescription());
//            }
//
//            objectResult = jerror;
//            logger.error(":调用接口出错:" + jerror.toString() + ",接口编号:" + sno);
//
//            if (StringUtils.isNotEmpty(lockName)) {
//                //解锁
//                RedisLockUtil.setLockStatus(lockName, LockStatus.fail);
//                //同时关闭同步锁
//                RedisLockUtil.unLock(lockName);
//            }
//        } finally {
//            //关闭crm日志 20200323
//
//
//            WebMapLog webMapLog=new WebMapLog();
//            webMapLog.setUri(uri);
//            webMapLog.setReq_param(JSONObject.toJSONString(param));
//            String content = JSONObject.toJSONString(objectResult);
//            //当前报文长度
//            int length = content.length();
//            webMapLog.setLengths(length);
//            webMapLog.setReturn_param(content);
//            webMapLog.setTimes(System.currentTimeMillis()-beginTime);
//            //日志
//            webMapLog.setStatus(isok);
//            webMapLog.setSno(sno);
//            MqUtil.sendMessage(MessageObject.getLogType(MessageType.web_map, webMapLog));
//            logger.info("this is after around advice normal end");
//        }

        return objectResult;
    }


    private String getRequestURI() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String uri = request.getRequestURI();
        return uri;
    }


    public Map<String, Object> getParameterMap(HttpServletRequest request) {
        // 参数Map
        Map<?, ?> properties = request.getParameterMap();
        // 返回值Map
        Map<String, Object> returnMap = new HashMap<String, Object>();
        Iterator<?> entries = properties.entrySet().iterator();

        Map.Entry<String, Object> entry;
        String name = "";
        String value = "";
        Object valueObj = null;
        while (entries.hasNext()) {
            entry = (Map.Entry<String, Object>) entries.next();
            name = (String) entry.getKey();
            valueObj = entry.getValue();
            if (null == valueObj) {
                value = "";
            } else if (valueObj instanceof String[]) {
                String[] values = (String[]) valueObj;
                for (int i = 0; i < values.length; i++) {
                    value = values[i] + ",";
                }
                value = value.substring(0, value.length() - 1);
            } else {
                value = valueObj.toString();
            }
            returnMap.put(name, value);
        }
        return returnMap;
    }


//    /**
//     *     * 异常通知:目标方法发生异常的时候执行以下代码    
//     */
//    @AfterThrowing(value = "execution(* com.biz.service.rest.henineken.impl.*.*(..))", throwing = "e")
//    public void afterThorwingMethod(JoinPoint jp, NullPointerException e) {
//        String methodName = jp.getSignature().getName();
//        System.out.println("【异常通知】the method 【" + methodName + "】 occurs exception: " + e);
//
//    }


//    //异常通知
//    @AfterThrowing(pointcut = "serviceAspect()", throwing = "e")
//    public void handleThrowing(JoinPoint joinPoint, Exception e) {
//
//        Signature signature = joinPoint.getSignature();
//        MethodSignature methodSignature = (MethodSignature) signature;
//        IntfaceException intfaceException = methodSignature.getMethod().getAnnotation(IntfaceException.class);
//
//        //连接异常进行短信通知
//        if (e instanceof CRMException) {
//
//            CRMException crmExcep = (CRMException) e;
//            JSONResult result = crmExcep.getResult();
//
//            TxySmsSetting txySmsSetting = SettingUtil.getTxySmsSetting();
//
//
//            if (result.getErrcode() == 1107) {
//
//                Object notify = RedisUtil.get("sys_excep_notify" + intfaceException.system());
//
//                if (notify != null) {
//                    return;
//                }
//
//                String[] content = new String[3];
//
//                content[0] = SystemConstants.profileName + "" + intfaceException.system();
//                content[1] = result.getMsg();
//                content[2] = intfaceException.module();
//
//                String[] phones = txySmsSetting.getExcepPhones().split(",");
//
//                SmsMessage smsMessage = new SmsMessage();
//                smsMessage.setAppuser("admin");
//                smsMessage.setContent(content);
//                smsMessage.setPhones(phones);
//                smsMessage.setTemplateId(txySmsSetting.getIntfacExcepTemplateId());
//                //12小时之内不重复发送短信
//                RedisUtil.set("sys_excep_notify" + intfaceException.system(), "Y", 43200L);
//                MqUtil.sendMessage(MessageObject.getType(null, smsMessage, MqConstant.mq_sms_send));
//
//            }
//
//        }
//
//
//    }


    /**
     * 获取注解中对方法的描述信息
     *
     * @param joinPoint 切点
     * @return 方法描述
     * @throws Exception
     */
    private static String getServiceMethodDescription(JoinPoint joinPoint)
            throws Exception {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        String description = "";
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    description = method.getAnnotation(HeineKenControllerLog.class).module();
                    break;
                }
            }
        }
        return description;
    }


}

 

 

 

第三步:把Controller的代理权交给cglib

在实例化ApplicationContext的时候需要加上

 

Xml代码 记我的第一次spring aop项目实践
            
    
    博客分类: Java aopspringaspectjspring aop日志记录 
  1. <!-- 启动对@AspectJ注解的支持 -->  
  2. <aop:aspectj-autoproxy/>  
  3. <aop:aspectj-autoproxy proxy-target-class="true" />

 在调用Controller的时候AOP发挥作用所以在SpringMVC的配置文件里加上

 

因为 SpringMVC的Contrller的容器和 spring 的容器不是同一个,必须再spring mvc的配置文件添加<aop:aspectj-autoproxy proxy-target-class="true" />

 这个语句,才能实现对controller的注解支持

 

第四步使用

 

Controller层的使用

 

@RequestMapping(method = RequestMethod.GET)
@WebMapControllerLog(methods = "goLogin",module = "跳转登录界面")
public String goLogin(HttpServletRequest request, HttpServletResponse response) {
    logger.debug("LoginController  goLogin");
    return BaseUntil.webPath + "/login/loginMain";
}