最近所在项目需要在每个方法执行的时候加入日志,遂想到Spring AOP面向切面的功能。使用切面来实现日志功能,这样就不必在每个功能的方法内添加插入日志的代码,只需将日志代码提取到切面类中。这样不仅可以减少工作量,降低程序的耦合度,而且在面对以后日志需求变化时也无需大动干戈的修改程序,只需修改切面类即可。
接下来则将日志代码贴出来,一则为了巩固记忆;二则为了记录问题。
在日志表中,记录执行方法所在的模块名称和功能名称、开始执行的时间,执行时长,操作类型、执行方法中的SQL涉及的数据库表名称。
package com.LogMethod;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.LogMethod.enums.OperationType;
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {
/*
*功能名称
*/
String functionName () default "";
/*
* 表名称
*/
String tableName() default "";
/*
* 操作类型
*/
OperationType operationType() default OperationType.UNKNOWN;
}
Java使用@interface来声明一个注解,就像使用class来声明类,用interface来声明接口一样。
自定义的注解上面按需要添加上元注解(即注解的注解):包括@Target、@Retention、@Document、@Inherited四种。
1.@Target 表示注解的作用目标
@Target(ElementType.TYPE) // 接口、类、枚举、注解
@Target(ElementType.FIELD) // 字段、枚举的常量
@Target(ElementType.METHOD) // 方法
@Target(ElementType.PARAMETER) // 方法参数
@Target(ElementType.CONSTRUCTOR) // 构造函数
@Target(ElementType.LOCAL_VARIABLE) // 局部变量
@Target(ElementType.ANNOTATION_TYPE) // 注解
@Target(ElementType.PACKAGE) // 包
2.@Retention 表示注解的保留策略
@Retention(RetentionPolicy.SOURCE) // 注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
3.@Documented 表示将注解包含在javadoc中
默认情况下javadoc命令不会将annotation生成到doc中去的,该标记就是告诉jdk将annotation生成到doc中。
4.@Inherited 表示注解可以被继承
即子类可以继承父类的注解
package com.LogMethod.enums;
public enum OperationType {
/*
* 操作类型
*/
SELECT("select"),
UPDATE("update"),
ADD("add"),
DELETE("delete"),
UNKNOWN("unknown");
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
OperationType(String s){
this.value = s;
}
}
package com.LogMethod;
import java.lang.reflect.Method;
import java.util.Date;
import java.text.SimpleDateFormat;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import com.LogMethod.bizc.LoggerImpl;
@Aspect
@Component
public class LogAspect {
@Autowired
private HttpServletRequest request;
private LoggerImpl loggerImpl = new LoggerImpl();//自定义的日志类,内部定义插入日志逻辑
public LogAspect(){
//在构造方法中加输出,确定切面类是否被spring容器加载
System.out.println("XX模块----------------LogAspect类加载-------------------");
}
@Pointcut("@annotation(com.LogMethod.OperationLog)")
public void operationLog(){}
@Around("operationLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable{
Object result = null;
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
String startTime = format.format(date);
long time = System.currentTimeMillis();
try{
result = joinPoint.proceed();
time = System.currentTimeMillis()-time;
return result;
}finally{
try{
//添加操作日志
addOperationLog(joinPoint,result,startTime,time);
}catch (Exception e){
System.out.println("LogAspect 添加操作日志失败:" + e.getMessage());
e.printStackTrace();
}
}
}
/**
* 添加操作日志
* @param joinPoint
* @param result
* @param startTime
* @param time
*/
private void addOperationLog(JoinPoint joinPoint, Object result, String startTime,long time){
Signature signature = joinPoint.getSignature();//方法签名
MethodSignature methodSignature = (MethodSignature)signature;
Method method = methodSignature.getMethod();
try {
//目标对象上有注解的方法
Method realMethod = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(), method.getParameterTypes());
//获取方法上的注解
OperationLogDetail annotation = realMethod.getAnnotation(OperationLogDetail.class);
/**
* 获取自定义注解的属性值
*/
if(annotation != null){
String functionName = annotation.functionName();//功能名
String tableName = annotation.tableName();//表名
String oprationType = annotation.operationType().getValue();//操作类型:增,删,改,查
loggerImpl.insertLogDet(request, functionNames, tableName, oprationType,startTime, time);
}
// System.out.println("classname:" + method.getDeclaringClass().getName());
// System.out.println("target:" + joinPoint.getTarget().getClass().getName());//返回被织入增强处理的目标对象
// System.out.println("proxy:" + joinPoint.getThis().getClass().getName()); //返回AOP框架为目标对象生成的代理对象
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
}
}
@Aspect注解将 LogAspect 类声明为一个切面
@Component注解将 LogAspect 交给 Spring 容器管理
LoggerImpl.java为自定义的日志类,大家可根据需求自己定义实现逻辑,这里便不再贴出。
最后,在需要的方法上,添加上自定义的注解@OperationLog,即可
@OperationLog(functionName="业扩全流程管控",tableName="T_YK_WORK",operationType=OperationType.SELECT)
public void query(){
// 查询逻辑
}
在获取方法上的注解时,起初使用的方式如下,但 getAnnotation(OperationLogDetail.class)得到的结果显示为NULL,
Signature signature = joinPoint.getSignature();//方法签名
MethodSignature methodSignature = (MethodSignature)signature;
//获取方法上的注解
OperationLogDetail annotation = methodSignature.getMethod().getAnnotation(OperationLogDetail.class);
将注解放在接口中定义的方法上,而非实现类中的方法上,则可以通过上面的方式获取到注解
或者将代码改成如下方式,也可以获取注解。(上网查询说是动态代理之类的问题,并不是很懂,还需继续研究)
Signature signature = joinPoint.getSignature();//方法签名
MethodSignature methodSignature = (MethodSignature)signature;
Method method = methodSignature.getMethod();
Method realMethod = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(), method.getParameterTypes());
//获取方法上的注解
OperationLogDetail annotation = realMethod.getAnnotation(OperationLogDetail.class);
想要通过aop或自定义注解获取方法中局部变量的值,目前还没有找到解决办法。