springmvc-day03

springmvc-day03

第一章 拦截器

1.概念

1.1 使用场景
1.1.1 生活中坐地铁的场景

为了提高乘车效率,在乘客进入站台前统一检票:
在这里插入图片描述

1.1.2 程序中的校验登录场景

在程序中,使用拦截器在请求到达具体 handler 方法前,统一执行检测。

在这里插入图片描述

1.2 拦截器与过滤器的对比
1.2.1 相同点

三要素相同

  • 拦截(配置拦截路径):必须先把请求拦住,才能执行后续操作
  • 过滤(根据某种规则/业务逻辑进行筛选):拦截器或过滤器存在的意义就是对请求进行统一处理
  • 放行(满足规则/筛选条件,就让你访问你想访问的资源):对请求执行了必要操作后,放请求过去,让它访问原本想要访问的资源
1.2.2 不同点
  • 工作平台不同
    • 过滤器工作在 Servlet 容器中
    • 拦截器工作在 SpringMVC 的基础上
  • 拦截的范围
    • 过滤器:能够拦截到的最大范围是整个 Web 应用
    • 拦截器:能够拦截到的最大范围是整个 SpringMVC 负责的请求(handler方法、view-controller跳转页面、default-servlet-handler处理的静态资源)
  • IOC 容器支持
    • 过滤器:想得到 IOC 容器需要调用专门的工具方法,是间接的
    • 拦截器:它自己就在 IOC 容器中,所以可以直接从 IOC 容器中装配组件,也就是可以直接得到 IOC 容器的支持

2. 具体使用

使用步骤:

1 编写拦截器
(1). 编写一个类实现HandlerInterceptor接口
(2). 重写HandlerInterceptor接口的三个方法:
① 在处理器处理请求之前执行preHandle(),该方法的返回值表示是否放行
② 在处理器处理请求之后执行postHandle()
③ 在视图渲染完毕(整个请求过程完成)之后执行afterCompletion()

2 配置拦截器的拦截路径(在spring的配置文件)
mvc:interceptors
mvc:interceptor

<mvc:mapping path=“/hello/*”/>

</mvc:interceptor>
</mvc:interceptors>

2.1 导入依赖
  <dependencies> 
    <!-- SpringMVC -->  
    <dependency> 
      <groupId>org.springframework</groupId>  
      <artifactId>spring-webmvc</artifactId>  
      <version>5.3.1</version> 
    </dependency>  
    <!-- 日志 -->  
    <dependency> 
      <groupId>ch.qos.logback</groupId>  
      <artifactId>logback-classic</artifactId>  
      <version>1.2.3</version> 
    </dependency>  
    <!-- ServletAPI -->  
    <dependency> 
      <groupId>javax.servlet</groupId>  
      <artifactId>javax.servlet-api</artifactId>  
      <version>3.1.0</version>  
      <scope>provided</scope> 
    </dependency>  
    <!-- Spring5和Thymeleaf整合包 -->  
    <dependency> 
      <groupId>org.thymeleaf</groupId>  
      <artifactId>thymeleaf-spring5</artifactId>  
      <version>3.0.12.RELEASE</version> 
    </dependency> 
  </dependencies> 
2.2 创建spring-web.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.oracle"></context:component-scan>

    <mvc:annotation-driven></mvc:annotation-driven>

    <mvc:default-servlet-handler></mvc:default-servlet-handler>

        <!-- Thymeleaf视图解析器 -->
            <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
                <property name="order" value="1"/>
                <property name="characterEncoding" value="UTF-8"/>
                <property name="templateEngine">
                    <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
                        <property name="templateResolver">
                            <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">

                                <!-- 视图前缀 -->
                                <property name="prefix" value="/WEB-INF/templates/"/>

                                <!-- 视图后缀 -->
                                <property name="suffix" value=".html"/>

                                <!--模板类型-->
                                <property name="templateMode" value="HTML5"/>
                                <!--模板的字符编码-->
                                <property name="characterEncoding" value="UTF-8" />
                            </bean>
                        </property>
                    </bean>
                </property>
            </bean>
</beans>
2.3 web.xml文件添加映射器和解决字符乱码问题
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
	version="4.0">

	<servlet>
			<servlet-name>dispatcherServlet</servlet-name>
			<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
			<init-param>
				<param-name>contextConfigLocation</param-name>
				<param-value>classpath:spring-web.xml</param-value>
			</init-param>
			<load-on-startup>1</load-on-startup>
		</servlet>

		<servlet-mapping>
			<servlet-name>dispatcherServlet</servlet-name>
			<url-pattern>/</url-pattern>
		</servlet-mapping>

	    <!--CharacterEncodingFilter-->
	        <!-- 配置过滤器解决 POST 请求的字符乱码问题 -->
	        <filter>
	            <filter-name>CharacterEncodingFilter</filter-name>
	            <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
	            <!-- encoding参数指定要使用的字符集名称 -->
	            <init-param>
	                <param-name>encoding</param-name>
	                <param-value>UTF-8</param-value>
	            </init-param>
	            <!-- 请求强制编码 -->
	            <init-param>
	                <param-name>forceRequestEncoding</param-name>
	                <param-value>true</param-value>
	            </init-param>
	            <!-- 响应强制编码 -->
	            <init-param>
	                <param-name>forceResponseEncoding</param-name>
	                <param-value>true</param-value>
	            </init-param>
	        </filter>
	        <filter-mapping>
	            <filter-name>CharacterEncodingFilter</filter-name>
	            <url-pattern>/*</url-pattern>
	        </filter-mapping>
</web-app>
2.4 创建拦截器类
package com.atguigu.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 Demo01Interceptor implements HandlerInterceptor {
    
    // 在处理请求的目标方法前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("Demo01Interceptor的preHandle方法执行了...");
        // 返回true放行 返回false不放行
        return true;
    }

    // 在目标方法之后,渲染视图之前执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("Demo01Interceptor的postHandle方法执行了...");

    }
    
    // 在渲染视图之后执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("Demo01Interceptor的afterCompletion方法执行了...");

    }
}

单个拦截器执行顺序

  • preHandle() 方法
  • 目标 handler 方法
  • postHandle() 方法
  • 渲染视图
  • afterCompletion() 方法
2.5 注册拦截器
2.5.1 默认拦截全部请求
<!-- 注册拦截器 -->
<mvc:interceptors> 
    <!-- 直接通过内部 bean 配置的拦截器默认拦截全部请求(SpringMVC 范围内) -->
    <bean class="com.atguigu.interceptor.Demo01Interceptor"/>
</mvc:interceptors>
2.5.2 配置拦截路径
2.5.2.1 精确匹配
<!-- 具体配置拦截器可以指定拦截的请求地址 -->
<mvc:interceptor>
    <!-- 精确匹配 -->
    <mvc:mapping path="/hello/sayHello"/>
    <bean class="com.atguigu.interceptor.Demo01Interceptor"/>
</mvc:interceptor>
2.5.2.2 模糊匹配:匹配单层路径
<mvc:interceptor>
    <!-- /*匹配路径中的一层 -->
    <mvc:mapping path="/hello/*"/>
    <bean class="com.atguigu.interceptor.Demo01Interceptor"/>
</mvc:interceptor>
2.5.2.3 模糊匹配:匹配多层路径
<mvc:interceptor>
    <!--模糊匹配多级目录-->
    <mvc:mapping path="/hello/**"/>

    <!--排除-->
    <mvc:exclude-mapping path="/hello/sayHello"/>

    <bean class="com.atguigu.interceptor.Demo01Interceptor"/>
</mvc:interceptor>
2.6 多个拦截器执行顺序
  • preHandle()方法:和配置的顺序一样
  • 目标handler方法
  • postHandle()方法:和配置的顺序相反
  • 渲染视图
  • afterCompletion()方法:和配置的顺序相反

第二章 类型转换

SpringMVC 将『把请求参数注入到 POJO 对象』这个操作称为**『数据绑定』**,英文单词是 binding。数据类型的转换和格式化就发生在数据绑定的过程中。 类型转换和格式化是密不可分的两个过程,很多带格式的数据必须明确指定格式之后才可以进行类型转换。最典型的就是日期类型。

1. 自动类型转换

HTTP 协议是一个无类型的协议,我们在服务器端接收到请求参数等形式的数据时,本质上都是字符串类型。请看 javax.servlet.ServletRequest 接口中获取全部请求参数的方法:

public Map<String, String[]> getParameterMap();

而我们在实体类当中需要的类型是非常丰富的。对此,SpringMVC 对基本数据类型提供了自动的类型转换。例如:请求参数传入“100”字符串,我们实体类中需要的是 Integer 类型,那么 SpringMVC 会自动将字符串转换为 Integer 类型注入实体类。

类型转换的种类:

类型转换的种类:
1 自动类型转换(SpringMVC会根据你接收请求参数的类型,进行自动转换)
2 手动类型转换(如果在类型转换的时候,有一些特殊的要求,我们可以通过手动进行转换)
(1). 使用SpringMVC提供的某些注解,对特定的类型进行手动转换
① DateTimeFormate
② NumberFormate注解
(2). 编写自定义的类型转换器
① 创建一个类型转换器类实现Converter接口,该接口有俩泛型(S,T)

2.手动类型转换(日期和数值类型转换)

2.1 先导入依赖
  <dependencies> 
    <!-- SpringMVC -->  
    <dependency> 
      <groupId>org.springframework</groupId>  
      <artifactId>spring-webmvc</artifactId>  
      <version>5.3.1</version> 
    </dependency>  
    <!-- 日志 -->  
    <dependency> 
      <groupId>ch.qos.logback</groupId>  
      <artifactId>logback-classic</artifactId>  
      <version>1.2.3</version> 
    </dependency>  
    <!-- ServletAPI -->  
    <dependency> 
      <groupId>javax.servlet</groupId>  
      <artifactId>javax.servlet-api</artifactId>  
      <version>3.1.0</version>  
      <scope>provided</scope> 
    </dependency>  
    <!-- Spring5和Thymeleaf整合包 -->  
    <dependency> 
      <groupId>org.thymeleaf</groupId>  
      <artifactId>thymeleaf-spring5</artifactId>  
      <version>3.0.12.RELEASE</version> 
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.24</version>
    </dependency>
  </dependencies> 
2.2 创建Product实体类
package com.atguigu.pojo;

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

import java.util.Date;

/**

 * SpringMVC提供了一些注解,可以让我们进行一些手动类型转换
 * 1. DateTimeFormat注解:可以对日期时间类型进行转换
 * 2. NumberFormat注解:可以对数值类型进行转换
 */
@Data
public class Product {
    
    // 可以根据yyyy-MM-dd格式转换成Date类型显示
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date productDate;
    
    // 可以根据#,#格式显示数字
    @NumberFormat(pattern = "#,#")
    private Double productPrice;
}

2.3 创建HelloController类
package com.atguigu.controller;

import com.atguigu.pojo.Product;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/hello")
public class HelloController {

    @RequestMapping("/demo01")
    public String sayHello(Product product){
        System.out.println("product = " + product);
        return "target";
    }
}

2.4 创建index.html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<form th:action="@{/hello/demo01}">
    生产日期:<input type="text" name="productDate"> <br>
    产品价格:<input type="text" name="productPrice"> <br>
    <button>提交</button>
</form>
</body>
</html>
2.5 数据绑定失败后处理方式
2.5.1 默认结果

在这里插入图片描述

2.4.2 BindingResult 接口

BindingResult 接口和它的父接口 Errors 中定义了很多和数据绑定相关的方法,如果在数据绑定过程中发生了错误,那么通过这个接口类型的对象就可以获取到相关错误信息。
在这里插入图片描述

2.4.3 重构 handler 方法
package com.atguigu.controller;

import com.atguigu.pojo.Product;
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.RequestMapping;

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

@Controller
@RequestMapping("/hello")
public class HelloController {

    @RequestMapping("/demo01")
    public String sayHello(Product product, BindingResult bindingResult, Model model){
        // 在该方法中声明bindingResult,SpringMVC就不会自己处理绑定错误了
        ArrayList<String> filedNameList = new ArrayList<>();
        // 判断是否有绑定错误
        if (bindingResult.hasErrors()){
            // 如果有绑定错误
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();
            for (FieldError fieldError : fieldErrors) {
                // 获取出现绑定错误的字段名
                String fieldName = fieldError.getField();
                // 获取字段绑定错误的信息
                String defaultMessage = fieldError.getDefaultMessage();
                System.out.println(fieldName + ":" + defaultMessage);
                filedNameList.add(fieldName);
            }
            model.addAttribute("errFieldNames", filedNameList);
            return "bind-error";
        }
        System.out.println("product = " + product);
        return "target";
    }
}

2.4.4 在页面上显示错误消息

页面是vind-error.html,放在Thymeleaf前后缀控制范围之内

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>数据绑定错误</title>
</head>
<body>
<h1>传入的参数有问题!</h1>
<ul th:each="fieldName:${errFieldNames}">
    <li th:text="${filedName}"></li>
</ul>
</body>
</html>

3.自定义类型转换器

现在我们希望通过一个文本框输入约定格式的字符串,然后转换为我们需要的类型,所以必须通过自定义类型转换器来实现,否则 SpringMVC 无法识别。

3.1 创建自定义类型转换器类

实现接口:org.springframework.core.convert.converter.Converter<S,T>

泛型 S:源类型(本例中是 String 类型)

泛型 T:目标类型(本例中是 Date类型)

package com.atguigu.comverter;

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

import java.util.Date;

public class AtguiguDateConverter implements Converter<String, Date> {
    @Override
    public Date convert(String source) {
        // source就是要进行转换的那个字符串,例如"1992-03-03"或者"1992/03/03"
        String[] strs = source.split("-");
        // 如果strs的长度大于1,那说明客户端传的是-格式
        if (strs.length > 1){
            int year = Integer.parseInt(strs[0]);
            int month = Integer.parseInt(strs[1]);
            int day = Integer.parseInt(strs[2]);
            return new Date(year, month, day);
        }

        strs = source.split("/");
        if (strs.length > 1){
            int year = Integer.parseInt(strs[0]);
            int month = Integer.parseInt(strs[1]);
            int day = Integer.parseInt(strs[2]);
            return new Date(year, month, day);
        }
        throw new RuntimeException("传入的参数只能是-或者/格式");
    }
}

3.2 在springmvc配置文件中注册类型转换器
    <!-- 在 mvc:annotation-driven 中注册 FormattingConversionServiceFactoryBean -->
    <mvc:annotation-driven conversion-service="formattingConversionService"></mvc:annotation-driven>



<!--    配置类型转换器-->
    <!-- 在 FormattingConversionServiceFactoryBean 中注册自定义类型转换器 -->
    <bean id="formattingConversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <!-- 在 converters 属性中指定自定义类型转换器 -->
        <property name="converters">
            <list>
                <bean class="com.atguigu.converter.AtguiguDateConverter"></bean>
            </list>
        </property>

    </bean>

配置好自定义类型转换器后不管是/格式还是-格式都可以转成Date类型显示

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

第三章 数据校验

在 Web 应用三层架构体系中,表述层负责接收浏览器提交的数据,业务逻辑层负责数据的处理。为了能够让业务逻辑层基于正确的数据进行处理,我们需要在表述层对数据进行检查,将错误的数据隔绝在业务逻辑层之外。

1. 数据校验概述

JSR 303 是 Java 为 Bean 数据合法性校验提供的标准,它已经包含在 JavaEE 6.0 标准中。JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。

注解规则
@Null标注值必须为 null
@NotNull标注值不可为 null,但是可以为空字符串
@AssertTrue标注值必须为 true
@AssertFalse标注值必须为 false
@Min(value)标注值必须大于或等于 value
@Max(value)标注值必须小于或等于 value
@DecimalMin(value)标注值必须大于或等于 value
@DecimalMax(value)标注值必须小于或等于 value
@Size(max,min)标注值大小必须在 max 和 min 限定的范围内
@Digits(integer,fratction)标注值值必须是一个数字,并且指定最大的整数位数和最大的小数位数
@Past标注值只能用于日期型,且必须是过去的日期
@Future标注值只能用于日期型,且必须是将来的日期
@Pattern(value)标注值必须符合指定的正则表达式

JSR 303 只是一套标准,需要提供其实现才可以使用。Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:

注解规则
@Email标注值必须是格式正确的 Email 地址
@Length标注值字符串大小必须在指定的范围内
@NotEmpty标注值字符串不能是空字符串
@Range标注值必须在指定的范围内

Spring 4.0 版本已经拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架。Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。在SpringMVC 中,可直接通过注解驱动 mvc:annotation-driven 的方式进行数据校验。Spring 的 LocalValidatorFactoryBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口。只要在Spring容器中定义了一个LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean中。Spring本身并没有提供JSR 303的实现,所以必须将JSR 303的实现者的jar包放到类路径下。

配置 mvc:annotation-driven 后,SpringMVC 会默认装配好一个 LocalValidatorFactoryBean,通过在处理方法的入参上标注 @Validated 注解即可让 SpringMVC 在完成数据绑定后执行数据校验的工作。

2. 具体操作

前提:1. springmvc环境 2. Tomcat8及以上版本

2.1 引入依赖
    <!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
    <dependency>
      <groupId>org.hibernate.validator</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>6.2.0.Final</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor -->
    <dependency>
      <groupId>org.hibernate.validator</groupId>
      <artifactId>hibernate-validator-annotation-processor</artifactId>
      <version>6.2.0.Final</version>
    </dependency>
2.2 应用校验规则
2.2.1 给要进行校验的字段加上校验规则注解
package com.atguigu.pojo;

import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import org.springframework.context.annotation.Bean;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Pattern;

@Data
public class User {

    @Null
    private Integer id;

    @NotNull
    @Length(min = 6, max = 10)
    private String username;

    @NotNull
    @Pattern(regexp = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$")
    private String phone;
    
    @NotNull
    @Email
    private String email;

    @NotNull
    @Range(min = 18, max = 80)
    private Integer age;
}

2.2.2 给处理器方法的形参加上Validated
package com.atguigu.controller;

import com.atguigu.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/add")
    public String add(@Validated User user){

        return "target";
    }

    @RequestMapping("/update")
    public String update(User user){
        System.out.println(user);
        return "target";
    }
}

2.3 测试效果
2.2.3.1 测试add方法 一个参数都没填

校验生效了 所以报400错误

在这里插入图片描述

2.2.3.2 测试update方法 一个参数都没填

能够成功访问页面 因为update方法中参数中没有加@Validated注解
在这里插入图片描述

2.2.3.3 测试add方法 参数全部填写正确

能够正常访问页面了
在这里插入图片描述

2.4 显示友好的错误提示
2.4.1 重构UserController方法
    @RequestMapping("/add")
    public String add(@Validated User user, BindingResult bindingResult, Model model){
        List<String > fieldNameList = new ArrayList();
        if (bindingResult.hasErrors()){
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();
            for (FieldError fieldError : fieldErrors) {
                String fieldName = fieldError.getField();
                String defaultMessage = fieldError.getDefaultMessage();
                fieldNameList.add(fieldName);
            }
            model.addAttribute("errorFieldNames", fieldNameList);
            return "bind-error";
        }
        System.out.println("user add = " + user);
        return "target";
    }
2.4.2 创建错误信息页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>数据绑定错误</title>
</head>
<body>
<h1>传入的参数有问题!</h1>
<ul th:each="fieldName:${errorFieldNames}">
    <li th:text="${fieldName}"></li>
</ul>
</body>
</html>
2.4.3 显示效果

在这里插入图片描述

2.5 分组校验

如果想要使用update方法时候id必须为null 使用add方法的时候id必须不能为null的时候该如何处理呢

使用分组校验解决

2.5.1 创建分组的接口

add分组接口

package com.atguigu.group;

public interface AddGroup {
}

update分组接口

package com.atguigu.group;

public interface UpdateGroup {
}

2.5.2 重构User实体类校验规则
package com.atguigu.pojo;

import com.atguigu.group.AddGroup;
import com.atguigu.group.UpdateGroup;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import org.springframework.context.annotation.Bean;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Pattern;

@Data
public class User {

    @Null(groups = AddGroup.class)
    @NotNull(groups = UpdateGroup.class)
    private Integer id;

    @NotNull(groups = {AddGroup.class,UpdateGroup.class})
    @Length(min = 6,max = 10, groups = {AddGroup.class,UpdateGroup.class})
    private String username;

    @NotNull(groups = {AddGroup.class,UpdateGroup.class})
    @Pattern(regexp = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$",
            groups = {AddGroup.class,UpdateGroup.class})
    private String phone;

    @NotNull(groups = {AddGroup.class,UpdateGroup.class})
    @Email(groups = {AddGroup.class,UpdateGroup.class})
    private String email;

    @NotNull(groups = {AddGroup.class,UpdateGroup.class})
    @Range(min = 18, max = 80, groups = {AddGroup.class,UpdateGroup.class})
    private Integer age;
}

2.5.3 重构UserController方法
package com.atguigu.controller;

import com.atguigu.group.AddGroup;
import com.atguigu.group.UpdateGroup;
import com.atguigu.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;

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

@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/add")
    // add方法使用AddGroup分组的校验
    public String add(@Validated(AddGroup.class) User user, BindingResult bindingResult, Model model){
        List<String > fieldNameList = new ArrayList();
        if (bindingResult.hasErrors()){
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();
            for (FieldError fieldError : fieldErrors) {
                String fieldName = fieldError.getField();
                String defaultMessage = fieldError.getDefaultMessage();
                fieldNameList.add(fieldName);
            }
            model.addAttribute("errorFieldNames", fieldNameList);
            return "bind-error";
        }
        System.out.println("user add = " + user);
        return "target";
    }

    @RequestMapping("/update")
    // update方法使用UpdateGroup分组的校验
    public String update(@Validated(UpdateGroup.class) User user){
        System.out.println("user update = " + user);
        return "target";
    }
}

2.5.4 测试效果
2.5.4.1 add方法传入id参数 不能通过校验

在这里插入图片描述

2.5.4.1 update方法传入id参数 通过校验

在这里插入图片描述

第四章 异常映射

1. 为什么需要异常映射

一个项目中会包含很多个模块,各个模块需要分工完成。如果张三负责的模块按照 A 方案处理异常,李四负责的模块按照 B 方法处理异常……各个模块处理异常的思路、代码、命名细节都不一样,那么就会让整个项目非常混乱。

异常映射可以将异常类型和某个具体的视图关联起来,建立映射关系。好处是可以通过 SpringMVC 框架来帮助我们管理异常。

  • 声明式管理异常:在配置文件中指定异常类型和视图之间的对应关系。在配置文件或注解类中统一管理。
  • 编程式管理异常:需要我们自己手动 try … catch … 捕获异常,然后再手动跳转到某个页面。

2. 异常映射的优势

  • 使用声明式代替编程式来实现异常管理
    • 让异常控制和核心业务解耦,二者各自维护,结构性更好
  • 整个项目层面使用同一套规则来管理异常
    • 整个项目代码风格更加统一、简洁
    • 便于团队成员之间的彼此协作

3. 基于 XML 的异常映射

3.1 XML配置

SpringMVC 会根据异常映射信息,在捕获到指定异常对象后,将异常对象存入请求域,然后转发到和异常类型关联的视图。

<!--配置异常处理-->
<bean id="exceptionResolver"
      class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">

    <!-- 配置异常映射关系 -->
    <property name="exceptionMappings">
        <props>
            <!-- key属性:指定异常类型 -->
            <!-- 文本标签体:和异常类型对应的逻辑视图 -->
            <prop key="java.lang.ArithmeticException">error-arith</prop>

            <prop key="java.lang.ClassNotFoundException">error-class</prop>

            <prop key="java.lang.RuntimeException">error-runtime</prop>
        </props>
    </property>
</bean>
3.2 异常范围

如果在配置文件中,发现有多个匹配的异常类型,那么 SpringMVC 会采纳范围上最接近的异常映射关系。

<prop key="java.lang.ArithmeticException">error-arith</prop>
<prop key="java.lang.RuntimeException">error-runtime</prop>

4. 基于注解的异常映射

4.1 创建异常处理器类

在这里插入图片描述

4.2 异常处理器加入IOC容器
4.2.1 包扫描
<!--1.包扫描-->
<context:component-scan base-package="com.atguigu"/>
4.2.2 给异常处理器类标记注解和处理异常的方法
package com.atguigu.handler;

import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.util.List;

@ControllerAdvice(basePackages = "com.atguigu.controller")
public class AtguiguExceptionHandler {
    // @ExceptionHandler注解:标记异常处理方法
    // value属性:指定匹配的异常类型 value可以不写
    // 异常类型的形参:SpringMVC 捕获到的异常对象
    @ExceptionHandler(RuntimeException.class)
    public String handlerRuntimeException(Exception e){
        // 记录异常信息
        System.out.println("记录日志:" + e);

        return "error";
    }

    
    // 标记匹配的异常类型是数据绑定异常类型
    @ExceptionHandler(BindException.class)
    public String handlerBindingException(BindException e){
        // 记录异常信息
        List<FieldError> fieldErrors = e.getFieldErrors();
        for (FieldError fieldError : fieldErrors) {
            System.out.println("记录日志:" + fieldError.getField() + "字段:" + fieldError.getDefaultMessage());

        }
        // 同步请求跳转报错页面
        return "error";
    }
}

4.3 处理器方法
package com.atguigu.controller;

import com.atguigu.pojo.Product;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/hello")
public class HelloController {

    @RequestMapping("/sayHello")
    public String sayHello(){
        System.out.println("hello world");
        int num = 10 / 0;
        return "target";
    }

    public String sayXixi(@Validated Product product){
        System.out.println("product = " + product);
        return "target";
    }
}

当同一个异常类型在基于 XML 和注解的配置中都能够找到对应的映射,那么以注解为准。

4.4 测试效果 测试sayHello和sayXixi方法报错是否使用了异常处理器

在这里插入图片描述

在这里插入图片描述

5. 区分请求类型

5.1 为什么要区分请求类型

异常处理机制和拦截器机制都面临这样的问题:
在这里插入图片描述

5.2 判断依据

查看请求消息头中是否包含 Ajax 请求独有的特征:

  • Accept 请求消息头:包含 application/json
  • X-Requested-With 请求消息头:包含 XMLHttpRequest

两个条件满足一个即可。

5.3 重构异常处理器兼容两种请求的处理方法
package com.atguigu.handler;

import com.atguigu.result.Result;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@ControllerAdvice(basePackages = "com.atguigu.controller")
public class AtguiguExceptionHandler {
    // @ExceptionHandler注解:标记异常处理方法
    // value属性:指定匹配的异常类型 value可以不写
    // 异常类型的形参:SpringMVC 捕获到的异常对象
    @ExceptionHandler(RuntimeException.class)
    public String handlerRuntimeException(HttpServletRequest request, HttpServletResponse response, Exception e) throws IOException {
        // 记录异常信息
        System.out.println("记录日志:" + e);
        // 判断是同步请求还是异步请求
        if (request.getHeader("accept").contains("application/json")){
            // 异步请求
            // 将result对象转成json响应给客户端
            response.getWriter().write(new ObjectMapper().writeValueAsString(Result.fail()));
            return null;
        }
        // 同步请求 跳转到错误页面
        return "error";
    }


    // 标记匹配的异常类型是数据绑定异常类型
    @ExceptionHandler(BindException.class)
    public String handlerBindingException(HttpServletRequest request,HttpServletResponse response,BindException e) throws IOException {
        // 记录异常信息
        List<FieldError> fieldErrors = e.getFieldErrors();
        for (FieldError fieldError : fieldErrors) {
            System.out.println("记录日志:" + fieldError.getField() + "字段:" + fieldError.getDefaultMessage());
        }
        
        // 判断是同步请求还是异步请求
        if (request.getHeader("accept").contains("application/json")){
            // 异步请求
            // 将result对象转成json响应给客户端
            response.getWriter().write(new ObjectMapper().writeValueAsString(Result.fail()));
            return null;
        }
        // 同步请求跳转报错页面
        return "error";
    }
}

5.4 测试效果
5.4.1 sayHello方法测试同步请求

在这里插入图片描述

5.4.2 sayHello方法测试异步请求 accept设为application/json

在这里插入图片描述

5.4.3 sayXixi方法测试同步请求

在这里插入图片描述

5.4.4 sayXixi方法测试异步请求 accept设为application/json

在这里插入图片描述

第五章 文件上传

1.前端表单

需要满足的要求:

  • 第一点:请求方式必须是 POST
  • 第二点:请求体的编码方式必须是 multipart/form-data(通过 form 标签的 enctype 属性设置)
  • 第三点:使用 input 标签、type 属性设置为 file 来生成文件上传框
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>文件上传</h1>
<form enctype="multipart/form-data" th:action="@{/file/upload}" method="post">
    请选择你想上传的文件:<input type="file" name="icon"> <br>
    请输入文件描述:<input type="text" name="desc"> <br>
    <button>提交</button>
</form>
</body>
</html>

2.SpringMVC环境

2.1 映入依赖
    <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.1</version>
    </dependency>
2.2 配置

在 SpringMVC 的配置文件中加入 multipart 类型数据的解析器:

    <!--配置文件上传的解析器-->
    <bean id="multipartResolver"
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 由于上传文件的表单请求体编码方式是 multipart/form-data 格式,所以要在解析器中指定字符集 -->
        <property name="defaultEncoding" value="UTF-8"/>
        <!--指定上传文件的最大体积: 8M-->
        <property name="maxUploadSize" value="8388608"/>

3 创建FileUtil工具类

package com.atguigu.utils;

import java.util.Calendar;
import java.util.Random;
import java.util.UUID;


public class FileUtil {
    /**
     * 随机生成唯一的文件名
     * @param originalFileName
     * @return
     */
    public static String getUUIDName(String originalFileName){
        //1. 获取文件名的后缀
        String suffix = originalFileName.substring(originalFileName.lastIndexOf("."));
        //2. 使用UUID生成一个唯一的字符串
        String str = UUID.randomUUID().toString().replaceAll("-","");

        //返回唯一的文件名
        return str + suffix;
    }

    /**
     * 生成随机目录的方法
     * @param dirPathLevel
     * @param dirPathNameSize
     * @return
     */
    public static String getRandomDirPath(int dirPathLevel,int dirPathNameSize){
        StringBuilder dirPath = new StringBuilder();
        //我们随机生成的目录就从这个字符串中随机取俩字母
        String source = "abcde0123456789";
        Random random = new Random();
        for (int j=0;j < dirPathLevel;j++) {
            for (int i = 0; i < dirPathNameSize; i++) {
                //循环两次,每次取出一个字符作为目录名
                int index = random.nextInt(source.length());
                dirPath.append(source.charAt(index));
            }
            dirPath.append("/");
        }
        return dirPath.toString();
    }

    /**
     * 按照日期生成随机目录:我们每天上传的内容放到一个目录中
     * @return
     */
    public static String getDateDirPath(){
        StringBuilder dirPath = new StringBuilder();
        //获取当前时间:获取当前的年、月、日就可以生成目录路径
        Calendar calendar = Calendar.getInstance();

        int year = calendar.get(Calendar.YEAR);
        dirPath.append(year+"/");

        //获取到的月是0-11,而真正的月是1-12
        int month = calendar.get(Calendar.MONTH) + 1;
        if (month < 10){
            dirPath.append("0"+month+"/");
        }else {
            dirPath.append(month+"/");
        }

        //获取日
        int day = calendar.get(Calendar.DAY_OF_MONTH);
        if(day < 10){
            dirPath.append("0" + day + "/");
        }else {
            dirPath.append(day + "/");
        }
        return dirPath.toString();
    }

    public static void main(String[] args) {
        System.out.println(getDateDirPath());
    }
}

4 创建FileController处理类接收数据

package com.atguigu.controller;

import com.atguigu.utils.FileUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletContext;
import java.io.File;
import java.io.IOException;

@Controller
@RequestMapping("/file")
public class FileController {

    @Autowired
    private ServletContext servletContext;

    @RequestMapping("/upload")
    public String upload(@RequestParam("desc") String desc,
                         @RequestParam("icon")MultipartFile multipartFile) throws IOException {
        System.out.println("desc = " + desc);
        System.out.println("multipartFile = " + multipartFile);

        // 将客户端上传的文件保存起来
        // 1.获取上传时候的文件名
        String originalFilename = multipartFile.getOriginalFilename();

        // 文件上传问题1:文件重名问题
        // 2.我们要针对每一个上传的文件给其一个唯一的文件名,但是要保证后缀不变(UUID)
        String uuidName = FileUtil.getUUIDName(originalFilename);

        // 文件上传问题2:所有文件都上传同一个目录,不好管理 最少弄两级目录(随机生成或者按照日期生成)
        String randomDirPath = FileUtil.getRandomDirPath(2, 2);

        // 方式一:将其保存到当前电脑的D:\\upload中
        // 创建表示目录的file对象
/*
        File dirFile = new File("D:\\upload");
        // 判断是否存在,如果不存在则创建这个目录
        if (!dirFile.exists()){
            dirFile.mkdirs();
        }
        // 将该文件转存到目录中
        multipartFile.transferTo(new File(dirFile,originalFilename));
*/

        // 方式二:使用servletContext获取项目部署路径
        // 动态获取项目部署路径中的upload文件夹
        String dirPath = servletContext.getRealPath(randomDirPath);
        File dirFile = new File(dirPath);
        // 判断是否存在,如果不存在则创建这个目录
        if (!dirFile.exists()){
            dirFile.mkdirs();
        }
        // 将该文件转存到目录中
        multipartFile.transferTo(new File(dirFile,originalFilename));

        return "target";
    }
}

5 测试效果

上传文件

在这里插入图片描述

上传到部署目录中了
在这里插入图片描述

6. MultipartFile接口介绍

在这里插入图片描述

6.1 底层原理

在这里插入图片描述

6.2 三种去向
6.2.1 本地转存

在这里插入图片描述

6.2.1.3 缺陷
  • Web 应用重新部署时通常都会清理旧的构建结果,此时用户以前上传的文件会被删除,导致数据丢失。
  • 项目运行很长时间后,会导致上传的文件积累非常多,体积非常大,从而拖慢 Tomcat 运行速度。
  • 当服务器以集群模式运行时,文件上传到集群中的某一个实例,其他实例中没有这个文件,就会造成数据不一致。
  • 不支持动态扩容,一旦系统增加了新的硬盘或新的服务器实例,那么上传、下载时使用的路径都需要跟着变化,导致 Java 代码需要重新编写、重新编译,进而导致整个项目重新部署。

在这里插入图片描述

6.2.2 文件服务器

在这里插入图片描述

6.2.2.1 优势
  • 不受 Web 应用重新部署影响
  • 在应用服务器集群环境下不会导致数据不一致
  • 针对文件读写进行专门的优化,性能有保障
  • 能够实现动态扩容

在这里插入图片描述

5.2.2.2 常见的文件服务器类型
  • 第三方平台:
    • 阿里的 OSS 对象存储服务
    • 七牛云
  • 自己搭建服务器:FastDFS等
5.2.3 上传到其他模块(了解)

这种情况肯定出现在分布式架构中,常规业务功能不会这么做,采用这个方案的一定的特殊情况。

在这里插入图片描述

第六章 文件下载

1. 原始形态

使用链接地址指向要下载的文件。此时浏览器会尽可能解析对应的文件,只要是能够在浏览器窗口展示的,就都会直接显示,而不是提示下载。

<a href="download/hello.atguigu">下载</a><br/>
<a href="download/tank.jpg">下载</a><br/>
<a href="download/chapter04.zip">下载</a><br/>

上面例子中,只有 chapter04.zip 文件是直接提示下载的,其他两个都是直接显示。

2.明确要求浏览器提示下载

2.1 前端代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>文件上传</h1>
<form enctype="multipart/form-data" th:action="@{/file/upload}" method="post">
    请选择你想上传的文件:<input type="file" name="icon"> <br>
    请输入文件描述:<input type="text" name="desc"> <br>
    <button>提交</button>

</form>

    <h1>文件下载</h1>
    <a th:href="@{/file/download(fileName='abc.7z')}">下载abc.7z</a> <br>
    <a th:href="@{/file/download(fileName='demo.txt')}">下载demo.txt</a> <br>
    <a th:href="@{/file/download(fileName='timg.jpg')}">下载timg.jpg</a> <br>
</body>
</html>
2.2 处理器方法
    @RequestMapping("/download")
    public ResponseEntity download(@RequestParam("fileName") String fileName) throws IOException {
        // 1.使用字节输入流读取要下载的文件
        InputStream is = servletContext.getResourceAsStream("static/download/" + fileName);
        // 2.将字节输入流中的所有字节读到byte[]
        // is.available() 获取字节输入流中的字节数
        byte[] buffer = new byte[is.available()];
        is.read(buffer);
        // 3.响应:响应行、头、体
        HttpHeaders headers = new HttpHeaders();
        //Content-Disposition:响应头是指示客户端下载内容
        headers.add("content-disposition", "attachment;filename=" + fileName);
        // 获取要下载的那个文件的媒体类型(mime-type)
        String mimeType = servletContext.getMimeType(fileName);
        headers.add("content-type", mimeType);

        // 4.使用输出流输出
        return new ResponseEntity(buffer, headers, HttpStatus.OK);
    }

3. 典型应用场景举例

我们目前实现的是一个较为简单的下载,可以用在下面的一些场合:

  • 零星小文件下载
  • 将系统内部的数据导出为 Excel、PDF 等格式,然后以下载的方式返回给用户
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值