zoukankan      html  css  js  c++  java
  • 深入浅出Spring MVC

    摘要

    本文旨在详细分析SpringMVC工作原理以及作为开发者如何基于SpringMVC做扩展。因为SpringMVC分析的文章比较多,所以本文重点讲解如何利用SpringMVC的扩展点实现我们的需求。

    什么是Spring MVC

    SpringMVC的作用是什么呢?需要解决什么问题呢?

    下图是一个客户端与服务端的交互

    在这里插入图片描述
    在之前的详解http报文(2)-web容器是如何解析http报文的一文中我也提到过。
    这次再更细致的分析一遍。一个请求如何中客户端发到服务端,再从服务端返回内容。干的这件事在web中叫请求动态内容,区别于静态内容。在java语言中,为了解决这件事定义了一个规范就是servlet。具体的实现由各大厂商自己定义。

    大体部分分为两部分一块是建立连接、一块是传输内容。所以servlet规范包括两大部分,,一部分是servlet接口,定义处理请求的规范。一部分是servlet容器的,去管理加载servlet实例。

    轻量级的servlet容器有tomcat/jetty/undertow,servlet框架有SpringMVC/Struts/Webx这些,本篇重点讲解SpringMVC

    SpringMVC工作流程

    在这里插入图片描述
    Spring MVC 顾名思义就是处理Controll-Model-View的。

    1. DispatchServlet 是入口,doDispatch方法开始处理请求
    2. 首先经过controll,controll包含两部分,一部分是url处理映射,将url与具体的处理bean映射起来。也就是HandleMapping,另一部分是具体的Handle,因为需要不同的handle,所以定义了HandleAdapter.
    3. Model比较简单,主要就是ModelView对象,
    4. View 包含两部分,一部分是ViewName的解析,另一部分是ViewName的对应的模板引擎,来渲染出最终的模板引擎。

    常见扩展点

    基于以上,Spring MVC 提供了不同层面的扩展,方便开发者实现定制化的功能,而不需要底层代码的修改

    一. Filter

    Filter其实不算是SpringMVC,是servlet的,这时候请求还没有到DispatchServlet。Filter允许对请求和响应做一些统一的定制化处理,比如你限流、日志、trace。

    实现javax.servlet.Filter接口即可

    二. Controll - HandleMapping,HandlerAdapter

    HandleMapping属于Controll层面,我们可以编写任意的HandlerMapping实现类,然后定义策略来决定一个web请求到HandlerExecutionChain对象的生成。

    继承RequestMappingHandlerMapping 类即可。
    这个具体案例可以看下fredal的博客-使用基于 SpringMVC 的透明 RPC 开发微服务

    简要来说,他的rpc通信协议是基于http的。所以rpc调用就是基于服务端还是原来的restful api。写法给普通的前端去掉无异,然后包一层rpc client。方便客户端调用。但是这样太麻烦了,对于不需要暴露给前端的API,单纯是服务间的rpc调用。再走一遍servlet-SpringMVC没必要。

    所以他基于RequestMappingHandlerMapping做了一个改造。不再基于SpringMVC,而是自己定义了一套rpc的范式,然后转换为springmvc。

    三. Controll - Interceptor

    Interceptor属于Controll层面,我们可以自定义各种拦截器,在一个请求被真正处理之前、请求被处理但还没输出到响应中、请求已经被输出到响应中之后这三个时间点去做任何我们想要做的事情。广泛应用于Log,Session,鉴权等场景。

    实现HandlerInterceptor接口即可

    四. View - HandlerMethodArgumentResolver

    解析方法参数的,可以很方便的扩展http请求参数。
    实现HandlerMethodArgumentResolver接口即可

    比如需要从http header中处理设备信息

    @Component
    public class DeviceResolver implements HandlerMethodArgumentResolver {
        @Override
        public boolean supportsParameter(final MethodParameter methodParameter) {
            return methodParameter.getParameterType().equals(DeviceInfo.class);
        }
    
        @Override
        public Object resolveArgument(final MethodParameter methodParameter,
                final ModelAndViewContainer modelAndViewContainer,
                final NativeWebRequest nativeWebRequest,
                final WebDataBinderFactory webDataBinderFactory) throws Exception {
            HttpServletRequest request =
                    (HttpServletRequest)  nativeWebRequest.getNativeRequest(HttpServletRequest.class);
    
            // 从head头中获取设备信息
            String id = request.getHeader("x-device-id");
            if (id != null) {
                DeviceInfo deviceInfo = new DeviceInfo();
                deviceInfo.setId("id");
                return deviceInfo;
            }
            return null;
        }
    }
    

    五. View - Converter

    类型转换器,主要和序列化相关,参数绑定时springmvc会对将前端传来的参数通过某种规则转化成方法定义的参数的类型,默认实现的有StringHttpMessageConverterByteArrayHttpMessageConverter等等,默认的不能满足需求时我们可自己定义此接口来实现自己的类型的转换。

    继承AbstractHttpMessageConverter 即可。

    六. View- HandlerExceptionResolver

    异常处理,通常需要定义的全局异常。

    @ControllerAdvice 注解即可
    一次和前端的相互甩锅的问题记录中有总结过这种

    七. 修改requestbody 内容

    当我们需要对RequestBody的内容进行统一处理时,因为HandlerMethodArgumentResolver只能处理特定类型的,做不到这点要求。

    实现RequestBodyAdvice 接口即可。比如我需要处理requestbody中的内容,将emoji输入转换掉

    @RestControllerAdvice
    public class EmojiReplaceAdvice implements RequestBodyAdvice {
        @Override
        public boolean supports(final MethodParameter methodParameter, final Type targetType,
                final Class<? extends HttpMessageConverter<?>> converterType) {
            return methodParameter.hasParameterAnnotation(EmojiReplace.class);
        }
    
        @Override
        public Object handleEmptyBody(final Object body, final HttpInputMessage inputMessage,
                final MethodParameter parameter, final Type targetType,
                final Class<? extends HttpMessageConverter<?>> converterType) {
            return body;
        }
    
        @Override
        public HttpInputMessage beforeBodyRead(final HttpInputMessage inputMessage,
                final MethodParameter parameter,
                final Type targetType, final Class<? extends HttpMessageConverter<?>> converterType)
                throws IOException {
            return new HttpInputMessage() {
                @Override
                public InputStream getBody() throws IOException {
                    final String content = IOUtils.toString(inputMessage.getBody());
                    final String emojiUnicodeToAlias = StringUtil.parseEmojiUnicodeToAlias(content);
                    return new ByteArrayInputStream(
                            emojiUnicodeToAlias.getBytes(StandardCharsets.UTF_8));
                }
    
                @Override
                public HttpHeaders getHeaders() {
                    return inputMessage.getHeaders();
                }
            };
        }
    
        @Override
        public Object afterBodyRead(final Object body, final HttpInputMessage inputMessage,
                final MethodParameter parameter, final Type targetType,
                final Class<? extends HttpMessageConverter<?>> converterType) {
            return body;
        }
    }
    

    总结

    这篇文章主要是系统的概括了SpringMVC的工作原理和各种扩展机制,属于高度概括,细节不足。具体的每个扩展点的实现、坑、应用场景需要在之后的文章继续阐述。

    参考

    https://fredal.xin/develop-with-transparent-rpc

  • 相关阅读:
    洛谷P3233 世界树
    线性基
    CF321E Ciel and Gondolas
    洛谷P2619 Tree I
    重温一下基本数据类型以及自动提升数据类型的问题
    不可理喻的JSTL标签库
    理解RESTful架构(转)
    Node.js的优点和缺点(转载)
    自制双色球随机号码
    编程, 细心永远都不嫌多(记录java连接数据库的一个错误)
  • 原文地址:https://www.cnblogs.com/stoneFang/p/12996839.html
Copyright © 2011-2022 走看看