继续创造,加速生长!这是我参加「日新计划 10 月更文应战」的第1天,点击查看活动概况 常用的接纳参数注解

@RequestParam
@PathVariable
@RequestBody

先看个比如

@RestController
public class testController {
    @RequestMapping(value = "/demo1",method = RequestMethod.GET)
    public String uploadFolder(String username,String password) {
        System.out.println(username);
        System.out.println(password);
        return "ok";
    }
}

写了个很简略的比如传递两个参数一个是用户名一个是暗码、央求方法是get央求、那我们肯定是直接拜访一下这个127.0.0.1:8080/demo1?username=abcd&password=123央求就能接纳到前端输入的

换种方法看后端参数接纳、主张躺着看!!!

@RequestParam

可以看到这种简略简略的传参是很便利的、你也可以去运用@RequestParam()注解去去一个接纳参数的别名、如

@RequestMapping(value = "/demo1",method = RequestMethod.GET)
public String uploadFolder(@RequestParam("username1") String username,
                           @RequestParam("password1") String password) {
    System.out.println(username);
    System.out.println(password);
    return "ok";
}

则央求参数就是127.0.0.1:8080/demo1?username1=abcd&password1=123就是以这种方法传递到后端

在html中页面怎么提交?

<form action="/demo1" method="get">
    <input type="text" name="username">
    <input type="text" name="password">
    <button type="submit">提交</button>
</form>

换种方法看后端参数接纳、主张躺着看!!!

@PathVariable

再来说另一种简略的就是@PathVariable()注解运用、这种你就可以理解为它回去找url中的内容、而不是作为?或许&拼接传递过来的内容。

@RequestMapping(value = "/demo2/{id}",method = RequestMethod.GET)
public String demo2(@PathVariable(value = "id") String id,
                           @RequestParam("username") String username,
                           @RequestParam("password") String password) {
    System.out.println(id);
    System.out.println(username);
    System.out.println(password);
    return "ok";
}

可以看到他标记了id为url拼接的参数、http://127.0.0.1:8981/demo2/243843?username=abcd&password=123、假设央求是这个http://127.0.0.1:8981/demo2?username=abcd&password=123会怎么样?肯定是找不到对应央求……

换种方法看后端参数接纳、主张躺着看!!!

那上面这种怎么在html中运用?

<form action="/demo2/213124" method="get">
    <input type="text" name="username">
    <input type="text" name="password">
    <button type="submit">提交</button>
</form>

也就是在action填写参数的占位内容找到详细的Controller接口方法。

@RequestBody

还有一种接纳的方法@RequestBody这种是接纳央求体中的内容、下面这方法是将接纳一个json字符串的格局数据

@RequestMapping(value = "/demo3",method = RequestMethod.GET)
public String demo3(@RequestBody String body) {
    System.out.println(body);
    return "ok";
}

换种方法看后端参数接纳、主张躺着看!!!

Map接纳

或许换成Map做接纳的参数、就会将数据解析成键值对。

@RequestMapping(value = "/demo3",method = RequestMethod.GET)
public String demo3(@RequestBody Map<String,Object> body) {
    System.out.println(body);
    return "ok";
}

方针接纳

最最常见的就是发送json后端运用实体类来接纳、而且Header要是"Content-Type", "application/json"

@RequestMapping(value = "/demo4",method = RequestMethod.GET)
public String demo4(@RequestBody User user) {
    System.out.println(user);
    return "ok";
}

要注意的就是User方针要写get、set方法。

表单提交

然后这种在html提交需求怎么弄?

<form action="/demo4" method="get">
    <input type="text" name="id">
    <input type="text" name="name">
    <button type="submit">提交</button>
</form>
@RequestMapping(value = "/demo4")
public String demo4(User user) {
    System.out.println(user);
    return "ok";
}

@RequestBody去掉后在form表单提交时不管是post仍是get央求都能将数据映射成功。

由此可以理解为@RequestBody只接受央求体中的数据、而且会校验Content-Typeapplication/json的数据。

@RequestMapping(value = "/demo4")
public String demo4(@RequestBody User user) {
    System.out.println(user);
    return "ok";
}

假设加上@RequestBody后from表单运用get央求会出现

2022-10-11 10:13:05.902  WARN 11440 --- [nio-8981-exec-5] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public java.lang.String com.wei.controller.testController.demo4(com.wei.pojo.User)]

运用get央求的话参数是跟在url后的所以他会找不到央求体的内容。

假设加上@RequestBody后from表单运用post央求会出现

2022-10-11 10:10:30.073  WARN 12856 --- [nio-8981-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported]

而post央求参数是携带在央求体中的、可是它的Content-Typeapplication/x-www-form-urlencoded不是application/json所以会导致不支撑的类型。

组合方法

这三种方法也可以组合运用@PathVariable+@RequestParam@PathVariable+@RequestBody@PathVariable+@RequestParam+@RequestBody

玩法很多种、但别瞎玩、瞎玩就是糟蹋自己!!!

骚操作

通过form表单提交数据、映射数据到方针中、仍是上班后发现的能这样玩、可是我在b站看MVC的教程发现别人有教!淦、我之前看的MVC视频都没教我这样玩!!!

@RequestMapping(value = "/demo6")
public String demo6(ParameterDTO parameterDTO) {
    System.out.println(parameterDTO);
    return "ok";
}
@Data
public class ParameterDTO {
    private Long id;
    private List<Long> idS;
    private Map<String,String> map;
    private MultipartFile multipartFile;
    private List<MultipartFile> multipartFiles;
    private ExtraSingle extraSingle;
    private List<ExtraMore> extraMores;
    @Override
    public String toString() {
        return "ParameterDTO{" +
                "id=" + id +
                ", idS=" + idS +
                 ", map=" + map +
                ", multipartFile=" + multipartFile.getResource().getFilename() +
                ", multipartFiles=" + multipartFiles.stream().map((Function<MultipartFile, Object>) multipartFile -> multipartFile.getResource().getFilename()).collect(Collectors.toList()) +
                ", extraSingle=" + extraSingle +
                ", extraMores=" + extraMores +
                '}';
    }
}
@Data
class ExtraSingle {
    private String extraSingleName;
    private List<Long> extraSingleIdS;
    private MultipartFile extraSingleMultipartFile;
    private List<MultipartFile> extraSingleMultipartFiles;
    @Override
    public String toString() {
        return "ExtraSingle{" +
                "extraSingleName='" + extraSingleName +
                ", extraSingleIdS=" + extraSingleIdS +
                ", extraSingleMultipartFile=" + extraSingleMultipartFile.getResource().getFilename() +
                ", extraSingleMultipartFiles=" + extraSingleMultipartFiles.stream().map((Function<MultipartFile, Object>) multipartFile -> multipartFile.getResource().getFilename()).collect(Collectors.toList()) +
                '}';
    }
}
@Data
class ExtraMore {
    private String extraMoreName;
    private List<Long> extraMoreIdS;
    private MultipartFile extraMoreMultipartFile;
    private List<MultipartFile> extraMoreMultipartFiles;
    @Override
    public String toString() {
        return "ExtraMore{" +
                "extraMoreName='" + extraMoreName +
                ", extraMoreIdS=" + extraMoreIdS +
                ", extraMoreMultipartFile=" + extraMoreMultipartFile.getResource().getFilename() +
                ", extraMoreMultipartFiles=" + extraMoreMultipartFiles.stream().map((Function<MultipartFile, Object>) multipartFile -> multipartFile.getResource().getFilename()).collect(Collectors.toList()) +
                '}';
    }
}
<form action="/demo6" method="post" enctype="multipart/form-data">
    <input type="text" name="id" value="2022">
    <input type="text" name="idS" value="2020,2021,2022">
    <input type="text" name="map[abc]" value="giao">
    <input type="text" name="map[abcKey]" value="giaogiao">
    <input type="file" name="multipartFile">
    <input type="file" name="multipartFiles[0]">
    <input type="file" name="multipartFiles[1]">
    <br>
    <input type="text" name="extraSingle.extraSingleName" value="extraSingleName称谓">
    <input type="text" name="extraSingle.extraSingleIdS" value="123">
    <input type="file" name="extraSingle.extraSingleMultipartFile">
    <input type="file" name="extraSingle.extraSingleMultipartFiles[0]">
    <input type="file" name="extraSingle.extraSingleMultipartFiles[1]">
    <br>
    <input type="text" name="extraMores[0].extraMoreName" value="extraMoreName称谓1">
    <input type="text" name="extraMores[0].extraMoreIdS" value="1,2,3">
    <input type="file" name="extraMores[0].extraMoreMultipartFile">
    <input type="file" name="extraMores[0].extraMoreMultipartFiles[0]">
    <input type="file" name="extraMores[0].extraMoreMultipartFiles[1]">
    <br>
    <input type="text" name="extraMores[1].extraMoreName" value="extraMoreName称谓2">
    <input type="text" name="extraMores[1].extraMoreIdS" value="4,5,6">
    <input type="file" name="extraMores[1].extraMoreMultipartFile">
    <input type="file" name="extraMores[1].extraMoreMultipartFiles[0]">
    <input type="file" name="extraMores[1].extraMoreMultipartFiles[1]">
    <button type="submit">提交</button>
</form>

检验效果

ParameterDTO{id=2022, idS=[2020, 2021, 2022], map={abcKey=giaogiao, abc=giao},multipartFile=1_2020-08-21_946.png, multipartFiles=[1_2020-08-21_946.png, 1_2020-08-21_946.png], extraSingle=ExtraSingle{extraSingleName='extraSingleName称谓', extraSingleIdS=[123], extraSingleMultipartFile=1_2020-08-21_946.png, extraSingleMultipartFiles=[1_2020-08-21_946.png, 1_2020-08-21_946.png]}, extraMores=[ExtraMore{extraMoreName='extraMoreName称谓1', extraMoreIdS=[1, 2, 3], extraMoreMultipartFile=1_2020-08-21_946.png, extraMoreMultipartFiles=[1_2020-08-21_946.png, 1_2020-08-21_946.png]}, ExtraMore{extraMoreName='extraMoreName称谓2', extraMoreIdS=[4, 5, 6], extraMoreMultipartFile=1_2020-08-21_946.png, extraMoreMultipartFiles=[1_2020-08-21_946.png, 1_2020-08-21_946.png]}]}

要注意的是一些版别或许不支撑这样!要注意的是MultipartFile文件类型或许有些版别不会有主动设置值的操作可以换一种方法

Map<String, MultipartFile> fileMap = WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class).getFileMap();

拿到所有的文件方针、通过name拿你需求的文件方针就可以了。

解决疑问

参数值是这么注入数据?Controller中的方法值每一个参数都去找到一个方法参数解析器也就xxxMethodArgumentResolver的一个类、如常见的在方法中写model方针或许request方针时都是通过对应的参数解析器把参数解析后设置方针供我们运用。

public String index(Model model,HttpServletRequest request){
    ...
}

自己写一个参数解析器

/**
 *  自定义方法参数解析器
 * @author https://juejin.cn/user/844892408381735
 * @Date 2022/10/11 16:39
 */
public class CustomArgumentResolver implements HandlerMethodArgumentResolver {
    /**
     *  参数是否支撑
     * @param parameter 方法参数类型信息、如对应的参数前的注解信息
     * @return 回来true 这表明运用该解析器、后续着会调用 下方resolveArgument(...)方法
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return User.class.equals(parameter.getParameterType());
    }
    /**
     *  处理并生成参数方针回来
     * @param parameter  方法参数方针
     * @param mavContainer  可以理解为model方针
     * @param webRequest  可以理解为是 HttpServletRequest 央求方针
     * @param binderFactory 数据绑定工厂
     * @return 回来参数方针
     */
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        User user = new User();
        user.setName("主动注入....");
        return user;
    }
}

增加到参数解析器中、设置为最前面便利检验。

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
    @PostConstruct
    public void init() {
        //获取到自定义requestMappingHandlerAdapter的特点(只读)
        List<HandlerMethodArgumentResolver> resolvers = requestMappingHandlerAdapter.getArgumentResolvers();
        //从头创立集结方针
        List<HandlerMethodArgumentResolver> newResolvers =
                new ArrayList<>(resolvers.size() + 1);
        // 增加 自定义解析器 到集结首位
        newResolvers.add(new CustomArgumentResolver());
        // 增加 已注册的 Resolver 方针集结
        newResolvers.addAll(resolvers);
        // 从头设置 Resolver 方针集结
        requestMappingHandlerAdapter.setArgumentResolvers(newResolvers);
    }
}

检验

@RequestMapping(value = "/demo4")
public String demo4(@RequestBody User user) {
    System.out.println(user);
    return "ok";
}

检验效果

User(id=null, name=主动注入...., age=null, sex=null)

可以发现@RequestBody是不起作用的、直接是运用我们自定义的CustomArgumentResolver类中resolveArgument()方法回来的方针做为参数塞到Controller央求方法中的。

由此可以得出先找到的参数解析器会优先运用、为了检验自定义的参数解析器设置在集结的第一个方位、为啥会这样就得看源码咯。

主要的在这个类InvocableHandlerMethodgetMethodArgumentValues方法、这个方法是得到方法参数需求注入的值。

换种方法看后端参数接纳、主张躺着看!!!

这个当地会处理每一个参数的值。

换种方法看后端参数接纳、主张躺着看!!!
赤色框、框柱的是获取一个参数解析器、假设最前面的解析器resolver.supportsParameter(parameter)回来的是true的情况下就运用其时解析器做为参数解析运用、所以我们自定义的解析器放在第一个的方位上就会优先运用自定义的。

绿色框、框柱的是解析器的处理参数的方法、最终回来方法参数。

问题1:我的参数是个方针、底层是这么设置方针特点的。

是通过反射履行对应的set方法设置的值、下方有个简练版比如。

public static void main(String[] args) throws Exception {
    Method setId = User.class.getMethod("setId", Integer.class);
    User user = new User();
    setId.invoke(user, 21212);
    System.out.println(user);
}

问题2:我Controller方法有两个参数、参数值都通过参数解析器解析出来了值、底层是怎么将值设置到Controller方法的?

也是通过反射履行对应的方法、也是调用方法的invoke()来设置参数的传递并履行Controller中的方法。