Spring MVC概念

Spring MVC基于模型-视图-控制器(Model-View-Controller,MVC)模式实现,能够构建像Spring框架那样灵活和松耦合的Web应用程序

跟踪Spring MVC的请求

《Spring MVC概念》

在请求离开浏览器时①,会带有用户所请求内容的信息,至少会包含请求的URL

请求旅程的第一站是Spring的DispatcherServlet。Spring MVC所有的请求都会通过一个前端控制器(front controller)Servlet。前端控制器是常用的Web应用程序模式,在这里一个单实例的Servlet将请求委托给应用程序的其他组件来执行实际的处理。在Spring MVC中,DispatcherServlet就是前端控制器

DispatcherServlet的任务是将请求发送给Spring MVC控制器(controller)。控制器是一个用于处理请求的Spring组件。在典型的应用程序中可能会有多个控制器,DispatcherServlet需要知道应该将请求发送给哪个控制器。所以DispatcherServlet以会查询一个或多个处理器映射(handler mapping)②来确定请求的下一站在哪里。处理器映射会根据请求所携带的URL信息来进行决策

一旦选择了合适的控制器,DispatcherServlet会将请求发送给选中的控制器③。到了控制器,请求会卸下其负载(用户提交的信息)并耐心等待控制器处理这些信息。(实际上,设计良好的控制器本身只处理很少甚至不处理工作,而是将业务逻辑委托给一个或多个服务对象进行处理。)

控制器在完成逻辑处理后,通常会产生一些信息,这些信息需要返回给用户并在浏览器上显示。这些信息被称为模型(model)。不过仅仅给用户返回原始的信息是不够的——这些信息需要以用户友好的方式进行格式化,一般会是HTML。所以,信息需要发送给一个视图(view),通常会是JSP

控制器所做的最后一件事就是将模型数据打包,并且标示出用于渲染输出的视图名。它接下来会将请求连同模型和视图名发送回DispatcherServlet④

这样,控制器就不会与特定的视图相耦合,传递给DispatcherServlet的视图名并不直接表示某个特定的JSP。实际上,它甚至并不能确定视图就是JSP。相反,它仅仅传递了一个逻辑名称,这个名字将会用来查找产生结果的真正视图。DispatcherServlet将会使用视图解析器(view resolver)⑤来将逻辑视图名匹配为一个特定的视图实现,它可能是也可能不是JSP

既然DispatcherServlet已经知道由哪个视图渲染结果,那请求的任务基本上也就完成了。它的最后一站是视图的实现(可能是JSP)⑥,在这里它交付模型数据。请求的任务就完成了。视图将使用模型数据渲染输出,这个输出会通过响应对象传递给客户端⑦

搭建Spring MVC

配置DispatcherServlet

DispatcherServlet是Spring MVC的核心。在这里请求会第一次接触到框架,它要负责将请求路由到其他的组件之中

使用Java将DispatcherServlet配置在Servlet容器中,而不使用web.xml文件。如下的程序清单展示了所需的Java类:

// 配置DispatcherServlet
package spittr.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispactcherServletInitializer
{
    @Override
    protected String[] getServletMappings()        // 将DispatcherServlet映射到“/”
    {
        return new String[] {"/"};
    }
    
    @Override
    protected class<?>[] getRootConfigClasses()
    {
        return new class<?>[] {RootConfig.class};
    }
    
    @Override
    protected class<?>[] getServletConfigClasses()        // 指定配置类
    {
        return new class<?>[] {WebConfig.class};
    }
}

扩展AbstractAnnotationConfigDispatcherServletInitializer的任意类都会自动地配置Dispatcher-Servlet和Spring应用上下文,Spring的应用上下文会位于应用程序的Servlet上下文之中

AbstractAnnotationConfigDispatcherServletInitializer剖析

在Servlet 3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果能发现的话,就会用它来配置Servlet容器

Spring提供了这个接口的实现,名为SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring 3.2引入了一个便利的WebApplicationInitializer基础实现,也就是AbstractAnnotationConfigDispatcherServletInitializer。因为我们的Spittr-WebAppInitializer扩展了AbstractAnnotationConfig DispatcherServlet-Initializer(同时也就实现了WebApplicationInitializer),因此当部署到Servlet 3.0容器中的时候,容器会自动发现它,并用它来配置Servlet上下文

在上述程序中,SpittrWebAppInitializer重写了三个方法:
第一个方法getServletMappings(),它会将一个或多个路径映射到DispatcherServlet上。在本例中,它映射的是“/”,这表示它会是应用的默认Servlet。它会处理进入应用的所有请求

为了理解其他的两个方法,首先要理解DispatcherServlet和一个Servlet监听器(也就是ContextLoaderListener)的关系

两个应用上下文之间的故事

启动DispatcherServlet时,创建Spring应用上下文,并加载配置文件或配置类中所声明的bean。在上述程序的getServletConfigClasses()方法中,DispatcherServlet加载应用上下文时,使用定义在WebConfig配置类(使用Java配置)中的bean

但是在Spring Web应用中,通常还会有另外一个应用上下文。另外的这个应用上下文是由ContextLoaderListener创建的

DispatcherServlet会加载包含Web组件的bean,如控制器、视图解析器以及处理器映射,而ContextLoaderListener加载应用中的其他bean。通常是驱动应用后端的中间层和数据层组件

AbstractAnnotationConfigDispatcherServletInitializer会同时创建DispatcherServlet和ContextLoaderListener。GetServletConfigClasses()方法返回的带有@Configuration注解的类将会用来定义DispatcherServlet应用上下文中的bean。getRootConfigClasses()方法返回的带有@Configuration注解的类将会用来配置ContextLoaderListener创建的应用上下文中的bean

在本例中,根配置定义在RootConfig中,DispatcherServlet
的配置声明在WebConfig中

通过AbstractAnnotationConfigDispatcherServletInitializer来配置DispatcherServlet是传统web.xml方式的替代方案

使用这种方式配置DispatcherServlet,而不使用web.xml,则只能部署到支持Servlet 3.0的服务器中才能正常工作,如Tomcat 7或更高版本

启动Spring MVC

// 最小但可用的Spring MVC配置
package spittr.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc        // 启用 Spring MVC
@ComponentScan("spittr.web")
public class WebConfig extends WebMvcConfigurerAdapter 
{
    @Bean
    public ViewResolver viewResolver()        // 配置JSP视图解析器 
    {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        resolver.setExposeContextBeansAsAttributes(true);
        return resolver;
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)
    {
        configurer.enable();        // 配置静态资源的处理
    }
}

上述程序中WebConfig现在添加了@ComponentScan注解,因此将会扫描spitter.web包来查找组件。编写的控制器将带有@Controller注解,这会使其成为组件扫描时的候选bean。因此无需在配置类中显式声明任何的控制器

ViewResolver bean具体来讲是InternalResourceViewResolver(试图解析器)。它会查找JSP文件,查找时会在视图名称上加一个特定的前缀和后缀(如名为home的视图将会解析为/WEB-INF/views/home.jsp)

扩展了WebMvcConfigurerAdapter并重写了其configureDefaultServletHandling()方法。通过调用DefaultServletHandlerConfigurer的enable()方法,要求DispatcherServlet将对静态资源的请求转发到Servlet容器中默认的Servlet上,而不是使用DispatcherServlet本身来处理此类请求

package spittr.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@ComponentScan(basePackages={"spitter", excludeFilters{@Filter(type=FilterType.ANNOTATION, value=EnableWebMvc.class)}})
public class RootConfig{}

Spring MVC配置文件dispatcher-servlet.xml详解

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-3.2.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
    <!-- 使用spring提供的PropertyPlaceholderConfigurer读取数据库配置信息.properties
     1、这里的classpath可以认为是项目中的src-
     2、属性名是 locations,使用子标签<list></list>可以指定多个数据库的配置文件,这里指定了一个
     其中order属性代表其加载顺序,而ignoreUnresolvablePlaceholders为是否忽略不可解析的 Placeholder,
     如配置了多个PropertyPlaceholderConfigurer,则需设置为true
     <bean id="propertyConfigurerForProject2" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="order" value="2" />
    <property name="ignoreUnresolvablePlaceholders" value="true" />
    <property name="locations">
      <list>
        <value>classpath:/spring/include/jdbc-parms.properties</value>
        <value>classpath:/spring/include/base-config.properties</value>
      </list>
    </property>
    </bean>-->
    <bean id="propertyConfigurer"
          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="ignoreUnresolvablePlaceholders" value="true"/>
        <property name="location" value="classpath:/application.properties"/>
    </bean>
    <!--注解探测器,在xml配置了这个标签后,spring可以自动去扫描base-pack下面或者子包下面的java文件,
    如果扫描到有@Component @Controller@Service等这些注解的类,则把这些类注册为bean
    注意:如果配置了<context:component-scan>那么<context:annotation-config/>标签就可以不用再xml中配置了,因为前者包含了后者。
    另外<context:annotation-config/>还提供了两个子标签
    1. <context:include-filter> 2.<context:exclude-filter>
    <context:component-scan>有一个use-default-filters属性,改属性默认为true,这就意味着会扫描指定包下的全部的标有@Component的类,
    并注册成bean.也就是@Component的子注解@Service,@Reposity等。所以如果仅仅是在配置文件中这么写
    <context:component-scan base-package="com.test.myapp.web"/>
     Use-default-filter此时为true,那么会对base-package包或者子包下的jun所有的进行java类进行扫描,并把匹配的java类注册成bean。
    可以发现这种扫描的粒度有点太大,如果你只想扫描指定包下面的Controller,该怎么办?此时子标签<context:incluce-filter>就起到了勇武之地。如下所示
    <context:component-scan base-package="com.test.myapp.web.Controller">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    这样就会只扫描base-package指定下的有@Controller下的java类,并注册成bean.
    但是因为use-dafault-filter在上面并没有指定,默认就为true,所以当把上面的配置改成如下所示的时候,就会产生与你期望相悖的结果(注意base-package包值得变化)
    <context:component-scan base-package="com.test.myapp.web ">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    此时,spring不仅扫描了@Controller,还扫描了指定包所在的子包service包下注解@Service的java类
    此时指定的include-filter没有起到作用,只要把use-default-filter设置成false就可以了。这样就可以避免在base-packeage配置多个包名这种不是很优雅的方法来解决这个问题了。
    另外在我参与的项目中可以发现在base-package指定的包中有的子包是不含有注解了,所以不用扫描,此时可以指定<context:exclude-filter>来进行过滤,说明此包不需要被扫描。综合以上说明
    Use-dafault-filters=”false”的情况下:<context:exclude-filter>指定的不扫描,<context:include-filter>指定的扫描-->
    <context:component-scan base-package="com.test.myapp">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!-- 视图解析器,根据视图的名称new ModelAndView(name),在配置文件查找对应的bean配置
     这个视图解析器跟XmlViewResolver有点类似,也是通过把返回的逻辑视图名称去匹配定义好的视图bean对象。
     不同点有二,一是BeanNameViewResolver要求视图bean对象都定义在Spring的application context中,
     而XmlViewResolver是在指定的配置文件中寻找视图bean对象,二是BeanNameViewResolver不会进行视图缓存。
     如果没有设置viewResolver,spring使用InternalResourceViewResolver进行解析。
     Spring实现ViewResolver的非抽象类且我们经常使用的viewResolver有以下四种:
     1、InternalResourceViewResolver  将逻辑视图名字解析为一个路径
     2、BeanNameViewResolver  将逻辑视图名字解析为bean的Name属性,从而根据name属性,找定义View的bean
     3、ResourceBundleResolver   和BeanNameViewResolver一样,只不过定义的view-bean都在一个properties文件中,用这个类进行加载这个properties文件
     4、XmlViewResolver  和ResourceBundleResolver一样,只不过定义的view-bean在一个xml文件中,用这个类来加载xml文件
     DispatcherServlet会加载所有的viewResolver到一个list中,并按照优先级进行解析。
     我们不想只使用一种视图解析器的话,可以在[spring-dispatcher-name]-servlet.xml定义多个viewResolver:
     注意order中的值越小,优先级越高。而id为viewResolver 的viewResolver的优先级是最低的。
     -->
    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
        <property name="order" value="1"/>
    </bean>
    <!--<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">-->
        <!--<property name="prefix" value="/WEB-INF/"/>-->
        <!--<property name="suffix" value=".html"/>-->
    <!--</bean>-->
    <!--基于json格式的mvc交互-->
    <bean name="jsonView" class="com.test.myapp.MappingFastJsonJsonView">
        <property name="contentType" value="application/json;charset=UTF-8"/>
    </bean>

    <!-- spring mvc +servlet3.0上传文件配置,文件上传插件uploadify的应用
     1)  在Web.xml的配置
     需要在web.xml添加multipart-config,如下所示
     <servlet>
        <servlet-name>AcrWeb</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
        <multipart-config>
            <max-file-size>52428800</max-file-size>
            <max-request-size>52428800</max-request-size>
            <file-size-threshold>0</file-size-threshold>
        </multipart-config>
    </servlet>
    2)  在spring的application.xml(名字不一定是application)的配置,需要在该配置文件下添加一个如下的bean
       spring mvc +servlet3.0上传文件配置
    <bean id="multipartResolver"
          class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
    </bean>
    3)  在jsp页面中需要引入一些相关的该插件的包
     <script src="<c:url value="/asset/admin/js/uploadify/jquery.uploadify.min.js"/>"></script>
    4)   定义一个选择文件的input框
     <div class="box-body">
       <span class="label input g1">上传apk</span>
       <input id="apk_upload" name="apk_upload" type="file"/>
       <input id="apkUrl" type="hidden" name="apkUrl"/>
     </div>
    5)  Input file与插件进行绑定
    $("#apk_upload").uploadify({
            swf: "<c:url value='/asset/admin/js/uploadify/uploadify.swf'/>",
            //cancelImg    : "<c:url value='/asset/admin/js/uploadify/uploadify-cancel.png'/>",
            uploader: "/acr/admin/app/apkupload",
            fileObjName: "file",//对应着文件输入框
            width:300,
            buttonText: '<img src="/acr/asset/admin/js/uploadify/upload.png" />',
            // onInit: function () { $(".uploadify-queue").hide();  },
            //removeCompleted : false,
            onUploadSuccess : function(file, data, response) {
                $("#apkUrl").val(data);
            },
            onUploadError : function(file, errorCode, errorMsg, errorString) {
                alert('文件 ' + file.name + ' 上传失败: ' + errorString);
            }
        });
         注意:该插件的uploadify.swf文件时放入到项目的某一个文件下面
         Uploader的值对应的是url,该值映射到了springmvc的一个方法,该方法是文件上传的核心,
         负责把文件写到指定位置的地方去。
         6)  Spring 后台代码的实现
         @RequestMapping(value = "/apkupload", method=RequestMethod.POST)
    public @ResponseBody String apkUpload(
            @RequestParam MultipartFile file,
            Model model,
            HttpServletRequest request) throws IOException {
        InputStream input = null;
        OutputStream output = null;
        String root = "H:/file";
        //生成了文件名字
        String filename = file.getOriginalFilename();
        //文件要上传的位置
        String fileFullName = buildUpPath(root, filename);
        try {
            File dir = new File(root);
            if(!dir.exists()){
                dir.mkdirs();
            }
            input = file.getInputStream();
            output = new FileOutputStream(new File(fileFullName));
            //保存文件
            IOUtils.copy(input, output);
        } catch (Throwable e) {
            throw e;
        }finally{
            IOUtils.closeQuietly(input);
            IOUtils.closeQuietly(output);
        }
        return root+"/"+filename;
    }
       其中filename对应着步骤5的onUploadSuccess中的data
    -->
    <bean id="multipartResolver"
          class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
    </bean>
    
    原文作者:布still
    原文地址: https://segmentfault.com/a/1190000010325830
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞