工作中面临日志问题以及自己现有日志的备份
问题的产生,必有其理由。说白点也就是客户需要,没办法的事。不过也到给我们添了不少麻烦。本人也希望大牛们能给在下提提更多的思路,在下在此谢过。
具体是这样:
1.要记录操作人员,操作时间,操作相应模块
2.要记录操作的原始数据(ps:列级别)和变更后数据
面临问题:
1.各个方法相对独立,没有公共接口
2.形参顺序不一,类型不一以及方法名称不一
针对以上,要是解决其实也很简单,主要不怕麻烦。写个接口,每个模块去调用,这种简单而且有效的方法。不过模块太多而且繁琐这要累死个人。
目前就我自己发现以及朋友提醒再加上资料等信息,大致将其分为2类,共四种方法:
第一类(每个相关业务中需要手工添加执行功能部分):
1.写日志操作类,在相关模块中添加此接口调用。这种方法比较简单,如果一开始就有这部分代码,那就更好解决了。此种方法自不必多说,跟正常接口一样。
2.利用消息中间件,2和1实现类似,也需要每次都调用jms,不过论性能2比1好点
3.利用log4j日志记录,相关请看:http://blog.csdn.net/ziruobing/article/details/3919501,logback也有相关
第二类(使用Aop,拦截器等):
1.Aop,该实现思路@BussAnnotation注解可以标示相关业务信息(ps:新增,删除,修改等操作,以及所属模块等信息,aop中形参ProceedingJoinPoint可以获取参数对象
@Component("userManager")
public class UserManagerApplogicImpl implements UserManagerApplogic {
@BussAnnotation(moduleName="人员管理",option="添加用户")
public void addUser(String name) {
System.out.println("add a User!Name is "+name);
}
}
@Aspect
@Component
public class LogInterceptor {
@Pointcut("execution(public * com.mlliud..*.addUser(..))")
public void aApplogic() {}
@Around(value = "aApplogic() && @annotation(annotation) &&args(object,..) ", argNames = "annotation,object")
public Object interceptorApplogic(ProceedingJoinPoint pj,BussAnnotation annotation, Object object) throws Throwable {
System.out.println("moduleName:"+annotation.moduleName());
System.out.println("option:"+annotation.option());
pj.proceed();
return object;
}
}
2.拦截器,这个简单日志还好,其他目测算了吧。个人愚笨只有想到根据访问链接记录一些日志。
看来以上问题,针对我们项目做分析。得出一下结论:
1.第一类在项目初期考虑或者有相关需求还好,涉及每个模块相关代码都要改,可以手工加入。要是项目结构复杂或者代码量较多,我觉得很坑.....
2.第二类aop中@BussAnnotation注解虽然可以标示些关键信息,但是我们毕竟要把整个vo的相关变更信息记录。由于当时参数没有特殊规范,以及类型没有规划,造成ProceedingJoinPoint获取参数没有规律可寻,例如:addUser(User u,int a),updateUser(User u,String x) 这种我获取第一个参数,往数据库中存就可以。其他你懂的....
3.拦截器....
维护时间:2016-03-15
经过短暂思考最后选择了,最终选择了aop。具体查看代码:
1.
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.FIELD, ElementType.METHOD}) public @interface BussAnnotation { String moduleName(); int option(); /* 0:新增 1:修改 2:删除 */ String repository(); }
2.具体的aop
import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import cn.mobilizer.channel.comm.mybatis.MyBatisDao; import cn.mobilizer.channel.interceptor.annotation.BussAnnotation; @Aspect @Component public class LogInterceptor implements ApplicationContextAware {/*ApplicationContextAware 主要是为了可以从spring容器中获取dao层对象*/ private final static String PACKAGELAB = "cn.base"; private static ApplicationContext context; private Object oldVo; private Object newVo; @SuppressWarnings("static-access") @Override public void setApplicationContext(ApplicationContext applicationContext) { this.context = applicationContext; } @Pointcut("execution(public * cn.base.service.ClientUserService.update(..))") public void recordLog() { } @Before(value = "recordLog() && @annotation(annotation) &&args(object,..) ", argNames = "annotation,object") public void recordLogBefore(JoinPoint point, BussAnnotation annotation, Object object) { int option = annotation.option(); String repository = annotation.repository(); Object[] args = point.getArgs(); if (option>0) { setVo(args, "1", repository); } else { setVo(args, oldVo, repository); } } @After(value = "recordLog() && @annotation(annotation) &&args(object,..) ", argNames = "annotation,object") public void recordLogAfter(JoinPoint point, BussAnnotation annotation, Object object) { String moduleName = annotation.moduleName(); Map<String, String> result = optionContent(oldVo, newVo); System.out.println(result); } /** * 通过反射比较对象参数值是否相等,并将其拼成json类型字符串 */ private Map<String, String> optionContent(Object olds, Object news) { Map<String, String> result = new HashMap<String, String>(); StringBuffer oldStr = new StringBuffer("{"); StringBuffer newStr = new StringBuffer("{"); if (olds == null && news == null) { return null; } // 得到对象的类 Class oldClazz = olds.getClass(); Class newClazz = news.getClass(); // 获取对象属性 Field[] fields = oldClazz.getDeclaredFields(); if (fields != null && fields.length > 0) { try { Method oldMethod, newMethod; Object oldValue, newValue; String fieldName; for (Field field : fields) { fieldName = field.getName(); if ("serialVersionUID".equals(fieldName)) { continue; } // 利用源对象的get方法和目标对象的set方法,来给目标对象赋值 oldMethod = oldClazz.getMethod("get" + uppercaseFirst(fieldName)); newMethod = newClazz.getMethod("get" + uppercaseFirst(fieldName)); oldValue = oldMethod.invoke(olds); newValue = newMethod.invoke(news); if (oldValue != null) { if (!oldValue.equals(newValue)) { oldStr.append("\"").append(fieldName).append("\":").append(oldValue).append(","); newStr.append("\"").append(fieldName).append("\":").append(newValue).append(","); } } else { if (newValue != null) { oldStr.append("\"").append(fieldName).append("\":").append(oldValue).append(","); newStr.append("\"").append(fieldName).append("\":").append(newValue).append(","); } } } oldStr.append("}"); newStr.append("}"); result.put("old", oldStr.toString()); result.put("new", newStr.toString()); } catch (Exception e) { e.printStackTrace(); } } return result; } private String uppercaseFirst(String name) { String firstLab = name.substring(0, 1); return name.replaceFirst(firstLab, firstLab.toUpperCase()); } private String getFieldValue(Object obj, String fieldName) { // 得到对象的类 Class clazz = obj.getClass(); String result = null; try { Method method = clazz.getMethod("get" + uppercaseFirst(fieldName)); result = String.valueOf(method.invoke(obj)); } catch (Exception e) { e.printStackTrace(); } return result; } private void setVo(Object[] args, Object oldVo, String repository) { if (args != null) { try { String clazzInfo; /*这里是获取我们自己封装的vo,obj.getClass().getName()该会获取包+类(ps:cn.base.po.ClientUser),我是根据包判断是否是我们自己的*/ for (Object obj : args) { clazzInfo = obj.getClass().getName(); if (clazzInfo.indexOf(PACKAGELAB) != -1) { newVo = obj; break; } } if (!"1".equals(oldVo)) { /*MyBatisDao为dao层公共集成方法*/ MyBatisDao dao = (MyBatisDao) context.getBean(repository)/*从spring容器中获取bean,根据名称获取*/; Map<String, Object> paramMap = new HashMap<String, Object>(); paramMap.put("clientUserId", getFieldValue(newVo, "clientUserId")); this.oldVo = dao.load("selectByPrimaryKey", paramMap);/*执行获取数据方法*/ } } catch (Exception e) { e.printStackTrace(); } } } }
3.MyBatisDao中代码
/* 日志使用 */ @SuppressWarnings({"unchecked"}) public <T> T load(String key, Object params) { if (params != null) { return (T) getSqlSession().selectOne(createStatementName(key), params); } else { return null; } }
4.具体方法上添加以下代码
@BussAnnotation(moduleName = "人员管理", option = 1, repository = "clientUserDao")
public int update(ClientUser clientUser, String oldRoleNames, Integer oldParentId)
以上总结,不管是添加,修改封装的实体vo,都是变更后的,可以根据其vo的id从数据库中 查询。所以用前置通知获取拦截方法没有执行前的数据库信息。删除想法是根据int option = annotation.option();获取类型判断是否是删除,如果是删除获取固定第几个形参,在进行操作。
维护时间:2016-03-16
遇到问题以及变更:
1.该BussAnnotation类新增int keyLocat(); /* 关键参数存放位置 新增和修改是vo位置 删除是id位置 */
使用:@BussAnnotation(moduleName = "人员管理", option = 1, repository = "clientUserDao", keyLocat = 1)
2.LogInterceptor类中@After用@AfterReturning替换。具体原因:@After不管拦截方法是否正确执行都会执行(报错),@AfterReturning只有aop拦截方法正确运行才会执行
3.@AfterReturning 该方法中报错不影响拦截的方法,不用担心该报错会被事务影响
4.问题 本来我们使用的是多数据源,但是经过测试发现@AfterReturning的方法中切换数据源无效,最终选择了表迁移
5.@Pointcut("execution(public * cn.base.*.service.*.*(..))")
public void recordLog() {
}
@Before(value = "recordLog() && @annotation(annotation) &&args(object,..) ", argNames = "annotation,object")
public void recordLogBefore(JoinPoint point, BussAnnotation annotation, Object object) {
这种我发现 @BussAnnotation(moduleName = "人员管理", option = 1, repository = "clientUserDao")才会执行,也变相可以说只有写才会被拦截,你定义全部方法也没问题
写此篇文章希望各位大神能给咱提下想法,寻找更好方法....
也希望在此宣传下群:189770377 希望各位大神能够入住,多多提出好的想法。也希望学弟学妹们,能够在此得到好的方向.