SpringMVC
SpringMvc
SpringMvc简介说明
Spring Web MVC框架提供模型、视图、控制器架构和随时可用的组件,用于开发灵活且松散耦合的Web应用程序。MVC模式有助于分离应用程序的不同方面,如输入逻辑,业务逻辑和UI逻辑,同时在所有这些元素之间提供松散耦合。MVC全名是Model View Controller,是模型model、视图view、控制器controller的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。最简单的、最经典就是Jsp(view)、Servlet(controller)、JavaBean(model)。SpringMvc框架是为了解决传统MVC模式(Jsp +Servlet + JavaBean)的一些问题而出现的框架。
传统MVC模式问题:
1)所有的Servlet和Servlet映射都要配置在web.xml中,如果项目太大,web.xml就太庞大,并且不能实现模块化管理。
2)Servlet的主要功能就是接受参数、调用逻辑、跳转页面,但是像其他字符编码、文件上传等功能也要写在Servlet中。
3)接受参数比较麻烦,不能通过model接收,只能单个接收,接收完成后转换封装model。如:
String name = request.getParameter("name");
User user=new User;
user.setName(name);
4)跳转页面方式比较单一(forword、redirect),并且当页面名称发生改变时需要修改Servlet源代码。
SpringMvc术语说明
Controller:处理器、页面控制器,做的是MVC中的C的事情,但控制逻辑转移到前端控制器了,用于对请求进行处理。
HandlerMapping:请求到处理器的映射,如果映射成功返回一个HandlerExecutionChain对象(包含一个Handler处理器对象、多个HandlerInterceptor拦截器)对象;如BeanNameUrlHandlerMapping将URL与Bean名字映射,映射成功的Bean就是此处的处理器。
HandlerAdapter:HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器模式的应用,从而很容易支持很多类型的处理器;如SimpleControllerHandlerAdapter将对实现了Controller接口的Bean进行适配,并且调用处理器的handleRequest方法进行功能处理。
ViewResolver:ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术,如InternalResourceViewResolver将逻辑视图名映射为jsp视图。
LocalResover:本地化解析,因为Spring支持国际化,因此LocalResover解析客户端的Locale信息从而方便进行国际化。
ThemeResovler:主题解析,通过它来实现一个页面多套风格,即常见的类似于如软件皮肤效果。
MultipartResolver:文件上传解析,用于支持文件上传。
HandlerExceptionResolver:处理器异常解析,可以将异常映射到相应的统一错误界面,从而显示用户友好的界面(而不是给用户看到具体的错误信息)。
RequestToViewNameTranslator:当处理器没有返回逻辑视图名等相关信息时,自动将请求URL映射为逻辑视图名。
FlashMapManager:用于管理FlashMap的策略接口,FlashMap用于存储一个请求的输出,当进入另一个请求时作为该请求的输入,通常用于重定向场景。
SpringMvc前端控制器
1)前端控制器(DsipatcherServlet)是整个MVC框架中最为核心的一块,它主要用来拦截符合要求的外部请求,并把请求分发到不同的控制器去处理,根据控制器处理后的结果,生成相应的响应发送到客户端。
2)前端控制器既可以使用Filter实现(Struts2采用这种方式),也可以使用Servlet来实现(SpringMvc框架)。SpringMvc中的前端控制器就是DsipatcherServlet,在web.xml中配置好DispatcherServlet位置后,容器启动时会去WEB-INF文件夹下去找(默认[servlet-name]-servlet.xml)dispatcherServlet-servlet.xml,解析文件并初始化里面中的bean等,DispatcherServlet继承自抽象类FrameworkServlet,间接继承了HttpServlet (FrameworkServlet继承自HttpServletBean,而HttpServletBean继承自HttpServlet )。
3)在使用SpringMvc时,前端控制器会默认加载一些组件,具体配置在SpringMvc下DispatcherServlet.properties中。从配置文件可以看到DispatcherServlet在启动时会自动注册这些特殊的Bean,无需我们注册,如果我们注册了,默认的将不会注册。如BeanNameUrlHandlerMapping、SimpleControllerHandlerAdapter是不需要注册的,DispatcherServlet默认会注册这两个Bean。
SpringMvc视图解析器
当我们对SpringMvc控制的资源发起请求时,这些请求都会被SpringMvc的DispatcherServlet处理,接着Spring会分析看哪一个HandlerMapping定义的所有请求映射中存在对该请求的最合理的映射。然后通过该HandlerMapping取得其对应的Handler,接着再通过相应的HandlerAdapter处理该Handler。HandlerAdapter在对Handler进行处理之后会返回一个ModelAndView对象。在获得了ModelAndView对象之后,Spring就需要把该View渲染给用户,即返回给浏览器。在这个渲染的过程中,发挥作用的就是ViewResolver和View。当Handler返回的ModelAndView中不包含真正的视图,只返回一个逻辑视图名称的时候,ViewResolver就会把该逻辑视图名称解析为真正的视图View对象。View是真正进行视图渲染,把结果返回给浏览器的。SpringMvc用于处理视图最重要的两个接口是ViewResolver和View。ViewResolver的主要作用是把一个逻辑上的视图名称解析为一个真正的视图,SpringMvc中用于把View对象呈现给客户端的是View对象本身,而ViewResolver只是把逻辑视图名称解析为对象的View对象。View接口的主要作用是用于处理视图,然后返回给客户端。Spring为我们提供了非常多的视图解析器,下面将列举一些视图解析器:
1)AbstractCachingViewResolver:这是一个抽象类,这种视图解析器会把它曾经解析过的视图保存起来,然后每次要解析视图的时候先从缓存里面找,如果找到了对应的视图就直接返回,如果没有就创建一个新的视图对象,然后把它放到一个用于缓存的map中,接着再把新建的视图返回。使用这种视图缓存的方式可以把解析视图的性能问题降到最低。
2)UrlBasedViewResolver:它是对ViewResolver的一种简单实现,而且继承了AbstractCachingViewResolver,主要就是提供的一种拼接URL的方式来解析视图,它可以让我们通过prefix属性指定一个指定的前缀,通过suffix属性指定一个指定的后缀,然后把返回的逻辑视图名称加上指定的前缀和后缀就是指定的视图URL了。如prefix=/WEB-INF/jsps/,suffix=.jsp,返回的视图名称viewName=test/indx,则UrlBasedViewResolver解析出来的视图URL就是/WEB-INF/jsps/test/index.jsp。默认的prefix和suffix都是空串。URLBasedViewResolver支持返回的视图名称中包含redirect:前缀,这样就可以支持URL在客户端的跳转,如当返回的视图名称是”redirect:test.do”的时候,URLBasedViewResolver发现返回的视图名称包含”redirect:”前缀,于是把返回的视图名称前缀”redirect:”去掉,取后面的test.do组成一个RedirectView,RedirectView中将把请求返回的模型属性组合成查询参数的形式组合到redirect的URL后面,然后调用HttpServletResponse对象的sendRedirect方法进行重定向。同样URLBasedViewResolver还支持forword:前缀,对于视图名称中包含forword:前缀的视图名称将会被封装成一个InternalResourceView对象,然后在服务器端利用RequestDispatcher的forword方式跳转到指定的地址。使用UrlBasedViewResolver的时候必须指定属性viewClass,表示解析成哪种视图,一般使用较多的就是InternalResourceView,利用它来展现jsp,但是当我们使用JSTL的时候我们必须使用JstlView。下面是一段UrlBasedViewResolver的定义,根据该定义,当返回的逻辑视图名称是test的时候,UrlBasedViewResolver将把逻辑视图名称加上定义好的前缀和后缀,即“/WEB-INF/test.jsp”,然后新建一个viewClass属性指定的视图类型予以返回,即返回一个url为“/WEB-INF/test.jsp”的InternalResourceView对象。
3)InternalResourceViewResolver:它是URLBasedViewResolver的子类,所以URLBasedViewResolver支持的特性它都支持。在实际应用中InternalResourceViewResolver也是使用的最广泛的一个视图解析器。那么InternalResourceViewResolver有什么自己独有的特性呢?单从字面意思来看,我们可以把InternalResourceViewResolver解释为内部资源视图解析器,这就是InternalResourceViewResolver的一个特性。InternalResourceViewResolver会把返回的视图名称都解析为InternalResourceView对象,InternalResourceView会把Controller处理器方法返回的模型属性都存放到对应的request属性中,然后通过RequestDispatcher在服务器端把请求forword重定向到目标URL。比如在InternalResourceViewResolver中定义了prefix=/WEB-INF/,suffix=.jsp,然后请求的Controller处理器方法返回的视图名称为test,那么这个时候InternalResourceViewResolver就会把test解析为一个InternalResourceView对象,先把返回的模型属性都存放到对应的HttpServletRequest属性中,然后利用RequestDispatcher在服务器端把请求forword到/WEB-INF/test.jsp。这就是InternalResourceViewResolver一个非常重要的特性,我们都知道存放在/WEB-INF/下面的内容是不能直接通过request请求的方式请求到的,为了安全性考虑,我们通常会把jsp文件放在WEB-INF目录下,而InternalResourceView在服务器端跳转的方式可以很好的解决这个问题**。**下面是一个InternalResourceViewResolver的定义,根据该定义当返回的逻辑视图名称是test的时候,InternalResourceViewResolver会给它加上定义好的前缀和后缀,组成“/WEB-INF/test.jsp”的形式,然后把它当做一个InternalResourceView的url新建一个InternalResourceView对象返回。
4)XmlViewResolver:它继承自AbstractCachingViewResolver抽象类,所以它也是支持视图缓存的。XmlViewResolver需要给定一个xml配置文件,该文件将使用和Spring的bean工厂配置文件一样的DTD定义,所以其实该文件就是用来定义视图的bean对象的。在该文件中定义的每一个视图的bean对象都给定一个名字,然后XmlViewResolver将根据Controller处理器方法返回的逻辑视图名称到XmlViewResolver指定的配置文件中寻找对应名称的视图bean用于处理视图。该配置文件默认是/WEB-INF/views.xml文件,如果不使用默认值的时候可以在XmlViewResolver的location属性中指定它的位置。XmlViewResolver还实现了Ordered接口,因此我们可以通过其order属性来指定在ViewResolver链中它所处的位置,order的值越小优先级越高。
5)ResourceBundleViewResolver:它和XmlViewResolver一样,也是继承自AbstractCachingViewResolver,但是它缓存的不是视图,这个会在后面有说到。和XmlViewResolver一样它也需要有一个配置文件来定义逻辑视图名称和真正的View对象的对应关系,不同的是ResourceBundleViewResolver的配置文件是一个属性文件,而且必须是放在classpath路径下面的,默认情况下这个配置文件是在classpath根目录下的views.properties文件,如果不使用默认值的话,则可以通过属性baseName或baseNames来指定。baseName只是指定一个基名称,Spring会在指定的classpath根目录下寻找以指定的baseName开始的属性文件进行View解析,如指定的baseName是base,那么base.properties、baseabc.properties等等以base开始的属性文件都会被Spring当做ResourceBundleViewResolver解析视图的资源文件。
6)FreeMarkerViewResolver、VolocityViewResolver:这两个视图解析器都是UrlBasedViewResolver的子类。FreeMarkerViewResolver会把Controller处理方法返回的逻辑视图解析为FreeMarkerView,而VolocityViewResolver会把返回的逻辑视图解析为VolocityView。因为这两个视图解析器类似,所以这里我就只挑FreeMarkerViewResolver来做一个简单的讲解。FreeMarkerViewResolver和VilocityViewResolver都继承了UrlBasedViewResolver。对于FreeMarkerViewResolver而言,它会按照UrlBasedViewResolver拼接URL的方式进行视图路径的解析。但是使用FreeMarkerViewResolver的时候不需要我们指定其viewClass,因为FreeMarkerViewResolver中已经把viewClass定死为FreeMarkerView了。
SpringMvc视图解析链
1)在SpringMvc中可以同时定义多个ViewResolver视图解析器,然后它们会组成一个ViewResolver链。当Controller处理器方法返回一个逻辑视图名称后,ViewResolver链将根据其中ViewResolver的优先级来进行处理。所有的ViewResolver都实现了Ordered接口,在Spring中实现了这个接口的类都是可以排序的。在ViewResolver中是通过order属性来指定顺序的,默认都是最大值。所以我们可以通过指定ViewResolver的order属性来实现ViewResolver的优先级,order属性是Integer类型,order越小,对应的ViewResolver将有越高的解析视图的权利,所以第一个进行解析的将是ViewResolver链中order值最小的那个。
2)当一个ViewResolver在进行视图解析后返回的View对象是null的话就表示该ViewResolver不能解析该视图,这个时候如果还存在其他order值比它大的ViewResolver就会调用剩余的ViewResolver中的order值最小的那个来解析该视图,依此类推。当ViewResolver在进行视图解析后返回的是一个非空的View对象的时候,就表示该ViewResolver能够解析该视图,那么视图解析这一步就完成了,后续的ViewResolver将不会再用来解析该视图。当定义的所有ViewResolver都不能解析该视图的时候,Spring就会抛出一个异常。基于Spring支持的这种ViewResolver链模式,我们就可以在SpringMvc应用中同时定义多个ViewResolver,给定不同的order值,这样我们就可以对特定的视图特定处理,以此来支持同一应用中有多种视图类型。注意:像InternalResourceViewResolver这种能解析所有的视图,即永远能返回一个非空View对象的ViewResolver一定要把它放在ViewResolver链的最后面。
SpringMvc与Struts2
SpringMvc实现原理
概述一:
1、 用户发送请求至前端控制器DispatcherServlet。
2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet调用HandlerAdapter处理器适配器。
5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、 Controller执行完成返回ModelAndView。
7、 HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。
8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9、 ViewReslover解析后返回具体View。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、 DispatcherServlet响应用户。
概述二:
1、捕获:用户向服务器发送请求,请求被Spring前端控制器DispatcherServlet捕获。
2、查找handler:DispatcherServlet对请求URL进行解析,得到请求资源标识符URI。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象,包括Handler对象以及Handler对象对应的拦截器,最后以HandlerExecutionChain对象的形式返回。
3、执行handler:DispatcherServlet根据获得的Handler,选择一个合适的HandlerAdapter。 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller),Handler执行完成后,向DispatcherServlet返回一个ModelAndView对象。
4、选择ViewResolver:DispatcherServlet根据返回的ModelAndView,选择一个适合的ViewResolver,必须是已经注册到Spring容器中的ViewResolver
5、渲染返回:通过ViewResolver结合Model和View,来渲染视图,DispatcherServlet将渲染结果返回给客户端。
Struts2实现原理
概述说明:
1、客户端浏览器发送请求。
2、这个请求经过一系列的过滤器(Filter),这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin。
3、接着FilterDispatcher(StrutsPrepareAndExecuteFilter)被调用,FilterDispatcher询问ActionMapper来决定这个请求是否需要调用某个Action。
4、如果ActionMapper决定需要调用某个Action,FilterDispatcher(StrutsPrepareAndExecuteFilter)把请求的处理交给ActionProxy。
5、ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类。
6、ActionProxy创建一个ActionInvocation的实例。
7、ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
8、一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2框架中继承的标签,在这个过程中需要涉及到ActionMapper。
面试必背:
1、浏览器发送请求,经过一系列的过滤器后,到达核心过滤器StrutsPrepareAndExecuteFilter。
2、StrutsPrepareAndExecuteFilter通过ActionMapper判断当前的请求是否需要某个Action处理,如果不需要,则走原来的流程,如果需要则把请求交给ActionProxy来处理。
3、ActionProxy通过Configuration Manager询问框架的配置文件Struts.xml,找到需要调用的Action类。
4、创建一个ActionInvocation实例,来调用Action的对应方法来获取结果集的name,在调用前后会执行相关拦截器。
5、通过结果集的Name知道对应的结果集来对浏览器进行响应。
SpringMvc、Struts2区别
目前企业中使用SpringMvc的比例已经远远超过Struts2,那么两者到底有什么区别:
1)核心控制器(前端控制器、预处理控制器):对于使用过mvc框架的人来说这个词应该不会陌生,核心控制器的主要用途是处理所有的请求,然后对那些特殊的请求统一的进行处理,如字符编码、文件上传、参数接受、异常处理等等,SpringMvc核心控制器是Servlet,而Struts2是Filter。
2)控制器实例:SpringMvc理论上会比Struts快一些。SpringMvc是基于方法设计,而Sturts是基于对象,每次发一次请求都会实例一个action,每个action都会被注入属性,而Spring更像Servlet一样,只有一个实例,每次请求执行对应的方法即可,注意:由于是单例实例,所以应当避免全局变量的修改,这样会产生线程安全问题。
3)管理方式:大部分的公司的核心架构中,都会使用到Spring,而SpringMvc又是Spring中的一个模块,所以Spring对于SpringMvc的控制器管理更加简单方便,而且提供了全注解方式进行管理,各种功能的注解都比较全面,使用简单,而Struts2需要采用XML很多的配置参数来管理,虽然也可以采用注解,但是几乎没有公司那样使用。
4)参数传递:Struts2中自身提供多种参数接收,其实都是通过ValueStack进行传递和赋值,而SpringMvc是通过方法的参数进行接收。
5)学习难度:Struts很多技术点,比如拦截器、值栈及OGNL表达式,学习成本较高,SpringMvc比较简单,较少的时间都能上手。
6)intercepter的实现机制:Struts有自己的interceptor机制,SpringMvc用的是独立的AOP方式。这样导致Struts的配置文件量还是比SpringMvc大,虽然Struts的配置能继承,所以我觉得论使用上来讲,SpringMvc使用更加简洁,开发效率SpringMvc确实比Struts2高。
7)SpringMvc是方法级别的拦截,一个方法对应一个request上下文,而方法同时又跟一个url对应,所以说从架构本身上SpringMvc就容易实现restful url。Struts2是类级别的拦截,一个类对应一个request上下文,实现restful url比较费劲,因为Struts2 action的一个方法可以对应一个url,而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了。
8)SpringMvc的方法之间基本上独立的,独享request、response数据,请求数据通过参数获取,处理结果通过ModelMap交回给框架,方法之间不共享变量,而Struts2搞的就比较乱,虽然方法之间也是独立的,但其所有Action变量是共享的,这不会影响程序运行,却给我们编码,读程序时带来麻烦。
9)SpringMvc处理ajax请求,直接返回数据,方法中使用注解@ResponseBody,SpringMvc自动帮我们对象转换为JSON数据,而struts2是通过插件的方式进行处理。
10)在SpringMvc流行起来之前,Struts2在MVC框架中占核心地位,随着SpringMvc的出现,SpringMvc慢慢的取代Struts2,但是很多企业都是原来搭建的框架,使用Struts2较多。
SpringMvc模型绑定(数据绑定)
最常用的参数和返回类型应该是SpringMvc提供的抽象,比如Model、View和ModelView等概念,以及用来识别请求或者表示返回类型的标准,如@PathVariable和ResponseBody等,这些可以说时SpringMvc的核心。另外有些参数类型可能会经常使用,比如需要访问HttpSession或者HttpServletRequest等原生的Servlet API,则直接声明该类型的一个参数即可。以上类型即使在一个大型的SpringMvc项目中也不一定都需要用到,但是其中一些特性在特定的场景下非常有用。比如,如果控制器希望获取当前URL的主机名、端口号、资源类型(scheme)、上下文路径等信息,则在@RequestMapping方法的参数中增加一个UriComponentsBuilder类型即可。SpringMvc会将相关信息自动填充好,直接使用即可。使用@ModelAttribute、Model、Map、@SessionAttributes能便捷地将我们的业务数据封装到模型里并交由视图解析调用。
@RequestMapping
RequestMapping注解修饰的方法所支持的常见参数类型
1)请求或响应对象(Servlet API)。可以是任何具体的请求或响应类型的对象,比如,ServletRequest或HttpServletRequest对象。
2)HttpSession类型的会话对象(Servlet API)。使用该类型的参数将要求这样一个session的存在,因此这样的参数永不为null。
3)当前请求的地区信息(java.util.Locale),由已配置的最相关的地区解析器解析得到。在MVC的环境下,就是应用中配置的LocaleResolver或LocaleContextResolver。
4)与当前请求绑定的时区信息java.util.TimeZone(java 6以上的版本)或java.time.ZoneId(java 8),由LocaleContextResolver解析得到。
5)org.springframework.http.HttpMethod。可以拿到HTTP请求方法。
6)包装了当前被认证用户信息的java.security.Principal。
7)带@PathVariable标注的方法参数,其存放了URI模板变量中的值。
8)带@RequestParam标注的方法参数,其存放了Servlet请求中所指定的参数。参数的值会被转换成方法参数所声明的类型。
9)带@RequestHeader标注的方法参数,其存放了Servlet请求中所指定的HTTP请求头的值。参数的值会被转换成方法参数所声明的类型。
10)带@RequestBody标注的参数,提供了对HTTP请求体的存取。参数的值通过HttpMessageConverter被转换成方法参数所声明的类型。
11)带@RequestPart标注的参数,提供了对一个multipart/form-data请求块(request part)内容的存取。
12)HttpEntity<?>类型的参数,其提供了对HTTP请求头和请求内容的存取。请求流是通过HttpMessageConverter被转换成entity对象的。
13)java.util.Map或org.springframework.io.Model或org.springframework.ui.ModelMap类型的参数,用以增强默认暴露给视图层的模型model的功能。
14)org.springframework.web.servlet.mvc.support.RedirectAttributes类型的参数,用以指定重定向下要使用到的属性集以及添加flash属性(暂存在服务端的属性,它们会在下次重定向请求的范围中有效)。
15)命令或表单对象,它们用于将请求参数直接绑定到bean字段(可能是通过setter方法)。你可以通过@InitBinder标注和/或HanderAdapter的配置来定制这个过程的类型转换。RequestMappingHandlerAdapter类webBindingInitializer属性的文档。这样的命令对象,以及其上的验证结果,默认会被添加到模型model中,键名默认是该命令对象类的类名,比如some.package.OrderAddress类型的命令对象就使用属性名orderAddress类获取。ModelAttribute标注可以应用在方法参数上,用以指定该模型所用的属性名。
16)org.springframework.validation.Errors或org.springframework.validation.BindingResult验证结果对象,用于存储前面的命令或表单对象的验证结果(紧接其前的第一个方法参数)。BindingResult的特殊性:在参数列表中,Errors或BindingResult参数必须紧跟在其所绑定的验证对象后面。这是因为,在参数列表中允许有多于一个的模型对象,Spring会为它们创建不同的BindingResult实例。因此,下面这样的代码是不能工作的:
//BindingResult与@ModelAttribute错误的参数次序
@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result)
上例中,因为在模型对象Pet和验证结果对象BindingResult中间还插了一个Model参数,这是不行的。要达到预期的效果,必须调整一下参数的次序:
@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model)
对于一些带有required属性的标注(比如@RequestParam、@RequestHeader等),JDK 1.8的java.util.Optional可以作为被它们标注的方法参数。在这种情况下,使用java.util.Optional与required=false的作用是相同的。
17)org.springframework.web.bind.support.SessionStatus对象,用以标记当前的表单处理已结束。这将触发一些清理操作:@SessionAttributes在类级别标注的属性将被移除。
18)org.springframework.web.util.UriComponentsBuilder构造器对象,用于构造当前请求URL相关的信息,比如主机名、端口号、资源类型(scheme)、上下文路径、servlet映射中的相对部分(literal part)等。
RequestMapping注解修饰的方法所支持的常见返回类型
1)ModelAndView对象,其中model隐含填充了命令对象,以及标注了@ModelAttribute字段的存取器被调用所返回的值。
2)Model对象,其中视图名称默认由RequestToViewNameTranslator决定,model隐含填充了命令对象以及标注了@ModelAttribute字段的存取器被调用所返回的值。
3)Map对象,用于暴露model,其中视图名称默认由RequestToViewNameTranslator决定,model隐含填充了命令对象以及标注了@ModelAttribute字段的存取器被调用所返回的值。
4)View对象。其中model隐含填充了命令对象,以及标注了@ModelAttribute字段的存取器被调用所返回的值。handler方法也可以增加一个Model类型的方法参数来增强model。
5)String对象,其值会被解析成一个逻辑视图名。其中,model将默认填充了命令对象以及标注了@ModelAttribute字段的存取器被调用所返回的值。handler方法也可以增加一个Model类型的方法参数来增强model。
6)void。如果处理器方法中已经对response响应数据进行了处理(比如在方法参数中定义一个ServletResponse或HttpServletResponse类型的参数并直接向其响应体中写东西),那么方法可以返回void。handler方法也可以增加一个Model类型的方法参数来增强model。
7)如果处理器方法标注了ResponseBody,那么返回类型将被写到HTTP的响应体中,而返回值会被HttpMessageConverters转换成所方法声明的参数类型。
8)HttpEntity<?>或ResponseEntity<?>对象,用于提供对Servlet HTTP响应头和响应内容的存取。对象体会被HttpMessageConverters转换成响应流。
9)HttpHeaders对象,返回一个不含响应体的response。
10)如果返回类型不是Spring MVC默认识别的类型,则会被处理成model的一个属性并返回给视图,该属性的名称为方法级的@ModelAttribute所标注的字段名(或者以返回类型的类名作为默认的属性名)。model隐含填充了命令对象以及标注了@ModelAttribute字段的存取器被调用所返回的值。
@RequestParam
RequestParam绑定单个请求参数值,用于将请求参数区数据映射到功能处理方法的参数上。
public String login(@RequestParam String username)
//请求中包含username参数(如/login?username=张三),则自动传入。通过@RequestParam("username")明确告诉SpringMvc使用username进行入参。
public String login(@RequestParam("username") String username)
RequestParam注解参数:
value:参数名字,即入参的请求参数名字,如username表示请求的参数区中的名字为username的参数的值将传入。
required:是否必须,默认是true,表示请求中一定要有相应的参数,否则将报404错误码。
defaultValue:默认值,表示如果请求中没有同名参数时的默认值,默认值可以是SpEL表达式,如“#{systemProperties['java.vm.version']}”。
RequestParam案例说明:
1)表示请求中可以没有名字为username的参数,如果没有默认为null,此处需要注意如下几点:原子类型:必须有值,否则抛出异常,如果允许空值请使用包装类代替。Boolean包装类型:默认Boolean.FALSE,其他引用类型默认为null。
public String login(@RequestParam(value="username",required=false) String username)
2)表示如果请求中没有名字为username的参数,默认值为“张三”。
public String login(@RequestParam(value="username", required=true, defaultValue="张三") String username)
3)请求中有多个同名的,如给用户授权时,可能授予多个权限,首先看下如下代码:
public String getRoleList(@RequestParam(value="role") String roleList)
如果请求参数类似于url?role=admin&role=user,则实际roleList参数入参的数据为“admin,user”,即多个数据之间使用逗号分割,我们应该使用如下方式来接收多个请求参数:
public String getRoleList(@RequestParam(value="role") String[] roleList)
或
public String getRoleList(@RequestParam(value="list") List<String> list)
@PathVariable
@PathVariable绑定URI模板变量值,用于将请求URL中的模板变量映射到功能处理方法的参数上。如请求的URL为“控制器URL/users/123/topics/456”,则自动将URL中模板变量{userId}和{topicId}绑定到通过@PathVariable注解的同名参数上,即入参后userId=123、topicId=456。
@RequestMapping(value="/users/{userId}/topics/{topicId}")
public String testPathVariable(@PathVariable(value="userId") int userId,
@PathVariable(value="topicId") int topicId)
@CookieValue
@CookieValue也拥有和@RequestParam相同的三个参数,含义一样。
1)@CookieValue绑定Cookie数据值,用于将请求的Cookie数据映射到功能处理方法的参数上。如下配置将自动将JSESSIONID值入参到sessionId参数上,defaultValue表示Cookie中没有JSESSIONID时默认为空。
public String testCookie(@CookieValue(value="JSESSIONID", defaultValue="") String sessionId)
2)传入参数类型也可以是javax.servlet.http.Cookie类型。
public String testCookie(@CookieValue(value="JSESSIONID", defaultValue="") Cookie sessionId)
@RequestHeader
@RequestHeader也拥有和@RequestParam相同的三个参数,含义一样。@RequestHeader绑定请求头数据,用于将请求的头信息区数据映射到功能处理方法的参数上。如下配置将自动将请求头“User-Agent”值入参到userAgent参数上,并将“Accept”请求头值入参到accepts参数上。
@RequestMapping(value="/header")
public String testHeader(@RequestHeader("User-Agent") String userAgent,
@RequestHeader(value="Accept") String[] accepts)
@Value
@Value绑定SpEL表示式,用于将一个SpEL表达式结果映射到到功能处理方法的参数上。
public String testValue(@Value("#{systemProperties['java.vm.version']}") String jvmVersion)
@SessionAttributes
有时候我们需要在多次请求之间保持数据,一般情况需要我们明确的调用HttpSession的API来存取会话数据,如多步骤提交的表单。SpringMvc提供了@SessionAttributes进行请求间透明的存取会话数据。
//1、在控制器类头上添加@SessionAttributes注解
//@SessionAttributes(value = {"user"}) 标识将模型数据中的名字为“user”的对象存储到会话中(默认HttpSession),此处value指定将模型数据中的哪些数据(名字进行匹配)存储到会话中,此外还有一个types属性表示模型数据中的哪些类型的对象存储到会话范围内,如果同时指定value和types属性则那些名字和类型都匹配的对象才能存储到会话范围内。
@SessionAttributes(value = {"user"})
public class SessionAttributeController
//2、@ModelAttribute注解的方法进行表单引用对象的创建
@ModelAttribute("user")
public UserModel initUser()
//3、@RequestMapping注解方法的@ModelAttribute注解的参数进行命令对象的绑定
@RequestMapping("/session")
public String session(@ModelAttribute("user") UserModel user)
//4、通过SessionStatus的setComplete()方法清除@SessionAttributes指定的会话数据
@RequestMapping("/session")
public String session(@ModelAttribute("user") UserModel user, SessionStatus status) {
if(true) {
status.setComplete();
}
return "success";
}
包含@SessionAttributes的执行流程如下:
1)首先根据@SessionAttributes注解信息查找会话内的对象放入到模型数据中。
2)执行@ModelAttribute注解的方法:如果模型数据中包含同名的数据,则不执行@ModelAttribute注解方法进行准备表单引用数据,而是使用步骤1中的会话数据;如果模型数据中不包含同名的数据,执行@ModelAttribute注解的方法并将返回值添加到模型数据中。
3)执行@RequestMapping方法,绑定@ModelAttribute注解的参数:查找模型数据中是否有@ModelAttribute注解的同名对象,如果有直接使用,否则通过反射创建一个;并将请求参数绑定到该命令对象;此处需要注意:如果使用@SessionAttributes注解控制器类之后,步骤3一定是从模型对象中取得同名的命令对象,如果模型数据中不存在将抛出HttpSessionRequiredException Expected session attribute ‘user’(Spring3.1)或HttpSessionRequiredException Session attribute ‘user’ required - not found in session(Spring3.0)异常。
4)如果会话可以销毁了,如多步骤提交表单的最后一步,此时可以调用SessionStatus对象的setComplete()标识当前会话的@SessionAttributes指定的数据可以清理了,此时当@RequestMapping功能处理方法执行完毕会进行清理会话数据。
我们通过SpringMvc的源代码验证一下,此处我们分析的是Spring3.1的RequestMappingHandlerAdapter:
1)RequestMappingHandlerAdapter.invokeHandlerMethod
//1、RequestMappingHandlerAdapter首先调用ModelFactory的initModel方法准备模型数据
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
//2、调用@RequestMapping注解的功能处理方法
requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
//3、更新/合并模型数据
modelFactory.updateModel(webRequest, mavContainer);
2)ModelFactory.initModel
Map<String, ?> attributesInSession = this.sessionAttributesHandler.retrieveAttributes(request);
//1、将与@SessionAttributes注解相关的会话对象放入模型数据中
mavContainer.mergeAttributes(attributesInSession);
//2、调用@ModelAttribute方法添加表单引用对象
invokeModelAttributeMethods(request, mavContainer);
//3、验证模型数据中是否包含@SessionAttributes注解相关的会话对象,不包含抛出异常
for (String name : findSessionAttributeArguments(handlerMethod)) {
if (!mavContainer.containsAttribute(name)) {
//4、此处防止在@ModelAttribute注解方法又添加了会话对象
//如在@ModelAttribute注解方法调用session.setAttribute("user", new UserModel());
Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
if (value == null) {
throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
}
mavContainer.addAttribute(name, value);
}
3)ModelFactory.invokeModelAttributeMethods
for (InvocableHandlerMethod attrMethod : this.attributeMethods) {
String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value();
//1、如果模型数据中包含同名数据则不再添加
if (mavContainer.containsAttribute(modelName)) {
continue;
}
//2、调用@ModelAttribute注解方法并将返回值添加到模型数据中,此处省略实现代码
}
4)requestMappingMethod.invokeAndHandle调用功能处理方法,此处省略
5)ModelFactory.updateMode更新模型数据
//1、如果会话被标识为完成,此时从会话中清除@SessionAttributes注解相关的会话对象
if (mavContainer.getSessionStatus().isComplete()){
this.sessionAttributesHandler.cleanupAttributes(request);
}
//2、如果会话没有完成,将模型数据中的@SessionAttributes注解相关的对象添加到会话中
else {
this.sessionAttributesHandler.storeAttributes(request, mavContainer.getModel());
}
//省略部分代码
多步骤提交表单需要考虑会话超时问题,这种方式可能对用户不太友好,我们可以采取隐藏表单(即当前步骤将其他步骤的表单隐藏)或表单数据存数据库(每步骤更新下数据库数据)等方案解决。
@ModelAttribute
@ModelAttribute具有如下三个作用:
1)绑定请求参数到命令对象:放在功能处理方法的入参上时,用于将多个请求参数绑定到一个命令对象,从而简化绑定流程,而且自动暴露为模型数据用于视图页面展示时使用。
2)暴露表单引用对象为模型数据:放在处理器的一般方法(非功能处理方法)上时,是为表单准备要展示的表单引用对象,如注册时需要选择的所在城市等,而且在执行功能处理方法(@RequestMapping注解的方法)之前,自动添加到模型对象中,用于视图页面展示时使用。
3)暴露@RequestMapping方法返回值为模型数据:放在功能处理方法的返回值上时,是暴露功能处理方法的返回值为模型数据,用于视图页面展示时使用。
绑定请求参数到命令对象
1)如用户登录,我们需要捕获用户登录的请求参数(用户名、密码)并封装为用户对象,此时我们可以使用@ModelAttribute绑定多个请求参数到我们的命令对象。
public String login(@ModelAttribute("user") UserModel user)
它的作用是将该绑定的命令对象以“user”为名称添加到模型对象中供视图页面展示使用。我们此时可以在视图页面使用${user.username}来获取绑定的命令对象的属性。
2)绑定请求参数到命令对象支持对象图导航式的绑定,如请求参数包含“?username=zhang&password=123&workInfo.city=bj”自动绑定到user中的workInfo属性的city属性中。
@RequestMapping(value="/login/{username}")
public String login(@ModelAttribute("model") DataBinderTestModel model)
URI模板变量也能自动绑定到命令对象中,当你请求的URL中包含“bool=yes&schooInfo.specialty=computer&hobbyList[0]=program&hobbyList[1]=music&map[key1]=value1&map[key2]=value2&state=blocked”会自动绑定到命令对象上。当URI模板变量和请求参数同名时,URI模板变量具有高优先权。
暴露表单引用对象为模型数据
1)在执行功能处理方法之前执行,并将其自动添加到模型对象中,在功能处理方法中调用Model入参的containsAttribute("cityList")将会返回true。
@ModelAttribute("cityList")
public List<String> cityList() {
return Arrays.asList("北京", "山东");
}
2)如你要修改用户资料时一般需要根据用户的编号或用户名查找用户来进行编辑,此时可以通过如下代码查找要编辑的用户。
@ModelAttribute("user")
public UserModel getUser(@RequestParam(value="username", defaultValue="") String username) {
//TODO 去数据库根据用户名查找用户对象
UserModel user = new UserModel();
user.setRealname("zhang");
return user;
}
如果有同名的命令对象,那SpringMvc内部如何处理的呢:
1、首先执行@ModelAttribute注解的方法,准备视图展示时所需要的模型数据;@ModelAttribute注解方法形式参数规则和@RequestMapping规则一样,如可以有@RequestParam等。
2、执行@RequestMapping注解方法,进行模型绑定时首先查找模型数据中是否含有同名对象,如果有直接使用,如果没有通过反射创建一个,如下的user将使用上面返回的命令对象。即下面的user等于上面的user。
@RequestMapping(value="/getUser")
public String getUser(@ModelAttribute("user") UserModel user, Model model)
暴露@RequestMapping方法返回值为模型数据
返回值类型是命令对象类型,而且通过@ModelAttribute("user")注解,此时会暴露返回值到模型数据(名字为user)中供视图展示使用。此时SpringMvc会根据RequestToViewNameTranslator进行逻辑视图名的翻译。当@RequestMapping注解方法的入参user暴露到模型数据中的名字也是user:返回值会覆盖方法中的同名参数对象。
public @ModelAttribute("user") UserModel getUser(@ModelAttribute("user") UserModel user)
匿名绑定命令参数
1)此时我们没有为命令对象提供暴露到模型数据中的名字,SpringMvc会自动将简单类名(首字母小写)作为名字暴露,如“UserModel”暴露的名字为“userModel”。
public String getUser(@ModelAttribute UserModel user, Model model)
或
public String getUser(UserModel user, Model model)
2)对于集合类型(Collection接口的实现者们,包括数组),生成的模型对象属性名为“简单类名(首字母小写)”+“List”,如List<String>生成的模型对象属性名为“stringList”,List<UserModel>生成的模型对象属性名为“userModelList”。
public @ModelAttribute List<String> getUser()
或
public @ModelAttribute List<UserModel> getUser()
3)其他情况一律都是使用简单类名(首字母小写)作为模型对象属性名,如Map<String, UserModel>类型的模型对象属性名为“map”。