天天看点

Spring MVC数据处理

作者:Iliuhu

类型转换器

<!--注解驱动,配置访问静态资源-->
<mvc:annotation-driven conversion-service="conversionService"/>

<!--配置自定义类型转换器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="com.demo.converters.MyStringToDateConverter" />
        </set>
    </property>
</bean>           
package com.demo.converters;

import org.springframework.core.convert.converter.Converter;
import org.springframework.util.StringUtils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 定义类型转换器 需要明确 源类型 和 目标类型
 * 在convert方法自定义类型转换的实现
 * 在spring配置文件中配置自定义类型转换器
 */
public class MyStringToDateConverter implements Converter<String, Date> {

    @Override
    public Date convert(String source) {
        try {
            if (!StringUtils.isEmpty(source)) {
                if (source.split("-").length == 3) {
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                    return sdf.parse(source);
                } else if(source.split("/").length == 3) {
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
                    return sdf.parse(source);
                } else {
                    throw new RuntimeException("日期转换错误:" + source);
                }
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
}           

数据格式化

  • @NumberFormat ===> 用在javabean的属性或方法参数上
  • @DateTimeFormat ===> 用在javabean的属性或方法参数上
<%--spring标签库--%>
<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%>
...

<%--spring:eval 用来显示格式化后的数据--%>
salary: <spring:eval expression="user.salary"></spring:eval>           
package com.demo.controllers.entity;

import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;

import java.util.Date;

@Data
public class User {
    private Integer id;
    private String username;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthday; // 2023-03-03
    @NumberFormat(style = NumberFormat.Style.CURRENCY)
    private Double balance; // ¥1000
    private String[] hobbies;
    @NumberFormat(pattern = "#,###.##")
    private Double salary; // 1,000.01
    @NumberFormat(style = NumberFormat.Style.PERCENT)
    private Double taskCount; // 96%
}           

数据校验

Hibernate Validator实现了JSR349验证注解规范的技术,在javaBean属性上标注
Spring MVC数据处理
Spring MVC数据处理
  • 基于原生html form表单实现方式
<form action="${pageContext.request.contextPath}/user" method="post">
    id:<input name="id" type="text" /> ${errors.id}<br />
    <input type="submit" value="提交" />
</form>           
package com.demo.controllers.entity;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;

@Data
public class User {
    @NotNull
    @Min(1)
    private Integer id;
}           
package com.demo.controllers;

import com.demo.controllers.entity.User;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PostMapping;

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

/**
 * 在需要验证的javaBean的属性上面加入对应的验证注解
 * 在需要验证的处理方法的对应javaBean参数上加上@Valid
 * 在需要验证的处理方法参数中加入BindingResult,代表自己处理错误,这样就不会显示错误页面了
 * 将错误信息循环通过map存入到request域中
 * 在jsp通过${errors.id}获取对应的错误信息
 */
@Controller
public class UserController {

    @PostMapping("/user")
    public String add(@Valid User user, BindingResult result, Model model) {

        // 将错误信息取出来,输出到jsp页面
        // 判断当前是否出现了错误
        if(result.hasErrors()) {
            // 存放错误信息,有利于在jsp中分别取出错误信息
            Map<String, String> errors = new HashMap<>(); // key: 错误信息的属性名,value: 错误信息

            // 获取所有的错误信息 包含 错误的属性,错误信息
            List<FieldError> fieldErrors = result.getFieldErrors();

            for (FieldError fieldError : fieldErrors) {
                errors.put(fieldError.getField(), fieldError.getDefaultMessage());
            }

            model.addAttribute("errors", errors);

            //如果验证失败将请求转发到添加页面
            return "user/add";
        }
        System.out.println(user);
        return "show";
    }
}           
  • 基于Spring form标签库的实现方式(静态数据)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
    <title>添加用户</title>
</head>
<body>
    <h1>添加用户</h1>
		<%-- Spring的form标签库method是支持put和delete--%>
    <form:form action="${pageContext.request.contextPath}/spring/user" method="post" modelAttribute="user">
        id: <form:input path="id"></form:input><form:errors path="id"></form:errors>
    </form:form>
</body>
</html>
           
/**
 * 在jsp中导入spring-form标签库
 * 在form标签上一定要加上modelAttribute
 * 加上对应的form标签,必须都要以<form:开头
 * @param user
 * @param result
 * @param model
 * @return
 */
@PostMapping("/form/user")
public String springformAdd(@Valid User user, BindingResult result, Model model) {

    if (result.hasErrors()) {
        return "user/add";
    }

    System.out.println(user);

    return "show";
}

/**
 * 添加一个显示jsp的处理方法,一定要传入一个空的User到model中
 * @param user
 * @return
 */
@GetMapping("/user/add")
public String addView(User user) {
    return "user/add";
}           
  • 基于Spring form标签库的实现方式(动态数据)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
    <title>添加用户</title>
</head>
<body>
    <h1>添加用户</h1>

    <form:form action="${pageContext.request.contextPath}/spring/user" method="post" modelAttribute="user">
        hobbies:
        <%--静态数据源--%>
        <form:checkbox path="hobbies" value="唱歌"></form:checkbox>
        <form:label path="hobbies">唱歌</form:label>
        <form:checkbox path="hobbies" value="跳舞"></form:checkbox>
        <form:label path="hobbies">跳舞</form:label> <br>

        <%--动态数据源--%>
        <form:checkboxes path="hobbies" items="${list}"></form:checkboxes>

        <select>
            <option value="1">北京</option>
            <option value="2">上海</option>
        </select>
    </form:form>
</body>
</html>           
/**
     * 初始化页面的数据
     *    调用其它的方法之前,都要调用此方法
     */
    @ModelAttribute
    public void initData(Model model) {
        // 初始化数据
        List<String> list = Arrays.asList("唱歌", "跳舞");
        model.addAttribute("list", list);

//        Map<String, String> map = new HashMap<>();
//        map.put("1", "唱歌");
//        map.put("2", "跳舞");
//
//        model.addAttribute("list", map);
    }

    /**
     * 在jsp中导入spring-form标签库
     * 在form标签上一定要加上modelAttribute
     * 加上对应的form标签,必须都要以<form:开头
     * @param user
     * @param result
     * @return
     */
    @PostMapping("/form/user")
    public String springformAdd(@Valid User user, BindingResult result) {

        if (result.hasErrors()) {
            return "user/add";
        }

        System.out.println(user);

        return "show";
    }

    /**
     * 添加一个显示jsp的处理方法,一定要传入一个空的User到model中
     * @param user
     * @return
     */
    @GetMapping("/user/add")
    public String addView(User user) {

        return "user/add";
    }           

JSON处理

java转换为json的过程:序列化

json转换为java的过程:反序列化

json要用双引号,不能用单引号

JAVA JSON
String "string"
Integer 111
JavaBean/Map {"id":1, "name":"zhangsan"}

数组/集合

String[]/List<String>

["a","b","c"]

List<User>

List<Map>

[

{"id":1, "name":"zhangsan"},

{"id":2, "name":"wangwu"},

...

]

User:

属性:id,name, Role role

{"id":1, "name":"zhangsan","role":{"id":2, "name":"wangwu"}}

User:

属性:id,name,List<Role>

{"id":1, "name":"zhangsan","role":[{"id":2, "name":"wangwu"},

{"id":3, "name":"lisi"}

,...]

}

  • RestController
控制器类中所有的处理方法都会以json的数据进行响应,相当于控制器类中所有的处理方法都加上了@ResponseBody
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.14.2</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.14.2</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.14.2</version>
</dependency>           
package com.demo.controllers.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String usernae;
    @JsonIgnore  // 当返回javaBean的json时,会忽略该属性
    private String password;
    @JsonFormat(pattern = "yyyy-MM-dd")  // 用户转换json时格式化数据
    private Date birthday;
}           
package com.demo.controllers;

import com.demo.controllers.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@Controller
@RestController
public class JsonController {

    @RequestMapping("/json/response")
//    @ResponseBody // 将返回值作为文本进行返回,并不是返回视图
    public User responseJson() {
        User user = new User(1, "zhangsan", "123456", new Date());

        return user;
    }
}           
  • Spring MVC获取JSON数据
// 对象转化为json字符串
var jsonValue = JSON.stringify(user);           
  • @RequestBody: 请求中JSON数据转化成JAVA
  • @ResponseBody: 响应中把JAVA转化成JSON数据

Spring MVC上传|下载

  • Servlet 下载
package com.demo.controllers;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;

/**
 * Servlet下实现的文件下载
 */
@WebServlet("/download")
public class DownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String realPath = this.getServletContext().getRealPath("/file/1.png"); // 获取要下载的文件的绝对路径
        // 将文件路径封装成File对象
        File tmpFile = new File(realPath);
        // 获取文件名
        String fileName = tmpFile.getName();
        // 设置响应头 content-disposition: 设置文件下载的打开方式,默认在网页上打开
        // attachment;filename是以下载方式来打开文件
        resp.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
        InputStream is = new FileInputStream(realPath); // 获取文件输入流

        int len = 0;
        byte[] buffer = new byte[1024];
        OutputStream out = resp.getOutputStream();
        while((len = is.read(buffer)) != -1) {
           out.write(buffer, 0, len); // 将缓冲区的数据输出到客户端浏览器
        }

        is.close();
    }
}           
  • Spring MVC 下载
package com.demo.controllers;

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.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;

@Controller
public class DownloadController {

    @RequestMapping("/download1")
    public String download1(HttpServletRequest request, HttpServletResponse response) throws Exception{
        String realPath = request.getServletContext().getRealPath("F:\\code\\springmv01\\web\\images\\img.png");
        File tmpFile = new File(realPath);
        String fileName = tmpFile.getName();

        response.setHeader("content-disposition", "attachment; filename = " + URLEncoder.encode(fileName, "UTF-8"));
        InputStream is = new FileInputStream(realPath);

        int len = 0;
        byte[] buffer = new byte[1024];
        OutputStream out = response.getOutputStream();
        while((len = is.read(buffer)) != -1) {
            out.write(buffer, 0, len); // 将缓冲区的数据输出到客户端浏览器
        }

        is.close();

        return null;
    }

    /**
     * 基于Spring ResponseEntity的文件下载 不支持缓冲区
     *      ResponseEntity 可以定制文件的响应内容,响应头,响应状态码
     * @return
     */
    @RequestMapping("/getDownload")
    public ResponseEntity<String> getDownload() {
        String body = "hello world";

        HttpHeaders headers = new HttpHeaders();
        headers.set("Set-Cookie","name=hello");

        return new ResponseEntity<>(body, headers, HttpStatus.OK);
    }

    @RequestMapping("/download2")
    public ResponseEntity<Byte[]> download2(HttpServletRequest request, HttpServletResponse response) throws Exception{
        String realPath = request.getServletContext().getRealPath("F:\\code\\springmv01\\web\\images\\img.png");
        File tmpFile = new File(realPath);
        String fileName = tmpFile.getName();

        HttpHeaders headers = new HttpHeaders();
        headers.set("content-disposition", "attachment; filename = " + URLEncoder.encode(fileName, "UTF-8"));
        InputStream is = new FileInputStream(realPath);

        return new ResponseEntity<Byte[]>(new Byte[is.available()], headers, HttpStatus.OK);
    }
}           
  • Spring MVC 上传
<!--文件上传解析器-->
<bean id= "multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="defaultEncoding" value="UTF-8" />
    <!--文件上传最大的字节数-->
    <property name="maxUploadSize" value="#{1024*1024*10}" />
</bean>           
<form enctype="multipart/form-data" action="${pageContext.request.contextPath}/upload" method="post">
  文件描述:<input type="text" name="desc" /> <br>
  文件:<input type="file" name="myfile" multiple accept="image/*"/> <br> <%--multiple: 多文件上传--%>
  <input type="submit" value="提交" />
</form>           
/**
 * 多文件文件上传
 */
@PostMapping("/upload")
public String upload(String desc, MultipartFile[] multipartFiles) throws IOException {

    for (MultipartFile myfile : multipartFiles) {
        String path = "D:/" + myfile.getOriginalFilename();
        File file = new File(path);

        myfile.transferTo(file);
    }

    return "success";
}           
/**
 * 多线程文件上传
 */
@PostMapping("/upload")
public String upload(String desc, MultipartFile[] multipartFiles) throws InterruptedException {

    for (MultipartFile myfile : multipartFiles) {
        Thread thread = new Thread(() -> {
            String path = "D:/" + myfile.getOriginalFilename();
            File file = new File(path);
            try {
                myfile.transferTo(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        thread.start();
        thread.join(); // 让子线程执行完在执行主线程
    }

    return "success";
}            
  • Spring MVC虚拟目录上传
Spring MVC数据处理
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <%--磁盘路径--%>
    <img src="${pageContext.request.contextPath}/img/${filename}">
</body>
</html>           
/**
 * 文件上传到三个路径
 *     1. 项目路径(适合项目小,上传使用率低
 *     2. 磁盘路径,这种方式通过虚拟目录的映射
 *     3. 静态资源服务器(CDN)
 */
@PostMapping("/upload")
public String upload(String desc, MultipartFile myfile, Model model) throws IOException {


    String path = "D:/" + myfile.getOriginalFilename();
    File file = new File(path);

    myfile.transferTo(file);

    model.addAttribute("filename", myfile.getOriginalFilename());

    return "success";
}           

Spring MVC拦截器

拦截器:采用AOP的设计思想,类似Servlet过滤器,用来拦截处理方法

如:权限验证、日志、异常记录、记录方法执行时间

<mvc:interceptors>
    <bean class="com.demo.interceptors.MyInterceptor" />
</mvc:interceptors>           
package com.demo.interceptors;

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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;

public class MyInterceptor implements HandlerInterceptor {

    /**
     * 在处理方法之前执行
     * @param request   在方法请求进来之前更改request中的的属性值
     * @param response
     * @param handler   封装了当前处理方法的信息
     * @return true: 后续调用链是否执行  false:中断后续执行
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handler1 = (HandlerMethod) handler;
        }

        System.out.println("-------类:["+ handler1.getBean().getClass() +"] 方法:["+ handler1.getMethod().getName() +"] 参数:["+ Arrays.toString(handler1.getMethod().getParameters()) +"] 前执行---------preHandle--------------------------------");
        return true;
    }

    /**
     * 在请求执行后执行,在视图渲染之前执行
     * 当处理方法出现了异常则不会执行方法
     * @param request
     * @param response 在方法执行后更改response中的信息
     * @param handler  封装了当前处理方法的信息
     * @param modelAndView  封装了model和view,请求结束后可以修改model中的数据或新增model数据,也可修改view的跳转
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("------方法后执行,在视图渲染之前----------postHandle--------------------------------");
    }

    /**
     * 在视图渲染之后执行,出现异常也会执行该方法
     * @param request
     * @param response
     * @param handler
     * @param ex 可以记录异常日志的功能,或者清除资源
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("-----------在视图渲染之后--------afterHandle----------");
    }
}           
  • 拦截器|过滤器的区别
Spring MVC数据处理
Spring MVC数据处理
Spring MVC数据处理
<!--必须保证对应的请求没有映射处理-->
<mvc:view-controller path="/admin" view-name="admin"></mvc:view-controller>

<mvc:interceptors>

    <!--直接配置bean会拦截Spring MVC所有请求-->
    <bean class="com.demo.interceptors.MyInterceptor" />

    <!--不是所有的请求都要拦截-->
    <mvc:interceptor>
        <!--需要拦截请求-->
        <mvc:mapping path="/**"/>
        <!--不需要拦截的请求-->
        <mvc:exclude-mapping path="/login"/>
        <!--拦截器-->
        <bean class="com.demo.interceptors.CheckLoginInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>           
package com.demo.interceptors;

import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

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

/**
 * 验证用户是否登录的拦截器
 */
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 国际化

  • 通过浏览器语言设置国际化
<!--设置国际化支持 配置国际化属性资源文件-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename">
        <array>
            <value>i18n/login</value>
            <value>i18n/index</value>
        </array>
    </property>
</bean>           
package com.demo.controllers;

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

/**
 * 基于浏览器设置的语言切换国际化
 *      1. 新建jsp对应的国际化属性资源文
 *          login_en_US.properties
 *          login_zh_CN.properties
 *      2. 配置sping_mvc.xml 将国际化支持和资源文件都注入到springmvc中
 *      3. 在jsp页面调用对应的属性资源内容:<spring:message code="绑定属性资源文件中对应的key"
 */
@Controller
public class I8NController {

    @RequestMapping("/i18n")
    public String i18n() {
        return "login";
    }
}           
  • 通过超链接来切换国际化
Spring MVC数据处理

》》》方式一

<!--使用SessionLocaleResolver保持Locale的状态,会在session中获取Locale对象-->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"></bean>           
package com.demo.controllers;

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

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

/**
 * 基于浏览器设置的语言切换国际化
 *      1. 新建jsp对应的国际化属性资源文
 *          login_en_US.properties
 *          login_zh_CN.properties
 *      2. 配置sping_mvc.xml 将国际化支持和资源文件都注入到springmvc中
 *      3. 在jsp页面调用对应的属性资源内容:<spring:message code="绑定属性资源文件中对应的key"
 */
@Controller
public class I8NController {

    @RequestMapping("/i18n")
    public String i18n() {
        return "login";
    }

    @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";
    }
}           

》》》方式二

<!--使用SessionLocaleResolver保持Locale的状态,会在session中获取Locale对象-->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"></bean>

<!--使用springmvc提供的拦截器,接收local参数,设置到session中去-->
<mvc:interceptors>
    <!--所有的请求都会被拦截-->
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
</mvc:interceptors>           
  • Spring MVC基于注解的国际化

Spring MVC 异常处理

  • @ExceptionHandler
通过@ExceptionHandler可以在方法中记录日志
  • @ControllerAdvice是对Controller的增强

全局异常处理

全局数据绑定

全局数据预处理

》》》优先级:处理器异常 > 全局异常中具体异常 > 全局异常

Spring MVC数据处理

继续阅读