Spring MVC学习文档

Spring MVC

image-20201221142508901

一,概述

1,为什么学习SpringMVC

spring框架已经是java web开发很主流的框架,这个框架有很多优点当然也有它的不足之处,比于之前的servlet,它一定程度上简化了开发人员的工作,使用servlet的话需要每个请求都去在web.xml中配置一个servlet节点,而Spring 中的DispatcherServlet他会拦截所有的请求,进一步去查找有没有合适的处理器,一个前端控制器就可以,

SpringMVC官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc

再原生的servlet中,我们需要做很多很繁琐的配置,甚至在某些时候我们看来,这些重复的步骤都是多余的,那么,有没有什么办法能解决这些问题呢?于是SpringMVC应运而生

2,SpringMVC运行流程图

image-20201221170541459

二,环境搭建

  1. 新建一个Maven项目并添加web服务的支持

  2. 添加需要的依赖

    <dependencies>
        <!--spring核心依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
        <!--log4j-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!--JSP相关依赖 provided代表这个jar在运行时由容器提供-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.3.3</version>
            <scope>provided</scope>
        </dependency>
        <!--标签库依赖-->
        <dependency>
            <groupId>org.apache.taglibs</groupId>
            <artifactId>taglibs-standard-impl</artifactId>
            <version>1.2.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.taglibs</groupId>
            <artifactId>taglibs-standard-spec</artifactId>
            <version>1.2.5</version>
        </dependency>
    </dependencies>
    
    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
    
    
  3. 配置tomcat

  4. 在web.xml中配置servlet

    <?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">
    
        <!--注册DispatcherServlet-->
        <servlet>
            <servlet-name>springmvc</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    	<!--初始化参数,加载spring的配置文件,如果这个文件和web.xml文件同级且名字为springmvc-servlet.xml那就可以省略不写,会自动加载-->
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:springmvc-servlet.xml</param-value>
            </init-param>
            <!--加载优先级,值为正整数,越低优先级越高-->
            <load-on-startup>1</load-on-startup>
        </servlet>
        <!--/* 代表所有请求都要经过这个处理器-->
        <!--/ 代表所有请求都要经过这个处理器(但是不包含jsp)-->
        <servlet-mapping>
            <servlet-name>springmvc</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    </web-app>
    
  5. 编写Controller处理器继承AbstractController

    public class HelloController extends AbstractController {
    
        @Override
        protected ModelAndView handleRequestInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
            System.out.println("hello,SpringMVC");
            //index为我们要跳转的页面的名字
            return new ModelAndView("index");
        }
    }
    
    
  6. 编写springmvc-servlet.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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!--视图解析器-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <property name="suffix" value=".jsp"/>
        </bean>
        
        <!--设置请求地址-->
        <bean name="/hello" class="com.hai.controller.HelloController"/>
    
    </beans>
    
    
  7. 编写前端页面,页面路径要与视图解析器相匹配

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    <h1>hello,SpringMVC</h1>
    </body>
    </html>
    
    
  8. 访问测试:

    image-20201221154330781

以上我们的环境就算是搭好了,

问题:

从搭建环境可以看出,我们每个请求都需要在springmvc-servlet中进行配置,那这?我们使用springmvc的意义是什么?代码好像越来越多了。使用注解代替配置文件

使用注解代替配置文件(annotation)

在SpringMVC之前,我们学习了Component,Repository,Service等注解,我们特别提到了一个注解叫Controller,这就是SpringMVC专用的控制器注解,这个注解联合RequestMapping注解可以让我们摆脱配置文件大量代码的烦恼,详细使用如下

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

    @RequestMapping("/hello")
    public ModelAndView index(){
        return new ModelAndView("index");
    }

}

值得注意的是,我们要使用注解,就得开启mvc注解的支持,和指定扫描包,就需要在配置文件加上这两个配置(原有的视图解析器不能丢)

<!--开启mvc注解的支持-->    
<mvc:annotation-driven/>
<!--扫描包-->
<context:component-scan base-package="com.hai.controller"/>

测试:

image-20201221164051044

三,参数传递

1,参数传递

前面我们的环境搭建测试没有使用任何的参数,在实际开发中,我们肯定是需要从页面采集用户所输入的参数的,那么这个参数,我们改如何接收呢?在这之前,我们的javaweb采用的是三种方式进行数据传递,分别是form表单,url,Ajax三种,在后台我们怎样接收呢,以前是request.getParameter(“key”),现在我们只需要在处理器方法里加上形参即可,可以使用注解@RequestParam进行参数约束和必要性设置,如下

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

    @RequestMapping("/hello")
    public ModelAndView index(@RequestParam("username") String username,@RequestParam(value = "password",required = false,defaultValue = "000000") Integer password){
        System.out.println("hello annotation springmvc");
        System.out.println("username = " + username);
        System.out.println("password = " + password);
        return new ModelAndView("index").addObject("username",username).addObject("password",password);
    }

}

以上代码中@RequestParam注解约束了传递的参数名必须为username,required = false代表这个参数可以不写,defaultValue参数为null时默认值为什么

前端jsp测试代码:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>hello,SpringMVC</h1>
<table>
    <tr>
    <td>${username}</td>
    <td>${password}</td>
    </tr>
</table>
</body>
</html>

测试结果

image-20201221165935405

以上的参数传递方法确实成功的将我们的数据传送到了view视图层,但是我们的方法返回值被限制为了ModelAndView,这显然不是我们想要的,SpringMVC为我们提供了一个接口 ‘Model’,我们可以通过这个接口作为方法的形式参数作为参数传递,这个方法底层实现使用的是Map集合,具体使用如下

@Controller
@RequestMapping("/rest")
public class RestfullController {

    @RequestMapping("/calc")
    public String index( Integer num1, Integer num2, Model model){
        int result = num1 + num2;
        model.addAttribute("result",result)
                .addAttribute("num1",num1)
                .addAttribute("num2",num2);
        return "Calc";
    }
}

通过model接口的addAttribute()方法设置参数,返回值为String,返回的值一样会通过视图解析器,找到对应的页面展示数据,除了使用Model,我们还可以使用原生的HttpServletRequest,当然,前面提到Model这个视口底层使用的是一个Map集合,那我们也可以使用一个Map集合作为形参来进行参数传递,但是使用得最多的还是Model

2,参数封装

在Javaweb的时候,我们的service业务层往往需要一个对象,而那时候我们获取到前端的数据时,在servlet层一个个的将数据做封装,代码繁琐且冗余,SpringMVC帮我们把这一步骤做了,我们需要对象,只需要在形参加上对象即可,SpringMVC在获取到前端数据时,会根据我们的处理器的参数将接收到的数据做封装,但是有个前提,我们前端的参数大多是通过from表单传递的,其中的name属性,必须要和我们后端的属性名相对应,否则就会失败,如下

实体对象

public class User {

    private String username;
    private String password;
    private String sex;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

处理器

@Controller
@RequestMapping("/from")
public class ModelFromController {

    @RequestMapping("/save")
    public String save(User user, Model model){
        System.out.println("user.getSex() = " + user.getSex());
        System.out.println("user.getPassword() = " + user.getPassword());
        System.out.println("user.getUsername() = " + user.getUsername());
        model.addAttribute("user",user);
        return "success";
    }

    @RequestMapping("/from")
    public String index(){
        return "from";
    }
}

数据采集页

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录</title>
    <link rel="stylesheet" href="https://www.layuicdn.com/layui/css/layui.css">
    <script type="text/javascript" src="https://www.layuicdn.com/layui/layui.js"></script>
</head>
<body>
<form class="layui-form" action="/from/save" style="margin: 100px">
    <div class="layui-form-item" style="width: 300px">
        <label class="layui-form-label">用户名</label>
        <div class="layui-input-block">
            <input type="text" name="username" required  lay-verify="required" placeholder="请输入账号" autocomplete="off" class="layui-input">
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">密&nbsp;码</label>
        <div class="layui-input-inline">
            <input type="password" name="password" required lay-verify="required" placeholder="请输入密码" autocomplete="off" class="layui-input">
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">单选框</label>
        <div class="layui-input-block">
            <input type="radio" name="sex" value="男" title="男">
            <input type="radio" name="sex" value="女" title="女" checked>
        </div>
    </div>
    <div class="layui-form-item">
        <div class="layui-input-block">
            <button class="layui-btn" lay-submit lay-filter="formDemo" type="submit">立即提交</button>
            <button type="reset" class="layui-btn layui-btn-primary">重置</button>
        </div>
    </div>
</form>

<script>
    //Demo
    layui.use('form', function(){
        var form = layui.form;

        //监听提交
        form.on('submit(formDemo)', function(data){
            layer.msg(JSON.stringify(data.field));
            return true;
        });
    });
</script>
</body>
</html>

数据展示页

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>用户信息</title>
</head>
<body>
<h1>${user.username} &nbsp;欢迎回来</h1>
<p>密码:${user.password}</p>
<p>性别:${user.sex}</p>
</body>
</html>

测试结果:

image-20201222152602239

3,Restful风格

什么是Restful(Representational State Transfer

RESTFUL特点包括:

1、每一个URI代表1种资源;

2、客户端使用GET、POST、PUT、DELETE4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源;

3、通过操作资源的表现形式来操作资源;

4、资源的表现形式是XML或者HTML;

5、客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息。 [1]

我们传统的传参方式的URL是这样的http://localhost:8080/user/modifyView?uid=113

使用Restful就是这样http://localhost:8080/user/modifyView/113

传的参数类似于磁盘路径:E:\d4149d59b47a70a11a4ebbf122e67595\hls\e0023oer37d.322004.hls

那数据应该怎么做传输呢?

后端,通过@PathVariable注解和@RequestMapping注解值中大括号的字符串对应,就能映射参数,前端可以直接通过URL请求

@RequestMapping("/restful/{one}/{two}")
@ResponseBody//通过这个注解让这个请求不走视图解析器,直接返回一个字符串
public String restful(@PathVariable("one") Integer num1,@PathVariable("two") Integer num2){
    return (num1+"+"+num2+"="+(num1+num2));
}

前端:

http://localhost:8080/user/restful/200/5962

结果:

image-20201224170111026

表单通过Restful提交大有讲究

4,乱码过滤处理

在这之前,我们出现乱码时,是自己编写一个编码过滤器来解决,而Springmvc给我们提供了这么一个过滤器,我们只需要在web.xml中配置即可,如下配置

<filter>
    <filter-name>filter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>Encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
5,向前端响应Json数据

在如今前后端的开发趋势,异步交互数据提升用户体验是必不可少的,前端Ajax大量使用,在有些时候,我们响应的不是一个页面,而是用户需要的数据,那么我们Springmvc怎样向前端对Json数据做出相应呢?在Spring的阶段,目前没有,但是有很多第三方的包整合了它,我们只需做一些简单的配置就可使用

需要的Maven依赖(选用阿里巴巴的fastJson):

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.41</version>
</dependency>

响应请求的方法:

@RequestMapping("/test")
@ResponseBody//跳过视图解析器
public List<User> test() throws Exception {
        ArrayList<User> list = new ArrayList<>();
        list.add(userService.getUserById(1));
        list.add(userService.getUserById(2));
        list.add(userService.getUserById(3));
        list.add(userService.getUserById(4));
        list.add(userService.getUserById(5));
        list.add(userService.getUserById(6));
        return list;
}

相关的Spring配置


<!--日期解析引用配置-->
<bean id="fastJsonConfig" class="com.alibaba.fastjson.support.config.FastJsonConfig">
    <property name="serializerFeatures">
        <array>
            <!--全局修改日期配置为 yy-MM-dd HH:mm:ss-->
            <value>WriteDateUseDateFormat</value>
        </array>
    </property>
</bean>

<!--配置消息转换器-->
<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
            <property name="fastJsonConfig" ref="fastJsonConfig"/>
            <property name="supportedMediaTypes">
                <list>
                    <value>application/json</value>
                </list>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

如果不做配置,会抛出不能转换的异常

结果:

image-20201224172633857

响应结果自动转换为Json

[
  {
    "address": "北京市海淀区成府路207号",
    "age": 37,
    "birthday": "1983-10-10 00:00:00",
    "createdBy": 1,
    "creationDate": "2013-03-21 16:52:07",
    "gender": 1,
    "phone": "13688889999",
    "roleId": 1,
    "userCode": "admin",
    "userId": 1,
    "userName": "系统管理员",
    "userPassword": "1234567"
  },
  {
    "address": "北京市东城区前门东大街9号",
    "age": 37,
    "birthday": "1983-12-10 00:00:00",
    "createdBy": 1,
    "creationDate": "2014-12-31 19:52:09",
    "gender": 2,
    "phone": "13688884457",
    "roleId": 2,
    "userCode": "liming",
    "userId": 2,
    "userName": "李明",
    "userPassword": "abcdef"
  },
  {
    "address": "北京市朝阳区北辰中心12号",
    "age": 36,
    "birthday": "1984-06-05 00:00:00",
    "createdBy": 1,
    "creationDate": "2014-12-31 19:52:09",
    "gender": 2,
    "phone": "18567542321",
    "roleId": 2,
    "userCode": "hanlubiao",
    "userId": 5,
    "userName": "韩明彪",
    "userPassword": "7654321"
  },
  {
    "address": "北京市海淀区学院路61号",
    "age": 37,
    "birthday": "1983-06-15 00:00:00",
    "createdBy": 1,
    "creationDate": "2013-02-11 10:51:17",
    "gender": 1,
    "phone": "13544561111",
    "roleId": 3,
    "userCode": "zhanghua",
    "userId": 6,
    "userName": "张华",
    "userPassword": "0000000"
  }
]
6,自动封装前端的特殊参数为对象

Springmvc帮我们做了接收参数并自动封装为对象的操作,但是有些特殊的类型,前端传输的类型都是字符串,有些特殊类型不能与字符串相转换。例如日期类型,这时候就会报如下异常

25-Dec-2020 15:02:18.454 警告 [http-nio-8080-exec-4] org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.logException Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'user' on field 'birthday': rejected value [2020-11-24]; codes [typeMismatch.user.birthday,typeMismatch.birthday,typeMismatch.java.util.Date,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.birthday,birthday]; arguments []; default message [birthday]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Date' for property 'birthday'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.util.Date] for value '2020-11-24'; nested exception is java.lang.IllegalArgumentException]]

不能将字符串转换为日期的异常,争对此类问题,我们有很多种解决方案,这里列举几种

  1. 在控制层添加这样一个方法,

    //date格式转换
    @InitBinder
    public void initBinder(WebDataBinder binder) throws Exception {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        CustomDateEditor editor = new CustomDateEditor(df, false);
        binder.registerCustomEditor(Date.class, editor);
    }
    

    通过WebDataBinder这个类注册一个自定义编辑器

  2. 在要转换的类型上添加日期注解

    @DateTimeFormat(pattern = "yyy-MM-dd")
    private Date creationDate;
    

四,JSR数据校验

1,为什么需要数据校验

在实际开发中,除了前端需要在表单中验证用户的输入。后台服务也需要对用户传入的参数进行效验,避免他人在得知请求格式后,直接通过类似Postman这样的测试工具进行非常数据请求。确保数据安全性,我们接收到的数据应该再服务器再做一次校验

2,JSR是什么

JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是Hibernate Validator。

此实现与 Hibernate ORM 没有任何关系。 JSR 303 用于对 Java Bean 中的字段的值进行验证。
Spring MVC 3.x 之中也大力支持 JSR-303,可以在控制器中对表单提交的数据方便地验证。
注:可以使用注解的方式进行验证

3,校验规则及错误信息传输

在使用JSR之前,我们得导入两个必须的依赖

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.8.Final</version>
</dependency>

基本校验规则

空检查 
@Null 验证对象是否为null 
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串 
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格. 
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.

Booelan检查 
@AssertTrue 验证 Boolean 对象是否为 true 
@AssertFalse 验证 Boolean 对象是否为 false

长度检查 
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内 
@Length(min=, max=) Validates that the annotated string is between min and max included.

日期检查 
@Past 验证 Date 和 Calendar 对象是否在当前时间之前,验证成立的话被注释的元素一定是一个过去的日期 
@Future 验证 Date 和 Calendar 对象是否在当前时间之后 ,验证成立的话被注释的元素一定是一个将来的日期 
@Pattern 验证 String 对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式,regexp:正则表达式 flags: 指定 Pattern.Flag 的数组,表示正则表达式的相关选项。

数值检查 
建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为”“,Integer为null 
@Min 验证 Number 和 String 对象是否大等于指定的值 
@Max 验证 Number 和 String 对象是否小等于指定的值 
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度 
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度 
@Digits 验证 Number 和 String 的构成是否合法 
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。 
@Range(min=, max=) 被指定的元素必须在合适的范围内 
@Range(min=10000,max=50000,message=”range.bean.wage”) 
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证) 
@CreditCardNumber信用卡验证 
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。 
@ScriptAssert(lang= ,script=, alias=) 
@URL(protocol=,host=, port=,regexp=, flags=)

使用:

1,在属性上添加相应的校验注解

public class User {
  private Integer userId;
  @NotBlank(message = "用户名不能为空")
  @Pattern(regexp = ".{1,10}",message = "用户名长度应在1-10位")
  private String userCode;
  private String userName;
  @NotBlank(message = "密码不能为空")
  @Pattern(regexp = ".{1,10}",message = "密码长度应在1-10位")
  private String userPassword;
}

2,通过请求的方法以形参的方式入参,并在作用对象形参前加上@Validated注解,需要获得错误消息的话,得再作用对象形参后紧跟这个对象引用 BindingResult,具体使用请参考以下代码:

@PostMapping("/login")
public String Login(@Validated @ModelAttribute("user")User user, BindingResult bindingResult, Model model) throws Exception {
    //判断是否存在错误信息
    if (bindingResult.hasErrors()) {
        //获得所有产生的错误信息,返回一个List集合
        List<ObjectError> allErrors = bindingResult.getAllErrors();
        //将错误信息存入Model以便前端获取
        model.addAttribute("error",allErrors);
        //携带错误信息返回登录页
        return "login";
    }

    if (null == userService.login(user.getUserCode(), user.getUserPassword())) {
        //手动添加我们自己验证的错误信息
        bindingResult.rejectValue("userCode","","用户名与密码不匹配");
        model.addAttribute("error", bindingResult.getAllErrors());
        return "login";
    }
    RequestProxy.getSession().setAttribute(Constants.USER_SESSION, user);
    return "frame";
}

3,前端通过技术手段遍历我们传过去的错误集合信息就行了(踩坑:添加后的依赖一定要手动添加到lib库下,否则不生效)

image-20201224172915798

五,文件上传

相关依赖需要支持高版本的Servlet-api

<!--文件上传-->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>

Spring配置(注意:id必须为multipartResolver,否则找不到文件流):

<!--文件上传的配置-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--请求的编码格式-->
    <property name="defaultEncoding" value="u-f-8"/>
    <!--上传文件大小上线,单位字节,10485760为10m-->
    <property name="maxUploadSize" value="10485760"/>
    <property name="maxInMemorySize" value="40960"/>
</bean>

后端控制器代码

@RequestMapping("/addUser")
public String addUser(@RequestParam(value = "userPic", required = false) CommonsMultipartFile userFile,
                      @RequestParam(value = "workPic", required = false) CommonsMultipartFile workFile,
                      User user) throws Exception {
    if (!userFile.isEmpty()) user.setIdPicPath(new UploadFile(userFile).start());
    if (!workFile.isEmpty()) user.setWorkPicPath(new UploadFile(workFile).start());
    user.setCreatedBy(RequestProxy.getSessionTarget(Constants.USER_SESSION, User.class).getUserId());
    user.setCreationDate(new Timestamp(new Date().getTime()));
    userService.add(user);
    return "redirect:/user/query";
}

通过线程实现上传

package cn.smbms.utils;

import org.springframework.web.multipart.commons.CommonsMultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class UploadFile implements Callable<String> {
    private final String path = RequestProxy.getSession().getServletContext().getRealPath("/upload");
    private final CommonsMultipartFile file;

    public UploadFile(CommonsMultipartFile file) {
        this.file = file;
    }

    public String start(){
        FutureTask<String> futureTask = new FutureTask<>(new UploadFile(file));
        new Thread(futureTask).start();
        try {
            return futureTask.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public String call() {
        if (file != null) {
            String picName = UUID.randomUUID()
                    .toString().replaceAll("-","").toUpperCase()
                    + ".jpg";
            File usFile = new File(path);
            if (!usFile.exists()) usFile.mkdir();
            try {
                file.transferTo(new File(usFile + "/" + picName));
            } catch (IOException e) {
                e.printStackTrace();
            }
            return picName;
        }
        return null;
    }
}

前端代码(请求方法必须为POST,enctype的值必须为 “multipart/form-data”)

<from method="post" enctype="multipart/form-data">
    <div class="form-row">
        <div class="form-group col-6">
            <div class="custom-file form-control-sm">
                <input type="file" class="custom-file-input"
                       id="userPic" name="userPic">
                <label class="custom-file-label mr-3" for="userPic" data-browse="证件照...">
                    ${user.idPicPath}</label>
                <div class="invalid-feedback">证件照片必须是图片文件,证件照片大小不能超过2mb</div>
            </div>
        </div>
        <div class="form-group  col-6">
            <div class="custom-file form-control-sm">
                <input type="file" class="custom-file-input" id="workPic"
                       name="workPic">
                <label class="custom-file-label mr-3" for="workPic" data-browse="工作照...">
                    ${user.workPicPath}</label>
                <div class="invalid-feedback">工作照片必须是图片文件,工作照片大小不能超过2mb</div>
            </div>
        </div>
    </div>
</from>

效果实践:

  • 上传页面:

    image-20201228181253464

  • 展示页面

    image-20201228181341423

六,全局异常及错误处理


在应用项目中,我们会遇到服务器的错误,资源找不到的错误,虽然发生错误时服务器给我们提示了相关错误,但是我们项目上线了之后,发生错误时Tomcat的错误页面也应该展示给用户吗?这显然不妥。我们可不可以在服务器发生错误时响应我们要给用户展示的页面呢?如下:

image-20201224141159282

这样的页面展现给用户,用户体验是不是感觉好了很多。那么我们应该怎么做才能实现呢?

实现的方法有很多,这里就列举两种

通过web.xml配置
<error-page>
    <error-code>404</error-code>
    <location>/WEB-INF/404.jsp</location>
</error-page>
<error-page>
    <error-code>500</error-code>
    <location>/WEB-INF/500.jsp</location>
</error-page>

对应location路径下的页面

image-20201224141934653

这样在发生与错误码相对应的错误时就能跳转到我们指定的页面

通过配置文件配置
<!--全局异常处理-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <prop key="java.lang.Exception">404</prop>
            <prop key="java.lang.RuntimeException">404</prop>
        </props>
    </property>
</bean>

配置bean会配合视图解析器在发生指定错误类型时跳转到指定页面

image-20201224143634004

七,拦截器(interceptor)


什么是拦截器?

拦截器作用类似于javaweb中的过滤器,实现思想是根据Spring的AOP思想,可以拦截指定请求到我们的应用层的不合法请求,用作登录验证等。。

image-20201224144215093

实现方法:编写一个类实现HandlerInterceptor接口

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //正在发起登录请求,放行
        if (request.getRequestURI().equals("/user/login")) return true;
        //已登录状态,放行
        if (request.getSession().getAttribute(Constants.USER_SESSION) != null) return true;
        //未登录状态,转发到登录页
        request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,response);
        return false;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("请求后");
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("最后执行");
    }
}

我们可以通过这样一个拦截器来实现用户在未登录的情况下限制对我们系统资源的访问

主要使用的是preHandle这个方法,返回值为true就放行,为false就做出拦截,

编写完毕之后需要在springmvc-servlet的配置文件进行配置

<!--拦截器配置-->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/user/*"/>
        <bean class="cn.smbms.interceptor.LoginInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/bill/*"/>
        <bean class="cn.smbms.interceptor.LoginInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/provider/*"/>
        <bean class="cn.smbms.interceptor.LoginInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(“请求后”);
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    System.out.println("最后执行");
}

}


我们可以通过这样一个拦截器来实现用户在未登录的情况下限制对我们系统资源的访问

主要使用的是preHandle这个方法,返回值为true就放行,为false就做出拦截,

编写完毕之后需要在springmvc-servlet的配置文件进行配置

```xml
<!--拦截器配置-->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/user/*"/>
        <bean class="cn.smbms.interceptor.LoginInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/bill/*"/>
        <bean class="cn.smbms.interceptor.LoginInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/provider/*"/>
        <bean class="cn.smbms.interceptor.LoginInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

可以配置多个,拦截指定请求

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

七号男技师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值