Srping AOP结合自定义注解实现日志功能

最近所在项目需要在每个方法执行的时候加入日志,遂想到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;
	}
	
}

切面类 LogAspect.java

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或自定义注解获取方法中局部变量的值,目前还没有找到解决办法。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Spring)