SpringMVC 学习笔记

打印 上一主题 下一主题

主题 838|帖子 838|积分 2524

概述

SpringMVC 中的 MVC 即模型-视图-控制器,该框架围绕一个 DispatcherServlet 改计而成,DispatcherServlet 会把请求分发给各个处理器,并支持可配置的处理器映射和视图渲染等功能
SpringMVC 的工作流程如下所示:


  • 客户端发起 HTTP 请求:客户端将请求提交到 DispatcherServlet
  • 寻找处理器:DispatcherServlet 控制器查询一个或多个 HandlerMapping,找随处理该请求的 Controller
  • 调用处理器:DispatcherServlet 将请求提交到 Controller
  • 调用业务处理逻辑并返回效果:Controller 在调用业务处理逻辑后,返回 ModelAndView
  • 处理视图映射并返回模型:DispatcherServlet 查询一个或多个 ViewResolver 视图解析器,找到 ModelAndView 指定的视图
  • HTTP 响应:视图负责将效果在客户端欣赏器上谊染和展示

DispatcherServlet

在 Java 中可以利用 Servlet 来处理请求,客户端每次发出请求,Servlet 会调用 service 方法来处理,SpringMVC 通过创建 DispatchServlet 来统一接收请求并分发处理
1. 创建 DispatcherServlet

在 Tomcat 中创建 DispatcherServlet 的方式有两种:
第一种方式是通过 web.xml,Tomcat 会在启动时加载根路径下 /WEB-INF/web.xml 配置文件,根据此中的配置加载 Servlet,Listener,Filter 等,下面是 SpringMVC 的常见配置:
  1. <servlet>
  2.     <servlet-name>dispatcher</servlet>
  3.     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  4.     <init-param>
  5.         
  6.         <param-name>contextConfigLocation</param-name>
  7.         <param-value>/WEB-INF/applicationContext.xml</param-value>
  8.         
  9.         <load-on-startup>1</load-on-startup>
  10.     </init-param>
  11. </servlet>
  12. <servlet-mapping>
  13.     <servlet-name>dispatch</servlet-name>
  14.     <servlet-pattern>/*</servlet-pattern>
  15. </servlet-mapping>
复制代码
第二种方式是通过 WebApplicationInitializer,简单来说就是 Tomcat 会探测并加载 ServletContainerInitalizer 的实现类,并调用他的 onStartup 方法,而 SpringMVC 提供了对应的实现类 SpringServletContainerInitializer。而 SpringServletContainerInitializer 又会探测并加载 ClassPath 下 WebApplicationContextInitializer 的实现类,调用它的 onStartUp 方法
因此我们可以继承 WebApplicationContextInitializer 实现 onStartUp 方法,在此中以代码的方式配置 DispatchServlet
  1. public class MyWebAppInitializer implements WebApplicationInitializer {
  2.     @Override
  3.     public void onStartup(ServletContext container) {
  4.         // 创建 dispatcher 持有的上下文容器
  5.         AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
  6.         dispatcherContext.register(DispatcherConfig.class);
  7.         // 注册、配置 dispatcher servlet
  8.         ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
  9.         dispatcher.setLoadOnStartup(1);
  10.         dispatcher.addMapping("/*");
  11.     }
  12. }
复制代码
在创建 DispatcherServlet 时,其内部会创建一个 Spring 容器 WebApplicationContext,目的是通过 Bean 的方式管理 Web 应用中的对象
2. DispatcherServlet 初始化

DispatcherServlet 是 Servlet 的实现类,Servlet的生命周期分为三个阶段:初始化、运行和销毁。初始化阶段会调用 init() 方法,DispatcherServlet 经过一系列封装,最终会调用 initStrategies 方法举行初始化,在这里我们重点关注 initHandlerMappings 和 initHandlerAdapters
  1. protected void initStrategies(ApplicationContext context) {
  2.     initMultipartResolver(context);
  3.     initLocaleResolver(context);
  4.     initThemeResolver(context);
  5.     initHandlerMappings(context);
  6.     initHandlerAdapters(context);
  7.     initHandlerExceptionResolvers(context);
  8.     initRequestToViewNameTranslator(context);
  9.     initViewResolvers(context);
  10.     initFlashMapManager(context);
  11. }
复制代码
initHandlerMappings 方法负责加载 HandlerMappings 也就是处理器映射器,如果步伐员没有配置,那么 SpringMVC 也有默认提供的 HandlerMapping。每个 HandlerMapping 会以 Bean 的形式保持在容器,并执行各自的初始化方法。
默认的 HandlerMapping 有以下两种:

  • RequestMappingHandlerMapping:根据请求 URL 映射到对应 @RequestMapping 方法
  • BeanNameUrlHandlerMapping:根据请求 URL 映射到对应的 Bean 的名称(如该 Bean 的名称为 /test),这个 Bean 会提供一个处理请求逻辑的方法
RequestMappingHandlerMapping 在初始化的过程中会从处理器 bean(即被 @Controller 注解)中找出所有的处理方法(即被 @RequestMapping 注解),把处理方法的 @RequestMapping 注解解析成 RequestMappingInfo 对象,再把处理方法对象包装成 HandlerMethod 对象。然后把 RequestMappingInfo 和 HandlerMethod 对象以 map 的形式缓存起来,key 为 RequestMappingInfo,value 为 HandlerMethod,日后将请求映射随处理器时会利用到
BeanNameUrlHandlerMapping 在初始化的过程中会扫描 Spring 容器中所有的 bean,获取每个 bean 的名称以及对应的 Bean 保持起来。将每个 bean 的名称与请求的 URL  路径举行匹配,如果 bean 的名称与 URL 路径匹配(忽略大小写),那么就以匹配的 Bean 作为处理该请求的处理器。匹配 Bean 的实现如下:
  1. @Componet("/welcome*")
  2. public class WelcomeController implements Controller {
  3.     @Override
  4.     public void handleRequest(HttpServletRequest request, HttpServletResponse response)   {
  5.         ...
  6.     }
  7. }
复制代码
或者
  1. @Componet("/welcome*")
  2. public class WelcomeController implements HttpRequestHandler {
  3.     @Override
  4.     public void handleRequest(HttpServletRequest request, HttpServletResponse response)   {
  5.         ...
  6.     }
  7. }
复制代码
initHandlerAdapters 方法负责加载适配器,同样以 Bean 的形式保持在容器并执行初始化方法。如果步伐员没有配置,那么 SpringMVC 也有默认提供的 HandlerAdapter。处理请求时,根据请求找到对应的处理器对象后,就会适配得到一个 HandlerAdapter,由 HandlerAdapter 执行处请求
SpringMVC 默认的适配器有:

  • RequestMappingHandlerAdapter:适配处理器是 HandlerMethod 对象
  • HandlerFunctionAdapter:适配处理器是HandlerFunction对象
  • HttpRequestHandlerAdapter:适配处理器是 HttpRequestHandler 对象
  • SimpleControerHandlerAdapter:适配处理器是 Controller 对象

父子容器

前面提到过,初始化 DispatcherServlet 时其内部会跟着创建一个 Spring 容器,那如果在 web.xml 中配置了两个差别的 DispatcherServlet,那么就会有两个分属差别 DispatcherServlet 的 Spring 容器
  1. <servlet>
  2.     <servlet-name>app1</servlet>
  3.     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  4.     <init-param>
  5.         <param-name>contextConfigLocation</param-name>
  6.         <param-value>/WEB-INF/spring1.xml</param-value>
  7.         <load-on-startup>1</load-on-startup>
  8.     </init-param>
  9. </servlet>
  10. <servlet-mapping>
  11.     <servlet-name>app1</servlet-name>
  12.     <servlet-pattern>/app1/*</servlet-pattern>
  13. </servlet-mapping>
  14. <servlet>
  15.     <servlet-name>app2</servlet>
  16.     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  17.     <init-param>
  18.         <param-name>contextConfigLocation</param-name>
  19.         <param-value>/WEB-INF/spring2.xml</param-value>
  20.         <load-on-startup>1</load-on-startup>
  21.     </init-param>
  22. </servlet>
  23. <servlet-mapping>
  24.     <servlet-name>app2</servlet-name>
  25.     <servlet-pattern>/app2/*</servlet-pattern>
  26. </servlet-mapping>
复制代码
出现多个 DispatcherServlet 一般是办理多版本的题目,比如有一个 TestV1Controller 在 app1 这个 DispatcherServlet,如今多了一个升级版 TestV2Controller,就可以放在 app2,利用差别的映射路径
而偶然候我们只希望区分差别的 Controller,而通用的 Service 并不需要在每个容器都生存一份,就可以配置父容器,将 Service 放在父容器。DispatcherServlet 初始化时会自动寻找是否存在父容器。
  1. <web-app>
  2.     <listener>
  3.         <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  4.     </listener>
  5.     <context-param>
  6.         <param-name>contextConfigLocation</param-name>
  7.         <param-value>/WEB-INF/root-spring.xml</param-value>
  8.     </context-param>
  9.     <servlet>
  10.         <servlet-name>app1</servlet-name>
  11.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  12.         <init-param>
  13.             <param-name>contextConfigLocation</param-name>
  14.             <param-value>/WEB-INF/spring1.xml</param-value>
  15.         </init-param>
  16.         <load-on-startup>1</load-on-startup>
  17.     </servlet>
  18.     <servlet-mapping>
  19.         <servlet-name>app1</servlet-name>
  20.         <url-pattern>/app1/*</url-pattern>
  21.     </servlet-mapping>
  22. </web-app>
复制代码
ContextLoaderListener 被配置到监听器列表,ServletContext 初始化时会利用 context-param 中参数名为 contextConfigLocation 设置的配置文件初始化父容器

SpringMVC 处理请求

SpringMVC 处理请求流程可分如下步调:

  • 根据路径找到对应的 Handler
  • 解析参数并绑定
  • 执行方法
  • 解析返回值
1. 根据请求寻找 Handler

请求到来会执行 DispatcherServlet 的 getHandler 方法。遍历所有 HanlderMapping,每个 HandlerMapping 都是根据请求寻找 Handler,但寻找的方式不一样,比如 RequestMappingHandlerMapping 就是根据请求路径寻找 HandlerMethod, BeanNameUrlHandlerMapping 则是将请求路径映射到对应的 Bean 的名称。通过遍历 HandlerMapping,直到请求能找到对应的 Handler
差别的 HanlderMapping 所对应的 Handler 范例也差别,因此要找到对应范例的适配器。遍历所有 HandlerAdapter,如果找对适配的 HandlerAdapter 就返回,执行适配器的 handle 方法
2. 解析参数并执行方法

以 RequestMappingHandlerMapping 为例,Handler 的实际范例是 HandlerMethod,适配的是 RequestMappingHandlerAdapter。执行 invokeHandlerMethod 方法,解析 @initBinder 注解的方法并生存,解析 @SessionAttributes 注解设置的键值对,解析 @ModelAttribute 注解的方法,上述解析的效果将生存在 ModelFactory 对象,ModelFactory 用来初始化 Model 对象,初始化时将 @SessionAttributes 和 @ModelAttribute 设置的值生存到 Model 对象
接下来是创建参数解析器 argumentResolvers 和返回值解析器 returnValueHandlers。解析器有多种范例,对应差别的场景,例如利用 @PathVariable 注解传参就利用 PathVariableMethodArgumentResolver 解析器对象,返回值是 ModelAndView 对象则用 ModelAndViewMethodReturnValueHandler 解析器对象
获取方法参数,方法参数的范例是 MethodParameter,不仅包含了参数的名称,还包括参数的信息,比如是否有 @ReqeustParam 注解。遍历方法参数,并逐一用参数解析器遍历,找到适用的解析器举行解析,再根据参数名称从请求中获取参数值。如果定义了范例转换器,那就对参数范例举行转换。末了利用反射执行真正的方法逻辑
3. 解析返回值

拿到返回值后也是遍历寻找合适的返回值解析器举行处理,比如开发中经常会利用 @ResponseBody 注解返回 json,就会利用 RequestResponseBodyMethodProcessor 处理器举行处理,该处理器同时还承担了参数解析的作用。解析的过程中需要用到消息转换器 HttpMessageConverter,其作用是将方法的返回值转换为接收端(如欣赏器)能接受的响应范例,SpringMVC 同样提供了默认的转换器。比如利用 @ResponseBody 注解的方法返回了 String 范例的返回值,那么就会遍历判断哪个消息转换器能处理 String 范例的返回值,在 RequestResponseBodyMethodProcessor 处理器中默认利用 StringHttpMessageConverter。接下来是内容协商,即是找到客户端能接受并且服务端能提供的内容范例,比如客户端希望优先返回 text/plain 范例的内容,而 StringHttpMessageConverter 能支持该范例,那么就利用 StringHttpMessageConverter 将方法返回值写入响应报文返回给客户端。如果我们希望方法直接返回对象范例并自动序列化为 json,那么就需要自定义消息转换器,此时 SpringMVC 将不再提供默认的转换器而是直接利用自定义的转换器,比如引入 MappingJackson2HttpMessageConverter 便能支持对象范例返回值转换为 json 并返回给客户端
如果不利用 @ResponseBody 注解,那么就会利用 ModelAndView 生存视图路径和数据。SpringMVC 同样提供了默认的视图解析器 ViewResolver,它会根据方法返回的 url 在 tomcat内部(不经过 DispatcherServlet 转发,而是利用原生 Servlet)举行一次转发请求到对应的视图文件如 jsp。如果 url 带有前缀 forward: 就表示这是一次转发请求,比如 forward:/app/test,SpringMVC 会去掉该前缀,利用 /app/test 重新交由 DispatcherServlet 转发交由对应处理器处理。如果 url 带有前缀 redirect:,比如 redirect:/test,SpringMVC 会去掉该前缀,给客户端的响应写上重定向头以及重定向地址即 /test,客户端会重新发送请求。转发和重定向的区别在于:转发请求是同一个,重定向则每次都是新的请求。转发时由于经过 DispatcherServlet,以是每次都会新建 Model,而重定向则会自动将 Model 中的参数拼接到重定向的 url

@EnableWebMvc

利用 @EnableWebMvc 注解可以资助我们在代码中自定义 SpringMVC 配置,比如添加拦截器。利用 @EnableWebMvc 注解的配置类必须继承 WebMvcConfigurer 类
需要注意的是,@EnableWebMvc 是较旧的配置 SpringMVC 的方式。如果利用 SpringBoot,它提供了自动配置,通常不需要显式利用 @EnableWebMvc,只需要在配置文件配置即可
  1. @Configuration
  2. @EnableWebMvc
  3. public class AppConfig implements WebMvcConfigurer {
  4.     @Autowired
  5.     private BeforMethodInteceptor beforMethodInteceptor;
  6.     @Override
  7.     public void addInterceptors(InterceptorRegistry registry) {   
  8.         // 注册自定义拦截器,添加拦截路径和排除拦截路径
  9.         registry.addInterceptor(beforMethodInteceptor) //添加拦截器
  10.                    .addPathPatterns("/**") //添加拦截路径
  11.                    .excludePathPatterns(  //添加排除拦截路径
  12.                            "/index",
  13.                            "/login",
  14.                            ...
  15.                            );
  16.         super.addInterceptors(registry);        
  17.     }
  18.     @Override
  19.     public void configureViewResolvers(ViewResolverRegistry registry) {
  20.         // 配置视图解析器
  21.         InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
  22.         viewResolver.setPrefix("");
  23.         viewResolver.setSuffix(".html");
  24.         viewResolver.setCache(false);
  25.         viewResolver.setContentType("text/html;charset=UTF-8");
  26.         viewResolver.setOrder(0);        
  27.         registry.viewResolver(viewResolver);
  28.         super.configureViewResolvers(registry);
  29.     }
  30.     @Override
  31.     public void addResourceHandlers(ResourceHandlerRegistry registry) {
  32.         // 定义静态资源位置和 URL 映射规则
  33.         // 例如,将所有以 /static/ 开头的 URL 映射到 /resources/ 目录下的静态资源
  34.         registry.addResourceHandler("/static/**")
  35.                 .addResourceLocations("/resources/");
  36.     }
  37.     @Override
  38.     public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
  39.         // 添加 JSON 消息转换器
  40.         converters.add(new MappingJackson2HttpMessageConverter());
  41.     }
  42.    @Override
  43.     public void addCorsMappings(CorsRegistry registry) {
  44.         // 跨域配置
  45.         registry.addMapping("/**")  // 配置允许跨域的路径
  46.                 .allowedOrigins("*")  // 配置允许访问的跨域资源的请求域名
  47.                 .allowedMethods("PUT,POST,GET,DELETE,OPTIONS")  // 配置允许访问该跨域资源服务器的请求方法
  48.                 .allowedHeaders("*"); // 配置允许请求 header 的访问
  49.         super.addCorsMappings(registry);
  50.     }
  51. }
复制代码
@EnableWebMvc 注解导入了 DelegatingWebMvcConfiguration 配置类,该类会将所有 WebMvcConfigurer 接口的实现类找到并生存起来。DelegatingWebMvcConfiguration 配置类还实现了 Aware 回调接口,因此会在 Spring 容器生命周期过程中调用回调接口,从而实现自定义配置

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

来自云龙湖轮廓分明的月亮

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表