记我的第一次spring aop项目实践 博客分类: Java aopspringaspectjspring aop日志记录
一、需求背景。
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的时候需要加上
- <!-- 启动对@AspectJ注解的支持 -->
- <aop:aspectj-autoproxy/>
-
<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"; }