操作日志:一般系统都需要日志来记录用户操作情况,我们也称为操作日志,当然了,也有做系统日志的(前端就可以做);但是对用户来说操作日志更加容易读懂。要记录操作日志有两种方法,第一种就是手动添加操作日志,在每个方法里面手动添加操作日志,这样做的好处就是日志可以做的很精准,当然这样工作量也比较大,一般的增删改什么的都需要日志来记录,每个方法都调用一次操作日志很繁琐;第二种方法就是Spring面向切面的AOP,今天就来实践一下AOP。
首先你想要知道的是:jar包
一般来说需要这两个就够了:aspectjweaver.jar,aspectjrt.jar 有些需要cglib.jar 。
maven项目的话直接添加就OK了,或者自己去下载然后导入jar包都可以
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjrtartifactId>
<version>1.8.10version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.10version>
dependency>
让我们首先了解一下AOP
spring切面可以应用5中类型的通知:
连接点是在应用执行过程中能够插入切面的一个点。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
切点的定义会匹配通知所要织入的一个或多个连接点。通常使用明确的类和方法名称来指定这些切点,或是利用正则表达式定义匹配的类和方法名称模式来指定这些切点。
切点用于准确定位应该在什么地方应用切面的通知。
切面是通知和切点的结合。通知和切点共同定义了关于切面的全部内容——它是什么,在何时和何处完成其他功能。
引入允许我们向现有的类添加新方法或属性,通过通知类,从而可以在无需修改现有的类的情况下,让它们具有新的行为和状态。
参见元素的使用,引入新的接口行为。
织入是将切面应用到目标对象来创建新的代理对象的过程,切面在指定的连接点被织入到目标对象中(注入前置、后置通知等)。
使用时只需要在class上注解@Aspect就可以了,如下:
@Aspect
public class OperaLog {}
现在来说怎么实现记录日志操作, 上面提到比如Before —— 在方法被调用之前调用通知; After —— 在方法完成之后调用通知,无论方法执行是否成功;看到这里大家也都知道了,就是在方法执行前后添加操作日志,就是监听方法,从而参数,返回值,方法名,请求方式等等来记录日志。
我们就拿AfterReturning来说:
@Aspect
public class OperaLog {
@AfterReturning(value ="within(com.nikoyo.dms.rest.*Controller)", returning = "returnValue")
public void AfterTitle(JoinPoint joinpoint, Object returnValue) throws Exception {
}
}
大家可以要问:“winthin()”是什么?该怎么用?returning = “returnValue”是什么?
大致来讲就是winthin()监听一些特定的方法,returning 就是返回值;这是一些常见的切入点的例子 :
需要的一些方法:
//遍历请求参数 可以和下面的adminOptionContent()方法合用
for (int i = 0; i < joinpoint.getArgs().length; i++) {
Object o = joinpoint.getArgs()[i];
}
/**
* 使用Java反射来获取被拦截方法(insert、update)的参数值,
* 将参数值拼接为操作内容
*/
public String adminOptionContent(Object[] args, String mName) throws Exception {
if (args == null) {
return null;
}
StringBuffer rs = new StringBuffer();
rs.append(mName);
String className = null;
int index = 1;
// 遍历参数对象
for (Object info : args) {
//获取对象类型
className = info.getClass().getName();
className = className.substring(className.lastIndexOf(".") + 1);
rs.append("[参数" + index + ",类型:" + className + ",值:" + info);
// 获取对象的所有方法
Method[] methods = info.getClass().getDeclaredMethods();
// 遍历方法,判断get方法
for (Method method : methods) {
String methodName = method.getName();
// 判断是不是get方法
if (methodName.indexOf("get") == -1) {// 不是get方法
continue;// 不处理
}
Object rsValue = null;
try {
// 调用get方法,获取返回值
rsValue = method.invoke(info);
if (rsValue == null) {//没有返回值
continue;
}
} catch (Exception e) {
continue;
}
//将值加入内容中
rs.append("(" + methodName + " : " + rsValue + ")");
}
rs.append("]");
index++;
}
return rs.toString();
}
/***
* 获取客户端ip地址(可以穿透代理)
* @param request
* @return
*/
public static String getClientIpAddr(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_CLUSTER_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_FORWARDED");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_VIA");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("REMOTE_ADDR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
前面说的AfterReturning是成功之后执行的,如果异常或者失败呢?操作日志就得用AfterThrowing。
@AfterThrowing(value = "within(com.nikoyo.dms.rest.*Controller)",throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
String params = "";
if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
for ( int i = 0; i < joinPoint.getArgs().length; i++) {
params += joinPoint.getArgs()[i] + ";";
}
}
System.out.println(("异常方法:异常信息:{}参数:{}"+ joinPoint.getTarget().getClass().getName() + joinPoint.getSignature().getName()+e.getClass().getName()+e.getMessage()+params));
}