SpringAop实现接口日志打印

本文介绍了如何使用AOP(面向切面编程)进行日志管理和方法增强。首先定义了一个`ApiLog`注解用于标记需要记录请求和响应日志的方法。接着,定义了`MethodAdviceHandler`接口处理方法执行前后的逻辑,包括异常处理和日志记录。`BaseMethodAdviceHandler`作为基础处理器实现了基本的日志记录和异常处理。`ApiLogAdviceHandler`实现了具体的日志增强处理,包括请求和响应日志的详细记录。最后,`ApiLogAspect`切面类负责织入切点,并使用`ApiLogAdviceHandler`进行方法增强。整个流程展示了如何在Spring AOP中实现灵活的日志管理和业务逻辑增强。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

定义切面注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 *  切面注解
 * @Description 用于打印请求和响应日志的注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiLog {

    String[] inIgnoreValues() default {};

    String[] outIgnoreValues() default {};

    String apiName() default "";

}

方法增强处理器

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 方法增强处理器
 * @param <R> 目标方法返回值的类型
 */
public interface MethodAdviceHandler<R> {
    /**
     * 目标方法执行之前的判断,判断目标方法是否允许执行。默认返回 true,即 默认允许执行
     *
     * @param point 目标方法的连接点
     * @return 返回 true 则表示允许调用目标方法;返回 false 则表示禁止调用目标方法。
     * 当返回 false 时,此时会先调用 getOnForbid 方法获得被禁止执行时的返回值,然后
     * 调用 onComplete 方法结束切面
     */
    default boolean onBefore(ProceedingJoinPoint point) {
        return true;
    }

    /**
     * 禁止调用目标方法时(即 onBefore 返回 false),执行该方法获得返回值,默认返回 null
     *
     * @param point 目标方法的连接点
     * @return 禁止调用目标方法时的返回值
     */
    default R getOnForbid(ProceedingJoinPoint point) {
        return null;
    }

    /**
     * 目标方法抛出异常时,执行的动作
     *
     * @param point 目标方法的连接点
     * @param e     抛出的异常
     * @throws Throwable
     */
    void onThrow(ProceedingJoinPoint point, Throwable e) throws Throwable;

    /**
     * 获得抛出异常时的返回值,默认返回 null
     *
     * @param point 目标方法的连接点
     * @param e     抛出的异常
     * @return 抛出异常时的返回值
     */
    default R getOnThrow(ProceedingJoinPoint point, Throwable e) {
        return null;
    }

    /**
     * 目标方法完成时,执行的动作
     *
     * @param point     目标方法的连接点
     * @param startTime 执行的开始时间
     * @param permitted 目标方法是否被允许执行
     * @param thrown    目标方法执行时是否抛出异常
     * @param result    执行获得的结果
     */
    default void onComplete(ProceedingJoinPoint point, long startTime, boolean permitted, boolean thrown, Object result) {
    }
}

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;

/**
 * 方法增强器基础类
 * @param <R>
 */
@Slf4j
public abstract class BaseMethodAdviceHandler<R> implements MethodAdviceHandler<R> {

    /**
     * 抛出异常时候的默认处理
     */
    @Override
    public void onThrow(ProceedingJoinPoint point, Throwable e) throws Throwable {
        String methodDesc = getMethodDesc(point);
        Object[] args = point.getArgs();
        log.error("{} 执行时出错,入参={}", methodDesc, args, e);
    }

    /**
     * 获得被代理的方法
     *
     * @param point 连接点
     * @return 代理的方法
     */
    protected Method getTargetMethod(ProceedingJoinPoint point) {
        // 获得方法签名
        Signature signature = point.getSignature();
        // Spring AOP 只有方法连接点,所以 Signature 一定是 MethodSignature
        return ((MethodSignature) signature).getMethod();
    }

    /**
     * 获得方法描述,目标类名.方法名
     *
     * @param point 连接点
     * @return 目标类名.执行方法名
     */
    protected String getMethodDesc(ProceedingJoinPoint point) {
        // 获得被代理的类
        Object target = point.getTarget();
        String className = target.getClass().getSimpleName();
        Signature signature = point.getSignature();
        String methodName = signature.getName();
        return className + "." + methodName;
    }

}
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.MDC;
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.Objects;
import java.util.UUID;

/**
 * 日志方法增强处理器的实现
 *
 */
@Slf4j
@Component
public class ApiLogAdviceHandler extends BaseMethodAdviceHandler<Object> {

    /**
     * 会话ID
     */
    private final static String SESSION_KEY = "sessionId";

    /**
     * 在进入controller之前拦截并打印请求报文日志
     *
     * @param point 目标方法的连接点
     * @return
     */
    @Override
    public boolean onBefore(ProceedingJoinPoint point) {
        String token = UUID.randomUUID().toString().replace("-", "");
        MDC.put(SESSION_KEY, token);

        Object[] args = point.getArgs();
        Method method = getTargetMethod(point);
        ApiLog apiLog = method.getAnnotation(ApiLog.class);
        String[] ignoreValues = apiLog.inIgnoreValues();
        SimplePropertyPreFilter propertyPreFilter = new SimplePropertyPreFilter();
        if (ignoreValues.length > 0) {
            Arrays.stream(ignoreValues).forEach(item -> propertyPreFilter.getExcludes().add(item));
        }

        ServletRequestAttributes requestAttributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (Objects.isNull(requestAttributes)) {
            log.info(
                    "==> 非http请求:" + "==> 请求报文:" + (args.length > 0 ? JSON.toJSONString(args[0], propertyPreFilter) : ""));
            return true;
        }

        HttpServletRequest request = requestAttributes.getRequest();
        String ip = HttpUtils.getIpAddress(request);
        log.info("==> 请求者IP:" + ip + "\n" + "==> 请求接口:" + request.getMethod() + " " + request.getRequestURL() + "\n"
                + "==> 请求报文:" + (args.length > 0 ? JSON.toJSONString(args[0], propertyPreFilter) : ""));
        return true;
    }

    @Override
    public void onThrow(ProceedingJoinPoint point, Throwable e) throws Throwable {
        throw e;
    }

    /**
     * 返回信息后,打印响应报文的日志
     *
     * @param point     目标方法的连接点
     * @param startTime 执行的开始时间
     * @param permitted 目标方法是否被允许执行
     * @param thrown    目标方法执行时是否抛出异常
     * @param result    执行获得的结果
     */
    @Override
    public void onComplete(ProceedingJoinPoint point, long startTime, boolean permitted, boolean thrown,
                           Object result) {
        long costTime = System.currentTimeMillis() - startTime;
        Method method = getTargetMethod(point);
        ApiLog apiLog = method.getAnnotation(ApiLog.class);
        String[] ignoreValues = apiLog.outIgnoreValues();
        SimplePropertyPreFilter propertyPreFilter = new SimplePropertyPreFilter();
        if (ignoreValues.length > 0) {
            Arrays.stream(ignoreValues).forEach(item -> propertyPreFilter.getExcludes().add(item));
        }
        log.info("<== 响应报文:{}, 耗时:{}ms", JSON.toJSONString(result, propertyPreFilter), costTime);
        MDC.remove(SESSION_KEY);
    }

}

切面类

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * 切面父类
 */
public abstract class BaseMethodAspect implements ApplicationContextAware {

    /**
     * 切点,通过 @Pointcut 指定相关的注解
     */
    protected abstract void pointcut();

    /**
     * 对目标方法进行环绕增强处理,子类需通过 pointcut() 方法指定切点
     *
     * @param point 连接点
     * @return 方法执行返回值
     */
    @Around("pointcut()")
    public Object advice(ProceedingJoinPoint point) throws Throwable {
        // 获得切面绑定的方法增强处理器的类型
        Class<? extends MethodAdviceHandler<?>> handlerType = getAdviceHandlerType();
        // 从 Spring 上下文中获得方法增强处理器的实现 Bean
        MethodAdviceHandler<?> adviceHandler = appContext.getBean(handlerType);
        // 使用方法增强处理器对目标方法进行增强处理
        return advice(point, adviceHandler);
    }

    /**
     * 获得切面绑定的方法增强处理器的类型
     *
     * @return
     */
    protected abstract Class<? extends MethodAdviceHandler<?>> getAdviceHandlerType();

    /**
     * 使用方法增强处理器增强被注解的方法
     *
     * @param point   连接点
     * @param handler 切面处理器
     * @return 方法执行返回值
     */
    private Object advice(ProceedingJoinPoint point, MethodAdviceHandler<?> handler) throws Throwable {
        // 执行之前,返回是否被允许执行
        boolean permitted = handler.onBefore(point);
        // 方法返回值
        Object result;
        // 是否抛出了异常
        boolean thrown = false;
        // 开始执行的时间
        long startTime = System.currentTimeMillis();

        // 目标方法被允许执行
        if (permitted) {
            try {
                // 执行目标方法
                result = point.proceed();
            } catch (Throwable e) {
                // 抛出异常
                thrown = true;
                // 处理异常
                handler.onThrow(point, e);
                // 抛出异常时的返回值
                result = handler.getOnThrow(point, e);
            }
        } else {
            // 目标方法被禁止执行,禁止执行时的返回值
            result = handler.getOnForbid(point);
        }
        // 结束
        handler.onComplete(point, startTime, permitted, thrown, result);
        return result;
    }

    private ApplicationContext appContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        appContext = applicationContext;
    }
}
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 *
 * 日志切面类
 */
@Aspect
@Component
@Slf4j
@Order(1)
public class ApiLogAspect extends BaseMethodAspect {

    /**
     * 定义空方法用于切点表达式
     */
    @Override
    @Pointcut("@annotation(com.baizhiedu.aop.ApiLog)")
    public void pointcut() {
    }


    @Override
    protected Class<? extends MethodAdviceHandler<?>> getAdviceHandlerType() {
        return ApiLogAdviceHandler.class;
    }
}

HttpUtils

import javax.servlet.http.HttpServletRequest;

public class HttpUtils {

    private static final String UNKNOWN = "unknown";

    /**
     * 获取真实ip地址,避免获取代理ip
     */
    public static String getIpAddress(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_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.getRemoteAddr();
        }
        return ip;
    }

}

### 构建任务失败解决方案 当遇到 `Execution failed for task ':app:shrinkReleaseRes'` 错误时,这通常意味着资源压缩过程中出现了问题。此错误可能由多种原因引起,包括但不限于配置不正确、依赖冲突或特定于项目的其他因素。 #### 可能的原因分析 1. **ProGuard 或 R8 配置不当** ProGuard R8 是用于优化混淆代码以及减少 APK 大小的工具。如果这些工具的配置存在问题,可能会导致资源无法正常处理[^1]。 2. **重复资源** 如果项目中有多个模块定义了相同的资源名称,可能导致冲突并引发该错误。检查是否存在重名的 drawable、string 等资源文件[^2]。 3. **第三方库兼容性** 某些第三方库可能当前使用的 Gradle 插件版本或其他库存在兼容性问题,从而影响到资源打包过程中的行为[^3]。 4. **Gradle 缓存问题** 有时旧缓存数据会干扰新编译的结果,尝试清理本地仓库重新同步项目可以帮助排除此类潜在障碍[^4]。 #### 推荐的操作方法 为了有效解决问题,建议按照以下步骤逐一排查: ```bash # 清理项目构建目录 ./gradlew clean # 删除 .gradle 文件夹下的所有内容以清除缓存 rm -rf ~/.gradle/caches/ ``` 调整 `build.gradle` 中的相关设置也是一个重要环节: ```groovy android { ... buildTypes { release { minifyEnabled true // 是否启用代码缩减 shrinkResources true // 是否开启资源压缩 proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' // 尝试禁用 shrinkResources 来测试是否为资源压缩引起的错误 // shrinkResources false } } } ``` 此外,在 `proguard-rules.pro` 文件内添加必要的保留规则,防止关键类被意外移除: ```text -keep class com.example.yourpackage.** { *; } # 替换为你自己的包路径 -dontwarn androidx.**,com.google.** # 忽略警告信息 ``` 最后,确保所使用的 Android Studio 版本是最新的稳定版,并且已经应用了所有的补丁更新。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值