3. springmvc的进阶用法

1 @ResponseBody

  1. 在前面使用SpringMVC时,Controller中的方法返回值会通过视图处理器ViewResolver处理为页面的URL,然后跳转到对应页面中
  2. 有时候我们希望Controller不进行页面跳转而是直接返回数据,这时候我们可以在方法上,添加注解:@ResponseBody,此时返回值会通过HTTP响应体直接发送给浏览器
package com.mashibing.controller;

import com.mashibing.bean.User;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class OtherController {
    @ResponseBody
    @RequestMapping("/testResponseBody")
    public String testResponseBody(){
        return "<h1>success</h1>";
    }
}
  1. 默认情况下,使用@ResponseBody返回的数据只能是String类型,其它类型返回时会出现异常,提示没有对应的类型转换器
  2. 阿里开源的fastjson可以实现Java对象和JSON的相互转换,引入对应的pom依赖后,SpringMVC会自动添加fastjson的转换器
  3. 此时返回的非字符串对象也可以转换为json格式字符串,返回的集合可以转为json数组
  4. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mashibing</groupId>
    <artifactId>springmv_ajax</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.0</version>
            <scope>provided</scope>
        </dependency>
        <!-- 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>
    </dependencies>
</project>
  1. springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="com.mashibing"></context:component-scan>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/page/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
    <mvc:default-servlet-handler></mvc:default-servlet-handler>
    <mvc:annotation-driven></mvc:annotation-driven>
</beans>
  1. JsonController.java
package com.mashibing.controller;

import com.mashibing.bean.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Controller
public class JsonController {

    @ResponseBody
    @RequestMapping("/json")
    public List<User> json(){
        List<User> list = new ArrayList<User>();
        list.add(new User(1,"zhangsan",12,"男",new Date(),"[email protected]"));
        list.add(new User(2,"zhangsan2",12,"男",new Date(),"[email protected]"));
        list.add(new User(3,"zhangsan3",12,"男",new Date(),"[email protected]"));
        return list;
    }
}
  1. User.java
package com.mashibing.bean;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;

import java.util.Date;

public class User {

    private Integer id;
    private String name;
    private Integer age;
    private String gender;
    @JsonFormat( pattern = "yyyy-MM-dd")
    private Date birth;
    @JsonIgnore
    private String email;

    public User() {
    }

    public User(Integer id, String name, Integer age, String gender, Date birth, String email) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.birth = birth;
        this.email = email;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                ", birth=" + birth +
                ", email='" + email + '\'' +
                '}';
    }
}

2 发送ajax请求获取json数据

  1. ajax.jsp
<%@ page import="java.util.Date" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script type="text/javascript" src="script/jquery-1.9.1.min.js"></script>
</head>
<%
    pageContext.setAttribute("ctp",request.getContextPath());
%>
<body>
<%=new Date()%>
<a href="${ctp}/json">获取用户信息</a>
<div>

</div>
<script type="text/javascript">
    $("a:first").click(function () {
        $.ajax({
            url:"${ctp}/json",
            type:"GET",
            success:function (data) {
                console.log(data)
                $.each(data,function() {
                    var user = this.id+"--"+this.name+"--"+this.age+"--"+this.gender+"--"+this.birth+"--"+this.email;
                    $("div").append(user+'<br/>');
                })
            }
        });
        return false;
    });
</script>
</body>
</html>

3 @RequestBody

  1. @RequestBody注释可以直接获取请求体中的数据,默认只能以String对象接收
package com.mashibing.controller;

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

@Controller
public class OtherController {

    @RequestMapping("/testRequestBody")
    public String testRequestBody(@RequestBody String body){
        System.out.println("请求体:"+body);
        return "success";
    }
}
  1. @RequestBody也可以根据Content-Type的类型,找对应的类型转换器,将请求体中数据转换为指定对象
  2. 当Content-Type为application/json,就会将请求体当做json的数据格式,转换为指定对象,由于涉及到json与对象互转,因此也需要依赖阿里开源的fastjson
  3. testOther.jsp
<%--
  Created by IntelliJ IDEA.
  User: root
  Date: 2020/3/13
  Time: 15:04
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<script type="text/javascript" src="script/jquery-1.9.1.min.js"></script>
<html>
<%
    pageContext.setAttribute("ctp",request.getContextPath());
%>
<head>
    <title>Title</title>
</head>
<body>
<form action="${ctp}/testRequestBody" method="post" enctype="multipart/form-data">
    <input name="username" value="zhangsan"><br>
    <input name="password" value="123456"><br>
    <input type="file" name="file" ><br>
    <input type="submit"><br>
</form>
<hr/>
<a href="${ctp}/testRequestJson">发送json数据</a>
<script type="text/javascript">
    $("a:first").click(function () {
        var user = {id:"1",name:"zhangsan",age:"12",gender:"男",birth:"2020-3-13",email:"[email protected]"};
        var userJson = JSON.stringify(user);
       $.ajax({
           url:"${ctp}/testRequestJson",
           type:"POST",
           data:userJson,
           contentType:"application/json",
           success:function (data) {
               alert(data);
           }
       });
       return false;
    });
</script>
</body>
</html>
  1. OtherController.java
package com.mashibing.controller;

import com.mashibing.bean.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class OtherController {

    @RequestMapping("/testRequestBody")
    public String testRequestBody(@RequestBody String body){
        System.out.println("请求体:"+body);
        return "success";
    }

    @RequestMapping("/testRequestJson")
    public String testRequestBody(@RequestBody User user){
        System.out.println("对象:"+user);
        return "success";
    }
}

4 HttpEntity对象接收请求参数

  1. 可以使用HttpEntity对象来接收请求中的参数
package com.mashibing.controller;

import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class OtherController {

    @RequestMapping("/testHttpEntity")
    public String testRequestBody(HttpEntity<String> httpEntity) {
        //1. 打印如下信息:[host:"localhost:8080", connection:"keep-alive", upgrade-insecure-requests:"1", user-agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36", sec-fetch-mode:"navigate", accept:"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", sec-fetch-site:"none", accept-encoding:"gzip, deflate, br", accept-language:"zh-CN,zh;q=0.9,zh-TW;q=0.8,en-US;q=0.7,en;q=0.6", cookie:"Idea-c6a6dd2=69b8d076-5b30-4324-8de0-0512ffa5b2b9; JSESSIONID=26BCB444F17750A980E2498DD81A3511"]
        System.out.println(httpEntity);
        return "success";
    }
}

5 使用RespsonseEntity定制响应内容

package com.mashibing.controller;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class OtherController {
    @RequestMapping("/testResponseEntity")
    public ResponseEntity<String> testResponseEntity(){
        String body = "<h1>hello</h1>";
        MultiValueMap<String,String> header = new HttpHeaders();
        header.add("Set-Cookie","name=zhangsan");
        //1. 可以自己定制响应相关信息,包括响应头、响应体、响应行中的状态码等
        return  new ResponseEntity<String>(body,header, HttpStatus.OK);
    }
}

6 文件下载

package com.mashibing.controller;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.io.FileInputStream;

@Controller
public class OtherController {

    @RequestMapping("/download")
    public ResponseEntity<byte[]> download(HttpServletRequest request) throws Exception {
        ServletContext servletContext = request.getServletContext();
        //1. 获取要下载文件的绝对路径,也就是在电脑上的位置
        String realPath = servletContext.getRealPath("/script/jquery-1.9.1.min.js");
        FileInputStream fileInputStream = new FileInputStream(realPath);

        byte[] bytes = new byte[fileInputStream.available()];
        fileInputStream.read(bytes);
        fileInputStream.close();
        HttpHeaders httpHeaders = new HttpHeaders();
        //2. 设置响应头中Content-Disposition为指定内容,这样浏览器才会下载该资源,与servlet中方法一致
        httpHeaders.set("Content-Disposition","attachment;filename=jquery-1.9.1.min.js");
        //3. 设置响应体中内容就是文件的字节数组
        return  new ResponseEntity<byte[]>(bytes,httpHeaders,HttpStatus.OK);
    }
}

7 文件上传

  1. Spring MVC 为文件上传提供了直接的支持,这种支持是通过即插即用(方法中定义了就会有该对象,没定义就没有)的MultipartResolver实现的
  2. Spring MVC没有为该类提供实现,我们常用Commons FileUpload中的CommonsMultipartResovler来作为其具体实现,需导入如下pom依赖
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>
  1. Spring MVC 上下文中默认没有装配 MultipartResovler,因此默认情况下不能处理文件的上传工作,如果想使用 Spring 的文件上传功能,需在applicationContext中配置 multipartResolver
<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. index.jsp
<%--
  Created by IntelliJ IDEA.
  User: root
  Date: 2020/3/13
  Time: 17:00
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <!--1. 必须加multipart才能完成上传操作-->
  <form action="testUpload" method="post" enctype="multipart/form-data">
    文件: <input type="file" name="file"/><br><br>
    描述: <input type="text" name="desc"/><br><br>
    <input type="submit" value="提交"/>
  </form>
  </body>
</html>

  1. UploadHandler.java
package com.mashibing.controller;

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

import java.io.File;
import java.io.IOException;

@Controller
public class UploadHandler {

    @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);
        //1. 获取上传的文件名称
        System.out.println("OriginalFilename : " + multipartFile.getOriginalFilename());
        //2. 将这个文件最后放到D:\\file
        multipartFile.transferTo(new File("E:\\file\\"+multipartFile.getOriginalFilename()));
        return "success"; //增加成功页面: /views/success.jsp
    }
}

7.1 多文件上传

  1. index.jsp
<%--
  Created by IntelliJ IDEA.
  User: root
  Date: 2020/3/13
  Time: 17:00
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <form action="testUpload" method="post" enctype="multipart/form-data">
    文件: <input type="file" name="file"/><br><br>
    文件: <input type="file" name="file"/><br><br>
    文件: <input type="file" name="file"/><br><br>
    描述: <input type="text" name="desc"/><br><br>
    <input type="submit" value="提交"/>
  </form>
  </body>
</html>

  1. UploadHandler.java
package com.mashibing.controller;

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

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

@Controller
public class UploadHandler {

    @RequestMapping(value = "/testUpload", method = RequestMethod.POST)
   	//1. 如果页面中配置了多个名为file的属性,那么可以将MultipartFile改为数据对文件进行接收
    public String testUpload(@RequestParam(value = "desc", required = false) String desc, @RequestParam("file") MultipartFile[] multipartFile) throws IOException {
        System.out.println("desc : " + desc);
        for (MultipartFile file : multipartFile) {
            //2. 防止虽然写了三个file,但实际只上传了两个文件
            if (!file.isEmpty()) {
                System.out.println("OriginalFilename : " + file.getOriginalFilename());
                file.transferTo(new File("E:\\file\\" + file.getOriginalFilename()));
            }
        }
        return "success"; 
    }
}

8 Springmvc拦截器

  1. SpringMVC提供了拦截器机制,允许运行目标方法之前进行一些拦截工作或者目标方法运行之后进行一下其他相关的处理。自定义的拦截器必须实现HandlerInterceptor接口
  2. 该接口提供如下几个抽象方法
    1. preHandle()
      1. 业务处理器处理请求之前调用,一般用于处理request
      2. 如果程序员决定该拦截器对请求进行拦截后还要调用其他拦截器,或业务处理器去进行处理,则返回true,如果程序员决定不需要再调用其他的组件去处理请求,则返回false
    2. postHandle()
      1. 业务处理器处理请求后、页面跳转前调用
    3. afterCompletion()
      1. 跳转的页面执行完成后被调用
      2. 一般在该方法中进行一些资源清理的操作
      3. 如果执行到方法中出现异常,那么后续流程不会处理但是afterCompletion方法会执行

8.1 自定义第一个拦截器

  1. MyFirstInterceptor.java
package com.mashibing.interceptor;


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

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

public class MyFirstInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println(this.getClass().getName()+"------->preHandle");
        return true;
    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println(this.getClass().getName()+"------->postHandle");
    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println(this.getClass().getName()+"------->afterCompletion");
    }
}

  1. TestInterceptorController.java
package com.mashibing.controller;

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

@Controller
public class TestInterceptorController {

    @RequestMapping("test01")
    public String test01(){
        System.out.println("test01");
        return "success";
    }
}
  1. springmvc.xml
<mvc:interceptors>
    <bean class="com.mashibing.interceptor.MyFirstInterceptor"></bean>
</mvc:interceptors>
  1. success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<% System.out.println("success.jsp");%>
success
</body>
</html>

8.2 定义多个拦截器

  1. MySecondInterceptor.java:另一个拦截器
package com.mashibing.interceptor;


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

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

public class MySecondInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println(this.getClass().getName()+"------->preHandle");
        return true;
    }
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println(this.getClass().getName()+"------->postHandle");
    }
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println(this.getClass().getName()+"------->afterCompletion");
    }
}

  1. springmvc.xml
```xml
<mvc:interceptors>
    <bean class="com.mashibing.interceptor.MySecondInterceptor"></bean>
    <bean class="com.mashibing.interceptor.MyFirstInterceptor"></bean>
</mvc:interceptors>
  1. 执行顺序
    1. 哪个拦截器先执行取决于配置的顺序
    2. 拦截器的preHandle是按照顺序执行的
    3. 拦截器的postHandle是按照逆序执行的
    4. 拦截器的afterCompletion是按照逆序执行的
    5. 如果执行的时候核心的业务代码出问题了,那么已经通过的拦截器的afterCompletion会接着执行
com.mashibing.interceptor.MySecondInterceptor------->preHandle
com.mashibing.interceptor.MyFirstInterceptor------->preHandle
test01
com.mashibing.interceptor.MyFirstInterceptor------->postHandle
com.mashibing.interceptor.MySecondInterceptor------->postHandle
success.jsp
com.mashibing.interceptor.MyFirstInterceptor------->afterCompletion
com.mashibing.interceptor.MySecondInterceptor------->afterCompletion

9 拦截器跟过滤器的区别

  1. 拦截器的本质实际上就是利用AOP为业务处理器处理的请求前后加上了内容
  2. 拦截器与过滤器的执行流程
    在这里插入图片描述
  3. 拦截器和过滤器的包含关系
    在这里插入图片描述

10 SpringMVC国际化

  1. 在日常工作中,如果你的网站需要给不同语言地区的人进行查看,此时就需要使用国际化操作,springmvc的国际化操作比较容易
  2. 浏览器默认按请求头中Accept-Language决定使用中文页面还是英文页面,而Accept-Language的值和浏览器的默认语言有关
  3. 在DispatcherServlet中会包含一个LocaleResolver属性,保存获取区域信息的解析器
  4. 该值默认从DispatcherServlet.properties中读取,为一个AcceptHeaderLocaleResolver对象
  5. 该对象的resolveLocale方法, 默认是从Accept-Language属性中读取,如果该属性值不存在,从request.getLocale中读取,而request.getLocale值默认也从Accept-Language属性中读取
  6. 而ResourceBundleMessageSource默认以AcceptHeaderLocaleResolver获取到的语言,匹配properties文件

10.1 在程序中可以获取Locale的相关信息

package com.mashibing.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Locale;

@Controller
public class I18nController {

    @Autowired
    private MessageSource messageSource;

    @RequestMapping("i18n")
    public String i18n(Locale locale){
        //打印:en_US
        System.out.println(locale);
        String username = messageSource.getMessage("username", null, locale);
        System.out.println(username);
        return "login";
    }
}

10.2 一个国际化配置案例

  1. index.jsp:方便发送i18n请求,其实不写这层也可以
<%--
  Created by IntelliJ IDEA.
  User: root
  Date: 2020/3/13
  Time: 17:00
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <a href="i18n">国际化页面登录</a>
  </body>
</html>

  1. login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<!--1. fmt标签中,key的值,为properties文件中的key值,最后页面中显式的为该key对应的value值-->
<h1><fmt:message key="welcomeinfo"/></h1>
<form action="login" method="post">
    <fmt:message key="username"/>: <input type="text" name="username"/><br><br>
    <fmt:message key="password"/>: <input type="password" name="password"/><br><br>
    <input type="submit" value="<fmt:message key="loginBtn"/>"/>
</form>
</body>
</html>
  1. 上面使用了jstl标签库,因此需要导入pom依赖
<!-- https://mvnrepository.com/artifact/org.apache.taglibs/taglibs-standard-impl -->
<dependency>
    <groupId>org.apache.taglibs</groupId>
    <artifactId>taglibs-standard-impl</artifactId>
    <version>1.2.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>
  1. I18nController.java
package com.mashibing.controller;

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

@Controller
public class I18nController {

    @RequestMapping("i18n")
    public String i18n(){
        return "login";
    }
}
  1. 需要在classpath中建立properties文件
  2. login_en_US.properties
welcomeinfo=welcome to mashibing.com
username=USERNAME
password=PASSWORD
loginBtn=LOGIN
  1. login_zh_CN.properties
welcomeinfo=欢迎进入马士兵教育
username=用户名
password=密码
loginBtn=登录
  1. springmvc.xml
<!--1. 这个类的作用就是读取资源属性文件.properties,然后根据AcceptHeaderLocaleResolver与.properties文件名进行匹配-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
   <!--2. 只配置以login开头的properties文件-->
    <property name="basename" value="login"></property>
</bean>

10.3 通过超链接来切换国际化

10.3.1 利用自定义LocaleResolver
  1. login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1><fmt:message key="welcomeinfo"/></h1>
<form action="login" method="post" >
    <fmt:message key="username"/>: <input type="text" name="username"/><br><br>
    <fmt:message key="password"/>: <input type="password" name="password"/><br><br>
    <input type="submit" value="<fmt:message key="loginBtn"/>"/>
    <!--1. 传入一个local属性,相当于发起uri?locale=zh_CN这种请求-->
    <a href="i18n?locale=zh_CN">中文</a><a href="i18n?locale=en_US">英文</a>
</form>
</body>
</html>

  1. MyLocaleResolver.java:自定义LocaleResolver
package com.mashibing;

import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
public class MyLocaleResolver implements LocaleResolver {
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        Locale locale = null;
        //1. 自定义的LocaleResolver中,不再默认返回request.getLocale,而是优先使用url中传入的locale属性
        String localeStr = request.getParameter("locale");
        if(localeStr!=null && ! "".equals(localeStr)){
            locale = new Locale(localeStr.split("_")[0],localeStr.split("_")[1]);
        }else{
            locale = request.getLocale();
        }
        return locale;
    }
	//2. 该方法不重要,使用其父类默认的实现即可
    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
        throw new UnsupportedOperationException(
                "Cannot change HTTP accept header - use a different locale resolution strategy");
    }
}
  1. springmvc.xml
</bean>
   <!--1. 配置为使用自定义的LocaleResolver-->
   <bean id="localeResolver" class="com.mashibing.MyLocaleResolver">
</bean>
10.3.2 SpringMVC中自带的SessionLocaleResolver
  1. 该LocaleResolver优先使用session中属性名为SessionLocaleResolver.LOCALE的属性,作为返回的Locale对象
  2. I18nController.java
package com.mashibing.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

import javax.servlet.http.HttpSession;
import java.util.Locale;

@Controller
public class I18nController {
    @Autowired
    private MessageSource messageSource;
    @RequestMapping("i18n")
    public String i18n(@RequestParam(value = "locale",defaultValue = "zh_CN") String localeStr,Locale locale, HttpSession session){
        Locale l = null;
        if(localeStr!=null && ! "".equals(localeStr)){
            l = new Locale(localeStr.split("_")[0],localeStr.split("_")[1]);
        }else{
            l = locale;
        }
        //1. 只需要向session中加入名为SessionLocaleResolver.LOCALE的属性,ResourceBundleMessageSource就会使用该属性值对应的Locale对象匹配properteis文件了
        session.setAttribute(SessionLocaleResolver.class.getName() + ".LOCALE",l);
        return "login";
    }
}
  1. springmvc.xml
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"></bean>
</beans>
10.3.3 LocaleChangeInterceptor拦截器实现
  1. springmvc.xml
<!--1. 通过配置LocaleChangeInterceptor,我们可以动态改变本地语言。它会检测请求中的参数并且改变地区信息。它调用LoacalResolver.setLocal()进行配置-->
<mvc:interceptors>
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>
</mvc:interceptors>
  1. I18nController.java
package com.mashibing.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

import javax.servlet.http.HttpSession;
import java.util.Locale;

@Controller
public class I18nController {

    @Autowired
    private MessageSource messageSource;

    @RequestMapping("i18n")
    public String i18n(@RequestParam(value = "locale",defaultValue = "zh_CN") String localeStr,Locale locale, HttpSession session){
        return "login";
    }
}

11 SpringMVC异常处理机制

可以解决当报错时,弹出特别难看的404页面的问题,对异常统一进行处理

11.1 异常处理类的加载

  1. 在SpringMVC中拥有一套非常强大的异常处理机制,SpringMVC通过HandlerExceptionResolver处理程序的异常,包括请求映射,数据绑定以及目标方法的执行时发生的异常
  2. Spring MVC有两种加载异常处理类的方式
    1. 根据类型,这种情况下,会加载spring配置文件中所有实现了ExceptionResolver接口的bean
    2. 根据名字,这种情况下会加载spring配置文件中,名字为handlerExceptionResolver的bean
  3. 不管使用那种加载方式,如果在spring配置文件中中没有找到异常处理bean,那么Spring MVC会加载默认的异常处理bean。默认的异常处理bean定义在DispatcherServlet.properties中
org.springframework.web.servlet.HandlerExceptionResolver=
#1. 该处理器会使用@ExceptionHandler注解和@ControllerAdvice定义的方法处理异常
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
#2. 处理使用了ResponseStatus注解的异常,该处理器会根据注解的内容,返回相应的HTTP Status Code和内容给客户端
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
#3. 处理Spring定义的各种标准异常,将其转化为相对应的HTTP Status Code
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

11.2 Spring MVC对异常处理bean的使用

  1. Spring MVC把请求映射和处理过程放到try catch中,捕获到异常后,使用异常处理bean依次进行处理
  2. 所有异常处理bean按照order属性排序,在处理过程中,遇到第一个成功处理异常的异常处理bean之后,不再调用后续的异常处理bean

11.3 最佳实践

  1. 如果自定义异常类,应加上@ResponseStatus,这样至少能够返回指定响应码和自定义的报错信息
  2. 非@ResponseStatus修饰的异常,一般使用@ExceptionHandler+@ControllerAdvice,或者通过配置SimpleMappingExceptionResolver,来为整个Web应用提供统一的异常处理
  3. 如果应用中有些异常处理方式,只针对特定的Controller使用,那么在这个Controller中使用@ExceptionHandler注解
  4. 不要使用过多的异常处理方式,不然的话,维护起来会很苦恼,因为异常的处理分散在很多不同的地方

11.4 @ExceptionHandler

  1. index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
<a href="exception1">自己处理异常</a>
  </body>
</html>
  1. ExceptionController.java
package com.mashibing.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.jws.WebParam;

@Controller
public class ExceptionController {

    @RequestMapping("exception1")
    public String exception(){
        System.out.println("exception.......");
        System.out.println(10/0);
        return "success";
    }

    @ExceptionHandler(value = {ArithmeticException.class})
    public ModelAndView handlerException1(Exception exception){
        System.out.println("handlerException1........");
        ModelAndView mv = new ModelAndView();
        mv.setViewName("error");
        mv.addObject("ex",exception);
        return mv;
    }

    @ExceptionHandler(value = {Exception.class})
    public ModelAndView handlerException2(Exception exception){
        System.out.println("handlerException2........");
        ModelAndView mv = new ModelAndView();
        mv.setViewName("error");
        mv.addObject("ex",exception);
        return mv;
    }
}
  1. error.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
我的出错页面:
错误信息:${ex}
</body>
</html>
  1. 在一个类中可能会包含多个@ExceptionHandler修饰的异常的处理方法,在不同的方法上可以使用不同范围的异常,在查找的时候会优先调用范围小的方法进行异常处理

11.5 @ControllerAdvice

  1. 某一个类中定义的@ExceptionHandler只能处理当前类的异常信息,无法处理其他类中的异常
  2. 可以使用@ControllerAdvice注解表示全局异常处理类,可以处理其他类中产生的异常
  3. 如果同时使用了@ExceptionHandler和@ControllerAdvice,每次进行异常处理时,如果本类跟全局都有相关异常的处理,那么会优先使用本类的,异常从小到大,再使用全局的,异常从小到大
  4. MyGlobalExceptionHandler
package com.mashibing.controller;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

@ControllerAdvice
public class MyGlobalExceptionHandler {
    @ExceptionHandler(value = {ArithmeticException.class})
    public ModelAndView handlerException1(Exception exception){
        System.out.println("handlerException1........");
        ModelAndView mv = new ModelAndView();
        mv.setViewName("error");
        mv.addObject("ex",exception);
        return mv;
    }
}

11.6 @ResponseStatus的使用

注意默认配置中,如果有@ExceptionHandler和@ControllerAdvice,会使用它们对异常进行处理

11.6.1 在方法上使用
  1. @ResponseStatus可以标注到方法上,但是标注在方法之后,如果value设置的值不是HttpStatus.OK,会导致即使方法中没有报错也会显式报错页面,因此一般不在方法上使用
  2. ExceptionController
package com.mashibing.controller;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;

import javax.jws.WebParam;

@Controller
public class ExceptionController {

    @ResponseStatus(reason = "不知道什么原因,反正错误",value = HttpStatus.NOT_ACCEPTABLE)
    @RequestMapping("exception1")
    public String exception(){
        System.out.println("exception.......");
        return "success";
    }
}
  1. 请求结果
    在这里插入图片描述
11.6.2 在自定义异常上使用
  1. UserNameException.java
package com.mashibing.controller;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
//reason为页面返回的消息详情,value为消息错误代码
@ResponseStatus(reason = "名字不是admin",value = HttpStatus.NOT_ACCEPTABLE)
public class UserNameException extends RuntimeException {
}
  1. ExceptionController.java
package com.mashibing.controller;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;

import javax.jws.WebParam;

@Controller
public class ExceptionController {
    @RequestMapping("exception1")
    public String exception(){
        System.out.println("exception.......");
        return "success";
    }
    @RequestMapping("exception2")
    public String exception2(String username){
        System.out.println("exception2222.......");
        if ("admin".equals(username)){
            return "success";
        }else{
            throw new UserNameException();
        }
    }
}

11.7 DefaultHandlerExceptionResolver处理springmvc自定义的异常

  1. index.jsp
<%--
  Created by IntelliJ IDEA.
  User: root
  Date: 2020/3/13
  Time: 17:00
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>$Title$</title>
</head>
<body>
<a href="exception3">Springmvc自己异常处理</a>
</body>
</html>
  1. ExceptionController.java
package com.mashibing.controller;

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

@Controller
public class ExceptionController {
    //1. 此处由于只接收post请求,因此我们jsp页面中发送的get请求将无法被接收,会抛出springmvc自定义的一个异常
    //2. 我们可以借此观察DefaultHandlerExceptionResolver如何处理这种异常
    @RequestMapping(value = "exception3",method = RequestMethod.POST)
    public String exception3(String username){
        System.out.println("exception3.......");
        return "success";
    }
}

  1. 请求结果
    在这里插入图片描述
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章