一 概述
1.什么是MVC?
Model-View-Controller,一种软件设计思想,将软件分为三层:模型层、视图层、控制层。
- 模型层:负责处理具体的业务。
- 视图层:与用户交互的界面。
- 控制层:将请求分发给指定的业务逻辑。
2.什么是Spring MVC?
Spring MVC是Spring对MVC思想的一种实现,建立在Spring核心功能之上,功能强大,使用方便。
二 Spring MVC执行流程
1.DispatcherServlet
DispathcherServlet获得请求以后,调用SpringMVC的各个组件来处理请求。
<servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>
⑴contextConfigLocation
DispatcherServlet默认从WEB-INF目录下加载SpringMVC的配置文件,可以通过属性contextConfigLocation更改配置文件的位置,这一点同Spring与Web整合时遇到的问题及其处理方式相同。
⑵load-on-startup
默认情况下,Servlet在被请求时才实例化初始化,如果希望在服务器启动时创建Servlet对象,可以通过<load-on-startup>标签设定。
⑶<url-pattern>
请求:一切以从服务器端获取资源为动机的行为都是请求,分为两类:
- 显式请求:用户在浏览器端通过手动点击或者输入的方式直接发出的请求。
- 隐式请求:包含在显示请求内部,不是由用户直接触发的请求,比如用户手动请求一个页面,这个请求就是显式,而页面从服务器加载图片的行为不是用户直接手动触发的,属于隐式请求。
一般写成*加后缀的形式,如“*.do”,不能写成这种形式“/*”,这种形式会把Web服务器接收到的请求全部转发给DispatcherServlet,而SpringMVC容器中不一定存在对应的Handler,会因找不到对应的资源而报错,比如无法加载HTML、JSP页面。
配置“/”形式,无法处理页面中的静态请求,即HTML页面以及JSP页面中的.jsp、.css等静态信息无法加载。两种处理方法:
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.jpg</url-pattern> </servlet-mapping>
- 通过匹配静态资源名称的方式将页面中所有静态资源包含进来。
- 实现原理:Web服务启动时加载两个web.xml文件,一个在tomcat安装目录下的config文件夹中,称作默认容器,一个是项目中的,称作自定义容器。当两个容器出现冲突信息时,以项目中的为准。在默认容器中,定义了一个DefaultServlet,servlet-name为default。DefaultServlet用于为服务器中的其他程序提供静态资源。
第二种方式:在Spring配置文件中加入,底层也是调用了DefaultServlet:
<mvc:default-servlet-handler/>
2.执行流程
浏览器发出请求,Web服务器解析该请求,如果匹配DispatcherServlet的映射路径,就将请求转发给DispatcherServlet。DispatcherServlet获得请求以后,首先判断该请求是否是文件上传请求,然后将请求转发给HandlerMapping,HandlerMapping根据映射关系找到对应的Handler,并将Handler与HandlerInterceptor封装成一个HandlerExecutionChain对象返回给中央调度器,中央调度器根据Handler获取HandlerAdaptor,HandlerAdaptor调用对应的Handler执行业务逻辑,处理完毕返回一个ModelAndView对象给中央处理器,中央处理器将ModelAndView对象转发给ViewResolver视图解析器,处理完毕返回一个View对象,中央处理器再将View对象转发给View视图渲染器,渲染完毕,中央处理器将视图响应给浏览器。
3.ModelAndView
一个沟通业务逻辑与视图的类,因为包含最终视图的全部信息,又不是最终视图,被称作逻辑视图,其中既包含早已建好的视图模板,也包含需要传入视图的动态参数Model。
三 配置式开发
1.配置式开发
由程序员定义SpringMVC各部分具体执行类的开发模式,叫做配置式开发,而不是完全采用系统默认的类。
2.处理器
继承抽象类或实现接口来自定义处理器,可用的抽象类与接口:
- Controller:接口。
- HttpRequestHandler:接口。
- AbstractController:继承类,通过属性supportedMethods可限定请求方式,GET或者POST。
3.HandlerMapping
访问资源时使用的不是资源的全限定性类名或者其他可以直接确定资源的方式,而是采用url,这样就需要在访问方式与与资源之间建立起一对一的关系,这种关系就是映射关系,HandlerMapping就负责创建与解析这种关系,根据访问方式确定处理器。
- BeanNameUrlHandlerMapping:默认的处理器映射器,url与beanName相同。
- DefaultAnnotationHandlerMapping:注解开发时默认的处理器映射器。
- SimpleUrlHandlerMapping:自定义url,在url与beanName之间建立映射关系:
-
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="urlMap"> <map> <entry key="/simpleUrl.do"value="myController" /> <entry key="/simpleUrl01.do"value="myController" /> </map> </property> </bean>
如果显式地配置了处理器映射器,那么默认的映射器不再起作用。
HandlerMapping返回一个封装了Handler与HandlerInterceptor的HandlerExecutionChain给中央调度器。
4.解析方法
从url中解析出方法:
- InternalPathMethodNameResolver:默认解析方法,请求url语法格式:/xxxx/methodName.do。
- ParamterMethodNameResolver:url语法格式/xxx?action=methodName,action为paramName,默认action,可以自定义。
- PropertiesMethodNameResolver:请求可以采用/*.do的格式,在PropertiesMethodNameResolver中通过属性mappings建立请求url与方法的映射关系。
5.HandlerAdapter
⑴背景
Handler有多种类型,中央调度器不能同时处理,而是通过调用对应的适配器来处理,这样分工明确,中央调度器结构清晰。
⑵底层实现
采用适配器模式:根据处理器实现的接口获取对应的适配器,由适配器调用处理器执行其中的方法。
6.ModelAndView
⑴model与view
- model:数据对象,包含将要传入页面中的动态参数。Model对象最好不要作为异常处理方法的形参,因为系统在向异常处理方法传递Model对象时存在很大的不确定性。
- view:视图对象,封装了显示的视图。
⑵3种创建方式:
-
ModelAndViewmv=new ModelAndView(); mv.setViewName("内部视图相对于项目的路径");
- 使用RedirectView类创建外部视图:
-
<bean id="taobao"class="org.springframework.web.servlet.view.RedirectView"> <property name="url" value="http://www.taobao.com" /> </bean>
- 使用JstlView创建内部视图:
-
<bean id="innerResource"class="org.springframework.web.servlet.view.JstlView"> <property name="url" value="/WEB-INF/firstDemo/welcome.html" /> </bean>
7.ViewResolver
不同的定义视图的方式对应不同的视图解析器:
- InternalResourceViewResolve:默认的视图解析器,根据路径解析视图,不仅可以解析站内视图,还可以解析站外视图。
- BeanNameViewResolver:在配置文件中利用RedirectView或者JstlView类封装视图,在java代码中使用beanName返回视图,BeanNameViewResolver根据beanName解析视图。
如果视图较多,可以单独放在一个文件中,SpringMVC提供了两种定义视图对象的文件:xml、properties。
⑴XmlViewResolver
将视图对象放在XML文件中:
<bean class="org.springframework.web.servlet.view.XmlViewResolver"> <property name="location"value="classpath:com/springmvc/viewResolver/myViews.xml"/> </bean>
⑵ResourceBundleViewResolver
将视图对象放在属性文件中,属性文件只能放在类路径下:
viewName.(class)=RedirectView/JstlView
viewName.url=http://xxxxxx
将属性文件加载到配置文件中并解析视图:
<bean class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="basenames" value="myViews" /> </bean>
- baseNames:类路径下放置视图的属性文件文件名,不包括扩展名。
⑶优先级
- 允许配置多个视图解析器,默认情况下,在配置文件中上方的优先级高于下方的,默认的视图解析器InternalResourceViewResolver级别最低。
- 可以使视图解析器的优先级不受配置顺序的约束:在配置视图解析器时,通过order属性设定优先级,int类型,大于0,值越小,优先级越高。
四 处理器的注解式开发
1.加载
Spring注解采用被动加载,必须指明采用注解的组件位置:
<context:component-scan base-package=""/>
2.常用注解
⑴@Controller
标在类名上,表明该类是一个处理器。
⑵@RequestMapping("")
标在类名上,用作请求url的公共开头部分,可称作命名空间,与方法上的url连接时没有“/”系统自动增加“/”,所以用以连接的“/”可以省略。
- /*:代表一级目录。
- /**:代表任意级目录。
⑶@RequestMapping(value="")
标在方法上,用来定义访问该处理方法的请求url,其他属性:
- method:限定请求方式,如method=RequestMethod.POST。
- params:限定请求必须携带的参数。
-
params={"paramName01","paramName02"}//请求中必须出现参数paramName01与paramName02 params={"!paramName01","paramName02"}//请求中不能出现参数paramName01,必须出现参数paramName02 params={"paramName01=value02"}//请求中必须出现参数paramName01,并且其值必须为value01 params={"paramName01!=value02"}//请求中必须出现参数paramName01,并且其值不能为value01
3.自定义方法
自定义处理方法没有任何限制,可以有返回值,也可以没有返回值,可以有形参,也可以没有形参。可以有的形参:
- HttpServletRequest
- HttpServletResponse
- HttpSession
- Model
- 请求参数或者请求参数的封装类
- BindingResult
五 表单输入注入处理方法
1.分散注入
表单输入名与方法形参名相同时,自动注入;不相同时,需要在表单输入名与形参名之间建立映射关系:
public ModelAndViewdoFirst(@RequestParam("name") String pname, int age) {}
2.封装注入
将表单输入注入到封装对象中,只需要将处理方法的形参设定为封装类型对象即可。
3.域属性注入
如果封装对象中包含域属性,表单在设定输入名称时域属性的属性通过域属性引用的形式给定。
<form action="rph03.do" method="post"> 姓名:<input type="text" name="name"><br> 年龄:<input type="text" name="age"> <br> 学校:<input type="text"name="school.name"><br> 地址:<input type="text" name="school.addr"><br> <input type="submit" value="提交"> </form>
- school是域属性,name/addr是域属性的属性。
4.pathVariable
在请求路径中加入变量,处理方法从路径中获取参数值:
@RequestMapping("/{pname}/rph04.do") public ModelAndView doFourth(@PathVariable(name = "pname") String name, int age){}
- name不仅作为路径变量,而且为方法的形参name赋值。实质上,就是将请求参数作为路径的一部分,包含在路径当中。
- @PathVariable:用来建立路径变量与方法形参之间的映射关系。
5.其他
- 不是所有的表单输入都必须被接收,也不是处理方法中的每一个参数都必须被注入。
- SpringMVC将接收表单输入的封装对象保存到request作用域中,分散接收变量没有保存到request作用域中。
六 处理器返回值
1.String
- 视图路径。
- 视图beanName。
2.void
可以向页面响应JSON字符串。
3.Object
将响应内容通过响应体发送给浏览器。
⑴环境搭建
- 在配置加入<mvc:annotation-driven/>。
- 在方法上注解@ResponseBody,表示将返回值放入响应体中。
⑵基本数据类型与字符串
解决中文乱码问题,在@RequestMapping中加入属性produces="text/html;charset=UTF-8",限定响应内容类型与编码。
⑶自定义类、Map集合、List集合
通过响应体以JSON字符串的格式返回,需要导入jackson架包,目的是在容器初始化时创建HttpMessageConverter转化器,将返回值转化为json字符串。
4.默认处理
默认情况下,返回的字符串类型的数据被当作视图名处理,如果希望保存到响应体中,必须在处理方法上加注解@ResponseBody。
七 转发与重定向
- 请求转发时,Model中的数据自动保存在request作用域中。
- 重定向时,Model中的数据自动保存在param环境信息中。
- return "forward:/xxx":转发到页面或者另一个处理方法,forward是默认值,可以省略。
- return "redirect:/xxxx":重定向到页面或者另一处理方法。
八 异常处理
1.跳转到页面
使用内置类SimpleMappingExceptionResolver,在异常类型与跳转页面间建立映射关系,发生异常时,跳转到指定页面:
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="defaultErrorView" value="/Exception/error.jsp" /> <property name="exceptionMappings"> <props> <prop key="com.springmvc.exception.AgeException">/Exception/ageException.jsp</prop> <prop key="com.springmvc.exception.NameException">/Exception/nameException.jsp</prop> </props> </property> </bean>
- defaultErrorView:为没有指定跳转页面的异常类型设定一个默认的跳转页面。
- exceptionMappings:为异常定制跳转页面。
2.接口式异常处理器
实现HandlerExceptionResolver接口,在配置文件中配置后即可使用,不仅可以处理方法内部的异常,还可以处理方法调用时的异常,如类型转化异常。
3.注解式异常处理器
⑴创建
@ExceptionHandler(MyException.class) public ModelAndView handleException(HttpServletRequest request,Exception ex)
自定义异常处理器方法中不能有Model类型参数, 因为Model只有在作为处理器方法的形参时才被实例化。
⑵可处理的异常范围
只对所在处理器中的方法有效,不仅可以处理方法内部发生的异常,也可以处理调用处理方法时发生的异常,比如类型转化异常。
4.优先级
如果同时创建了接口式异常处理器与注解式异常处理器,采用注解式异常处理器,接口式不起作用。
5.选择依据
根据需要对异常进行处理的深度,选择异常处理方式:
- 如果当异常发生时,仅仅需要输出异常信息,跳转到指定页面,可以采用配置内置的异常处理器的方式。
- 如果需要进一步处理异常,可以采用接口式或者注解式异常处理器。
6.异常处理过程
异常处理器被调用的过程是一个请求转发的过程。
7.异常
异常处理器只能捕获抛出的异常,在方法内部处理的异常无法获取,因为异常已经在方法内部处理了,不需要再处理。
九 数据类型转换
1.类型转换器选择
根据表单输入与处理器方法形参之间的映射关系,将表单输入按照对应的形参类型进行转换。
2.内置转换器的作用
SpringMVC为常见的java类型提供了类型转换器,转化是由框架默认完成的,不需要程序员参与,主要是将字符串转换为基本数据类型,如将字符串转化为int、double等。
3.接口式类型转换器器
实现Converter接口自定义类型转化器,在SpringMVC中类型转换器统一由ConversionServiceFactoryBean管理,将自定义的类型转换器添加到管理器中:
<bean id="myDateConverter"class="com.springmvc.conversion.MyDateConverter" /> ------------生成的不是工厂对象,而是由工厂类生成转换服务对象----------- <bean id="conversionService"class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <ref bean="myDateConverter"/> </set> </property> </bean>
将转换服务对象添加到注解驱动中, 一些不常用的功能统一由注解驱动管理:
<mvc:annotation-driven conversion-service="conversionService" />
接口式类型转换器中发生的异常不能被接口式异常处理器处理,运行时异常可以被注解式异常处理器处理,受查异常无法被注解式异常处理器处理。
4.注解式类型转换器
返回值void,方法形参WebDataBinder类型:
@InitBinder//表明该方法用作类型转化方法
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Date.class,new MyPropertiesEditor());//自定义属性编辑器
}
- 自定义属性编辑器MyPropertiesEditor,继承PropertiesEditor,提交格式不在可转换格式之列时,必须抛出异常TypeMismatchException,不然无法启动异常处理器。主要实现同接口式相同,调用setValue方法保存转换结果。
- 注解式类型转化器必须在不可转换时显式抛出异常,才能启动异常处理器。
5.优先级
如果同时创建了接口式与注解式类型转换器,注解式优先起作用。
6.异常
当处理器方法分散接收表单输入时,无论形参时基本数据类型,还是对象类型,转化失败,都将抛出TypeMismatchException。
当处理器方法以封装形式接收表单输入时,基本数据类型转化失败,抛出BindException,对象类型转换失败,抛出IllegalArgumentException。
7.数据回显与显示提示信息
⑴什么是数据回显?
当表单输入发生类型转化错误或者其他原因导致验证未通过时,视觉上页面不跳转,用户输入数据不丢失,原样显示。
⑵原理
数据回显与显示提示信息都是利用异常处理机制,当发生类型转化异常或者其他验证异常时,启动异常处理器,经异常处理器跳转到输入页面,同时将输入数据与提示信息显示在输入页面中。
⑶具体实现
- 在表单设计时,使用EL表达式插入用于回显的内容。
- 用于数据回显的异常处理器必须限定异常类型,类型转换过程可能抛出三种异常:TypeMismatchException/BindException/IllegalArgumentException,只能接收这三种异常,不然发生其他异常时也触发异常处理器,跳转到输入页面:
-
@ExceptionHandler({ TypeMismatchException.class, BindException.class, IllegalArgumentException.class }) public void handleException(HttpServletRequest request, Exception ex) { }
十 数据验证
1.前提
数据验证是在类型转换成功的基础上对表单输入的进一步验证,如果类型转换未通过,不会进行数据验证。
2.Hibernate Validator
SpringMVC支持多种数据验证实现,目前项目中主要使用Hibernate Validator,使用时需导入Hibernate Validator架包。
3.域模型
Hibernate Validator是基于domain model的,必须在域模型属性上添加约束,使用时只有注解@Validated开启约束后才起作用:
- @NotNull(message=""):非空约束,用于约束字符串。
- @NotEmpty():既不可以为空字符串,也不可以为null。
- @Size(max="",min="",message="取值范围{min}-{max}"):约束字符串长度。
- @Max(value=""):最大值约束。
- @Min(value=""):最小值约束。
- @Post:约束日期,表示只能输入相对于当前过去的日期。
- @Future:约束日期,表示只能输入相对于当前未来的日期。
- @Range(max="",min=""):约束取值范围。
- @Pattern(regexp=""):要求输入内容必须匹配给定的正则表达式。
- @DateTimeFormat(pattern = "yyyy-MM-dd"):使用指定的格式将字符串转化为日期。
4.配置文件编写
注册验证器:
<bean id="validator"class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> <property name="providerClass"value="org.hibernate.validator.HibernateValidator" /> </bean> <mvc:annotation-driven validator="validator" />
Hibernate Validator抑制接口式类型转换器,即一旦创建了Hibernate Validator,接口式类型转换器不再起作用。
5.处理器方法编写
⑴形参
- @Validated:处理器采用域模型接收表单输入,形参前添加注解@Validated,表明此处的域模型对象需要验证。
- BindingResult:在形参中加入BindingResult对象,获取表单输入注入状况,既包含类型转换状况,也包含数据验证状况。
⑵一般步骤
boolean b=result.hasFieldErrors();//判断注入过程中是否发生异常 FieldError nameFieldError=result.getFieldError("name");//获取向某个属性注入表单输入时发生的异常 String message=nameFieldError.getDefaultMessage();//获取发生异常时的提示信息
- 发生验证异常时的提示信息是程序员在域模型中定义的message,可以直接输出。
- 发生类型转换异常时默认采用系统自定义的信息,英文,较长,对用户不友好,因此发生转换异常时需要自定义提示信息。由于发生类型转换异常的提示信息,大多以“Failed to convert property value of type”开头,据此可以判断异常类型。
⑶对应
一个域模型对象对应一个BindingResult,为了保证对应关系,在处理器方法中域模型形参与BindingResult两个形参紧邻,中间不能有其他参数,并且域模型在前。
十一 文件上传
1.具体过程
DispatcherServlet获取请求以后,首先判断该请求是否是文件上传请求,如果是则采用文件上传时特有的操作。基本过程与处理非文件上传请求时相同,加入了特有操作。
2.CommonsMultipartResolver
该类定义了很多实际开发时需要调整的参数,需要在配置文件中配置,由DispatcherServlet调用,引用变量名multipartResolver不能更改。
- 设定编码方式,解决文件名为中文时的乱码问题。
- 定义单次允许上传的最大文件值。
3.MultipartFile
处理方法形参中必须包含一个MultipartFile参数,接收上传的文件,变量名为表单中文件名,多个文件上传时,参数为数组。
4.一般步骤
文件上传前判断是否选择了文件,不然导致虚假上传。即使在没有文件上传时MultipartFile对象依然被实例化,因此不能根据对象是否为null判断是否选择了文件,可以根据文件大小getSize判断。
String filename=mf.getOrginalFilename();//获取文件名getName获取的表单中的文件名 ............................................ mf.transferTo(file);
十二 拦截器
1.创建
SpringMVC只提供了一种创建拦截器的方法:接口式,即实现HandlerInterceptor接口。接口中共有三个方法:
⑴preHandler
拦截器前置通知,在处理器方法执行前执行,用于决定是否继续执行请求。返回true,继续执行后面的拦截器或者处理方法,不论后面的执行状况如何,afterCompletion都会被执行。返回false终止执行,请求结束,不会执行afteCompletion。
⑵postHandler
拦截器后置通知,在处理器方法执行完毕后执行,主要用于向ModelAndView中添加数据。
⑶afterCompletion
拦截器渲染后通知,主要用于关闭资源:
- 执行条件:前置通知返回true。
- 执行时机:在视图渲染完毕之后执行。
2.注册
<mvc:interceptors> <mvc:interceptor> <mvc:mappingpath="/*" /> <ref bean="oneInterceptor" /> </mvc:interceptor> <mvc:interceptor> <mvc:mappingpath="/*" /> <bean class="com.springmvc.interceptor.TwoInterceptor" /> </mvc:interceptor> </mvc:interceptors>