常用参数相关注解

使用Spring最常用的AOP实现就是注解,对于Http的参数可以在注解的帮助下非常容易的取得和使用。

@RequestBody

@RequestBody注解用来获取POST等方式提交的body内容,并自动赋值到对应的bean中。如调用POST /user来提交新增用户的服务:

@RestController
@RequestMapping("/user")
public class UserController {

    ...
    
    @PostMapping("")
    public AddUserRsp addUser(@RequestBody AddUserReq req) {
        ...
        System.out.prinlnt(req.getName());   // 打印传入user的name
    }
}

@PathVariable

@PathVariable负责处理路径参数,自动获取并转换为定义的类型。如调用PUT /user/:id来更新用户信息的服务:

@RestController
@RequestMapping("/user")
public class UserController {

    ...
    
    @PutMapping("/:id")
    public AddUserRsp updateUser(@RequestBody UpdateUserReq req, @PathVariable Integer id) {
        ...
        System.out.prinlnt(req.getName());   // 打印传入user的name
        System.out.prinlnt(id);   // 打印路径参数id
    }
}

@RequestParam

@RequestParam负责处理GET请求的查询参数,所有定义在请求URL上的参数都可以被解析并使用。如调用GET /user?page=1&size=10&name=abc来获取对应用户列表的服务:

@RestController
@RequestMapping("/user")
public class UserController {

    ...
    
    @GetMapping("")
    public FindUserListRsp findUserList(@RequestParam Integer page, @RequestParam Integer size, @RequestParam String name) {
        ...
        System.out.prinlnt(name); // 打印name
        System.out.prinlnt(page);   // 打印page
    }
}

RequestParam的不便之处

上面的例子有一个很明显的不足。

  1. 如果业务持续增加,查询参数从page、size和name变为了十几个各类参数,那么这个方法的参数定义将变得不忍直视。
  2. 如果多个方法都有共同的查询参数,那么这些参数定义就属于重复定义,将来的重构也是灾难性的。

如果有一种方式,直接将URL中的查询参数直接转化为方法的入参bean,那么就可以和POST方式同样方便的去管理请求参数了。简而言之,我们需要这样的代码:

@RestController
@RequestMapping("/user")
public class UserController {

    ...
    
    @PutMapping("/")
    public FindUserListRsp findUserList(FindUserListReq req) {
        ...
        System.out.prinlnt(req.getName());   // 打印传入user的name
    }
}

使用@ModelAttribute

解决办法很简单,使用@ModelAttribute注解即可。

```java
@RestController
@RequestMapping("/user")
public class UserController {

    ...
    
    @PutMapping("/")
    public FindUserListRsp findUserList(@ModelAttribute FindUserListReq req) {
        ...
        System.out.prinlnt(req.getName());   // 打印传入user的name
    }
}

学习实现一个类ModelAttribute的注解

如果要对所有的GET请求,都可以使用这样简单的方式来接收参数,那实现方式就非AOP不可了。

整个处理过程可以分以下几个步骤:

  1. Controller调用侧,使用注解方式声明要传入的bean类型
  2. 声明注解类
  3. 在AOP实现类中,获取GET请求参数并注入到bean的实例中

以下,把示例代码挂上来。

1.声明注解类

package com.lynx.common.web.aop;

import java.lang.annotation.*;

/**
 * GET方式参数封装到指定Bean中
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GetParams {
    Class value();
}

2.实现AOP

package com.lynx.common.web.aop;

import org.apache.commons.beanutils.BeanUtils;
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.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
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.HashMap;
import java.util.Iterator;
import java.util.Map;


@Aspect
@Component
public class GetParamAOP {

    @Pointcut("@annotation(com.lynx.common.web.aop.GetParams)")
    public void handleParamAop() {
    }


    @Around("handleParamAop()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {

        // 取得注解传入的bean
        MethodSignature sign =  (MethodSignature) pjp.getSignature();
        Method method = sign.getMethod();
        GetParams annotation = method.getAnnotation(GetParams.class);
        Class beanClass = annotation.value();

        // 取得Controller方法的入参列表,找到对应的参数
        Object[] args = pjp.getArgs();
        int beanArgIndex = -1;
        for (int i = 0; i < args.length; i++) {
            if (args[i].getClass().equals(beanClass)) {
                beanArgIndex = i; break;
            }
        }

        // 取得Request请求的Map
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        Map<String, Object> paramsMap = getParameterMap(sra.getRequest());

        // 将GET请求Map赋值到Bean中
        Object beanArg = args[beanArgIndex];
        BeanUtils.populate(beanArg, paramsMap);
        args[beanArgIndex] = beanArg;

        // 将bean传递给方法
        return pjp.proceed(args);
    }


    private Map<String, Object> getParameterMap(HttpServletRequest request) {

        Map<String, String[]> properties = request.getParameterMap();//把请求参数封装到Map<String, String[]>中
        Map<String, Object> returnMap = new HashMap<String, Object>();
        Iterator<Map.Entry<String, String[]>> iter = properties.entrySet().iterator();
        String name = "";
        String value = "";
        while (iter.hasNext()) {
            Map.Entry<String, String[]> entry = iter.next();
            name = entry.getKey();
            Object valueObj = entry.getValue();
            if (null == valueObj) {
                value = "";
            } else if (valueObj instanceof String[]) {
                String[] values = (String[]) valueObj;
                for (int i = 0; i < values.length; i++) { //用于请求参数中有多个相同名称
                    value = values[i] + ",";
                }
                value = value.substring(0, value.length() - 1);
            } else {
                value = valueObj.toString();//用于请求参数中请求参数名唯一
            }
            returnMap.put(name, value);
        }

        return returnMap;
    }
}

3.使用注解并获取GET请求bean

@RestController
@RequestMapping("/user")
public class UserController {

    ...
    
    @PutMapping("/")
    @GetParams(FindUserListReq.class)
    public FindUserListRsp findUserList(FindUserListReq req) {
        ...
        System.out.prinlnt(req.getName());   // 打印传入user的name
    }
}

改善

以上注解类的Target值为@Target(ElementType.METHOD),表明这是针对method进行的AOP处理,如果要达到@RequestBody这样针对参数的处理,需要进一步改善。