springmvc高级

文章介绍了SpringMVC中的拦截器机制,包括原理、配置和执行顺序,以及在异常处理和权限校验中的应用。接着讨论了JSON数据的交互,包括如何接收和返回JSON数据。此外,还涵盖了文件上传的处理和RESTful风格的API设计,以及SpringMVC中的静态资源处理和国际化支持。
摘要由CSDN通过智能技术生成

springmvc高级

1.springmvc拦截器

1.1 什么是拦截器

  • 回顾JavaWeb中的过滤器Filter:

    过滤器实际上就是对web资源进行拦截,做一些处理后再交给下一个过滤器或servlet处理,
    通常都是用来拦截request进行处理的,也可以对返回的response进行拦截处理。

    典型使用场景:在Servlet中我们一般都会对request和response中的字符集编码进行配置,如果Servlet过多字符集编码发生变化时修改会很麻烦,这些通用的字符集编码配置等工作我们可以放到Filter中来实现。

    – 我们在实现spring中文乱码时也使用了过滤器。

  • 过滤器(Filter)与拦截器(Interceptor)异同:

Spring的拦截器与Servlet的Filter有相似之处:

二者都是AOP编程思想的体现,都能实现权限检查、日志记录等。

不同的是:
1.使用范围不同:Filter是Servlet规范规定的,只能用于Web程序中。而拦截器既可以用于Web程序,也可以用于Application、Swing程序中。
2.规范不同:Filter是在Servlet规范中定义的,是Servlet容器支持的。而拦截器是在Spring容器内的,是Spring框架支持的。

3.使用的资源不同:同其他的代码块一样,拦截器也是一个Spring的组件,归Spring管理,配置在Spring文件中,因此能使用Spring里的任何资源、对象,例如Service对象、数据源、事务管理等,通过IoC注入到拦截器即可;而Filter则不能。

4.深度不同:Filter只在Servlet前后起作用。而拦截器能够深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。所以在Spring构架的程序中,要优先使用拦截器。

拦截器是spring提供的一个特殊的组件,当DispatcherServlet收到请求之后,如果有拦截器,会先调用拦截器,然后调用响应的处理器(Handler)

  • 拦截器的使用场景:

    1、日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。

    2、权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面;

    3、性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);

    4、通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。

    5、OpenSessionInView:如hibernate,在进入处理器打开Session,在完成后关闭Session。

1.2 拦截器原理

SpringMVC中的拦截器需要实现HandlerInterceptor。

源码

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;

public interface HandlerInterceptor {
 
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

它有三个抽象方法:

preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法

postHandle:控制器方法执行之后执行postHandle(),它的入参还含有modelAndView,因此可以对返回视图进行干涉或者处理。

afterComplation:处理完视图和模型数据,渲染视图完毕之后执行afterComplation()。但仅调用处理器执行链中preHandle返回true的拦截器的afterCompletion

1.3 演示拦截器执行顺序

  • 代码演示

    • 拦截器 MyInterceptor2代码一致只改个名字

      ```
      package com.liyw.interceptor;
      
      import org.springframework.lang.Nullable;
      import org.springframework.web.servlet.ModelAndView;
      import org.springframework.web.servlet.HandlerInterceptor;
      
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      
      public class MyInterceptor implements HandlerInterceptor {
      
          public MyInterceptor() {
          }
      
         @Override
          public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
              System.out.println("preHandle===");
              return true;
          }
      
          @Override
          public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
              System.out.println("postHandle===");
          }
          @Override
          public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
              System.out.println("afterCompletion===");
          }
      
      }
      ```
      
      • controller

        @Controller
        public class InterceptorController {
        
            @RequestMapping("/interceptor")
            public String customException(Model model, @RequestParam(value="id",required=true) Integer id)throws Exception {
                System.out.println("id"+id);
                return "success";
            }
        
        }
        
      • servlet.xml

      参数说明:

      1)mvc:mapping 拦截器路径配置,如果需要拦截所有的请求路径为/**

      2)mvc:exclude-mapping 拦截器不需要拦截的路径

<!--配置interceptor-->
<mvc:interceptors>
    <mvc:interceptor>
        <!--1和2的顺序不能变-->
        <!--1--><mvc:mapping path="/interceptor"/>
        <!--2--><bean class="com.liyw.interceptor.MyInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <!--1和2的顺序不能变-->
        <!--1--><mvc:mapping path="/interceptor"/>
        <!--2--><bean class="com.liyw.interceptor.MyInterceptor2"/>
    </mvc:interceptor>

</mvc:interceptors>

输出:

preHandle
preHandle2
id111
postHandle2
postHandle
afterCompletion2
afterCompletion

说明执行顺序+多个拦截器chain的执行顺序:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lNC9wLNq-1688372883209)(springmvc高级.assets/image-20211111172530677.png)]

  • 多个拦截器的执行顺序

    正常流程:

    a>若每个拦截器的preHandle()都返回true

    此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关:

    preHandle()会按照配置的顺序执行,而postHandle()和afterComplation()会按照配置的反序执行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eR0plenQ-1688372883210)(springmvc高级.assets/image-20211112090119517.png)]

1.4 异常流程拦截器执行

  • 代码

    • 拦截器:

      MyInterceptor 3和MyInterceptor 4 与 之前的 MyInterceptor 1和MyInterceptor 2一样,

      只是在MyInterceptor 4的preHandle方法返回false:

      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
          System.out.println("preHandle4===");
          response.getWriter().print("break");//流程中断的话需要我们进行响应的处理  
          return false;//返回false表示流程中断
      }
      
    • servlet.xml

      拦截器改成3和4

    • 输出结果:

      preHandle3==
      preHandle4==
      afterCompletion3==

    此处我们可以看到只有MyInterceptor3的afterCompletion执行,否和图2的中断流程。

    而且页面上会显示我们在MyInterceptor4 preHandle 直接写出的响应“break”。

b>若某个拦截器的preHandle()返回了false

preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false的拦截器之前的拦截器的afterComplation()会执行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5rLIINKd-1688372883210)(springmvc高级.assets/image-20211112160505735.png)]

1.5 拦截器应用案例开发

  • 性能监控案例:

场景需求:如记录一下请求的处理时间,得到一些慢请求(如处理时间超过500毫秒),从而进行性能改进,一般的反向代理服务器如apache都具有这个功能,但此处我们演示一下使用拦截器怎么实现。

实现分析:

1、在进入处理器之前记录开始时间,即在拦截器的preHandle记录开始时间;

2、在结束请求处理之后记录结束时间,即在拦截器的afterCompletion记录结束实现,并用结束时间-开始时间得到这次请求的处理时间。

问题:

我们的拦截器是单例,因此不管用户请求多少次都只有一个拦截器实现,即线程不安全,那我们应该怎么记录时间呢?

解决方案:

使用ThreadLocal,它是线程绑定的变量,提供线程局部变量(一个线程一个ThreadLocal,A线程的ThreadLocal只能看到A线程的ThreadLocal,不能看到B线程的ThreadLocal)

NamedThreadLocal:Spring提供的一个命名的ThreadLocal实现。

在测试时需要把stopWatchHandlerInterceptor放在拦截器链的第一个,这样得到的时间才是比较准确的。

代码:

package com.liyw.interceptor;

import org.springframework.core.NamedThreadLocal;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

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

public class StopWatchHandlerInterceptor implements HandlerInterceptor {
    private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("StopWatch-StartTime");

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        long beginTime = System.currentTimeMillis();// 1、开始时间
        startTimeThreadLocal.set(beginTime);// 线程绑定变量(该数据只有当前请求的线程可见)
        return true;// 继续流程
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        long endTime = System.currentTimeMillis();// 2、结束时间
        long beginTime = startTimeThreadLocal.get();// 得到线程绑定的局部变量(开始时间)
        long consumeTime = endTime - beginTime;// 3、消耗的时间
        if (consumeTime > 500) {// 此处认为处理时间超过500毫秒的请求为慢请求
            System.out.println(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
        }
    }


}
  • 用户登录状态校验

1.6 其他

拦截器可能会造成拦截静态资源的问题,因此需要对其进行配置。

建议方案:在web.xml中增加对静态资源的处理(放在DispatcherServlet的注册之前)。如有更多类型的静态资源,请根据实际情况配置。

<servlet-mapping>
  <servlet-name>default</servlet-name>
  <url-pattern>*.js</url-pattern>
  <url-pattern>*.html</url-pattern>
  <url-pattern>*.css</url-pattern>
  <url-pattern>/assets/*"</url-pattern>
  <url-pattern>/images/*</url-pattern>
</servlet-mapping>

2. json数据交互

2.1 什么是json

JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式,目前使用特别广泛。

常用于远程接口传输,http传输json数据,非常方便页面进行提交/请求结果解析。

  • 优点:

简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。

易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

2.2 JSON和JavaScript 的关系

在 JavaScript 语言中,一切都是对象。因此,任何JavaScript 支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等。看看他的要求和语法格式:

1.对象表示为键值对,数据由逗号分隔

2.花括号保存对象

3.方括号保存数组

JSON 键值对是用来保存 JavaScript 对象的一种方式,和 JavaScript 对象的写法也大同小异,键/值对组合中的键名写在前面并用双引号 “” 包裹,使用冒号 : 分隔,然后紧接着值:

{“name”: “QinJiang”}
{“age”: “3”}
{“sex”: “男”}
很多人搞不清楚 JSON 和 JavaScript 对象的关系,甚至连谁是谁都不清楚。其实,可以这么理解:

JSON 是 JavaScript 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。

var obj = {a: ‘Hello’, b: ‘World’}; //这是一个对象,注意键名也是可以使用引号包裹的
var json = ‘{“a”: “Hello”, “b”: “World”}’; //这是一个 JSON 字符串,本质是一个字符串

JSON 和 JavaScript 对象互转

要实现从JSON字符串转换为JavaScript 对象,使用 JSON.parse() 方法:

var obj = JSON.parse('{"a": "Hello", "b": "World"}');
//结果是 {a: 'Hello', b: 'World'}

要实现从JavaScript 对象转换为JSON字符串,使用 JSON.stringify() 方法:

var json = JSON.stringify({a: 'Hello', b: 'World'});
//结果是 '{"a": "Hello", "b": "World"}'
  • 代码测试:

新建一个html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JSON&JS</title>
</head>
<body>

<script type="text/javascript">
    //编写一个js的对象
    var user = {
        name:"张三",
        age:20,
        sex:"男"
    };
    //将js对象转换成json字符串
    var str = JSON.stringify(user);
    console.log(str);

    //将json字符串转换为js对象
    var user2 = JSON.parse(str);
    console.log(user2.age,user2.name,user2.sex);

</script>

</body>
</html>

控制台结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KMm4Fb4T-1688372883210)(springmvc高级.assets/image-20211110164425026.png)]

2.3 Controller返回JSON数据

2.3.1 导入包

SpringMVC默认用MappingJacksonHttpMessageConverter对json数据进行转换,需要加入jackson的包。

当然工具不止这一个,比如还有阿里巴巴的 fastjson 等等。

我们这里使用Jackson,使用它需要导入它的jar包;

<dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.12.1</version>
</dependency>
2.3.2 配置

按照springmvc架构配置好web.xml (servlet+servlet-mapping+filter)和 servlet.xml(视图解析+注解扫描)

在SpringMVC的核心配置文件中开启mvc的注解驱动,此时在HandlerAdaptor中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter,可以将响应到浏览器的Java对象转换为Json格式的字符串。

servlet.xml里添加这一行:

<mvc:annotation-driven/>

如果有报错,再追加如下详细配置(旧版本必配,新版本默认可不配):

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
    </mvc:message-converters>
</mvc:annotation-driven>
2.3.3 返回json数据 - @ResponseBody

User.java:

public class User {
    private String userName;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPass() {
        return userPass;
    }

    public void setPassword(String password) {
        this.userPass = password;
    }

    private String userPass;

    @Override
    public String toString() {
        return "User{" +
                "userName='" + userName + '\'' +
                ", userPass='" + userPass + '\'' +
                '}';
    }

    public User(String userName, String userPass) {
        this.userName = userName;
        this.userPass = userPass;
    }


    //jackson的反序列化需要无参构造函数,不加会报错
    public User() {
    }
}

controller:

package com.liyw.controller;


import com.liyw.pojo.User;
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;


//解释json转换
@Controller
public class UserController {


    @RequestMapping("/json")
    public String jsonPage(){
        return "jsonDemo";//经过视图解析器后去转向/WEB-INF/jsp/user.jsp
    }


//    http://localhost:8080/testResponseUser
//   结果 {"userName":"AMY","userPass":"11111"}
    @RequestMapping("/testResponseUser")
    @ResponseBody
    public User json1()  {
        //创建一个对象
        User user = new User("AMY","11111");
        //由于@ResponseBody注解,这里会将对象转成json格式返回;十分方便
        return user;
    }


}

2.3.4 接受json数据-@RequestBody
  • controller

新增一个方法,接受前端提交的json格式的用户提交信息,并以json的结构返回:

//  json收+json发
    @RequestMapping("/testAjax")
    @ResponseBody
    public User testAjax(@RequestBody User user){        System.out.println("username:"+user.getUserName()+",password:"+user.getUserPass());
        user.setUserName("后端返回的用户名");
        return user;
   }

提示:除了接受用户的方法,我们还需要一个跳转到对应html界面执行操作的方法。

@RequestMapping("/toUserAddJsonPage")
    public String toUserAddJsonPage(){
        return "userAddJsonPage";
    }
  • 核心配置文件.xml

    我们想在前端写一个html来完成这里的前后端交互,因此需要修改配置文件中视图解析器的配置。修改后的html文件应位于/WEB-INF/html/xx…html位置

<!--3.视图解析器:DispatcherServlet给他的ModelAndView-->
    <!-- 对转向页面的路径解析。 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          id="InternalResourceViewResolver">
        <!--前缀-->
        <property name="prefix" value="/WEB-INF/html/"/>
        <!--后缀-->
        <property name="suffix" value=".html"/>
    </bean>

增加配置:

<!-- 
   处理静态资源,例如html、js、css、jpg
  若只设置该标签,则只能访问静态资源,其他请求则无法访问
  此时必须设置<mvc:annotation-driven/>解决问题
 -->
<mvc:default-servlet-handler/>
  • html

userAddJsonPage.html

注:

关于静态资源配置在下一部分,这一部分js的可以引用外部网站的js源,在下一节课讲完之后再改成静态资源获取

版本一:使用原生html表单+ajax提交用户表单数据

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Ajax前后端交互</title>
    <script type="text/javascript" src="http://code.jquery.com/jquery-3.5.1.min.js"></script>
</head>
<body>

<!--html form表单-->
<form>
    <p>用户名:</p>
    <input type="text" id="userName">
    <p>密码:</p>
    <input type="text" id="userPass">
    <p><button type="button" onclick="sendJson()">点击我,发送json数据</button></p>
</form>


<script type="text/javascript">
    function sendJson() {
    //   获取参数
        console.log("用户名:"+$("#userName").val())
        console.log("密码:"+$("#userPass").val())
        //定义一个js对象
        var formData = {"userName":$("#userName").val(),"userPass":$("#userPass").val()}
    //    ajax
        $.ajax({
            type:"post",
            url:"./testAjax",
            data:JSON.stringify(formData),//转换成json数据结构
            contentType:"application/json",//发送数据的结构  -- 配合后端@RequestBody
            dataType:"json",//返回数据类型  -- 配合后端@ResponseBody
            success:function (result) {
            //    成功的回调
            //    拆解返回的数据
                var newName = result.userName;
                alert("后端返回的用户名:"+newName);
            }
        })


    }
</script>

</body>
</html>

版本二:使用vue(vue.js)+axios提交用户表单数据

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vue+axios前后端交互</title>
    <script src="https://unpkg.com/vue@3"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>

<!--html form表单-->
<form id="app">
    <p>用户名:</p>
    <input type="text" id="userName" v-model="userName">
    <p>密码:</p>
    <input type="text" id="userPass" v-model="userPass">
    <p>
        <button type="button" @click="sendJson">点击我,发送json数据</button>
    </p>
</form>

<script type="text/javascript">
    var app = Vue.createApp({
        data() {
            return {
                userName: '',
                userPass: '',
            }
        },
        methods:{
            sendJson(){
            //    获取参数
                console.log("用户名:"+this.userName)
                console.log("密码:"+this.userPass)
            //    配合axios发送
                var params = {'userName':this.userName,'userPass':this.userPass}
                axios.post('./testAjax',params).then(response=>{
                    console.log(response.data)
                    alert("后端返回的用户名:"+response.data.userName);
                })

            }

        }
    }).mount("#app");

</script>

</body>
</html>

如果遇到问题,第一步先检查打包/编译出来的包里,依赖是不是正确!

如果遭遇Http 415错误:[org.springframework.web.HttpMediaTypeNotSupportedException: Content type ‘application/x-www-form-urlencoded’ not supported…

如果我们在controller层使用@RequestBody接受数据,那么我们必须要发送application/json的数据格式。我们可以在网络部分查看我们发送的数据格式是否正确。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9k6HzIKK-1688372883211)(springmvc高级.assets/image-20220918160911321.png)]

如果遭遇 HTTP 400 错误的原因常见有:

  • 1、前端提交数据的字段名称或者是字段类型和后台的实体类不一致,导致无法封装;
  • 2、前端提交的到后台的数据应该是 json 字符串类型,而前端没有将对象转化为字符串类型;

解决方案:

  • 1、对照字段名称,类型保证一致性

  • 2、使用 stringify 将前端传递的对象转化为字符串

总结:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-siIVG0wl-1688372883211)(springmvc高级.assets/image-20211110173209836.png)]

注:在类上直接使用 @RestController ,这样子,里面所有的方法都只会返回 json 字符串了,不用再每一个都添加@ResponseBody 。

@RequestBody主要用来接收前端传递给后端的json中的数据的(请求体中的数据的)。

@ResponseBody主要用将后端的返回值/对象转换成json数据传递给前端

@RestController
public class UserRestController {

    @RequestMapping("/testRestAjax")
    public User testAjax(@RequestBody User user){
        System.out.println("username:"+user.getUserName()+",password:"+user.getUserPass());
        user.setUserName("new Name");
        return user;
    }

}

注:@RequestBody说明

@RequestBody,它是用来处理前台定义发来的数据Content-Type: 除application/x-www-form-urlencoded外的编码内容,例如application/json, application/xml等;

换句话说我们使用@RequestBody注解的时候,前台的Content-Type必须要改为application/json,如果没有更改,前台会报错415(Unsupported Media Type)。后台日志就会报错Content type ‘application/x-www-form-urlencoded;charset=UTF-8’ not supported

如果是表单提交 Content-type 会是application/x-www-form-urlencoded,可以去掉@RequestBody注解,可以直接用实体类获取对象。

4.文件上传下载

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c9nXbEcU-1688372883212)(springmvc高级.assets/image-20211128163736676.png)]

4.1 文件上传

文件上传要求form表单的请求方式必须为post,并且添加属性enctype=“multipart/form-data”

SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息

上传步骤:

a>添加依赖:

<!--fileupload 这个版本会自动引入io包-->
<dependency>
  <groupId>commons-fileupload</groupId>
  <artifactId>commons-fileupload</artifactId>
  <version>1.3.3</version>
</dependency>

b>在SpringMVC的配置文件中添加配置:

 <!-- 配置文件上传,如果没有使用文件上传可以不用配置,当然如果不配,那么配置文件中也不必引入上传组件包 -->
    <bean id="multipartResolver"
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 默认编码 -->
        <property name="defaultEncoding" value="utf-8" />
        <!-- 文件大小最大值 -->
        <!-- 设定文件上传的最大值为5MB,5*1024*1024 -->
        <property name="maxUploadSize" value="5242880"></property>
        <!-- 内存中的最大值  设定文件上传时写入内存的最大值,如果小于这个参数不会生成临时文件,默认为10240 -->
        <property name="maxInMemorySize" value="40960" />
    </bean>

c>控制器方法:

package com.liyw.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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 javax.servlet.http.*;
import java.io.File;

@Controller
public class FileUploadController {


    @RequestMapping("/fileuploadPage")
    public String login1(Model model){
        return "fileUpload";//经过视图解析器后去转向/WEB-INF/html/fileUpload.html
    }


    @RequestMapping(value = "fileupload", method = RequestMethod.POST,
            produces = "text/html;charset=utf-8")
    public void addPic(
            HttpServletResponse response,
            HttpServletRequest request,
            @RequestParam("uname") String uname,
            @RequestParam(value = "file_upload", required = false) MultipartFile file) {
        // 上传文件路径
        String path = request.getServletContext().getRealPath("/images/");
        String filename = file.getOriginalFilename();
        File filepath = new File(path, filename);
        System.out.println(uname);
        System.out.println(filename);
        // 判断路径是否存在,如果不存在就创建一个
        if (!filepath.getParentFile().exists()) {
            filepath.getParentFile().mkdirs();
        }
        try {
            // 将上传文件保存到一个目标文件当中
            file.transferTo(new File(path + File.separator + filename));
            response.getWriter().write("success");
            response.setHeader("Access-Control-Allow-Origin", "*");
        }catch (Exception e ){
            System.out.println(e);
        }
        // return "success";
    }

}

写法二-返回的是统一返回类型:

@RequestMapping(value = "fileupload", method = RequestMethod.POST,
            produces = "application/json;charset=utf-8")
    @ResponseBody
    public BaseResult addPic(
            HttpServletResponse response,
            HttpServletRequest request,
            @RequestParam("uname") String uname,
            @RequestParam(value = "file_upload", required = false) MultipartFile file) {
        BaseResult baseResult = new BaseResult();
        // 上传文件路径
        String path = request.getServletContext().getRealPath("/images/");
        String filename = file.getOriginalFilename();
        File filepath = new File(path, filename);
        System.out.println(uname);
        System.out.println(filename);
        // 判断路径是否存在,如果不存在就创建一个
        if (!filepath.getParentFile().exists()) {
            filepath.getParentFile().mkdirs();
        }
        try {
            // 将上传文件保存到一个目标文件当中--
            file.transferTo(new File(path + File.separator + filename));
            Map map = new HashMap();
            map.put("fileName",filename);
            baseResult = BaseResult.setResult(ResultCodeEnum.SUCCESS,map);
        }catch (Exception e ){
            System.out.println(e);
        }
         return baseResult;
    }

d>前端:

方式一:表单提交写法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>提交文件</title>
</head>
<body>

<form id= "uploadForm" action= "./fileupload" method= "post" enctype ="multipart/form-data">
    <h1 >用户信息 </h1>
    <p >用户名:
        <input type ="text" id="uname"  name="uname" />
    </p>
    <p >密码:
        <input type ="text" id="upass" name="upass" />
    </p>
    <p >上传头像: <input type ="file" name="file_upload" /></p>
    <input type ="submit" value="上传"/>
</form>

</body>
</html>

方式二:Ajax异步提交方式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>提交文件</title>
</head>
<body>

<p>AJAX</p>
<form id="ajaxForm" enctype="multipart/form-data">
    <h1 >用户信息 </h1>
    <p >用户名:
        <input type ="text" id="uname"  name="uname" />
    </p>
    <p >密码:
        <input type ="text" id="upass" name="upass" />
    </p>
    <p >上传头像: <input type ="file" name="file_upload" /></p>
    <input type="button" value="上传图片" id="upload" onclick="ajaxFileUpload()"/> <br/>
</form>

<script type="text/javascript" src="http://code.jquery.com/jquery-3.5.1.min.js"></script>
<script type="application/javascript">
    function ajaxFileUpload(){
        var formData = new FormData($('#ajaxForm')[0]);
        $.ajax({
            type:"post",
            url:"./fileupload",
            async:false,
            contentType: false,    //这个一定要写
            processData: false, //这个也一定要写,不然会报错
            data:formData,
            // dataType:'text',    //返回类型,有json,text,HTML
            success:function(data){
                alert("success")
            },
        });
    }
</script>

</body>
</html>

方式三:vue+Axios异步提交方式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>提交文件</title>
</head>
<body>

<p>vue+axios</p>
<form id="app" enctype="multipart/form-data">
    <h1 >用户信息 </h1>
    <p >用户名:
        <input type ="text" id="uname"  name="uname" v-model = "uname"/>
    </p>
    <p >密码:
        <input type ="text" id="upass" name="upass" v-model = "upass"/>
    </p>
    <p >上传头像:  <input type="file" @change="getFile($event)"></p>
    <input type="button" value="上传图片" id="upload" @click.prevent="fileUpload"/> <br/>
</form>

<script src="https://unpkg.com/vue@3"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="application/javascript">
    var app = Vue.createApp({
        data(){
            return {
                uname: '',
                upass: '',
                file: '',
            }
        },
        methods:{
            getFile(event) {
                this.file = event.target.files[0];
                console.log(this.file);
            },
            fileUpload(){
                //获取参数
                console.log(this.uname)
                //设置multipart/form-data
                let config = {
                    headers: {
                        'Content-Type': 'multipart/form-data'
                    }
                }
                //    配axios
                let formData = new FormData();
                formData.append('uname',this.uname);
                formData.append('file_upload',this.file);
                axios.post('./fileupload',formData,config).then(res =>{
                   alert(res.data)
                })
            }
        }
    }).mount("#app");
</script>

</body>
</html>

5.restful支持

5.1 什么是restful

  • REST:Representational State Transfer,表现层资源状态转移。

Restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

RESTful软件开发理念,RESTful是对http进行非常好的诠释。

  • 功能

资源:互联网所有的事物都可以被抽象为资源

资源操作:使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作。

分别对应 添加、 删除、修改、查询。

传统方式操作资源 :通过不同的参数来实现不同的效果!方法单一,post 和 get

​ http://127.0.0.1/item/queryItem.action?id=1 查询,GET

​ http://127.0.0.1/item/saveItem.action 新增,POST

​ http://127.0.0.1/item/updateItem.action 更新,POST

​ http://127.0.0.1/item/deleteItem.action?id=1 删除,GET或POST

使用RESTful操作资源 :可以通过不同的请求方式来实现不同的效果!如下:请求地址一样,但是功能可以不同!

​ http://127.0.0.1/item/1 查询,GET

​ http://127.0.0.1/item 新增,POST

​ http://127.0.0.1/item 更新,PUT

​ http://127.0.0.1/item/1 删除,DELETE

5.2 代码演示

5.2.1 准备工作

按照正常创建springmvc项目,注册dispatcherServlet和开启包的扫描注解等等。

5.2.2 controller
package com.liyw.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class RestFulController {

    @RequestMapping("/rest/{p1}/{p2}")
    public String index(@PathVariable int p1,@PathVariable int p2){
        int result = p1+p2;
        System.out.println("结果是:"+result);
        return "success";
    }
    
    
    @RequestMapping("/rest1/{p1}/{p2}")
    public String rest01(@PathVariable int p1,@PathVariable int p2,Model model){
        int result = p1+p2;
        System.out.println("结果是:"+result);
//        Spring MVC会自动实例化一个Model对象用于向视图中传值
        model.addAttribute("msg","result:"+result);
        //返回视图位置 login.jsp
        return "login";
    }
}

访问url: http://localhost:8081/rest/2/3

5.2.3 静态资源

如果你的DispatcherServlet拦截“/”,拦截了所有的请求,同时对*.js,*.jpg的访问也就被拦截了。

我们注册的dispatcherServlet会拦截所有的请求,那对于某些静态资源,我们应该怎么处理呢?

比如我们上面demo里

<script type="text/javascript" src="http://code.jquery.com/jquery-3.5.1.min.js">
//改成 我们想引入自己的js
<script type="application/javascript" src="../js/jquery-3.5.1.min.js"></script>
  • 现象

前端控制台会显示资源404

后台报错:web.servlet.DispatcherServlet.noHandlerFound No mapping for GET /js/jquery-3.5.1.min.js

  • 解决:添加配置:
<!-- 
   处理静态资源,例如html、js、css、jpg
  若只设置该标签,则只能访问静态资源,其他请求则无法访问
  此时必须设置<mvc:annotation-driven/>解决问题
 -->
<mvc:default-servlet-handler/>

或者配置方法二:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-coiIHaLb-1688372883212)(springmvc高级.assets/image-20211111150356204.png)]

<mvc:resources mapping=“/resources/**” location=“/resources/”/>

location元素表示webapp目录下的resources包下的所有文件;

mapping元素表示以/resources开头的所有请求路径,如/resources/a 或者/resources/a/b;

该配置的作用是:DispatcherServlet不会拦截以/static开头的所有请求路径,并当作静态资源

交由Servlet处理。

注意:引入js需要注意项目结构,路径一般从webapp之后开始写。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ipfn9XlN-1688372883213)(springmvc高级.assets/image-20211213145851946.png)]

重启项目测试是否可以正常访问js资源。

5.2.4 restful的好处
    • 思考:使用路径变量的好处?

      • 使路径变得更加简洁,很容易读懂;

        -- 举例像百度百科url https://baike.baidu.com/item/李开复  自解释性
        
        • 获得参数更加方便,框架会自动进行类型转换。

        • 通过路径变量的类型可以约束访问参数,如果类型不一样,则访问不到对应的请求方法,如这里访问是的路径中两个参数本该是int,但是我传了一个string,类似是/commit/1/a,就会失败。

          Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type ‘java.lang.String’ to required type ‘int’; nested exception is java.lang.NumberFormatException: For input string: “a3”]

6.统一异常处理

6.1 为什么需要统一异常处理

不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。出现异常如果不处理,界面展示500之类的服务器错误页面,是不是用户体验很差呢?

但是如果每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。
那么,能不能将所有类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护?

答案是肯定的。下面将介绍使用Spring MVC统一处理异常的解决和实现过程。

6.2 实现方式

Spring MVC处理异常有3种方式:
(1)使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver;
(2)实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器;
(3)使用@ExceptionHandler注解实现异常处理;

  • SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolver

HandlerExceptionResolver接口的实现类有:DefaultHandlerExceptionResolver和SimpleMappingExceptionResolver

SpringMVC提供了自定义的异常处理器SimpleMappingExceptionResolver,所以其实方法一和方法二本质上都是直接实现HandlerExceptionResolver。

6.2.1 使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver:
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
 <!-- 定义需要特殊处理的异常,用类名或完全路径名作为key,异常也页名作为值 --> 
    <property name="exceptionMappings">
        <props>
        	<!--
        		properties的键表示处理器方法执行过程中出现的异常
        		properties的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面
        	-->
            <prop key="java.lang.ArithmeticException">error</prop>
        </props>
    </property>
</bean>
@Controller
public class ExceptionController {

    @RequestMapping("/simpleException")
    public String simpleException(){
        System.out.println(1/0);
        return "success";
    }
}

如果正常运行,转向success.jsp;如果出现运行异常,经过统一异常处理转向error.jsp

6.2.2 自定义自己的异常处理器

实现Spring的异常处理接口HandlerExceptionResolver。

相比于6.2.1的方式,除了可以进行页面跳转之外,它可以通过我们自己定义异常来从controller层中携带更多的信息。我们可以定义多个自定义异常,然后在异常处理器内对不同类型的异常进行不同的处理和界面转向。

  • 定义异常信息bean
package com.liyw.dto;

public class UserException extends Exception {
    //异常信息
    public String message;
    public UserException(String message) {
        super(message);
        this.message = message;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
}
  • 自定义异常处理器
package com.liyw.utils;

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

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

public class UserExceptionResolver implements HandlerExceptionResolver {

    private UserException userException;

    public ModelAndView resolveException(HttpServletRequest request,
                                          HttpServletResponse response, Object handler, Exception ex) {
        //如果抛出的是系统自定义的异常则直接转换
        if(ex instanceof UserException) {
            userException = (UserException) ex;
        } else {
            //如果抛出的不是系统自定义的异常则重新构造一个未知错误异常
            //这里我就也有UserException省事了,实际中应该要再定义一个新的异常
            userException = new UserException("系统未知错误");
        }
        //向前台返回错误信息
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("message", userException.getMessage());
        modelAndView.setViewName("error");
        return modelAndView;
    }
}
  • 配置、

    <!--异常处理-->
    <bean class="com.liyw.utils.UserExceptionResolver"></bean>
    
  • 测试

    @RequestMapping("/customException")
    public String customException(Model model, @RequestParam(value="id",required=true) Integer id)throws Exception {
        User user=null;
        if(user==null) {
            throw new UserException("用户不存在");
        }
        return "success";
    }
    
6.2.3 注解方式
@ControllerAdvice

该注解为统一异常处理的核心,是一种作用于控制层的切面通知(Advice),该注解能够将通用的@ExceptionHandler、@InitBinder和@ModelAttributes方法收集到一个类型,并应用到所有控制器上

该类中的设计思路:

  1. 使用@ExceptionHandler注解捕获指定或自定义的异常;
  2. 使用@ControllerAdvice集成@ExceptionHandler的方法到一个类中;
  3. 在该类中定义一个通用的异常捕获方法,便于捕获未定义的异常信息;
  4. 自定一个异常类,然后在该类中捕获针对项目或业务的异常
  5. 可以在捕获异常的方法上配合@ResponseBody,将异常信息同样封装为一个统一结果返回。
package com.liyw.utils;

import com.liyw.dto.BaseResult;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

//统一异常处理
@ControllerAdvice
public class ExceptionAutoUtils {

    /**-------- 通用异常处理方法  界面跳转--------**/
    @ExceptionHandler(Exception.class)
    public String error(Exception e, Model model) {
        model.addAttribute("message", e);
        return "error"; // 通用异常结果
    }


    /**-------- 指定异常处理方法 --------**/

    //@ExceptionHandler用于设置所标识方法处理的异常 此处执行ArithmeticException
    @ExceptionHandler(ArithmeticException.class)
    //ex表示当前请求处理中出现的异常对象
    public String handleArithmeticException(Exception ex, Model model){
        model.addAttribute("message", ex);
        return "error";
    }

//    配合 @ResponseBody返回统一的返回结果json对象
    @ExceptionHandler(UserException.class)
    @ResponseBody
    public BaseResult error() {
        return BaseResult.exceptionResult(); // 通用异常结果
    }

}

其他部分代码不动,但要注意开启这个文件夹的注解扫描。

如果遇到了UserException,则返回json为:

{"success":false,"code":3,"message":"系统异常","data":null}

注:JSP和Servlet版本导致el功能默认关闭,加入<%@page isELIgnored=“false”%>标签手动开启el功能。

补充:springmvc国际化支持

1.概念

国际化(internationalization:i18n):国际化是指程序在不做任何修改的情况下,就可以在不同的国家或地区和不同的语言环境下,按照当地的语言和格式习惯的显示字符。例如:对于中国大陆的用户,会自动显示中文简体的提示信息,错误信息等;而对于美国的用户,会自动显示英文的提示信息,错误信息。

本地化(Localization):国际化的程序运行在本地机器上时,能够根据本地机器的语言和地区设置相应的字符,这个过程叫做本地化。

中国建设银行网站默认为中文,可选”繁体/ENGLISH”

2.原理

Spring MVC国际化是建立在Java国际化的基础之上的

Spring MVC的国际化的结构:DispatcherServlet会解析一个LocaleResolver接口对象,通过它来决定用户区域,读出对应用户系统设定的语言或者用户选择的语言,确定其国际化。对于DispatcherServlet而言,只能够注册一个LocaleResolver接口对象,LocaleResolver接口的实现类都在org.springframework.web.servlet.i18n包下

Spring MVC也支持国际化的操作,可使用Spring MVC提供的语言区域解析器接口LocaleResolver,该接口常用实现类:

  • AcceptLanguageLocaleResolver:控制器无需写额外的内容,可以不用显示配置

  • SessionLocaleResolver:使用Session传输语言环境,根据用户session的变量读取区域设置,它是可变的,如果session没有设置,那么它也会使用开发者设置的默认值

  • CookieLocaleResolver:使用Cookie传送语言环境,根据Cookie数据获取国际化信息,如果用户禁止Cookie或者没有设置,它会根据accept-language HTTP头部确定默认区域。

3.基于SessionLocaleResolver的国际化实现

1.在resources文件夹下新建资源文件messages_en_US.properties、messages_zh_CN.properties

messages_en_US.properties:

msg=It's English version.

messages_zh_CN.properties

msg=这是中文版本

注:需要开启idea如下设置进行转换,否则此处需要进行unicode中文转码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N2FYkXx3-1688372883213)(springmvc高级.assets/image-20211122205318747.png)]

2.配置文件 sevelet.xml

<!--国际化-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <!-- 国际化信息所在的文件名,根据ResourceBundleMessageSource类加载资源文件
    .\src\main\resources\messages\messages_en_US.properties -->
    <property name="basename" value="messages/messages" />
    <!-- 如果在国际化资源文件中找不到对应代码的信息,就用这个代码作为名称  -->
    <property name="useCodeAsDefaultMessage" value="true" />
    <property name="defaultEncoding" value="UTF-8"/>
</bean>
<mvc:interceptors>
    <!-- 国际化操作拦截器 如果采用基于(请求/Session/Cookie)则必需配置 -->
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
</mvc:interceptors>
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver" />

基于HttpSession的国际化实现使用的是LocaleResolver接口的SessionLocaleResolver实现类,SessionLocaleResolver不是默认的语言区域解析器,需要对其进行显式配置。且Spring中LocaleResolver的bean名称必须为localeResolver,因为Spring读取该bean时是通过该名称读取的;

3.controller

package com.liyw.controller;

import org.springframework.context.i18n.LocaleContextHolder;
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.bind.annotation.ResponseBody;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import org.springframework.web.servlet.support.RequestContext;

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

@Controller
public class MyController {

    @RequestMapping(value="/test",
            method={RequestMethod.POST,RequestMethod.GET},
            produces="text/html;charset=UTF-8;"
    )
    @ResponseBody
    public String test(HttpServletRequest request,
                       @RequestParam(value="langType", defaultValue="zh")
                               String langType){

        if(langType.equals("zh")){
            Locale locale = new Locale("zh", "CN");
            request.getSession().setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME,locale);
        }
        else if(langType.equals("en")){
            Locale locale = new Locale("en", "US");
            request.getSession().setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME,locale);
        }else{
            request.getSession().setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, LocaleContextHolder.getLocale());
        }
        //从后台代码获取国际化信息
        RequestContext requestContext = new RequestContext(request);
        String msg = requestContext.getMessage("msg");
        System.out.println(msg);
        return msg;
    }
}

注:

使用@RequestMapping + @ResponseBody String返回中文乱码

原因分析: 确定的是(经过多次测试的结果)只有当返回值是 String时才会出现中文乱码,而当返回值是Map<String, Object>或者是其它类型时,并没有中文乱码的出现.

然后找原因: 原因是这可以说是spring mvc的一个bug,spring MVC有一系列HttpMessageConverter去处理用@ResponseBody注解的返回值,如返回list或其它则使用 MappingJacksonHttpMessageConverter,返回string,则使用 StringHttpMessageConverter,而这个convert使用的是字符集是iso-8859-1,而且是final的。所以在当返回json中有中文时会出现乱码。

因此在demo中加上了:produces=“text/html;charset=UTF-8;” 或 produces={“text/html;charset=UTF-8;”,“application/json;”} 可以解决乱码。

正常在项目界面中交互不会出现这个问题。

项目结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-13ODYQVd-1688372883214)(springmvc高级.assets/image-20211122205142956.png)]

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uj1N3V74-1688372883214)(springmvc高级.assets/image-20211122205552566.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0quN5Cer-1688372883215)(springmvc高级.assets/image-20211122210137886.png)]

4.补充说明

一般项目国际化分为前端国际化与后端国际化。springmvc的国际化主要做的是后端国际化,也就是对一些后端返回的提示信息做国际化处理。用户存储的数据一般是不需要做国际化的,所以在界面我们只需要正常渲染就可以。

而前端国际化主要是对界面展示的静态资源做国际化处理(也有前端国际化去翻译后端返回消息的,但是不建议这么做),最常见的处理思路:

1、针对不同的语言,各写一套界面。

2、使用配置文件的方式,使用的是同一套界面,根据语言的不同加载对应的配置文件。(推荐)

前端国际化方案常用的是jQuery的国际化插件jQuery.i18n.properties ,它是一款轻量级的插件,压缩后仅 4kb,api也比较简单,它的国际化资源文件以“.properties”为后缀,包含了各语言相关的键值对。有些前端框架也有自己对应的国际化插件,比如vue的国际化插件 vue-i18n。
}
else if(langType.equals(“en”)){
Locale locale = new Locale(“en”, “US”);
request.getSession().setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME,locale);
}else{
request.getSession().setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, LocaleContextHolder.getLocale());
}
//从后台代码获取国际化信息
RequestContext requestContext = new RequestContext(request);
String msg = requestContext.getMessage(“msg”);
System.out.println(msg);
return msg;
}
}


注:

使用@RequestMapping + @ResponseBody String返回中文乱码

原因分析: 确定的是(经过多次测试的结果)只有当返回值是 String时才会出现中文乱码,而当返回值是Map<String, Object>或者是其它类型时,并没有中文乱码的出现.

**然后找原因:** 原因是这可以说是spring mvc的一个bug,spring MVC有一系列HttpMessageConverter去处理用@ResponseBody注解的返回值,如返回list或其它则使用 MappingJacksonHttpMessageConverter,返回string,则使用 StringHttpMessageConverter,而这个convert使用的是字符集是iso-8859-1,而且是final的。所以在当返回json中有中文时会出现乱码。

因此在demo中加上了:produces="text/html;charset=UTF-8;" 或 produces={"text/html;charset=UTF-8;","application/json;"} 可以解决乱码。

正常在项目界面中交互不会出现这个问题。





项目结构:



[外链图片转存中...(img-13ODYQVd-1688372883214)]



测试结果:

[外链图片转存中...(img-Uj1N3V74-1688372883214)]

[外链图片转存中...(img-0quN5Cer-1688372883215)]





#### 4.补充说明

一般项目国际化分为前端国际化与后端国际化。springmvc的国际化主要做的是后端国际化,也就是对一些后端返回的提示信息做国际化处理。用户存储的数据一般是不需要做国际化的,所以在界面我们只需要正常渲染就可以。

而前端国际化主要是对界面展示的静态资源做国际化处理(也有前端国际化去翻译后端返回消息的,但是不建议这么做),最常见的处理思路:

1、针对不同的语言,各写一套界面。

2、使用配置文件的方式,使用的是同一套界面,根据语言的不同加载对应的配置文件。(推荐)

前端国际化方案常用的是jQuery的国际化插件jQuery.i18n.properties ,它是一款轻量级的插件,压缩后仅 4kb,api也比较简单,它的国际化资源文件以“.properties”为后缀,包含了各语言相关的键值对。有些前端框架也有自己对应的国际化插件,比如vue的国际化插件 vue-i18n。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值