之前总结写了一篇通过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();
}
}
@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配置方式要省事很多。