Springboot Aop--对请求方法返回参数进行拦截

Springboot Aop–对请求方法返回参数进行拦截

我们常知spring一共有两大核心技术:Aop, IOC。
Aop面向切面,IOC控制反转,在平常开发项目我们也经常用的到

最近楼主在开发一个日志模块的时候需要拿到每个方法的请求返回结果
使用spring boot项目对请求方法的结果进行拦截。在每个方法执行完之后使用Aop的面向切面进行拦截,获取返回结果。

功能描述
@Before在每个方法执行之前可以对入参的参数进行修改或者将参数进行替换。
@AfterReturning在方法执行完毕后也可以对参数进行拦截并进行修改。
@Around环绕通知,环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,

我写了一个例子使用postman亲测有效:
先准备一个实体类,也是需要返回的实体内容,代码如下:

@Data
public class User {

    private String name;

    private int age;

    public User(){}

    public User(String name, int age){
        this.name = name;
        this.age = age;
    }
}

在写一个入口controller,给个提示表示进入了该方法,代码如下:

@Slf4j
@RestController
public class HelloController implements HandlerInterceptor {

    @RequestMapping("/hello")
    public User Helloshow(){
        User user = new User("小龙",23);
        log.info("先进方法");
        return user;
    }
}

注入Aop的依赖,代码如下:

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

接着定义一个Aspect,并指定一个切入点,配置需要拦截的方法
我这里对整个controller进行拦截

@Slf4j
@Aspect
@Configuration
public class ServiceAspect {

    private final String ExpGetResultDataPonit = "execution(* com.haylion.demo.controller..*.*(..))";

    /**
     * 后置返回通知
     * 这里需要注意的是:
     * 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
     * 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
     * returning 限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
     */
    @AfterReturning(value = ExpGetResultDataPonit, returning = "keys")
    public void doAfterReturningAdvice1(JoinPoint joinPoint, Object keys) {
        log.info("进入方法获取返回结果为:" + keys);
        if (keys instanceof User) {
            User resultVO = (User) keys;
            resultVO.setName("小龙人");
        }
        log.info("可在方法内修改返回结果:"+ keys);
    }
 }

然后执行方法就可以看到下面的结果了,在请求完方法后可以获取到返回的值也可以对返回值进行修改,修改之后在进行返回:
返回结果

如果想在方法执行之前拦截方法可以加入如下代码:

//执行方法前的拦截方法
    @Before(ExpGetResultDataPonit)
    public void doBeforeMethod(JoinPoint joinPoint) {
        log.info("我是前置通知,我将要执行一个方法了");
        //获取目标方法的参数信息
        Object[] obj = joinPoint.getArgs();
        for (Object argItem : obj) {
           log.info("参数修改前:" + argItem);
            if (argItem instanceof User) {
                User user = (User) argItem;
                user.setName("xiaolong英文版");
            }
            log.info("参数修改后:" + argItem);
        }
    }

在修改一下controller入口进行参数传入,代码如下:

@PostMapping("/hello")
    public User Helloshow(@RequestBody User user){
//        User user = new User("小龙",23);
        log.info("先进方法");
        return user;
    }

我这里是使用postman进行请求的,编写简单的参数进行传值:
在这里插入图片描述

看效果,:
在这里插入图片描述

还有一个环绕方法(推荐使用),是可以结合前置和后置在一起的方法,代码如下:

    /**
     * 环绕通知:
     * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,
     * 执行完毕是否需要替换返回值。
     * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
     */
    @Around(ExpGetResultDataPonit)
    public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
        System.out.println("环绕通知的目标方法名:" + proceedingJoinPoint.getSignature().getName());
        processInputArg(proceedingJoinPoint.getArgs());
        try {//obj之前可以写目标方法执行前的逻辑
            Object obj = proceedingJoinPoint.proceed();//调用执行目标方法
            processOutPutObj(obj);
            return obj;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null;
    }

    /**
     * 处理返回对象
     */
    private void processOutPutObj(Object obj) {
        log.info("原OBJ为:" + obj.toString());
        if (obj instanceof User) {
            User resultVO = (User) obj;
            resultVO.setName("小龙人");
            log.info("修改后:"+ resultVO);
        }
    }

    /**
     * 处理输入参数
     *
     * @param args 入参列表
     */
    private void processInputArg(Object[] args) {
        for (Object arg : args) {
            System.out.println("ARG原来为:" + arg);
            if (arg instanceof User) {
                User paramVO = (User) arg;
                paramVO.setName("xiaolong英文版");
            }
        }
    }

最后执行结果,和前面两个方法结合起来的效果是一样的:
在这里插入图片描述

切面完整代码:

@Slf4j
@Aspect
@Configuration
public class ServiceAspect {

    private final String ExpGetResultDataPonit = "execution(* com.haylion.demo.controller..*.*(..))";

    //定义切入点,拦截servie包其子包下的所有类的所有方法
    //@Pointcut("execution(* execution(* com.haylion.demo.controller..*.*(..))")
    //拦截指定的方法,这里指只拦截 Helloshow() 方法
    //@Pointcut("execution(* execution(* com.haylion.demo.controller.Helloshow..*.*(..))")


    //执行方法前的拦截方法
//    @Before(ExpGetResultDataPonit)
//    public void doBeforeMethod(JoinPoint joinPoint) {
//        log.info("我是前置通知,我将要执行一个方法了");
//        //获取目标方法的参数信息
//        Object[] obj = joinPoint.getArgs();
//        for (Object argItem : obj) {
//           log.info("参数修改前:" + argItem);
//            if (argItem instanceof User) {
//                User user = (User) argItem;
//                user.setName("xiaolong英文版");
//            }
//            log.info("参数修改后:" + argItem);
//        }
//    }

    /**
     * 后置返回通知
     * 这里需要注意的是:
     * 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
     * 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
     * returning 限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
     */
//    @AfterReturning(value = ExpGetResultDataPonit, returning = "keys")
//    public void doAfterReturningAdvice1(JoinPoint joinPoint, Object keys) {
//        log.info("进入方法获取返回结果为:" + keys);
//        if (keys instanceof User) {
//            User resultVO = (User) keys;
//            resultVO.setName("小龙人");
//        }
//        log.info("可在方法内修改返回结果:"+ keys);
//    }


    /**
     * 后置最终通知(目标方法只要执行完了就会执行后置通知方法)
     */
//    @After("excuteService()")
//    public void doAfterAdvice(JoinPoint joinPoint) {
//        System.out.println("后置通知执行了!!!!");
//    }




    /**
     * 环绕通知:
     * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,
     * 执行完毕是否需要替换返回值。
     * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
     */
    @Around(ExpGetResultDataPonit)
    public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
        log.info("环绕通知的目标方法名:" + proceedingJoinPoint.getSignature().getName());
        processInputArg(proceedingJoinPoint.getArgs());
        try {//obj之前可以写目标方法执行前的逻辑
            Object obj = proceedingJoinPoint.proceed();//调用执行目标方法
            processOutPutObj(obj);
            return obj;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null;
    }

    /**
     * 处理返回对象
     */
    private void processOutPutObj(Object obj) {
        log.info("原OBJ为:" + obj.toString());
        if (obj instanceof User) {
            User resultVO = (User) obj;
            resultVO.setName("小龙人");
            log.info("修改后:"+ resultVO);
        }
    }

    /**
     * 处理输入参数
     *
     * @param args 入参列表
     */
    private void processInputArg(Object[] args) {
        for (Object arg : args) {
            log.info("ARG原来为:" + arg);
            if (arg instanceof User) {
                User paramVO = (User) arg;
                paramVO.setName("xiaolong英文版");
            }
        }
    }
}

主要参考:https://blog.csdn.net/puhaiyang/article/details/78146620

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答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会改变事务管理的行为。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值