昨天和同事一起看怎么用自定义注解和aop实现操作日志,现在来记录一下。
大致的思路是在修改删除新增的方法前面加上自定义注解,然后定义一个切面,在切面的环绕通知中处理注解和请求带过来的参数。PS:JSONObject真好用,因为现在的查询后的数据都是用JSONObject来接收的,所以对比修改前后的变化连反射都不用。
首先是自定义注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) //表示注解用在方法上
public @interface OperateLogAnnotation {
String page() default "";
String opration_table() default "";
String key() default ""; //id名称
}
使用自定义注解:
注意:因为实现了接口,所以使用的是jdk动态代理,代理那些接口中有的方法,@自定义注解只能加在接口对应的方法上
@OperateLogAnnotation(page="系统管理——数据字典",opration_table="patee_dictionary",key="id_patee_dictionary")
public BaseResult editDict(JSONObject req) {
// TODO Auto-generated method stub
log.info("editDict in, req:{}", JSONObject.toJSON(req));
String uuid = req.getString("uuid");
String oper = req.getString("oper");
BaseResult result = null;
if ("edit".equals(oper)) {
result = updateDictById(req);
log.info("updateDictById out result:{}", result);
return result;
} else if ("del".equals(oper)) {
result = deleteDictById(req);
log.info("deleteDictById out result:{}", result);
return result;
} else if ("add".equals(oper)) {
result = addDict(req);
log.info("addDict out result:{}", result);
return result;
}
return BaseResult.returnSuccessMessage("所传编辑类型有误!", uuid);
}
PS:之前一致纠结的问题是怎么让注解知道是修改删除还是新增,即使这个注解是用在方法上的,但当真的用在updateDictById/addDict/deleteDictById上时,aop并不会进去,自定义注解也不支持占位符(mark一下占位符的使用:MessageFormat.format(),莫大神推荐的),还好req里面有oper(edit/add/del),可以在切面里面拿到req的具体值。
看到莫大神写的一段代码,注解里面是可以有占位符的,但是这里好像也不太适用
@RequestMapping(value = "dataGridEdit/{service}/{method}", method = RequestMethod.POST)
@ResponseBody
public BaseResult dataGridEdit(@PathVariable("service") String service,@PathVariable("method") String method,HttpServletRequest request){
UUID uuid = UUID.randomUUID();
JSONObject req = getParameterJson(request);
req.put("uuid", uuid.toString());
log.info("dataGridEdit in req : {},url is {}", req,request.getRequestURI());
return callService(req, "app/public/"+service+"/"+method);
}
定义切面:
@Aspect
@Component
public class OperateLogAspect {
private static final Logger logger = LoggerFactory.getLogger(OperateLogAspect.class);
@Autowired
TableChangeRecoderDao tableChangeRecoderDAO;
/**
* Controller层切点 注解拦截
*/
@Pointcut("@annotation(com.paic.patee.core.app.aop.OperateLogAnnotation)")
public void controllerAspect() {
}
// 配置controller环绕通知,使用在方法aspect()上注册的切入点
@Around(value = "controllerAspect()&&@annotation(operate)")
public Object around(ProceedingJoinPoint pjp, OperateLogAnnotation operate) throws Throwable {
logger.info("OperateLogAspect around in");
JSONObject req = null;
Object[] args = pjp.getArgs();
if (args.length > 0 && args[0] instanceof JSONObject) {
req = (JSONObject) args[0];
}
JSONObject oldObject = new JSONObject();
if (null == req || req.isEmpty()) {
return pjp.proceed();
}
String oper = req.getString("oper");
if("edit".equals(oper)||"del".equals(oper)) {
oldObject = searchOldObject(operate.opration_table(),operate.key(),req.get(operate.key()));
}
BaseResult result = (BaseResult) pjp.proceed();
String errorCode = result.getErrorCode();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
String userName=request.getHeader("userName");
if ("0".equals(errorCode)) {
JSONObject json = new JSONObject();
json.put("um_id", userName);
String page = operate.page();
json.put("page", page);
if("edit".equals(oper)) {
String opration_description = compareTwoObject(req,oldObject);
json.put("opration_description", "修改了id为:"+req.get(operate.key())+opration_description);
}else if("del".equals(oper)) {
json.put("opration_description", "删除!删除掉的数据为:"+oldObject);
}else if("add".equals(oper)) {
json.put("opration_description", "新增!新增的数据为:"+req);
}
json.put("opration_table", operate.opration_table());
json.put("change_id", UUID.randomUUID().toString());
json.put("operation_time",new Date());
tableChangeRecoderDAO.insertOneRecord(json);
}
return result;
}
public JSONObject searchOldObject(String tableName,String key,Object id){
JSONObject req = new JSONObject();
req.put("table_name", tableName);
req.put("key", key);
req.put("id", "'"+id+"'");
return tableChangeRecoderDAO.queryObj(req);
}
public static String compareTwoObject(JSONObject req, JSONObject oldObject) {
StringBuffer sb = new StringBuffer();
sb.append("修改的数据为:{");
JSONObject newObject = req;
for(String str:newObject.keySet()){
for(String str1:oldObject.keySet()){
if((!"update_time".equals(str))&&str.equals(str1)&&(!newObject.get(str).equals(oldObject.get(str1)))) {
sb.append(str1).append(":").append(oldObject.get(str1));
sb.append(" 改为 ").append(newObject.get(str)).append(";");
}
}
}
sb.deleteCharAt(sb.length()-1);
sb.append("}");
return sb.toString();
}
}
AOP的基本概念:
Aspect(切面):通常是一个类,里面可以定义切入点和通知;
JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用。被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器;
Advice(通知):AOP在特定的切入点上执行的增强处理,有before(前置),after(后置),afterReturning(最终),afterThrowing(异常),around(环绕);
Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式