Spring Boot 学习之路——4 AOP注解方式实现打印日志

前言:

据XX统计,四分之一的代码都是日志有关,日志对于定位和解决问题尤为重要,以前公司的编码规范中要求接口必须在日志中记录入参和返回值以及关键代码,参数部分完全可以用Spring的AOP——面向切面来实现。


什么叫AOP?

百度:AOP(Aspect Oriented Programming),意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AspectJ是AOP的一个很悠久的实现,它能够和 Java 配合起来使用。

Aspect: Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
Joint point:表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
Pointcut:表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
Advice:Advice 定义了在 pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。

简单介绍完概念,上代码。


1.定义日志注解类

package com.joanna.annotationdemo.demo.annotation;


import java.lang.annotation.*;

/**
 * 监听每个方法传入的参数、返回值,打印日志
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface LoggerProfile {
    // 方法注释
    String methodNote();
}

2.定义日志处理的切面类

package com.joanna.annotationdemo.demo.annotation;

import com.alibaba.fastjson.JSON;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * 使用AOP对方法进行日志记录
 */
@Aspect//表示这是一个切面类
@Component//加入IOC容器
public class LoggerProfilerIntercepter {

    private static final Logger LOGGER = LoggerFactory.getLogger("LoggerProfilerDemo");

    // 用@PointCut注解统一声明,然后在其它通知中引用该统一声明即可!
    @Pointcut("@annotation(com.joanna.annotationdemo.demo.annotation.LoggerProfile)")
    public void loggerProfilePointCut() {
    }

    // 环绕通知@Around 修饰的方法一定要将方法的返回值返回!本身相当于代理!
    @Around("loggerProfilePointCut()")
    public Object doLoggerProfiler(final ProceedingJoinPoint joinPoint) throws Throwable {
        Map<String, Object> logMap = new HashMap<String, Object>();
        // part 1 获取目标方法名、参数等
        try {
            if (LOGGER.isInfoEnabled()) {
                String className = joinPoint.getTarget().getClass().getName();
                logMap.put("className", className);

                String methodName = joinPoint.getSignature().getName();
                logMap.put("methodName", methodName);

                Method method = getMethod(joinPoint);
                LoggerProfile loggerProfile = method.getAnnotation(LoggerProfile.class);
                String methodNote = loggerProfile.methodNote();
                if (null != methodNote && methodNote.length() > 0) {
                    logMap.put("note", methodNote);
                }

                Object[] args = joinPoint.getArgs();
                if (null != args && args.length > 0) {
                    for (int i = 0; i < args.length; i++) {
                        logMap.put("arg-" + i, args[i]);
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.error("LoggerProfile Part 1 Exception ", e);
        }

        // part 2 执行目标方法
        Object obj = null;
        Exception error = null;
        try {
            obj = joinPoint.proceed();
        } catch (Exception e) {
            error = e;
        }

        // part 3 获取返回值
        try {
            if (LOGGER.isInfoEnabled()) {
                if (null != error) {
                    logMap.put("error", error);
                } else {
                    if (null != obj) {
                        logMap.put("result", obj);
                    }
                }
                LOGGER.info(JSON.toJSONString(logMap));
            }
        } catch (Exception e) {
            LOGGER.error("LoggerProfile Part 2 Exception ", e);
        }
        return obj;
    }

    /**
     * 获取当前方法
     *
     * @param joinPoint
     * @return
     * @throws NoSuchMethodException
     */
    private Method getMethod(final JoinPoint joinPoint)
            throws NoSuchMethodException {
        final Signature sig = joinPoint.getSignature();
        if (!(sig instanceof MethodSignature)) {
            throw new NoSuchMethodException(
                    "This annotation is only valid on a method.");
        }
        final MethodSignature msig = (MethodSignature) sig;
        final Object target = joinPoint.getTarget();
        return target.getClass().getMethod(msig.getName(),
                msig.getParameterTypes());
    }

}

3.添加依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.8</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.8</version>
</dependency>

4.在controller方法上添加注解

@LoggerProfile(methodNote = "humanSays")
@RequestMapping(value = "/humanSays", method = RequestMethod.GET)
public String humanSays(@RequestParam(name = "name", required = false) String name,
                        @RequestParam(name = "comeFrom", required = false) String comeFrom) {
    return "Hello " + name + ", who comes from " + comeFrom + ", I'm a human , hahaha";
}


5.测试

启动项目,访问:http://localhost:8080/sayHello/humanSays?name=Joanna&comeFrom=China

可以看到console中打印:

Spring Boot 学习之路——4 AOP注解方式实现打印日志