zoukankan      html  css  js  c++  java
  • 【WEB】初探Spring MVC框架

    Spring MVC框架算是当下比较流行的Java开源框架。但实话实说,做了几年WEB项目,完全没有SpringMVC实战经验,乃至在某些交流场合下被同行严重鄙视“奥特曼”了。“心塞”的同时,只好默默的打开IDE从HelloWorld开始。

     

    初步认识

     

    宏观视野决定微观实现的质量,首先对Spring MVC框架组件及其流程做一个简单的认识。以下是从互联网中某Spring MVC教材扣来一张介绍图(懒得重复造轮子了):

    从上图可以看出,Spring MVC框架的核心组件有DispatcherServlet、HandlerMapping、HandlerAdapter、Handler、ModelAndView、Model、View以及ViewResolver。既然是核心组件,怎么也得结合组件源码来探索个究竟吧:

     

    DispatcherServlet

     

    从名字可以看出,这就是一个Servlet实例,既然是Servlet,那当然是Srping MVC框架入口了,也是web.xml的一个Spring MVC配置项:

    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>

    其中springmvc为servlet的自定义命名名称,其中Spring MVC配置文件也是默认名称为[servletName]-servlet.xml。

     

    从DispatcherServlet源码看到到,DispatcherServlet的基础结构是:

    DispatcherServlet extend FrameworkServlet

    FrameworkServlet extend HttpServletBean

    HttpServletBean extend HttpServlet

    初略的看了一下DispatcherServlet的干系源码,主要做了两大部分,其一是初始化WEB容器的上下文信息和一些Spring MVC策略容器(如HandlerMapping、HandlerAdapter等),在启动WEB容器时可以通过控制台输出看到Spring MVC的一些初始化操作:

    ……
    信息: Starting Servlet Engine: Apache Tomcat/6.0.13
    2016-4-12 10:51:54 org.apache.catalina.core.ApplicationContext log
    信息: Initializing Spring FrameworkServlet 'springmvc'
    2016-4-12 10:51:54 org.springframework.web.servlet.FrameworkServlet initServletBean
    信息: FrameworkServlet 'springmvc': initialization started
    2016-4-12 10:51:54 org.springframework.context.support.AbstractApplicationContext prepareRefresh
    信息: Refreshing WebApplicationContext for namespace 'springmvc-servlet': startup date [Tue Apr 12 10:51:54 CST 2016]; root of context hierarchy
    2016-4-12 10:51:54 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
    信息: Loading XML bean definitions from class path resource [springmvc-servlet.xml]
    2016-4-12 10:51:55 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
    信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@c7f06: defining beans [helloWorldAnnotation,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping#0,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#0,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping#0,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter#0,org.springframework.web.servlet.view.InternalResourceViewResolver#0,/helloWorldController]; root of factory hierarchy
    2016-4-12 10:51:55 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping registerHandler
    信息: Mapped URL path [/helloWorldAnnotation] onto handler [com.maventest.springmvc.HelloWorldAnnotation@9c393d]
    2016-4-12 10:51:55 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping registerHandler
    信息: Mapped URL path [/helloWorldAnnotation.*] onto handler [com.maventest.springmvc.HelloWorldAnnotation@9c393d]
    2016-4-12 10:51:55 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping registerHandler
    信息: Mapped URL path [/helloWorldAnnotation/] onto handler [com.maventest.springmvc.HelloWorldAnnotation@9c393d]
    2016-4-12 10:51:55 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping registerHandler
    信息: Mapped URL path [/helloWorldController] onto handler [com.maventest.springmvc.HelloWorldController@85ce5a]
    2016-4-12 10:51:55 org.springframework.web.servlet.FrameworkServlet initServletBean
    信息: FrameworkServlet 'springmvc': initialization completed in 1222 ms
    ……

    其二就是对MVC容器的流程控制,其主要流程控制方法是doDispatch,接下来结合源码针对此方法的一些重要操作进行分析和学习:

    //检查请求是否是multipart(如文件上传),如果是则通过MultipartResolver解析
    processedRequest = checkMultipart(request);
    multipartRequestParsed = processedRequest != request;
    
    //获取请求对应的mappedHandler
    mappedHandler = getHandler(processedRequest);
    if (mappedHandler == null || mappedHandler.getHandler() == null) {
    noHandlerFound(processedRequest, response);
    return;
    }
    
    //获取请求对应的HandlerAdapter
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
    //由适配器执行处理器(调用处理器相应功能处理方法)
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    //如果HandlerAdapter没有对应的ModelAndView响应,怎通过上下文获取默认对应的view,接着
    applyDefaultViewName(request, mv);
    
    //看applyPostHandle得知,这是定义拦截器的处理方法
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    
    //解析视图并进行视图的渲染
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

    从doDispatch方法流程分析可以看出,跟以上Spring MVC框架流程图的处理流程是一致的,整个DispatcherServlet组件就是Spring MVC的总流程控制器,再形象一点就如下图所示:

     

    HandlerMapping

     

    察人先察色,HandlerMapping中文意思就是“处理映射”,作为一个强大的开源框架,命名自然不会乱来,通过名称就大概知其所以。看看getHandle这个方法:

    protected HandlerExecutionChain getHandler(HttpServletRequest request)…

    先不看源码,就大概可以猜个一二,这是通过request参数,获取一个对应的的处理类,而这个HandlerExecutionChain就是这个返回的处理类。这个HandlerMapping已经在项目启动的时候跟随Servlet一同初始化了:

    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);

    而getHandler方法可以通过request获取请求的所有信息,包括请求方法、URL路径等,就可以通过这个映射容器找出对应的处理类了。下面再看看这个HandlerExecutionChain响应类属性:

    private final Object handler;
    private HandlerInterceptor[] interceptors;
    private List<HandlerInterceptor> interceptorList;
    private int interceptorIndex = -1;

    它包括了请求处理的所有拦截实例和核心处理handler实例,这都会在DispatcherServlet往下几个步骤会使用到的,具体可以往上回看DispatcherServlet的处理流程。

     

    HandlerAdapter

     

    还是从名称理解开始,HandlerAdapter中文意思就是处理对象适配器,按意思就是说Spring MVC有很多个Handler处理对象,这个处理器实际就是一个Handler代理。那么如果不自己定义Handler代理的话,那默认有多少个呢,那就可以看看DispatcherServlet.properties这个配置文件了:

    Name:org.springframework.web.servlet.HandlerAdapter
    Value:org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

    哦,原来默认有HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter和AnnotationMethodHandlerAdapter一共三个默认的Handler代理。那他们分别有什么用呢,看看我自己亲自动手做过Spring MVC HelloWorld实例就很明白了,我通过两种方式实现了两个HelloWorld Handler,一个在配置文件配置的bean:

    <bean name="/helloWorldController" class="com.maventest.springmvc.HelloWorldController"/>
    public class HelloWorldController implements Controller{
    
        public ModelAndView handleRequest(HttpServletRequest arg0,
                HttpServletResponse arg1) throws Exception {
            
            ModelAndView mv = new ModelAndView();
            mv.addObject("message", "Hello World!,i am HelloWorldController.");
            mv.setViewName("hello");
            
            return mv;
        }
    }

    而另一个是通过注解实现的HelloWorld Handler:

    @Controller
    public class HelloWorldAnnotation{
    
        @RequestMapping(value="/helloWorldAnnotation")
        public String hello(ModelMap model){
            
            model.addAttribute("message", "Hello, World!I am HelloWorldAnnotation.");
            
            return "hello";
        }
    }

    这两种方式就是分别通过SimpleControllerHandlerAdapte和AnnotationMethodHandlerAdapter处理的,那这样一说就很明白了。另外这三个个Handler代理都实现了HandlerAdapter接口,就是Spring MVC规定了Handler代理的规则,分别有以下定义方法:

    public interface HandlerAdapter {
        //判断处理适配器是不是支持该Handler
        boolean supports(Object handler);
        //调用对应的Handler中适配到的方法,并返回一个ModelView
        ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
        //这个暂时还没看懂具体想干什么(不是重点,暂时放下)
        long getLastModified(HttpServletRequest request, Object handler);
    }

    其中判断是否找到合适的Handler代理就靠这个supports方法的具体实现,如果适配成功,这个代理会替这个Handler实现业务路基处理。再简单这三个代理的supports实现:

    //HttpRequestHandlerAdapter
    @Override
    public boolean supports(Object handler) {
    return (handler instanceof HttpRequestHandler);
    }
    
    //SimpleControllerHandlerAdapte
    @Override
    public boolean supports(Object handler) {
        return (handler instanceof Controller);
    }
    
    //AnnotationMethodHandlerAdapter
    @Override
    public boolean supports(Object handler) {
        return getMethodResolver(handler).hasHandlerMethods();
    }

    再来简单分别说说以上三个代理对handle方法的实现:

    HttpRequestHandlerAdapter和SimpleControllerHandlerAdapte都是直接调用handler的handleRequest方法,而AnnotationMethodHandlerAdapter稍微复杂一点,它是通过注释和反射获取相关自定义信息,进行匹配和封装,具体可自行参考其源码。

     

    Handler

     

    这就是自己实现的具体业务处理类了,上文提到很多,不用多说了。

     

    ModelAndView

     

    通过handler代理完成业务流程后返回一个ModelAndView对象,从名称就大概可以知道这是一个装载的数据模型(Model)和数据视图的对象(View)。

     

    Model

     

    从源码可以看出,model集成了LinkedHashMap<String,Object>类,这个model对象装载了所有在Handler响应给页面的数据。例如在我自己例子中的message数据:

    model.addAttribute("message", "Hello, World!I am HelloWorldAnnotation.");

    这些数据将会在页面上通过JSTL获取。

     

    View

    View接口表示一个响应给用户的视图,例如jsp文件,pdf文件,html文件等,该接口定义如下:

    public interface View {
        //HttpServletRequest中的属性名,其值为响应状态码
        String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
        //HttpServletRequest中的属性名,前一篇文章用到了该变量,它的对应值是请求路径中的变量,及@PathVariable注解的变量
        String PATH_VARIABLES = View.class.getName() + ".pathVariables";
        //该视图的ContentType
        String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
        //获取该视图ContentType
        String getContentType();
        //渲染该视图
        void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
    }

    该接口只有两个方法定义,分别表明该视图的ContentType和如何被渲染。Spring中提供了丰富的视图支持,并且可以自定义视图。

     

    ViewResolver

    ViewResolver接口定义了如何通过view 名称来解析对应View实例的行为。例如在我自己的一个注解Handler实现里面,我返回的是“hello”view name字符串,意思就是响应到对应的hello.jsp视图(在springmvc-servlet.xml配置文件定义了):

    //controller
    @RequestMapping(value="/helloWorldAnnotation")
    public String hello(ModelMap model){
        …
        return "hello";
    }

    springmvc-servlet.xml:

    <!-- ViewResolver -->  
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/jsp/" />
            <property name="suffix" value=".jsp" />
    </bean>

    在这里,我选择了默认Spring MVC JSP的实现类InternalResourceViewResolver。再来看看ViewResolver的接口定义:

    public interface ViewResolver {
        View resolveViewName(String viewName, Locale locale) throws Exception;
    }

    该接口只有一个方法,通过view name 解析出View。还是以我例子为准,通过“hello”view name字符串,通过ViewResolver. resolveViewName方法生成View实例。再通过View实例的render方法渲染该视图,剩下的具体细节可自行学习。

     

    总结

     

    两天学习下来,终于对Spring MVC有个大概的了解。毕竟是一个通用的框架,除了默认的实现,Spring MVC框架还定义了大量的标准可供用户自定义实现,整体也算是采用了Open-Closed原则,扩展性好,但有不失整体优雅。

  • 相关阅读:
    国家语言,语言代码,locale id对应表
    SpringMVC,SpringBoot文件下载
    lombok使用基础教程
    博客园主题修改中用到的css属性
    Hexo next博客添加折叠块功能添加折叠代码块
    IntelIj IDEA运行JUnit Test OutOfMemoryError
    Spring Boot-JPA、Hibernate、Spring data jpa之间的关系
    IntelliJ IDEA-设置字体大小
    win10-mysql卸载干净
    IntelliJ IDEA Check out from git
  • 原文地址:https://www.cnblogs.com/wcd144140/p/5383483.html
Copyright © 2011-2022 走看看