JAVA反射与AOP双剑合璧详细记录操作日志

来源:互联网 发布:龙腾世纪3优化差 编辑:程序博客网 时间:2024/06/06 15:41

       运用AOP来记录用户的操作日志在项目中比较常见,优点是只需在一个地方编写Advice,通过AOP声明(织入)然后就可以记录很多不同的操作(API)。但是也有其缺点,因为Advice服务于不同的API,而各个API的参数,返回值不同,甚至服务的对象都不一样,那么能做到的也只能是判断是否有异常,异常的具体信息等简单的内容。如果想要个性化的为每一个API都记录执行参数,返回值,甚至Target的属性时就无能为力,因为能够拿到对象,但是不知道到底是什么类型的。本文就利用Java反射机制来获取这些信息。


       首先要能使用反射,那么就必须知道方法或者属性名称,所以需要配置相关信息。本示例中用Bean的属性(Field)来配置。一个操作对应一个API,由开发人员配置。最后记录的信息以key=value的形式保存,key就是key,容易理解,value是指对对象(参数和返回值两种)的field属性值,如果嵌套多层则用.(dot)隔开。值得注意的的参数的情形,[0]表示第一个参数,[1]表示第二个参数。之所以使用序号是由于Java参数名在编译完之后会丢失,如果AOP使用AspectJ编译的话可以保存。XML文件具体设置如下:

<apis>     <api name="ServerWebServiceImp.login">  <params><param key="user_name" value="[0]" /><param key="password" value="[1]" />  </params>  <returns><return key="aspclient_id" value="ASPClient_Id" /><return key="user_id" value="users_Id" />  </returns>      </api></apis>

需要把XML解析为JavaBean存到内存里面,解析引擎有很多选择,如Dom4j等。具体的解析过程不贴了,很简单,JavaBean如下:

/** * API实体类 */public class ApiEntity {// 名称private String name;// API的参数private List<Pair> params;// API返回值private List<Pair> returns;public ApiEntity() {params = new ArrayList<ApiEntity.Pair>();returns = new ArrayList<ApiEntity.Pair>();}public void addParam(ApiEntity.Pair pair) {params.add(pair);}public void addReturn(ApiEntity.Pair pair) {returns.add(pair);}public String getName() {return name;}public void setName(String name) {this.name = name;}public List<Pair> getParams() {return params;}public void setParams(List<Pair> params) {this.params = params;}public List<Pair> getReturns() {return returns;}public void setReturns(List<Pair> returns) {this.returns = returns;}public static class Pair {private String key;private String value;public String getKey() {return key;}public void setKey(String key) {this.key = key;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}}}


         上面是说明如何设置以及表示API,下面就需要利用设置的内容来具体处理参数以及返回值。通常我们记录的是JavaBean的属性值,但是也会出现List,Map,甚至HttpSession的Attribute。为了能够处理所有的内容,我们需要设置一个插件的钩子,有特殊需求的人可以自己去处理获取对象内容的过程。声明一个接口:

/**
 * 如何获取对象的值或者属性值接口
 */
public interface ValueGetter {
/**
* 判断是否可以处理该对象的属性值

* @param target
*            需要处理的对象,已经保证不会为NULL
* @param key
*            配置文件中的key值,起辅助作用
* @param fieldName
*            对应的属性名称,已经保证不会为空
* @return
*/
Boolean canGet(Object target, String key, String fieldName);


/**
* 获取对象的属性值

* @param target
*            需要处理的对象,已经保证不会为NULL
* @param fieldName
*            对应的属性名称,已经保证不会为空
* @return
*/
Object getValue(Object target, String fieldName);
}


具有特殊需求的开发人员可以实现该接口并注册,那么框架自动会调用个性化的实现。下面是普通的JavaBean实现,框架自己提供的,主要用到Java的反射机制:

/** * 默认的JavaBean获取属性实现. 支持处理私有的,父类的属性 */public class JavaBeanValueGetter implements ValueGetter {@Overridepublic Boolean canGet(Object target, String key, String fieldName) {Field field = getField(target.getClass(), fieldName);if (field != null) {return true;} else {return false;}}@Overridepublic Object getValue(Object target, String fieldName) {Field field = getField(target.getClass(), fieldName);Object fieldValue = null;if (field != null) {field.setAccessible(true);try {fieldValue = field.get(target);} catch (Exception e) {e.printStackTrace();}}return fieldValue;}@SuppressWarnings("rawtypes")private Field getField(Class c, String fieldName) {Field field = null;try {field = c.getDeclaredField(fieldName);} catch (NoSuchFieldException e) {Class parentClass = c.getSuperclass();if (parentClass != null) {field = getField(parentClass, fieldName);}}return field;}}

Around的Advice实现我也不贴了,主要用Spring的AOP实现。该Advice里面会维护一个List<ValueGetter>,并且BeanValueGetter为第一个(效率的考虑,用得最多的是BeanValueGetter)。下面举一个最简单的例子:

public class Customer {private String name;private Address address;public String getName() {return name;}public void setName(String name) {this.name = name;}public Address getAddress() {return address;}public void setAddress(Address address) {this.address = address;}}

public class Address {private String roadNo;public String getRoadNo() {return roadNo;}public void setRoadNo(String roadNo) {this.roadNo = roadNo;}}


API为 public Long saveCustomer(Customer customer);

配置文件为

<api name="saveCustomer"><params><param key="roadNo" value="[0].address.roadNo" /></params><returns><return key="customer_id" value="" /></returns></api>

特别说明: .表示的嵌套属性先分割开再处理,处理的逻辑单元为没有点的情形,逐级调用,roadNo调用了两次BeanValueGetter。value为空表示对象自己,不再获取属性值。

最后的描述信息可能为:saveCustomer{params(roadNo=1024),returns(customer_id=67)}

 

  上面只是展示了最简单的情形,你可以添加更多的API属性,比如API描述信息,所属模块等。总之就是使用XML配置+Java反射+AOP详细记录操作日志信息。

原创粉丝点击