之前总结写了一篇通过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@Componentpublic 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配置方式要省事很多。
新闻热点
疑难解答