目录
(3)使用Model,Map,ModelMap传输数据到页面
(7)使用redirect来实现重定向,相当于 response.sendRedirect("index.jsp"),跟视图解析器无关
(8)使用RespsonseEntity可以用来定制响应内容
2、在对应处理方法上面加上@ResponseBody用于标记该处理方法返回json
3、在接受参数前加@RequestBody代表接受的是JSON格式
1、请求处理
(1)处理请求
在之前的servlet中我们可以通过request.getParameter()来获取请求中的参数,但是在我们编写的SpringMVC的应用程序中,在具体请求的方法中并不包含request参数,那么我们应该如何获取请求中的参数呢?
需要使用以下几个注解:
@RequestParam:获取请求的参数,使用此注解之后,参数的名称不需要跟请求的名称一致,但是必须要写
public String request(@RequestParam(value = "user",required = false,defaultValue = "hehe") String username)
- value:表示要获取的参数值
- required:表示此参数是否必须,默认是true,如果不写参数那么会报错,如果值为false,那么不写参数不会有任何错误
- defaultValue:如果在使用的时候没有传递参数,那么定义默认值即可
@RequestHeader:获取请求头信息,相当于 request.getHeader("User-Agent"),注解中参数与@RequestParam一样
public String header(@RequestHeader("User-Agent") String agent)
@CookieValue:获取cookie中的值,注解中参数与@RequestParam一样
public String cookie(@CookieValue("JSESSIONID") String id){
相当于
Cookie[] cookies = request.getCookies();
for(Cookie cookie : cookies){
cookie.getValue();
}
}
(2)请求映射处理
@RequestMapping
@RequestMapping用来匹配客户端发送的请求,可以在方法上使用,也可以在类上使用。
- 方法:表示用来匹配要处理的请求
- 类上:表示为当前类的所有方法的请求地址添加一个前置路径,访问的时候必须要添加此路径
- 注意:在整个项目的不同方法上不能包含相同的@RequestMapping值
其他属性值
- value:要匹配的请求
- method:限制发送请求的方式:POST、GET、DELETE、PUT等 method = RequestMethod.POST
- params:表示请求要接受的参数,如果定义了这个属性,那么发送的时候必须要添加参数
- 1. 必须要有某些参数 params = {"username"}
- 2. 必须没有某些参数 params = {"!username"}
- 3. 参数必须要等于什么值 params = {"username=123"}
- 4. 参数必须要不等于什么值 params = {"username!=123"}
- headers:填写请求头信息-必须包含某个值
- @RequestMapping(value = "/hello4",headers = {"Accept-Language=zh-CN,zh;q=0.9"})
- consumers:当前请求的内容类型必须为指定值 consumes = {"application/x-www-form-urlencoded"}
- application/x-www-form-urlencoded form表单提交默认的内容类型
- multipart/form-data form表单提交文件流的的内容类型
- application/json ajax提交的json内容类型
- produces:返回的内容类型 produces = {"application/json"}
@PathVariable
@PathVariable提供了对占位符URL的支持,将URL中占位符参数绑定到控制器处理方法的参数中。
如果是单个参数接收必须要使用@PathVariable来声明对应的参数占位符名字
@RequestMapping("/user/{id}/{username}")
public String path01(@PathVariable("id") Integer id,@PathVariable("username") String name)
如果是javaBean可以省略@PathVariable,要保证占位符的名字和javaBean的属性名字一样
@RequestMapping("/user02/{id}/{name}")
public String path02(User user)
(3)REST请求风格
REST即表述性状态传递(英文:Representational State Transfer,简称REST)是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。它是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。
如果是原来的架构风格,需要发送四个请求,分别是:
- 查询用户:http://localhost:8080/app/user.do?action=getUser&id=xxx GET
- 增加用户: http://localhost:8080/app/user_add.do POST
- 修改用户: http://localhost:8080/app/xiugaiuser.do POST
- 删除用户: http://localhost:8080/app/delete.do?id=1 GET/POST
按照此方式发送请求的时候比较麻烦,需要定义多种请求,而在HTTP协议中,有不同的发送请求的方式,分别是GET、POST、PUT、DELETE等,我们如果能让不同的请求方式表示不同的请求类型就可以简化我们的查询,改成名词
- 查询用户: http://localhost:8080/xxx/user/1 GET --查询
- 查询多个用户: http://localhost:8080/xxx/users GET
- 新增用户: http://localhost:8080/xxx/user POST ---新增
- 修改用户: http://localhost:8080/xxx/user/1 PUT --修改
- 删除用户:http://localhost:8080/xxx/user/1 DELETE --删除
还有一点需要特别注意,就是HTML现在无法支持PUT和DELETE,如果我们就想实现,有办法吗?那就得用HiddenHttpMethodFilter过滤器了
1、添加HiddenHttpMethodFilter过滤器
<filter>
<filter-name>hiddenHttpMethod</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethod</filter-name>
<servlet-name>springmvc</servlet-name>
</filter-mapping>
2、在表单中添加一个隐藏域
<input type="hidden" value="put" name="_method"> value就是对应的请求方式
3、将form的method设置POST
这样确实是可以进入到方法,但是呢,tomcat 7以上的版本对request.method更加严格,只支持GET/POST/HEAD,所以在转发到页面的时候会报错HTTP Status 405 - JSPs only permit GET POST or HEAD,当然这个我们也是可以解决的:
- 1、用tomcat7
- 2、不用转发,用重定向
- 3、将jsp的page指定 isErrorPage属性改成true(不建议)
- 4、自定义一个过滤器,将request.method改回POST
public class GetMethodConvertingFilter implements Filter {
@Override
public void init(FilterConfig config) throws ServletException {
// do nothing
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(wrapRequest((HttpServletRequest) request), response);
}
@Override
public void destroy() {
// do nothing
}
private static HttpServletRequestWrapper wrapRequest(HttpServletRequest request) {
return new HttpServletRequestWrapper(request) {
@Override
public String getMethod() {
return "POST";
}
};
}
}
- 5、BackToPostHttpMethodFilter
<!--一般修改和删除都会进行重定向,万一有遇到修改或删除还要 转发 就使用这个-->
<filter>
<filter-name>backToPostHttpMethodFilter</filter-name>
<filter-class>com.ns.filter.BackToPostHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>backToPostHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
2、响应处理
(1)使用默认内置视图解析器(ViewResolver)
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/"/>
<property name="suffix" value=".jsp"></property>
</bean>
(2)使用视图控制器<view-controller>
如果我们有些请求只是想跳转页面,不需要来后台处理什么逻辑,我们无法在Action中写一个空方法来跳转,直接在中配置一个如下的视图跳转控制器即可(不经过Action,直接跳转页面)
<mvc:view-controller path="/" view-name="index" />
(3)使用Model,Map,ModelMap传输数据到页面
可以在方法的参数上传入Model,ModelMap,Map类型,此时都能够将数据传送回页面
public String output2(/*Model model*/ModelMap model/*Map map*/){
model.addAttribute("msg","hello,Springmvc");
return "output";
}
三者的关系
(4)使用ModelAndView对象传输数据到页面
当使用modelAndView对象的时候,返回值的类型也是此对象,可以将要跳转的页面设置成view的名称,来完成跳转的功能,同时数据也是放到request作用中。
//ModelAndView mv = new ModelAndView("output");
ModelAndView mv = new ModelAndView();
mv.addObject("msg","hello.modelAndView");
mv.setViewName("output");
return mv;
ps:springmvc处理以上几种设置model的request域中的几种方式以外,springmvc还会隐式的讲请求绑定参数自动设置到request域
(5)@ModelAttribute
写在方法上面
@ModelAttribute的方法会在当前处理器中所有的处理方法之前调用
- 1.通过@ModelAttribute来给全局变量赋值(不推荐)
- 2. 当我们调用执行全字段的更新数据库操作时,假如提供给用户的修改字段只有部分几个,这个时候就会造成其他字段更新丢失,解决:
- 1.自己定制update语句, 只更新指定的那些字段
- 2.如果无法定制sql语句, 可以在更新之前进行查询, 怎么在更新之前查询?只能在springmvc 绑定请求参数之前查询, 利用@ModelAttribute就可以在参数绑定之前查询, 但是怎么将查询出来的对象和参数的对象进行合并? springmvc具有该特性, 会将model中和参数名相同的属性拿出来进行合并,将参数中的新自动进行覆盖,没有的字段进行保留。这样就可以解决这个问题。
写在参数上面
可以省略,加上则会从model中获取一个指定的属性和参数进行合并,因为model和sessionAttribute具有共通的特性,所以如果session中有对应的属性也会进行合并
(6)使用forward实现页面转发
return "forward:/index.jsp";
由服务器的页面进行跳转,不需要客户端重新发送请求:
- 1、地址栏的请求不会发生变化,显示的还是第一次请求的地址
- 2、请求的次数,有且仅有一次请求
- 3、请求域中的数据不会丢失
- 4、根目录:localhost:8080/项目地址/,包含了项目的访问地址
(7)使用redirect来实现重定向,相当于 response.sendRedirect("index.jsp"),跟视图解析器无关
在浏览器端进行页面的跳转,需要发送两次请求(第一次是人为的,第二次是自动的)
- 1、地址栏的地址发生变化,显示最新发送请求的地址
- 2、请求次数:2次
- 3、请求域中的数据会丢失,因为是不同的请求
- 4、根目录:localhost:8080/ 不包含项目的名称
(8)使用RespsonseEntity可以用来定制响应内容
return new ResponseEntity<String>("返回数据", HttpStatus.OK);
3、类型转换&数据格式化&数据验证
(1)自定义类型转换器
在日常的企业开发需求中,我们输入文本框的内容全部都是字符串类型,但是在后端处理的时候我们可以用其他基本类型来接受数据,也可以使用实体类来接受参数,这个是怎么完成的呢?就是通过SpringMVC内部提供的类型转换器,但是有些情况下有可能难以满足我们的需求,因此需要我们自己实现
MyConverter.java
public class MyConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
if(!StringUtils.isEmpty(source)){
// 即支持yyyy-MM-dd yyyy/MM/dd
try {
if(source.split("-").length==3){
DateFormat df=new SimpleDateFormat("yyyy-MM-dd");
return df.parse(source);
}else if(source.split("/").length==3){
DateFormat df=new SimpleDateFormat("yyyy/MM/dd");
return df.parse(source);
}else{
throw new RuntimeException("日期转换错误:"+source);
}
} catch (ParseException e) {
e.printStackTrace();
}
}
return null;
}
}
springmvc.xml
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="com.cn.myConverter"></ref>
</set>
</property>
</bean>
(2)数据格式化
Spring 提供了两个可以用于格式化数字、日期和时间的注解@NumberFormat和@DateTimeFormat,这两个标签可以用于javabean的属性或方法参数上。@NumberFormat可以用来格式化任何的数字的基本类型(如int,long)或java.lang.Number的实例(如 BigDecimal, Integer)。@DateTimeFormat可以用来格式化java.util.Date、java.util.Calendar和 java.util.Long类型.
@NumberFormat
pattern:类型为String,使用自定义的数字格式化字符串,"##,###.##"。@NumberFormat(pattern = "0.00")
style:类型为NumberFormat.Style,常用值:
- Style.NUMBER正常数字类型 @NumberFormat
- Style.PERCENT百分数类型 @NumberFormat(style = Style.PERCENT)
- Style.CURRENCY 货币类型 @NumberFormat(style = Style.CURRENCY)
@DateTimeFormat,互斥属性
iso:类型为DateTimeFormat.ISO
- DateTimeFormat.ISO.DATE: 格式yyyy-MM-dd。
- DateTimeFormat.ISO.DATE_TIME: 格式yyyy-MM-dd HH:mm:ss .SSSZ。
- DateTimeFormat.ISO.TIME: 格式HH:mm:ss .SSSZ。
- DateTimeFormat.ISO.NONE: 表示不使用ISO格式的时间。
pattern:类型为String,使用自定义的时间格式化字符串。
style:类型为String,通过样式指定日期时间的格式,由两位字符组成,第1位表示日期的样式,第2位表示时间的格式:
- S: 短日期/时间的样式;
- M: 中日期/时间的样式;@DateTimeFormat(style = "M-")
- L: 长日期/时间的样式;
- F: 完整日期/时间的样式;
- -: 忽略日期/时间的样式;@DateTimeFormat(style = "-S")
(3)数据校验
一般情况下我们会在前端页面实现数据的校验,但是大家需要注意的是前端校验会存在数据的不安全问题,因此一般情况下我们都会使用前端校验+后端校验的方式,这样的话既能够满足用户的体验度,同时也能保证数据的安全。
JSR303是 Java 为 Bean 数据合法性校验提供的标准,它已经包含在 JavaEE 6.0 中 。JSR 303 (Java Specification Requests意思是Java 规范提案)通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通 j过标准的验证接口对 Bean 进行验证。
JSR303
Hibernate Validator 实现了JSR349验证注解规范的技术,扩展注解
1、spring中拥有自己的数据校验框架,同时支持JSR303标准的校验框架,Hibernate Validator需要导入依赖的包。
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.0.Final</version>
</dependency>
2、在方法实体类前加注解@Valid,在需要验证的处理方法参数中加入BindingResult,代表自己处理错误,这样就不会显示错误页面了
@RequestMapping("/dataValidate")
public String validate(@Valid User user, BindingResult bindingResult) {
System.out.println(user);
if (bindingResult.hasErrors()) {
System.out.println("验证失败");
return "redirect:/index.jsp";
} else {
System.out.println("验证成功");
return "hello";
}
}
3、实体类中需要验证的字段上加具体注解
4、如下方法进行错误获取
if (bindingResult.hasErrors()) {
System.out.println("验证失败");
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
for (FieldError fieldError : fieldErrors) {
System.out.println(fieldError.getDefaultMessage());
System.out.println(fieldError.getField());
errorsMap.put(fieldError.getField(),fieldError.getDefaultMessage());
}
model.addAttribute("errors",errorsMap);
return "add";
}
5、既然后端有校验了,我们希望能在页面上体现出来
基于原生html form表单实现方式
- 1.在将错误信息循环通过map存入到request域中
- 2.在jsp通过${errors.id}获取对应的错误信息
基于spring form标签库的实现方式
- 1.添加一个显示jsp的处理方法, 传入一个空的User到model中
- 2.在jsp中导入spring-form标签库
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
- 3.在form标签上必须都要以<form:>开头,一定要加上ModelAtrribute
- 4.数据自动回显:可以使用modelAttribute指定数据的对象,或者再传入空的User到model中
- 5.支持全部http请求方法 比如method=”put” put\delete 提交方式
- 6.使用path来双向绑定属性
- add.jsp
<form:form action="dataValidate" modelAttribute="user" method="post">
id:<form:input path="id"></form:input><form:errors path="id"></form:errors> <br/>
name:<form:input path="name"></form:input><form:errors path="name"></form:errors><br/>
age:<form:input path="age"></form:input><form:errors path="age"></form:errors><br/>
gender:<form:input path="gender"></form:input><form:errors path="gender"></form:errors><br/>
birth:<form:input path="birth"></form:input><form:errors path="birth"></form:errors><br/>
email:<form:input path="email"></form:input><form:errors path="email"></form:errors><br/>
<input type="submit" value="submit">
</form:form>
- 7.动态数据绑定:Select 、 checkboxes、 radiobottons、 都可以使用Items 制定数据源 可以是list (当List的泛型是javaBean的时候需要制定itemValue和itemLabel)、map(不需要制定itemValue和itemLabel)
<form:checkboxes path="hobbies" items="${list}"></form:checkboxes>
4、JSON处理
ava转换为Json的过程一般会称为 “序列化”
Json转换为java 的过程一般会称为 “反序列化”
Json的属和字符串值 必须要用双引号“” 不能用单引号
(1)SpringMVC的获取JSON数据(前端)
ajax我们经常用到,传的数据是json数据,json数据又有对象,数组。以下给大家总结了4种常用的获取json方式
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
<script src="https://cdn.staticfile.org/jquery/3.5.1/jquery.min.js"></script>
<script type="text/javascript">
// 也没加载事件简写方式
$(function(){
$("#btnJson1").click(function(){
$.ajax({
url:"${pageContext.request.contextPath}/json/request01",
method:"post",
data:"张三",
contentType:'application/json',
dataType:"json",
success:function(user){
alert(user.name);
}
});
});
$("#btnJson2").click(function(){
var user={'id':'1','name':'张三'}; // 定义js对象
var jsonValue=JSON.stringify(user); // 对象转换为json字符串
console.log(jsonValue)
$.ajax({
url:"${pageContext.request.contextPath}/json/request02",
method:"post",
data:'{"id":"1","name":"张三","birthady":"2019-01-01"}',
contentType:'application/json',
dataType:"json",
success:function(user){
alert(user.name);
}
});
});
$("#btnJson3").click(function(){
$.ajax({
url:"${pageContext.request.contextPath}/json/request03",
method:"post",
data:'{"idxx":"1","namexx":"张三","birthadyxx":"2019-01-01"}',
contentType:'application/json',
dataType:"json",
success:function(user){
alert(user.name);
}
});
});
$("#btnJson4").click(function(){
var listUser=new Array();
var user1={"id":"1","name":"张三","birthady":"2019-01-01"};
var user2={"id":"2","name":"李四","birthady":"2019-01-01"};
listUser.push(user1)
listUser.push(user2)
$.ajax({
url:"${pageContext.request.contextPath}/json/request04",
method:"post",
//data:'[{"id":"1","name":"张三","birthady":"2019-01-01"},{"id":"2","name":"李四","birthady":"2019-01-01"}]',
data:JSON.stringify(listUser),
contentType:'application/json',
dataType:"json",
success:function(user){
alert(user.name);
}
});
});
})
</script>
</head>
<body>
<input type="button" value="发送单个参数的json数据" id="btnJson1"/><br/>
<input type="button" value="发送对象的json数据用javaBean接收" id="btnJson2"/><br/>
<input type="button" value="发送对象的json数据用Map接收" id="btnJson3"/><br/>
<input type="button" value="发送数组对象的json数据用List<User>接收" id="btnJson4"/><br/>
</body>
</html>
(2)SpringMVC的返回JSON数据(后端)
1、加入jackson依赖
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.10.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.10.3</version>
</dependency>
2、在对应处理方法上面加上@ResponseBody用于标记该处理方法返回json
3、在接受参数前加@RequestBody代表接受的是JSON格式
5、上传&下载
(1)上传
Spring MVC 为文件上传提供了直接的支持,这种支持是通过即插即用的 MultipartResolver 实现的。Spring 用 Jakarta Commons FileUpload 技术实现了一个 MultipartResolver 实现类:CommonsMultipartResovler
Spring MVC 上下文中默认没有装配 MultipartResovler,因此默认情况下不能处理文件的上传工作,如果想使用 Spring 的文件上传功能,需现在上下文中配置 MultipartResolver。
pom.xml
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
springmvc.xml
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"></property>
<property name="maxUploadSize" value="1024000"></property>
</bean>
1、单文件上传
@RequestMapping(value = "/testUpload", method = RequestMethod.POST)
public String testUpload(@RequestParam(value = "desc", required = false) String desc, @RequestParam("file") MultipartFile multipartFile) throws IOException {
System.out.println("desc : " + desc);
System.out.println("OriginalFilename : " + multipartFile.getOriginalFilename());
multipartFile.transferTo(new File("D:\\file\\"+multipartFile.getOriginalFilename()));
return "success"; //增加成功页面: /views/success.jsp
}
2、多文件上传
public String upload02(String desc,MultipartFile[] myfile) throws IOException {
for (MultipartFile multipartFile : myfile) {
System.out.println(desc);
System.out.println(multipartFile.getOriginalFilename());
String path = "D:\\out\\" + multipartFile.getOriginalFilename();
File file = new File(path);
multipartFile.transferTo(file);
}
return "success";
}
3、多文件多线程上传
public String upload03(String desc,MultipartFile[] myfile) throws IOException, InterruptedException {
System.out.println(desc);
for (MultipartFile multipartFile : myfile) {
// 声明线程
Thread thread = new Thread(() -> {
System.out.println(multipartFile.getOriginalFilename());
String path = "D:\\out\\" + multipartFile.getOriginalFilename();
File file = new File(path);
try {
multipartFile.transferTo(file);
} catch (IOException e) {
e.printStackTrace();
}
});
thread.start(); //启动线程
thread.join(); // 让子线程执行完再执行主线程
}
return "success";
}
(2)下载
servlet原生下载方式
@WebServlet("/download")
public class DownloadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获得当前项目路径下的下载文件(真实开发中文件名肯定是从数据中读取的)
String realPath =request.getServletContext().getRealPath("/file/20181129204254948.png");
// 根据文件路径封装成了File对象
File tmpFile=new File(realPath);
// 可以直接根据File对象获得文件名
String fileName = tmpFile.getName();
// 设置响应头 content-disposition: 就是设置文件下载的打开方式,默认会在网页上打开,
// 设置attachment;filename= 就是为了以下载方式来打开文件
// "UTF-8"设置如果文件名有中文就不会乱码
response.setHeader("content-disposition", "attachment;filename="+ URLEncoder.encode(fileName, "UTF-8"));
// 根据文件路径 封装成文件输入流
InputStream in = new FileInputStream(realPath);
int len = 0;
// 声明了一个1KB的字节 的缓冲区
byte[] buffer = new byte[1024];
// 获取输出流
OutputStream out = response.getOutputStream();
// 循环读取文件,每次读1KB,避免内存溢出
while ((len = in.read(buffer)) > 0) {
// 往客户端写入
out.write(buffer,0,len);//将缓冲区的数据输出到客户端浏览器
}
in.close();
}
}
使用RespsonseEntity实现下载
@Controller
public class OtherController {
@RequestMapping("/download")
public ResponseEntity<byte[]> download(HttpServletRequest request) throws Exception {
// 获得当前项目路径下的下载文件(真实开发中文件名肯定是从数据中读取的)
String realPath =request.getServletContext().getRealPath("/file/20181129204254948.png");
// 根据文件路径封装成了File对象
File tmpFile=new File(realPath);
// 可以直接根据File对象获得文件名
String fileName = tmpFile.getName();
HttpHeaders headers=new HttpHeaders();
headers.set("content-disposition", "attachment;filename="+ URLEncoder.encode(fileName, "UTF-8"));
// 根据文件路径 封装成文件输入流
InputStream in = new FileInputStream(realPath);
return new ResponseEntity<>(new byte[in.available()],headers,HttpStatus.OK);
}
}
6、拦截器
拦截器采用AOP的设计思想,它跟过滤器类似,用来拦截处理方法在之前和之后执行一些跟主业务没有关系的一些公共功能:权限控制、日志、异常记录、记录方法执行时间.....
(1)Springmvc拦截器
SpringMVC提供了拦截器机制,允许运行目标方法之前进行一些拦截工作或者目标方法运行之后进行一下其他相关的处理。自定义的拦截器必须实现HandlerInterceptor接口。
拦截器有3个回调方法:
preHandle:预处理回调方法,实现处理器的预处理(如登录检查),第三个参数为响应的处理器返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
postHandle:后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
afterCompletion:整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中preHandle返回true的拦截器才会执行
自定义拦截器
MyInterceptor.class
public class MyInterceptor implements HandlerInterceptor {
/**
* 在处理方法之前执 日志、权限、 记录调用时间
* @param request 可以在方法请求进来之前更改request中的属性值
* @param handler 封装了当前处理方法的信息
* @return true 后续调用链是否执行/ false 则中断后续执行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在请求映射到对应的处理方法映射,实现类才是HandlerMethod。
// 如果是视图控制器,实现类ParameterizableViewController
if(handler instanceof HandlerMethod ) {
HandlerMethod handMethod = (HandlerMethod) handler;
}
/*System.out.println("-------类["+handMethod.getBean().getClass().getName()+"]" +
"方法名["+handMethod.getMethod().getName()+"]" +
"参数["+ Arrays.toString(handMethod.getMethod().getParameters()) +"]前执行--------preHandle");*/
System.out.println("---------方法后执行,在渲染之前--------------preHandle");
return true;
}
/**
* 如果preHandle返回false则会不会允许该方法
* 在请求执行后执行, 在视图渲染之前执行
* 当处理方法出现了异常则不会执行方法
* @param response 可以在方法执行后去更改response中的信息
* @param handler 封装了当前处理方法的信息
* @param modelAndView 封装了model和view.所以当请求结束后可以修改model中的数据或者新增model数据, 也可以修改view的跳转
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("---------方法后执行,在渲染之前--------------postHandle");
}
/**
* 如果preHandle返回false则会不会允许该方法
* 在视图渲染之后执行,相当于try catch finally 中finally,出现异常也一定会执行该方法
* @param ex Exception对象,在该方法中去做一些:记录异常日志的功能,或者清除资源
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("---------在视图渲染之后--------------afterCompletion");
}
}
spring-mvc.xml
<!--拦截器-->
<mvc:interceptors>
<!--直接配置一个Bean会拦截SpringMVC的所有请求-->
<bean class="cn.test.interceptors.MyInterceptor"></bean>
</mvc:interceptors>
多拦截器拦截顺序(取决于配置的顺序)
- 拦截器的preHandle是按照顺序执行的
- 拦截器的postHandle是按照逆序执行的
- 拦截器的afterCompletion是按照逆序执行的
- 如果执行的时候核心的业务代码出问题了,那么已经通过的拦截器的afterCompletion会接着执行。
(2)拦截器跟过滤器的区别
- 过滤器是基于函数回调的,而拦截器是基于java反射的
- 过滤器依赖于servlet容器,而拦截器不依赖与Servlet容器,拦截器依赖SpringMVC
- 过滤器几乎对所有的请求都可以起作用,而拦截器只能对SpringMVC请求起作用
- 拦截器可以访问处理方法的上下文,而过滤器不可以
拦截器与过滤器执行顺序
(3)使用拦截器实现登录权限拦截
CheckLoginInterceptor.class
public class CheckLoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
// 如果没有登录
if(StringUtils.isEmpty(session.getAttribute("username"))){
response.sendRedirect(request.getContextPath()+"/login");
return false;
}
else{
return true;
}
}
}
spring-mvc.xml
<!--拦截器-->
<mvc:interceptors>
<!--直接配置一个Bean会拦截SpringMVC的所有请求-->
<bean class="cn.test.interceptors.MyInterceptor"></bean>
<!--如果不是所有的请求都要拦截,可以加一个<mvc:interceptor>-->
<mvc:interceptor >
<!--需要拦截请求-->
<mvc:mapping path="/**"/>
<!--不需要拦截的请求(只会放过get请求)-->
<mvc:exclude-mapping path="/login"/>
<!--拦截器-->
<bean class="cn.test.interceptors.CheckLoginInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
7、国际化
新建jsp对应的国际化属性资源文件
login.properties
login_en_US.properties
welcomeinfo=welcome
username=USERNAME
password=PASSWORD
loginBtn=LOGIN
login_zh_CN.properties
welcomeinfo=欢迎进入
username=用户名
password=密码
loginBtn=登录
配置springmvc, 将国际化资源文件注入到springmvc中
<bean class="org.springframework.context.support.ResourceBundleMessageSource" id="messageSource">
<property name="basenames">
<array><value>i18n/login</value></array>
</property>
</bean>
(1)基于浏览器设置的语言切换国际化
其实SpringMVC中国际化的处理非常简单,就是按照浏览器所带来的语言信息决定的
默认情况下,SpringMVC 根据Accept-Language参数判断客户端的本地化类型。
当接受到请求时,SpringMVC 会在上下文中查找-一个本地化解析器(LocalResolver) ,找到后使用它获取请求所对应的本地化类型信息
Locale locale = request.getLocale();//获取浏览器的区域信息
在页面来调用属性资源文件
<spring:message code="password"></spring:message>
(2)通过超链接来切换国际化
更改默认本地化语言解析器LocaleResolver 改成SessionLocaleResolver
方式1: 使用SessionLocaleResolver 保持Locale的状态 会从session中获取Locale对象
<bean class="org.springframework.web.servlet.i18n.SessionLocaleResolver" id="localeResolver"></bean>
<a class="col-md-6" href="${basePath}/i18n/zh_CN">中文</a>
<a class="col-md-6" href="${basePath}/i18n/en_US">English</a>
@RequestMapping("/i18n/{language}_{country}")
public String changeLocale(@PathVariable("language") String language,
@PathVariable("country") String country,
HttpServletRequest request,
HttpServletResponse response,
@Autowired SessionLocaleResolver localeResolver){
Locale local=new Locale(language,country);
localeResolver.setLocale(request,response,local);
return "login";
}
方式2: 使用springmvc提供的拦截器,接收local参数(en_US、zh_CN) 设置session中去
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>
</mvc:interceptors>
<a class="col-md-6" href="${basePath}/i18n/?locale=zh_CN">中文_拦截器</a>
<a class="col-md-6" href="${basePath}/i18n/?locale=en_US">English_拦截器</a>
8、异常处理
在J2EE项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。
那么,能不能将所有类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护?答案是肯定的。下面将介绍使用Spring MVC统一处理异常的解决和实现过程。
(1)内置异常处理解析器
在SpringMVC中拥有一套非常强大的异常处理机制,SpringMVC通过HandlerExceptionResolver处理程序的异常,包括请求映射,数据绑定以及目标方法的执行时发生的异常。
在容器启动好,进入DispatcherServlet之后,会对HandlerExceptionResolver进行初始化操作,默认的从DispatcherServlet.properties中找到对应的异常处理类
#默认的处理类
org.springframework.web.servlet.HandlerExceptionResolver=
#处理@ExceptionHandler
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
#解析@ResponseStatus注释类型的异常
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
#按照不同类型分别对异常进行解析
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
AbstractHandlerMethodExceptionResolver是ExceptionHandlerExcpetionResolver父类
SimpleMappingExceptionResolver:通过配置的异常类和view的对应关系解析异常
当然,我们不了解上面过程也行,我们现在只需要知道怎么用就行
@ExceptionHandler:
通过@ExceptionHandler可以在方法中记录日志
如果@ExceptionHandler写在@Controller中只能处理当前控制器类的处理方法
@ExceptionHandler(Exception.class)
public ModelAndView handleException(Exception ex){
System.out.println("@Controller异常处理");
ModelAndView modelAndView=new ModelAndView();
modelAndView.setViewName("error");
modelAndView.addObject("ex",ex);
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ex.printStackTrace(pw);
System.out.println(sw.toString()); // 日志记录
return modelAndView;
}
(2)统一异常处理
@ControllerAdvice是Spring3.2提供的新注解,它是对Controller的增强,可对controller中被@RequestMapping注解的方法加一些逻辑处理
全局异常处理
全局数据绑定
全局数据预处理 全局异常 当异常没有满足上面两种情况, 会交给全局异常处理
在实际项目开发中,往往只需要一个全局异常就够了
@ControllerAdvice
public class GeneralExceptionHandler {
@ExceptionHandler(Exception.class)
public ModelAndView handleException(HttpServletRequest request,
HttpServletResponse reponse, Exception ex,
HandlerMethod handle){
System.out.println("全局异常处理");
// 如果当前请求是ajax就返回json
// 1.根据用户请求的处理方法,是否是一个返回json的处理方法
//RestController restAnnotation = handle.getClass().getAnnotation(RestController.class); // 获得类上面的某个注解
//ResponseBody responseBody = handle.getMethod().getAnnotation(ResponseBody.class);//获得方法上面的某个注解
// if(restAnnotation!=null || responseBody!=null){ }
// 2.可以根据请求头中的类型Content-Type包含application/json
if(request.getHeader("Accept").indexOf("application/json")>-1){
// 可以直接输出json reponse.getWriter().write(); 或者集成jackson
// 集成jackson的方式:
//ModelAndView 同时支持视图返回和json返回
// 这种方式就是返回json
ModelAndView modelAndView = new ModelAndView(new MappingJackson2JsonView());
// 通常会根据不同的异常返回不同的编码
modelAndView.addObject("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
modelAndView.addObject("message",ex.getMessage());
return modelAndView;
}
else{
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error");
modelAndView.addObject("ex", ex);
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ex.printStackTrace(pw);
System.out.println(sw.toString()); // 日志记录
return modelAndView;
}
}
}