springmvc原理浅析

原理

基于servlet
springmvc的核心类DispatcherServlet,基于javaweb传统开发模式,顶层继承自HttpServlet,在web.xml中配置该Servlet匹配的路径为/,即所有请求都会经过DispatcherServlet进行处理。实现servlet的doGet、doPost等方法,最终都经过doDispatch方法处理,在该方法进行参数解析、路径匹配处理、响应处理、异常处理等等。

基于listener
springmvc的ContextLoaderListener通过实现监听器,初始化springmvc的全局上下文WebApplicationContext

执行流程

以下流程都在doDispatch中处理。

  1. 先匹配是否是Multipart文件上传请求,如果是则对请求进行解析,并封装成DefaultMultipartHttpServletRequest.
  2. 通过HandlerMapping根据request得到请求路径寻找匹配的类和方法,获取到匹配的handlerMethod.(通过@requetMapping配置的类和方法,每个方法会被封装成一个handlerMethod,该类包含了配对到的类的类型和方法信息method类)。
  3. 根据当前路径匹配符合的拦截器HandlerInterceptor,将所有匹配的拦截器对象加入interceptor集合,并与第二步获取的handlerMethod一块封装成执行链HandlerExecutionChain对象。
  4. 调用执行链中interceptorpreHandler方法,在请求到达对应的handlerMethod前进行拦截处理。
  5. 根据handlerMethod获取对应的HandlerAdapter准备开始处理请求,这时会获取到RequestMappingHandlerAdapter。在该类中通过实现InitializingBean初始化,设置已有的参数解析器集合HandlerMethodArgumentResolver,然后循环调用匹配的参数解析器进行处理,默认参数解析器会借助DataBind类进行请求数据和对象的绑定转换。(一个参数只会被一个参数解析器匹配并处理)。
  6. 参数解析后,handlmethod通过method类进行反射调用匹配的方法
  7. 方法执行后根据RequestMappingHandlerAdapter封装的返回值处理HandlerMethodReturnValueHandler集合,获取匹配的处理器,进行处理。处理后封装modelAndView对象.(比如常用的@RequestBody注解,就是通过返回值处理器RequestResponseBodyMethodProcessor处理)。
  8. 调用执行链中interceptorpostHandler,即在handlerMethod完成处理后,还没渲染视图前处理。
  9. 以上整个过程的处理都是通过try catch包裹的,如果报错会被catch,然后放到Exception对象引用,如果Exception对象不为空,则会通过HandlerExceptionResolver异常处理器处理,同样只要有一个异常匹配并处理了就不会被其他处理器处理,处理后获取异常封装的modelAndView。
  10. 根据modelAndView(此时的modelAndView可能是正常请求结束后封装的,也可能是出异常后封装的)中的视图名字,通过ViewResolver集合渲染视图。
  11. 调用执行链中interceptorafterCompletion,进行视图渲染后的操作(这一步被另外try catch,且只会打印错误信息,不会影响请求响应数据)。

注意:1-8步如果那部出现异常,都会提前跳到第9步。

简单总结:HandlerMapping路径匹配->interceptor执行拦截器preHandler->HandlerMethodArgumentResolver参数解析->invoke执行匹配的方法->HandlerMethodReturnValueHandler返回值解析->封装modelAndView->interceptor执行拦截器postHandler
->HandlerExceptionResolver异常处理->渲染视图->interceptor执行拦截器afterCompletion.

数据绑定器DataBinder

用于数据绑定,将请求的数据转成对应的实体类,并且可支持校验参数,且支持类型转换。如果是基本数据类型
BeanWrapper
该类是spring用于方便的修改和获取bean中的数据,内部依赖BeanUtils对bean做反射操作。DataBinder内部依赖
BeanWrapper对bean进行设值和获取值。
WebDataBinder
继承DataBinderd类,对DataBinder做了增强处理,且支持将MultipartFile绑定到javaBean属性上。

通过@ControllerAdvice修饰类,@InitBinder注解方法,方法可以传入WebDataBinder参数,再创建一个类继承PropertyEditorSupport,可以自定义针对某种类型进行自定义的转化。比如自定义data类型的转换,将data转String,还可以对请求参数携带某些前缀的参数进行匹配,然后

原理:RequestMappingHandlerAdapter在初始化数据绑定起时,会通过ApplicationContext获取@ControllerAdvice注解修饰的类,找到类中被@InitBinder注解修饰的方法,封装Method存储起来。在生成绑定工厂时会获取这些Method创建。

实现例子

@ControllerAdvice
public class ControllerAdvices {
     
    /**
     * 把date类型空字符串转为null
     */
    @InitBinder
    public void initBinder(WebDataBinder binder) {
     
        binder.registerCustomEditor(Date.class, new StringTrimmerEditor(true));
    }
}

Converter

另外一种参数转换器,initBind使用String->其他类型和其他类型->String,Converter支持类型转类型,initBind的优先级比较高。
例子:

public class QueryTypeConverter implements Converter<String, QueryType> {
     

    @Override
    public QueryType convert(String source) {
     
        String value = source.trim();
        if ("".equals(value)) {
     
            return null;
        }
        return QueryType.getByType(Integer.valueOf(value));
    }

}
@Configuration
public class MyMvcConfigurer implements WebMvcConfigurer{
     
	@Override
	public void addFormatters(FormatterRegistry registry) {
     
		registry.addConverter(new QueryTypeConverter());
	}
}

参数解析器

1.实现参数解析器,只要实现HandlerMethodArgumentResolver接口,然后实现WebMvcConfigurer类进行配置,通过addArgumentResolvers将解析器加入到解析器集合中。通常可以用于实现自定义的参数解析器,需要注意有执行顺序的问题,排在前面的解析器先匹配成功可能会被前面的处理了。可以用来实现自定义注解解析参数。

2.也可以通过实现RequestBodyAdvice接口,同时通过@ControllerAdvice注解修饰类。原理是RequestResponseBodyMethodProcessor实现了HandlerMethodArgumentResolver(同时也实现返回值处理器),处理参数解析时会获取实现了ResponseBodyAdvice接口的类,通过循环遍历匹配处理。而RequestMappingHandlerAdapter在获取参数解析器时,有一步就是把RequestResponseBodyMethodProcessor解析器加入集合中。

返回值处理器

1.实现返回值处理器,只要实现HandlerMethodReturnValueHandler接口,通过通过WebMvcConfigurer类进行配置。可以用来实现自定义注解,对返回做一些处理,比如过滤某些字段、过滤空值等。

2.也可以通过实现ResponseBodyAdvice接口,同时通过@ControllerAdvice注解修饰类。原理是RequestResponseBodyMethodProcessor实现了HandlerMethodReturnValueHandler,处理参数解析时会获取实现了ResponseBodyAdvice接口,通过循环遍历匹配处理。而RequestMappingHandlerAdapter在获取默认返回值处理器时,有一步就是把RequestResponseBodyMethodProcessor解析器加入集合中。

实现参数解析器RequestBodyAdvice和实现返回值处理器ResponseBodyAdvice都是通过获取实现了接口的类的对象列表,那么这个对象列表是从哪获取的,什么时候注入的?
RequestMappingHandlerAdapter实现了InitializingBean接口,在afterPropertiesSet中,通过ApplicationContext获取所有包含@ControllerAdvice注解的类,得到一个类的集合,然后循环判断是否为RequestBodyAdvice类的子类或者ResponseBodyAdvice子类,如果是则加入到本类的成员变量requestResponseBodyAdvice类集合中。然后经过一层封装,封装到前面提到的RequestResponseBodyMethodProcessor类,再根据返回值处理器和参数解析器封装到各自的集合,放到成员变量中。

拦截器

实现拦截器只要实现HandlerInterceptor接口,通过WebMvcConfigurer配置即可。可以做一些请求拦截,日志打印等处理。

异常处理器

实现异常处理器只要实现HandlerExceptionResolver接口,通过WebMvcConfigurer配置即可。可以对一些异常进行捕捉处理,返回自定义的ModelAndView.

注解方式
通过@ControllerAdvice注释类 + @ExceptionHandler注释处理异常的方法 + @ResponseStatus + @ResponseBody
结合使用处理全局异常。
原理:是spring通过ExceptionHandlerExceptionResolver类来处理,该类是spring标准异常处理器之一,会被加到处理器集合中,不用通过webMvcConfigurer配置。该类实现了HandlerExceptionResolver,在被创建时,通过实现InitializingBeanafterPropertiesSet方法中,会对@ControllerAdvice封装的类进行获取并解析,解析该类中中被@ExceptionHandler注释的方法,最终封装成一个ExceptionHandlerMethodResolver类,设置到ExceptionHandlerExceptionResolver集合全局变量。该类实现了异常处理器接口,所以有异常时,会遍历到此类进行处理,该类通过遍历ExceptionHandlerExceptionResolver集合进行类型匹配。

总结

1.以上的处理器都可以通过WebMvcConfigurer配置,即写一个类实现该类,并用@Configuration注解修饰类实现。原理是通过
DelegatingWebMvcConfiguration类,该类依赖注入一个WebMvcConfigurer集合,将所有实现该类的对象注入进来,然后该类又继承了webMvcConfigurationSupport类并配置了@Configuration加入到容器,而RequestMappingHandlerAdapter获取参数解析器、返回值处理器其中有一步就是从这里获取。

2.其他配置方式则是基于spring默认的一些实现了对应处理器接口的类实现,这些默认类通过获取ApplicationContext类获取使用某些注解的类或者实现了某些接口的类,将这些类解析到标准处理类中处理。为用户提供了方便的注解方式扩展。
校验签名可以通过拦截器实现

加密解密可以通过参数解析器和返回值解析器实现, spring mvc web xml配置

配置

使用springmvc需要如下配置。使用springboot则不用配。

<servlet>
    <servlet-name>springservlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    <init-param>
        <param-name>contextConfigLocationparam-name>
        <param-value>classpath:spring-servlet.xmlparam-value>
    init-param>
    <load-on-startup>1load-on-startup>
servlet>

<servlet-mapping>
    <servlet-name>springservlet-name>
    <url-pattern>/*url-pattern>
servlet-mapping>
  

<listener>
   <listenerclass>
 org.springframework.web.context.ContextLoaderListener
   listener-class>
listener>
  

<context-param>
    <param-name>contextConfigLocationparam-name>
    <param-value>classpath:config/applicationContext.xmlparam-value>
context-param>

你可能感兴趣的:(spring)