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

本文介绍如何结合Java反射和AOP技术,详细记录用户操作日志。通过配置XML文件和自定义接口ValueGetter,可以处理各种类型参数和返回值,包括JavaBean、List、Map等,实现灵活的日志记录。最终,记录的信息以key=value的形式呈现,如:saveCustomer{params(roadNo=1024),returns(customer_id=67)}。" 111674818,10293633,OpenGL材质设置与工业设计手绘素材,"['OpenGL编程', '工业设计手绘', '材质渲染', '马克笔绘画', '建筑景观']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       运用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 {
	@Override
	public Boolean canGet(Object target, String key, String fieldName) {
		Field field = getField(target.getClass(), fieldName);
		if (field != null) {
			return true;
		} else {
			return false;
		}
	}

	@Override
	public 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详细记录操作日志信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值