欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

spring boot 之 aop日志

程序员文章站 2022-04-25 20:01:41
...

一、aop日志简述

AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善,是spring中最核心的原则。在实际开发中,日志打印是非常重要的,一般都需要对请求的出参入参以及处理时间进行打印。为了提高代码的重用性,一般将这些日志打印放在aop中。

二、代码示例

1.引入jar包依赖

<!-- 对aop的支持 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.aop编写

package com.wangcongming.aop;

import com.alibaba.fastjson.JSON;
import com.wangcongming.annotation.LogDescription;
import com.wangcongming.annotation.LogType;
import com.wangcongming.util.CollectionUtils;
import com.wangcongming.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Map;

/**
 * @author wangcongming
 * @version V1.0
 * @ClassName LogAspect
 * @Description 日志切面
 *              @Pointcut 定义切点 有两种写法
 *              1. @Pointcut("execution(public * com.learn.blog.controller.*.*(..))")
 *              2. @Pointcut("@annotation(com.chhliu.springboot.aop.annotation.RedisCache)") 注解类型
 *              @Before  定义前置通知
 *              获取请求信息
 *              ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
 *              HttpServletRequest request = attributes.getRequest();
 * @date 2018/4/7 18:11
 */
@Aspect
@Component
@Slf4j
public class LogAspect {

    /**
     * 定义切点
     */
    @Pointcut("@within(com.wangcongming.annotation.AopLog)")
    public void logPointcut(){}

    /**
     *
     * @param joinPoint
     * @throws Throwable
     */
    @Before("logPointcut()")
    public void deBefore(JoinPoint joinPoint) throws Throwable {

    }
  
    @AfterReturning(returning = "ret", pointcut = "logPointcut()")
    public void doAfterReturning(Object ret) throws Throwable {
    }
  
    //后置异常通知  
    @AfterThrowing(pointcut = "logPointcut()",throwing = "e")
    public void error(JoinPoint jp, Throwable e){
        log.error(getUrl() + ":请求异常<<<<<<<<<<<<<<<<<<<<<<<<<",e);
    }  
  
    //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行  
    @After("logPointcut()")
    public void after(JoinPoint jp){
    }

    /**
     * 用于打印日志
     * 当未使用注解时
     * @param pjp
     * @return
     */
    //环绕通知,环绕增强,相当于MethodInterceptor  
    @Around("logPointcut()")
    public Object around(ProceedingJoinPoint pjp) {
        long currentTime = System.currentTimeMillis();
        try {
            //拦截的实体类
            Object target = pjp.getTarget();
            //拦截方法的参数
            Object[] args = pjp.getArgs();
            //拦截的方法名称  
            String methodName = pjp.getSignature().getName();
            LogDescription logDes = this.getLogDes(pjp);
            String name = "";
            if(logDes == null){
                name = getUrl();
                return dealLog(name,methodName,args,currentTime,pjp);
            }else{
                name = logDes.name();
                name = StringUtils.isEmpty(name) ? getUrl() : name;
                LogType[] type = logDes.type();
                Map<Object, Object> map = CollectionUtils.convertMap(type);
                if(map.get(LogType.ALL) != null){
                    return dealLog(name, methodName,args, currentTime,pjp);
                }
                if(map.get(LogType.PARAMETER) != null){
                    log.info("|--->{}<--|<<{}>>请求参数:{}",methodName,name,args);
                }
                //执行方法,并获取返回结果
                Object o =  pjp.proceed();
                if(map.get(LogType.RESULT) != null){
                    log.info("|--->{}<--|<<{}>>请求返回结果:{}",methodName,name, JSON.toJSONString(o));
                }
                if(map.get(LogType.TIME) != null){
                    long endTime = System.currentTimeMillis();
                    log.info("|--->{}<--|<<{}>>请求耗时:{}ms******************",methodName,name,(endTime - currentTime));
                }
                return o;
            }
        } catch (Throwable e) {
            log.error("aop异常",e);
            return null;  
        }  
    }

    private Object dealLog(String name,String methodName,Object[] args,long currentTime,ProceedingJoinPoint pjp){
        log.info("|--->{}<--|<<{}>>请求参数:{}",methodName,name,args);
        try {
            Object o = pjp.proceed();
            log.info("|--->{}<--|<<{}>>请求返回结果:{}",methodName,name, JSON.toJSONString(o));
            long endTime = System.currentTimeMillis();
            log.info("|--->{}<--|<<{}>>请求耗时:{}ms******************",methodName,name,(endTime - currentTime));
            return o;
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return null;
    }

    private LogDescription getLogDes(ProceedingJoinPoint pjp){
        Method method = getMethod(pjp);
        if(method == null){
            log.error("无法获取到目标方法对象。。。。。");
            return null;
        }
        LogDescription annotation = method.getAnnotation(LogDescription.class);
        return annotation;
    }

    /**
     * 获取打印日志的标识符,优先使用注解LogDescription 中的name,获取不到则使用url
     * @return
     */
    private String getName(ProceedingJoinPoint pjp){
        LogDescription logDes = this.getLogDes(pjp);
        //注解不存在
        if(logDes == null){
            return getUrl();
        }
        String name = logDes.name();
        //注解中name未指明值
        if(StringUtils.isEmpty(name)){
            return getUrl();
        }
        return name;
    }

    /**
     * 获取所拦截的目标方法 Method
     * @param pjp
     * @return
     */
    private Method getMethod(ProceedingJoinPoint pjp){
        Object[] args = pjp.getArgs();
        Class<?>[] argTypes = new Class[pjp.getArgs().length];
        for (int i = 0; i < args.length; i++) {
            argTypes[i] = args[i].getClass();
        }
        Method method = null;
        try {
            method = pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(), argTypes);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        return method;
    }

    /**
     * 获取当前请求的url
     * @return
     */
    private String getUrl(){
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String url = request.getRequestURI().toString();
        return url;
    }

    /**
     * 获取请求参数列表
     * @param request
     * @return
     */
    private String getParams(HttpServletRequest request){
        Enumeration<String> parameterNames = request.getParameterNames();
        StringBuffer params = new StringBuffer();
        while (parameterNames.hasMoreElements()){
            String name = parameterNames.nextElement();
            String value = request.getParameter(name);
            params.append(String.format("参数:[%s=%s] ",name,value));
        }
        return params.toString();
    }
}

代码中的logPointcut()是对注解进行切入的,也可以对包进行切入。使用注解切入,有一个好处就是,logaop可以单独封装成一个jar包,直接引入就可使用。

其中@AopLog @LogDescription都是自定义的注解,代码如下:

package com.wangcongming.annotation;

import java.lang.annotation.*;

/**
 * @author wangcongming
 * @version V1.0
 * @ClassName AopLog
 * @Description 自定义log注解 该注解只能被使用在controller层
 * @date 2018/4/7 18:11
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface AopLog {

    String value() default "";
}
package com.wangcongming.annotation;

import java.lang.annotation.*;

/**
 * @author wangcongming
 * @version V1.0
 * @ClassName LogDescription
 * @Description 自定义log注解 该注解只能被使用在controller层
 * @date 2018/4/7 18:11
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogDescription {

    //标识符,建议使用url
    String name() default "";
    //打印日志类型
    LogType[] type() default {};
}

三、pointcut的其他用法

Pointcut 是指那些方法需要被执行"AOP",是由"Pointcut Expression"来描述的.
Pointcut可以有下列方式来定义或者通过&& || 和!的方式进行组合. 
args()
@args()
execution()
this()
target()
@target()
within()
@within()
@annotation
其中execution 是用的最多的,其格式为:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
returning type pattern,name pattern, and parameters pattern是必须的.
ret-type-pattern:可以为*表示任何返回值,全路径的类名等.
name-pattern:指定方法名,*代表所以,set*,代表以set开头的所有方法.
parameters pattern:指定方法参数(声明的类型),(..)代表所有参数,(*)代表一个参数,(*,String)代表第一个参数为任何值,第二个为String类型.
举例说明:
任意公共方法的执行:
execution(public * *(..))
任何一个以“set”开始的方法的执行:
execution(* set*(..))
AccountService 接口的任意方法的执行:
execution(* com.xyz.service.AccountService.*(..))
定义在service包里的任意方法的执行:
execution(* com.xyz.service.*.*(..))
定义在service包和所有子包里的任意类的任意方法的执行:
execution(* com.xyz.service..*.*(..))
定义在pointcutexp包和所有子包里的JoinPointObjP2类的任意方法的执行:
execution(* com.test.spring.aop.pointcutexp..JoinPointObjP2.*(..))")

***> 最靠近(..)的为方法名,靠近.*(..))的为类名或者接口名,如上例的JoinPointObjP2.*(..))

pointcutexp包里的任意类.
within(com.test.spring.aop.pointcutexp.*)
pointcutexp包和所有子包里的任意类.
within(com.test.spring.aop.pointcutexp..*)
实现了Intf接口的所有类,如果Intf不是接口,限定Intf单个类.
this(com.test.spring.aop.pointcutexp.Intf)
***> 当一个实现了接口的类被AOP的时候,用getBean方法必须cast为接口类型,不能为该类的类型.

带有@Transactional标注的所有类的任意方法.
@within(org.springframework.transaction.annotation.Transactional)
@target(org.springframework.transaction.annotation.Transactional)
带有@Transactional标注的任意方法.
@annotation(org.springframework.transaction.annotation.Transactional)
***> @within和@target针对类的注解,@annotation是针对方法的注解

参数带有@Transactional标注的方法.
@args(org.springframework.transaction.annotation.Transactional)
参数为String类型(运行是决定)的方法.
args(String)

 

分享一套spring boot 学习视频:https://download.csdn.net/download/linhui258/10546450