Aop监控所有Controller,包括void类型的response中的出参(工具类)

一、主要坐标

       <!--aop-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>30.1-jre</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.41</version>
        </dependency>

完整pom.xml文件(自行参考)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xpf</groupId>
    <artifactId>springboot_test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot_test</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--aop-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>30.1-jre</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.41</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

二、aop类

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
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.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @Author xpf
 * @Date 2023/8/2 10:30
 * @Version 1.0
 */
@Slf4j
@Aspect
@Component
@Order(99999)
public class ControllerAop {

    private static final String ACTIONAUTH = "actionAuth_";
    private static final String ACTION = "mddaction_";
    private static final String SUFFIX_LSIT = "List";

    private Set<String> excludeUris = Sets.newHashSet();
    private static final PathMatcher URI_PATH_MATCHER = new AntPathMatcher();
    private static final List<String> DEFAULT_DOWNLOAD_CONTENT_TYPE = Lists.newArrayList(
            "application/vnd.ms-excel",//.xls
            "application/msexcel",//.xls
            "application/cvs",//.cvs
            MediaType.APPLICATION_OCTET_STREAM_VALUE,//.*( 二进制流,不知道下载文件类型)
            "application/x-xls",//.xls
            "application/msword",//.doc
            MediaType.TEXT_PLAIN_VALUE,//.txt
            "application/x-gzip"//.gz
    );

    @Pointcut("execution (* com..*Controller.*(..)) && !execution (* *..ExtendController.testConnect(..))")
    public void serviceApi() {
    }

    @Around("serviceApi()")
    public Object processLog(ProceedingJoinPoint jp) throws Throwable {
        Object resObj = null;
        try{
            Method method = ((MethodSignature) jp.getSignature()).getMethod();
            //获取方法名称
            String methodName = method.getName();
            //获取参数名称
            LocalVariableTableParameterNameDiscoverer paramNames = new LocalVariableTableParameterNameDiscoverer();
            String[] params = paramNames.getParameterNames(method);

            Class<?> classTarget=jp.getTarget().getClass();
            Class<?>[] par=((MethodSignature) jp.getSignature()).getParameterTypes();
            HttpServletResponse response=null;
            RequestAttributes ra;
            ServletRequestAttributes sra;
            HttpServletRequest request=null;
            String url=null;
            String _contextPath=null;
            ContentCachingResponseWrapper wrapper = null;

            ra = RequestContextHolder.getRequestAttributes();
            sra = (ServletRequestAttributes) ra;

            if (null != sra) {
                request = sra.getRequest();
                // 如果是被排除的uri,不记录log
                if (matchExclude(request.getRequestURI())) {
                    return jp.proceed();
                }

                url = request.getRequestURI();
                if (null != request.getServletContext()) {
                    _contextPath = request.getServletContext().getContextPath();
                }
                response = sra.getResponse();
            }

            if(StringUtils.isNotBlank(_contextPath)){
                url=url.replaceFirst(_contextPath, "");
            }
            long start = System.currentTimeMillis();
            String finalUrl = url;
            String ip = getRemortIP(request);
            //获取参数
            Object[] args = jp.getArgs();
            //替换response为包装类型ContentCachingResponseWrapper
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof HttpServletResponse){
                    wrapper = new ContentCachingResponseWrapper((HttpServletResponse) args[i]);
                    args[i] = wrapper;
                    break;
                }
            }
            //过滤掉request和response,以及文件类型,不能序列化
            List<Object> filteredArgs = Arrays.stream(args)
                    .filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)
                            && !(arg instanceof MultipartFile)))
                    .collect(Collectors.toList());
            JSONObject rqsJson = new JSONObject();
            rqsJson.put("rqsMethod", methodName);
            if (filteredArgs == null || filteredArgs.isEmpty()) {
                rqsJson.put("rqsParams", null);
            } else {
                //拼接请求参数
//                Map<String, Object> rqsParams = IntStream.range(0, filteredArgs.size())
//                        .boxed()
//                        .collect(Collectors.toMap(j -> params[j], j -> filteredArgs.get(j)));

                rqsJson.put("rqsParams", JSON.toJSONString(filteredArgs));
            }
            try {
                resObj = jp.proceed(args);
            } catch (Exception e) {
                log.error(methodName + "方法执行异常!", e);
//                //可以使用线程池将日志信息存入表中
//                threadPool.submit(() -> {
//                    operationLogService.insert(JSON.toJSONString(rqsJson), null,1, finalUrl, ip, e.getMessage(), System.currentTimeMillis() - start);
//                });
                log.info("ip地址为:{} \n,url为:{} \n,耗时:{}毫秒 \n,入参:{} \n, 异常信息为:{} \n",
                        ip, finalUrl, System.currentTimeMillis() - start, JSON.toJSONString(rqsJson), e.getMessage());

                throw e;
            }
            if (isDownload(response)) {
                return resObj;
            }

            String outPrams = null;
            if (resObj == null) {
                if (wrapper != null){
                    byte[] content = wrapper.getContentAsByteArray();
                    if (content.length > 0) {
                        outPrams = new String(content, wrapper.getCharacterEncoding());

                        copyResponse(wrapper);
                    }
                }

            }else {
                //排除文件类型,无法序列化为 JSON 字符串
                if (!(resObj instanceof MultipartFile)){
                    outPrams = JSON.toJSONString(resObj);
                }
            }

            String finalOutPrams = outPrams;
//            threadPool.submit(() ->
//                operationLogService.insert(JSON.toJSONString(rqsJson), finalOutPrams,0, finalUrl, ip,  null, System.currentTimeMillis() - start)
//            );
            log.info("ip地址为:{} \n,url为:{} \n,耗时:{}毫秒 \n,入参:{} \n, 出参:{} \n",
                    ip, finalUrl, System.currentTimeMillis() - start, JSON.toJSONString(rqsJson), finalOutPrams);
        } catch(Exception e){
            log.error("AopAddLogError" + e.getMessage() ,e);
            throw e;
        }
        return resObj;
    }

    private void copyResponse(final HttpServletResponse response) {
        final ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        if (wrapper != null) {
            try {
                wrapper.copyBodyToResponse();
            } catch (IOException ignored) {
            }
        }
    }

    private boolean isDownload(final HttpServletResponse response) {
        final String contentType = response.getContentType();
        if (StringUtils.isBlank(contentType)) {
            return false;
        }
        return DEFAULT_DOWNLOAD_CONTENT_TYPE.stream().anyMatch(it -> StringUtils.equalsIgnoreCase(it, contentType));
    }

    private static String getRemortIP(HttpServletRequest request) {
        String ip = getXForwardedFor(request);
        return ip.indexOf(",") > 0 ? ip.split(",")[0].trim() : ip;
    }

    private static String getXForwardedFor(HttpServletRequest request) {
        String ip = request.getHeader("x-original-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Forwarded-For");
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("X-Forward-For");
                if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                    return request.getRemoteAddr();
                }
            }
        }
        return ip;
    }


    private boolean matchExclude(final String uri) {
        if (CollectionUtils.isEmpty(excludeUris)) {
            return false;
        }
        for (final String excludeUri : excludeUris) {
            if (URI_PATH_MATCHER.match(excludeUri, uri)) {
                return true;
            }
        }
        return false;
    }
}

三、测试

1、写一个有返回值得方法  2、写一个没有返回值,即void类型,但是有response输出

response.getWriter().write(); 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/*@Controller
@ResponseBody*/
@RestController //上面两个注释的注解等于此注解
@RequestMapping("/test")
public class HelloController {

    private static final Logger logger =  LoggerFactory.getLogger(HelloController.class);

    @GetMapping("/hello")
    public String sayHello(@RequestParam String testParam){
        return "hello world";
    }

    /**
     * 模拟捕获返回类型为void的,response.getWriter().write();中的参数(储藏)
     * @param testParam
     * @param response
     */
    @GetMapping("/sayVoid")
    public void sayVoid(@RequestParam String testParam, HttpServletResponse response){
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json;charset=utf-8");
        try{
            response.getWriter().write(testParam);
        }catch (IOException e){
            logger.error("HelloController.sayVoid error", e);
        }
    }
}

分别调用

1、有返回值类型

查看控制台输出

 2、无返回类型void

 查看控制台输出

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AOP(面向切面编程)是一种编程范式,可以在不修改源代码的情况下修改、添加或删除功能。在实际应用,我们可以使用AOP来拦截Controller下的所有类和方法。 要实现这个目标,我们可以使用Spring框架AOP模块。首先,我们需要在项目的配置文件配置AOP的相关内容。在Spring框架,我们可以使用XML配置或注解方式来实现。 假设我们使用XML配置方式,在配置文件,我们需要定义一个切面(Aspect)来拦截Controller下的所有类和方法。切面定义了要在哪些连接点(Joinpoint)上执行哪些切面逻辑(Advice)。在本例,我们将使用@Before通知类型的切面逻辑,该通知会在目标方法调用前执行。 接下来,我们需要定义一个切点(Pointcut),用于指定在哪些Joinpoint上执行指定的Advice。在这个例子,我们可以使用execution表达式来指定具体的包路径和方法名,以满足拦截Controller下的所有类和方法。 最后,我们需要将切面和切点配置在AOP配置文件,并启用AOP。这样,当程序执行到Controller下的方法时,AOP将会拦截并执行相应的逻辑。 总的来说,通过使用Spring框架AOP模块,我们可以在不修改源代码的情况下拦截Controller下的所有类和方法。在配置文件定义切面和切点,然后将其配置在AOP,就可以实现这个功能。这种方式可以方便地对Controller层进行统一的处理和控制,提高代码的可维护性和灵活性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值