SpringBoot -- 使用AOP拦截Controller,输出入参和响应

  最近闲来无事,想着给项目加点什么东西能让系统使用和问题解决更方便,感觉拦截controller和service,输出入参并统计下该controller的响应时间挺有意思的,也能更好的发现问题解决问题。下面就上代码吧。
  做java的肯定都知道aop,那就不怎么介绍它了,直接上步骤吧。

  1. 引入aop依赖
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
 </dependency>
  1. 然后定义一个切面,并指定一个切入点,配置要进行哪些类或方法的拦截,这里我们对所有的controller进行拦截。
@Component
@Aspect
public class ParamOutAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(ParamOutAspect.class);
    //对包下所有的controller结尾的类的所有方法增强
    private final String executeExpr = "execution(* com.caxs.app..*Controller.*(..))";
}
  1. 使用环绕通知,对controller之前和之后增强,输出入参和响应参数,下面是所有代码。
/**
 * @Author: TheBigBlue
 * @Description: 拦截controller,输出入参、响应内容和响应时间
 * @Date: 2019/6/17
 */
@Component
@Aspect
public class ParamOutAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(ParamOutAspect.class);
    //对包下所有的controller结尾的类的所有方法增强
    private final String executeExpr = "execution(* com.caxs.app..*Controller.*(..))";

    @Value("${spring.aop.maxReduceTime}")
    private long maxReduceTime;

    /**
     * @param joinPoint:
     * @Author: TheBigBlue
     * @Description: 环绕通知,拦截controller,输出请求参数、响应内容和响应时间
     * @Date: 2019/6/17
     * @Return:
     **/
    @Around(executeExpr)
    public Object processLog(ProceedingJoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //获取方法名称
        String methodName = method.getName();
        //获取参数名称
        LocalVariableTableParameterNameDiscoverer paramNames = new LocalVariableTableParameterNameDiscoverer();
        String[] params = paramNames.getParameterNames(method);
        //获取参数
        Object[] args = joinPoint.getArgs();
        //过滤掉request和response,不能序列化
        List<Object> filteredArgs = Arrays.stream(args)
                .filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)))
                .collect(Collectors.toList());
        JSONObject rqsJson = new JSONObject();
        rqsJson.put("rqsMethod", methodName);
        rqsJson.put("rqsTime", DateUtil.getCurrentFormatDateLong19());
        if (ObjectIsNullUtil.isNullOrEmpty(filteredArgs)) {
            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", rqsParams);
        }
        LOGGER.info(methodName + "请求信息为:" + rqsJson.toJSONString());
        Object resObj = null;
        long startTime = System.currentTimeMillis();
        try {
            //执行原方法
            resObj = joinPoint.proceed(args);
        } catch (Throwable e) {
            LOGGER.error(methodName + "方法执行异常!", e);
            throw new BusinessException(methodName + "方法执行异常!");
        }
        long endTime = System.currentTimeMillis();
        // 打印耗时的信息
        this.printExecTime(methodName, startTime, endTime);
        if (resObj != null) {
            if (resObj instanceof JsonResponse) {
                //输出响应信息
                JsonResponse resJson = (JsonResponse) resObj;
                LOGGER.info(methodName + "响应信息为:" + resJson.toString());
                return resJson;
            } else {
                return resObj;
            }
        } else {
            return JsonResponse.success();
        }
    }

    /**
     * @param methodName:
     * @param startTime:
     * @param endTime:
     * @Author: TheBigBlue
     * @Description: 打印方法执行耗时的信息,如果超过了一定的时间,才打印
     * @Date: 2019/6/17
     * @Return:
     **/
    private void printExecTime(String methodName, long startTime, long endTime) {
        long diffTime = endTime - startTime;
        if (diffTime > maxReduceTime) {
            LOGGER.info(methodName + " 方法执行耗时:" + diffTime + " ms");
        }
        //TODO 可以集成redis,将每个controller的执行时间追加到redis中,再用定时每周一次同步到库中
    }
}

  1. 环绕通知还可以统计下该controller处理执行了多长时间,可以将每个controller响应时间写入redis,再用定时每周或者多久同步到数据库一次,这样就可以统计耗时较大的逻辑并可以进行相应的处理。这里因为时间原因没有集成redis,以后有时间补上。
  2. 上面有一条过滤掉request和response的操作,是因为我需要对入参输出,但是request,response不能序列化,但是有些入参是需要这些参数的,所以过滤掉,防止报错。具体的错误如下。
It is illegal to call this method if the current request is not in asynchronous mode
nested exception is java.lang.IllegalStateException: It is illegal to call this method 
if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)] with root cause
  • 1
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spring Boot AOP可以用来拦截Controller中的方法,实现对请求的统一处理。通过定义切面和切点,可以在请求前、后、异常等不同的阶段进行处理,比如记录日志、权限校验、参数校验等。在Spring Boot中,可以使用@Aspect注解定义切面,使用@Pointcut注解定义切点,使用@Before、@After、@Around等注解定义不同类型的通知。同时,还可以使用@Order注解指定切面的执行顺序。 ### 回答2: 在Web应用中,Controller是连接前端和后端的重要部分,用于处理请求和响应返回数据。在某些情况下,我们需要对Controller进一步处理,例如记录请求日志、验证权限、异常处理等。这就需要用到SpringBoot AOP进行拦截SpringBoot AOP是一种面向切面的编程方式,通过拦截目标方法,插入特定的处理逻辑来增强系统功能。在Controller使用AOP进行拦截可以帮助我们方便的实现业务逻辑,避免重复代码。 实现步骤如下: 1. 创建一个自定义注解,用于标记要拦截Controller方法。 ``` @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface LogRecord { String value() default ""; } ``` 2. 在拦截器中定义具体的切面逻辑,例如记录请求日志、验证权限等。 ``` @Component @Aspect public class ControllerInterceptor { @Autowired private HttpServletRequest request; private static final Logger logger = LoggerFactory.getLogger(ControllerInterceptor.class); @Around("@annotation(com.example.demo.annotations.LogRecord)") public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); String methodName = joinPoint.getSignature().getName(); String className = joinPoint.getTarget().getClass().getSimpleName(); logger.info("请求路径:{},请求参数:{},请求方法:{}.{}", request.getRequestURI(), JsonUtil.toJSONString(request.getParameterMap()), className, methodName); Object result = joinPoint.proceed(); return result; } } ``` 3. 在Controller方法上标注自定义注解,使其成为切面的切点。 ``` @RestController @RequestMapping("/demo") public class DemoController { @Autowired private UserService userService; @RequestMapping(value = "/getUserById", method = RequestMethod.GET) @LogRecord public ResponseEntity<?> getUserById(@RequestParam("id") String id) { User user = userService.getUserById(id); return ResponseEntity.ok(user); } } ``` 4. 在SpringBoot主类上使用@EnableAspectJAutoProxy注解开启代理自动配置。 ``` @SpringBootApplication @EnableAspectJAutoProxy public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } } ``` 以上就是使用SpringBoot AOP进行Controller拦截的步骤,通过自定义注解和拦截器的方式,我们可以方便的实现各种Controller拦截逻辑,提升Web应用的可维护性和可扩展性。 ### 回答3: Spring Boot AOP(面向切面编程)是Spring Framework的一个重要部分,它允许我们在程序中实现横切关注点的模块化。横切关注点是一个横跨不同模块和层的功能,如日志、安全性、事务管理等,这些功能在整个程序中多次出现,从而导致代码不需要重复编写。在Spring Boot中,我们可以使用AOP来实现一些特定的横切关注点,例如:拦截Controller请求。 在Spring Boot中,拦截Controller请求是很常见的需求,这是因为我们需要对请求进行验证、权限控制和日志记录等。使用AOP拦截Controller请求的过程中,我们需要定义一个Aspect切面,切入到Controller的请求中执行某些操作。 下面是实现在Spring Boot中使用AOP拦截Controller请求的步骤: 1. 定义一个Aspect切面: @Component @Aspect public class MyAspect { @Pointcut("execution(public * com.example.controller.*.*(..))") public void controllerPointcut() {} @Before("controllerPointcut()") public void beforeControllerMethod(JoinPoint jp) { //执行请求前的逻辑 } @AfterReturning(pointcut="controllerPointcut()", returning="result") public void afterControllerMethod(JoinPoint jp, Object result) { //执行请求后的逻辑 } } 2. 在切面中定义一个Pointcut,用来指定需要拦截的请求。上面的例子中,我们使用execution表达式指定了com.example.controller包下所有public方法为切入点。 3. 在切面中定义拦截Controller请求时需要执行的操作。我们可以在@Before注解中定义请求进入前的逻辑,在@AfterReturning注解中定义请求结束后的逻辑。 4. 在Controller类中注入我们定义的切面,即可使用AOP拦截Controller请求。 @Controller public class MyController { @Autowired private MyAspect myAspect; @GetMapping("/test") public String test() { //请求逻辑 return "test"; } } 通过以上步骤,我们就可以在Spring Boot中使用AOP拦截Controller请求了。需要注意的是,在AOP中定义的逻辑需要足够简洁,并且避免阻塞请求。此外,对于业务逻辑中存在事务的Controller方法不建议使用AOP拦截,因为AOP会改变事务管理的行为。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值