Stage3-day08-springmvc详解3

拦截器执行顺序:

用户请求到DispatherServlet中,DispatherServlet调用HandlerMapping查找Handler,HandlerMapping返回一个拦截器链(HandlerExecutionChain)springmvc中的拦截器是通过HandlerMapping发起的。
--------------------- 
作者:愚人节第二天 
来源:CSDN 
原文:https://blog.csdn.net/xnf1991/article/details/53113519 
版权声明:本文为博主原创文章,转载请附上博文链接!

/和 /*的区别

/* 和 都对当前项目中的所有的请求进行拦截,但是 /* 存在一个问题,就是如果请求的是一个jsp页面,那么前端控制器会找一个controller对此请求做处理,但是我们只是想访问页面没有controller,所以不能用/*只能用/,还需要注意以前用*.action的时候只拦截action后缀的文件,现在是所有的请求都被拦截,我们配置的js和css等文件也会被拦截,我们需要在mvc的配置文件中给他们放行,放行方式有两种,详见下面:

数据回显

  1. 当客户进行了数据校验之后,存在数据问题,在给客户响应回去原页面的同时,不仅仅要有校验信息,还应该有客户所填的原始信息,即让客户上次填写的内容还在当前页面展示,这就是数据回显
  2. 实现

    1. 将返回的信息再放进request作用域中

    2. 通过EL表达式获取到返回的信息,优点是当没有值返回的时候是空,被引号引起来就是空字符串,所以不会显示null     

统一异常处理

  1. 给客户友好响应页面,使之体验度较好
  2. 可以异常在服务器端也可以简单处理
  3. 实现---从下层向上层一路向上抛出,最后抛给框架
  4. 也可以自定义异常---特殊异常,预定义的无法满足的时候;自定义异常是非运行时异常那么就继承Exception;自定义异常是运行时异常那么就继承RuntimeException

  5. 实现: 自定义一个异常解析器类---由解析器类创建解析器对象即用@Component注解来在容器加载的时候自动创建对象

    package com.offcn.resolver;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.HandlerExceptionResolver;
    import org.springframework.web.servlet.ModelAndView;
    
    @Component//创建异常的对象,项目加载之后对象自动创建就在容器中了,出现异常自动调用
    public class ExceptionResolver implements HandlerExceptionResolver {
    
    	@Override
    	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object obj,
    			Exception ex) {
    		//ex接收的是从下层抛上来的异常
    		//异常信息送到日志文档或者日志服务器,方便运维查看,此处我们先打印出来看一下
    		ex.printStackTrace();
    		System.out.println("前面出现异常所以调用了异常对象");
    		ModelAndView mv = new ModelAndView();
    		mv.setViewName("WEB-INF/error.jsp");
    		return mv;
    	}
    
    }

上传和下载

上传操作:

jar包:commons-fileupload、commons-io

  1. 页面:表单--post请求方式、enctype=multipart/form-data
  2. 页面:表单中设置input标签---file类型
  3. 解析器---上传解析器---实现解析,将客户端送到服务器端的字符串解析
  4. 在处理请求的执行单元中添加MultipartFile[] XXX形参来接收文件和非文件数据
    package com.offcn.controller;
    
    import java.io.File;
    import java.io.IOException;
    import java.util.UUID;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.multipart.MultipartFile;
    
    import com.offcn.bean.Person;
    
    @Controller
    public class UploadAndDownLoadController {
    
    	@RequestMapping("/upload.action")
    	//MultipartFile是一个接口,框架会帮我们创建实现类和对象,注意后面的参数名必须和上传控件的名字相同,
    	//他是一个数组,可以实现多上传,即多个上传空间都叫一个名字即可
    	public String upload(Person person,MultipartFile[] photo) throws IllegalStateException, IOException {
    		System.out.println("上传文件名称是null,因为获取上传文件名称在下面:"+person);
    		for(MultipartFile mf:photo) {
    			//当数组中有上传数据时才处理,否则会空指针
    			if(mf != null) {
    				//获取上传文件名称
    				String fileOldName = mf.getOriginalFilename();
    				person.setPhotoOldName(fileOldName);//把旧名字给属性赋值
    				//重名问题:用文件夹或随机名称解决
    				//判断是否存在路径,然后进行截取扩展名,拼合新名称
    				int index1 = fileOldName.lastIndexOf("\\");
    				if(index1 != -1) {
    					fileOldName.substring(index1+1);
    				}
    				int index2 = fileOldName.lastIndexOf(".");
    				String extension = fileOldName.substring(index2);
    				String uuid = UUID.randomUUID().toString();	
    				String fileNewName = uuid + extension;//拼合后的新名字
    				//获取指定路径,就是我们要上传的目的地,这是一个完整的路径,包含文件夹和文件名
    				File file = new File("C:\\Users\\Chris\\Desktop\\imgs",fileNewName);
    				//实现将上传的文件复制到指定路径
    				mf.transferTo(file);
    			}
    		}
    		return "/main.jsp";
    	}
    }

    控制台输出结果文件夹中有文件,上传成功

  5. 上传的时候,可以设置上传文件的大小限制

下载操作

  1. 展示下载列表,然后点击下载
  2. 实现下载列表:

解决方式是更改当前字符集为iso8859-1

package com.offcn.controller;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;

import com.offcn.bean.Person;

@Controller
public class UploadAndDownLoadController {

	@RequestMapping("/upload.action")
	//MultipartFile是一个接口,框架会帮我们创建实现类和对象,注意后面的参数名必须和上传控件的名字相同,
	//他是一个数组,可以实现多上传,即多个上传空间都叫一个名字即可
	public String upload(Person person,MultipartFile[] photo) throws IllegalStateException, IOException {
		System.out.println("上传文件名称是null,因为获取上传文件名称在下面:"+person);
		for(MultipartFile mf:photo) {
			//当数组中有上传数据时才处理,否则会空指针
			if(mf != null) {
				//获取上传文件名称
				String fileOldName = mf.getOriginalFilename();
				person.setPhotoOldName(fileOldName);//把旧名字给属性赋值
				//重名问题:用文件夹或随机名称解决
				//判断是否存在路径,然后进行截取扩展名,拼合新名称
				int index1 = fileOldName.lastIndexOf("\\");
				if(index1 != -1) {
					fileOldName.substring(index1+1);
				}
				int index2 = fileOldName.lastIndexOf(".");
				String extension = fileOldName.substring(index2);
				String uuid = UUID.randomUUID().toString();	
				String fileNewName = uuid + extension;//拼合后的新名字
				//获取指定路径,就是我们要上传的目的地,这是一个完整的路径,包含文件夹和文件名
				File file = new File("C:\\Users\\Chris\\Desktop\\imgs",fileNewName);
				//实现将上传的文件复制到指定路径
				mf.transferTo(file);
			}
		}
		return "/main.jsp";
	}
	
	@RequestMapping("/list.action")
	public String list(Model model) {//方法的作用是返回下载列表和名字
		//假如在files中保存着所有的要下载的文件的名称
		//先找到文件所在路径
		File file = new File("C:\\Users\\Chris\\Desktop\\imgs");
		//获取当前目录下所有文件和文件夹的名称,仅限该级别目录,要获取所有文件名可以使用递归
		String[] files = file.list();
		model.addAttribute("files", files);
		return "/downlist.jsp";
	}
	
//	//该方法是实现下载功能,原生态方式
//	@RequestMapping("/download.action")
//	public void download(String fname,HttpServletRequest request,HttpServletResponse response) throws IOException {
//		File file = new File("C:\\Users\\Chris\\Desktop\\imgs",fname);
//		//在下载时,可能存在中文乱码,我们要将当前的编码utf-8转化成iso8859-1
//		fname = new String(fname.getBytes("utf-8"),"iso8859-1");
//		
//		//一个头(响应头),两个流(输入流,输出流)
//		InputStream is = null;
//		OutputStream os = null;
//		is = new BufferedInputStream(new FileInputStream(file));
//		os = response.getOutputStream();
//		//设置下载的时候的文件名称
//		response.setHeader("content-Disponsition", "attachment;fileName="+fname);
//		int len = -1;
//		byte[] bs = new byte[1024];
//		while((len= is.read(bs))!= -1) {
//			os.write(bs,0,len);
//		}
//		os.close();
//		is.close();
//	}
	
	//下载实现的新方式
	@RequestMapping("/download.action")
	public ResponseEntity<byte[]> download(String fname) throws IOException{
		//找到文件所在路径
		File file = new File("C:\\Users\\Chris\\Desktop\\imgs",fname);
		//处理可能存在的中文乱码问题
		fname = new String(fname.getBytes("utf-8"),"iso8859-1");
		//准备响应头
		HttpHeaders header = new HttpHeaders();
		header.setContentDispositionFormData("attachment", fname);
		//响应内容的类型,可以省略不写
		header.setContentType(MediaType.APPLICATION_OCTET_STREAM);
		//实现返回,参数解释
		/**
		 * FileUtils.readFileToByteArray(file)是返回要下载的文件
		 * header:我们上面设置的响应头
		 * HttpStatus.OK,ok表示正常,即响应码200
		 */
		return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file), header, HttpStatus.OK);
	}
}

restful风格的项目

  1. 项目在实现的过程中采用restful风格,那么项目就是restful风格的项目
  2. restful风格:从请求的url、从请求的方式(post模拟put等)、到响应的方式(用json响应)都做了一系列的规范
    1. 原来的url:http://localhost:8080/springmvc/index.jsp?id=10 一般请求controller的时候如下设计,通常值前面的一级url使用名词复数格式. 新的url:http://localhost:8080/springmvc/UploadAndDownLoads/10
    2. 变化:1.参数不再通过?的字符串拼接形式而是作为url的一部分直接写,没有?了,restful风格的url实现传参如果我们页面上没有东西来接我们传过去的json,那么这个json就会直接输出到页面上返回多个对象直接在url后面用/连接即可其他也类似,list,map等
  3. restful风格的url要求前端控制器的映射改为 /,那么此时拦截除了jsp页面之外的所有请求,包括.css、.js、.html、.jpg,所有的静态数据也被拦截,这是不可以的,对静态资源进行放行实现放行的操作:到springmvc的配置文件中添加配置配置实现有两种:

web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">

	<!-- 前端控制器配置 -->
	<servlet>
		<servlet-name>abc123</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>abc123</servlet-name>
		
		<!-- 注意这里不能写/*,  /*和/都对当前项目中的所有的请求进行拦截看,但是/*存在一个问题,
		就是如果请求的是一个jsp页面,那么前端控制器会找一个controller对此请求做处理,但是我们只是想访问页面
		没有controller,所以不能用/*只能用/,还需要注意以前用*.action的时候只拦截action后缀的文件,现在
		所有的请求都拦截,我们配置的js和css等文件也会被拦截,我们需要在mvc的配置文件中给他们放行 -->
		<url-pattern>/</url-pattern>
	</servlet-mapping>
</web-app>

mvc配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

	<!-- 扫描包 -->
	<context:component-scan base-package="com.offcn"></context:component-scan>
	
	<!-- 注解方式的适配器和注解方式的映射器 -->
	<!-- mvc:annotation代替注解的适配器和映射器 -->
	<mvc:annotation-driven></mvc:annotation-driven>
	<!-- <mvc:default-servlet-handler/> -->
	
	<!-- location设置静态资源的文件夹即路径 ;mapping设置路径中的所有文件和子文件夹,注意这里要用两个*
	一个*只是表示所有文件,**表示所有文件和文件夹 -->
	<mvc:resources location="/js/" mapping="/js/**"></mvc:resources>
	<mvc:resources location="/css/" mapping="/css/**"></mvc:resources>
	<mvc:resources location="/imgs/" mapping="/imgs/**"></mvc:resources>
	
	<!-- 视图解析器 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	</bean>
	
</beans>

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="js/jquery.js"></script>
</head>
<body>
<!-- http://localhost:8080/day08-springmvc-restful/users/10 -->
<a href="users/10">提交--restful风格的url</a>
<hr>
<a href="users/10/zhangfei">多个值</a>
<hr>
<a href="users">多个值</a>
<hr>
<img src="imgs/123.jpg">
<form id="formData" enctype="application/x-www-form-urlencoded">
	<p><input type="text" name="pid"></p>
	<p><input type="text" name="pname"></p>
	<p><input type="text" name="weight"></p>
	<p><input type="text" name="photoOldName"></p>
	<p><input type="text" name="photoNewName"></p>
	<input type="button" id="btn" value="提交">
</form>
<script type="text/javascript">
	$(function(){
		//找到按钮
		$("#btn").click(function(){
			//实现异步操作
			$.ajax({
				url:"users",
				data:$("#formData").serialize(),
				type:"post",
				dataType:"json",
				cache:false,
				contentType:"application/x-www-form-urlencoded;charset=utf8",
				async:true,
				success:function(data){
					console.log(data);
				},
				error:function(){
					
				}
			});
		});
	})
</script>

</body>
</html>

controller

package com.offcn.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.offcn.bean.Person;

@Controller
@RequestMapping("/users")//共同的部分可以抽离出来
public class UserController {

	//要想实现页面的参数传入后台页面的/users/10要改成/users/{id},这个id是变量名,名称任意同时方法中
	//形参前面要加上@PathVariable(value = "id")注解表明这个形参对应的就是传入的id
	@RequestMapping(value="/{id}",method = RequestMethod.GET)
	@ResponseBody//将对象转化为json传出
	public Person getPerson(@PathVariable(value = "id")int x) {
		System.out.println(x);
		Person person = new Person();
		person.setPid(1001);
		person.setPname("张飞");
		person.setWeight(200.0);
		person.setPhotoOldName("1.jpg");
		return person;
	}
	
		@RequestMapping(value="/{id}/{username}",method = RequestMethod.GET)
		@ResponseBody//将对象转化为json传出
		public List<Person> getPerson2(@PathVariable(value = "id")int x,
				@PathVariable(value="username")String name) {
			System.out.println(x+"--"+name);
			Person person = new Person();
			person.setPid(1001);
			person.setPname("张飞");
			person.setWeight(200.0);
			person.setPhotoOldName("1.jpg");
			
			Person person2 = new Person();
			person2.setPid(1001);
			person2.setPname("关羽");
			person2.setWeight(200.0);
			person2.setPhotoOldName("2.jpg");
			List<Person> list = new ArrayList<>();
			list.add(person);
			list.add(person2);
			return list;
		}
		
		@RequestMapping(method = RequestMethod.GET)//因为地址就是/users,所以此处不用在写了,已经抽离出去了
		@ResponseBody
		public Map getPersonMap() {
			Map map = new HashMap<>();
			Person person = new Person();
			person.setPid(1001);
			person.setPname("张飞");
			person.setWeight(200.0);
			person.setPhotoOldName("1.jpg");
			
			map.put("person", person);
			return map;
		}
		
		@RequestMapping(method=RequestMethod.POST)
		@ResponseBody
		public Person savePerson(Person person) {
			
			return person;
		}
}

拦截器

拦截器和过滤器针对功能来说十分相似,都起到过滤拦截的功能

  1. 拦截器是框架中的内容,过滤器是servlet的内容
  2. 拦截器对象可以使用框架容器管理,过滤器对象servlet容器管理
  3. 拦截器基于的是反射的方式,过滤器基于的是函数回调方式
  4. 执行的先后:请求---过滤器---拦截器---后端控制器---拦截器----过滤器----响应

拦截器中重写的几个方法的执行情况

  1. preHandler是在后端控制器执行之前执行,实现拦截控制;通过返回值false和true控制;false则不放行,true放行
  2. postHandler是在后端控制器执行单元执行结束,返回模型视图给适配器之前执行此方法,如果后端控制器的执行单元出现异常此方法不执行
  3. afterCompletion是在后端控制器执行单元执行完毕之后执行,不分执行单元中是否有异常;有异常执行,无异常也执行

自定义拦截器

package com.offcn.inteceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class Firstinterceptor implements HandlerInterceptor {

	@Override
	public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
		//实现拦截的,返回值为false不放行
		System.out.println("pre............Handle");
		return false;
	}
	

	@Override
	public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
			throws Exception {
		//在后端控制器的方法执行完毕还没有返回模型和视图之前执行此方法
		//执行单元执行完毕,模型试图返回适配器之前,执行此方法
		//此方法适合在后端控制器返回模型视图之前的再处理
		System.out.println("post.......Handle");
	}

	@Override//相当于aop中的最终通知
	public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
			throws Exception {
		//在后端控制器执行完之前不管是否发生异常始终要执行的方法,finally的
		System.out.println("after.......completion");
	}
}

配置拦截器

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="login" method="post">
	<p><input type="text" name="username"></p>
	<p><input type="password" name="password"></p>
	<p><input type="submit" value="登录"></p>
</form>
</body>
</html>

main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<a href="users">这个连接需要登录成功才能操作</a>
</body>
</html>

userController.java

package com.offcn.controller;

import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class UserController {

	@RequestMapping(value="/login",method = RequestMethod.POST)
	public String login(String username,String password,HttpSession session) {
		//通过username和password进行判断---正常,说明登录成功
		//信息保存到session
		session.setAttribute("username", username);
		return "/WEB-INF/main.jsp";
	}
	
	@RequestMapping("/users")
	public String getUser() {
		return "/WEB-INF/user.jsp";
	}
}

拦截器

package com.offcn.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class LoginInterceptor implements HandlerInterceptor {

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object obj) throws Exception {
		//做登录拦截的操作
		
		//获取当前请求的url
		String url = request.getRequestURI();
		url = url.substring(url.lastIndexOf("/")+1);
		if("login".equals(url)) {
			return true;
		}
		
		HttpSession session = request.getSession();
		String name = (String) session.getAttribute("username");
		if(name != null) {
			return true;
		}
		//原生态的响应操作,路径带有/,表示的是端口号位置,没有项目名
		response.sendRedirect(session.getServletContext().getContextPath()+"/index.jsp");
		return false;
	}
	
	@Override
	public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
			throws Exception {

	}

	@Override
	public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
			throws Exception {

	}
}

猜你喜欢

转载自blog.csdn.net/qq_42837554/article/details/90214171