Spring MVC (三) —— 文件上传 + 异常处理 + 拦截器

一、回顾

在上一篇 Spring MVC (二) —— 请求参数绑定 中,我了解了

【1】SpringMVC 的参数绑定机制、使用要求

  • POJO 类中包含集合类型参数
  • POJO 类型作为参数示例

【2】请求参数乱码问题的解决!

  • ① POST 请求方式乱码
  • ② 静态资源可以不过滤
  • ③ GET 请求方式乱码

【3】自定义类型转换器 Converter 的配置步骤

接下来,我继续学习 SpringMVC 文件上传、SpringMVC 中的拦截器


二、响应数据和结果视图

【1】返回 void

之前了解到:用 @RequestMapping 注解的方法的返回值是一个 String,表示指定逻辑视图名,通过视图解析器解析为物理视图地址(xxx/xxx–>路径)。 其实返回值也可以为 void

@RequestMapping("/testReturnVoid") 
public void testReturnVoid1(HttpServletRequest request,
						   HttpServletResponse response) throws Exception {  }  

controller 方法形参上可以定义 request 和 response,使用 request 或 response 指定响应结果:

一、使用 request 转发页面:

request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request, response);

二、通过 response 页面重定向:

response.sendRedirect("testRetrunVoid2") 

三、也可通过 response 指定响应结果,例如响应 json 数据

response.setCharacterEncoding("utf-8"); 
response.setContentType("application/json;charset=utf-8");//设置浏览器打开页面的编码
response.getWriter().write("json 串");

【2】ModelAndView

ModelAndView 是 SpringMVC 提供的一个用作控制器方法的返回值对象。

方法一:

public ModelAndView addObject(String attributeName, Object attrValue){
	this.getModelView.addAttribute(attributeName, attrValue);
	return this;
}

可以在页面中,通过 ${requestScope.attributeName} 方式获取到值。


方法二:

用于设置逻辑视图名,视图解析器会根据 viewName 再结合配置文件找指定视图,上面的例子,会找到 success 页面

public void setViewName(@Nullble String viewName){
	this.view = viewName;
}
@RequestMapping("/testReturnModelAndView") 
public ModelAndView testReturnModelAndView() {
    ModelAndView mv = new ModelAndView();  
    mv.addObject("username", "张三");  
    mv.setViewName("success"); 
}
<body> ${requestScope.username} </body> 

【3】转发和重定向的不同写法

① Forward

@RequestMapping("/testForward")  
public String testForward() {   
	System.out.println("AccountController 的 testForward 方法执行了。。。。"); 
	return "forward:/WEB-INF/pages/success.jsp";  
} 

注: 如果用了 formward:则路径必须写成实际视图 url,不能写逻辑视图 (就是不能写成只有页面名称)。 相当于

“request.getRequestDispatcher("url").forward(request,response)”。

使用请求转发,既可以转发到 jsp,也可以转发到其他的控制器方法。


① Redirect

@RequestMapping("/testRedirect") 
public String testRedirect() {  
	System.out.println("AccountController 的 testRedirect 方法执行了。。。。");  
	return "redirect:testReturnModelAndView"; 
}

注: 相当于 response.sendRedirect(url)。需要注意的是,重定向是两次请求,如果是重定向到 Jsp 页面,则 Jsp 页面不能写在 WEB-INF 目录中,否则无法找到。


三、@ResponseBody 响应 JSON 数据

作用: 该注解用于将 Controller 的方法返回的对象,通过 HttpMessageConverter 接口转换为指定格式的数据如:json,xml 等,通过 Response 响应给客户端

需求: 使用 @ResponseBody 注解实现将 controller 方法返回对象转换为 json 响应给客户端。

前置知识点: SpringMVC 默认用 MappingJacksonHttpMessageConverter 对 json 数据进行转换,需要加入 jackson 的 jar 包。

jackson-annotation-2.9.0.jar
		databind-2.9.0.jar
		core--2.9.0.jar

原始 ajax 请求代码

<script> 
	type="text/javascript" src="${pageContext.request.contextPath}/js/jquery.min.js">
</script> 

<script>
 $(function(){	 // 页面加载,绑定单击事件
     $("#btn").click(function(){
         // alert("hello btn");
         // 发送ajax请求
         $.ajax({
             // 编写json格式,设置属性和值
             url:"user/testAjax",
             contentType:"application/json;charset=UTF-8",
             data:'{"username":"hehe","age":30}',
             dataType:"json",
             type:"post",
             success:function(data){
                 // data服务器端响应的json的数据,进行解析
                 alert(data);
                 alert(data.username);
                 alert(data.age);
             }
         });
     });
 });
</script>

<input type="button" value=" 测试ajax请求json和响应json" id="testJson"/> 
@RequestMapping("/testAjax")//注:此处应与 url:"user/testAjax" 相同
public @ResponseBody User testAjax(@RequestBody User user){
    // 客户端发送ajax的请求,传的是json字符串,后端把json字符串封装到user对象中
    System.out.println(user);
    // 做响应,模拟查询数据库
    user.setUsername("李四");
    user.setAge(40);
    // 做响应
    return user;
}

四、SpringMVC 实现文件上传

【1】单服务器实现

必要前提

A form 表单的 enctype 取值必须是:multipart/form-data      
	(默认值是:application/x-www-form-urlencoded)     
	enctype:是表单请求正文的类型 
	
B method 属性取值必须是 Post 

C 提供一个文件选择域<input type=”file” /> 

② 借助第三方组件实现文件上传

Commons-fileupload 组件(jar)从 1.1 版本开始,工作时需 commons-io 包的支持。 导入jar


③ 编写 JSP

<form action="/fileUpload" method="post" enctype="multipart/form-data"> 
 	名称:<input type="text" name="picname"/><br/>  
 	图片:<input type="file" name="uploadFile"/><br/>  
 	<input type="submit" value=" 上传 "/> 
</form>

注: 图片标签的 name 属性 (uploadFile),必须和 请求参数绑定的 MultipartFile 对象的名字一样


④ 编写控制器

@RequestMapping("/fileupload3")
public String testResponseJson (String imgName, MultipartFile uploadFile, HttpServletRequest request) throws Exception{ 
    //定义文件名   
    String fileName = ""; 
  	//1. 获取原始文件名 ---- 关键
  	String uploadFileName =  uploadFile.getOriginalFilename(); 
  	
  	//2. 截取文件扩展名   
 	String extendName = 
	uploadFileName.substring(uploadFileName.lastIndexOf(".")+1, uploadFileName.length()); 
  	
  	//3. 把文件加上随机数,防止文件重复   
  	String uuid = UUID.randomUUID().toString().replace("-", "").toUpperCase(); 
  	
  	//4. 判断是否输入了文件名   
  	if(!StringUtils.isEmpty(picname)) {
  	    fileName = uuid+"_" + picname + "." + extendName;   
  	}else {  fileName = uuid+"_" + uploadFileName;  }   
  	System.out.println(fileName); 
  	
    ServletContext context = request.getServletContext(); 
  	String basePath = context.getRealPath("/uploads"); 
  	
  	//5. 解决同一文件夹中文件过多问题   
  	String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date());   
  	
  	//6 .判断路径是否存在   
  	File file = new File(basePath+"/"+datePath);   		
  	if (!file.exists()) { file.mkdirs(); } 
  	
  	//7 .使用 MulitpartFile 接口中方法,把上传的文件写到指定位置  ---- 关键
  	uploadFile.transferTo(new File(file, fileName));   
  	return "success";  
}

⑥ 配置文件解析器

<!-- 配置文件上传解析器 --> <!-- id 的值是固定的-->  
<bean id="multipartResolver"  
   	class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 设置上传文件的最大尺寸为 5MB -->  
    <property name="maxUploadSize">   
    	<value>5242880</value>  
    </property> 
</bean> 

注: 文件上传的解析器 id 是固定的,不能起别的名称,否则无法实现请求参数的绑定。(不光是文件,其他字段也将无法绑定)


附:原理

【1】当用户请求上传文件时,通过前端控制器,前端控制器发现这是文件上传请求,就把请求分发到我们配置的文件解析器。

【2】文件解析器解析完毕,返回一个 upload 对象给前端控制器。

【3】通过 参数绑定机制 就可以获取该 upload 文件上传项对象


【2】跨服务器方式

在实际开发中,我们会有很多处理不同功能的服务器。例如:

  • 应用服务器:负责部署我们的应用
  • 数据库服务器:运行我们的数据库
  • 缓存和消息服务器:负责处理大并发访问的缓存和消息
  • 文件服务器:负责存储用户上传文件的服务器。

应用服务器作为一个中转站,分发不同的请求

(注意:此处说的不是服务器集群)


① 配置文件服务器

不之所云:在文件服务器的 tomcat 配置中加入,允许读写操作。

  • 该服务器的端口应该与应用服务器相异

  • 创建专门放置图片的工程,并把工程部署到文件服务器上!



② 拷贝 jar包

在负责处理文件上传的项目中导入额外 jar 包 + 单服务器需要的!

jersey-client-1.18.jar
jersey-core-1.18.jar

③ 编写 jsp 页面 + 文件上传解析器


④ 编写控制器

@RequestMapping("/fileupload3")
public String fileuoload3(MultipartFile upload) throws Exception {
    System.out.println("跨服务器文件上传...");

    // 定义上传文件服务器路径
    String path = "http://localhost:9090/uploads/";
    // 说明上传文件项
    // 获取上传文件的名称
    String filename = upload.getOriginalFilename();
    // 把文件的名称设置唯一值,uuid
    String uuid = UUID.randomUUID().toString().replace("-", "");
    filename = uuid+"_"+filename;

    // .创建 sun 公司提供的 jersey 包中的 Client 对象 
    Client client = Client.create();
    // 指定上传文件的地址,该地址是 web 路径
    WebResource webResource = client.resource(path + filename);
    // 实现上传 
    webResource.put(upload.getBytes());
    return "success";
}

五、SpringMVC 中的异常处理

【1】异常处理的思路

系统中异常包括两类:

异常种类处理逻辑
预期异常通过捕获异常从而获取异常信息
运行时异常 RuntimeException主要通过规范代码开发、测试通过手段减少运行时异常的发生。

系统的 dao、service、controller 出现都通过 throws Exception 向上抛出,最后由前端控制器交由异常处理器进行异常处理。


【2】实现步骤

① 编写异常类和错误页面

自定义异常

public class CustomException extends Exception { 
 	 private String message; 
	 public CustomException(String message) { this.message = message; } 
	 public String getMessage() { return message; }
}

error.jsp 错误提示页面

<%@ page contentType="text/html;charset=UTF-8" language="java" 
		 isELIgnored="false" %>
<body>  ${errorMsg}  </body>

② 自定义异常处理器

public class SysExceptionResolver implements HandlerExceptionResolver{
	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        SysException e = null;
        //如果抛出的是系统自定义异常则直接转换 
        if(ex instanceof SysException){
            e = (SysException)ex;
        }else{//如果抛出的不是系统自定义异常则重新构造一个系统错误异常。 
            e = new SysException("系统正在维护....");
        }
        // 创建ModelAndView对象
        ModelAndView mv = new ModelAndView();
        mv.addObject("errorMsg",e.getMessage());
        mv.setViewName("error");
        return mv;
    }
}

③ 配置异常处理器

在主配置文件中:

<!-- 配置自定义异常处理器 --> 
<bean id="handlerExceptionResolver"  
 	  class="com.shonan.exception.CustomExceptionResolver"/> 

六、SpringMVC中的拦截器

【1】 Interceptor 的作用

Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,也同样有拦截器链。用于对处理器进行预处理和后处理,是 AOP 思想的具体应用。


【2】 Interceptor 与 Filter 的区别

InterceptorFilter
由SpringMVC 提供,只有使用 SpringMVC 框架的工程才能用servlet 规范中的一部分,任何 java web 工程都可
只拦截访问的控制器方法,如果访问的是 jsp, html, css, image 或者 js 等静态资源不会进行拦截如果在 url-pattern 中配置了 /* 之后,可以对所有要访问的资源拦截。

【3】自定义拦截器的步骤

① 实现 HandlerInterceptor 接口

public class MyInterceptor1 implements HandlerInterceptor{
    /*预处理,controller方法执行前
     * return true: 放行,执行下一个拦截器,
					如果没有,执行controller中的方法
     * return false:不放行
     * @param request
     * @param response
     * @param handler	*/
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("拦截器预处理");
        //在放行之前转发到 某个页面,后序的控制器方法不会执行
        request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
        return true;
    }
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("拦截器后序处理");
        //request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
    }  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor1执行了...最后1111");
    }
}

② 配置拦截器

<!--通知前端控制器以下静态资源不拦截-->
<mvc:resources location="/css/" 	mapping="/css/**"/>
<mvc:resources location="/images/" 	mapping="/images/**"/>
<mvc:resources location="/js/" 		mapping="/js/**"/>
    
<mvc:interceptors>		 			 <!--配置拦截器们-->
   <mvc:interceptor>	 			 <!--配置单个拦截器-->
      <mvc:mapping path="/user/*"/>	 <!--拦截的具体的方法-->
      <!--配置拦截器不拦截的方法:<mvc:exclude-mapping path=""/>-->
      <!--配置拦截器对象-->
      <bean class="com.shonan.interceptor.MyInterceptor1"/>
   </mvc:interceptor>
</mvc:interceptors>

③ 拦截器3个方法执行的流程说明

情况一:

请求 —> 拦截器1【preH(true)】 —> 拦截器2【preH(true)】 —>

控制器业务方法() —> 拦截器2【postH()】 —> 拦截器1【postH()】 —>

拦截器2【aftercoml()】 —> 拦截器1【aftercoml()】—> 响应浏览器


情况二:

请求 —> 拦截器1【preH(true)】 —> 拦截器2【preH(false)】—>

拦截器1【aftercoml()】


方法执行时间
preHandle()在控制器方法执行前
postHandle()在拦截器链内所有拦截器返成功调用
afterCompletion()只有拦截器的 preHandle() 返回 true 才调用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值