Java Server 理解与实践 —— 集成Spring-webmvc

引言

在Java服务端编程中,几乎没人能绕的开”Spring”这个单词。”Spring”发展至今,已经不是一个简单的IOC和AOP框架了,其中衍生出了非常多的组件,而其中一个非常重要的组件就是”Spring MVC”。Spring-webmvc解决了在网站开发时遇到的的很多常见问题,比如:

  • 如何将特定的请求(url + 方法)分派到特定的对象处理方法中。
  • 如何将请求中的request body映射为特定的类对象,又如何将返回的类对象映射到response body中
  • 如何处理文件上传的请求
  • 如何处理静态资源的缓存状态
  • 如何处理在请求中可能发生的异常

webmvc,其实上述所指的更多的是c的部分,因为目前大多项目应用都实行前后端分离,因此笔者也并没有太过于关注m和v之间的绑定以及c和v之间的交互关系。spring通过注解、对象分离等等的方式,极大的简便了web开发的流程。

这篇文章将会主要会讲述两点,第一点是对DispatcherServlet做了一个简单介绍,包括其拥有的配置、功能,而第二点则是围绕着一个请求从到达Servlet直至处理完毕并且返回的流程来介绍spring-webmvc框架,其中包含有其请求分派机制,消息读写处理机制与异常处理机制。

Spring-webmvc的核心 —— DispatcherServlet

org.springframework.web.servlet这个包底下,有着spring-webmvc两个重要的Servlet实现——ResourceServlet(主要用于静态资源)与DispatcherServlet(主要用于动态请求)。我们将会重点讲解DispatcherServlet

DispatcherServlet的配置与功能

配置与功能实际上是相辅相成的,拥有特定的配置,就代表着它拥有特定的功能。所以我们首先关注一下DispatcherServlet的初始化函数。

protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context); //用于文件处理
        initLocaleResolver(context);//用于处理位置信息
        initThemeResolver(context);
        initHandlerMappings(context);//用于处理url与处理函数的映射
        initHandlerAdapters(context);//用于将请求与Java类之间的转换
        initHandlerExceptionResolvers(context);//统一的异常处理
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

DispatcherServlet主要组成
可以看到,在DispatcherServlet初始化的时候,会有很多相关的实例被注册到Servlet当中。这些实例实际上就决定了Servlet的功能。

以HandlerMapping为例,查看DispatcherServlet的初始化模式

private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;
        //默认情况下,detectAllHandlerMappings的值为true
        if (this.detectAllHandlerMappings) {
            // 在检测Handler的模式下,首先会找到所有实现了HandlerMapping接口的Bean
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
                //对HandlerMapping进行排序
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            try {
                //如果不是检测所有Bean的话,就以handlerMapping这个key去找对应的Bean是否存在
                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                this.handlerMappings = Collections.singletonList(hm);
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Ignore, we'll add a default HandlerMapping later.
            }
        }

        // 如果都不存在,则使用默认的HandlerMapping
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            if (logger.isDebugEnabled()) {
                logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
            }
        }
    }

通过以上代码可以知道在DispatcherServlet中,首先会搜索由我们定义的HanderMapping实例,在找不到的情况下,则会使用默认的HandlerMapping。这也是spring-webmvc中比较常见的做法——如果扫描到用户定义的,则使用用户的,否则提供默认实例。

DispatcherServlet中的service方法

既然是Servlet,就必须实现service方法,DispatcherServlet继承了抽象类FrameworkServlet,在其service方法中,默认调用的是doService方法,而在doService方法中又调用了doDispatch方法。

所以doDispatch方法中才是包含了处理请求最核心的代码。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ...//
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                ...
                //获取handler
                mappedHandler = getHandler(processedRequest);
                //...

                // 获取handlerAdapter
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                .....

                // 处理请求
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                ...
            }
            catch (Exception ex) {
                //异常抛出及获取
                dispatchException = ex;
            }
            catch (Throwable err) {
                // As of 4.3, we're processing Errors thrown from handler methods as well,
                // making them available for @ExceptionHandler methods and other scenarios.
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            //将异常交由exceptionResolver进行处理
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        ....
    }

忽略了部分代码之后,实际上我们就能看到请求的处理逻辑,首先获取对应的mappingHandler,然后由handlerAdapter进行请求参数的解析以及实际处理函数的调用,最后处理异常。

以上,也就是关于DispatcherServlet的简介了。接下来会讲述一些处理细节。

HandlerMapping —— Spring中的url分发机制

要说清楚url分发机制,我们首先应该了解其中的类继承关系。我们由RequestMappingHandlerMapping出发,一探究竟。
RequestMappingHanderMapping类图
从上图我们可以知道,在RequestMappingHandlerMapping类中实际上持有了MappingRegistry,而MappingRegistry,则拥有所有url以及对应处理类/方法的映射关系。知道了这一层关系之后,实际上我们就能够找出这些映射关系是何时注册的。

实际上,映射关系的注册是由AbstractHandlerMethodMapping中的initHandlerMethods方法完成的。

protected void initHandlerMethods() {
        ...
        //扫描所有bean,找出合适的(拥有注释@RequestMapping或者是@Controller,进行映射注册。
        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                getApplicationContext().getBeanNamesForType(Object.class));

        for (String beanName : beanNames) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                Class<?> beanType = null;
                try {
                    beanType = getApplicationContext().getType(beanName);
                }
                catch (Throwable ex) {
                    //异常处理
                }
                if (beanType != null && isHandler(beanType)) {
                    detectHandlerMethods(beanName);
                }
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }

在HandlerMapping中,完成了url+方法到具体类方法的映射。但是如果需要将我们的POST请求里面的body转换到具体Java方法参数以及将我们返回的Java对象映射到响应的Body中,其实还有一段路要走,而这一段路,是交给HandlerAdapter来完成的。

HandlerAdapter —— 将请求与Java方法中的参数适配

对于HandlerAdapter,我们采用的例子是RequestMappingHandlerAdapter

首先查看这个类的属性:

private List<HandlerMethodArgumentResolver> customArgumentResolvers;

    private HandlerMethodArgumentResolverComposite argumentResolvers;

    private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers;

    private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;

    private HandlerMethodReturnValueHandlerComposite returnValueHandlers;

    private List<ModelAndViewResolver> modelAndViewResolvers;

以上,实际上就是我们能够进行配置的。而在afterPropertiesSet方法中,我们还能够看到又一次的支持配置,同时保留默认。三个方法分别设置了三种类型的resolver,argumentResolversinitBinderArgumentResolvers,以及returnValueHandlers

在这三个方法中,我们能够知道spring-webmvc具体支持哪些变量的适配。更为常用的是变量的适配,因此我们从这里先入手。

    private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();

        // 基于注解的变量
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
        resolvers.add(new RequestParamMapMethodArgumentResolver());
        resolvers.add(new PathVariableMethodArgumentResolver());
        resolvers.add(new PathVariableMapMethodArgumentResolver());
        resolvers.add(new MatrixVariableMethodArgumentResolver());
        resolvers.add(new MatrixVariableMapMethodArgumentResolver());
        resolvers.add(new ServletModelAttributeMethodProcessor(false));
        //注意,这里使用了messageCoverter作为covert message的执行实例,用于将请求的内容映射到被@Request标注Java对象
        resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
        //同上,但这里是映射以@RequestPart标记的对象。
        resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new RequestHeaderMapMethodArgumentResolver());
        resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new SessionAttributeMethodArgumentResolver());
        resolvers.add(new RequestAttributeMethodArgumentResolver());

        // 基于类型的变量
        resolvers.add(new ServletRequestMethodArgumentResolver());
        resolvers.add(new ServletResponseMethodArgumentResolver());
        resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RedirectAttributesMethodArgumentResolver());
        resolvers.add(new ModelMethodProcessor());
        resolvers.add(new MapMethodProcessor());
        resolvers.add(new ErrorsMethodArgumentResolver());
        resolvers.add(new SessionStatusMethodArgumentResolver());
        resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

        // 自定义的变量
        if (getCustomArgumentResolvers() != null) {
            resolvers.addAll(getCustomArgumentResolvers());
        }

        // 其它
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
        resolvers.add(new ServletModelAttributeMethodProcessor(true));

        return resolvers;
    }

我们再往下看HttpMessageConverter的接口。

public interface HttpMessageConverter<T> {

    boolean canRead(Class<?> clazz, MediaType mediaType);

    boolean canWrite(Class<?> clazz, MediaType mediaType);

    List<MediaType> getSupportedMediaTypes();

    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException;

    void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException;

}

显然,从请求到具体方法中的Java参数,实际上也是分成了几个部分,首先会被分派到Resolver中,然后具体resolver执行read函数返回实际对象。

而返回也是类似的,具体我们可以去查看Handler中的returnValueHandlers

Servlet中的异常处理

至于异常处理,则相对来说是比较好理解的一块,在DispatcherServlet中的processHandlerException就可以直接看到异常时如何被处理的。

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
            Object handler, Exception ex) throws Exception {

        ...
        ModelAndView exMv = null;
        for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
        //逐个ExceptionResolver遍历,直到异常被处理(返回的ModelView不为空)
            exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
            if (exMv != null) {
                break;
            }
        }
        if (exMv != null) {
            ...//处理返回的异常对应的ModelView
            return exMv;
        }

        throw ex;
    }

所以,我们只需要增加自己的ExceptionResolver声明(Bean),并且在处理异常完毕后返回特定的modelView就可以了。

实例

一个简单的集成了spring-webmvc的实例。

首先,将相关的依赖加入到POM文件中。

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.3.3.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.5.3</version>
        </dependency>
        <!--用于处理输入输出的数据 -->

然后,声明一个Controller

@RestController
@RequestMapping("/hello")
public class HelloWordController {

    @RequestMapping(method = RequestMethod.GET)
    public ResponseEntity<String> Hello() {
        return new ResponseEntity<String> ("Hello World", HttpStatus.OK);
    }
}

然后,声明servlet以及添加对应的XML文件。

    <servlet>
        <servlet-name>SpringDispatchServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:/META-INF/spring/sample-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="web.rest"/>

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>
    <bean class=" org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <ref bean="jsonHttpMessageConverter" />
            </list>
        </property>
    </bean>

    <bean id="jsonHttpMessageConverter"
        class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
        <property name="supportedMediaTypes">
            <list>
                <value>application/json;charset=UTF-8</value>
            </list>
        </property>
    </bean>

</beans>

更详细的内容,可以到我的github上查看。

小结

spring-webmvc框架主要解决了从请求到实例方法调用的问题,包括中间的对象路由,参数转换以及地理位置、session、cookie等信息的获取。配合注释,我们能够迅速地创建一个后端应用。

参考文献

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值