系统运维(二):AOP+反射实现 普通操作日志设计

普通操作日志和业务操作日志

在开始做之前,必须把两个日志分清楚,那就是普通操作日志业务操作日志,这两者有何区别?

在我理解,普通操作日志就是单表的操作记录,而业务操作日志则就是一系列的普通操作日志的集合。

打个比方,用户需要购买一样宝贝,已经到了下单那步,下单就是个业务,这个业务背后就是一系列的业务,如:

生成订单 → 生成商品快照 → 发送一条站内信 → 删除购物车里对应宝贝

这样一个下单操作就包含了4部分,可以把这4部分看成是4张表,分别对这4张表进行对应的操作,就实现了业务。

因为不同项目的业务不尽相同,所以它无法做成通用模块,所以下面讲的是普通操作日志。

首先,哪些地方需要记录操作日志?执行insert、update、delete这3个操作的时候,就需要进行日志,而日志执行的先后顺序如下

insert

在insert后执行

update

在update前后都要执行,操作前获取操作前数据,操作后获取操作后数据

delete

在delete前执行

实现的效果展示如下



根据界面可以抽象成2张表,一张主表一张从表,主表记录操作表及操作人等信息,从表记录操作的表字段信息。

下面说说实现

日志的管理可以使用过滤器,或者是Spring的拦截器进行日志的处理。

他们的好处,就是配一下就能够切入到系统当中。

如果是用过滤器,只要对所有的.do提交进行拦截,然后获取action的提交路径就可以获取对每个方法的调用,然后进行日志记录。使用过滤器的好处是可以自己选择性的对某一些方法进行过滤,记录日志。

另外一种就是使用Spring的AOP了,实现简单,只要配置一下配置文件就可以了。但这种方式会拦截下所有的对action或者dao的每个操作,所以效率比较低。

下面我说说使用AOP实现方式

spring中配置。配置在指定包下的指定方法执行前或者后,执行指定类

<!-- 配置日志管理 -->
<bean id="LogManagerment"class="com.log.LogManagerment"></bean>
    <aop:config>
           <!--调用日志类-->
    <aop:aspectid="b" ref="LogManagerment">
   <!--配置在com.dao包下所有的类的add方法,在调用之前都会被拦截-->
    <aop:pointcutid="logScope" expression="execution(*com.dao.*.add*(..))"/>
   <!--在com.dao包下所有的类的add方法执行之前会调用LogManagerment中的before方法-->
    <aop:beforepointcut-ref="logScope" method="before"/>
    </aop:aspect>
    </aop:config>

日志管理类,和配置文件对应:指定包下的指定方法执行前或者后,执行该类

LogManagerment的before方法,在com.dao包下所有的类的add方法执行之前会执行这个方法。

这个方法,通过反射得到增加实体的信息,包括实体名称、字段类型、字段值

/**
 * 日志管理类
 * @author TCH
 *
 */
public class LogManagerment {
/**
 * add操作执行之前执行的方法
 * @param joinpoint包含action所有的相关配置信息和request等内置对象。
 * @throwsClassNotFoundException
 * @throwsInstantiationException
 * @throwsIllegalAccessException
 */
public void before(JoinPoint joinpoint)
  throwsClassNotFoundException, InstantiationException, IllegalAccessException{
 
                 ModelClassHelpermodelHelper = new ModelClassHelper();
                 
                 //此方法返回的是一个数组,数组中包括request以及ActionCofig等类对象
           joinpoint.getArgs();
            Object[]argumnets = joinpoint.getArgs();
           
            for (int i = 0;i < argumnets.length; i++) {
                   // 打印实体类
                   String className =argumnets[i].toString(); // com.entity.User@eiid000
                   String modelName =className.substring(className.lastIndexOf('.') + 1, className.indexOf('@'));
                   System.out.println("实体类:"+ modelName);
//                   System.out.println("实体对应表:t_"+ modelName.toLowerCase());
                   System.out.println("==========================");
                   
                   // 打印参数对象信息
                Object obj =argumnets[i];
                List  fieldInfos= modelHelper.getFiledsInfo(obj);
                for(Iterator iter = fieldInfos.iterator(); iter
.hasNext();) {
Map map = (Map) iter.next();
System.out.println("字段类型:" + map.get("type"));
System.out.println("字段名称:" + map.get("name"));
System.out.println("字段值:" + map.get("value"));
System.out.println("==========================");
}
}
           
            // 打印操作类型
            StringmethodName = joinpoint.getSignature().getName();
           System.out.println("方法名:" + methodName);
           System.out.println("操作类型" +modelHelper.getOperationType(methodName));
           
           System.out.println("被拦截方法调用之前调用此方法 before");
  }
 
/**
 * 根据方法名称,映射操作类型
 * @param methodName
 * @return
 */
private String getOperationType(String methodName) {
String type = "查询";
if (methodName.indexOf("add") > -1) {
type = "增加";
} else if (methodName.indexOf("del") > -1) {
type = "删除";
} else if (methodName.indexOf("update") > -1) {
type = "更新";
}
return type;
}


ModelClassHelper类,主要实现通过反射Object,得到对应的具体类信息

package com.log;
 
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
public class ModelClassHelper {
/**
 * 通过反射Object,根据字段名称,获取字段值
 * @param fieldName
 * @param o
 * @return
 */
private Object getFieldValueByName(String fieldName, Object o) {
try {
String firstLetter = fieldName.substring(0, 1).toUpperCase();
String getter = "get" + firstLetter +fieldName.substring(1);
Method method = o.getClass().getMethod(getter, new Class[] {});
Object value = method.invoke(o, new Object[] {});
return value;
} catch (Exception e) {
return null;
}
}
 
/**
 * 通过反射Object,得到字段名称
 * @param o
 * @return
 */
private String[] getFiledName(Object o) {
Field[] fields = o.getClass().getDeclaredFields();
String[] fieldNames = new String[fields.length];
 
for (int i = 0; i < fields.length; i++) {
System.out.println(fields[i].getType());
fieldNames[i] = fields[i].getName();
}
return fieldNames;
}
 
/**
 * 通过反射Object,获取对象信息:类型、名称、值
 * @param o
 * @return
 */
public List getFiledsInfo(Object o) {
Field[] fields = o.getClass().getDeclaredFields();
String[] fieldNames = new String[fields.length];
List list = new ArrayList();
Map infoMap = null;
for (int i = 0; i < fields.length; i++) {
infoMap = new HashMap();
infoMap.put("type", fields[i].getType().toString());
infoMap.put("name", fields[i].getName());
infoMap.put("value",getFieldValueByName(fields[i].getName(), o));
list.add(infoMap);
}
return list;
}
}


打印信息

实体类:User

==========================

字段类型:class java.lang.String

字段名称:username

字段值:admin

==========================

字段类型:class java.lang.String

字段名称:password

字段值:admin

==========================

字段类型:class java.lang.String

字段名称:name

字段值:tch

==========================

字段类型:int

字段名称:age

字段值:25

==========================

方法名:addUser

操作类型增加

被拦截方法调用之前调用此方法 before

这些打印的信息,在实际系统中应该写入数据库。这里有个小问题,通过aop可以得到实体类以及数据信息,这些信息都是通过映射得到的,跟具体业务无关,所以他们对应的名字(表名、字段名)怎么获得,我现在想的是把表的信息在配置文件中保留一份。

小结

我们容易着重系统的功能性实现,却忽视日志的作用。日志有系统日志、业务日志、工作日志等。

系统日志可供用户查询指定时间段内对系统的操作记录,也就是上面所说的普通操作日志,正如上面实现那样,跟具体业务无关,像个切片写入系统中,需要时配置下就好了。

业务操作日志则是跟系统的具体业务有关,是一系列的普通操作日志的集合。

工作日志在今目标中可以看到,用来跟踪业务的进展,方便管理者更好的把握工作进展情况,以便及时加以控制,有效调配资源。在今目标里,管理者可以批量查看一个部门或者项目团队成员的日志,跟踪重要事件,及时指导。工作日志还有一个业绩证明的作用,员工的工作量、工作效果在这里得到体现。工作日志,对管理很有帮助的工具。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值