第四章 SpringMVC核心技术
4.1请求转发与重定向
当处理器对请求处理完毕后,向其它资源进行跳转时,有两种跳转方式:请求转发与重定向。而根据所要跳转的资源类型,又可分为两类:跳转到页面与跳转到其它处理器。
注意,对于请求转发的页面,可以是WEB-INF中页面;而重定向的页面,是不能为WEB-INF中页的。因为重定向相当于用户再次发出一次请求,而用户是不能直接访问WEB-INF中资源的。
4.1.1返回ModelAndView时的请求转发
默认情况下,当处理器方法返回 ModelAndView时,跳转到指定的View,使用的是请求转发。但也可显式的进行指出。此时,需在setViewName()指定的视图前添加forward:,且此时的视图不会再与视图解析器中的前辍与后辍进行拼接。即必须写出相对于项目根的路径。
故此时的视图解析器不再需要前辍与后辍。
(1)请求转发到页面:
当通过请求转发跳转到目标资源(页面或Controller)时,若需要向下传递数据,除了可以使用request,session外,还可以将数据存放于ModelAndView中的Model中。页面通过EL表达式可直接访问该数据。
1、修改处理器类
2、spring MVC配置文件
(2)请求转发到其它Controller
直接修改处理器类
4.1.2返回ModelAndView时的重定向
返回ModelAndView时的重定向,需在setViewName()指定的视图前添加redirect:,且此时的视图不会再与视图解析器中的前辍与后辍进行拼接。即必须写出相对于项目根的路径。
故此时的视图解析器不再需要前辍与后辍。
重定向的目标资源中,将无法访问用户提交请求request中的数据。
(1)重定向到页面
在重定向时,请求参数是无法通过request的属性向下一级资源中传递的。但可以通过以下方式将请求参数向下传递。
A、通过ModelAndView的Model携带参数
当ModelAndView中的Mode存入数据后,视图解析器InternalResourceViewResolver会将map中的key与value,以请求参数的形式放到请求的URL后。
不过需要注意以下几点:
>由于视图解析器会将Map的value放人到URL后作为请求参数传递出去,所以无论什么类型的value,均会变为String。故此,放入到Model中的value,只能是基本数据类型与String,不能是身定义类型的对象数据。
>重定向的面页中是无法request中读取数据的。但由于map中的key与value,以请求参数的形式放到了请求的URL后,所以,页面可以通过EL表达式中的请求参数param读取。
>重定向的页面不能是/WEB-INF中的页面。
/36-redirect-page-modelAndView
1、修改处理器类
用redirect重定向
@org.springframework.stereotype.Controller //表示当前类是一个处理器
@RequestMapping("/test") //命名空间 会帮助你自动拼接,所以不用考虑hello等加不加斜杠了
public class MyController {
@RequestMapping("/register.do")
public ModelAndView doRegister(String name, int age) {
System.out.println("name = " + name);
System.out.println("age = " + age);
ModelAndView mv = new ModelAndView();
mv.addObject("pname", name);//其实就是放在request域空间
mv.addObject("page", age);
mv.setViewName("redirect:/welcome.jsp");
return mv;
}
}
2、修改welcome页面位置
修改show页面位置
由于重定向的请求无法访问/WEB-INF/下的内容,所以此时的show页面需要调换位置:
将show页面放到项目的根路径WebContent下。
由于Mode中的数据是放在URL后的参数,所以页面需要使用param来接收。
param.age的底层执行的是 request.getParameter(“age”);获取到字符串
requestScope.age的底层执行的是request.getAttribute(“age”);获取到的是整型
<html>
<head>
<title>welcome page</title>
</head>
<!-- param.age的底层执行的是 request.getParameter("age");获取到字符串
requestScope.age的底层执行的是request.getAttribute("age");获取到的是整型 -->
<body>
name = ${param.pname }<br>
age = ${param.page }<br>
</body>
</html>
B、使用HttpSession 携带参数 见文档。
(2)重定向到Controller
重定向到其它Controller时,若要携带参数,完全可以采用前面的方式。而对于目标Controller 接收这些参数,则各不相同。
A、通过ModelAndView的Model携带参数
目标 Controller 在接收这些参数时,只要保证目标 Controller的方法形参名称与发送Controller发送的参数名称相同即可接收。当然,目标Controller也可以进行参数的整体接收。
只要保证参数名称与目标 Controller接收参数类型的属性名相同即可。
直接修改处理器。
逐个接收:
4.1.4返回String时的重定向
在处理器方法返向的视图字符串的前面添加前辍redirect:,则可实现重定向跳转。
当重定向到目标资源时,若需要向下传递参数值,除了可以直接通过请求URL携带参数,通过HttpSession携带参数外,还可使用其它方式。
(1)重定向到页面
A、通过Model 形参携带参数
可以在Controller形参中添加Model参数,将要传递的数据放入Model中进行参数传递。
该方式同样也是将参数拼接到了重定向请求的URL后,所以放入其中的数据只能是基本类型数据,不能是自定义类型。
/37-redirect-controller-string-2
@org.springframework.stereotype.Controller //表示当前类是一个处理器
@RequestMapping("/test") //命名空间 会帮助你自动拼接,所以不用考虑hello等加不加斜杠了
public class MyController {
@RequestMapping("/register.do")
public String doRegister(String name, int age, Model model) {
model.addAttribute("pname", name);//注意这里http协议里不能放对象
model.addAttribute("page", age);
//通过model来访问
return "redirect:other.do";//注意这里不加斜杠,加了斜杠就是服务器应用的根,
//会差一个test访问不到other
}
@RequestMapping("/other.do")
public String doOther(String pname, int page) {
System.out.println("pname = " + pname);
System.out.println("page = " + page);
return "/welcome.jsp";
}
}
还有返回void类型的例子,就不一一赘述了。
4.2异常处理
常用的SpringMVC异常处理方式主要有三种:
>使用系统定义好的异常处理器SimpleMappingExceptionResolver
>使用自定义异常处理器
>使用异常处理注解
4.2.1SimplelMappingExceptionResolver 异常处理器
该方式只需要在SpringMVC配置文件中注册该异常处理器 Bean即可。该Bean比较特殊,没有id属性,无需显式调用或被注入给其它,当异常发生时会自动执行该类。
提示:当请求参数的值与接收该参数的处理器方法形参的类型不匹配时,会抛出类型匹配有误异常TypeMismatchException。
/38-SimpleMappingExceptionResolver
(1)自定义异常类
定义三个异常类:NameException、AgeException、StudentException。其中StudentException是另外两个异常的父类。
NameException、AgeException都继承自它,这里就省略了,代码一样。
(2)修改Controller
判断传进来的参数是否符合要求,增加一个其它异常3/0
@org.springframework.stereotype.Controller //表示当前类是一个处理器
@RequestMapping("/test") //命名空间 会帮助你自动拼接,所以不用考虑hello等加不加斜杠了
public class MyController {
@RequestMapping("/register.do")
public ModelAndView doRegister(String name, int age) throws StudentException {
//int i = 3 / 0;
if(!"beijing".equals(name)) {
throw new NameException("用户名不正确");
}
if(age > 60) {
throw new AgeException("太老了");
}
ModelAndView mv = new ModelAndView();
mv.addObject("name", name);//其实就是放在request域空间
mv.addObject("age", age);
mv.setViewName("/WEB-INF/jsp/welcome.jsp");
return mv;
}
}
(3)注册异常处理器
exceptionMappings:Properties 类型属性,用于指定具体的不同类型的异常所对应的异常响应页面。Key为异常类的全限定性类名,value则为响应页面路径
defaultErrorView:指定默认的异常响应页面。若发生的异常不是exceptionMappings中指定的异常,则使用默认异常响应页面。
exceptionAttribute:捕获到的异常对象。一般异常响应页面中使用。
(4)定义异常响应页面
在WebContent下新建一个目录errors,在其中定义三个异常响应页面。
初始页面
<html>
<head>
<title>My JSP 'index.jsp' starting page</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/test/register.do" method="POST">
姓名:<input type="text" name="name"/><br>
年龄:<input type="text" name="age"/><br>
<input type="submit" value="注册"/>
</form>
</body>
</html>
4.2.2自定义异常处理器
使用SpringMVC定义好的SimpleMappingExceptionResolver异常处理器,可以实现发生指定异常后的跳转。但若要实现在捕获到指定异常时,执行一些操作的目的,它是完成不了的。此时,就需要自定义异常处理器。
自定义异常处理器,需要实现HandlerExceptionResolver接口,并且该类需要在SpringMVC配置文件中进行注册。
/39-customExceptionResolver
(1)定义异常处理器
当一个类实现了HandlerExceptionResolver接口后,只要有异常发生,无论什么异常,都会自动执行接口方法resolveException()。
package com.bjpowernode.resolvers;
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler,
Exception ex) {
ModelAndView mv = new ModelAndView();
mv.addObject("ex",ex);
mv.setViewName("/errors/error.jsp");//默认其他异常页面
//前一个操作符是一个引用变量,后一个操作数通常是一个类(可以是接口),
//用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。如果是返回true,否则返回false。
if(ex instanceof NameException) {
//执行一些操作
mv.setViewName("/errors/nameError.jsp");
}
if(ex instanceof AgeException) {
//执行一些操作
mv.setViewName("/errors/ageError.jsp");
}
return mv;
}
(2)注册异常处理器
<!-- 注册异常处理器 -->
<bean class="com.bjpowernode.resolvers.MyHandlerExceptionResolver"/>
其他的都一样。
4.2.3 异常处理注解
使用注解@ExceptionHandler 可以将一个方法指定为异常处理方法。该注解只有一个可选属性value,为一个Class<?>数组,用于指定该注解的方法所要处理的异常类,即所要匹配的异常。
而被注解的方法,其返回值可以是ModelAndView、String,或void,方法名随意,方法参数可以是Exception及其子类对象、HttpServletRequest、HttpServletResponse等。系统会自动为这些方法参数赋值。对于异常处理注解的用法,也可以直接将异常处理方法注解于Controller之中。
不过,一般不这样使用。而是将异常处理方法专门定义在一个Controller中,让其它Controller 继承该Controller即可。但是,这种用法的弊端也很明显:Java是“单继承多实现”的,这个Controller的继承将这唯一的一个继承机会使用了,使得若再有其它类需要继承,将无法直接实现。
/40-exceptionHandler
(1)定义异常处理基类
package com.bjpowernode.handlers;
@org.springframework.stereotype.Controller //表示当前类是一个处理器
//基本异常类,其它类继承它就可以,不处理请求
public class BaseController {
//可以带参数的写法 处理NameException异常
@ExceptionHandler(NameException.class)
public ModelAndView handlerNameException(Exception ex) {
ModelAndView mv = new ModelAndView();
mv.addObject("ex",ex);
mv.setViewName("/errors/nameError.jsp");
return mv;
}
//可以带参数的写法 处理AgeException异常
@ExceptionHandler(AgeException.class)
public ModelAndView handlerAgeException(Exception ex) {
ModelAndView mv = new ModelAndView();
mv.addObject("ex",ex);
mv.setViewName("/errors/ageError.jsp");
return mv;
}
//处理其它异常
@ExceptionHandler
public ModelAndView handlerException(Exception ex) {
ModelAndView mv = new ModelAndView();
mv.addObject("ex",ex);
mv.setViewName("/errors/error.jsp");//默认其他异常页面
return mv;
}
}
(2)处理器类继承异常类
package com.bjpowernode.handlers;
@org.springframework.stereotype.Controller //表示当前类是一个处理器
@RequestMapping("/test") //命名空间 会帮助你自动拼接,所以不用考虑hello等加不加斜杠了
public class MyController extends BaseController {
@RequestMapping("/register.do")
public ModelAndView doRegister(String name, int age) throws StudentException {
//int i = 3 / 0;
//如果名字和年龄都错了 则显示用户名不正确页面,因为已经抛出了,年龄那不再执行
if(!"beijing".equals(name)) {
throw new NameException("用户名不正确");
}
if(age > 60) {
throw new AgeException("太老了");
}
ModelAndView mv = new ModelAndView();
mv.addObject("name", name);//其实就是放在request域空间
mv.addObject("age", age);
mv.setViewName("/WEB-INF/jsp/welcome.jsp");
return mv;
}
}
4.3类型转换器
在前面的程序中,表单提交的无论是int还是double类型的请求参数,用于处理该请求的处理器方法的形参,均可直接接收到相应类型的相应数据,而非接收到String再手工转换。
那是因为在SpringMVC框架中,有默认的类型转换器。这些默认的类型转换器,可以将String类型的数据,自动转换为相应类型的数据。
4.3.1搭建测试环境
/41-typeConverter
(1)修改index页面
<body>
<form action="${pageContext.request.contextPath }/test/register.do" method="POST">
年龄:<input type="text" name="age"/><br>
生日:<input type="text" name="birthday"/><br>
<input type="submit" value="注册"/>
</form>
</body>
(2)修改处理器类
把输入的年龄和生日日期放入request域空间
@org.springframework.stereotype.Controller //表示当前类是一个处理器
@RequestMapping("/test") //命名空间 会帮助你自动拼接,所以不用考虑hello等加不加斜杠了
public class MyController {
@RequestMapping("/register.do")
public ModelAndView doRegister(int age, Date birthday) {
System.out.println("age = " + age);
System.out.println("birthday = " + birthday);
ModelAndView mv = new ModelAndView();
mv.addObject("birthday", birthday);//其实就是放在request域空间
mv.addObject("age", age);
mv.setViewName("/WEB-INF/jsp/welcome.jsp");
return mv;
}
}
(3)修改welcome页面
显示输入的信息
<html>
<head>
<title>welcome page</title>
</head>
<body>
age = ${age }<br>
birthday = ${birthday }<br>
</body>
</html>
(4)修改SpringMVC配置文件
<!-- 注册组件扫描器 -->
<context:component-scan base-package="com.bjpowernode.handlers"/>
4.3.2自定义类型转换器
/41-typeConverter
若要定义类型转换器,则需要实现Converter接口。该Converter接口有两个泛型:第一个为待转换的类型,第二个为目标类型。而该接口的方法convert(),用于完成类型转换。
1、新加一个类实现Converter接口
//Converter接口中的两个泛型表示:
//第一个:表示源的类型
//第二个:表示宿的类型,即转换为的目标类型
public class MyDateConverter implements Converter<String, Date> {
//这里是实现别人的接口,所以只能try catch不能抛出哦
@Override
public Date convert(String source) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
try {
return sdf.parse(source);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
4.3.3对类型转换器的配置
类型转换器定义完毕后,需要在SpringMVC的配置文件中对类型转换进行配置。首先要注册类型转换器,然后再注册/个转换服务 Bean。将类型转换器注入给该转换服务Bean。
最后由处理器适配器来使用该转换服务Bean。
都是这一个程序**/41-typeConverter**
再修改SpringMVC
1、注册类型转换器
2、创建转换服务Bean
对于类型转换器,并不是直接使用,而是通过转换服务Bean来调用类型转换器。而转换服务Bean的创建,是由转换服务工厂Bean–ConversionServiceFactoryBean完成。
该工厂Bean有一个Set集合属性converters,用于指定该转换服务可以完成的转换,即可以使用的转换器。从Set集合可知,各转换器间无先后顺序。
由于SpringMVC对于类型转换的完成,不是直接使用类型转换器,而是通过创建出具有多种转换功能的转换服务Bean来完成的。所以,SpringMVC同时支持多种类型转换器。
3、使用转换服务Bean
转换服务Bean是由处理器适配器直接调用的。采用mvc的注解驱动注册方式,可以将转换服务直接注入给处理器适配器。
<!-- 注册类型转换器 -->
<bean id="myDateConverter" class="com.bjpowernode.converters.MyDateConverter"/>
<!-- 注册转换服务对象 -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters" ref="myDateConverter"/>
</bean>
<!-- 注册MVC注解驱动 -->
<mvc:annotation-driven conversion-service="conversionService"/>
拦截器one是true,two是false。
结果:
执行OneInterceptor------preHandle()---------
执行TwoInterceptor------preHandle()---------
执行OneInterceptor------afterCompletion()---------
如果拦截器one是false,two是true。
结果:
执行OneInterceptor------preHandle()---------
总结:只有当preHandle()方法执行了,且返回的是true时,-afterCompletion()才会被执行。
4.3.4接收多种日期格式的类型转换器
直接修改类型转换器即可
/42-typeConverter-2
//Converter接口中的两个泛型表示:
//第一个:表示源的类型
//第二个:表示宿的类型,即转换为的目标类型
public class MyDateConverter implements Converter<String, Date> {
//这里是实现别人的接口,所以只能try catch不能抛出哦
@Override
public Date convert(String source) {
SimpleDateFormat sdf = getDateFormat(source);
try {
return sdf.parse(source);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
private SimpleDateFormat getDateFormat(String source) {
SimpleDateFormat sdf = new SimpleDateFormat();
if(Pattern.matches("^\\d{4}-\\d{2}-\\d{2}$", source)) {
sdf = new SimpleDateFormat("yyyy-MM-dd");
} else if(Pattern.matches("^\\d{4}/\\d{2}/\\d{2}$", source)){
sdf = new SimpleDateFormat("yyyy/MM/dd");
} else if(Pattern.matches("^\\d{4}\\d{2}\\d{2}$", source)){
sdf = new SimpleDateFormat("yyyyMMdd");
}
return sdf;
}
}
4.3.5数据回显
当数据类型转换发生异常后,需要返回到表单页面,让用户重新填写。但正常情况下,发生类型转换异常,系统会自动跳转到400页面。所以,若要在发生类型转换异常后,跳转到指定页面,则需要将异常捕获,然后通过异常处理器跳转到指定页面。
若仅仅是完成跳转,则使用系统定义好的SimpleMappingExceptionResolver就可以。但,当页面返回到表单页面后,还需要将用户原来填写的数据显示出来,让用户更正填错的数据。
也就是还需要完成数据回显功能。此时就需要自定义异常处理器了。
/45-typeConverter-5
(1)修改处理器
类型转换异常为TypeMismatchException。
数据回显原理:在异常处理器中,通过request.getParameter(()将用户输入的表单原始数据获取到后,直接放入到ModelAndView中的Model中,然后从要转向的页面中就可以直接通过EL表达式读取出,也就实现了数据回显。
@org.springframework.stereotype.Controller //表示当前类是一个处理器
@RequestMapping("/test") //命名空间 会帮助你自动拼接,所以不用考虑hello等加不加斜杠了
public class MyController {
@RequestMapping("/register.do")
public ModelAndView doRegister(int age, Date birthday) {
System.out.println("age = " + age);
System.out.println("birthday = " + birthday);
ModelAndView mv = new ModelAndView();
mv.addObject("birthday", birthday);//其实就是放在request域空间
mv.addObject("age", age);
mv.setViewName("/WEB-INF/jsp/welcome.jsp");
return mv;
}
@ExceptionHandler(TypeMismatchException.class)
public ModelAndView exceptionResolver(HttpServletRequest request, Exception ex) {
ModelAndView mv = new ModelAndView();
String age = request.getParameter("age");
String birthday = request.getParameter("birthday");
//捕获到的异常信息
String errorMSG = ex.getMessage();
if(errorMSG.contains(age)) {
mv.addObject("ageErrors", "年龄输入有误");
}
if(errorMSG.contains(birthday)) {
mv.addObject("birthdayErrors", "日期输入有误");
}
mv.addObject("age", age);
mv.addObject("birthday", birthday);
mv.addObject("ex", ex);
mv.setViewName("/index.jsp");
return mv;
}
}
SpringMyc并没有专门的用于自定义类型转换失败后提示信息的功能。需要程序员自己实现。
查看类型转换失败后系统给出的提示信息,发现内容很多,但其中却包含着用户所提交的输入数据。所以,可以通过从系统提示信息中查找用户输入数据的方式,来确定是哪个数据出现了类型转换异常。使用String类的contains)方法来判断系统异常信息中是否存在用户输入数据。一旦出问题的数据被确定,则异常信息就可以进行准确替换了。
(2)页面表单
<html>
<head>
<title>My JSP 'index.jsp' starting page</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/test/register.do" method="POST">
年龄:<input type="text" name="age" value=${age }>${ageErrors }<br>
生日:<input type="text" name="birthday" value=${birthday }>${birthdayErrors }<br>
<input type="submit" value="注册"/>
</form>
</body>
</html>
(3)修改类型转换器
以上代码只能解决当年龄输入有误,但日期输入正确的回显问题。但,若日期输入有误,年龄输入正确时,将无法实现回现。因为当日期格式输入有误时,SimpleDateFormat的parse()方法将会抛出ParseException,而非TypeMismatchException。
那就让异常处理器再捕获ParseException完成跳转不也行吗?但类型转换器接口Converter<S,T>的convter()方法是没有抛出异常的,那也就是说,我们所实现的自定义类型转换器中的convert()方法了不能抛出异常,对异常的处理方式必须为try-catch。换句话说,convert()方法要将异常自己消化,而非抛出给调用者,即JVM。即当ParseException发生时,convert()方法自己处理了异常,而对于JVM来说,根本就不知道有ParseException异常的发生。这样的话,异常处理器也就无法捕获到ParseException了。
所以,解决的策略是,当用户输入的日期格式不符合要求时,手工抛出一个类型匹配异常。
```java
//Converter接口中的两个泛型表示:
//第一个:表示源的类型
//第二个:表示宿的类型,即转换为的目标类型
public class MyDateConverter implements Converter<String, Date> {
//这里是实现别人的接口,所以只能try catch不能抛出哦
@Override
public Date convert(String source) {
SimpleDateFormat sdf = getDateFormat(source);
try {
return sdf.parse(source);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
private SimpleDateFormat getDateFormat(String source) {
SimpleDateFormat sdf = new SimpleDateFormat();
if(Pattern.matches("^\\d{4}-\\d{2}-\\d{2}$", source)) {
sdf = new SimpleDateFormat("yyyy-MM-dd");
} else if(Pattern.matches("^\\d{4}/\\d{2}/\\d{2}$", source)){
sdf = new SimpleDateFormat("yyyy/MM/dd");
} else if(Pattern.matches("^\\d{4}\\d{2}\\d{2}$", source)){
sdf = new SimpleDateFormat("yyyyMMdd");
} else {
throw new TypeMismatchException("", Data.class);
}
return sdf;
}
}
不过,对于TypeMismatchException类,其没有无参构造器,这里使用了一个带两个参数的构造器。第一个参数为要匹配的值,第二个参数为要匹配的类型。即当第一个参数的类型与第二参数的类型没有isa关系时,该异常发生。
由于本例中要判断的值确定为String类型,而目标类型确定为Date类型,所以第一个参数就随便写了一个string常量。因为这里的判断与具体的数值无关,只与类型有关。
4.4 数据验证
这里暂时省略,一般都是在前端做数据验证,后台做比较少。
4.5文件上传
4.5.1上传单个文件
(1)导入两个jar包。
(2)定义上传页面
定义具有文件上传功能的页面index.jsp,其表单的设置需要注意,method属性为POST,enctype属性为multipart/form-data。另外,需要注意file 表单元素的参数名称,Controller中需要使用。
<html>
<head>
<title>My JSP 'index.jsp' starting page</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/test/upload.do" method="POST"
enctype="multipart/form-data">
文件:<input type="file" name="img"/><br>
<input type="submit" value="上传"/>
</form>
</body>
</html>
(3)定义处理器
@org.springframework.stereotype.Controller //表示当前类是一个处理器
@RequestMapping("/test") //命名空间 会帮助你自动拼接,所以不用考虑hello等加不加斜杠了
public class MyController {
@RequestMapping("/upload.do")
public String doFileUpload(MultipartFile img, HttpSession session) throws Exception {
String path = session.getServletContext().getRealPath("/images");
if (img.getSize() > 0) {
//获取到上传文件的原始名称
String fileName = img.getOriginalFilename();
if (fileName.endsWith("jpg") || fileName.endsWith("png")) {
File file = new File(path, fileName);
//上传
img.transferTo(file);
}
return "/success.jsp";
}
return "/error.jsp";
}
}
对于处理器的定义,需要注意以下几点:
A、处理器方法的形参
用于接收表单元素所提交参数的处理器方法的形参类型不是File,而是MultipartFile。
MultipartFile为一个接口,专门用于处理文件上传问题。该接口中具有很多有用的方法,例如获取参数名称getName(),本例指的是homework;获取文件的原始名称getOriginalFilename();获取文件大小getsize();判断文件是否为空isEmpty();文件上传transfefTo()等。
MultipartFile接口常用的实现类为CommonsMultipartResolver。而该实现类中具有设置上传文件大小、上传文件字符集等属性,可以通过为其注入值,来限定上传的文件。
B、未选择上传文件
若用户未选择上传的文件就直接提交了表单,此时处理器方法的MultipateFile 形参所接收到的实参值并非为nu,而是一个内容为empty的文件。所以,对于未选择上传文件的情况的处理,其判断条件为file.isEmpty(),而非file==null。
C、上传文件类型
SpringMVC的文件上传功能并未有直接的用于限定文件上传类型的方法或属性,需要对获取到的文件名后加以判断。此时使用String的endWith()方法较为简捷。
D、上传方法
对于上传单个文件,直接使用MultipartFile的transferTo()方法,就可以完成上传功能。
但是,需要注意的是,该方法要求服务端用于存放客户上传文件的目录必须存在,否则报错。
即其不会自己创建该目标目录。如本例中,必须手工创建 images目录。
(4)在SpringMVC中注册文件上传处理器
<!-- 注册组件扫描器 -->
<context:component-scan base-package="com.bjpowernode.handlers"/>
<!-- 注册上传处理器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8"/>
<property name="maxUploadSize" value="1048576"/>
</bean>
<!-- 注册mvc注解驱动 -->
<mvc:annotation-driven/>
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorView" value="/error.jsp"/>
</bean>
(5)设置文件超出大小的异常处理
当上传文件超出指定大小时,会抛出MaxUploadSizeExceededException异常。通过在SpringMVC配置文件中设置SimpleMappingExceptionResolver,可实现对该异常的处理。
A、注册异常处理器
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorView" value="/error.jsp"/>
</bean>
B、定义异常响应页面和成功页面
<html>
<head>
<title>welcome page</title>
</head>
<body>
error page
</body>
</html>
<html>
<head>
<title>welcome page</title>
</head>
<body>
上传成功
</body>
</html>
4.5.2上传多个文件
/50-fileupload-multi
(1)修改index页面
<body>
<form action="test/upload.do" method="POST"
enctype="multipart/form-data">
文件1:<input type="file" name="imgs"/><br>
文件2:<input type="file" name="imgs"/><br>
文件3:<input type="file" name="imgs"/><br>
<input type="submit" value="上传"/>
</form>
</body>
(2)修改处理器类
限制上传文件大小和类型
@org.springframework.stereotype.Controller //表示当前类是一个处理器
@RequestMapping("/test") //命名空间 会帮助你自动拼接,所以不用考虑hello等加不加斜杠了
public class MyController {
@RequestMapping("/upload.do")
public String doFileUpload(@RequestParam MultipartFile[] imgs, HttpSession session) throws Exception {
String path = session.getServletContext().getRealPath("/images");
for(MultipartFile img : imgs) {
if (img.getSize() > 0) {
//获取到上传文件的原始名称
String fileName = img.getOriginalFilename();
if (fileName.endsWith("jpg") || fileName.endsWith("png")) {
File file = new File(path, fileName);
//上传
img.transferTo(file);
}
}
}
return "/success.jsp";
}
}
4.6拦截器
SpringMVC中的Interceptor拦截器是非常重要和相当有用的,它的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理。其拦截的时间点在“处理器映射器根据用户提交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器,在处理器适配器执行处理器之前”。当然,在处理器映射器映射出所要执行的处理器类时,已经将拦截器与处理器组合为了一个处理器执行链,并返回给了中央调度器。
/51-interceptor
4.6.1一个拦截器的执行
(1)自定义拦截器
实现HandlerInterceptor接口
public class OneInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
System.out.println("执行OneInterceptor------preHandle()---------");
return false;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
System.out.println("执行OneInterceptor------postHandle()---------");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// TODO Auto-generated method stub
System.out.println("执行OneInterceptor------afterCompletion()---------");
}
}
自定义拦截器,需要实现Handlerlnterceptor接口。而该接口中含有三个方法:>preHandle(request,response,Object handler):该方法在处理器方法执行之前执行。其返回值为boolean,若为true,则紧接着会执行处理器方法,且会将afterCompletion()方法放入到一个专门的方法栈中等待执行。
postHandle(request,response,Object handler,modelAndView):该方法在处理器方法执行之后执行。处理器方法若最终未被执行,则该方法不会执行。
由于该方法是在处理器方法执行完后执行,且该方法参数中包含ModelAndView,所以该方法可以修改处理器方法的处理结果数据,且可以修改跳转方向。
afterCompletion(request,response,Object handler,Exception ex):当preHandle()方法返回true时,会将该方法放到专门的方法栈中,等到对请求进行响应的所有工作完成之后才执行该方法。即该方法是在中央调度器渲染(数据填充)了响应页面之后执行的,此时对ModelAndView再操作也对响应无济于事。
拦截器中方法与处理器方法的执行顺序如下图:
(2)注册拦截器
mvc:mapping/用于指定当前所注册的拦截器可以拦截的请求路径,而/**表示拦截所有请求。
<!-- 注册组件扫描器 -->
<context:component-scan base-package="com.bjpowernode.handlers"/>
<!-- 注册拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.bjpowernode.interceptors.OneInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
(3)修改index页面
<html>
<head>
<title>My JSP 'index.jsp' starting page</title>
</head>
<body>
This is my JSP page. <br>
</body>
</html>
(4)修改处理器
@org.springframework.stereotype.Controller
@RequestMapping("/test")
public class MyController {
@RequestMapping("/some.do")
public String doSome() {
System.out.println("执行处理器的doSome()方法");
return"/WEB-INF/jsp/welcome.jsp";
}
}
(5)修改welcome页面
<!DOCTYPE html>
<html>
<head>
<title>welcome page</title>
</head>
<body>
welcome page
</body>
</html>
(6)控制台输出结果
4.6.2多个拦截器的执行
/52-interceptor-2
(1)再定义一个拦截器
public class TwoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("执行TwoInterceptor------preHandle()---------");
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("执行TwoInterceptor-----postHandle()---------");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("执行TwoInterceptor------afterCompletion()---------");
}
}
(2)多个拦截器的注册与执行
<!-- 注册拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.bjpowernode.interceptors.OneInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.bjpowernode.interceptors.TwoInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
(3)控制台执行结果
两个都是true时:
执行OneInterceptor------preHandle()---------
执行TwoInterceptor------preHandle()---------
执行处理器的doSome()方法
执行TwoInterceptor-----postHandle()---------
执行OneInterceptor------postHandle()---------
执行TwoInterceptor------afterCompletion()---------
执行OneInterceptor------afterCompletion()---------
第一个是true,第二个是false时:
执行OneInterceptor------preHandle()---------
执行TwoInterceptor------preHandle()---------
执行OneInterceptor------afterCompletion()---------
第一个是false,第二个是true时:
执行OneInterceptor------preHandle()---------
两个都是false时:
执行OneInterceptor------preHandle()---------
从图中可以看出,只要有一个preHandle()方法返回false,则上部的执行链将被断开,其后续的处理器方法与postHandle()方法将无法执行。但,无论执行链执行情况怎样,只要方法栈中有方法,即执行链中只要有preHandle()方法返回true,就会执行方法栈中的afterCompletion()方法。最终都会给出响应。