創建項目
gitee地址:https://gitee.com/shapeless/demo/tree/master/demo_springboot_mvc
創建SpringBoot項目,引入spring-boot-starter-web
實體類
User.java
package com.example.demo.entity;
import java.util.List;
public class User {
private String name;
private Integer age;
private String[] password;
private List<Integer> scoreArray;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String[] getPassword() {
return password;
}
public void setPassword(String[] password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List<Integer> getScoreArray() {
return scoreArray;
}
public void setScoreArray(List<Integer> scoreArray) {
this.scoreArray = scoreArray;
}
}
Account.java
package com.example.demo.entity;
import java.io.Serializable;
public class Account implements Serializable {
private String phoneNum;
private String[] emails;
public String getPhoneNum() {
return phoneNum;
}
public void setPhoneNum(String phoneNum) {
this.phoneNum = phoneNum;
}
public String[] getEmails() {
return emails;
}
public void setEmails(String[] emails) {
this.emails = emails;
}
}
x-www-form-urlencoded請求
Controller
BasicController.java
package com.example.demo.controller;
import com.example.demo.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Arrays;
import java.util.List;
// localhost:8080/basic/no_annotation/string1
@RequestMapping("/basic")
@Controller
public class BasicController {
@RequestMapping("/1")
@ResponseBody
public String test1(int intVal, Long longVal, String myStr){
System.out.println("intVal : " + intVal);
System.out.println("longVal : " + longVal);
System.out.println("myStr : " + myStr);
return "Hello world";
}
@RequestMapping("/2")
@ResponseBody
public User test2(@RequestParam Integer intVal,
@RequestParam(value = "my_str", required = false) String[] myStr,
@RequestParam String[] password,
@RequestParam List<Integer> scoreArray,
@RequestParam Integer age,
User user){
System.out.println("intVal : " + intVal);
System.out.println("age : " + age);
System.out.println("myStr : " + Arrays.asList(myStr));
System.out.println("password : " + Arrays.asList(password));
System.out.println("scoreArray : " + scoreArray);
return user;
}
@RequestMapping("/3")
@ResponseBody
public User test3(@RequestBody User user){
return user;
}
}
測試
測試1:基本類型爲空導致錯誤
後端打印
java.lang.IllegalStateException: Optional int parameter 'intVal' is present but cannot be translated into a null value due to being declared as a primitive type. Consider declaring it as object wrapper for the corresponding primitive type.
前端結果
測試2:正常操作
其實也不正常,最好不要用基本類型接收。
http://localhost:8080/basic/1?intVal=2&myStr=hehe
後端
intVal : 2
longVal : null
myStr : hehe
前端
測試3:正常操作
後端
intVal : 2
age : 21
myStr : [Tony, Stark]
password : [ab, cd]
scoreArray : [99, 98]
前端
結論
- 如果一個參數,實體類與接口參數都有,則都會賦值。
- 若實體類中有數組/List 成員,也會對它直接賦值。
- 若實體類含有實體類,無法賦值。因爲參數中沒有與實體類相同名字的key,此時就要用form-data或者json了。(或者x-www-urlencoded也有寫類參數的方法,目前不知道)
測試4:@RequestParam缺少參數訪問(失敗)
後端
2020-07-04 18:00:37.222 WARN 15844 --- [nio-8080-exec-8] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required List parameter 'scoreArray' is not present]
前端
測試5:用@RequestBody接收(失敗)
後端
2020-07-04 14:02:55.954 WARN 15844 --- [nio-8080-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public com.example.demo.entity.User com.example.demo.controller.BasicController.test3(com.example.demo.entity.User)]
前端
form-data請求
其他網址
Controller
FormDataController.java
package com.example.demo.controller;
import com.example.demo.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
// localhost:8080/basic/no_annotation/string1
@RequestMapping("/formdata")
@Controller
public class FormDataController {
@RequestMapping("/1")
@ResponseBody
public String test1(int intVal, Long longVal, String myStr){
System.out.println("intVal : " + intVal);
System.out.println("longVal : " + longVal);
System.out.println("myStr : " + myStr);
return "Hello world";
}
@RequestMapping("/2")
@ResponseBody
public User test2(@RequestParam Integer intVal,
@RequestParam(value = "my_str", required = false) String[] myStr,
@RequestParam String[] password,
@RequestParam List<Integer> scoreArray,
@RequestParam Integer age,
User user){
System.out.println("intVal : " + intVal);
System.out.println("age : " + age);
System.out.println("myStr : " + Arrays.asList(myStr));
System.out.println("password : " + Arrays.asList(password));
System.out.println("scoreArray : " + scoreArray);
return user;
}
@RequestMapping("/3")
@ResponseBody
public User test3(User user){
System.out.println("name : " + user.getName());
System.out.println("password : " + Arrays.asList(user.getPassword()));
System.out.println("scoreArray : " + user.getScoreArray());
System.out.println("acount.phoneNum : " + user.getAccount().getPhoneNum());
System.out.println("account.emails : " + Arrays.asList(user.getAccount().getEmails()));
return user;
}
}
測試
測試1:基本類型爲空導致錯誤
postman(http://localhost:8080/formdata/1)
後端
java.lang.IllegalStateException: Optional int parameter 'intVal' is present but cannot be translated into a null value due to being declared as a primitive type. Consider declaring it as object wrapper for the corresponding primitive type.
測試2:基本類型不爲空(成功)
最好不要用基本類型接收。
postman:訪問http://localhost:8080/formdata/1/
後端
intVal : 2
longVal : null
myStr : hehe
測試3:正常操作:沒有@RequestBody的接收單個實體類接收
postman的body數據
後端
name : Jarvis
password : [ab, cd]
scoreArray : [99, 98]
acount.phoneNum : null
account.emails : [[email protected], [email protected]]
測試4:有@RequestBody的接收單個實體類接收(失敗)
postman訪問:http://localhost:8080/formdata/4/
postman數據:
後端打印
2020-07-06 09:18:19.637 WARN 16088 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'multipart/form-data;boundary=--------------------------428878162965035610803450;charset=UTF-8' not supported]
JSON請求(單個對象)
Controller
JsonController.java
package com.example.demo.controller;
import com.example.demo.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Arrays;
import java.util.List;
@RequestMapping("/json")
@Controller
public class JsonController {
@RequestMapping("/1")
@ResponseBody
public User setUserNoAnnotation(User user, List<String> password, Integer[] scoreArray) {
printUser(user);
return user;
}
@RequestMapping("/2")
@ResponseBody
public User setUserAnnotation(@RequestBody User user) {
printUser(user);
return user;
}
@RequestMapping("/3")
@ResponseBody
public User setUserAnnotation1(@RequestBody User user, @RequestParam List<String> password, Integer[] scoreArray) {
System.out.println(password);
if (scoreArray != null) {
System.out.println(Arrays.asList(scoreArray));
} else {
System.out.println("scoreArray = null");
}
System.out.println();
printUser(user);
return user;
}
@RequestMapping("/4")
@ResponseBody
public User setUserAnnotation2(@RequestBody User user, @RequestBody List<String> password, @RequestBody Integer[] scoreArray) {
if (password != null) {
System.out.println(password);
} else {
System.out.println("password = null");
}
if (scoreArray != null) {
System.out.println(Arrays.asList(scoreArray));
} else {
System.out.println("scoreArray = null");
}
System.out.println();
printUser(user);
return user;
}
private void printUser(User user){
System.out.println("name : " + user.getName());
System.out.println("password : " + Arrays.asList(user.getPassword()));
System.out.println("scoreArray : " + user.getScoreArray());
System.out.println("acount.phoneNum : " + user.getAccount().getPhoneNum());
System.out.println("account.emails : " + Arrays.asList(user.getAccount().getEmails()));
}
}
測試
測試前提
json的body
{
"name": "Jarvis",
"password": [
"ab",
"cd"
],
"scoreArray": [
99,
98
],
"account": {
"phoneNum": "123",
"emails": [
"[email protected]",
"[email protected]"
]
}
}
測試1:正常操作
使用postman,url:http://localhost:8080/json/2
後端
name : Jarvis
password : [ab, cd]
scoreArray : [99, 98]
acount.phoneNum : 123
account.emails : [[email protected], [email protected]]
postman
{
"name": "Jarvis",
"age": null,
"password": [
"ab",
"cd"
],
"scoreArray": [
99,
98
],
"account": {
"phoneNum": "123",
"emails": [
"[email protected]",
"[email protected]"
]
}
}
測試2:(不使用@RequestBody接收)
使用postman,url:http://localhost:8080/json/1
後端
2020-07-03 23:27:18.504 ERROR 12568 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: No primary or default constructor found for interface java.util.List] with root cause
java.lang.NoSuchMethodException: java.util.List.<init>()
at java.lang.Class.getConstructor0(Class.java:3082) ~[na:1.8.0_51]
at java.lang.Class.getDeclaredConstructor(Class.java:2178) ~[na:1.8.0_51]
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.createAttribute(ModelAttributeMethodProcessor.java:216) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.createAttribute(ServletModelAttributeMethodProcessor.java:85) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:139) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.36.jar:9.0.36]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) [tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373) [tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) [tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590) [tomcat-embed-core-9.0.36.jar:9.0.36]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.36.jar:9.0.36]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_51]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_51]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.36.jar:9.0.36]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_51]
postman
{
"timestamp": "2020-07-03T15:44:10.224+00:00",
"status": 500,
"error": "Internal Server Error",
"message": "",
"path": "/json/1"
}
測試3:(一個有@RequestBody+多個無)
postman訪問:http://localhost:8080/json/3?password=ef,gh
後端
[ef, gh]
scoreArray = null
name : Jarvis
password : [ab, cd]
scoreArray : [99, 98]
acount.phoneNum : 123
account.emails : [[email protected], [email protected]]
postman
{
"name": "Jarvis",
"age": null,
"password": [
"ab",
"cd"
],
"scoreArray": [
99,
98
],
"account": {
"phoneNum": "123",
"emails": [
"[email protected]",
"[email protected]"
]
}
}
測試4:多個@RequestBody
postman訪問:http://localhost:8080/json/4?password=ef,gh
後端
2020-07-03 23:48:06.907 WARN 2644 --- [nio-8080-exec-5] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: I/O error while reading input message; nested exception is java.io.IOException: Stream closed]
postman
{
"timestamp": "2020-07-03T15:47:45.924+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/json/4"
}
錯誤原因
其他網址:https://www.e-learn.cn/topic/3352427
每個方法只能有一個@RequestBody。使用@RequestBody把請求轉化爲特定的Object(在最後會關閉相應的流),所以在同一個方法中第二次使用@RequestBody是沒用的,因爲流已經關閉。
You cannot use it this way as only one @RequestBody per method is allowed. Using @RequestBody Spring converts incoming request body into the specified object (what closes the stream representing body at the end) so attempting to use @RequestBody second time in the same method makes no sense as stream has been already closed.
JSON請求(多個對象)
其他網址
方案1:Map對象接收
前端
$("#ok2").click(function(){
var json = {"user":{"id":9527,"userName":"zcy","realName":"鋼鐵俠"},"info":{"id":998,"address":"紐約"}};
$.ajax({
url:"http://localhost:8080/more/show",
type:"post",
cache:false,
contentType:"application/json",
data:JSON.stringify(json),
success:function(data){
alert(data);
}
});
});
後端
@RequestMapping(value = "/show")
public String test(@RequestBody Map<String,Object> map){
// 拿到Object之後 再做轉換爲實體即可 可以用FastJson
Object user = map.get("user");
Object info = map.get("info");
return "success";
}
方案2:自定義處理方法
前端
$("#ok2").click(function(){
var json = {"user":{"id":9527,"userName":"zcy","realName":"鋼鐵俠"},"info":{"id":998,"address":"紐約"}};
$.ajax({
url:"http://localhost:8080/more/custom",
type:"post",
cache:false,
// 直接傳josn對象 這裏與上文不同
data:json,
success:function(data){
alert(data);
}
});
});
後端
自定義註解 (加在控制器的參數前作爲標記)
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JsonObject {
}
自定義處理類(實現HandlerMethodArgumentResolver接口)
public class JsonObjectArgResolverHandler implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(JsonObject.class);
}
@Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
// 獲取Controller中的參數名
String name = methodParameter.getParameterName();
// 獲取Controller中參數的類型
Class clazz = methodParameter.getParameterType();
Object arg = null;
// 獲取該參數實體的所用屬性
Field[] fields = clazz.getDeclaredFields();
// 實例化
Object target = clazz.newInstance();
// 創建WebDataBinder對象 反射 遍歷fields給屬性賦值
WebDataBinder binder = webDataBinderFactory.createBinder(nativeWebRequest,null,name);
for (Field field:fields){
field.setAccessible(true);
String fieldName = field.getName();
Class<?> fieldType = field.getType();
// 在request中 多對象json數據的key被解析爲 user[id] user[realName] info[address] 的這種形式
String value = nativeWebRequest.getParameter(name + "[" + fieldName + "]");
arg = binder.convertIfNecessary(value,fieldType,methodParameter);
field.set(target,arg);
}
return target;
}
}
註冊自己寫的處理類
@Component
public class MyWebAppConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
// 配置自定義接收參數
WebMvcConfigurer.super.addArgumentResolvers(resolvers);
resolvers.add(new JsonObjectArgResolverHandler());
}
}
Controller
@RequestMapping(value = "/custom")
public String custom(@JsonObject User user, @JsonObject Info info){
System.out.println(user.toString());
System.out.println(info.toString());
return "success";
}