第3章〓Spring MVC的组件开发本章目标
1. 了解Spring MVC框架中的拦截器,掌握拦截器的创建和配置方法。
2. 掌握文件的上传和下载的基本原理和使用方法。
3. 了解Spring MVC标签库,掌握几个常用标签的使用方法。
3.1拦截器
Spring MVC中的拦截器(interceptor)类似于Servlet中的过滤器(filter),主要用于拦截用户请求并作相应的处理。例如,通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。使用Spring MVC中的拦截器,须对拦截器类进行定义和配置。
3.1.1Spring MVC拦截器的设计
在开发拦截器之前,要先了解拦截器的设计结构。拦截器必须实现HandlerInterceptor接口。为增强功能,开发了多个拦截器。Spring MVC拦截器接口与类设计结构如图31所示。
图31Spring MVC拦截器接口与类设计结构从图31中可以看出,所有的类都直接或间接实现了HandlerInterceptor接口,HandlerIntercerptor的源代码如下。1. package org.springframework.web.servlet; 
2. public interface HandlerInterceptor {  
3. default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)  
4. throws Exception {
5. return true;  
6. } 
7. default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,  
8. @Nullable ModelAndView modelAndView) throws Exception {  
9. } 
10. default void afterCompletion(HttpServletRequest request, HttpServlet-
Response response, Object handler,  
11. @Nullable Exception ex) throws Exception {  
12. }
13. }Java Web框架开发技术(Spring+Spring MVC+MyBatis)第3章Spring MVC的组件开发HandlerInterceptor接口中的3个方法描述如下。
 preHandle()方法: 该方法在控制器方法被调用前执行,其返回值为一个boolean值,如果为true,表示继续向下执行;如果为false,会中断后面的所有操作。
 postHandle()方法: 该方法在控制器方法被调用后执行,且在解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步修改。
 afterCompletion()方法: 该方法在视图渲染结束之后执行,即该方法在整个请求完成后,无论是否产生异常都会执行。
拦截器的执行流程可用如图32所示的流程图表示。
图32拦截器的执行流程图
3.1.2单个拦截器的使用
要使用Spring MVC中的拦截器,需要对拦截器类进行定义和配置。通常,拦截器类可以通过两种方式定义。 
 通过实现HandlerInterceptor接口。
 继承HandlerInterceptor接口的实现类(如HandlerInterceptorAdapter)定义。
以继承HandlerInterceptorAdapter类为例,自定义拦截器,拦截对象是所有以.do结尾的请求资源,类如代码清单31所示。
代码清单31: /chap3/src/com/interceptor/UserInterceptor.java1. package com.interceptor;
2. import javax.servlet.http.HttpServletRequest;  
3. import javax.servlet.http.HttpServletResponse;
4. import org.springframework.lang.Nullable;  
5. import org.springframework.web.servlet.ModelAndView;  
6. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 
7. public class UserInterceptor extends HandlerInterceptorAdapter {  
8. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)  
9. throws Exception {  
10. System.out.println("preHandle of UserInterceptor ");
11. return true;  
12. }  
13. public  void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,  
14. @Nullable ModelAndView modelAndView) throws Exception {  
15. System.out.println("postHandle of UserInterceptor ");  
16. }  
17. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,  
18. @Nullable Exception ex) throws Exception {  
19. System.out.println("afterCompletion of UserInterceptor ");  
20. }  
21. } 在这个自定义的拦截器中,每个方法中只是在控制台上输出一个字符串,表示当前方法被调用。
下面是控制器的一个处理请求的方法,如代码清单32所示。
代码清单32: com/controller/HelloController.java1.  @Controller  
2. public class HelloController {
3. @RequestMapping("/hello")  
4. public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {  
5. //创建ModelAndView对象  6. System.out.println("第一个Hello World程序");  
7. ModelAndView mv=new ModelAndView();  
8. //将信息保存在ModelAndView对象,这个对象中的值可传递到JSP页面中  
9. mv.addObject("msg","第一个Hello World程序");  
10. //设置要转发的页面  
11. mv.setViewName("helloWorld");  
12. return mv;  
13. }
14. }在这个请求处理方法中,在控制台上打印了一个字符串,表示当前方法被调用,然后转发到helloWorld.jsp页面。
最后,在Spring MVC的配置文件中添加拦截器的配置信息,如代码清单33所示。
代码清单33: /chap3/WebContent/WEBINF/springmvcconfig.xml1.  <?xml version="1.0" encoding="UTF-8"?>  
2. <beans xmlns="http://www.springframework.org/schema/beans"  
3.  xmlns:mvc="http://www.springframework.org/schema/mvc"  
4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
5. xmlns:p="http://www.springframework.org/schema/p"  
6. xmlns:context="http://www.springframework.org/schema/context"  
7. xsi:schemaLocation="  
8. http://www.springframework.org/schema/beans  
9. https://www.springframework.org/schema/beans/spring-beans.xsd  
10. http://www.springframework.org/schema/context  
11. https://www.springframework.org/schema/context/spring-context.xsd 
12. http://www.springframework.org/schema/mvc  
13. https://www.springframework.org/schema/mvc/spring-mvc.xsd">  
14. <!-- 指定需要扫描的包 -->  
15. <context:component-scan base-package="com.controller" />  
16. <!-- 定义视图解析器 -->  
17. <bean id="viewResolver" class=  
18. "org.springframework.web.servlet.view.InternalResourceViewResolver">  
19.  <!-- 设置前缀 -->  
20.  <property name="prefix" value="/WEB-INF/jsp/" />  
21.  <!-- 设置后缀 -->  
22.  <property name="suffix" value=".jsp" />  
23. </bean>  
24. <mvc:interceptors>  
25. <mvc:interceptor>  26. <mvc:mapping path="/.do"/>  
27. <bean class="com.interceptor.UserInterceptor"/>  
28. </mvc:interceptor>  
29. </mvc:interceptors>  
30. </beans>第3、12、13行黑体字部分是定义mvc命名空间和所需的schema文件位置信息。第24行的 <mvc:interceptors>是一个容器标签,其子标签是<mvc:interceptor>,用这个标签可定义多个拦截器。
在<mvc:interceptor>标签下有两个子标签,第26行的<mvc:mapping path="/.do">,表示拦截后缀为.do的所有请求uri。第27行定义实现这个拦截器的类。
首先,发布此服务,然后在浏览器中访问此服务,其运行结果如图33所示。
图33单个拦截器运行结果
从运行结果看,拦截器中的3个方法调用次序与前面分析的结果一致。
3.1.3多个拦截器的使用
当一个项目比较大,业务逻辑复杂,需求也在不断变化的时候,可以添加多个拦截器满足实际工程的需求,此时多个拦截器中的方法执行次序也发生了变化。下面通过有多个拦截器存在时的示例说明拦截器中的方法与处理器的方法之间的先后执行次序。第一个拦截器的定义如代码清单34所示。
代码清单34: /chap3/src/com/interceptor/Interceptor1.java1.  public class Interceptor1 extends HandlerInterceptorAdapter {  
2. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)  
3. throws Exception {  
4. System.out.println("preHandle of UserInterceptor1 ");
5. return true;
6. }  
7. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,  
8. @Nullable ModelAndView modelAndView) throws Exception {  
9. System.out.println("postHandle of UserInterceptor1 ");  
10. }  11. public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler,  
12. @Nullable Exception ex) throws Exception {  
13. System.out.println("afterCompletion of UserInterceptor1 ");  
14. }  
15. }第二个拦截器的定义如代码清单35所示。
代码清单35: /chap3/src/com/interceptor/Interceptor2.java1. public class Interceptor2 extends HandlerInterceptorAdapter {  
2. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)  
3. throws Exception {  
4. System.out.println("preHandle of UserInterceptor2 ");
5. return true;  
6. }  
7. public  void postHandle(HttpServletRequest request, HttpServletResponse
response, Object handler,  
8. @Nullable ModelAndView modelAndView) throws Exception {  
9. System.out.println("postHandle of UserInterceptor2 ");  
10. }  
11. public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler,  
12. @Nullable Exception ex) throws Exception {  
13. System.out.println("afterCompletion of UserInterceptor2 ");  
14. }  
15. }在Spring MVC配置文件中添加拦截器配置,如代码清单36所示。
代码清单36: /chap3/WebContent/WEBINF/springmvcconfig.xml1.  <mvc:interceptors>   
2. <mvc:interceptor>  
3. <mvc:mapping path="/.do"/>  
4. <bean class="com.interceptor.UserInterceptor1"/>  
5. </mvc:interceptor>
6. <mvc:interceptor>  
7. <mvc:mapping path="/.do"/>  
8. <bean class="com.interceptor.UserInterceptor2"/>  
9. </mvc:interceptor>
10. </mvc:interceptors>控制器处理请求的方法还是用代码清单32所示代码,启动服务后,在浏览器中访问处理请求的映射地址,运行结果如图34所示。
图34多个拦截器运行结果
从运行结果分析,在执行请求方法handleRequest()之前,依次执行Interceptor1和Interceptor2中的prdHandle()方法,在执行handleRequest()之后,反序执行拦截器中的另外两个方法。多拦截器各方法之间执行次序如图35所示。
图35多拦截器各方法之间执行次序
3.1.4拦截器应用——用户权限验证
拦截器的作用是在请求处理方法执行前,执行拦截器中的prehandle()方法,这样可以预先处理一些事务,如判断一个用户是否已经登录一个网站,如果已经登录,可直接进入网站;如果没有登录,则转发到登录页面。下面是一个通过拦截器完成用户权限验证的实例。
例31当用户访问系统所有资源前进行权限的验证,如果是已登录的用户,可直接进入相应的请求处理方法;如果是未登录的用户,则重定向到登录页面。
(1) 编写控制器类,如代码清单37所示。
代码清单37: /chap3/src/com/controller/UserController1.java1. package com.controller;
2. //处理用户请求的控制器  
3. @Controller  
4. //响应所有userInterceptor路径下的处理请求  5. @RequestMapping(value="/userInterceptor")
6. public class UserController1 {  
7. @GetMapping(value="/login")//响应login.do请求  
8. public String login() {  
9. return "/login";//转发到login.jsp页面  
10. }  
11. //这是处理用户登录的方法,响应userInterceptor/login.do的处理请求 
12. @PostMapping(value="/login") 
13. public String login(User user,Model model,HttpSession session ) {  
14. String msg;//登录是否成功信息
15. if(user.getUsername()!=null&&user.getUsername().equals("admin")  
16. &&user.getPassword()!=null&&user.getPassword().equals(
17. "123456"))  
18. {  
19. msg="登录成功";   
20. model.addAttribute("user",user);//保存数据到Model 对象中  
21. session.setAttribute("user",user);  //保存数据到session对象中
22. return "main";//转发到main.jsp  
23. }  
24. else  
25. msg="登录失败";  
26. model.addAttribute("msg",msg);  //保存数据到Model 对象中  
27. return "login";//转发到login.jsp  
28. }  
29. @RequestMapping(value="/main")//响应main.do请求  
30. public String main() {
31. return "main";//转发到main.jsp页面  
32. }  
33. @GetMapping(value="/logout")//响应logout.do请求  
34. public String logout(HttpSession session) {  
35. session.invalidate();//当前的session失效  
36. //重定向到Login.do请求  
37. return "redirect:/userInterceptor/login.do";
38. }  
39. }(2) 编写拦截器类,决定哪些请求是允许的,哪些请求是非法的并加以拒绝,如代码清单38所示。
代码清单38: /chap3/src/com/interceptor/LoginInterceptor.java1. public class LoginInterceptor extends HandlerInterceptorAdapter {  
2. //进入Handler()方法之前执行  
3. //可用于身份认证、身份授权。如果认证没有通过,表示用户没有登录,需要此方法拦
//截不再往下执行,否则就放行  4. @Override  
5. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)  
6. throws Exception {  
7. //获取请求的url  
8. String url= request.getRequestURI();  
9. //判断url是否公开地址(实际使用时将公开地址配置到配置文件中)  
10. //这里假设公开地址是登录提交的地址  
11. if (url.indexOf("login.do") > 0) {  
12. //如果进行登录提交,放行  
13. return true;  
14. }  
15. //判断session  
16. HttpSession session= request.getSession();  
17. //从session中取出用户身份信息  
18. User user= (User) session.getAttribute("user");  
19. if (user != null) {  //如果用户存在,表示已经登录,放行
20. return true;  
21. }  
22. //执行到这里表示用户身份需要验证,跳转到登录页面  
23. request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);  
24. return false;  
25. }   
26. }(3) 在springmvcconfig.xml中配置自定义的拦截器,如代码清单39所示。
代码清单39: /chap3/WebContent/WEBINF/springmvcconfig.xml1. <mvc:interceptors>
2. <mvc:interceptor>
3. <!-只拦截/userInterceptor路径下的请求-->
4. <mvc:mapping path="/userInterceptor/.do"/>
5. <!-自定义的拦截器类-->
6. <bean class="com.interceptor.LoginInterceptor"/>
7. </mvc:interceptor>
8. </mvc:interceptors>配置文件中其他省略的代码与代码清单33相同。
(4) 视图页面,main.jsp作为主页,login.jsp是登录页面,在没有登录前,只能显示login.jsp页面,其他页面是不允许访问的,只有登录后,才能访问其他页面,如main.jsp。
main.jsp页面如代码清单310所示。
代码清单310: /chap3/WebContent/WEBINF/jsp/main.jsp1.  <body>   
2. 当前用户: ${user.username}  
3. <a href="${pageContext.request.contextPath}/userInterceptor/logout.do">退出</a>  
4.   
5. </body>login.jsp页面如代码清单311所示。
代码清单311: /chap3/WebContent/WEBINF/jsp/login.jsp1.  <form action="${pageContext.request.contextPath}/userInterceptor/
login.do" method="post" onsubmit="return check()">  
2.   <br /><br />  
3. 账 号: <input id="usercode" type="text" name="username" />  
4. <br /><br />  
5. 密 码: <input id="password" type="password" name="password" />  
6. <br /><br />
7. <center><input type="submit" value="登录" /></center>  
8. </form>代码清单311中只给出了form部分的代码,其他代码与前面的相同。
(5) 发布服务,启动Tomcat,首先访问main.do,出现如图36所示的页面。
图36login.jsp页面
由于用户没有登录,此时访问main.do时被拦截器拦截,跳转到login.jsp页面,此时输入正确的用户名和密码,单击“登录”按钮后进入main.jsp页面,如图37所示。
图37main.jsp页面
登录成功后,可以随时进入主页面main.jsp,但当单击“退出”链接后,当前的session失效,将不能再直接访问main.jsp,而是跳转到index.jsp页面。
3.2文件的上传与下载
在实际工程中,文件的上传与下载是必备的功能之一,Spring MVC对此给出了很好的解决方案,可以方便地实现这个功能。
3.2.1文件的上传
Spring MVC为上传文件提供了良好的支持。首先,Spring MVC的文件上传是通过MultipartResolver(Multipart处理器)处理的,对于MultipartResolver来说,它只是一个接口,有两个实现类。
 CommonsMultipartResolver类,依赖于Apache下的jakarta Common FileUpload项目解析Multipart请求,它可以在Spring的各个版本中使用,只是它依赖于第三方JAR包才能实现。
 StandardServletMultipartResolver类,是Spring 3.1版本后的产物,它依赖于Servlet 3.0或者更高版本的实现,但不依赖于第三方JAR包。
两个实现类与MultipartResolver接口的关系如图38所示。
图38两个实现类与MultipartResolver接口的关系
下面以第二种方案实现为例,讲解Spring MVC如何实现文件的上传功能。
要实现文件上传,需要前端与后端服务器配合才能实现。首先,在前端页面上要有一个上传文件的表单,表单需要满足下面3个条件。
 form表单的method属性设置为post。
 form表单的enctype属性设置为multipart/formdata。
 设置<input>标签的 type="file"。
示例代码如下: 1.  <form action="upload.do"   enctype="multipart/form-data"  
2. method="post" >  
3. <input  type="file" name="file" /> 
4. <input  type="submit" value="上传文件" multiple="multiple"/> 上述代码中,上传文件除了满足3个必备条件外,还增加了multiple属性,该属性是HTML 5.0中的新特性,如果使用了该属性,同时选中多个文件进行上传,即可实现多文件上传。
当客户端form表单的enctype属性为multipart/formdata时,浏览器会采用二进制流的形式处理表单的数据,服务器端就会对文件上传的请求进行解析处理。Spring MVC为文件上传提供了直接的支持,这是通过MultipartResolver接口实现的。
MultipartResolver接口有两个实现类,前面已经介绍过,这里用Spring MVC自己提供的StandardServletMultipartResolver类完成文件的上传。由于这个类是基于Servlet的,所以要在Web.xml中进行配置,示例代码如下。1.  <multipart-config>  
2.<!--临时文件的目录-->  
3.<location>e:/mvc/upload</location>  
4.<!-- 上传文件最大2MB -->  
5.<max-file-size>2097152</max-file-size>  
6.<!-- 上传文件整个请求不超过4MB -->  
7.<max-request-size>4194304</max-request-size>  
8. </multipart-config>上面的代码中,<multipartconfig>作为DispatcherServlet的子标签,在此是设置上传文件的相关属性,如默认目录,上传文件大小限制等。接下来还要在Spring MVC配置文件中进行配置,如下面的代码所示。1.  <!--配置文件上传解析器 -->  
2. <bean id="multipartResolver"  
3. class="org.springframework.web.multipart.support.StandardServletMult ipartResolver">因为MultipartResolver接口的实现类StandardServletMultipartResolver,内部是引用MultipartResolver字符串获取该实现对象并完成文件解析的,所以在配置这个Bean的实例时,必须指定Bean的id为multipartResolver。
当完成页面表单和文件上传解析器的配置后,需要在Controller中编写文件上传的方法实现文件上传的功能。在Spring MVC,实现文件上传功能其实很简单,其代码如下。1. //处理上传文件请求的方法及方法的形参 
2. @RequestMapping(value = "/upload")  
3. public  ModelAndView upload(
4. @RequestParam("username") String username,  
5. HttpServletRequest request,  
6. MultipartFile file)该方法是用来处理上传文件请求的,其中的file是一个MultipartFile引用变量,用来接收前端页面传递过来的文件,并将文件封装在这个对象中。MultipartFile接口中提供了一些获取文件信息的方法,见表31。表31MultipartFile接口中提供的获取文件信息的方法方法说明Byte[] getBytes()以字节数组的形式返回文件的内容String getContentType()返回文件的内容InputStream getInputStream()读取文件内容,返回一个InputStream流String getName()获取多部件form表单的参数名称String getOriginalFilename()获取上传文件的原始文件名Boolean isEmpty()判断上传文件的内容是否为空Void transferTo(File file)保存上传文件到目标目录中例32文件上传示例,要求在一个JSP页面单击“浏览”按钮,打开一个文件管理窗口,选中某一个或多个文件后,可以上传到服务器指定的目录下。
具体实现步骤如下。
(1) 创建Web工程,添加Spring MVC 的JAR包,修改web.xml配置文件,添加SpringMVC必需的listener和servlet。
(2) 创建上传文件页面fileUpload.jsp,如代码清单312所示。
代码清单312: /chap3/WebContent/WEBINF/jsp/fileUpload.jsp1. <form action="${pageContext.request.contextPath}/file/upload.do"   
2. enctype="multipart/form-data"  
3. method="post" onsubmit="return check()">  
4.<br /><br />  
5. 上传人: <input id="username" type="text" name="username" />  
6.<br /><br />  
7.请选择文件: <input id="filename" type="file" name="file" />  
8.<br /><br />
9.<center><input type="submit" value="上传" /></center>  
10. </form>代码第1行中的action请求的地址是file/upload.do,对应的是FileUploadController控制器类中的upload()方法,在此处理上传文件的请求。
(3) 创建控制器类FileUploadController,如代码清单313所示。
代码清单313: /chap3/src/com/controller/FileUploadController.java1. @Controller  
2. @RequestMapping("/file")  
3. public class FileUploadController {
4. @RequestMapping(value = "/toUpload")  
5. public String toUpload() {
6. return "fileUpload";  7. }  
8. //处理上传文件请求的方法  
9. @RequestMapping(value= "/upload")  
10. public  ModelAndView upload(@RequestParam("username") String username,
HttpServletRequest request,MultipartFile file) {  
11. ModelAndView mv=new ModelAndView();  
12. //指定要上传的文件所在路径  
13. String path = request.getServletContext().getRealPath("/upload/");  
14. //获取原始文件名  
15. String fileName=file.getOriginalFilename();  
16. fileName=path+File.separator+fileName;  
17. file.getContentType();  
18. //创建目标文件  
19. File dest=new File(fileName);  
20. String msg=null;  
21. try {  
22. //保存文件  
23. file.transferTo(dest);  
24. msg="上传文件成功";
25. }catch(IllegalStateException|IOException e) {  
26. msg="上传文件失败";
27. e.printStackTrace();  
28. }  
29. mv.addObject("msg", msg);//提示信息保存在ModelAndView对象中
30. //用户信息保存在ModelAndView对象中
31. mv.addObject("username",username);  
32. mv.setViewName("result");  
33. return mv;  
34. }
35. }第10行的MultipartFile file是MultipartFile接口的封装对象,其实现类是在Spring MVC中配置的StandardServletMultipartResolver类,此处的参数对应表单中的上传文件控件提交的文件信息。
第13行指定上传文件保存在服务中的路径,如果不指定,将保存在web.xml中指定的默认路径。
第15行得到上传文件原来的名字,在此也可以根据需要重新对文件进行命名。
第23行将上传文件保存到指定的服务器路径下。
(4) 创建springmvcconfig.xml配置文件,如代码清单314所示。
代码清单314: /chap3/WebContent/WEBINF/springmvcconfig.xml1.  <beans>  <!-schema配置信息省略 --> 
2. <!-- 指定需要扫描的包 -->  
3. <context:component-scan base-package="com.controller" />  
4. <!-- 定义视图解析器 -->  
5. <bean id="viewResolver" class=  
6. "org.springframework.web.servlet.view.InternalResourceViewResolver">
7.  <!-- 设置前缀 -->  
8.  <property name="prefix" value="/WEB-INF/jsp/" />  
9.  <!-- 设置后缀 -->  
10.  <property name="suffix" value=".jsp" />  
11. </bean>
12. <!--配置文件上传解析器 -->  
13. <bean id="multipartResolver"  
14. class="org.springframework.web.multipart.support.Standard-ServletMultipartResolver">  
15. </bean>  
16. </beans>第14行配置的bean是MultipartFile接口的实现类,此处通过Spring IoC创建类的实例。
(5) 修改web.xml配置文件,修改后的代码如代码清单315所示。
代码清单315: /chap3/WebContent/WEBINF/web.xml1. <servlet-name>app</servlet-name>  
2. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
3. <init-param>  
4. <param-name>contextConfigLocation</param-name>  
5. <param-value>/WEB-INF/springmvc-config.xml</param-value>  
6. </init-param>  
7. <load-on-startup>1</load-on-startup>  
8. <multipart-config>  
9. <!--临时文件的目录-->  
10. <location>e:/mvc/upload</location>  
11. <!-- 上传文件最大2MB -->  
12. <max-file-size>2097152</max-file-size>  
13. <!-- 上传文件整个请求不超过4MB -->  
14. <max-request-size>4194304</max-request-size>  
15.</multipart-config>  
16.   
17. </servlet>第8~15行配置了MultipartFile接口实现类StandardServletMultipartResolver的几个属性,这几个属性是上传文件必需的。
(6) 发布服务,启动Tomcat,在浏览器中输入地址
http://localhost/chap3/file/toUpload.do,浏览器中出现如图39所示的页面。
图39上传文件页面
单击“选择文件”按钮,打开目录管理窗口,选择要上传的文件,并输入上传人的姓名,单击“上传”按钮后,出现上传文件成功的页面,此时文件被上传到项目工程所在目录的upload目录中。
如果工程的名字为chap3,发布到Eclipse的Tomcat中,则发布后工程的实际目录为F:\\workspace\\.metadata\\.plugins\\org.eclipse.wst.server.core\\tmp0\\wtpwebapps\\chap3\\其中F:\\workspace是Eclipse的工作空间所在目录。
3.2.2文件的下载
文件下载看似简单,其实还是要分几种情况分别进行处理。最简单的情况是在页面给出一个超链接,该链接href的属性等于要下载文件的文件名,就可以实现文件下载了。这种情况只适用于非中文文件件名,而且文件不是很大。如果该文件的文件名为中文文件名,在某些早先的浏览器上就会导致下载失败;如果使用最新的Firefox、Chrome、Opera、Safari,则都可以正常下载文件名为中文的文件。为了保证下载成功,此时要对文件名进行转码处理,避免出现中文乱码。如果要下载的文件很大,而且要随时掌握下载的状态,此时可通过服务器读取本地文件流,然后将文件流输出到客户端,在这个过程中可以轻易获取文件传输过程中的各个参数。下面给出的是利用ResponseEntity类型实现的一种文件下载方式。
例33创建一个下载文件的页面,单击下载链接可下载服务器端指定的文件,要求对汉字文件名也可以正常下载。
(1) 创建一个下载页面,如代码清单316所示。
代码清单316: /chap3/WebContent/WEBINF/jsp/download.jsp1. <%@ page language="java" contentType="text/html; charset=UTF-8"  
2.  pageEncoding="UTF-8"%>  
3. <%@page import="java.net.URLEncoder"%>  4. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"   
5.  "http://www.w3.org/TR/html4/loose.dtd">  
6. <html>  
7. <head>  
8. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">  
9. <title>下载页面</title>  
10. </head>  
11. <body>  
12. <a href="${pageContext.request.contextPath}/file/download.do?
filename=第1章spring的基础知识.pptx"> 文件下载</a>  
13. </body>  
14. </html>  
15. </html>此处的下载文件名为“第1章spring的基础知识.pptx”,是存放在服务器端指定下载目录中的文件,此处是发布工程目录下的upload目录。
(2) 在控制器类FileUploadController中创建处理下载文件请求的方法fileDownload(),如代码清单317所示。
代码清单317: /chap3/src/com/controller/FileUploadController.java1.  //处理文件下载请求的方法  
2. @RequestMapping("/download")  
3. public ResponseEntity<byte[]> fileDownload(HttpServletRequest request,  
4. @RequestParam("filename") String filename,  
5. @RequestHeader("User-Agent") String userAgent) throws Exception{  
6. //指定要下载的文件所在路径  
7. String path= request.getServletContext().getRealPath("/upload/");  
8. System.out.println(path);  
9. //创建该文件对象  
10. File file= new File(path+File.separator+filename);  
11. //该方法返回一个static ResponseEntity.BodyBuilder 对象
12. BodyBuilder builder=ResponseEntity.ok();  
13.
14. builder.contentType(MediaType.APPLICATION_OCTET_STREAM);  
15. //对文件名编码,防止中文文件乱码  
16. filename= URLEncoder.encode(filename,"UTF-8");  
17. //不同的浏览器,处理方式不同,要根据浏览器版本区别对待
18. //如果是IE,只用UTF-8字符集进行URL编码即可
19. if(userAgent.indexOf("MSIE")>0) {  
20. builder.header("Content-Disposition","attachment;filename="+filename);}  21.   //而FireFox、Chrome等浏览器,则需要说明编码的字符集
22. else  
23. {builder.header("Content-Disposition","attachment;filename=UTF-8''"+filename);  
24. }  
25.   
26.return builder.body(FileUtils.readFileToByteArray(file));  
27. }ResponseEntity继承了HttpEntity,是HttpEntity的子类,第12行的ResponseEntity.ok()方法返回一个BodyBuilde,可用于RestTemplate和Controller层方法。第26行的builder.body()方法返回一个ResponseEntity对象。
(3) 发布服务,并启动Tomcat,分别在不同的浏览器中进行测试。下面是在Chrome浏览器中测试结果。在地址栏中输入网址http://localhost/chap3/file/toDownload.do,在页面上单击“文件下载”超链接,出现如图310所示的页面。
图310下载文件页面
从运行结果看,中文文件名下载是正常的,对不同的浏览器进行文件下载测试,程序都能正常运行。
3.3Spring的表单标签库
从Spring 2.0版开始,Spring提供了一组全面的可绑定数据的标签库,用于在使用JSP和Spring Web MVC时处理表单元素,每个标签都支持其相应HTML标签对应的属性集。与其他表单/输入标签库不同,Spring的表单标签库与Spring Web MVC集成,使标签可以访问控制器处理的命令对象和引用数据。正如我们在以下示例中所示,表单标记使JSP更易于开发、读取和维护。
表单标签库实现类在springwebmvc.jar,标签库描述符文件是springform.tld。要在页面上使用此标签库中的标签,需要在JSP页面的头部添加如下的JSP指令: <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>3.3.1form标签
使用Spring的form标签的作用与HTML中的form功能类似,只是属性有所不同,其中一个重要的属性modelAttribute是Spring所特有的,它可以绑定模型属性名称,默认值是command,可自定义修改。示例代码如下: <form:form modelAttribute="user" method="post" action="">
...
</form:form>包含这段代码的JSP页面要保证在Model中存在user这个属性变量名称。
3.3.2input标签
Spring MVC中的input标签会被渲染成一个类型为HTML input的标签,使用Spring标签的好处是可以绑定表单数据,一般是通过input标签的path属性绑定Model中的值。示例代码如下: 1.  <form:form  method="post" action="addProduct.do" >
2. <table>  
3. <tr>  
4. <td>产品名称:</td>  
5. <td><form:input path="name"/></td>
6. </tr>  
7. <tr>  
8. <td>生产日期:</td>  
9. <td><form:input path="pd"/></td>  
10. </tr>  
11. <tr>  
12. <td>价格:</td>  
13. <td><form:input path="price"/></td>  
14. </tr>   
15. <tr>  
16. <td><input type="submit" value="提交"/></td>  
17. </tr>  
18. </table>  
19. </form:form>这里的path属性相当于HTML中input标签的name属性,只是这里的path属性可以与Model中的值进行绑定。除此标签外,如password、hidden、textarea等标签都最终渲染为HTML对应的标签,主要区别是有一个path属性绑定与Controller中请求处理方法的Model属性对应的属性值。3.3.3checkboxes标签
Spring MVC的checkboxes标签会渲染多个类型为checkbox的普通HTML 标签。checkboxes常用的属性主要有两个: path和items,具体含义如下。
 path属性: 要绑定属性路径,与Controller请求方法的参数对应。
 items属性: 用于生成checkbox元素的对象的Collection、Map或者Array。
使用checkboxes标签时,以上两个属性是必须指定的,items表示当前要用来显示的项的集合数据,而path绑定的表单对象的属性表示当前表单对象拥有的项,即在items显示的所有项中,表单对象拥有的项会被设定为选中状态。
例34以用户的爱好为例演示checkboxes标签的使用方法。
(1) 创建TagController控制器,生成填充复选框选项的值,如代码清单318所示。
代码清单318: chap3/src/com/controller/TagController.java1.  package com.controller;  
2. import java.util.ArrayList;  
3. import java.util.List;  
4. import org.springframework.stereotype.Controller;  
5. import org.springframework.ui.Model;  
6. import org.springframework.web.bind.annotation.RequestMapping;  
7. import com.po.User;  
8. @Controller  
9. public class TagController {  
10. //处理选择爱好的请求方法  
11. @RequestMapping("/hobby")  
12. public String hobby(Model model) {  
13. //创建一个user对象  
14. User user=new User();  
15. //hobbys用于存放当前用户的所有爱好  
16. List<String> hobbys=new ArrayList<String>();  
17. hobbys.add("篮球");  
18. hobbys.add("游泳");  
19. //将hobbys赋值给user对象  
20. user.setHobbys(hobbys);  
21. //用于在页面上显示所有爱好的选项  
22. List<String> hobbyList=new ArrayList<String>();  
23. hobbyList.add("篮球");  
24. hobbyList.add("围棋");  
25. hobbyList.add("游泳");  
26. hobbyList.add("画画");  
27. //将user保存在Model中  
28. model.addAttribute("user",user);