The Wayback Machine - http://web.archive.org/web/20250622150611/https://www.iocoder.cn/Spring-Boot/SpringMVC/?self
⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

:  http://www.iocoder.cn/Spring-Boot/SpringMVC/ 


1. 

2. 

3. 

4. 

5. 

6. HandlerInterceptor 

7. ServletFilterListener

8. Cors 

9. HttpMessageConverter 

10.  Fastjson

666. 





🙂🙂🙂****


(一)RocketMQ / MyCAT / Sharding-JDBC 

(二)RocketMQ / MyCAT / Sharding-JDBC  GitHub 

(三)

(四)

(五)





 https://github.com/YunaiV/SpringBoot-Labs  lab-23 

 Star 

1. 


 Java Web  Web MVC  Struts2  SpringMVC SpringMVC  Struts2 


 Struts2  SpringMVC  


SpringMVC  Web MVC 使 SpringMVC  2011  Struts2  SpringMVC 

SpringMVC  Struts2 Spring 

 Spring Boot  SpringMVC SpringMVC 

2. 



lab-springmvc-23-01 


使 spring-boot-starter-web  SpringMVC 
请求方法 URL 功能
GET /users 查询用户列表
GET /users/{id} 获得指定用户编号的用户
POST /users 添加用户
PUT /users/{id} 更新指定用户编号的用户
DELETE /users/{id} 删除指定用户编号的用户

~

2.1 


使 SpringMVC 


@Controller

@RestController

@RequestMapping

@GetMapping

@PostMapping

@PutMapping

@RequestParam

@PathVariable



2.1.1 @Controller


@Controller  Controller 


name  Controller  Bean 


@RestController  @Controller  @ResponseBody 使 JSON/XML 使 InternalResourceViewResolver  HTML 

   API  99.99% 使 @RestController 

 API  Restful  Restful 


RESTful API 

 Github  Restful HTTP API 

2.1.2 @RequestMapping


@RequestMapping /

@RequestMapping 


path [] 

values  path 

method  RequestMethod  GETPOSTPOSTDELETE [] 


@RequestMapping 



name 



params 



headers  params 



consumes  params ( Content-Type )



produces  params ( Accept )


 consumes  produces  Http  Content-Type  Accept  Spring MVC  




便Spring 


@GetMapping  @GET  @RequestMapping 

@PostMapping  @POST  @RequestMapping 

@PutMapping  @PUT  @RequestMapping 

@DeleteMapping  @DELETE  @RequestMapping 



2.1.3 @RequestParam


@RequestParam 


name 使

value  name 

required  true 

defaultValue 


@PathVariable  @RequestParam  defaultValue 

 SpringMVC 

2.2 


 pom.xml 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>lab-springmvc-23-01</artifactId>

<dependencies>
<!-- 实现对 Spring MVC 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 方便等会写单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

</project>

具体每个依赖的作用,胖友自己认真看下艿艿添加的所有注释噢。

2.3 Application

创建 Application.java 类,配置 @SpringBootApplication 注解即可。代码如下:

@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

}


 Controller 

2.4 UserController


 cn.iocoder.springboot.lab23.springmvc  UserController 

// UserController.java

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

/**
* 查询用户列表
*
* @return 用户列表
*/
@GetMapping("")
public List<UserVO> list() {
// 查询列表
List<UserVO> result = new ArrayList<>();
result.add(new UserVO().setId(1).setUsername("yudaoyuanma"));
result.add(new UserVO().setId(2).setUsername("woshiyutou"));
result.add(new UserVO().setId(3).setUsername("chifanshuijiao"));
// 返回列表
return result;
}

/**
* 获得指定用户编号的用户
*
* @param id 用户编号
* @return 用户
*/
@GetMapping("/{id}")
public UserVO get(@PathVariable("id") Integer id) {
// 查询并返回用户
return new UserVO().setId(id).setUsername("username:" + id);
}

/**
* 添加用户
*
* @param addDTO 添加用户信息 DTO
* @return 添加成功的用户编号
*/
@PostMapping("")
public Integer add(UserAddDTO addDTO) {
// 插入用户记录,返回编号
Integer returnId = 1;
// 返回用户编号
return returnId;
}

/**
* 更新指定用户编号的用户
*
* @param id 用户编号
* @param updateDTO 更新用户信息 DTO
* @return 是否修改成功
*/
@PutMapping("/{id}")
public Boolean update(@PathVariable("id") Integer id, UserUpdateDTO updateDTO) {
// 将 id 设置到 updateDTO 中
updateDTO.setId(id);
// 更新用户记录
Boolean success = true;
// 返回更新是否成功
return success;
}

/**
* 删除指定用户编号的用户
*
* @param id 用户编号
* @return 是否删除成功
*/
@DeleteMapping("/{id}")
public Boolean delete(@PathVariable("id") Integer id) {
// 删除用户记录
Boolean success = false;
// 返回是否更新成功
return success;
}

}


  •  @RestController 使 JSON 



     @RequestMapping("/users")  UserController  /users 
  • #list() 方法,查询用户列表。请求对应 GET /users,请求结果为:

    [
    {
    "id": 1,
    "username": "yudaoyuanma"
    },
    {
    "id": 2,
    "username": "woshiyutou"
    },
    {
    "id": 3,
    "username": "chifanshuijiao"
    }
    ]


    UserVO VO




    #get(Integer id)  GET /users/{id} :
    {
    "id": 1,
    "username": "username:1"
    }

  • #add(UserAddDTO addDTO) 方法,添加用户。请求对应 POST /users ,请求结果为:

    1


     Integer  POJO 使 JSON 

    UserAddDTO  DTO 




    #update(Integer id, UserUpdateDTO updateDTO)  PUT /users/{id} 
    true

  • #delete(Integer id) 方法,删除指定用户编号的用户。请求对应 DELETE /users/{id} 【路径参数】,请求结果为:

    false

以上的测试,肯定需要通过运行 Application ,启动项目。这里,补充下它的启动日志如下:

2019-11-15 18:46:00.671  INFO 99493 --- [           main] c.i.s.lab23.springmvc.Application        : Starting Application on MacBook-Pro-8 with PID 99493 (/Users/yunai/Java/SpringBoot-Labs/lab-23/lab-springmvc-23-01/target/classes started by yunai in /Users/yunai/Java/SpringBoot-Labs)
2019-11-15 18:46:00.673 INFO 99493 --- [ main] c.i.s.lab23.springmvc.Application : No active profile set, falling back to default profiles: default
2019-11-15 18:46:01.593 INFO 99493 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2019-11-15 18:46:01.613 INFO 99493 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2019-11-15 18:46:01.613 INFO 99493 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.16]
2019-11-15 18:46:01.619 INFO 99493 --- [ main] o.a.catalina.core.AprLifecycleListener : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/Users/yunai/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.]
2019-11-15 18:46:01.684 INFO 99493 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-11-15 18:46:01.684 INFO 99493 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 976 ms
2019-11-15 18:46:01.844 INFO 99493 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2019-11-15 18:46:01.987 INFO 99493 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2019-11-15 18:46:01.990 INFO 99493 --- [ main] c.i.s.lab23.springmvc.Application : Started Application in 1.559 seconds (JVM running for 2.146)

  • 我们可以看到,Spring Boot 在启动 SpringMVC 时,会默认初始化一个内嵌的 Tomcat ,监听 8080 端口的请求。

2.5 UserController2


使 GET  POST  Restful API  Restful API 

 SpringMVC  @PathVariable 使


1 URL  URL 

2 URL  URL  URL  URL 便

3@PathVariable  URL  SpringMVC  SpringMVC RESTful  


 UserController2  API 

// UserController2.java

@RestController
@RequestMapping("/users2")
public class UserController2 {

/**
* 查询用户列表
*
* @return 用户列表
*/
@GetMapping("/list") // URL 修改成 /list
public List<UserVO> list() {
// 查询列表
List<UserVO> result = new ArrayList<>();
result.add(new UserVO().setId(1).setUsername("yudaoyuanma"));
result.add(new UserVO().setId(2).setUsername("woshiyutou"));
result.add(new UserVO().setId(3).setUsername("chifanshuijiao"));
// 返回列表
return result;
}

/**
* 获得指定用户编号的用户
*
* @param id 用户编号
* @return 用户
*/
@GetMapping("/get") // URL 修改成 /get
public UserVO get(@RequestParam("id") Integer id) {
// 查询并返回用户
return new UserVO().setId(id).setUsername(UUID.randomUUID().toString());
}

/**
* 添加用户
*
* @param addDTO 添加用户信息 DTO
* @return 添加成功的用户编号
*/
@PostMapping("add") // URL 修改成 /add
public Integer add(UserAddDTO addDTO) {
// 插入用户记录,返回编号
Integer returnId = UUID.randomUUID().hashCode();
// 返回用户编号
return returnId;
}

/**
* 更新指定用户编号的用户
*
* @param updateDTO 更新用户信息 DTO
* @return 是否修改成功
*/
@PostMapping("/update") // URL 修改成 /update ,RequestMethod 改成 POST
public Boolean update(UserUpdateDTO updateDTO) {
// 更新用户记录
Boolean success = true;
// 返回更新是否成功
return success;
}

/**
* 删除指定用户编号的用户
*
* @param id 用户编号
* @return 是否删除成功
*/
@DeleteMapping("/delete") // URL 修改成 /delete ,RequestMethod 改成 DELETE
public Boolean delete(@RequestParam("id") Integer id) {
// 删除用户记录
Boolean success = false;
// 返回是否更新成功
return success;
}

}

  • 每一处的修改,看下 @XXXMapping 注解后的注释说明。

3. 测试接口


lab-springmvc-23-01 


使 Postmancurl API 

SpringMVC  MockMvc 便 2.4 UserController  lab-springmvc-23-01 

MockMvc  3.1   3.2  


 Java 



3.1 


 UserControllerTest  UserController 

// UserControllerTest.java

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@AutoConfigureMockMvc
public class UserControllerTest {

@Autowired
private MockMvc mvc;

@Test
public void testList() throws Exception {
// 查询用户列表
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.get("/users"));
// 校验结果
resultActions.andExpect(MockMvcResultMatchers.status().isOk()); // 响应状态码 200
resultActions.andExpect(MockMvcResultMatchers.content().json("[\n" +
" {\n" +
" \"id\": 1,\n" +
" \"username\": \"yudaoyuanma\"\n" +
" },\n" +
" {\n" +
" \"id\": 2,\n" +
" \"username\": \"woshiyutou\"\n" +
" },\n" +
" {\n" +
" \"id\": 3,\n" +
" \"username\": \"chifanshuijiao\"\n" +
" }\n" +
"]")); // 响应结果
}

@Test
public void testGet() throws Exception {
// 获得指定用户编号的用户
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.get("/users/1"));
// 校验结果
resultActions.andExpect(MockMvcResultMatchers.status().isOk()); // 响应状态码 200
resultActions.andExpect(MockMvcResultMatchers.content().json("{\n" +
"\"id\": 1,\n" +
"\"username\": \"username:1\"\n" +
"}")); // 响应结果
}

@Test
public void testAdd() throws Exception {
// 获得指定用户编号的用户
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.post("/users")
.param("username", "yudaoyuanma")
.param("passowrd", "nicai"));
// 校验结果
resultActions.andExpect(MockMvcResultMatchers.status().isOk()); // 响应状态码 200
resultActions.andExpect(MockMvcResultMatchers.content().string("1")); // 响应结果
}

@Test
public void testUpdate() throws Exception {
// 获得指定用户编号的用户
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.put("/users/1")
.param("username", "yudaoyuanma")
.param("passowrd", "nicai"));
// 校验结果
resultActions.andExpect(MockMvcResultMatchers.status().isOk()); // 响应状态码 200
resultActions.andExpect(MockMvcResultMatchers.content().string("true")); // 响应结果
}

@Test
public void testDelete() throws Exception {
// 获得指定用户编号的用户
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.delete("/users/1"));
// 校验结果
resultActions.andExpect(MockMvcResultMatchers.status().isOk()); // 响应状态码 200
resultActions.andExpect(MockMvcResultMatchers.content().string("false")); // 响应结果
}

}


 @AutoConfigureMockMvc  MockMvc Bean  mvc  mvc  API  API  Spring 

 API  MockMvcRequestBuilders  mvc  ResultActions 

 ResultActions  andExpect(ResultMatcher matcher) 


ResultActions 


#andDo(ResultHandler handler)  ResultHandler 

#andReturn()  MvcResult  MvcResult 


 #testGet() 使

// UserControllerTest.java

@Test
public void testGet2() throws Exception {
// 获得指定用户编号的用户
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.get("/users/1"));
// 校验结果
resultActions.andExpect(MockMvcResultMatchers.status().isOk()); // 响应状态码 200
resultActions.andExpect(MockMvcResultMatchers.content().json("{\n" +
"\"id\": 1,\n" +
"\"username\": \"username:1\"\n" +
"}")); // 响应结果

// <1> 打印结果
resultActions.andDo(MockMvcResultHandlers.print());

// <2> 获得 MvcResult ,后续执行各种自定义逻辑
MvcResult mvcResult = resultActions.andReturn();
System.out.println("拦截器数量:" + mvcResult.getInterceptors().length);
}

  • <1> 处,打印请求和响应信息。输出如下:

    MockHttpServletRequest:
    HTTP Method = GET
    Request URI = /users/1
    Parameters = {}
    Headers = []
    Body = null
    Session Attrs = {}

    Handler:
    Type = cn.iocoder.springboot.lab23.springmvc.controller.UserController
    Method = public cn.iocoder.springboot.lab23.springmvc.vo.UserVO cn.iocoder.springboot.lab23.springmvc.controller.UserController.get(java.lang.Integer)

    Async:
    Async started = false
    Async result = null

    Resolved Exception:
    Type = null

    ModelAndView:
    View name = null
    View = null
    Model = null

    FlashMap:
    Attributes = null

    MockHttpServletResponse:
    Status = 200
    Error message = null
    Headers = [Content-Type:"application/json;charset=UTF-8"]
    Content type = application/json;charset=UTF-8
    Body = {"id":1,"username":"username:1"}
    Forwarded URL = null
    Redirected URL = null
    Cookies = []

  • <2> 处,获得 MvcResult 后,打印下拦截器的数量。输出如下:

    拦截器数量:2

3.2 单元测试

为了更好的展示 SpringMVC 单元测试的示例,我们需要改写 UserController 的代码,让其会依赖 UserService 。修改点如下:

  • cn.iocoder.springboot.lab23.springmvc.service 包路径下,创建 UserService 类。代码如下:

    // UserService.java

    @Service
    public class UserService {

    public UserVO get(Integer id) {
    return new UserVO().setId(id).setUsername("test");
    }

    }

  • UserController 类中,增加 GET /users/v2/{id} 接口,获得指定用户编号的用户。代码如下:

    // UserController.java

    @Autowired
    private UserService userService;

    /**
    * 获得指定用户编号的用户
    *
    * @param id 用户编号
    * @return 用户
    */
    @GetMapping("/v2/{id}")
    public UserVO get2(@PathVariable("id") Integer id) {
    return userService.get(id);
    }


     UserService Bean  userService  UserService#get(Integer id) 




     UserControllerTest2  UserController  API 

    // UserControllerTest2.java

    @RunWith(SpringRunner.class)
    @WebMvcTest(UserController.class)
    public class UserControllerTest2 {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private UserService userService;

    @Test
    public void testGet2() throws Exception {
    // Mock UserService 的 get 方法
    System.out.println("before mock:" + userService.get(1)); // <1.1>
    Mockito.when(userService.get(1)).thenReturn(
    new UserVO().setId(1).setUsername("username:1")); // <1.2>
    System.out.println("after mock:" + userService.get(1)); // <1.3>

    // 查询用户列表
    ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.get("/users/v2/1"));
    // 校验结果
    resultActions.andExpect(MockMvcResultMatchers.status().isOk()); // 响应状态码 200
    resultActions.andExpect(MockMvcResultMatchers.content().json("{\n" +
    " \"id\": 1,\n" +
    " \"username\": \"username:1\"\n" +
    "}")); // 响应结果
    }

    }


    •  @WebMvcTest  UserController  UserController 



      @WebMvcTest  @AutoConfigureMockMvc  MockMvc Bean  mvc  mvc  API  API  Mock  Mock  Spring 







      userService  @MockBean 使 Mockito  UserService Mock UserService Mock 代理对象


      UserController  UserService  Mock  UserService Bean 




    • <1.1> 处,我们调用 UserService#get(Integer id) 方法,然后打印返回结果。执行结果如下:

      before mock:null


       null  id = 1  UserVO  userService  Mockito  Mock 




      <1.2>  Mockito  Mock userService  #get(Integer id)  id = 1  id = 1  username = "username:1"  UserVO 



      <1.3>  UserService#get(Integer id) 
      after mock:cn.iocoder.springboot.lab23.springmvc.vo.UserVO@23202c31

      • 打印的就是我们 Mock 返回的 UserVO 对象。

  • 使 mvc  API 



      

     Testing the Web Layer Spring 

    4. 



    lab-springmvc-23-02 


     API  API 










     + 

     + 


     RESTful API 使 HTTP  


    HTTP 

     HTTP  200403404500 


     Response Body 





    code


     0 





     success  true  false  0 




    data



    message




    // 成功响应
    {
    code: 0,
    data: {
    id: 1,
    username: "yudaoyuanma"
    }
    }

    // 失败响应
    {
    code: 233666,
    message: "徐妈太丑了"
    }





     2.   3.  

    4.1 


     2.2  

    4.2 Application


     2.3 Application 

    4.3 CommonResult


     cn.iocoder.springboot.lab23.springmvc.core.vo  CommonResult 

    // CommonResult.java

    public class CommonResult<T> implements Serializable {

    public static Integer CODE_SUCCESS = 0;

    /**
    * 错误码
    */
    private Integer code;
    /**
    * 错误提示
    */
    private String message;
    /**
    * 返回数据
    */
    private T data;

    /**
    * 将传入的 result 对象,转换成另外一个泛型结果的对象
    *
    * 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
    *
    * @param result 传入的 result 对象
    * @param <T> 返回的泛型
    * @return 新的 CommonResult 对象
    */
    public static <T> CommonResult<T> error(CommonResult<?> result) {
    return error(result.getCode(), result.getMessage());
    }

    public static <T> CommonResult<T> error(Integer code, String message) {
    Assert.isTrue(!CODE_SUCCESS.equals(code), "code 必须是错误的!");
    CommonResult<T> result = new CommonResult<>();
    result.code = code;
    result.message = message;
    return result;
    }

    public static <T> CommonResult<T> success(T data) {
    CommonResult<T> result = new CommonResult<>();
    result.code = CODE_SUCCESS;
    result.data = data;
    result.message = "";
    return result;
    }

    @JsonIgnore // 忽略,避免 jackson 序列化给前端
    public boolean isSuccess() { // 方便判断是否成功
    return CODE_SUCCESS.equals(code);
    }

    @JsonIgnore // 忽略,避免 jackson 序列化给前端
    public boolean isError() { // 方便判断是否失败
    return !isSuccess();
    }

    // ... 省略 setting/getting/toString 方法

    }




    4.4 GlobalResponseBodyHandler


     cn.iocoder.springboot.lab23.springmvc.core.web  GlobalResponseBodyHandler 

    // GlobalResponseBodyHandler.java

    @ControllerAdvice(basePackages = "cn.iocoder.springboot.lab23.springmvc.controller")
    public class GlobalResponseBodyHandler implements ResponseBodyAdvice {

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
    return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType,
    ServerHttpRequest request, ServerHttpResponse response) {
    // 如果已经是 CommonResult 类型,则直接返回
    if (body instanceof CommonResult) {
    return body;
    }
    // 如果不是,则包装成 CommonResult 类型
    return CommonResult.success(body);
    }

    }


     SpringMVC 使 ResponseBodyAdvice  @ControllerAdvice  Controller  @ControllerAdvice  basePackages  "cn.iocoder.springboot.lab23.springmvc.controller"  Controller  Swagger 使 Controller  API  GlobalResponseBodyHandler 

     #supports(MethodParameter returnType, Class converterType)  true  Controller  API 

     #beforeBodyWrite(...)  CommonResult  CommonResult 

     API  CommonResult 

    API  GlobalResponseBodyHandler 使 CommonResult#success(T data)  CommonResult  API  Controller  5.  



    4.5 UserController


     cn.iocoder.springboot.lab23.springmvc.controller  UserController 

    // UserController.java

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

    /**
    * 获得指定用户编号的用户
    *
    * 提供不使用 CommonResult 包装
    *
    * @param id 用户编号
    * @return 用户
    */
    @GetMapping("/get")
    public UserVO get(@RequestParam("id") Integer id) {
    // 查询并返回用户
    return new UserVO().setId(id).setUsername(UUID.randomUUID().toString());
    }

    /**
    * 获得指定用户编号的用户
    *
    * 提供使用 CommonResult 包装
    *
    * @param id 用户编号
    * @return 用户
    */
    @GetMapping("/get2")
    public CommonResult<UserVO> get2(@RequestParam("id") Integer id) {
    // 查询用户
    UserVO user = new UserVO().setId(id).setUsername(UUID.randomUUID().toString());
    // 返回结果
    return CommonResult.success(user);
    }

    }



     #get(Integer id)  UserVO  GlobalResponseBodyHandler  CommonResult 
    {
    "code": 0,
    "message": "",
    "data": {
    "id": 10,
    "username": "f0ab9401-062f-4697-bcc9-1dc70c1c1310"
    }
    }


     "message": "" 使 SpringMVC  Jackson  CommonResult  message = null  "message": "" 




     #get2(Integer id)  Common<UserVO>  GlobalResponseBodyHandler  CommonResult 


    4.6 


    使 GlobalResponseBodyHandler  Controller 

    使使使 AOP  onemall  Controller  CommonResult  Controller#get2(Integer id) 

     GlobalResponseBodyHandler 

    ResponseBodyAdvice  Response Body SpringMVC  RequestBodyAdvice  Request Body 使 ResponseBodyAdvice  ResponseBodyAdvice 使西

    5. 



    lab-springmvc-23-02 


     4.  使 CommonResult  CommonResult 




     4.   lab-springmvc-23-02 

    5.1 ServiceExceptionEnum


     cn.iocoder.springboot.lab23.springmvc.constants  ServiceExceptionEnum 

    // ServiceExceptionEnum.java

    public enum ServiceExceptionEnum {

    // ========== 系统级别 ==========
    SUCCESS(0, "成功"),
    SYS_ERROR(2001001000, "服务端发生异常"),
    MISSING_REQUEST_PARAM_ERROR(2001001001, "参数缺失"),

    // ========== 用户模块 ==========
    USER_NOT_FOUND(1001002000, "用户不存在"),

    // ========== 订单模块 ==========

    // ========== 商品模块 ==========
    ;

    /**
    * 错误码
    */
    private int code;
    /**
    * 错误提示
    */
    private String message;

    ServiceExceptionEnum(int code, String message) {
    this.code = code;
    this.message = message;
    }

    // ... 省略 getting 方法

    }

    • 因为错误码是全局的,最好按照模块来拆分。如下是艿艿在 onemall 项目的实践:

      /**
      * 服务异常
      *
      * 参考 https://www.kancloud.cn/onebase/ob/484204 文章
      *
      * 一共 10 位,分成四段
      *
      * 第一段,1 位,类型
      * 1 - 业务级别异常
      * 2 - 系统级别异常
      * 第二段,3 位,系统类型
      * 001 - 用户系统
      * 002 - 商品系统
      * 003 - 订单系统
      * 004 - 支付系统
      * 005 - 优惠劵系统
      * ... - ...
      * 第三段,3 位,模块
      * 不限制规则。
      * 一般建议,每个系统里面,可能有多个模块,可以再去做分段。以用户系统为例子:
      * 001 - OAuth2 模块
      * 002 - User 模块
      * 003 - MobileCode 模块
      * 第四段,3 位,错误码
      * 不限制规则。
      * 一般建议,每个模块自增。
      */

    5.2 ServiceException


     Service 


     ServiceException  throws 

     CommonResult  return 


     CommonResult 


     Spring @Transactional 使 CommonResult 

     CommonResult 


     ServiceException 

     cn.iocoder.springboot.lab23.springmvc.core.exception  ServiceException  RuntimeException 

    // ServiceException.java

    public final class ServiceException extends RuntimeException {

    /**
    * 错误码
    */
    private final Integer code;

    public ServiceException(ServiceExceptionEnum serviceExceptionEnum) {
    // 使用父类的 message 字段
    super(serviceExceptionEnum.getMessage());
    // 设置错误码
    this.code = serviceExceptionEnum.getCode();
    }

    // ... 省略 getting 方法

    }


     serviceExceptionEnum 

    5.3 GlobalExceptionHandler


     cn.iocoder.springboot.lab23.springmvc.core.web  GlobalExceptionHandler 

    // GlobalExceptionHandler.java

    @ControllerAdvice(basePackages = "cn.iocoder.springboot.lab23.springmvc.controller")
    public class GlobalExceptionHandler {

    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
    * 处理 ServiceException 异常
    */
    @ResponseBody
    @ExceptionHandler(value = ServiceException.class)
    public CommonResult serviceExceptionHandler(HttpServletRequest req, ServiceException ex) {
    logger.debug("[serviceExceptionHandler]", ex);
    // 包装 CommonResult 结果
    return CommonResult.error(ex.getCode(), ex.getMessage());
    }

    /**
    * 处理 MissingServletRequestParameterException 异常
    *
    * SpringMVC 参数不正确
    */
    @ResponseBody
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public CommonResult missingServletRequestParameterExceptionHandler(HttpServletRequest req, MissingServletRequestParameterException ex) {
    logger.debug("[missingServletRequestParameterExceptionHandler]", ex);
    // 包装 CommonResult 结果
    return CommonResult.error(ServiceExceptionEnum.MISSING_REQUEST_PARAM_ERROR.getCode(),
    ServiceExceptionEnum.MISSING_REQUEST_PARAM_ERROR.getMessage());
    }

    /**
    * 处理其它 Exception 异常
    */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public CommonResult exceptionHandler(HttpServletRequest req, Exception e) {
    // 记录异常日志
    logger.error("[exceptionHandler]", e);
    // 返回 ERROR CommonResult
    return CommonResult.error(ServiceExceptionEnum.SYS_ERROR.getCode(),
    ServiceExceptionEnum.SYS_ERROR.getMessage());
    }

    }


     @ControllerAdvice  4.4 GlobalResponseBodyHandler  ResponseBodyAdvice 

     @ExceptionHandler  @ResponseBody 使 API 

    #serviceExceptionHandler(...)  ServiceException 使 code + message  CommonResult 

    #missingServletRequestParameterExceptionHandler(...)  MissingServletRequestParameterException  ServiceExceptionEnum.MISSING_REQUEST_PARAM_ERROR  CommonResult 

    #exceptionHandler(...)  Exception  ServiceExceptionEnum.SYS_ERROR  CommonResult  GlobalExceptionHandler 


     #exceptionHandler(...) 使 logger 便 ELK 

    5.4 UserController


     UserController  API 便

    // UserController.java

    /**
    * 测试抛出 NullPointerException 异常
    */
    @GetMapping("/exception-01")
    public UserVO exception01() {
    throw new NullPointerException("没有粗面鱼丸");
    }

    /**
    * 测试抛出 ServiceException 异常
    */
    @GetMapping("/exception-02")
    public UserVO exception02() {
    throw new ServiceException(ServiceExceptionEnum.USER_NOT_FOUND);
    }



     #exception01()  NullPointerException  GlobalExceptionHandler#exceptionHandler(...)  CommonResult 
    {
    "code": 2001001000,
    "message": "服务端发生异常",
    "data": null
    }



     #exception02()  ServiceException  GlobalExceptionHandler#serviceExceptionHandler(...)  CommonResult 
    {
    "code": 1001002000,
    "message": "用户不存在",
    "data": null
    }

6. HandlerInterceptor 拦截器


lab-springmvc-23-02 


使 SpringMVC 使 HandlerInterceptor  SpringMVC 




 access_token 访 ThreadLocal  ThreadLocal 

 API 访 API 

 API 


HandlerInterceptor 

// HandlerInterceptor.java

public interface HandlerInterceptor {

default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}

default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}

default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}

}


  •  API  handler 

     users/exception_03 handler  UserController  #exception03() 

    HandlerInterceptor  Handler  Handler 




     handler 

    // 伪代码
    Exception ex = null;
    try {
    // 前置处理
    if (!preHandle(request, response, handler)) {
    return;
    }

    // 执行处理器,即执行 API 的逻辑
    handler.execute();

    // 后置处理
    postHandle(request, response, handler);
    } catch(Exception exception) {
    // 如果发生了异常,记录到 ex 中
    ex = exception;
    } finally {
    afterCompletion(request, response, handler);
    }




    便




    #preHandle(...)  handler  true  handler  false  handler 


     false  handler 




    #postHandle(...)  handler 


     View 使




    #afterCompletion(...)  handler  #preHandle(...)  true  HandlerInterceptor  #afterCompletion(...)  HandlerInterceptor 


     ThreadLocal 线使线

     handler  5.  






     HandlerInterceptor  HandlerInterceptor  Chain 


     HandlerInterceptor  #preHandle(...) 

     handler 

     HandlerInterceptor  #postHandle(...) 

     HandlerInterceptor  #afterCompletion(...) 





     HandlerInterceptor  #preHandle(...)  false 

    handler  Exception 

     HandlerInterceptor  #afterCompletion(...)  Exception 

    ... 





     5.   lab-springmvc-23-02 

    6.1  HandlerInterceptor


     cn.iocoder.springboot.lab23.springmvc.core.interceptor  HandlerInterceptor 
    • FirstInterceptor 代码如下:

      // FirstInterceptor.java

      public class FirstInterceptor implements HandlerInterceptor {

      private Logger logger = LoggerFactory.getLogger(getClass());

      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
      logger.info("[preHandle][handler({})]", handler);
      return true;
      }

      @Override
      public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
      logger.info("[postHandle][handler({})]", handler);
      }

      @Override
      public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
      logger.info("[afterCompletion][handler({})]", handler, ex);
      }

      }

      • 每个方法中,打印日志。
    • SecondInterceptor 代码如下:

      // SecondInterceptor.java

      public class SecondInterceptor implements HandlerInterceptor {

      private Logger logger = LoggerFactory.getLogger(getClass());

      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
      logger.info("[preHandle][handler({})]", handler);
      return false; // 故意返回 false
      }

      @Override
      public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
      logger.info("[postHandle][handler({})]", handler);
      }

      @Override
      public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
      logger.info("[afterCompletion][handler({})]", handler, ex);
      }

      }

    • ThirdInterceptor 代码如下:

      // ThirdInterceptor.java

      public class ThirdInterceptor implements HandlerInterceptor {

      private Logger logger = LoggerFactory.getLogger(getClass());

      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
      logger.info("[preHandle][handler({})]", handler);
      return true;
      }

      @Override
      public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
      logger.info("[postHandle][handler({})]", handler);
      }

      @Override
      public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
      logger.info("[afterCompletion][handler({})]", handler, ex);
      throw new RuntimeException("故意抛个错误"); // 故意抛出异常
      }

      }


       FirstInterceptor  #afterCompletion(...)  RuntimeException 



      6.2 SpringMVCConfiguration


       cn.iocoder.springboot.lab23.springmvc.config  SpringMVCConfiguration 

      // SpringMVCConfiguration.java

      @Configuration
      public class SpringMVCConfiguration implements WebMvcConfigurer {

      @Bean
      public FirstInterceptor firstInterceptor() {
      return new FirstInterceptor();
      }

      @Bean
      public SecondInterceptor secondInterceptor() {
      return new SecondInterceptor();
      }

      @Bean
      public ThirdInterceptor thirdInterceptor() {
      return new ThirdInterceptor();
      }

      @Override
      public void addInterceptors(InterceptorRegistry registry) {
      // 拦截器一
      registry.addInterceptor(this.firstInterceptor()).addPathPatterns("/**");
      // 拦截器二
      registry.addInterceptor(this.secondInterceptor()).addPathPatterns("/users/current_user");
      // 拦截器三
      registry.addInterceptor(this.thirdInterceptor()).addPathPatterns("/**");
      }

      }


       WebMvcConfigurer  SpringMVC  @Configuration  SpringMVCConfiguration 

      #addInterceptors(InterceptorRegistry registry)  HandlerInterceptor  InterceptorRegistry SecondInterceptor  /users/current_user  SecondInterceptor#preHandle(...)  false 

      6.3 UserController


       UserController 

       /users/do_something 

      // UserController.java

      @GetMapping("/do_something")
      public void doSomething() {
      logger.info("[doSomething]");
      }

      调用该接口,执行日志如下:

      // 首先,按照 HandlerInterceptor 链的**正序**,执行 `#preHandle(...)` 方法。
      2019-11-17 12:31:38.049 INFO 28157 --- [nio-8080-exec-1] c.i.s.l.s.c.i.FirstInterceptor : [preHandle][handler(public void cn.iocoder.springboot.lab23.springmvc.controller.UserController.doSomething())]
      2019-11-17 12:31:38.050 INFO 28157 --- [nio-8080-exec-1] c.i.s.l.s.c.i.ThirdInterceptor : [preHandle][handler(public void cn.iocoder.springboot.lab23.springmvc.controller.UserController.doSomething())]
      2019-11-17 12:31:38.055 INFO 28157 --- [nio-8080-exec-1] c.i.s.l.s.controller.UserController :

      // 然后,执行 `handler` 的逻辑处理。
      [doSomething]

      // 之后,按照 HandlerInterceptor 链的**倒序**,执行 `#postHandle(...)` 方法。
      2019-11-17 12:31:38.109 INFO 28157 --- [nio-8080-exec-1] c.i.s.l.s.c.i.ThirdInterceptor : [postHandle][handler(public void cn.iocoder.springboot.lab23.springmvc.controller.UserController.doSomething())]
      2019-11-17 12:31:38.109 INFO 28157 --- [nio-8080-exec-1] c.i.s.l.s.c.i.FirstInterceptor : [postHandle][handler(public void cn.iocoder.springboot.lab23.springmvc.controller.UserController.doSomething())]

      // 最后,按照 HandlerInterceptor 链的**倒序**,执行 `#afterCompletion(...)` 方法。
      2019-11-17 12:31:38.109 INFO 28157 --- [nio-8080-exec-1] c.i.s.l.s.c.i.ThirdInterceptor : [afterCompletion][handler(public void cn.iocoder.springboot.lab23.springmvc.controller.UserController.doSomething())]
      java.lang.RuntimeException: 故意抛个错误 // ... 省略异常堆栈

      2019-11-17 12:31:38.116 INFO 28157 --- [nio-8080-exec-1] c.i.s.l.s.c.i.FirstInterceptor : [afterCompletion][handler(public void cn.iocoder.springboot.lab23.springmvc.controller.UserController.doSomething())]




       SecondInterceptor  /users/current_user  API 

      ThirdInterceptor  #afterCompletion(...)  FirstInterceptor  #afterCompletion(...) 


       /users/current_user 

      // UserController.java

      @GetMapping("/current_user")
      public UserVO currentUser() {
      logger.info("[currentUser]");
      return new UserVO().setId(10).setUsername(UUID.randomUUID().toString());
      }

      调用该接口,执行日志如下:

      // 首先,按照 HandlerInterceptor 链的**正序**,执行 `#preHandle(...)` 方法。
      2019-11-17 12:48:37.357 INFO 28157 --- [nio-8080-exec-5] c.i.s.l.s.c.i.FirstInterceptor : [preHandle][handler(public cn.iocoder.springboot.lab23.springmvc.vo.UserVO cn.iocoder.springboot.lab23.springmvc.controller.UserController.currentUser())]
      2019-11-17 12:48:37.357 INFO 28157 --- [nio-8080-exec-5] c.i.s.l.s.c.i.SecondInterceptor : [preHandle][handler(public cn.iocoder.springboot.lab23.springmvc.vo.UserVO cn.iocoder.springboot.lab23.springmvc.controller.UserController.currentUser())]

      //【不存在】然后,执行 `handler` 的逻辑处理。

      //【不存在】之后,按照 HandlerInterceptor 链的**倒序**,执行 `#postHandle(...)` 方法。

      // 最后,按照 HandlerInterceptor 链的**倒序**,执行 `#afterCompletion(...)` 方法。
      2019-11-17 12:48:37.358 INFO 28157 --- [nio-8080-exec-5] c.i.s.l.s.c.i.FirstInterceptor : [afterCompletion][handler(public cn.iocoder.springboot.lab23.springmvc.vo.UserVO cn.iocoder.springboot.lab23.springmvc.controller.UserController.currentUser())]


       FirstInterceptor  #preHandle(...)  FirstInterceptor  #afterCompletion(...) 

       handler HandlerInterceptor  #postHandle(...) handler 


       /users/exception-03 

      // UserController.java

      @GetMapping("/exception-03")
      public void exception03() {
      logger.info("[exception03]");
      throw new ServiceException(ServiceExceptionEnum.USER_NOT_FOUND);
      }

      调用该接口,执行日志如下:

      // 首先,按照 HandlerInterceptor 链的**正序**,执行 `#preHandle(...)` 方法。
      2019-11-17 12:54:45.029 INFO 28157 --- [nio-8080-exec-7] c.i.s.l.s.c.i.FirstInterceptor : [preHandle][handler(public void cn.iocoder.springboot.lab23.springmvc.controller.UserController.exception03())]
      2019-11-17 12:54:45.029 INFO 28157 --- [nio-8080-exec-7] c.i.s.l.s.c.i.ThirdInterceptor : [preHandle][handler(public void cn.iocoder.springboot.lab23.springmvc.controller.UserController.exception03())]

      // 然后,执行 `handler` 的逻辑处理。
      2019-11-17 12:54:45.029 INFO 28157 --- [nio-8080-exec-7] c.i.s.l.s.controller.UserController : [exception03]

      //【不存在】之后,按照 HandlerInterceptor 链的**倒序**,执行 `#postHandle(...)` 方法。

      // 最后,按照 HandlerInterceptor 链的**倒序**,执行 `#afterCompletion(...)` 方法。
      2019-11-17 12:54:45.036 INFO 28157 --- [nio-8080-exec-7] c.i.s.l.s.c.i.ThirdInterceptor : [afterCompletion][handler(public void cn.iocoder.springboot.lab23.springmvc.controller.UserController.exception03())]
      2019-11-17 12:54:45.037 ERROR 28157 --- [nio-8080-exec-7] o.s.web.servlet.HandlerExecutionChain : HandlerInterceptor.afterCompletion threw exception
      java.lang.RuntimeException: 故意抛个错误 // ... 省略异常堆栈

      2019-11-17 12:54:45.037 INFO 28157 --- [nio-8080-exec-7] c.i.s.l.s.c.i.FirstInterceptor : [afterCompletion][handler(public void cn.iocoder.springboot.lab23.springmvc.controller.UserController.exception03())]


      handler  Exception HandlerInterceptor  #postHandle(...)  /users/current_user 

      6.4 


       HandlerInterceptor 


      Spring Boot  API 

      访AccessLogInterceptor.java

       + UserSecurityInterceptor.java

       + AdminSecurityInterceptor.java


       HandlerInterceptor 

      7. ServletFilterListener


       SpringMVC 使 java.servlet  ServletFilterListener 使 java.servlet  SpringMVC 

      使 Shiro  Shiro  ShiroFilterFactoryBean 

      使 Java  ServletFilterListener 


       Bean 







       6. HandlerInterceptor   lab-springmvc-23-02 

      7.1  Bean 


       SpringMVCConfiguration  ServletFilterListener  Bean 

      // SpringMVCConfiguration.java

      @Bean
      public ServletRegistrationBean testServlet01() {
      ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean<>(new HttpServlet() {

      @Override
      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      logger.info("[doGet][uri: {}]", req.getRequestURI());
      }

      });
      servletRegistrationBean.setUrlMappings(Collections.singleton("/test/01"));
      return servletRegistrationBean;
      }

      @Bean
      public FilterRegistrationBean testFilter01() {
      FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(new Filter() {

      @Override
      public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
      logger.info("[doFilter]");
      filterChain.doFilter(servletRequest, servletResponse);
      }

      });
      filterRegistrationBean.setUrlPatterns(Collections.singleton("/test/*"));
      return filterRegistrationBean;
      }

      @Bean
      public ServletListenerRegistrationBean<?> testListener01() {
      return new ServletListenerRegistrationBean<>(new ServletContextListener() {

      @Override
      public void contextInitialized(ServletContextEvent sce) {
      logger.info("[contextInitialized]");
      }

      @Override
      public void contextDestroyed(ServletContextEvent sce) {
      }

      });
      }


       Spring Boot  ServletRegistrationBean  Servlet BeanFilterRegistrationBean  Filter BeanServletListenerRegistrationBean  Listener Bean 

      使

      7.2 


       Servlet3.0  @WebServlet@WebFilter@WebListener 便 ServletFilterListener 

       SpringBoot  Application  @ServletComponentScan  @WebServlet@WebFilter@WebListener 使 Web Server 

       cn.iocoder.springboot.lab23.springmvc.core.servlet 

      // TestServlet02.java
      @WebServlet(urlPatterns = "/test/02")
      public class TestServlet02 extends HttpServlet {

      private Logger logger = LoggerFactory.getLogger(getClass());

      @Override
      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      logger.info("[doGet][uri: {}]", req.getRequestURI());
      }

      }

      // TestFilter02.java
      @WebFilter("/test/*")
      public class TestFilter02 implements Filter {

      private Logger logger = LoggerFactory.getLogger(getClass());

      @Override
      public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
      logger.info("[doFilter]");
      filterChain.doFilter(servletRequest, servletResponse);
      }

      }

      // TestServletContextListener02.java
      @WebListener
      public class TestServletContextListener02 implements ServletContextListener {

      private Logger logger = LoggerFactory.getLogger(getClass());

      @Override
      public void contextInitialized(ServletContextEvent sce) {
      logger.info("[contextInitialized]");
      }

      @Override
      public void contextDestroyed(ServletContextEvent sce) {
      }

      }

      8. Cors 跨域


      lab-springmvc-23-02 


       http://www.iocoder.cn  API  http://api.iocoder.cn 

        CORS  😈 

       Nginx  Spring Boot SpringMVC 使 SpringMVC 


      使 @CrossCors  API 

      使 CorsRegistry.java  API 

      使 CorsFilter.java 


       CorsInterceptor 


       7. ServletFilterListener  lab-springmvc-23-02 

      8.1 @CrossCors


      @CrossCors / Cors 

      @CrossCors 


      origins []  * 

      value  origins 

      allowCredentials  Cookie  false  Cookie 

      maxAge  1800 


      @CrossCors 


      methods []  GET + POST 

      allowedHeaders  Header []  * 

      exposedHeaders  Header []  * 


       Controller  @CrossCors  API  Method 

      // TestController.java

      @RestController
      @RequestMapping("/test")
      @CrossOrigin(origins = "*", allowCredentials = "true") // 允许所有来源,允许发送 Cookie
      public class TestController {

      /**
      * 获得指定用户编号的用户
      *
      * @return 用户
      */
      @GetMapping("/get")
      @CrossOrigin(allowCredentials = "false") // 允许所有来源,不允许发送 Cookie
      public UserVO get() {
      return new UserVO().setId(1).setUsername(UUID.randomUUID().toString());
      }

      }


       Controller  @CrossOrigin(allowCredentials = "true") 



      使 CORS  Vue  axios  OPTIONS  SpringMVC 

       handler  HandlerMethod  UserSecurityInterceptor  handler  HandlerMethod  @RequiresLogin  handler  PreFlightHandler getCorsHandlerExecutionChain



      1 handler  HandlerMethod 

      2使 8.3 CorsFilter  OPTIONS 


      1  2  8.3 CorsFilter 

      😈  SpringMVC Cors  

      8.2 CorsRegistry


       Controller  @CrossOrigin  CorsRegistry 

       SpringMVCConfiguration  CorsRegistry 

      // SpringMVCConfiguration.java

      @Override
      public void addCorsMappings(CorsRegistry registry) {
      // 添加全局的 CORS 配置
      registry.addMapping("/**") // 匹配所有 URL ,相当于全局配置
      .allowedOrigins("*") // 允许所有请求来源
      .allowCredentials(true) // 允许发送 Cookie
      .allowedMethods("*") // 允许所有请求 Method
      .allowedHeaders("*") // 允许所有请求 Header
      // .exposedHeaders("*") // 允许所有响应 Header
      .maxAge(1800L); // 有效期 1800 秒,2 小时
      }


       /**  CORS 

       CORS  CorsRegistry#addMapping(String pathPattern)  CORS 

       originns 


       8.1 @CrossCors  8.3 CorsFilter 

      8.3 CorsFilter


       Spring Web  CorsFilter  CORS 

       Filter  7.1  Bean   SpringMVCConfiguration  CorsFilter 

      // SpringMVCConfiguration.java

      @Bean
      public FilterRegistrationBean<CorsFilter> corsFilter() {
      // 创建 UrlBasedCorsConfigurationSource 配置源,类似 CorsRegistry 注册表
      UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
      // 创建 CorsConfiguration 配置,相当于 CorsRegistration 注册信息
      CorsConfiguration config = new CorsConfiguration();
      config.setAllowedOrigins(Collections.singletonList("*")); // 允许所有请求来源
      config.setAllowCredentials(true); // 允许发送 Cookie
      config.addAllowedMethod("*"); // 允许所有请求 Method
      config.setAllowedHeaders(Collections.singletonList("*")); // 允许所有请求 Header
      // config.setExposedHeaders(Collections.singletonList("*")); // 允许所有响应 Header
      config.setMaxAge(1800L); // 有效期 1800 秒,2 小时
      source.registerCorsConfiguration("/**", config);
      // 创建 FilterRegistrationBean 对象
      FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(
      new CorsFilter(source)); // 创建 CorsFilter 过滤器
      bean.setOrder(0); // 设置 order 排序。这个顺序很重要哦,为避免麻烦请设置在最前
      return bean;
      }

      • 艿艿已经添加了详细的注释,胖友自己看下噢。效果上,和 「8.2 CorsRegistry」 是一致的。

       SpringMVC  CORS **使 8.3 CorsFilter **😈  Spring  CORS  

      9. HttpMessageConverter 



      lab-springmvc-23-02 


       Spring MVC 使 @RequestBody  @ResponseBody **** Spring 3.x  HttpMessageConverter 

      😜  UserVO  JSON 使 MappingJackson2HttpMessageConverter  UserVO  JSON 😈 

       API 使 JSON  API 使 MappingJackson2HttpMessageConverter  JSON 😈 !

       HttpMessageConverter 

      // HttpMessageConverter.java

      // 是否能够读取指定的 mediaType 内容类型,转换成对应的 clazz 对象
      boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
      // 读取请求内容,转换成 clazz 对象
      T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
      throws IOException, HttpMessageNotReadableException;

      // 是否能够将 clazz 对象,序列化成 mediaType 内容类型
      boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
      // 将 clazz 对象,序列化成 contentType 内容类型,写入到响应。
      void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
      throws IOException, HttpMessageNotWritableException;

      // 获得 HttpMessageConverter 能够支持的内容类型。
      List<MediaType> getSupportedMediaTypes();


       Content-Type Request BodySpringMVC  HttpMessageConverter  #canRead(clazz, mediaType)  mediaType  clazz  #read(Class<? extends T> clazz, HttpInputMessage inputMessage)  clazz 

       Accept Response BodySpringMVC  HttpMessageConverter  #canWrite(clazz, mediaType)  clazz  mediaType  #write(contentType, outputMessage)   clazz  contentType 


       JSON/XML  JSON/XML 


       8. Cors   lab-springmvc-23-02 

      9.1 


       pom.xml  jackson-dataformat-xml 

      <!-- 引入 jackson 对 xml 的转换器,实现对 XML 的序列化 -->
      <dependency>
      <groupId>com.fasterxml.jackson.dataformat</groupId>
      <artifactId>jackson-dataformat-xml</artifactId>
      </dependency>

      9.2 SpringMVCConfiguration

      修改 SpringMVCConfiguration 配置类,增加 MappingJackson2XmlHttpMessageConverter 相关的配置,用于 XML 格式的 HttpMessageConverter 消息转换器。代码如下:

      // SpringMVCConfiguration.java

      @Override
      public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
      // 增加 XML 消息转换器
      Jackson2ObjectMapperBuilder xmlBuilder = Jackson2ObjectMapperBuilder.xml();
      xmlBuilder.indentOutput(true);
      converters.add(new MappingJackson2XmlHttpMessageConverter(xmlBuilder.build()));
      }

      9.3 UserController

      UserController 类中,我们添加一个 API 接口,新增用户,方便我们 XML/JSON 格式的请求和响应。代码如下:

      // UserController.java

      @PostMapping(value = "/add",
      // ↓ 增加 "application/xml"、"application/json" ,针对 Content-Type 请求头
      consumes = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE},
      // ↓ 增加 "application/xml"、"application/json" ,针对 Accept 请求头
      produces = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}
      )
      public UserVO add(@RequestBody UserVO user) {
      return user;
      }




       @RequestBody 

       consumes  "application/xml""application/json"  Content-Type 





       UserController  @RestController  API  @ReponseBody 

       produces  "application/xml""application/json"  Accept 




      使 Postman 

       JSON JSON 

      JSON + JSON
       XML XML 

      XML + XML
       JSON XML 

      JSON + XML

      10.  Fastjson



      lab-springmvc-23-02 


        Spring  Fastjson

      使 Fastjson  JSON  JSON  Fastjson  FastJsonHttpMessageConverter 便 SpringMVC  HttpMessageConverter 


       8. Cors   lab-springmvc-23-02 

      10.1 


       pom.xml  fastjson 

      <!-- 引入 Fastjson ,实现对 JSON 的序列化 -->
      <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.62</version>
      </dependency>

      10.2 SpringMVCConfiguration

      修改 SpringMVCConfiguration 配置类,增加 FastJsonHttpMessageConverter 相关的配置。代码如下:

      // SpringMVCConfiguration.java

      @Override
      public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
      // 创建 FastJsonHttpMessageConverter 对象
      FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
      // 自定义 FastJson 配置
      FastJsonConfig fastJsonConfig = new FastJsonConfig();
      fastJsonConfig.setCharset(Charset.defaultCharset()); // 设置字符集
      fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect); // 剔除循环引用
      fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
      // 设置支持的 MediaType
      fastJsonHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON,
      MediaType.APPLICATION_JSON_UTF8));
      // 添加到 converters 中
      converters.add(0, fastJsonHttpMessageConverter); // 注意,添加到最开头,放在 MappingJackson2XmlHttpMessageConverter 前面
      }


      SpringMVC  Fastjson 便

      666. 


      2354  Joker (2019) 

       API  API 


        API 

         API 

         API 

        API 

        API 



文章目录
  1. 1. 1. 概述
  2. 2. 2. 快速入门
    1. 2.1. 2.1 注解
      1. 2.1.1. 2.1.1 @Controller
      2. 2.1.2. 2.1.2 @RequestMapping
      3. 2.1.3. 2.1.3 @RequestParam
    2. 2.2. 2.2 引入依赖
    3. 2.3. 2.3 Application
    4. 2.4. 2.4 UserController
    5. 2.5. 2.5 UserController2
  3. 3. 3. 测试接口
    1. 3.1. 3.1 集成测试
    2. 3.2. 3.2 单元测试
  4. 4. 4. 全局统一返回
    1. 4.1. 4.1 引入依赖
    2. 4.2. 4.2 Application
    3. 4.3. 4.3 CommonResult
    4. 4.4. 4.4 GlobalResponseBodyHandler
    5. 4.5. 4.5 UserController
    6. 4.6. 4.6 小小的讨论
  5. 5. 5. 全局异常处理
    1. 5.1. 5.1 ServiceExceptionEnum
    2. 5.2. 5.2 ServiceException
    3. 5.3. 5.3 GlobalExceptionHandler
    4. 5.4. 5.4 UserController
  6. 6. 6. HandlerInterceptor 拦截器
    1. 6.1. 6.1 自定义 HandlerInterceptor
    2. 6.2. 6.2 SpringMVCConfiguration
    3. 6.3. 6.3 UserController
    4. 6.4. 6.4 拓展阅读
  7. 7. 7. Servlet、Filter、Listener
    1. 7.1. 7.1 通过 Bean 的方式
    2. 7.2. 7.2 通过注解的方式
  8. 8. 8. Cors 跨域
    1. 8.1. 8.1 @CrossCors
    2. 8.2. 8.2 CorsRegistry
    3. 8.3. 8.3 CorsFilter
  9. 9. 9. HttpMessageConverter 消息转换器
    1. 9.1. 9.1 引入依赖
    2. 9.2. 9.2 SpringMVCConfiguration
    3. 9.3. 9.3 UserController
  10. 10. 10. 整合 Fastjson
    1. 10.1. 10.1 引入依赖
    2. 10.2. 10.2 SpringMVCConfiguration
  11. 11. 666. 彩蛋