为啥前端给到的参数又不对,该如何传递呢?

开发 前端
最常见的一种传输参数的方式,URL 查询参数是指附加在 URL 后面的以键值对形式传递的参数,通常用于 GET 请求中向服务器传递简单的数据。  

在Spring Boot开发过程中,前端与后端之间的传参是一个核心且常见的问题。本文将详细探讨前端如何向后端传递参数、后端如何接收参数、接收参数的原理,以及在实际开发中如何进行合理的配置与设置,确保参数能够正确、安全地传输和处理。

六种常见的方式

URL 查询参数

最常见的一种传输参数的方式,URL 查询参数是指附加在 URL 后面的以键值对形式传递的参数,通常用于 GET 请求中向服务器传递简单的数据。  

// 前端
axios.get('/api/users', {
  params: {
    name: 'John',
    age: 30
  }
});
// 后端
@GetMapping("/users")
public ResponseEntity<?> getUser(@RequestParam("name") String name,
                                 @RequestParam("age") Integer age) {
    // 处理业务逻辑
    return ResponseEntity.ok("User: " + name + ", age: " + age);
}

注意:我们可以去省略这个@RequestParam 注解,使用这个注解的好处就是可以设置参数的默认值defaultValue。

路径参数

直接将参数嵌入到 URL 路径当中的一种传递方式,对于后端而言需要指定特殊的标识符和注解才能使用。

// 前端
axios.get('/api/users/123');
// 后端
@GetMapping("/users/{id}")
public ResponseEntity<?> getUserById(@PathVariable("id") Long id) {
    // 根据 id 查询用户
    return ResponseEntity.ok("User ID: " + id);
}

请求体参数

主要用于 POST、PUT 等请求,常传递 JSON 数据或表单数据。  

// 前端
axios.post('/api/users', {
  name: 'John',
  age: 30
});
// 后端
@PostMapping("/users")
public ResponseEntity<?> createUser(@RequestBody User user) {
    // user 对象由 JSON 数据自动反序列化而来
    return ResponseEntity.ok("User created: " + user.getName());
}

注意:请求头需要设置  Content-Type: application/json,主要用于解析 JSON 数据 。

表单数据参数

 表单数据指的是通过 HTML 表单或 application/x-www-form-urlencoded 方式提交的参数,主要用于 POST、PUT 请求。后端通常使用 @RequestParam 或 @ModelAttribute 来解析。

// 前端
let formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('username', 'John');


axios.post('/api/upload', formData, {
  headers: {
    'Content-Type': 'multipart/form-data'
  }
});
// 后端
// 普通表单接收方式
@PostMapping("/users/form")
public ResponseEntity<?> createUserForm(@ModelAttribute User user) {
    return ResponseEntity.ok("User created via form: " + user.getName());
}
// 涉及文件上传表单接收方式
@PostMapping("/upload")
public ResponseEntity<?> handleFileUpload(@RequestPart("file") MultipartFile file,
                                          @RequestParam("username") String username) {
    // 处理文件上传和其他参数
    return ResponseEntity.ok("File uploaded by: " + username);
}

post 方式只有一个属性

@PostMapping("/string")
public ResponseEntity<?> receiveString(@RequestParam("text") String text) {
    return ResponseEntity.ok("Received: " + text);
}


axios.post('/string', 'text=Hello%20World', {
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
})


@PostMapping("/string")
public ResponseEntity<?> receiveString(@RequestBody String text) {
    return ResponseEntity.ok("Received: " + text);
}


axios.post('/string', 'Hello World', {
  headers: {
    'Content-Type': 'text/plain'
  }
})

注意:@RequestParam 方式(不可也可以,但是加上更清晰可读)下可直接放在 Url 后面,@RequestBody 方式下使用 json 格式传递 。

图片图片

post 请求传递数组

@PostMapping("/list")
public ResponseEntity<?> receiveList(@RequestBody List<String> list) {
    return ResponseEntity.ok("Received: " + list);
}


axios.post('/list', ["apple", "banana", "cherry"], {
    headers: { 'Content-Type': 'application/json' }
})


@PostMapping("/list")
public ResponseEntity<?> receiveList(@RequestParam List<String> list) {
    return ResponseEntity.ok("Received: " + list);
}
axios.post('/list', null, { params: { list: ["apple", "banana", "cherry"] } })
.then(response => console.log(response.data))
.catch(error => console.error(error));
http://localhost:8080/list?list=apple&list=banana&list=cherry

图片图片

图片图片

原理分析

图片图片

RequestParamMethodArgumentResolver 类

在 Spring MVC 中,@RequestParam 参数的解析由 RequestParamMethodArgumentResolver 负责。它是 HandlerMethodArgumentResolver 的实现类之一,专门用于解析 @RequestParam 注解的参数。  

// 判断是否由该类进行解析
public boolean supportsParameter(MethodParameter parameter) {
    if (parameter.hasParameterAnnotation(RequestParam.class)) {
        if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
            RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
            return (requestParam != null && StringUtils.hasText(requestParam.name()));
        }
        else {
            return true;
        }
    }
    else {
        if (parameter.hasParameterAnnotation(RequestPart.class)) {
            return false;
        }
        parameter = parameter.nestedIfOptional();
        if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
            return true;
        }
        else if (this.useDefaultResolution) {
            return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
        }
        else {
            return false;
        }
    }
}


// 具体解析逻辑
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {


    NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
    MethodParameter nestedParameter = parameter.nestedIfOptional();


    Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
    if (resolvedName == null) {
        throw new IllegalArgumentException(
                "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
    }


    Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
    if (arg == null) {
        if (namedValueInfo.defaultValue != null) {
            arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
        }
        else if (namedValueInfo.required && !nestedParameter.isOptional()) {
            handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
        }
        arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
    }
    else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
        arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
    }


    if (binderFactory != null) {
        WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
        try {
            arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
        }
        catch (ConversionNotSupportedException ex) {
            throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
                    namedValueInfo.name, parameter, ex.getCause());
        }
        catch (TypeMismatchException ex) {
            throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
                    namedValueInfo.name, parameter, ex.getCause());
        }
        // Check for null value after conversion of incoming argument value
        if (arg == null && namedValueInfo.defaultValue == null &&
                namedValueInfo.required && !nestedParameter.isOptional()) {
            handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
        }
    }


    handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);


    return arg;
}


protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);


    if (servletRequest != null) {
        Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
        if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
            return mpArg;
        }
    }


    Object arg = null;
    MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
    if (multipartRequest != null) {
        List<MultipartFile> files = multipartRequest.getFiles(name);
        if (!files.isEmpty()) {
            arg = (files.size() == 1 ? files.get(0) : files);
        }
    }
    if (arg == null) {
        String[] paramValues = request.getParameterValues(name);
        if (paramValues != null) {
            arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
        }
    }
    return arg;
}

解析条件:首先调用supportsParameter 方法判断是否需要该类进行解析,判断逻辑参数上存在@RequestParam 注解或者未标@RequestParam注解,但是 useDefaultResolutinotallow=true 也会尝试解析。

解析逻辑:调用该类父类AbstractNamedValueMethodArgumentResolver 的resolveArgument 方法解析参数。

关键点:该类的resolveName 方法是从request.getParameter()获取参数值。

PathVariableMethodArgumentResolver 类

@PathVariable 注解的解析由 PathVariableMethodArgumentResolver 负责,它的解析逻辑与 @RequestParam 类似,但它解析的是 路径参数,而非查询参数。

public boolean supportsParameter(MethodParameter parameter) {
    if (!parameter.hasParameterAnnotation(PathVariable.class)) {
        return false;
    }
    if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
        PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
        return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
    }
    return true;
}

解析条件:首先调用supportsParameter 方法判断是否需要该类进行解析,判断逻辑参数上存在@PathVariable 注解。

解析逻辑:与前面@RequestParam的一致。

关键点:该类的resolveName 方法是从 HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE  获取参数值。

RequestResponseBodyMethodProcessor 类

该类是用来 处理 @RequestBody 和 @ResponseBody注解的,它主要用于解析请求体(@RequestBody)和返回值(@ResponseBody),并完成 JSON/XML 的序列化和反序列化。    

public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(RequestBody.class);
}


@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {


    parameter = parameter.nestedIfOptional();
    Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
    String name = Conventions.getVariableNameForParameter(parameter);


    if (binderFactory != null) {
        WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
        if (arg != null) {
            validateIfApplicable(binder, parameter);
            if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
            }
        }
        if (mavContainer != null) {
            mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
        }
    }


    return adaptArgumentIfNecessary(arg, parameter);
}

解析条件:首先调用supportsParameter 方法判断是否需要该类进行解析,判断逻辑是存在@RequestBody 注解。

解析逻辑:直接自身的resolveArgument 方法,通过readWithMessageConverters 方法进行请求体转换,获取参数名称,进行数据绑定和校验,适配参数。

拓展分析

如果内置的参数绑定方式无法满足特定的要求,我们可以通过自定义 HandlerMethodArgumentResolver  来实现独特的参数方式,这样可以做到更加的安全可靠,需要将自定义解析器加入链条当中。

public class CustomArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // 根据条件判断是否支持该参数解析
        return parameter.hasParameterAnnotation(MyCustomAnnotation.class);
    }


    @Override
    public Object resolveArgument(MethodParameter parameter,
                                  ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory) throws Exception {
        // 自定义解析逻辑
        return ...;
    }
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new CustomArgumentResolver());
    }
}

具体的实现逻辑可以根据需求去添加,该方式适合于复杂的数据转换和自定义注解绑定逻辑,相对于统一的传参方式更加隐秘安全。

责任编辑:武晓燕 来源: java从零到壹
相关推荐

2017-12-05 08:53:20

Golang参数传递

2024-02-26 08:52:20

Python传递函数参数参数传递类型

2025-02-12 10:51:51

2010-06-04 10:46:18

同事上司

2023-01-05 08:12:11

分层应用代码

2023-04-10 15:14:03

2015-11-09 10:29:05

设计师前端

2020-05-22 18:16:57

数据泄露网络安全互联网

2023-07-29 22:11:58

Spring容器Component

2009-06-09 21:54:26

传递参数JavaScript

2020-12-23 07:56:40

前端UICSS

2021-02-24 14:01:13

微服务开发框架

2018-07-27 17:28:37

人工智能机器学习AI

2021-05-20 10:11:51

固态硬盘碎片系统

2015-09-08 10:16:41

Java参数按值传递

2012-02-21 14:04:15

Java

2009-07-30 13:07:49

ASP.NET中的三层

2024-09-19 20:59:49

2012-06-19 14:35:24

JSPJava乱码

2017-11-23 15:06:14

前端数据库开发
点赞
收藏

51CTO技术栈公众号