• Spring 全家桶 第六章:Spring MVC 实践


    39 I 编写第一个 Spring MVC Controller

             认识 Spring MVC

    DispatcherServlet

    • Controller
    • xxxResolver
        
           ViewResolver

           HandlerExceptionResolver

           MultiparResolver
       
    • HandlerMapping

            Spring MVC 中的常用注解

    • @Controller
    • @RequestMapping
       
           @GetMapping / @PostMapping

           @PutMapping / @DeleteMapping
       
    • @RequestBoyd / @ResponseBoyd / @ResponseStatus

    40 I 理解 Spring的应用上下文

                  Spring 的应用程序上下文

    关于上下文常用的接口及其实现  

    • BeanFactory

          DefaultListableBeanFactory
      ​​​​
    • ApplicationContext

          ClassPathXmlApplicationContext

          FilesSystemXmlApplicationContext

          AnnotationConfigApplicationContext
       
    • WebApplicationContext
    1. @SpringBootApplication
    2. @Slf4j
    3. public class ContextHierarchyDemoApplication implements ApplicationRunner {
    4. public static void main(String[] args) {
    5. SpringApplication.run(ContextHierarchyDemoApplication.class, args);
    6. }
    7. @Override
    8. public void run(ApplicationArguments args) throws Exception {
    9. ApplicationContext fooContext = new AnnotationConfigApplicationContext(FooConfig.class);
    10. ClassPathXmlApplicationContext barContext = new ClassPathXmlApplicationContext(
    11. new String[] {"applicationContext.xml"}, fooContext);
    12. TestBean bean = fooContext.getBean("testBeanX", TestBean.class);
    13. bean.hello();
    14. log.info("=============");
    15. bean = barContext.getBean("testBeanX", TestBean.class);
    16. bean.hello();
    17. bean = barContext.getBean("testBeanY", TestBean.class);
    18. bean.hello();
    19. }
    20. }

          FooConfig.java:父上下文(parent application context)。
          applicationContext.xml:子上下文(child application context)。
          FooConfig.java 中定义两个 testBean,分别为 testBeanX(foo) 和 testBeanY(foo)。 applicationContext.xml 定义了一个 testBeanX(bar)。
         委托机制:在自己的 context 中找不到 bean,会委托父 context 查找该 bean。 ---------- 代码解释: fooContext.getBean("testBeanX"),在父上下文查找 testBeanX,命中直接返回 testBeanX(foo)。 barContext.getBean("testBeanX"),在子上下文查找 testBeanX,命中直接返回 testBeanX(bar)。 barContext.getBean("testBeanY"),在子上下文查找 testBeanY,未命中;委托父上下文查找,命中,返回 testBeanY(foo)。
           ---------- 场景一: 父上下文开启 @EnableAspectJAutoProxy 的支持 子上下文未开启 <aop: aspectj-autoproxy /> 切面 fooAspect 在 FooConfig.java 定义(父上下文增强) 输出结果: testBeanX(foo) 和 testBeanY(foo) 均被增强。 testBeanX(bar) 未被增强。 结论: 在父上下文开启了增强,父的 bean 均被增强,而子的 bean 未被增强。
          ---------- 场景二: 父上下文开启 @EnableAspectJAutoProxy 的支持 子上下文开启 <aop: aspectj-autoproxy /> 切面 fooAspect 在 applicationContext.xml 定义(子上下文增加) 输出结果: testBeanX(foo) 和 testBeanY(foo) 未被增强。 testBeanX(bar) 被增强。

          结论: 在子上下文开启增强,父的 bean 未被增强,子的 bean 被增强。 ---------- 根据场景一和场景二的结果,有结论:“各个 context 相互独立,每个 context 的 aop 增强只对本 context 的 bean 生效”。如果想将切面配置成通用的,对父和子上下文的 bean 均支持增强,则: 1. 切面 fooAspect 定义在父上下文。 2. 父上下文和子上下文,均要开启 aop 的增加,即 @EnableAspectJAutoProxy 或<aop: aspectj-autoproxy /> 的支持。

    41 I 理解请求的处理机制

            Spring MVC的请求处理流程

                     一个请求的大致处理流程

    绑定一些 Attribute

    • WebApplicationContext / LocaleResolver / ThemeResolver

    处理 Mulitipart

    • 如果是,则将请求转为 MutipartHttpServletRequest

    Hadnler 处理

    • 如果找到对应 Handler,执行 Controller 及前后置处理器逻辑

    处理返回的 Model,呈现视图 

    类的继承关系:

    1. public class DispatcherServlet extends FrameworkServlet
    2. |
    3. public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware
    4. |
    5. public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware
    6. |
    7. public abstract class javax.servlet.http.HttpServlet extends GenericServlet
    8. |
    9. public abstract class javax.servlet.GenericServlet implements Servlet, ServletConfig,
    10. java.io.Serializable
    11. |
    12. public interface javax.servlet.Servlet

    代码处理流程:

    1. DispatcherServlet.java: doService()开始处理请求;
    2. DispatcherServlet.java: getHandler() 遍历几种handler
    3. AbstractHandlerMapping.java: getHandler() 获取处理类的实例
    4. AbstractHandlerMapping.java: getHandler() 检查是否为跨域请求(检查代码: request.getHeader("Origin") != null;)
    5. AbstractHandlerMethodMapping.java: lookupHandlerMethod()->实际是在addMatchingMappings()方法里获取请求url对应的Controller全限定类路径及返回参数及全限定方法名. (例如(因代码过长,所以折行.): public java.util.List<xyz.suancaiyu.complexcontrollerdemo.model.Coffee> xyz.suancaiyu.complexcontrollerdemo.controller.CoffeeController.getAll() )
    6. DispatcherServlet.java: doService()实际调用处理程序:mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    7. DispatcherServlet.java: doService()处理完成,并返回. 

    42 I 如何定义处理方法 (上)

                    定义映射关系

    @Controller

    @RequestMapping

    • path / method 指定映射路径与方法
    • params / headers 限定映射范围
    • consumers / produces 限定请求与响应格式

    一些快捷方式

    • @RestController
    • @GetMapping / @PostMapping / @PutMapping / @DeleteMapping / @PatchMapping

             定义处理方法

    • @RequestBody / @ResponseBody / @ResponseStatus
    • @PathVariable / @RequestParam / @RequestHeader
    • HttpEntity / ResponseEntity
    1. @Controller
    2. @RequestMapping("/coffee")
    3. public class CoffeeController {
    4. @Autowired
    5. private CoffeeService coffeeService;
    6. @GetMapping(path = "/", params = "!name")
    7. @ResponseBody
    8. public List<Coffee> getAll() {
    9. return coffeeService.getAllCoffee();
    10. }
    11. @RequestMapping(path = "/{id}", method = RequestMethod.GET,
    12. produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    13. @ResponseBody
    14. public Coffee getById(@PathVariable Long id) {
    15. Coffee coffee = coffeeService.getCoffee(id);
    16. return coffee;
    17. }
    18. @GetMapping(path = "/", params = "name")
    19. @ResponseBody
    20. public Coffee getByName(@RequestParam String name) {
    21. return coffeeService.getCoffee(name);
    22. }
    23. }

    43 I 如何定义处理方法 (下)

                            定义类型转换

    自己实现 WebMvcConfigurer

    • Spring Boot  在 WebMvcAutoConfiguration 中实现了一个
    • 添加自定义的 Converter
    • 添加自定义的 Formatter
    1. @Component
    2. public class MoneyFormatter implements Formatter<Money> {
    3. /**
    4. * 处理 CNY 10.00 / 10.00 形式的字符串
    5. * 校验不太严密,仅作演示
    6. */
    7. @Override
    8. public Money parse(String text, Locale locale) throws ParseException {
    9. if (NumberUtils.isParsable(text)) {
    10. return Money.of(CurrencyUnit.of("CNY"), NumberUtils.createBigDecimal(text));
    11. } else if (StringUtils.isNotEmpty(text)) {
    12. String[] split = StringUtils.split(text, " ");
    13. if (split != null && split.length == 2 && NumberUtils.isParsable(split[1])) {
    14. return Money.of(CurrencyUnit.of(split[0]),
    15. NumberUtils.createBigDecimal(split[1]));
    16. } else {
    17. throw new ParseException(text, 0);
    18. }
    19. }
    20. throw new ParseException(text, 0);
    21. }
    22. @Override
    23. public String print(Money money, Locale locale) {
    24. if (money == null) {
    25. return null;
    26. }
    27. return money.getCurrencyUnit().getCode() + " " + money.getAmount();
    28. }
    29. }

                      定义校验

    • 通过 Validator 对绑定结果进行校验

             Hibernate Validator
    • @Valid 注解
    • BindingResult
    1. @Getter
    2. @Setter
    3. @ToString
    4. public class NewCoffeeRequest {
    5. @NotEmpty
    6. private String name;
    7. @NotNull
    8. private Money price;
    9. }
    10. @PostMapping(path = "/", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    11. @ResponseBody
    12. @ResponseStatus(HttpStatus.CREATED)
    13. public Coffee addCoffee(@Valid NewCoffeeRequest newCoffee,
    14. BindingResult result) {
    15. if (result.hasErrors()) {
    16. // 这里先简单处理一下,后续讲到异常处理时会改
    17. log.warn("Binding Errors: {}", result);
    18. return null;
    19. }
    20. return coffeeService.saveCoffee(newCoffee.getName(), newCoffee.getPrice());
    21. }

                 Multipart 上传

    • 配置 MultipartResolver

          Spring Boot 自动配置 MultipartAutoConfiguration
    • 支持类型 multipart/form-data
    • MultipartFile 类型        
    1. @PostMapping(path = "/", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    2. @ResponseBody
    3. @ResponseStatus(HttpStatus.CREATED)
    4. public List<Coffee> batchAddCoffee(@RequestParam("file") MultipartFile file) {
    5. List<Coffee> coffees = new ArrayList<>();
    6. if (!file.isEmpty()) {
    7. BufferedReader reader = null;
    8. try {
    9. reader = new BufferedReader(
    10. new InputStreamReader(file.getInputStream()));
    11. String str;
    12. while ((str = reader.readLine()) != null) {
    13. String[] arr = StringUtils.split(str, " ");
    14. if (arr != null && arr.length == 2) {
    15. coffees.add(coffeeService.saveCoffee(arr[0],
    16. Money.of(CurrencyUnit.of("CNY"),
    17. NumberUtils.createBigDecimal(arr[1]))));
    18. }
    19. }
    20. } catch (IOException e) {
    21. log.error("exception", e);
    22. } finally {
    23. IOUtils.closeQuietly(reader);
    24. }
    25. }
    26. return coffees;
    27. }

    44 I Spring MVC中的视图解析机制(上)

                                视图解析的实现基础

    ViewResolver 与 View 接口

    • AbstractCachingViewResolver
    • UrlBasedViewResolver
    • FreeMarkerViewResolver
    • ContentNegotiatingViewResolver
    • InternalResourceViewResolver

              DispatcherServlet 中的视图解析逻辑

    • initStrategies()

             initViewResolvers() 初始化了对应 ViewResolver
    • doDispatch()

             processDispatchResult()
       
                   没有返回视图的话,尝试 RequestToViewNameTranslator
       
                   resolveViewName() 解析 View 对象

    45 I Spring MVC中的视图解析机制(下)

                             DispatcherServlet 中的视图解析逻辑

    使用 @ResponseBoyd 的情况

    • 在 HandlerAdapter.handle() 的中完成了 Response 输出

          RequestMappingHandleAdapter.invokeHandlerMethod()

                    HandlerMethodReturnValueHandlerComposite.handleReturnValue()

                               RequestResponseBodyMethodProcessor.handReturnValue()

                           重定向

    两种不同的重定向逻辑

    • redirect: 又叫重定向,当请求发给A服务时,服务A返回重定向给客户端,客户端再去请求B服务。
    • forward: 又叫转发,当请求来到时,可以将请求转发到其他的指定服务,用户端不知晓。

    46 I Spring MVC中的常用视图 (上)

                            Spring MVC 支持的视图

    支持的视图列表

                            Spring Boot 对 Jackson的支持

    • JacksonAutoConfiguration

               Spring Boot 通过 @JsonComponent 注册 JSON 序列化组件

                Jackson2ObjectMapperBuilderCustomizer
    • JacksonHttpMessageConvertersConfiguration

            增加 jackson-dataformat-xml 以支持 xml 序列化

    47 I Spring MVC中的常用视图 (下)

    48 I 静态资源与缓存

    49 I Spring MVC中的异常处理机制

    50 I 了解Spring MVC的切入点

    51 I Spring Bucks 实战项目进度小结

    52 I 课程答疑

  • 相关阅读:
    面试:dex文件结构
    【观察】软通动力:以数智化技术创新,赋能每一个降碳场景
    高并发缓存策略大揭秘:面试必备的缓存更新模式解析
    深度学习的数学基础
    <C++>类和对象-中
    FS4067 SOP8 5V输入两节锂电池升压型充电管理芯片
    Python读取NC格式数据绘制风场和涡度图
    自动化测试(Java+eclipse)教程
    【Spring源码】14. 消息多播器(观察者模式)
    基于JAVA的电子书城系统(Web)
  • 原文地址:https://blog.csdn.net/qq_15700115/article/details/125485029