本文共 36082 字,大约阅读时间需要 120 分钟。
Spring MVC框架学习总结
——田超凡
一、MVC设计模式
MVC设计模式是软件工程中的一种架构模式,他强制性的使软件的输入、处理和输出分开,把软件系统分成三个基本部分:模型(Model)、视图(View)、控制器(Controller),如以下常用的web应用程序开发的分层设计架构模式就是典型的MVC设计模式:
数据访问接口 DAO层
业务逻辑层 Service层
数据实体(持久化类) POJO
前端请求的接受和处理:Servlet
前端页面展示 JSP
(JSP+Servlet+JavaBean架构,最经典的MVC设计模式)
在这种架构模式中,
DAO层+Service层+POJO层对应MVC设计模式的Model模型
JSP对应MVC设计模式中的View视图
Servlet对应MVC设计模式中的Controller控制器
(1)视图(View):负责格式化数据并将它们以一定格式呈现给用户,包括数据展示、用户交互、客户端数据验证、界面设计等功能,对应组件:JSP或者HTML文件
(2)模型(Model):模型层的模型对象拥有最多的处理任务,是整个应用程序的主体部分,负责数据逻辑的处理(业务规则)和实现数据持久化操作(对数据库中的数据进行操作)。对应组件:处理业务逻辑的Service层(JavaBean)+数据持久层DAO层+贯穿于各层的数据实体(POJO)。
(3).控制器(Controller):负责接收并转发请求,对请求进行处理后指派视图View进行数据渲染并将响应结果发送给客户端,对应组件:Servlet
二、MVC设计模式组成
1. JSP Model1(JSP+JavaBean)
当业务流程较为简单的时候,可以把控制器的功能交给视图来实现,这种模式就是JSP Model1,在这种模式下,只有视图View和模型Model,没有控制器,即JSP+JavaBean.
JSP Model1模式的基础是JSP,该模式由JSP+JavaBean组成,JSP从HTTP Request请求中获得所需的数据,并进行业务逻辑的处理,然后将结果通过HTTP Response返回给前端浏览器。从中可见,Model1在一定程度上实现了MVC,即JSP将视图层和控制层合二为一,JavaBean为模型层。但是由于JSP身兼多职,既要负责视图层的数据展示,又要负责业务流程的控制,结构较为混乱,并且部是我们所希望的松耦合架构模式,所以当业务流程复杂的时候不推荐使用该模式。
2. JSP Model2(JSP+Servlet+JavaBean)
当业务流程较为复杂的时候,就需要把业务流程控制交给控制器实现,JSP专注于视图的展现即可,这种模式就是JSP Model2(JSP+Servlet+JavaBean)。
相比Model1,Model2是将控制层Controller单独划分出来负责业务流程的控制,接受请求,创建所需的JavaBean实例,并将处理后的数据再返回给视图层View进行界面数据展示。这样的结构清晰,效果明显优化很多,并且是一个松耦合的架构模式,所以除非项目非常简单,一般推荐使用JSP Model2模式实现MVC。
三、MVC处理流程和优缺点
1. MVC处理流程
模型、视图、控制器的职责划分如下:
模型(Model):
(1) .封装应用程序状态
(2) .响应状态查询
(3) .处理业务流程
(4) .通知视图业务状态更新
视图(View):
(1) .解释模型数据
(2) 接受数据更新请求
(3) 发送用户输入给控制器
(4) 允许控制器选择视图进行数据渲染
控制器(Controller):
(1) .接收用户请求
(2) .调用模型响应用户请求
(3) 选择视图显示响应请求
MVC整体的处理过程分析:
(1) .首先视图提供系统与用户交互的界面,并发送用户输入给控制器
(2) 控制器接收用户请求,并决定应该调用哪个模型来进行处理
(3) 模型根据用户请求进行数据持久化操作和业务逻辑处理,并返回处理结果(数据)
(4) 控制器根据模型返回的处理结果,调用相应的视图格式化模型返回的数据,并通过视图呈现给用户结果。
2. MVC优缺点:
优点:
①多视图共享一个模型,大大提高了代码的可重用性
②MVC三个模块相互独立,松耦合架构
③控制器提高了应用程序的灵活性和可配置性
④有利于软件工程化管理
总之,我们通过MVC设计模式可以搭建出一个松耦合+高重用性+高可适用性的完美架构,这也是架构设计的目标之一。
缺点:
①原理复杂
②增加了系统结构和实现的复杂性
③视图对模型数据的低效率访问
总结:对于MVC来说,他并不适合小型甚至中型规模的项目,花费大量时间将MVC应用到规模并不是很大的应用程序上通常得不偿失,所以对于MVC设计模式的使用要根据具体的应用场景来决定。
四、Spring MVC介绍和环境搭建
1. Spring MVC框架介绍
Spring MVC框架是Spring框架中拥有Web应用开发的一个模块,是Spring提供的一个基于MVC设计模式的优秀Web开发框架,它本质上相当于Servlet,在MVC设计模式中,Spring MVC作为控制器Controller来建立模型和视图的数据交互,是结构最清晰的MVC Model2实现,可称为一个典型的MVC框架。
在Spring MVC框架中,Controller替换Servlet担负控制器的职责,Controller接受请求,调用相应的Model进行处理,处理器完成业务处理后返回处理结果给Controller,Controller调用相应的视图View并对处理结果进行视图渲染,最终客户端得到响应信息。
由于Spring MVC的结构较为复杂,上述只是对其框架结构的一个简单介绍。下文会对Spring MVC的体系架构和请求处理流程进行具体介绍。
Spring MVC框架采用松耦合、可插拔的组件结构,具有高度可配置性,比起其他MVC框架更具扩展性和灵活性。此外Spring MVC的注解驱动和对REST风格的支持,也是他最具特色的功能,无论是在框架设计,还是扩展性、灵活性等方面都已经全面超越了Struts2等MVC框架,并且Spring MVC框架本身就是Spring框架的一部分,与Spring框架整合可以说是无缝集成,性能方面具有天生的优越性,对于开发者来说,开发效率也高于其他Web框架,在企业中的应用也越来越广泛,成为主流的MVC框架。
2. Spring MVC开发环境搭建
(1) .引入Spring框架所需要的Jar文件和Spring MVC框架所需的Jar文件:
aopalliance.jar
aspectjweaver-1.6.9.jar
commons-logging-1.2.jar
log4j-1.2.17.jar
spring-aop-3.2.13.RELEASE.jar
spring-beans-3.2.13.RELEASE.jar
spring-context-3.2.13.RELEASE.jar
spring-core-3.2.13.RELEASE.jar
spring-expression-3.2.13.RELEASE.jar
spring-web-3.2.13.RELEASE.jar
spring-webmvc-3.2.13.RELEASE.jar
(2) .在web.xml中配置SpringMVC核心控制器DispatcherServlet
<!-- 配置SpringMVC前端核心控制器DispatcherServlet -->
<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>
(3) .创建Spring MVC配置文件springmvc-servlet.xml
①配置处理器映射HandlerMapping,采用默认的BeanNameUrlHandlerMapping处理器映射
②配置视图解析器ViewResolver
③启用SpringMVC一键式解决方案(简化配置),并提供对处理器映射相关注解的支持。
<!-- handleMapper处理器映射采用默认的BeanNameUrlHandleMapping -->
<bean name="/welcome" class="controller.IndexController"></bean>
<!-- 启用Spring IoC注解,使用注解注册控制器 -->
<context:component-scan base-package="controller"></context:component-scan>
<!-- 启用注解实现处理器映射 -->
<mvc:annotation-driven />
<!-- 配置视图解析器InternalResourceViewResolver -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
(4) .创建控制器Controller
①继承AbstractController类,重写handleRequestInternal()方法
Public class IndexController extends AbstractController
{
Protected ModelAndView handleRequestInternal(HttpServletRequest request,HttpServletResponse response)throws Exception
{
Return new ModelAndView(“逻辑视图名”);
}
}
②使用Spring注解定义控制器并映射请求URL和处理方法。
注意:前提是在SpringMVC配置文件中启用了<context:component-scan/>和<mvc:annotation-driven/>支持注解标注控制器和注解式处理器(DefaultAnnotationHandlerMapping)
@Controller
public class IndexController{
private Logger logger=Logger.getLogger(IndexController.class);
//处理相应请求的方法
@RequestMapping("/welcome")
public String start()
{
logger.info("学框架就学Spring MVC!");
return "index";
}
//跳转到填写userCode页面
@RequestMapping("/index.html")
public String toInputUserCode()
{
return "index";
}
}
(5) .创建jsp视图进行页面呈现。
注意:
①控制器处理方法返回值ModelAndView对象:正如其名所示,他代表Spring MVC中呈现视图界面所使用的Model模型数据和View逻辑视图名。由于Java一个方法一次只能返回一个对象,所以ModelAndView的作用就是封装这两个对象,以方便我们一次返回所所需的模型数据Model和逻辑视图名View。返回的模型和视图都是可选的,在一些情况下,模型中没有任何数据,那么只返回视图即可,或者只返回模型,让SpringMVC根据请求URL来决定视图。
②控制器返回的逻辑视图名被DispatcherServlet获取到之后,会通过SpringMVC配置文件中配置的视图解析器将逻辑视图解析为一个真正的JSP视图名并将视图呈现给用户。
小结Spring MVC框架的请求处理流程简述:
①当用户发送URL请求时,根据web.xml中配置的DispatcherServlet,该请求会被DispatcherServlet拦截,并根据HandlerMapping处理器映射找到处理响应请求的控制器Controller来处理请求。
②控制器处理完成之后,返回ModelAndView对象,该对象告诉DispatcherServlet需要通过哪个视图进行模型数据的展示,DispatcherServlet根据视图解析器把控制器返回的逻辑视图名解析为真正的视图并输出,呈现给用户。
(6) .更改HandlerMapping处理器映射
将使用的HandlerMapping处理器映射(BeanNameUrlHandlerMapping)更改为支持注解式处理器,配置<mvc:annotation-driven/>,他是Spring MVC提供的一键式配置方法,配置此标签后SpringMVC会自动帮我们做一些注册组件之类的事情。这种配置方法非常简单,适用于初学者快速搭建Spring MVC环境。简单理解就是配置此标签后,我们可以通过注解的方式,把一个URL映射到控制器Controller上。
<mvc:annotation-driven/>作用:
配置该标签会自动注册DefaultAnnotationHandlerMapping处理器映射和AnnotationMethodHandlerAdapter处理器适配器这两个Bean,Spring MVC需要通过这两个Bean实例来完成对@Controller和@RequestMapping等注解的支持,从而找出URL与控制器处理方法handler method的关系并予以关联。换句话说,在Spring容器中注册这两个Bean是Spring MVC为@Controller分发请求的必要支持。
<context:component-scan/>作用:
对包进行扫描,实现注解驱动Bean的定义,同时将Bean自动注入容器中使用。即:使标注了Spring MVC注解(如@Controller等)的Bean生效。换句话说,如果没有配置此标签,那么标注@Controller的Bean仅仅是一个普通的Bean,而不是一个可以处理请求的控制器。
注意:
Spring3.2之前的版本,开启注解式处理器支持的配置是DefaultAnnotationHandlerMapping处理器映射和AnnotationMethodHandlerAdapter处理器适配器,Spring 3.2以后,使用RequestMappingHandlerMapping和RequestMappingHandlerAdapter来替代处理器映射和处理器适配器。
五、Spring MVC框架的请求处理流程和体系架构
1. Spring MVC框架的请求处理流程
Spring MVC框架也是一个基于请求驱动的Web框架,并且也使用了前端控制器模式进行设计,再根据请求映射规则将请求分发给不同的页面控制器(处理器)进行处理。详细分析Spring MVC框架处理请求的流程步骤具体如下:
(1) .首先用户发送请求到前端控制器DispatcherServlet,前端控制器根据请求信息(如URL)来决定选择哪一个页面控制器来进行处理,并把请求委托给他,即Servlet控制器的控制逻辑部分。
(2) 页面控制器收到请求后,调用Model进行数据访问和业务处理,处理完毕返回一个ModelAndView对象
(3) 前端控制器DispatcherServlet收回控制权,根据页面控制器返回的逻辑视图名,依据Spring MVC配置文件中视图解析器ViewResolver的配置,选择相应的真正视图,并把模型数据传入以便将视图渲染展示。
(4) 视图渲染完成后,DispatcherServlet再次收回控制权,将响应结果呈现给用户,至此整个请求处理流程结束。
2. Spring MVC框架的体系架构
在Spring MVC框架模型中,我们可以发现从发送请求到返回响应,Spring MVC框架的众多组件通力配合、各司其职的完成整个流程工作。在整个框架中,SpringMVC通过前端控制器DispatcherServlet接受所有的请求,并将具体工作委托给其他组件进行处理,DispatcherServlet处于整个Spring MVC体系架构的核心地位,负责协调不同组件完成请求处理并返回响应。根据Spring MVC的请求处理流程,我们来分析一下具体的每个组件所负责的工作内容。
(1) .客户端发送HTTP请求,Web应用服务器接收此请求,若匹配DispatcherServlet的请求映射(servlet-mapping)路径,则Web容器将该请求转交给DispatcherServlet处理。
(2) DispatcherServlet接收该请求后,将根据请求信息(如URL、请求参数、HTTP请求方法get/post等)及HandlerMapping处理器映射的配置(在SpringMVC配置文件中配置),找到处理请求的处理器Handler
(3) 当DispatcherServlet根据处理器映射handlerMapping找到对应当前请求的Handler之后,通过HandlerAdapter处理器适配器对Handler进行封装,再以统一的适配器接口调用Handler,HandlerAdapter可以理解为具体使用Handler来干活的人。HandlerAdapter处理器适配器接口中定义了三个方法:
Supports(Object):判断是否可以使用某个Handler
Handle(HttpServletRequest request,HttpServletResponse response,Object):具体使用Handler干活
getLastModified(HttpServletRequest,Object):获取资源的Last-Modified
注意:Spring MVC中没有定义Handler接口,也没有对处理器进行任何限制,处理器可以以任意合理的方式来表现。换句话说,任何一个Object都可以成为请求处理器Handler,从HandlerAdapter的handle方法可以看出,他是Object类型,这种模式给开发者提供了极大的自由度。
(4) .在请求信息到达真正调用Handler的处理方法之前的这段时间内,Spring MVC还完成了很多工作,它会将请求信息以一定格式转换并绑定到请求方法的入参中,对于入参的对象会进行数据转换、数据格式化以及数据校验等。这些都做完了之后,最后才真正的调用Handler的处理方法进行相应的业务逻辑处理。
(5) 处理器完成业务逻辑处理之后返回一个ModelAndView对象给DispatcherServlet,ModelAndView对象中包含了模型数据和逻辑视图名信息。
(6) ModelAndView对象中包含的视图是“逻辑视图名”而非真正的视图对象。DispatcherServlet会通过视图解析器ViewResolver将逻辑视图名解析为真正的视图对象View.当前,负责数据展示的视图可以为JSP、xml、json、pdf等多种数据格式,对此Spring MVC均可灵活配置。
(7) 当得到真正的视图对象之后,DispatcherServlet会使用ModelAndView对象中的模型数据对View进行视图渲染。
(8) 最终客户端得到相应信息,根据配置,可以是普通的HTML页面,也可以是一个XML或者JSON格式的数据等。
3. Spring MVC框架的特点:
①清晰地角色划分
②灵活的配置功能
③提供了大量的控制器接口实现类,开发者可以使用Spring提供的控制器实现类,也可以自己实现控制器接口。
④真正做到与View实现无关,他不会强制开发者使用JSP,也可以根据项目需求使用Velocity,XSLT等技术,使用起来更加灵活,
⑤国际化支持
⑥面向接口编程
⑦Spring框架提供了Web应用开发的一整套流程,不仅仅是MVC,他们之间可以很方便的结合在一起。
总之,一个好的框架需要减轻开发者处理复杂问题的负担,内部要有良好的扩展并且有一个支持他的强大用户群体,恰恰Spring MVC做到了!
六、Spring MVC框架的参数传递
1. View To Controller视图往控制器传递数据
(1) .使用@RequestMapping注解映射请求的URL到控制器用来处理该请求的处理方法上。
注意:HTTP请求信息除了包含请求的URL地址外,还包括很多其他的请求信息,如请求方法(GET/POST),HTTP协议及版本,HTTP的请求报文头、HTTP的请求报文体。使用@RequestMapping处理可以使用URL映射请求外,还可以使用请求方法、请求参数等映射请求。
①通过请求URL进行映射
使用@RequestMapping映射请求,具体包括四个方面的信息项:请求URL、请求方法、请求参数、请求头。
@RequestMapping注解不仅可以定义在方法定义处,还可以定义在类定义处。当在类定义处使用@RequestMapping映射了请求URL后,在方法定义处定义的@RequestMapping映射的URL相对于类定义处指定的URL。如类定义处映射的URL是/welcome,方法定义处映射的URL是/index,则处理该请求的处理方法最终映射的请求URL将是/welcome/index。在这种情况下,类定义处定义的URL相对于Web应用的部署路径。
如果仅仅在处理方法处使用@RequestMapping注解定义了映射的URL,没有在类定义处定义映射URL,则此时该方法定义处定义的@RequestMapping映射的URL直接相对于Web应用的部署路径。
注意:在整个Web项目中,每个@RequestMapping映射的请求信息必须确保全局唯一。
在实际开发中,一般会在不同业务Controller类定义处指定相应的@RequestMapping,即把同一个Controller下的操作请求都安排在同一个URL之下,以便于区分请求,并且通过URL就可以看出是属于那个业务模块下的请求。
@RequestMapping注解的value属性返回值是一个字符串数组String[],可以在同一个@RequestMapping注解中配置多个可以被定义该注解的处理方法拦截并进行处理的URL。
②通过请求方法和请求参数进行请求映射。
@RequestMapping除了可以使用请求URL映射请求之外,还可以使用请求方法、请求参数映射请求,通过多条件可以让请求映射更加精确。
value属性表示映射的URL,method属性表示映射的请求方法(RequestMethod.GET/POST),params属性表示映射的请求参数。
注意:若使用方法参数直接入参,方法入参的参数名必须与请求中的参数名保持一致。
(2) .通过使用@RequestParam指定Controller处理方法入参对应的请求参数。
Value:映射的请求参数名
Required:是否必须,默认为true,表示请求中必须包含对应的请求参数名,不存在则抛出异常。
defaultValue:默认参数名,不推荐使用。
小结:
将View视图中的数据传递到Controller控制器处理方法入参时,
通过使用@RequestMapping映射请求到控制器的相应处理方法对请求进行处理,通过使用@RequestParam指定处理方法入参对应绑定的请求参数。
2. Controller to View控制器往视图传递数据,需要涉及模型数据的处理。
对于MVC框架来说,模型数据是最重要的,因为Controller控制器是为了产生模型数据Model,而视图View最终也是为了渲染模型数据并进行输出。将Controller生成的模型数据Model传递到View视图进行模型数据渲染,是SpringMVC框架的一项重要工作。SpringMVC提供了多种方式将Controller产生的模型数据Model传递给视图View,输出模型数据。
①ModelAndView(控制器处理方法直接返回ModelAndView对象)
控制器处理方法的返回值如果是ModelAndView,则既包含逻辑视图信息,也包含模型数据信息,有了该对象之后,SpringMVC就可以使用视图对模型数据进行渲染。
@RequestMapping(“/index1”)
Public ModelAndView index(String userName)
{
ModelAndView mv=new ModelAndView();
Mv.setViewName(“逻辑视图名”);
Mv.addObject(“key”,”value”);
Return mv;
}
注意:
ModelAndView对象常用的方法如下:
①添加模型数据
Mv.addObject(“key”,”value”);
为添加的模型数据指定一个key(确保在这个Model中全局唯一),并赋对应的值value
Mv.addAllObjects(Map<String,Object> maps);
直接添加Map对象到模型Model中。
②设置视图
Mv.setViewName(“逻辑视图名”);
Mv.setView(View view);指定一个具体的视图对象。
②Model(控制器处理方法使用Model对象入参)
除了可以使用ModelAndView返回模型数据外,还可以使用SpringMVC提供的Model对象来完成模型数据的传递。
注意:
SpringMVC在调用控制器处理方法之前,会先创建一个隐含的模型对象,作为模型数据的存储容器,一般称为“隐含模型”,
若处理方法的入参为Model对象,SpringMVC会将隐含模型的引用传递给这些入参。简单的说,就是在控制器处理方法中可以通过一个Model对象入参访问到模型中存储的所有数据,当然也可以在模型中继续添加新的属性数据。
@RequestMapping(“/index1”)
Public String index(String userName,Model model)
{
Model.addAttribute(“key”,”value”);
Return “逻辑视图名”;
}
注意:Model对象也是一个Map类型的数据结构,并且对于添加模型数据时key的指定并不是必须的。
在调用model对象的addAttribute()方法往模型中添加数据时,如果没有传入key,直接传入value,这样的情况下默认会使用传入value的数据类型作为key.如直接传入String类型的数据,则默认key为”string”
③Map(控制器处理方法直接使用Map<String,Object>入参)。
@RequestMapping(“/index1”)
Public String index(String userName,Map<String,Object> model)
{
Model.put(“key”,”value”);
Return “逻辑视图名”;
}
注意:SpringMVC控制器的处理方法如果有Map<String,Object>或者Model类型的入参,就会将请求内的隐含模型对象传递给这些入参。因此在方法体内可以通过这个入参对模型中的数据进行读写操作。
SpringMVC的标准用法建议使用Model对象入参。
④@ModelAttribute注解:
将使用该注解标注的入参数据对象直接放入数据模型Model中
⑤@SessionAttributes注解:
可以将模型中的数据存入HttpSession中,以便在多个请求之间共享该属性。
七、Spring MVC 控制器的单例管理
1. 单例模式
单例模式是23中设计模式之一,也是最常用的设计模式。顾名思义,单例模式指的是整个应用运行期间,有且仅有一个实例,单例模式有三个必须满足的关键点:
①一个类只有一个实例,这是满足单例模式最基本的要求。若要满足这个关键点,只能提供私有的构造方法,这样才能保证不能随意创建该类的实例,一般可以将读取配置文件I/O操作的代码放入私有构造函数中,这样可以有效保证这部分操作在系统运行期间只会执行一次,解决资源消耗问题。
②它必须自行创建这个实例。我们要保证唯一性,也就意味着必须要自行提供一个实例,如读取数据源配置文件的工具类ConfigManager实现单例模式如下:
public class ConfigManager {
//属性对象
private static Properties properties;
private static ConfigManager configManager;
//私有构造函数
private ConfigManager()
{
//读取配置文件
properties=new Properties();
InputStream inputStream=ConfigManager.class.getClassLoader().getResourceAsStream("database.properties");
try
{
properties.load(inputStream);
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
try
{
inputStream.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
}
③它必须自行向整个系统提供这个实例。这一点至关重要,外界需要获取并使用这个单例类的实例,但是由于该类的构造方法是私有的,外界无法通过new去获取它的实例,那么就必须提供一个静态的公有方法,该方法用来创建或者获取它本身的静态私有对象并返回。
public static ConfigManager getInstance()
{
if(configManager==null)
{
configManager=new ConfigManager();
}
return configManager;
}
这是一个全局的访问点getInstance()方法,该方法返回该类的实例,以被外界使用。在该方法中进行了简单的逻辑判断,若实例为空,则创建这个实例,否则直接返回创建好的实例即可,不需要在实例化该类的对象,实现了单例模式。
注意:全局访问点必须定义为静态的方法,原因是该类的构造函数是私有的,外界无法通过new对象来调用该构造函数,只能通过类名调用公有的静态方法获取这个单例类的实例,否则无法调用该方法。
使用单例模式实现读取数据源配置文件工具类ConfigManager如下:
public class ConfigManager {
//属性对象
private static Properties properties;
private static ConfigManager configManager;
//私有构造函数
private ConfigManager()
{
//读取配置文件
properties=new Properties();
InputStream inputStream=ConfigManager.class.getClassLoader().getResourceAsStream("database.properties");
try
{
properties.load(inputStream);
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
try
{
inputStream.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
public static ConfigManager getInstance()
{
if(configManager==null)
{
configManager=new ConfigManager();
}
return configManager;
}
//读取配置信息
public String getValue(String key)
{
return properties.getProperty(key);
}
}
BaseDao类获取连接部分:
/**
* 获取数据库连接
* @return
*/
public static Connection getConnection()
{
String driver=ConfigManager.getInstance().getValue("driver");
String url=ConfigManager.getInstance().getValue("url");
String userName=ConfigManager.getInstance().getValue("username");
String password=ConfigManager.getInstance().getValue("password");
//加载驱动
try
{
Class.forName(driver);
}
catch (Exception e)
{
e.printStackTrace();
}
//获取连接
Connection connection=null;
try
{
connection=DriverManager.getConnection(url,userName,password);
}
catch(Exception e)
{
e.printStackTrace();
}
return connection;
}
以上这种实现单例模式的方式,在并发环境下存在严重的弊端,如线程不安全,很有可能会出现多个configManager实例。因此我们需要先了解下单例模式的三种实现方式:饿汉模式、懒汉模式和静态内部类,并进行对比说明。
①懒汉模式:在类加载时不创建类的实例,采用延迟加载的方式,在系统运行调用时创建实例。但是在这种模式下,虽然保持了延迟加载的特性,但也存在线程不安全的问题,在多线程环境下无法正常工作,这也是致命的缺陷,要解决这种线程不安全的问题,首先可以考虑线程的同步。
public static synchronized ConfigManager getInstance()
{
if(configManager==null)
{
configManager=new ConfigManager();
}
return configManager;
}
这种使用线程同步的方式解决懒汉模式在多线程并发环境下的线程不安全问题,能够在多线程并发环境下很好的工作,而且也具备了延迟加载的特性,但是遗憾的是这种处理方式效率不高,因为大多数情况下是不需要考虑线程同步的,那么对于线程不安全问题还可以使用饿汉模式解决。饿汉模式由于在类加载时不创建实例,所以类加载速度快,但是在系统运行调用时创建实例,因此获取对象速度慢。
②饿汉模式:在类加载时就完成了对象的初始化工作,因此类加载速度慢,但是获取对象的速度很快。并且由于饿汉模式是在类初始化时就已经自行创建了该类的实例,因此肯定不存在线程安全问题。
public class ConfigManager {
//属性对象
private static Properties properties;
private static ConfigManager configManager=new ConfigManager();
//私有构造函数
private ConfigManager()
{
//读取配置文件
properties=new Properties();
InputStream inputStream=ConfigManager.class.getClassLoader().getResourceAsStream("database.properties");
try
{
properties.load(inputStream);
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
try
{
inputStream.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
public static ConfigManager getInstance()
{
return configManager;
}
//读取配置信息
public String getValue(String key)
{
return properties.getProperty(key);
}
}
这种方式基于classloader机制,有效的避免了多线程的同步问题。但是由于导致类加载的原因有很多,在单例模式中大多数是调用getInstance()方法,但是也可能会有其他的方法(或者其他静态方法)导致类装载,而此时单例类在类加载时就实例化,显然没有达到延迟加载lazyloading的效果。
解决此问题,就是要求饿汉模式同时要具备延迟加载的特性,那么就可以使用静态内部类实现。
/**
* 单例模式,读取数据源配置文件,获取配置信息
* @author Hasee
*
*/
public class ConfigManager {
//属性对象
private static Properties properties;
// //懒汉模式
// private static ConfigManager configManager;
//饿汉模式
private static ConfigManager configManager;
//私有构造函数
private ConfigManager()
{
//读取配置文件
properties=new Properties();
InputStream inputStream=ConfigManager.class.getClassLoader().getResourceAsStream("database.properties");
try
{
properties.load(inputStream);
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
try
{
inputStream.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
//静态内部类
public static class Singleton
{
private static final ConfigManager INSTANCE=new ConfigManager();
}
//获取实例
public static ConfigManager getInstance()
{
// //懒汉模式,加载类速度快,获取对象速度慢,线程不安全,要给方法上锁
// if(configManager==null)
// {
// configManager=new ConfigManager();
// }
//
// //饿汉模式,加载类速度慢,获取对象速度快,线程安全,但是不能延迟加载
// return configManager;
//静态内部类,饿汉模式+延迟加载的结合,线程安全
if(configManager==null)
{
configManager=Singleton.INSTANCE;
}
return configManager;
}
//读取配置信息
public String getValue(String key)
{
return properties.getProperty(key);
}
}
※实现单例模式的三种方式对比:
懒汉模式:在类加载时不创建实例,采用延迟加载的方式,在系统运行调用时创建实例。
特点:
(1)类加载速度快,获取对象速度慢。
(2)多线程并发环境下存在严重的线程安全问题,但是实现了延迟加载lazyloading
饿汉模式:在类加载时就完成了实例对象的初始化工作,在系统运行调用时直接返回创建好的实例。
特点:
(1)类加载速度慢,获取对象速度快。
(2)没有实现延迟加载lazyloading,但是多线程并发环境下也能很好的工作,不存在线程安全问题。
静态内部类:在外部的类加载时却不一定初始化了该单例类的实例,因为静态内部类没有主动被调用。
特点:既确保了多线程并发环境下的线程安全,又避免了线程同步带来的性能影响。
注意:使用饿汉模式、懒汉模式、静态内部类中的哪一种方式实现单例模式都是根据实际具体的业务需求决定。若是选择单例模式实现,就必须满足:在整个程序运行期间有且仅有一个实例,若违背了这一点,那么所设计的类就不是单例类。
单例模式的特点:
(1)应用程序运行期间有且仅有一个实例
(2)他必须自行创建这个唯一实例
(3)他必须自行向整个系统提供这个唯一实例。
2. SpringMVC-Controller的单例管理
在SpringMVC中,所有的控制器Controller默认都是单例的(singleton),这样设计的原因是基于性能的考虑,因为将Controller控制器设计为单例模式,不需要每次都创建实例,速度和性能自然很优越,基于SpringMVC控制器Controller的这一特点,我们在使用的时候也要千万注意,在默认单例模式下,尽量不要再Controller控制器中定义成员变量,否则将会导致严重的线程安全以及资源使用问题。
注意:
单例模式在SpringMVC框架中的运用小结:
①Spring MVC的控制器默认就是单例的,其内部的成员变量是公用的,开发的时候需要注意资源使用问题。因此在Controller中一般将Service业务组件定义为成员变量(Service业务接口类型),然后通过Spring IoC自动装配注解进行装载。
这是因为Service是大家公用的,并且他是接口,接口内没有成员变量,都是方法,而方法中的变量也都是线程级别的,所以不会出现数据资源抢占或者内存溢出的问题。所以一般情况下,Controller控制器中的成员变量一般只有Service业务层对象。
八、Spring+SpringMVC+JDBC框架整合环境搭建
1. 加入Spring、SpringMVC、数据库驱动、log日志相关jar文件
2. 创建Spring配置文件applicationContext.xml
启用SpringIoC注解
<!-- 启用Spring IoC注解 -->
<context:component-scan base-package="cn.smbms.dao,cn.smbms.service"></context:component-scan>
3. 配置web.xml
引入Spring配置文件、配置Spring监听器ContextLoaderListender、配置SpringMVC核心控制器DispatcherServlet、配置Spring字符编码过滤器CharacterEncodingFilter
<!-- 引入Spring配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext-mybatis.xml</param-value>
</context-param>
<!-- 配置Spring监听器ContextLoaderListener,监听web应用程序的创建,并同时加载Spring配置文件 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置Spring字符编码过滤器 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置SpringMVC核心控制器DispatcherServlet -->
<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>
4. 创建SpringMVC配置文件springmvc-servlet.xml,相关配置见上文搭建SpringMVC开发环境
九、Spring MVC控制器处理方法使用Servlet API入参
在SpringMVC中,控制器可以不依赖任何ServletAPI对象,也可以将ServletAPI对象作为控制器处理方法的入参,在SpringMVC中使用Servlet API对象作为控制器处理方法的入参时,Spring MVC会自动将Web层对应的Servlet对象传递给处理方法的入参,当Servlet API对象和其他类型的数据同时入参时,他们之间的位置、顺序均没有特殊要求。
常用的Servlet API对象:
HttpServletRequest请求
HttpServletResponse响应
HttpSession会话
十、SpringMVC框架中静态资源文件的引用
默认情况下,jsp视图页面中的js/css/images都是没有起效的,这是因为在web.xml中配置的SpringMVC核心控制器DispatcherServlet请求映射为/,则Spring MVC将捕获(拦截)到Web容器的所有请求,当然也包括了静态资源的请求。SpringMVC会将他们当做普通的请求处理,但是由于找不到相应的处理这类请求的处理器,所以按照常规的方式在视图jsp页面中引用静态资源文件将无法访问。
使用<mvc:resources/>解决SpringMVC中静态资源访问的问题
<!-- 引用静态资源文件 -->
<mvc:resources location="/statics/" mapping="/statics/**"></mvc:resources>
属性说明:
Mapping:将静态资源映射到指定的路径(/statics)下。
Location:本地静态资源文件所在的目录(/statics/)。
十一、SpringMVC框架的异常处理机制
Spring MVC通过处理异常解析器HandlerExceptionResolver处理程序异常,包括处理器异常、数据绑定异常以及处理器执行时发生的异常。HandlerExceptonResolver接口仅有一个接口方法resolveException(HttpServletRequest request,HttpServletResponse response,Object obj,Exception),返回值ModelAndView对象
当发生异常时,Spring MVC会调用HandlerExceptionResolver接口中的resolveException()方法,并转到ModelAndView对应的视图中,作为一个异常报告页面反馈给用户。对于异常处理,SpringMVC进行异常处理一般分为局部异常处理和全局异常处理。
①局部异常处理:
仅能指定某个Controller控制器中的异常。使用@ExceptionHandler注解实现。
//局部异常处理,精确显示错误提示
@ExceptionHandler(value=RuntimeException.class)
public String showLoginError(RuntimeException e,HttpServletRequest request)
{
request.setAttribute("e",e);
return "login";
}
②全局异常处理:
全局异常处理作用于全局,即整个应用程序运行时抛出的异常都可以使用全局异常处理机制进行异常处理。
实现全局异常处理,可以在SpringMVC配置文件中通过配置
SimpleMappingExceptionResolver实现,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。配置如下:
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.RuntimeException">error</prop>
</props>
</property>
</bean>
十二、Spring表单标签
通过使用Spring提供的标签,可以很容易的将模型数据中的表单命令对象绑定到HTML表单元素中。首先要在视图jsp页面中通过使用taglib指令引入Spring表单标签库并指定一个Spring表单标签引用前缀
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="fm" %>
Spring常用的表单标签:
<fm:form/>渲染表单元素
<fm:input/>输入框组件标签
<fm:password/>密码框组件标签
<fm:hidden/>隐藏域组件标签
<fm:textarea/>多行文本框(文本域)组件标签
<fm:radiobutton/>单选按钮组件标签
<fm:select/>下拉列表框组件标签
<fm:checkbox/>复选框组件标签
<fm:errors/>显示表单数据校验时所对应的错误信息
Spring表单标签共有属性:
path属性路径,对应绑定的模型对象的属性名
cssClass表单组件对应的css样式类名
cssErrorClass提交表单时报错(服务器错误),采用的css样式类名
cssStyle表单组件对应的css样式
htmlEscape绑定的表单属性值是否要对HTML特殊字符进行转换,默认为true
使用<fm:form/>标签渲染表单元素时,无需通过action属性指定表单提交的目标URL。换句话说,当不指定目标URL的时候,会自动提交到获取表单页面的URL
十三、SpringMVC实现服务器端数据校验
在Spring MVC中有两种方式可以验证用户输入:
①使用Spring自带的验证框架
②使用JSR 303实现服务器端数据校验(推荐使用)
JSR 303是Java为Bean数据合法性校验所提供的标准框架,JSR 303通过在Bean属性上标注类似于@NotNull/@Max等标准的注解指定校验规则,并通过标准的验证接口对Bean数据进行验证。JSR 303不需要编写验证器,它定义了一套可标注在成员变量、属性方法上的校验注解。
常用的JSR 303约束:
@Null必须为Null
@NotNull必须不为null
@AssetTrue必须为true
@AssetFalse必须为false
@Min必须是一个数字,大于等于指定的最小值
@Max必须是一个数字,小于等于指定的最大值
@DecimalMin必须是一个数字(浮点数),大于等于指定的最小值
@DecimalMax必须是一个数字(浮点数),小于等于指定的最大值
@Size(max,min)大小必须在指定的范围内
@Digits(integer,fraction)必须是一个数字,值必须在可接受的范围内。
@Past必须是一个过去的日期
@Future必须是一个未来的日期
@Pattern(value)格式必须符合指定的正则表达式
注意:
SpringMVC支持JSR 303标准的校验框架,Spring的DataBinder在对请求参数和处理方法的入参之间进行数据绑定时,可同时调用校验框架来完成数据校验工作。在SpringMVC中,可以直接通过注解驱动来进行数据校验。
由于Spring本身没有提供JSR 303的实现,Hibernate Validator实现了JSR 303,所以必须在项目中加入来自Hibernate Validator库的jar文件,Spring会自动加载并装配
例子:
public class User {
private Integer id; //id
@NotEmpty(message="用户编码不能为空")
private String userCode; //用户编码
@NotEmpty(message="用户名称不能为空")
private String userName; //用户名称
@Length(min=6,max=10,message="用户密码长度为6-10")
private String userPassword; //用户密码
private Integer gender; //性别
@Past(message="必须是一个过去的时间")
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birthday; //出生日期
@Pattern(regexp="(13[0-9]|15[0-9]|18[0-9])[0-9]{8}",message="手机号格式不正确")
private String phone; //电话
@NotEmpty(message="地址不能为空")
private String address; //地址
private Integer userRole; //用户角色
}
注意:还要在控制器处理方法上对需要进行数据校验的对象入参前加上@Valid注解,并在该对象入参后添加一个BindingResult类型的入参,SpringMVC会自动将数据校验后的数据绑定结果注入给该入参。
控制器处理方法举例:
//修改用户信息
@RequestMapping("/modify")
public String modifyUserInfo(@Valid User user,
BindingResult result,
HttpSession session,
Model model,
String fileType,
@RequestParam(value="attachs",required=false)MultipartFile[] attachs)
{
if(result.hasErrors())
{
//数据校验不通过
}
}
十四、SpringMVC查看信息明细——REST风格
REST风格指的是表述性状态转移,即使用URL表示资源时,每一个资源都用一个独一无二的URL来表示,并使用HTTP方法表示操作,准确描述服务器对资源的处理动作(GET/POST/PUT/DELETE),实现资源的增删改查。URL参数不再使用?传递。这种风格的URL可读性更好,使得项目架构清晰,最关键的SpringMVC页提供对这种风格的支持。但是也有弊端,对于很多国内项目,URL参数有时候会传递中文,容易出现中文乱码问题,所以我们需要根据实际情况进行灵活处理,大多数网站采用的都是传统URL风格与REST风格混用
以下控制器处理方法映射的URL举例
http://localhost:8080/SMBMS/show/35
//查看用户信息
@RequestMapping("/show/{id}")
public String showUserInfo(@PathVariable String id,Model model)
{
try
{
//根据id获取用户信息
User user=userService.getUserInfoById(Integer.parseInt(id));
//存入模型
model.addAttribute("user",user);
return "userview";
}
catch(Exception e)
{
e.printStackTrace();
return "error";
}
}
@PathVariable注解的作用是将URL中的{XXX}占位符参数绑定到控制器处理方法使用该注解标注的入参中。
十五、SpringMVC实现文件上传
Spring MVC为实现文件上传提供了直接的支持,即文件上传解析器MultipartResolver,MultipartResolver文件上传解析器用于处理上传请求,将上传请求包装为可以直接获取文件的数据,从而方便操作。他有两个实现类:
StandardServletMultipartResolver:使用了Servlet3.0标准的上传方式
CommonsMultipartResolver:使用了Apache的Commons-FileUpload组件来完成具体的文件上传操作。
在SpringMVC中上传文件一般建议使用CommonsMultipartResolver
实现文件上传需要部署的jar包:
在SpringMVC配置文件中配置CommonsMultipartResolver组件
<!-- 配置文件上传解析器CommonsMultipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="5000000000"></property>
<property name="defaultEncoding" value="UTF-8"></property>
</bean>
属性说明:
defaultEncoding:请求的编码格式,默认为ISO-8859-1,建议设置为UTF-8
注意:该属性取值必须和jsp页面page指令的pageEncoding属性值保持一致,以便正确读取表单内容
maxUploadSize:上传文件大小上限,单位为字节。
注意:使用SpringMVC实现文件上传,表单元素的类型必须设置为支持文件上传类型:
Enctype=”multipart/form-data”
并且只能post方式提交:
Method=”POST”
文件域组件的name属性必须要指定,作为表单提交后对应该文件域上传文件的请求参数名。
控制器处理方法添加一个入参(MultipartFile类型),SpringMVC会将上传文件绑定到MultipartFile类型的入参对象中,使用MultipartFile对象可以获取上传文件信息,如文件名,文件大小等。
MultipartFile接口常用实现文件上传的方法:
getOriginalFileName()获取上传的文件名
getSize()获取上传文件的大小
isEmpty()判断上传的文件是否为空
transferTo(File file)将文件上传到目标路径
1. 单文件上传
在表单中定义一个File控件并指定name属性,处理相应请求的控制器处理方法添加一个入参(MultipartFile类型对象)
2. 多文件上传
在表单中定义多个File控件并指定每个控件的name属性(建议file控件的name属性保持一致),处理相应请求的控制器处理方法添加一个入参(MultipartFile[]数组类型对象入参),
@RequestMapping("/modify")
public String modifyUserInfo(@Valid User user,
BindingResult result,
HttpSession session,
Model model,
String fileType,
@RequestParam(value="attachs",required=false)MultipartFile[] attachs)
{
//文件上传部分
//身份证路径
String idPicPath=null;
//工作证路径
String workPicPath=null;
if(attachs!=null)
{
for(int i=0;i<attachs.length;i++)
{
MultipartFile attach=attachs[i];
if(!attach.isEmpty())
{
try
{
//获取源文件名
String oldFileName=attach.getOriginalFilename();
//后缀
String ext=FilenameUtils.getExtension(oldFileName);
//最大上传容量
int maxSize=5000000;
if(attach.getSize()>maxSize)
{
if(fileType.equals("idPicPath"))
{
model.addAttribute("idPicPathError","* 上传文件大小不能超过5M!");
}
else
{
model.addAttribute("idPicPathError","* 上传文件大小不能超过5M!");
}
return "usermodify";
}
else
{
if(ext.equalsIgnoreCase("jpg") || ext.equalsIgnoreCase("jpeg")
|| ext.equalsIgnoreCase("png") || ext.equalsIgnoreCase("pneg"))
{
//生成上传文件名
String fileName=System.currentTimeMillis()+RandomUtils.nextInt(1000000)+"_person.jpg";
//目标上传路径
String destinePath=session.getServletContext().getRealPath("statics"+File.separator+"uploadfiles");
File file=new File(destinePath,fileName);
try
{
attach.transferTo(file);
if(fileType.equals("idPicPath"))
{
idPicPath=destinePath+File.separator+fileName;
}
else if(fileType.equals("workPicPath"))
{
workPicPath=destinePath+File.separator+fileName;
}
else
{
if(i==0)
{
idPicPath=destinePath+File.separator+fileName;
}
else
{
workPicPath=destinePath+File.separator+fileName;
}
}
}
catch(Exception e)
{
e.printStackTrace();
if(fileType.equals("idPicPath"))
{
model.addAttribute("idPicPathError","* 上传文件出现异常");
}
else
{
model.addAttribute("workPicPathError","* 上传文件出现异常");
}
return "usermodify";
}
}
else
{
if(fileType.equals("idPicPath"))
{
model.addAttribute("idPicPathError","* 上传文件只能是jpg/png/jpeg/pneg格式");
}
else
{
model.addAttribute("workPicPathError","* 上传文件只能是jpg/png/jpeg/pneg格式");
}
return "usermodify";
}
}
}
catch(Exception e)
{
e.printStackTrace();
if(fileType.equals("idPicPath"))
{
model.addAttribute("idPicPathError","* 上传文件出现异常");
}
else
{
model.addAttribute("workPicPathError","* 上传文件出现异常");
}
return "usermodify";
}
}
}
}
}
十六、SpringMVC传递JSON对象的处理(控制器和视图之间传递)
1. 使用@ResponseBody实现数据输出
@ResponseBody注解的作用是将控制器处理方法的返回值直接写入HTTP Response Body(response对象的body数据区)。一般情况下@ResponseBody都会在使用ajax异步获取数据时使用,被其标注的控制器处理方法返回的数据将直接输出到响应流中,客户端获取并显示数据。
2. 常用的json技术
Json-lib 缺点:依赖很多Jar包
Jackson SpringMVC内置的JSON转换工具,缺点:对于复杂类型的json转换bean会出现问题。
Gson:功能最全的json解析器,由Google谷歌公司自行研发提供。
缺点:性能较差
FastJson:Java编写的高性能json处理器,由阿里巴巴公司开发。缺点:复杂类型的bean转换json会出现一些问题。
建议:在SpringMVC中传递json数据建议使用FastJson或Jackson
FastJson依赖的jar包,需要部署到项目中:
3.解决:JSON数据传递时的中文乱码问题
①指定@RequestMapping注解的 属性produces={ “application/json;charset=UTF-8”}
②在SpringMVC中配置消息转换器StringHttpMessageConverter,一次配置,永久搞定
<mvc:annotation-driven conversion-service="converter">
<mvc:message-converters>
<!-- 配置消息转换器,解决json数据传递时的中文乱码问题 -->
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
3. 解决:JSON数据传递时的日期格式问题(默认输出时间戳)
①使用FastJson提供的@JSONField(format)注解标注需要传递的json对象中的日期时间类型的属性。
②在SpringMVC配置文件中配置FastJson消息转换器FastJsonHttpMessageConverter
<mvc:annotation-driven conversion-service="converter">
<mvc:message-converters>
<!-- 配置消息转换器,解决json数据传递时的中文乱码问题 -->
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
<!-- 配置FastJson消息转换器,解决JSON数据传递时的日期格式问题 -->
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
<property name="features">
<list>
<value>WriteDateUseDateFormat</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
注意:
1. 如果只配置了FastJsonHttpMessageConverter消息转换器,没有使用@JSONField注解,默认输出日期格式:yyyy-MM-dd HH:mm:ss
2. 没有配置FastJsonHttpMessageConverter,只使用了@JSONField注解,则日期格式以注解format属性配置的日期格式为准
3. 同时配置了FastJsonHttpMessageConverter和@JSONField注解,则注解优先级更高(注解优先)
4. 既没有配置FastJsonHttpMessageConverter,也没有使用@JSONField注解(默认情况下),输出日期时间戳
十七、SpringMVC框架视图解析器
在控制器Controller的处理方法处理请求完成之后,最终会返回一个ModelAndView对象,它包含了逻辑视图名和模型数据信息,此时SpringMVC就要根据视图解析器ViewResolver将逻辑视图名进行解析成真正的视图。ViewResolver是SpringMVC处理视图的重要接口,真正的视图对象可以各种各样,如JSP视图、FreeMarker、Velocity等模板技术的视图,还可以是JSON、XML、PDF、HTML等各种数据格式的视图。SpringMVC提供了多种试视图解析器用来对不同格式的视图进行解析,所有的视图解析器都实现了ViewResolver接口,jsp视图解析器通常使用InternalResourceViewResolver,它是URLBasedViewResolver的子类,UrlBasedViewResolver类实现了视图解析器ViewResolver接口。InternalResourceViewResolver视图解析器会把返回的视图名层都解析为InternalResourceView对象,该对象会把Controller处理方法返回的模型属性都放在对应的请求作用域中,然后通过RequestDispatcher在服务器端把请求转发到目标URL。
在SpringMVC配置文件中配置视图解析器InternalResourceViewResolver
<!-- 配置视图解析器InternalResourceViewResolver -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
属性说明:
Prefix:逻辑视图名在被解析时需要加上的前缀
Suffix: 逻辑视图名在被解析时需要加上的后缀
例如:/WEB-INF/jsp/index.jsp
index为逻辑视图名
/WEB-INF/jsp/为配置的前缀prefix
.jsp为配置的后缀suffix
多视图解析器ContentNegotiatingViewResolver
通过配置多视图解析器,SpringMVC可以根据请求报文头的Accept属性值,将控制器处理方法的返回值以xml/json/html等不同的形式输出响应(该控制器处理方法标注了@ResponseBody注解),也就是说可以通过设置请求报文头的Accept属性值来控制服务器返回的数据格式(如我们只是希望资源数据能以纯json格式输出),那就可以通过配置SpringMVC多视图解析器ContentNegotiatingViewResolver来进行灵活处理。
在SpringMVC配置文件中配置多视图解析器ContentNegotiatingViewResolver
<!-- 配置SpringMVC多视图解析器 -->
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<!-- 根据请求参数format的值指派响应数据的格式 -->
<property name="favorParameter" value="true"></property>
<property name="defaultContentType" value="text/html"></property>
<property name="mediaTypes">
<map>
<entry key="html" value="text/html;charset=UTF-8"></entry>
<entry key="xml" value="application/xml;charset=UTF-8"></entry>
<entry key="json" value="application/json;charset=UTF-8"></entry>
</map>
</property>
<property name="viewResolvers">
<list>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</list>
</property>
</bean>
例如:
请求url:
/user/view.json返回Json格式
/user/view.html返回Html格式
/user/view.xml返回xml格式
...
通过配置ContentNegotiatingViewResolver多视图解析器,达到了对于同一份资源数据根据相同URL访问,并通过设置MIME格式控制服务器返回的数据格式,从而获取不同格式的响应内容,这其实也恰恰是REST风格(表述性状态转移)
十八、SpringMVC数据转换与格式化
SpringMVC框架默认不支持日期时间类型数据的自动绑定,此时在对控制器处理方法入参Bean的日期时间类型属性进行自动绑定时就会报绑定异常BindingResult.这是Spring MVC框架本身的问题,解决方式就是适用格式化注解或自定义数据类型转换器ConversionServiceFactoryBean覆盖使用<mvc:annotation-driven/>一键式配置Spring MVC并加载注解驱动时创建的默认的formattingConversionServiceFactoryBean.
在请求信息真正到达控制器处理方法之前,SpringMVC还完成了很多工作,如数据格式化和转换、数据校验等。SpringMVC数据绑定流程如下:
①SpringMVC将请求ServletRequest对象以及处理方法的入参对对象实例传递给DataBinder,DataBinder调用ConversionService组件对ServletRequest中的请求参数进行数据转换和格式化,然后将转换和格式化之后的请求数据填充到处理方法的入参对象中,然后DataBinder又会调用Validator组件对已经填充好数据的入参对象属性进行数据合法性校验,并最终生成数据绑定结果BindingResult对象,注入到控制器处理方法的BindingResult入参对象中。
DataBinder是数据绑定的核心部件,在整个流程中起到核心调度作用。
ConversionService类型转换体系的核心接口,对请求参数进行数据转换和格式化工作
BindingResult包含了已完成数据绑定的处理方法入参对象和相应的校验错误对象,SpringMVC会抽取BindingResult中的入参对象以及校验错误对象,把他们赋值给控制器处理方法对应的入参(BindingResult类型的入参对象).
①编写自定义转换器,实现Converter<S,T>接口:
/**
* 自定义类型转换器,字符串转换为指定格式的Date类型数据,在ConversionService组建中载入该自定义类型转换器
* @author Hasee
*
*/
public class StringToDateConverter implements Converter<String,Date>{
private Logger logger=Logger.getLogger(StringToDateConverter.class);
//转换的日期格式
private String dateFormater;
//构造柱入日期格式,加载SpringMVC配置文件时进行构造柱入
public StringToDateConverter(String dateFormater)
{
logger.warn(dateFormater);
this.dateFormater=dateFormater;
}
//转换处理方法
public Date convert(String str)
{
Date date=null;
try
{
date=new SimpleDateFormat(dateFormater).parse(str);
}
catch(Exception e)
{
e.printStackTrace();
}
return date;
}
}
SpringMVC中配置ConversionServiceFactoryBean,指定自定义类型转换器并在<mvc:annotation-driven/>中通过conversion-service属性引用,覆盖默认创建的FormattingConversionServiceFactoryBean
<!-- 配置ConversionServiceFactoryBean指定自定义类型转换器,进行数据格式化和转换 -->
<bean id="converter" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="cn.smbms.tools.StringToDateConverter">
<!-- 构造柱入转换的日期格式 -->
<constructor-arg index="0" value="yyyy-MM-dd"></constructor-arg>
</bean>
</set>
</property>
</bean>
<mvc:annotation-driven conversion-service="converter">
②使用@InitBinder装配自定义编辑器实现类型转换:(Controller控制器继承该自定义编辑器所在类BaseController)
Public class BaseController
{
@InitBinder
Public void initBinder(WebDataBinder binder)
{
Binder.registCustomEditor(Date.class,
New CustomDateEditor(new SimpleDateFormat(“yyyy-MM-dd”),true));
}
}
十九、SpringMVC拦截器
在接受前端请求时,SpringMVC核心控制器DispatcherServlet会将请求交给处理器映射HandlerMapping,让他找出对应请求的HandlerExecutionChain对象,该对象是一个执行链,它包含处理该请求的处理器Handler以及若干个对请求实施拦截的拦截器HandlerInterceptor。HandlerInterceptor拦截器接口方法:
preHandler(HttpServletRequest reques,HttpServletResponse response,Object object),返回值:boolean
前置处理方法:控制器执行之前执行
postHandler(HttpServletRequest reques,HttpServletResponse response,Object object,ModelAndView modelAndView),返回值:void
后置处理方法:控制器处理方法执行完毕后,在生成视图之前执行。
afterCompletion(HttpServletRequest request,HttpServletResponse response,Object object,Exception)
最终处理方法:渲染响应之后执行,即最终执行
注意:SpringMVC拦截器基于处理器映射HandlerMapping,可以根据不同业务需求,基于不同的处理器映射HandlerMapping定义多个拦截器。
在SpringMVC配置文件中配置自定义拦截器:
<!-- 配置Spring MVC拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/user/**"/>
<bean class="util.SysInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
注意:自定义拦截器仅需继承HandlerInterceptorAdapter类,因为HandlerInterceptorAdapter类实现了SpringMVC拦截器接口HandlerInterceptor,提供了拦截方法的默认实现,如此一来自定义拦截器继承HandlerInterceptorAdapter类可以灵活重写拦截器拦截请求的处理方法。
注意:转载请注明原作者
原作者:田超凡
2018年6月2日