三大框架之SpringMVC4框架 第四章

第四章 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()方法。最终都会给出响应。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值