整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

10个知识点让你读懂Spring MVC容器

随着 Spring Boot 逐步全面覆盖到我们的项目之中,我们已经基本忘却当年经典的 Servlet + Spring MVC 的组合,那让人熟悉的 web.xml 配置。而本文,我们想先抛开 Spring Boot 到一旁,回到从前,一起来看看 Servlet 是怎么和 Spring MVC 集成,怎么来初始化 Spring 容器的。

  • Spring MVC概述
  • 注解驱动的控制器
  • 处理方法的数据绑定
  • 视图和视图解析器
  • 本地化
  • 文件上传
  • WebSocket
  • 静态资源处理
  • 拦截器
  • 异常处理

一、Spring MVC的概述

MVC:Model + View + Controller(数据模型 + 视图 + 控制器)

三层架构

三层架构:Presentation tier + Application tier + Data tier(展示层 + 应用层 + 数据访问层)

MVC和三层架构的关系

MVC和三层架构的关系, MVC只存在三层架构的展示层。

M实际是数据模型,是包含数据的对象。在Spring MVC里,有一个专门的类叫Model,用来和V之间的数据交互、传值。

V指的是视图界面,包含JSP、freeMarker、Velocity、Thymeleaf、Tile等。

C就是控制器(Spring MVC的注解@Controller的类)。

三层架构是整个应用的的架构,是由Spring框架负责管理的,一般项目结构中都由Service层、Dao层,这两个反馈在应用层和数据访问层。

Spring MVC框架围绕DispatcherServlet这个核心展开,它负责截获请求并将其分派给相应的处理器处理。Spring MVC框架包括注解驱动控制器、请求及响应的信息处理、视图解析、本地化解析、上传文件解析、异常处理以及表单标签绑定等内容。

体系结构

Spring MVC是基于Model 2实现的技术框架。Spring MVC通过一个DispatcherServlet接收所有请求,并将具体工作委托给其他组件进行处理。

Spring MVC体系结构

  1. 客户端发出一个HTTP请求,Web应用服务器接收到这个请求,如果匹配DispatcherServlet的请求映射路径(在web.xml中指定),Web容器将该请求转交给DispatcherServlet处理。
  2. DispatcherServlet接收到这个请求后,将根据请求的信息(包括URL、HTTP方法、请求报文头、请求参数、Cookie等)及HandlerMapping的配置找到处理请求的处理器(Handler)。可将HandlerMapping看成路由控制器,将Handler看成目标主机。值得注意的是:Spring MVC中并没有定义一个Handler接口,实际上任何一个Object都可以成为请求处理器。
  3. 当DispatcherServlet根据HandlerMapping得到对应当前请求的Handler后,通过HandlerAdapter对Handler进行封装,再以统一的适配器接口调用Handler。 HandlerAdapter是Spring MVC的框架级接口,顾名思义HandlerAdapter是一个适配器,它用统一的接口对各种Handler方法进行调用。
  4. 处理器完成业务逻辑的处理后将运回一个ModelAndView给DispatcherServlet,ModelAndView包含了视图逻辑名和模型数据信息。
  5. ModelAndView中包含的是“逻辑视图名”而非真正的视图对象,DispatcherServlet借由ViewResolver完成逻辑视图名到真实视图对象的解析工作。
  6. 当得到真实的视图对象View后,DispatcherServlet就使用这个View对象对ModelAndView中的模型数据进行视图渲染。
  7. 最终客户端得到的响应消息,可能是一个普通的HTML页而,也可能是一个XML或JSON串, 甚至是一张图片或一个PDF文档等不同的媒体形式。

配置DispatcherServlet

可以在web.xml中配置一个Servlet,并通过指定其处理的URL。

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
         http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">

    <!-- (1)从类路径下加载Spring配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:application-context.xml</param-value>
    </context-param>

    <!-- (2)负责启动 Spring 容器的监听器,它将引用(1)处的上下文参数获得Spring配置文件的地址 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- (3)配置DispatcherServlet -->
    <servlet>
        <servlet-name>web</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <!-- (4)指定处理的URL路径 -->
    <servlet-mapping>
        <servlet-name>web</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>
复制代码
  1. 在(1)处,通过contextConfigLocation参数指定业务层Spring容器的配置文件(多个配置文件用 , 分割)。
  2. 在(2)处,ContextLoaderListener是一个ServletLoaderListener,它通过contextConfigLocation指定的Spring配置文件启动业务层的Spring容器。
  3. 在(3)处,配置了名为web的DispatcherServlet,它默认加载/WEB-INF/web-servlet.xml(-servlet.xml)的Spring配置文件,启动Web层的Spring容器。Web层容器将作为业务层容器的子容器,Web层容器可以访问业务层容器的Bean,而业务层容器访问不了Web层容器的Bean。
  4. 在(4)处,通过指定DispatcherServlet处理 /* 全部的HTTP请求。一个web.xml可以配置多个DispatcherServlet,通过其对应的配置,让每个DispatcherServlet处理不同的请求。

DispatcherServlet 的配置参数

可以通过的属性指定配置参数:

  1. namespace参数:DispatcherServlet对应的命名空间,默认是WEB-INF/-servlet.xml。在显式配置该参数后,新的配置文件对应的路径是WEB-INF/.xml,例如如果将namespace设置为sample,则对应的Spring配置文件为WEB-INFmple.xml。
  2. contextConfigLocation:如果DispatcherServlet上下文对应的Spring配置文件有多个,则可以使用该属性按照Spring资源路径的方式指定,如classpath:sample1.xml,classpath:sample2.xml。
  3. publishContext:默认为true。DispatcherServlet根据该属性决定是否将WebApplicationContext发布到ServletContext的属性列表中,方便调用者可借由ServletContext找到WebApplicationContext实例,对应的属性名为DispatcherServlet#getServletContextAttributeName()的返回值。
  4. publishEvents:默认为true。当DispatcherServlet处理完一个请求后,是否需要向容器发布一个ServletRequestHandleEvent事件。

Spring容器配置

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.0.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">

    <context:component-scan base-package="com.ankeetc.web"/>
    <!-- 会自动注册RequestMappingHandlerMapping与RequestMappingHandlerAdapter两个Bean,这是SpringMVC为@Controllers分发请求所必需的 -->
    <!-- 并提供了数据绑定支持、@NumberFormatannotation支持、 @DateTimeFormat支持、@Valid支持、读写XML的支持和读写JSON的支持等功能。 -->
    <mvc:annotation-driven />

</beans>
复制代码

基于编程的配置

Spring 4.0已经全面支持Servlet 3.0,可以使用编程的方式配置Servlet容器。在Servlet 3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果发现实现类,就会用它来配置Servlet容器。Spring提供了这个接口的实现,名为SpringServletContainerInitializer,这个类反过来又查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring还提供了一个WebApplicationInitializer基础实现类AbstractAnnotationConfigDispatcherServletInitializer,使得它在注册DispatcherServlet时只需要简单地指定它的Servlet映射即可。

public class WebApplicationInitilalizer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        ServletRegistration.Dynamic registration = servletContext.addServlet("web", new DispatcherServlet());
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}
复制代码

DispatcherServlet的内部逻辑

protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }
复制代码

DispatcherServlet#initStrategies()方法将在WebApplicationContext初始化后执行,此时Spring上下文中的Bean已经初始化完毕,该方法通过反射查找并装配Spring容器中用户自定义的Bean,如果找不到就装配默认的组件实例。

默认组件

在DispatcherServlet.properties配置文件里边,指定了DispatcherServlet所使用的默认组件。如果用户希望采用非默认的组件,只需在Spring配置文件中配置自定义的组件Bean即可。

# 本地化解析器
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

# 主题解析器
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

# 处理器解析器
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

# 处理器适配器
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

# 异常处理器
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

# 视图名称处理器
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

# 视图解析器
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
复制代码

DispatcherServlet装配各型组件的逻辑

二、注解驱动的控制器

@RequestMapping映射请求

  1. 在POJO类上标注@Controller,再通过 context:component-scan 扫描到该类,可以是POJO成为一个能处理HTTP请求的控制器。
  2. 在控制器的类定义和方法定义处都可以使用@RequestMapping映射对应的处理方法。
  3. @RequestMapping不但支持标准的URL,还支持Ant风格和{XXX}占位符的URL。
  4. @RequestMapping和value、method、params及headers分别表示请求路径、请求方法、请求参数及报文头的映射条件。

获取请求内容

org.springframework.web.context.request

使用HttpMessageConverter

HttpMessageConverter接口可以将请求信息转换为一个对象(类型为T),并将对象(类型为T)绑定到请求方法的参数中或输出为响应信息。DispatcherServlet默认已经安装了RequestMethodHandlerAdapter作为HandlerAdapter组件的实现类,HttpMessageConverter即由RequestMethodHandlerAdapter使用,将请求信息转换为对象,或将对象转换为响应信息。

HttpMessageConverter的实现类

Spring为HttpMessageConverter提供了众多的实现类:

实现类

实现类

默认的HttpMessageConverter

RequestMappingHandlerAdapter已经默认装配了以下的HttpMessageConverter:

  • StringHttpMessageConverter
  • ByteArrayHttpMessageConverter
  • SourceHttpMessageConverter
  • AllEncompassingFormHttpMessageConverter

装配其他类型的HttpMessageConverter

如果需要装配其他类型的HttpMessageConverter,可以在Spring的Web容器上下文中自行定义一个RequestMappingHandlerAdapter,注册若干HttpMessageConverter。如果在Spring web容器中显式定义了一个RequestMappingHandlerAdapter,则Spring MVC将使用它 覆盖 默认的RequestMappingHandlerAdapter。

使用HttpMessageConverter

  1. 可以使用@RequestBody、@ResponseBody对处理方法进行标注
  2. 可以使用HttpEntity、ResponseEntity作为处理方法的入参或返回值

RestTemplate是Spring的模板类,可以使用该类调用Web服务端的服务,它支持Rest风格的URL。

结论

  1. 当控制器处理方法使用到@RequestBody、@ResponseBody 或 HttpEntity、ResponseEntity 时,Spring MVC才会使用注册的HttpMessageConvertor对请求、相应消息进行处理。
  2. 当控制器处理方法使用到@RequestBody、@ResponseBody 或 HttpEntity、ResponseEntity时,Spring 首先根据请求头或响应的Accept属性选择匹配的 HttpMessageConverter,进而根据参数类型或泛型类型的过滤得到匹配的 HttpMessageConverter,若找不到可用的 HttpMessageConverter 将报错。
  3. @RequestBody、@ResponseBody不需要成对出现。

处理XML和JSON

Spring MVC提供了几个处理XML和JSON格式的请求、响应消息的HttpMessageConverter:

  • MarshallingHttpMessageConverter:处理XML
  • Jaxb2RootElementHttpMessageConverter:处理XML,底层使用JAXB
  • MappingJackson2HttpMessageConverter:处理JSON格式

只要在Spring Web容器中为RequestMappingHandlerAdapter装配好相应的HttpMessageConverter,并在交互中通过请求的Accept指定MIME类型,Spring MVC就可以是服务器端的处理方法和客户端透明的通过XML或JSON格式进行通信。

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
                <bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"/>
                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
            </list>
        </property>
    </bean>
复制代码

使用@RestController

@RestController已经标注了@ResponseBody和@Controller,可以直接在控制器上标注该注解,就不用在每个@RequestMapping方法上添加@ResponseBody了。

AsyncRestTemplate

Spring 4.0提供了AsyncRestTemplate用于以异步无阻塞的方式进行服务访问。

public class WebApplicationInitilalizer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        ServletRegistration.Dynamic registration = servletContext.addServlet("web", new DispatcherServlet());
        registration.setLoadOnStartup(1);
        // 此处要设置为true
        registration.setAsyncSupported(true);
        registration.addMapping("/");
    }
}

@RestController
public class AsyncController {

    @RequestMapping(value = "/async", method = RequestMethod.GET)
    public Callable<String> async() {
        System.out.println("hello!");
        return new Callable<String>() {
            @Override
            public String call() throws Exception {
                TimeUnit.SECONDS.sleep(5);
                return "ASYNC";
            }
        };
    }
}
public class Main {
    public static void main(String[] args) {
        AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate();

        ListenableFuture<ResponseEntity<String>> future = asyncRestTemplate.getForEntity("http://localhost:8080/async", String.class);

        System.out.println("return");
        future.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
            @Override
            public void onFailure(Throwable ex) {
                System.out.println("Failure");
            }

            @Override
            public void onSuccess(ResponseEntity<String> result) {
                System.out.println("Success");
            }
        });
    }
}
复制代码

处理模型数据

Spring MVC提供了多种途径输出模型数据:

  1. ModelAndView:当处理方法返回值类型为ModelAndView时,方法体即可通过该对象添加模型数据;
  2. @ModelAttribute:在方法入参标注该注解后,入参的对象就会放到数据模型中;
  3. Map和Model:如果方法入参为org.framework.ui.Model、org.framework.ui.ModelMap、java.util.Map,当处理方法返回时,Map中的数据会自动添加到模型中;
  4. @SessionAttributes:将模型中的某个属性暂存到HttpSession中,以便多个请求之间可以共享这个属性。

三、处理方法的数据绑定

Spring会根据请求方法签名的不同,将请求中的信息以一定方式转换并绑定到请求方法的入参中,还会进行数据转换、数据格式化及数据校验等。

数据绑定流程

数据绑定

Spring MVC通过反射对目标签名进行分析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是DataBinder。Spring MVC主框架将ServletRequest对象及处理方法的入参对象实例传递给 DataBinder,DataBinder 首先调用装配在 Spring Web 上下文中的 ConversionService 组件进行数据类型转换、数据格式化等工作,将ServletRequest中的消息填充到入参对象中, 然后调用Validator组件对已经绑定了请求消息数据的入参对象进行数据合法性校验,最 终生成数据绑定结果BindingResult对象。BindingResult包含了已完成数据绑定的入参 对象,还包含相应的校验错误对象。Spring MVC抽取BindingResult中的入参对象及校验错误对象,将它们赋给处理方法的相应入参。

数据转换

类型转换模块位于org.framework.core.convert包中,同时由于历史原因,Spring还支持JDK的PropertyEditor。

ConversionService简介

ConversionService 是 Spring 类型转换体系的核心接口,它定义了以下4个方法:

  • boolean canConvert(Class sourceType, Class targetType):判断是否可以将一个Java类转换为另一个Java类。
  • Boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType):需转换的类将以成员变量的方式出现在宿主类中。TypeDescriptor不但描述了需转换类的信息,还描述了从宿主类的上下文信息,如成员变量上的注解,成员变量是否以数组、集合或Map的方式呈现等。类型转换逻辑可以利用这些信息做出 各种灵活的控制。
  • T convert(Object source, Class targetType):将原类型对象转换为目标类型对象。
  • Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType):
    将对象从原类型对象转换为目标类型对象,此时往往会用到所在宿主类的上下 文信息。

第一个和第三个接口方法类似于PmpertyEditor,它们不关注类型对象所在的上下文 信息,只简单地完成两个类型对象的转换,唯一的区别在于这两个方法支持任意两个类型的转换。而第二个和第四个接口方法会参考类型对象所在宿主类的上下文信息,并利用这些信息进行类型转换。

使用ConversionService

可以利用 org.springframework.context.support.ConversionServiceFactoryBean 在 Spring 的 上下文中定义一个ConversionService。Spring将自动识别出上下文中的ConversionService, 并在Bean属性配置及Spring MVC处理方法入参绑定等场合使用它进行数据转换。该FactoryBean创建ConversionService内建了很多转换器,可完成大多数Java类型的转换工作。除了包括将String对象转换为各种基础类型的对象外,还包括String、 Number、Array、Collection、Map、Properties 及 Object 之间的转换器。可通过ConversionServiceFactoryBean的converters属性注册自定义的类型转换器:

<bean class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <list>
                <bean class="com.ankeetc.MyConverter"/>
            </list>
        </property>
    </bean>
复制代码

Spring支持的转换器

Spring 在 org.springframework.core.convert.converter 包中定义了3种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到ConversionServiceFactoryBean中。这3种类型的转换器接口分别为:

  • Converter<S, T>:将S类型的对象转换为T类型的对象
  • GenericConverter:根据源类对象及目标类对象所在的宿主类的上下文信息进行类型转换工作。该类还有一个子接口ConditionalGenericConverter,它添加了一个接口方法根据源类型及目标类型所在宿主类的上下文信息决定是否要进行类型转换。
  • ConverterFactory:

ConversionServiceFactoryBean 的 converters 属性可接受 Converter、ConverterFactory、 GenericConverter或ConditionalGenericConverter接口的实现类,并把这些转换器的转换逻辑统一封装到一个 ConversionService 实例对象中(GenericConversionService)。Spring 在Bean属性配置及Spring MVC请求消息绑定时将利用这个ConversionService实例完成类型转换工作。

在Spring中使用@lnitBinder 和 WebBindingInitializer装配自定义编辑器

Spring也支持JavaBeans的PropertyEditor。可以在控制器中使用@InitBinder添加自定义的编辑器,也可以通过 WebBindingInitializer 装配在全局范围内使用的编辑器。

@InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(User.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                User user = new User();
                user.setName(text);

                this.setValue(user);
            }
        });
    }
复制代码

如果希望在全局范围内使用,则可实现WebBindingInitializer接口并在该实现类中注册。

  1. 实现WebBindingInitializer接口并在initBinder接口方法中注册了自定义的编辑器。
  2. 在 Spring 上下文中通过 RequestMappingHandlerAdapter 装配自定义的Initializer。

顺序

对于同一个类型对象来说,如果既在ConversionService中装配了自定义转换器,又通过WebBindinglnitializer装配了自定义编辑器,同时还在控制器中通过@InitBinder装 配了自定义编辑器,那么Spring MVC将按以下优先顺序查找对应类型的编辑器:

  1. 查询通过@InitBinder装配的自定义编辑器。
  2. 查询通过ConversionService装配的自定义转换器。
  3. 查询通过WebBindingInitializer装配的自定义编辑器。

数据格式化

Spring的转换器并不提供输入及输出信息格式化的工作,一般需要转换的源类型数据(一般是字符串)都是具有一定格式的,在不同的本地化环境中, 同一类型的数据还会相应地呈现不同的显示格式。Spring引入了一个新的格式化框架,这个框架位于org.springframework.format类包中。

最重要的 Formatter接口

注解驱动格式化AnnotationFormatterFactory

为了让注解和格式化的属性类型关联起来,Spring在Formatter所在的包中还提供了一个 AnnotationFormatterFactory 接口。

启用注解驱动格式化功能

对属性对象的输入/输出进行格式化,从本质上讲依然属于“类型转换”的范畴。 Spring就是基于对象转换框架植入“格式化”功能的。Spring 在格式化模块中定义了一个实现 ConversionService 接口的 FormattingConversionService实现类,该实现类扩展了 GenericConversionService,因此它既具有类型转换功能,又具有格式化功能。

FormattingConversionService 也拥有一个对应的 FormattingConversionServiceFactoryBean 工厂类,后者用于在Spring上下文中构造一个FormattingConversionService。通过这个工厂类,既可以注册自定义的转换器,还可以注册自定义的注解驱动逻辑。由于 FormattingConversionServiceFactoryBean 在内部会自动注册 NumberFormatAnnotationFormatterFactory 和 JodaDateTimeFormatAnnotationFormatterFactory,因此装配了 FormattingConversionServiceFactoryBean 后,就可以在 Spring MVC 入参绑定及模型数据输出时使用注解驱动的格式化功能。

值得注意的是,

mvc:annotation-driven/ 标签内部默认创建的ConversionService实例就是一个 FormattingConversionServiceFactoryBean。

数据校验

Spring拥有自己独立的数据校验框架,同时支持JSR-303标准的校验框架。Spring 的DataBinder在进行数据绑定时,可同时调用校验框架完成数据校验工作。在Spring MVC中,则可直接通过注解驱动的方式进行数据校验。

LocalValidatorFactoryBean 既实现了 Spring 的 Validator 接口,又实现了 JSR-303 的 Validator 接口。只要在 Spring 容器中定义了一个 LocalValidatorFactoryBean,即可将其注入需要数据校验的Bean中。值得注意的是,Spring本身没有提供JSR-303的实现,所以必须将JSR-303的实现 者(如Hibernate Validator)的JAR文件放到类路径下,Spring将自动加载并装配好 JSR-303的实现者。

mvc:annotation-driven/

会默认装配一个 LocalValidatorFactoryBean,通过在处理方法的入参上标注@Valid注解,即可让Spring MVC在完成数据绑定后执行数据校验工作。

四、视图和视图解析器

五、本地化

Spring提供了以下4个本地化解析器。

  • AcceptHeaderLocaleResolver:根据 HTTP 报文头的 Accept-Language 参数确定本 地化类型。如果没有显式定义本地化解析器,则Spring MVC默认采用 AcceptHeaderLocaleResolver。
  • CookieLocaleResolver:根据指定的Cookie值确定本地化类型。
  • SessionLocaleResolver:根据Session中特定的属性值确定本地化类型。
  • LocaleChangeInterceptor:从请求参数中获取本次请求对应的本地化类型。

六、文件上传

Spring MVC为文件上传提供了直接支持,这种支持是通过即插即用的MultipartResolver 实现的。Spring 使用 Jakarta Commons FileUpload 技术实现了一个 MultipartResolver 实现 类:CommonsMultipartResolver。

在Spring MVC上下文中默认没有装配MultipartResolver,因此默认情况下不能 处理文件的上传工作。如果想使用Spring的文件上传功能,则需要先在上下文中配置 MultipartResolver。

七、WebSocket

八、静态资源处理

  1. mvc:default-servlet-handler/ :在 smart-servlet.xml 中配置 mvc:default-servlet-handler/ 后,会在 Spring MVC 上下文中定义一个 org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它将充当一个检查员的角色,对进入DispatcherServlet的URL进行筛查。如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理;如果不是静态资源 的请求,则由DispatcherServlet继续处理。
  2. mvc:resources/ : mvc:default-servlet-handler/ 将静态资源的处理经由Spring MVC框架交回Web应 用服务器。而 mvc:resources/ 更进一步,由SpringMVC框架自己处理静态资源,并添 加一些有用的附加功能。

九、拦截器

当收到请求时,DispatcherServlet将请求交给处理器映射(HandlerMapping),让它找出对应该请求的HandlerExecutionChain对象。在讲解HandlerMapping之前,有必要 认识一下这个 HandlerExecutionChain 对象。

HandlerExecutionChain

HandlerExecutionChain负责处理请求并返回ModelAndView的处理执行链,它包含一个处理该请求的处理器 (Handler),同时包括若干个对该请求实施拦截的拦截器(HandlerInterceptor)。当 HandlerMapping 返回 HandlerExecutionChain 后,DispatcherServlet 将请求交给定义在 HandlerExecutionChain中的拦截器和处理器一并处理。

位于处理器链末端的是一个 Handler,DispatcherServlet通过 Handler Adapter适配器对 Handler进行封装,并按统一的适配器接口对 Handler处理方法进行调用。可以在 web-servlet.xml 中配置多个拦截器,每个拦截器都可以指定一个匹配的映射路径,以限制拦截器的作用范围。

十、异常处理

Spring MVC通过 HandlerExceptionResolver处理程序的异常,包括处理器映射、数据绑定及处理器执行时发生的异常。 HandlerExceptionResolver仅有一个接口方法:Modelandview resolveException(HttpServletRequest request HttpServletResponse response Object handler, Exception ex)。当发生异常时,Spring MVC将调用 resolveException方法,并转到 ModelAndView 对应的视图中,作为一个异常报告页面反馈给用户。

实现类

HandlerExceptionResolver拥有4个实现类

  1. DefaultHandlerExceptionResolver:默认装配了该类,将对应异常转换为错误码
  2. SimpleMappingExceptionResolver:对所有异常进行统一处理
  3. AnnotationMethodHandlerExceptionResolver:默认注册了该类,允许通过@ExceptionHandler注解指定处理特定的异常
  4. ResponseStatusExceptionResolver

文章到这里就结束了

小编这里总结一份springMVC的思维导图,想了解的小伙伴可以看看呢

喜欢小编分享的技术文章可以点赞关注哦!

小编这边整理了一些spring MVC的技术资料以及面试题

原文链接: https://juejin.cn/post/6902238540466225159

如果觉得本文对你有帮助,可以转发关注支持一下

我们的开发工程中经常会使用到各种图,所谓的图就是由节点和节点之间的连接所形成的系统,数学上专门有一个分支叫图论(Graph Theroy)。利用图我们可以做很多工具,比如思维导图,流程图,状态机,组织架构图,等等。今天我要做的是用开源的HTML5工具来快速构造一个做图的工具。

工具选择

工预善其事,必先利其器。第一件事是选择一件合适的工具,开源时代,程序员还是很幸福的,选择很多。

  • flowchart.js http://adrai.github.io/flowchart.js/ , 基于SVG创建Flow Chart
  • go.js http://www.gojs.net/latest/index.html go.js 提供一整套的JS工具 ,支持各种交互式图表的创建。有免费版和收费版
  • joint.js http://www.jointjs.com/ joint.js 是另一个创建图标库的工具,也提供免费版和商业版
  • jsPlumb http://www.jsplumb.org/ jsPlumb是一套开源的流程图创建工具 ,小巧精悍,使用简单
  • d3 http://d3js.org 在html5领域,d3可谓是最好的可视化基础库,提供方面的DOM操作,非常强大。

最终,我选择了jsPlumb,因为它完全开源,使用很简单,用D3的话可能会多花很多功夫。joint.js也不错。大家可以根据自己的需要选择。

构建静态应用

下面我们一步一步的来使用jsPlumb来创建我们的流程图工具。

第一步是等待DOM和jsPlumb初始化完毕,类似document.ready()和jquery.ready(), 要使用jsPlumb, 需要把代码放在这个函数里:

jsPlumb.ready(function() {
    // ... your code goes here ...
}


创建一个jsPlumb的实例,并初始化jsPlumb的配置参数:

//Initialize JsPlumb
var color = "#E8C870";
var instance = jsPlumb.getInstance({
    // notice the 'curviness' argument to this Bezier curve.  the curves on this page are far smoother
    // than the curves on the first demo, which use the default curviness value.      
    Connector : [ "Bezier", { curviness:50 } ],
    DragOptions : { cursor: "pointer", zIndex:2000 },
    PaintStyle : { strokeStyle:color, lineWidth:2 },
    EndpointStyle : { radius:5, fillStyle:color },
    HoverPaintStyle : {strokeStyle:"#7073EB" },
    EndpointHoverStyle : {fillStyle:"#7073EB" },
    Container:"container-id"
 });


这里给给出了一些配置包括,连接线(这里配置了一个贝塞尔曲线),线的风格,连接点得风格。Container需要配置一个对应的DIV容器的id。(这里也可以使用setContainer的方法)

下面我们要创建一个节点(node),每一个节点可以用一个DIV来实现。我这里提供了一个函数来创建节点。

function addNode(parentId, nodeId, nodeLable, position) {
  var panel = d3.select("#" + parentId);
  panel.append('div').style('width','120px').style('height','50px')
    .style('position','absolute')
    .style('top',position.y).style('left',position.x)
    .style('border','2px #9DFFCA solid').attr('align','center')
    .attr('id',nodeId).classed('node',true)
    .text(nodeLable);

  return jsPlumb.getSelector('#' + nodeId)[0];
}


这里做的事情就是创建了一个DIV元素,并放在对应的容器的制定位置上,注意为了支持拖拽的功能,必须使用position:absolute 。

我使用D3来操作DOM,大家可能会更习惯JQuery,这纯属个人喜好的问题。

最后返回创建节点的实例引用,这是的selector使用了jsPlumb.getSelector()方法,它和JQuery的selector是一样的,这样用的好处是你可以使用不同的DOM操作库,例如Vanilla

下面我使用一个函数来创建端点/锚点(anchor),锚点就是节点上的连接点,用于连接不同的节点。

function addPorts(instance, node, ports, type) {
  //Assume horizental layout
  var number_of_ports = ports.length;
  var i = 0;
  var height = $(node).height();  //Note, jquery does not include border for height
  var y_offset = 1 / ( number_of_ports + 1);
  var y = 0;

  for ( ; i < number_of_ports; i++ ) {
    var anchor = [0,0,0,0];
    var paintStyle = { radius:5, fillStyle:'#FF8891' };
    var isSource = false, isTarget = false;
    if ( type === 'output' ) {
      anchor[0] = 1;
      paintStyle.fillStyle = '#D4FFD6';
      isSource = true;
    } else {
      isTarget =true;
    }

    anchor[1] = y + y_offset;
    y = anchor[1];

    instance.addEndpoint(node, {
      uuid:node.getAttribute("id") + "-" + ports[i],
      paintStyle: paintStyle,
      anchor:anchor,
      maxConnections:-1,
      isSource:isSource,
      isTarget:isTarget
    });
  }
}


instance是jsPlumb的实例

node是我们用addNode方法创建的Node实例

ports,是一个string的数组,指定端点的个数和名字

type,可能是output或者input,指定端点的种类,一个节点的输出端口可以连接另一个节点的输入端口。

这里anchor是一个四维数组,0维和1维分别是锚点在节点x轴和y轴的偏移百分比。我这里希望把端口画在节点的左右两侧,并按照端口的数量均匀分布。

最后使用instance.addEndpoint来创建端点。注意这里只要指定isSource和isTarget就可以用drag&drop的方式来连接端点,非常方便。

下面一步我们提供一个函数来连接端点:

function connectPorts(instance, node1, port1, node2 , port2) {
  // declare some common values:
  var color = "gray";
  var arrowCommon = { foldback:0.8, fillStyle:color, width:5 },
  // use three-arg spec to create two different arrows with the common values:
  overlays = [
    [ "Arrow", { location:0.8 }, arrowCommon ],
    [ "Arrow", { location:0.2, direction:-1 }, arrowCommon ]
  ];

  var uuid_source = node1.getAttribute("id") + "-" + port1;
  var uuid_target = node2.getAttribute("id") + "-" + port2;

  instance.connect({uuids:[uuid_source, uuid_target]});
}


node1和node2是源节点和目标节点的引用,port1和port2是源端口和目标端口的名字。

使用instance.connect方法来创建连接。 overlays用来添加连接线的箭头效果或者其他风格,我这里没有使用,因为觉得都不是很好看。大家如果要用,只要把overlays加入到instance.connect的方法参数就可以了。

调用以上方法来创建节点,端点和连接线。

var node1 = addNode('container-id','node1', 'node1', {x:'80px',y:'20px'});
var node2 = addNode('container-id','node2', 'node2', {x:'280px',y:'20px'});

addPorts(instance, node1, ['out1','out2'],'output');
addPorts(instance, node2, ['in','in1','in2'],'input');

connectPorts(instance, node1, 'out2', node2, 'in');


这里我们创建了两个节点,第一个节点有两个输出端口,第二个节点有三个输入端口,然后把第一个节点的out2端口连接到第二个端点的in端口。效果如下:

最后我们给节点增加drag&drop的功能,这样我们就可以拖动这些节点来改变图的布局了。

instance.draggable($('.node'));


这里似乎依赖于JQuery-UI,我还不是很清楚。

交互式创建节点

我们已经初步具有了创建图的功能,可是节点的创建必须通过程序,我们希望用交互的方式来创建节点。

通常我们希望有一个tree view的控件,让后通过拖拽来创建对应类型的节点。这里我使用了这个开源的tree view,基于bootstrap https://github.com/jonmiles/bootstrap-treeview

我们先创建一个tree view:

function getTreeData() {
  var tree = [
    {
      text: "Nodes",
      nodes: [
        {
          text: "Node1",
        },
        {
          text: "Node2"
        }
      ]
    }
  ]; 

  return tree;
}
//Initialize Control Tree View
$('#control-panel').treeview({data: getTreeData()});


树上有两个节点:

然后我实现从树上拖拽对应的节点,到流程图上的逻辑。

//Handle drag and drop
$('.list-group-item').attr('draggable','true').on('dragstart', function(ev){
  //ev.dataTransfer.setData("text", ev.target.id);
  ev.originalEvent.dataTransfer.setData('text',ev.target.textContent);
  console.log('drag start');
});

$('#container-id').on('drop', function(ev){
  //avoid event conlict for jsPlumb
  if (ev.target.className.indexOf('_jsPlumb') >= 0 ) {
    return;
  }

  ev.preventDefault();
  var mx = '' + ev.originalEvent.offsetX + 'px';
  var my = '' + ev.originalEvent.offsetY + 'px';

  console.log('on drop : ' + ev.originalEvent.dataTransfer.getData('text'));
  var uid = new Date().getTime();
  var node = addNode('flow-panel','node' + uid, 'node', {x:mx,y:my});
  addPorts(instance, node, ['out'],'output');
  addPorts(instance, node, ['in1','in2'],'input');
  instance.draggable($(node));
}).on('dragover', function(ev){
  ev.preventDefault();
  console.log('on drag over');
});


这里要注意的是要避免和jsPlumb拖拽端点的逻辑冲突,当检测到target是jsPlumb对象是需要直接从drop方法中退出以执行对应的jsPlumb的drop逻辑。

好了,一个绘制流程图的软件工具初步完工。

我把代码放在oschina的代码托管服务上了, 大家有兴趣可以去试试。

ervlet容器主要是JavaWeb应用提供运行时环境,所以也可以称之为JavaWeb应用容器,或者Servlet/JSP容器。Servlet容器主要负责管理Servlet、JSP的生命周期以及它们的共享数据。

Servlet容器有哪些:

目前最流行的Servlet容器软件包括: Tomcat、Jetty、Jboss等。

Tomcat

Tomcat和IIS等Web服务器一样,具有处理HTML页面的功能,另外它还是一个Servlet和JSP容器,独立的Servlet容器是Tomcat的默认模式。不过,Tomcat处理静态HTML的能力不如Apache服务器。

Jetty

Jetty 是一个开源的servlet容器,它为基于Java的web容器,例如JSP和servlet提供运行环境。Jetty是使用Java语言编写的,它的API以一组JAR包的形式发布。开发人员可以将Jetty容器实例化成一个对象,可以迅速为一些独立运行(stand-alone)的Java应用提供网络和web连接。

Jboss

Jboss是一个基于J2EE的开放源代码的应用服务器。 JBoss代码遵循LGPL许可,可以在任何商业应用中免费使用。JBoss是一个管理EJB的容器和服务器,支持EJB 1.1、EJB 2.0和EJB3的规范。但JBoss核心服务不包括支持servlet/JSP的WEB容器,一般与Tomcat或Jetty绑定使用。

Servlet是和平台无关的服务器端组件(java编写的,跨平台),它运行在Servlet容器中。

Servlet容器负责Servlet和客户的通信以及调用Servlet的方法,Servlet和客户的通信采用“请求/响应”的模式 Servlet可完成如下功能:

1、创建并返回基于客户请求的动态HTML页面

2、创建可嵌入到现有HTML 页面中的部分HTML 页面(HTML 片段)

3、与其它服务器资源(如数据库或基于Java的应用程序)进行通信

Servlet容器响应客户请求过程: