SpringMVC

源码地址:https://gitee.com/tirklee/lea-springmvc

SpringMVC简介

MVC(Model View Controller)是Java项目以及JavaEE中使用最为广泛的,也是唯一提倡的整体设计模式。传统的Web开发中需要利用反射与大量的配置文件才能实现一个便于维护、可动态扩充的MVC设计模式。为简化MVC设计难度SpringMVC应用而生。
SpringMVC整体架构图
SpringMVC中,用户的所有请求都会被DispatcherServlet程序类接收。该类是利用反射与Spring提供的工具类实现的一个动态请求处理,它会根据用户访问的请求路径,将请求转交给指定的Action类(开发者定义的控制器)进行处理,处理完成后再利用字符串进行路径跳转(使用Model类保存属性)。也可以利用ModelAndView类进行跳转路径的配置与传递request属性定义。

注意:
SpringMVC与Struts。
Struts是一款优秀的MVC开发设计框架,从Struts 1.x到Struts 2.x,一直在行业中应用广泛。本质上来说,SpringMVC与Struts这两个开发框架在整体设计上并无太大差别。但Struts路径直接基于反射进行操作,产生的漏洞曾导致过数据泄露。从那以后,SpringMVC迎来了发展契机。SpringMVC使用注解配置访问路径,更加安全,对于参数的接收与处理过程也更加简单。

在SpringMVC中还支持视图解析器(以实现页面安全访问)、请求参数与类对象转换、Restful风格展示、文件上传、拦截器等核心技术。

搭建SpringMVC项目开发环境

  • 新建【lea-springmvc】项目在pom.xml文件中添加spring以及springmvc的相关依赖。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    引入servlet、jsp、spring,spring-mvc、spring-webmvc等相关依赖
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.xiyue.leaspring</groupId>
  <artifactId>lea-springmvc</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>lea-springmvc Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring.version>5.3.5</spring.version>
    <compiler.version>3.6.1</compiler.version>
    <lombok.version>1.18.18</lombok.version>
    <slf4j.version>1.7.25</slf4j.version>
    <logback.version>1.2.3</logback.version>
    <taglibs.version>1.2.5</taglibs.version>
    <servlet-api.version>2.3</servlet-api.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>${servlet-api.version}</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.taglibs</groupId>
      <artifactId>taglibs-standard-spec</artifactId>
      <version>${taglibs.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.taglibs</groupId>
      <artifactId>taglibs-standard-impl</artifactId>
      <version>${taglibs.version}</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>${slf4j.version}</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>${logback.version}</version>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>${lombok.version}</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <finalName>lea-springmvc</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>${compiler.version}</version>
          <configuration>
            <source>${maven.compiler.source}</source>
            <target>${maven.compiler.target}</target>
            <encoding>${project.build.sourceEncoding}</encoding>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>
  • 配置src/main/webapp/WEB-INF/web.xml文件。
  1. Spring监听器
    在这里插入图片描述
    2.指明Spring配置文件路径启动Spring
    在这里插入图片描述
    3.SpringMVC的核心是DispatcherServlet转发处理类
    在这里插入图片描述
    4.为了杜绝程序开发中的乱码问题,还需要统一配置编码过滤器
    在这里插入图片描述
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >
<!-- 配置文件需要按照以下顺序,否则不能正常使用
The content of element type "web-app" must match "
(icon?,
display-name?,
description?,
distributable?,
context-param*,
filter*,
filter-mapping*,
listener*,
servlet*,servlet-mapping*,
session-config?,
mime-mapping*,
welcome-file-list?,
error-page*,
taglib*,
resource-env-ref*,
resource-ref*,
security-constraint*,
login-config?,
security-role*,
env-entry*,
ejb-ref*,
ejb-local-ref*)".
-->
<web-app>
  <display-name>lea-springmvc</display-name>
  <!--设置上下文参数,实际上设置application属性,等价于setAttibute()-->
  <context-param>
    <param-name>contextConfigLocation</param-name><!--Spring中定义的属性名称-->
    <param-value>classpath:spring.xml</param-value><!--资源文件-->
  </context-param>
  <!--配置编码过滤器,已解决数据传输乱码问题-->
  <filter>
    <filter-name>encoding</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>
  </filter>
  <filter-mapping><!--所有路径都必须经过此过滤器-->
    <filter-name>encoding</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <!--配置Spring容器启动的监听器,以加载Spring中的核心配置文件-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <!--设置SpringMVC的核心控制器类,利用此类实现所有的请求分发处理(Action)-->
  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param><!--定义SpringMVC的配置文件-->
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-*.xml</param-value>
    </init-param>
  </servlet>
  <servlet-mapping><!--SpringMVC设计中的路径都是以action结尾的-->
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern><!--拦截所有请求 小编在这里使用*.action使用无法获取资源修改为/就可以了-->
  </servlet-mapping>
</web-app>
  • 配置src/main/resources/spring-mvc.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/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <context:component-scan base-package="com.xiyue.leaspring.action"/>
    <!--启用SpringMVC注解配置支持-->
    <mvc:annotation-driven/>
    <!--启动Servlet的配置处理-->
    <mvc:default-servlet-handler/>
</beans>

注意:
为防止重复扫描配置,扫描路径可以定义为子目录。
整合SpringMVC开发框架时,需要使用context:component-scan注解定义扫描包。如果此时配置为父包(n.mldn.mldnspring),将与Spring监听器配置文件中的自动扫描包重复,造成重复扫描问题,产生未知的错误。

编写第一个SpringMVC程序

SpringMVC程序符合标准MVC设计模式的要求,即前台通过表单或URL地址重写,进行请求参数的发送,而后通过指定路径访问Action类中的处理方法。数据处理完成后,通过request属性范围保存要显示的数据信息,并在JSP页面中显示。SpringMVC为了简化内置对象的应用,所有的参数都可以通过Action方法进行自动接收。对于配置跳转路径与传递属性数据,使用ModelAndView类进行包装。
在这里插入图片描述
1.新建src/main/java/com/xiyue/leaspring/action/EchoAction.java负责请求处理。

package com.xiyue.leaspring.action;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import java.util.Arrays;
import java.util.Date;

@Controller//定义控制器
@RequestMapping("/pages")//定义访问父路径,与方法中过的路径组合为完整的路径
@Slf4j//注入日志实例
public class EchoAction {//自定义程序类
    
    @RequestMapping("/echo")//访问的路径为echo.action
    public ModelAndView echo(String msg){//根据参数名称自动进行匹配处理
        log.info("*****EchoAction接收到请求参数,msg"+msg);//日志输出
        //日志输出ModeAndView主要功能时设置跳转路径以及保存request属性
        ModelAndView mav = new ModelAndView("/pages/message/message_show.jsp");
        mav.addObject("echoMessage","【Echo】msg="+msg);
        return mav;
    }
}

本程序完成了一个最基础的Action类定义。
@Controller:SpringMVC的控制器需要使用@Controller注解声明(与@Service、@Repository注解意义相同)。功能是向Spring中配置一个自定义的Bean。  
@RequestMapping:控制器的访问路径。注解中如果直接定义访问路径,则表示支持全部的HTTP请求模式(GET、POST、PUT等),可通过RequestMethod枚举类设置请求类型。
在这里插入图片描述
两个访问路径,最终的访问路径是这两个路径的叠加。
|- 在EchoAction类上定义了访问路径/pages/message/,该路径属于父路径,所以使用通配符“”。
|- 在echo方法上定义了子路径/echo,实际访问中需要将两个路径合并在一起使用。
|- 如果觉得此注解过于麻烦,可以采用@GetMapping("/echo")或@PostMapping("/echo")之类的注解简化HTTP请求模式的定义。
 ModelAndView:主要功能是配置跳转路径与传递的request属性
 echo(String msg):方法的参数名称与请求参数名称匹配时,可以自动接收,不需要再编写request.getParameter(“msg”)语句。
 注意
 访问路径可以返回字符串。SpringMVC设计模式中,推荐读者使用ModelAndView操作处理。Spring中,同一种操作可以有多种处理方案,这里也可以在方法上使用String作为返回路径配置,通过方法参数定义一个Model对象,以实现属性传递。

 @RequestMapping("/echo")//访问的路径为echo.action
 public String echo(String msg, Model model){//根据参数名称自动进行匹配处理
     model.addAttribute("echoMessage","【Echo】msg="+msg);
     return "/pages/message/message_show.jsp";
 }

本程序并没有定义父路径,而是在echo方法上直接定义了完整路径,同时在配置echo参数时配置了Model对象,利用该对象实现了属性传递。这样echo方法执行后的跳转路径就可以通过字符串进行描述了。

  • 创建src/main/webapp/pages/message/message_show.jsp页面,显示参数内容。
<%@ page import="java.util.*" contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%--加入相应的jar才能编译jsp--%>
<%@page isELIgnored="false" %><%--启动EL表达式解析--%>
<html>
<head>
    <title>show_msg</title>
</head>
<%
    request.setCharacterEncoding("UTF-8");
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+
        request.getServerPort()+request.getContextPath()+"/";
%>
<body>
<base href="<%=basePath%>">
<H1>ECHO消息显示:${echoMessage}</H1>
</body>
</html>

启动项目访问:http://localhost:8080/lea_springmvc/pages/echo?msg=xiyue1
在这里插入图片描述

  • 新建src/main/webapp/pages/message/message_input.jsp利用表单输入参数。
<%@ page import="java.util.*" contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%--加入相应的jar才能编译jsp--%>
<%@page isELIgnored="false" %><%--启动EL表达式解析--%>
<html>
<head>
    <title>show_msg</title>
</head>
<%
    request.setCharacterEncoding("UTF-8");
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+
            request.getServerPort()+request.getContextPath()+"/";
    String message_input_url = basePath + "pages/input";
%>
<body>
<base href="<%=basePath%>">
<form action="<%=message_input_url%>" method="post">
    请输入消息:<input type="text" name="msg" id="msg" value="xiyue1">
    <input type="submit" value="发送">
</form>
</body>
</html>

本程序利用表单实现了msg参数的输入处理,同时在表单上设置了EchoAction完整的处理路径。

-src/main/java/com/xiyue/leaspring/action/EchoAction.java中添加方法用于跳转页面

 @RequestMapping("/inputView")//访问的路径为echo.action
 public String inputView(){//根据参数名称自动进行匹配处理
     return "/pages/message/message_input.jsp";
 }

重新启动项目访问:http://localhost:8080/lea_springmvc/pages/inputView
在这里插入图片描述

接收请求参数

SpringMVC设计中,为了简化参数的接收过程,可直接在控制层方法上通过方法参数名称匹配,实现请求参数的自动接收。如果请求参数的名称与方法中的名称不相同,可在定义方法参数上使用@RequestParam注解进行配置。@RequestParam注解中有3个常用配置属性。
在这里插入图片描述
案例1:修改EchoAction类中的echo方法。

   @RequestMapping("/echo")
   public ModelAndView echo(
           @RequestParam(//表示对请求参数的配置
               name = "msg",//表示映射的请求参数的名称
               required = false,//表示该参数是否比逊传递
               defaultValue = "天选"//表示该参数为null时的默认值
           )
           String p1){//接收请求参数
       log.info("*****EchoAction接收到请求参数,msg"+msg);//日志输出
       //日志输出ModeAndView主要功能时设置跳转路径以及保存request属性
       ModelAndView mav = new ModelAndView("/pages/message/message_show.jsp");
       mav.addObject("echoMessage","【Echo】msg="+msg);
       return mav;
   }

echo方法中的参数名称与请求参数名称并不相同,但由于使用了@RequestParam注解,仍然可以实现指定参数的接收,并可在不传递参数时使用默认值进行设置。
注意
@RequestParam注解。
如果只为了实现请求参数与方法接收参数的匹配,不需要设置默认值,使用@RequestParam(“msg”)配置注解即可。这种配置较为复杂,实际开发中建议采用原始方式,保持请求参数名称与方法参数名称的一致。

进行参数接收时,可以实现数据类型的自动转换处理,也可以在一个方法中实现多个参数的接收。

  • 修改src/main/webapp/pages/message/message_input.jsp页面,实现多个参数的输入。
<%@ page import="java.util.*" contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%--加入相应的jar才能编译jsp--%>
<%@page isELIgnored="false" %><%--启动EL表达式解析--%>
<html>
<head>
    <title>show_msg</title>
</head>
<%
    request.setCharacterEncoding("UTF-8");
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+
            request.getServerPort()+request.getContextPath()+"/";
    String message_input_url = basePath + "pages/input";
%>
<body>
<base href="<%=basePath%>">
<form action="<%=message_input_url%>" method="post">
    消息内容:<input type="text" name="msg" id="msg" value="xiyue1"><br>
    消息级别:<select id="level" name="level">
                <option value="0">紧急</option>
                <option value="1">普通</option>
                <option value="2">延迟</option>
            </select><br>
    发送日期:<input type="text" id="pubdate" name="pubdate" value="2254-01-54"><br>
    消息标签:
        <input type="checkbox" name="tags" value="政治">政治
        <input type="checkbox" name="tags" value="经济">经济
        <input type="checkbox" name="tags" value="文化">文化
    <input type="submit" value="发送">
    <input type="reset" value="重置">
</form>
</body>
</html>

在这里插入图片描述

  • 修改src/main/java/com/xiyue/leaspring/action/EchoAction.java中的echo方法,进行参数接收与处理。
@RequestMapping("/input")//访问的路径为echo.action
public ModelAndView input(@RequestParam String msg
    , Integer level, String tags[], Date pubdate){//根据参数名称自动进行匹配处理
    ModelAndView modelAndView = new ModelAndView("/pages/message/show.jsp");
    modelAndView.addObject("echoMessage","【Echo】msg="+msg);
    modelAndView.addObject("echolevel","【Echo】level="+msg);
    modelAndView.addObject("echoTags","【Echo】tags="+ Arrays.toString(tags));
    modelAndView.addObject("echoPubDate","【Echo】pubdate="+pubdate);
    return modelAndView;
}
  • 针对日期需要进行处理src/main/java/com/xiyue/leaspring/action/EchoAction.java
@InitBinder
public void initBinder(WebDataBinder binder){//设置一个Web数据的绑定转换
    SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd");//定义转换处理
    //在web数据绑定中注册自定义规则绑定器,主要用于处理java.util.Date类型,允许为null
    binder.registerCustomEditor(java.util.Date.class,new CustomDateEditor(sdf,true));
}

可以在父类中定义转换处理。如果所有的Action程序类都需要重复定义,initBinder方法将会非常麻烦。为了提高代码重用性,可以建立一个统一的抽象类AbstractAction,在此类中定义initBinder方法,这样所有继承自AbstractAction的程序类都将具备日期数据处理支持。
4.新建src/main/webapp/pages/message/show.jsp显示数据。

<%@ page import="java.util.*" contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%--加入相应的jar才能编译jsp--%>
<%@page isELIgnored="false" %><%--启动EL表达式解析--%>
<html>
<head>
    <title>show_msg</title>
</head>
<%
    request.setCharacterEncoding("UTF-8");
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+
            request.getServerPort()+request.getContextPath()+"/";
%>
<body>
    <base href="<%=basePath%>">
    <H1>ECHO消息显示:${echoMessage}</H1>
    <H1>ECHO消息显示:${echolevel}</H1>
    <H1>ECHO消息显示:${echoTags}</H1>
    <H1>ECHO消息显示:${echoPubDate}</H1>
</body>
</html>

在这里插入图片描述

参数与对象转换

  • 在src/main/java/com/xiyue/leaspring/vo下新建部门与员工的VO类
package com.xiyue.leaspring.vo;

import java.io.Serializable;
import java.util.Date;

public class Dept implements Serializable {
    private Long deptno;
    private String dname;
    private Date createdate;

    public Long getDeptno() {
        return deptno;
    }

    public void setDeptno(Long deptno) {
        this.deptno = deptno;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    public Date getCreatedate() {
        return createdate;
    }

    public void setCreatedate(Date createdate) {
        this.createdate = createdate;
    }

    @Override
    public String toString() {
        return "Dept{" +
                "deptno=" + deptno +
                ", dname='" + dname + '\'' +
                ", createdate=" + createdate +
                '}';
    }
}
package com.xiyue.leaspring.vo;

import java.io.Serializable;
import java.util.Date;

public class Emp implements Serializable {
    private Long empno;
    private String ename;
    private Double salary;
    private Date hiredate;
    private Integer level;
    private Dept dept;

    public Long getEmpno() {
        return empno;
    }

    public void setEmpno(Long empno) {
        this.empno = empno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Double getSalary() {
        return salary;
    }

    public void setSalary(Double salary) {
        this.salary = salary;
    }

    public Date getHiredate() {
        return hiredate;
    }

    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }

    public Integer getLevel() {
        return level;
    }

    public void setLevel(Integer level) {
        this.level = level;
    }

    public Dept getDept() {
        return dept;
    }

    public void setDept(Dept dept) {
        this.dept = dept;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "empno=" + empno +
                ", ename='" + ename + '\'' +
                ", salary=" + salary +
                ", hiredate=" + hiredate +
                ", level=" + level +
                ", dept=" + dept +
                '}';
    }
}
  • 建立EmpAction程序类并且继承BaseAction(处理日期格式转换)
package com.xiyue.leaspring.action;

import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;

import java.text.SimpleDateFormat;

public abstract class BaseAction {

    @InitBinder
    public void initBinder(WebDataBinder binder){//设置一个Web数据的绑定转换
        SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd");//定义转换处理
        //在web数据绑定中注册自定义规则绑定器,主要用于处理java.util.Date类型,允许为null
        binder.registerCustomEditor(java.util.Date.class,new CustomDateEditor(sdf,true));
    }
}

package com.xiyue.leaspring.action;

import com.sun.org.apache.xpath.internal.operations.Mod;
import com.xiyue.leaspring.vo.Emp;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller//定义控制器
@RequestMapping("/emp")//定义父路径
@Slf4j//日志记录
public class EmpAction extends BaseAction{

    @RequestMapping("/addview")
    public String addView(){
        return "/pages/message/emp_add.jsp";//数据增加前进行跳转
    }

    @RequestMapping("/add")//新增方法路由
    public ModelAndView add(Emp emp){//请求参数
        log.info(emp.toString());//输出信息
        ModelAndView mav = new ModelAndView("/pages/message/emp.jsp");
        mav.addObject("emp",emp);//保存数据
        return mav;//
    }
}
  • 创建/pages/message/emp_add.jsp用于新增员工
<%--
  Created by IntelliJ IDEA.
  User: xiyue
  Date: 2021/3/31
  Time: 17:04
  To change this template use File | Settings | File Templates.
--%>
<%@ page import="java.util.*" contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%--加入相应的jar才能编译jsp--%>
<%@page isELIgnored="false" %><%--启动EL表达式解析--%>
<html>
<head>
    <title>show_msg</title>
</head>
<%
    request.setCharacterEncoding("UTF-8");
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+
            request.getServerPort()+request.getContextPath()+"/";
    String emp_add_url = basePath + "emp/add";
%>
<body>
<base href="<%=basePath%>">
    <form action="<%=emp_add_url%>" method="post">
        员工编号:<input type="text" name="empno" value="213123"><br>
        员工姓名:<input type="text" name="ename" value="xiyue"><br>
        基本工资:<input type="text" name="salary" value="12354.01"><br>
        雇佣日期:<input type="text" name="hiredate" value="1992-12-1"><br>
        员工等级:<input type="text" name="level" value="0"><br>
        部门编号:<input type="text" name="dept.deptno" value="10"><br>
        部门名称:<input type="text" name="dept.dname" value="财务部"><br>
        部门成立:<input type="text" name="dept.createdate" value="1978-02-12"><br>
        <input type="submit" value="添加">
        <input type="reset" value="重置">
    </form>
</body>
</html>

Emp内部的Dept实例可以通过"."的形式获取部门信息。
重新启动项目访问:http://localhost:8080/lea_springmvc/emp/addview
在这里插入图片描述

  • 创建/pages/message/emp_add.jsp用于显示员工信息
<%--
  Created by IntelliJ IDEA.
  User: xiyue
  Date: 2021/3/31
  Time: 17:04
  To change this template use File | Settings | File Templates.
--%>
<%@ page import="java.util.*" contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%--加入相应的jar才能编译jsp--%>
<%@page isELIgnored="false" %><%--启动EL表达式解析--%>
<html>
<head>
    <title>show_msg</title>
</head>
<%
    request.setCharacterEncoding("UTF-8");
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+
            request.getServerPort()+request.getContextPath()+"/";
%>
<body>
<base href="<%=basePath%>">
    <h3>员工详细信息</h3>
    员工编号:${emp.empno}<br>
    员工姓名:${emp.ename}<br>
    基本工资:${emp.salary}<br>
    雇佣日期:${emp.hiredate}<br>
    员工等级:${emp.level}<br>
    部门编号:${emp.dept.deptno}<br>
    部门名称:${emp.dept.dname}<br>
    部门成立:${emp.dept.createdate}<br>
</body>
</html>

重启项目访问:http://localhost:8080/lea_springmvc/emp/addview
在这里插入图片描述
输入参数点击“添加”按钮
在这里插入图片描述
结合SpringDataJPA效果会更好。本程序提供了请求参数与类对象的自动转换,如果项目数据层采用SpringDataJPA实现,则可以直接利用持久化对象PO类进行参数的接收,从而进一步简化程序代码。

Restful展示风格

restful是一种数据交换格式,在控制层方法上将数据以JSON结构形式进行返回,可以帮助开发者简化对象与JSON数据转化处理。
在SpringMVC中使用restful风格需要引入jackson相关依赖,同时在方法使用@ResponseBody注解。

  • 在pom.xml文件中添加jackson相关依赖。
<jackson.version>2.9.4</jackson.version>

<dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>${jackson.version}</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>${jackson.version}</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>${jackson.version}</version>
    </dependency>
  • EmpAction中添加返回集合的list()方法。
@RequestMapping("/list")
@ResponseBody
public Object list(){
    List<Emp> all = new ArrayList<>();
    for(int i=0;i<2;i++){
        Emp emp = new Emp();
        emp.setEmpno(123L+i);
        emp.setEname("小寒"+i);
        Dept dept = new Dept();
        dept.setDeptno(10L+i);
        dept.setDname("天马"+i);
        emp.setDept(dept);
        all.add(emp);
    }
    return all;
}

重启项目访问:http://localhost:8080/lea_springmvc/emp/list

在这里插入图片描述

获取内置对象

SpringMVC虽然为我们提供了自动化支持,但是很多情况下需要我们直接获取内置处理对象。获取内置对象的方式有两种:

  • 在控制器处理方法上直接定义要是用的内置对象(HttpServletRequest, HttpServletResponse)
package com.xiyue.leaspring.action;


import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

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

@Controller
@RequestMapping("/webobj")
@Slf4j
public class WebObjectAction {

    @RequestMapping("/showone")
    public ModelAndView showOne(HttpServletRequest request, HttpServletResponse response){
        log.info("**【REQUEST内置对象】ContextPath="+request.getContextPath());
        log.info("**【APPLICATION内置对象】RealPaTH="+request.getSession().getServletContext().getRealPath("/"));
        log.info("**【RESPONSE内置对象】Locale="+response.getLocale());
        log.info("**【SESSION内置对象】SessionID="+request.getSession().getId());
        return null;
    }
}

**【REQUEST内置对象】ContextPath=/lea_springmvc
**【APPLICATION内置对象】RealPaTH=D:\apache-tomcat-8.5.64\webapps\lea_springmvc\
**【RESPONSE内置对象】Locale=zh_CN
**【SESSION内置对象】SessionID=3DA5154E5BAF83909AD438E4120495D9
  • 利用org.springframework.web.context.request.RequestContextHolder请求上下文获得。
@RequestMapping("/showtwo")
public ModelAndView showTwo(){
     //通过RequestContextHolder类可以获得HttpServletRequest与HttpServletResponse
     HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
     HttpServletResponse response = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getResponse();
     HttpSession session = request.getSession();
     ServletContext context = request.getSession().getServletContext();
     log.info("**【REQUEST内置对象】ContextPath="+request.getContextPath());
     log.info("**【APPLICATION内置对象】RealPaTH="+request.getSession().getServletContext().getRealPath("/"));
     log.info("**【RESPONSE内置对象】Locale="+response.getLocale());
     log.info("**【SESSION内置对象】SessionID="+request.getSession().getId());
     return null;
 }
**【REQUEST内置对象】ContextPath=/lea_springmvc
**【APPLICATION内置对象】RealPaTH=D:\apache-tomcat-8.5.64\webapps\lea_springmvc\
**【RESPONSE内置对象】Locale=zh_CN
**【SESSION内置对象】SessionID=CC49A5DE7A0554AE4FB368A4D4E2D340

Web资源安全访问

安全问题作为软件开发中不可忽视的一个环节,web项目中WEB-INF是Web项目中安全级别最高的目录,所以可以将web资源统一放置在此目录下保留一个index首页,利用MVC设计模式,通过控制器跳转和ViewResolver(视图解析器)实现资源的访问。

  • 【lea-springmvc项目】将所有保存在Web目录下的资源(.jsp、.html、.css、.js、图片等)按照已有结构保存到WEB-INF目录中。
    在这里插入图片描述
  • 【lea-springmvc项目】修改spring-mvc.xml配置文件,追加ViewResolver配置。
 <!--启动一个视图访问的解析器,该操作会自动在容器中加载,不需要做任何的依赖配置-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/pages"/><!--定义路径前缀-->
  <property name="suffix" value=".jsp"/> <!--定义路径后缀-->
</bean>

为了便于页面路径定义,本程序使用了视图解析器。在控制器中编写访问路径时,可以省略其前缀以及后缀内容。

  • 【lea-springmvc项目】以EchoAction程序类为例,修改其跳转路径。
 @RequestMapping("/inputView1")//访问的路径为echo.action
 public String inputView1(){//根据参数名称自动进行匹配处理
     return "/pages/message/message_input1";
 }
 @RequestMapping("/input1")//访问的路径为echo.action
 public ModelAndView input1(@RequestParam String msg
         , Integer level, String tags[], Date pubdate){//根据参数名称自动进行匹配处理
     ModelAndView modelAndView = new ModelAndView("/pages/message/show");
     modelAndView.addObject("echoMessage","【Echo】msg="+msg);
     modelAndView.addObject("echolevel","【Echo】level="+level);
     modelAndView.addObject("echoTags","【Echo】tags="+ Arrays.toString(tags));
     modelAndView.addObject("echoPubDate","【Echo】pubdate="+pubdate);
     return modelAndView;
 }

本程序通过控制器实现页面跳转时,由于视图解析器的配置存在,所以只需要编写访问路径的中间部分即可。

  • 【lea-springmvc项目】一个Web项目里,除了JSP页面,还会有*.js、*.css、图片等文件。为了安全,同样要将其保存在WEB-INF目录中,并在Spring中进行静态资源访问映射。修改spring-mvc.xml文件。
 <!--进行静态web资源的映射路径配置-->
 <mvc:resources mapping="WEB-INF/js/" location="/mvcjs/**"/>
 <mvc:resources mapping="WEB-INF/css/" location="/mvccss/**"/>
 <mvc:resources mapping="WEB-INF/images/" location="/mvcimages/**"/>
  • 【lea-springmvc项目】修改web.xml配置文件,为DispatcherServlet增加一个新的访问路径,通过该路径实现静态资源的访问映射。
<servlet-mapping><!--SpringMVC设计中的路径都是以action结尾的-->
   <servlet-name>springmvc</servlet-name>
   <!--<url-pattern>*.action</url-pattern>--><!--拦截所有请求 小编在这里使用*.action使用无法获取资源修改为/就可以了-->
   <url-pattern>/</url-pattern><!--静态资源映射使用-->
 </servlet-mapping>
  • 【lea-springmvc项目】配置完成后,如果要导入一些静态资源,可以通过映射名称完成。
    在这里插入图片描述
    考虑到安全性问题所有的资源都应该进行上述配置。

读取资源文件

Web项目里,通常有大量的*.properties资源文件,例如文字提示信息(messages.properties)、跳转路径(pages.properties)、验证规则(validations.properties)等都在Spring配置文件中进行处理。

  • 【lea-springmvc项目】在src/main/resources源文件夹目录中创建一个新的包cn.xiyue.message。
  • 【lea-springmvc项目】在cn.xiyue.message包中建立两个资源文件。

在这里插入图片描述

  • 【lea-springmvc项目】修改spring-mvc.xml配置文件,追加资源映射。
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
   <property name="basenames"><!--定义所有BeanName名称-->
       <array>
           <value>com.xiyue.message.messages</value><!--资源定位-->
           <value>com.xiyue.message.pages</value><!--资源定位-->
       </array>
   </property>
</bean>

资源文件读取,使用ResourceBundleMessageSource类完成配置。此类是MessageSource接口的子类,在程序中可以利用MessageSource接口提供的方法实现资源读取。
在这里插入图片描述

  • 【lea-springmvc项目】在EchoAction程序类中注入MessageSource接口对象。
@Autowired
private MessageSource messageSource;
  • 【lea-springmvc项目】在EchoAction类中建立一个方法,进行资源读取测试。
 @GetMapping("/message")
 public ModelAndView messageView(){
     log.info("【echo.input.page】"+this.messageSource
             .getMessage("echo.input.page",null, Locale.getDefault()));
     log.info("【welcome.info】"+this.messageSource
             .getMessage("welcome.info",new Object[]{"喜悦"}, Locale.getDefault()));
     log.info("【noting】"+this.messageSource
             .getMessage("nothing",null, Locale.getDefault()));
     return null;
 }
【echo.input.page】message/message_input
【welcome.info】欢迎喜悦光临!

程序根据key实现了资源文件中数据的读取。读取时可利用对象数组,设置资源中占位符的数据。资源不存在时,会出现org.springframework.context.NoSuchMessageException异常。

文件上传

文件上传是Web开发中的重要环节,也是MVC开发框架必须支持的功能。SpringMVC中主要使用Apache FileUpload组件实现了文件的上传管理。下面通过具体步骤讲解SpringMVC的操作实现。

  • 【lea-springmvc项目】在pom.xml文件中配置上传组件依赖管理。
 <fileupload.version>1.4</fileupload.version>
 <commons-io.version>2.8.0</commons-io.version>
 
 <dependency>
   <groupId>commons-fileupload</groupId>
   <artifactId>commons-fileupload</artifactId>
   <version>${fileupload.version}</version>
 </dependency>
 <dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>${commons-io.version}</version>
  </dependency>
  • 【lea-springmvc项目】SpringMVC中,上传依赖CommonsMultipartResolver类进行处理,所以首先要在spring-mvc.xml配置文件中进行定义。
<!--配置FileUpload组件在SPring中整合的处理类-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--定义上传文件的最大字节大小,这里为5MB-->
        <property name="maxUploadSize" value="5242880"/>
        <!--定义上传文件允许的最大字节数,这里为2MB-->
        <property name="maxUploadSizePerFile" value="2097152"/>
        <!--定义内存中允许占用的最大字节数-->
        <property name="maxInMemorySize" value="10485760"/>
    </bean>
  • 【lea-springmvc项目】由于定义了上传限制,所以一旦上传出现问题,应跳转到错误页配置spring-mvc.xml与新建error.jsp。
 <!--配置错误的映射处理,出现指定的异常后,可以让其跳转到错误页src/main/webapp/WEB-INF/pages/errors.jsp-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings"><!--进行整体的错误映射-->
        <props><!--定义错误类型-->
            <prop key="org.springframework.web.multipart.MaxUploadSizeExceededException">
                errors
            </prop>
        </props>
    </property>
</bean>

项目中有视图解析机制,所以错误页/WEB-INF/pages/errors.jsp只需要配置errors即可(不需要编写前缀与后缀)。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h3>出错了</h3>
</body>
</html>
  • 5.【lea-springmvc项目】定义文件上传表单页面/pages/photo/photo_upload.jsp。
<%@ page pageEncoding="UTF-8" contentType="text/html;charset=UTF-8" language="java" %>
<%
    request.setCharacterEncoding("UTF-8");
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+
        request.getServerPort()+request.getContextPath()+"/";
    String photo_input_url = basePath+"/pages/upload";
%>
<html>
<head>
    <base href="<%=basePath%>">
</head>
<body>
    <form action="<%=photo_input_url%>" method="post" enctype="multipart/form-data">
        输入消息:<input type="text" name="msg" id="msg" value="www.xiyue.cn"><br>
        上传图片:<input type="file" name="photo" id="photo"><br>
        <input type="submit" value="发送">
    </form>
</body>
</html>
  • 【lea-springmvc项项目】定义UploadAction类,实现上传处理。文件的保存路径为/WEB-INF/upload。
package com.xiyue.leaspring.action;

import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

@Controller//定义控制器
@RequestMapping("/pages")//访问父路径,与方法中的路径进行组合为完整路径
public class UploadAction {//自定义Action程序类

    @GetMapping("/uploadview")
    public String uploadView() {
        return "/photo/photo_upload";//上传表单
    }

    @PostMapping("/upload")
    @ResponseBody
    public Object upload(String msg, MultipartFile photo) throws IOException {
        Map<String,Object> result = new HashMap<>();//保存结果
        result.put("msg",msg);//保存信息
        if(photo==null||photo.isEmpty()){
            result.put("photo-name","nophoto");//没有文件,保存默认名称
        }else {//现在有上传文件
            String photoName = UUID.randomUUID()+"."+ photo.getContentType()
                    .substring(photo.getContentType().lastIndexOf("/")+1);//创建保存文件名称
            String photoPath = ContextLoader.getCurrentWebApplicationContext().getServletContext()
                    .getRealPath(File.separator+"WEB-INF"+File.separator+"upload"+File.separator)+File.separator+photoName;//保存路径
            result.put("photo-size",photo.getSize());//保存文件上传信息
            result.put("photo-mime",photo.getContentType());//保存文件上传信息
            result.put("photo-name",photoName);//保存文件上传信息
            result.put("photo-path",photoPath);//保存文件上传信息
            File file = new File(photoPath);
            File filePath = new File(file.getParent());
            if(!filePath.exists() && !filePath.isDirectory()){
                filePath.mkdirs();
            }
            if(!file.exists()){
                file.createNewFile();
            }
            photo.transferTo(file);//进行文件转存
        }
        return  result;//返回文件信息
    }
}

程序使用MultipartFile实现了上传文件接收,并利用此类中提供的方法实现了上传文件的相关信息获取。为了简化理解,对于上传的处理结果,使用Restful风格返回了相应的文件信息与保存路径。
提示:关于上传错误处理。
本节程序实现了对上传的限制,并将项目发布到了Tomcat服务器中。如果此时开发者上传了一个非常大的文件,执行程序仍会出现错误(不跳转到错误页),同时会在控制台打印org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException异常信息。这是由Tomcat自身的上传限制导致的。要解决这个问题,需要取消Tomcat的上传限制。
范例:修改server.xml配置文件,取消上传限制。
在这里插入图片描述
本程序配置了maxSwallowSize="-1"属性,表示不再受上传限制。此时,SpringMVC的上传检测可以正常执行。

拦截器

拦截器是AOP概念的一种应用,即在用户发送请求与控制层接收请求之间追加一些拦截程序,以保证提交到Action中的请求数据的合法性。
在这里插入图片描述
用户发送请求到控制层的处理过程中,所有的请求路径依然只关心控制层的目标路径。拦截器就像Web过滤器一样,会根据配置自动执行处理。

在SpringMVC中,HandlerInterceptor是拦截器处理的标准父接口,子类只有实现此接口才可以实现拦截器功能。
在这里插入图片描述
在实际项目开发中,往往需要在请求处理前进行拦截,所以HandlerInterceptor接口的preHandle方法使用得较多。

提示:Spring版本不同,HandlerInterceptor接口也不同。
本书使用的是Spring 5.x开发版本,所以HandlerInterceptor接口中的方法全部使用default进行定义。在这之前,该接口的全部方法都是抽象方法,即子类必须要全部实现。

定义基础拦截器

要定义拦截器,首先需要有一个完整的请求处理操作,然后需要配置拦截器的执行路径,之后才可以观察到最终的执行结果。

提示:本例的程序基础模型。
本例需要进行完整的请求与回应处理,这里使用14.5节的程序,即雇员数据发送处理,通过此程序观察拦截器的运用。

  • 【lea-springmvc项目】创建一个拦截器类,此类实现HandlerInterceptor接口,并覆写接口全部方法。
package com.xiyue.leaspring.util.interceptor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

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

public class ValidationInterceptor implements HandlerInterceptor {//定义拦截器

    private Logger logger = LoggerFactory.getLogger(ValidationInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        this.logger.info("1.preHandle方法执行,"+handler.getClass());
        return true;//返回true,表示请求继承;返回flase,表示不执行后续的Action或拦截器
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        this.logger.info("2.postHandle方法执行,"+handler.getClass()+"、ModelAndView="+modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        this.logger.info("3.afterCompletion方法执行,"+handler.getClass());
    }
}

本程序中拦截器定义了3个方法,并在这3个方法中提供了一个重要的对象Object handler,为了方便观察,利用反射进行了此对象所属类的打印。

  • 【lea-springmvc项目】拦截器需要配置拦截路径后才可以正常执行。修改spring-mvc.xml文件,进行配置。
 <!--配置拦截器,可以定义多个-->
 <mvc:interceptors>
     <mvc:interceptor>
         <mvc:mapping path="/pages/**/*.action"/><!--拦截路径-->
         <bean class="com.xiyue.leaspring.util.interceptor.ValidationInterceptor"/><!--拦截器处理类-->
     </mvc:interceptor>
 </mvc:interceptors>

本程序配置了拦截路径,只要访问此路径,就会自动触发ValidationInterceptor拦截器的执行。

  • 【lea-springmvc项目】启动Web项目,访问/emp/addview路径,拦截器的执行结果如下。
21:16:49.332 [http-nio-8080-exec-5] INFO com.xiyue.leaspring.util.interceptor.ValidationInterceptor - 1.preHandle方法执行,class org.springframework.web.method.HandlerMethod
21:16:49.420 [http-nio-8080-exec-5] INFO com.xiyue.leaspring.action.EmpAction - Emp{empno=213123, ename='xiyue', salary=12354.01, hiredate=Tue Dec 01 00:00:00 CST 1992, level=0, dept=Dept{deptno=10, dname='财务部', createdate=Sun Feb 12 00:00:00 CST 1978}}
21:16:49.420 [http-nio-8080-exec-5] INFO com.xiyue.leaspring.util.interceptor.ValidationInterceptor - 2.postHandle方法执行,class org.springframework.web.method.HandlerMethod、ModelAndView=ModelAndView [view="/message/emp"; model={emp=Emp{empno=213123, ename='xiyue', salary=12354.01, hiredate=Tue Dec 01 00:00:00 CST 1992, level=0, dept=Dept{deptno=10, dname='财务部', createdate=Sun Feb 12 00:00:00 CST 1978}}, org.springframework.validation.BindingResult.emp=org.springframework.validation.BeanPropertyBindingResult: 0 errors}]
21:16:49.421 [http-nio-8080-exec-5] DEBUG org.springframework.web.servlet.view.JstlView - View name '/message/emp', model {emp=Emp{empno=213123, ename='xiyue', salary=12354.01, hiredate=Tue Dec 01 00:00:00 CST 1992, level=0, dept=Dept{deptno=10, dname='财务部', createdate=Sun Feb 12 00:00:00 CST 1978}}, org.springframework.validation.BindingResult.emp=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
21:16:49.421 [http-nio-8080-exec-5] DEBUG org.springframework.web.servlet.view.JstlView - Forwarding to [/WEB-INF/pages/message/emp.jsp]
21:16:49.844 [http-nio-8080-exec-5] INFO com.xiyue.leaspring.util.interceptor.ValidationInterceptor - 3.afterCompletion方法执行,class org.springframework.web.method.HandlerMethod

分析可以发现,拦截器中每个拦截方法里都有一个HandlerMethod类对象,该类是进行拦截处理的核心。

HandlerMethod类

实际开发中,为了确保进入控制器中的请求都是合法请求,定义最多的拦截器方法就是preHandle()。该方法中提供了3个对象:HttpServletRequest、HttpServletResponse和Object(HandlerMethod)。利用HandlerMethod类对象,可以获取请求访问的目标程序类的相关信息。
在这里插入图片描述
范例:【lea-springmvc项目】在拦截器中对HandlerMethod类对象进行信息输出。

package com.xiyue.leaspring.util.interceptor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

public class ValidationInterceptor implements HandlerInterceptor {//定义拦截器

    private Logger logger = LoggerFactory.getLogger(ValidationInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //this.logger.info("1.preHandle方法执行,"+handler.getClass());
        if (handler instanceof HandlerMethod) {//执行向下转型
            HandlerMethod handlerMethod = (HandlerMethod) handler;//强制类型转换
            this.logger.info("Action对象:" + ((HandlerMethod) handler).getBean());
            this.logger.info("Action类:" + ((HandlerMethod) handler).getBeanType());
            this.logger.info("Action方法:" + ((HandlerMethod) handler).getMethod());
        }
        return true;//返回true,表示请求继承;返回flase,表示不执行后续的Action或拦截器
    }
}

本程序定义的拦截器中只使用了preHandle方法。为了更好地观察HandlerMethod类的作用,将emp_add.jsp提交表单到EmpAction.add方法的拦截结果,如下所示。

Action对象:com.xiyue.leaspring.action.EmpAction@2d8ca9bc
Action类:class com.xiyue.leaspring.action.EmpAction
Action方法:public org.springframework.web.servlet.ModelAndView com.xiyue.leaspring.action.EmpAction.add(com.xiyue.leaspring.vo.Emp)

可以发现,开发者使用HandlerMethod类处理时,可以取得目标处理Action类的相关反射信息。如果想实现拦截控制,可以利用这些反射对象进行处理。

使用拦截器实现服务端请求验证

拦截器最大的作用是保证用户请求的正确性。例如,在某些控制器进行参数接收时,必须保证参数的格式正确。开发中最常用的参数类型有以下几种:字符串(string)、整数(int、long)、小数(double)、日期(date)、日期时间(datetime)、验证码(rand)、字符串数组(string[])、整数数组(int[]、long[])。抽象出常用的数据类型后,就可以实现验证规则的定义,而有了验证规则,就可以利用拦截器实现一个可重用的服务端数据验证组件的定义。
在这里插入图片描述
给出的设计结构中,实现的关键是MessageSource接口。Spring中可以利用该接口获取资源信息。

本程序一共使用3个资源资源文件。

  • validations.properties:采用“Action名称.方法名称”的形式保存执行本方法时所使用- 的验证规则结构。
  • messages.properties:保存提示信息以及验证出错后的信息。
  • pages.properties:保存所有的跳转路径与某一个控制器拦截器验证失败后的跳转路径。

验证规则的定义与获取

拦截器中可以利用HandlerMethod类获取目标Action的反射对象(Class、Method),利用反射对象可以获取类名称与方法名称。要进行服务端数据验证,最好的做法是按照如下格式进行定义。

Action类名称.控制器方法名称=属性:规则|属性:规则|…

对于采用的验证规则,可以在程序中使用字符串(string)、整数(int、long)、小数(double)、日期(date)、日期时间(datetime)、验证码(rand)、字符串数组(string[])、整数数组(int[]、long[])进行处理。

  • 【lea-springmvc项目】创建com.xiyue.leaspring.messages.validations.properties文件,保存验证规则。

com.xiyue.leaspring.action.EmpAction.add=empno:long|ename:string|salary:double|
hiredate:date|dept.deptno:long|dept.dname:string|dept.createdate:date

由于在Emp程序处理时,addview方法不需要执行验证,所以这里只定义了add方法的请求验证规则。

  • 【lea-springmvc项目】修改spring-mvc.xml配置文件,配置资源文件。
<!--配置SpringMVC里面需要使用的各项资源-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basenames"><!--定义所有BeanName名称-->
        <array>
            <value>com.xiyue.message.pages</value><!--资源定位-->
            <value>com.xiyue.message.messages</value><!--资源定位-->
            <value>com.xiyue.message.validations</value><!--资源定位-->
        </array>
    </property>
</bean>
  • 【lea-springmvc项目】在ValidationInterceptor类中注入MessageSource接口对象。
  @Autowired
  private MessageSource messageSource;//获取资源数据
  • 【lea-springmvc项目】ValidationInterceptor拦截器类的preHandle方法里拼凑资源key,以获取资源数据。
 @Override
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
     //this.logger.info("1.preHandle方法执行,"+handler.getClass());
     if (handler instanceof HandlerMethod) {//执行向下转型前应先判断是否是指定类的实例
         HandlerMethod handlerMethod = (HandlerMethod) handler;//强制类型转换
         String validationRuleKey = handlerMethod.getBeanType().getName()+"."+
                 handlerMethod.getMethod().getName();
         String validationRule = null;//保存要读取指定的资源key对应的验证规则
         try{
             validationRule = this.messageSource.getMessage(validationRuleKey,null,null);
         }catch (Exception c){}
         if (validationRule!=null){//验证处理操作,则需要进行验证处理
             this.logger.info("【验证规则-{"+request.getRequestURI()+"}】"+validationRule);
         }
     }
     return true;//返回true,表示请求继承;返回flase,表示不执行后续的Action或拦截器
 }
  • 【lea-springmvc项目】启动Web程序,由于只针对add方法绑定了验证规则,所以只有在访问add.action路径时才会出现如下提示信息。
【验证规则  -  {lea_springmvc/emp/add}】 
empno:long|ename:string|salary:double|hiredate:date|dept.deptno:long|dept.dname:string|dept.createdate:date 

这样就实现了资源文件保存验证规则的处理逻辑,而程序编写完成后,开发者在以后的开发过程中只需要清楚验证规则的配置,即可轻松实现服务端数据验证处理。

数据验证处理

数据验证处理主要是针对用户的请求参数发出的内容进行检测,在validations.properties文件中已经定义了所有可能提交到控制器中的参数信息,而此时程序只需要基于正则表达式实现验证处理即可。

  • 【lea-springmvc项目】在messages.properties配置文件中定义验证错误提示信息。
#*************************【验证规则信息开始】ValidationInterceptor拦截器使用*************************
validation.string.msg=该请求参数的内容不允许为空!
validation.int.msg=该请求参数的内容必须为整数!
validation.long.msg=该请求参数的内容必须为整数!
validation.double.msg=该请求参数的内容必须为小数!
validation.date.msg=该请求参数的内容必须为日期格式(yyyy-MM-dd)!
validation.datetime.msg=该请求参数的内容必须为日期时间格式(yyyy-MM-dd HH:mm:ss)!
validation.rand.msg=验证码输入错误,请核实后重新输入!
validation.string[].msg=该请求参数的内容不允许为空!
validation.int[].msg=该请求参数的内容必须为整数!
validation.long[].msg=该请求参数的内容必须为整数!
#*************************【验证规则信息结束】ValidationInterceptor拦截器使用*************************
  • 【lea-springmvc项目】验证错误后,需要指定错误页。修改pages.properties资源文件,定义错误页路径。
#***********定义公共错误页的跳转路径**************
error.page=/pages/error
#***********EmpAction相关跳转页面路径(验证失败路径)**************
com.xiyue.leaspring.action.EmpAction.add.error.page=/pages/emp/addvie

在本配置文件中,定义了两个错误页的信息。  
$ 某一控制层处理指定错误页,格式为“控制器类名称.方法名称.error.page=路径”,优先级高。  
$ 公共错误页,格式为“error.page=路径”,优先级低(在不配置具体错误页时生效)。

  • 【lea-springmvc项目】建立一个公共处理的Action控制器类,实现公共页面跳转配置。
package com.xiyue.leaspring.action;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller//定义控制器
public class CommonAction {//公共Action
    @RequestMapping("/pages/error")//访问路径
    public String error(){
        return "error";//跳转到/WEB-INF/pages/error.jsp
    }
}

此时开发者可以自行定义errors.jsp页面的错误提示信息。

  • 【lea-springmvc项目】为了减少拦截器中的操作代码,可以建立一个ActionValidationUtil验证类,该类主要针对各种给定的验证规则进行正则检测,同时将所有错误的验证信息保存在Map集合中。Map集合的key为参数名称,value就是messages.properties中保存的信息。
package com.xiyue.leaspring.util.validate;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * 实现Action数据的验证处理类
 */
public class ActionValidationUtil {

    private Logger logger = LoggerFactory.getLogger(ActionValidationUtil.class);//日志组件
    private Map<String,String> errors = new HashMap<>();//保存错误信息
    private String rule;//保存验证规则
    private HttpServletRequest request;//请求对象
    private MessageSource messageSource;//读取资源文件

    /**
     * 实例化Action数据验证工具类,在此类中可以直接实现数据验证以及错误信息保存
     * @param rule  要执行的数据验证规则
     * @param request 通过该参数可以取得用户的请求参数
     * @param messageSource 所有的消息资源的文字提示信息
     */
    public ActionValidationUtil(String rule,HttpServletRequest request,
                                MessageSource messageSource){
        this.rule = rule;//保存规则
        this.request = request;//保存request对象
        this.messageSource = messageSource;//保存messageSource对象
        this.handleValidator();//构造方法进行验证操作
    }

    /**
     * 实现验证的具体操作,根据指定的验证规则来获取验证数据以实现各个数据的检测处理
     */
    private void handleValidator() {
        String ruleResult[] = this.rule.split("\\|");//验证规则拆分
        for (int x=0;x<ruleResult.length;x++){//获取全部参数
            //第一个元素为请求参数、第二个为验证规则
            String temp[] = ruleResult[x].split(":");//获取每一个规则
            try {
                String paramterValue = this.request.getParameter(temp[0]);//根据参数获取数据
                switch (temp[1]){//验证处理操作
                    case "int":{//int规则
                        if(this.validateInt(paramterValue)){//没有验证通过
                            this.errors.put(temp[0],this.messageSource
                                    .getMessage("validation.int.msg",null,null));
                        }
                        break;
                    }
                    case "string":{//String规则
                        if(this.validateString(paramterValue)){//没有验证通过
                            this.errors.put(temp[0],this.messageSource
                                    .getMessage("validation.string.msg",null,null));
                        }
                        break;
                    }
                    case "double":{//double规则
                        if(this.validateDouble(paramterValue)){//没有验证通过
                            this.errors.put(temp[0],this.messageSource
                                    .getMessage("validation.double.msg",null,null));
                        }
                        break;
                    }
                    case "long":{//long规则
                        if(this.validateLong(paramterValue)){//没有验证通过
                            this.errors.put(temp[0],this.messageSource
                                    .getMessage("validation.long.msg",null,null));
                        }
                        break;
                    }
                    case "date":{//date规则
                        if(this.validateDate(paramterValue)){//没有验证通过
                            this.errors.put(temp[0],this.messageSource
                                    .getMessage("validation.date.msg",null,null));
                        }
                        break;
                    }
                    case "datetime":{//datetime规则
                        if(this.validateDatetime(paramterValue)){//没有验证通过
                            this.errors.put(temp[0],this.messageSource
                                    .getMessage("validation.datetime.msg",null,null));
                        }
                        break;
                    }
                    case "rand":{//rand规则
                        if(this.validateRand(paramterValue)){//没有验证通过
                            this.errors.put(temp[0],this.messageSource
                                    .getMessage("validation.rand.msg",null,null));
                        }
                        break;
                    }
                    case "string[]":{//stringp[]规则
                        if(this.validateStringArray(this.request.getParameterValues(temp[0]))){//没有验证通过
                            this.errors.put(temp[0],this.messageSource
                                    .getMessage("validation.string[].msg",null,null));
                        }
                        break;
                    }
                    case "long[]":{//long[]规则
                        if(this.validateLongArray(this.request.getParameterValues(temp[0]))){//没有验证通过
                            this.errors.put(temp[0],this.messageSource
                                    .getMessage("validation.long[].msg",null,null));
                        }
                        break;
                    }
                    case "int[]":{//int[]规则
                        if(this.validateIntArray(this.request.getParameterValues(temp[0]))){//没有验证通过
                            this.errors.put(temp[0],this.messageSource
                                    .getMessage("validation.int[].msg",null,null));
                        }
                        break;
                    }
                }
            }catch (Exception e){
                this.logger.error(e.toString());
            }
        }
    }

    private boolean validateIntArray(String paramterValue[]) {
        if(this.validateStringArray(paramterValue)){//验证内容是否为空
            for (int x=0;x<paramterValue.length;x++){
                if(this.validateString(paramterValue[x])){
                    if(!paramterValue[x].matches("\\d+")) {//没有验证通过
                        return false;//验证失败
                    }
                }else{
                    return false;//有内容为空
                }
            }
        }
        return false;
    }

    /**
     * 验证指定的字符串是否由数字所组成
     * @param paramterValue 字符串
     * @return 如果全部由数据所组成返回true
     */
    private boolean validateLongArray(String paramterValue[]) {
        if(this.validateStringArray(paramterValue)){//验证内容是否为空
            for (int x=0;x<paramterValue.length;x++){
                if(this.validateString(paramterValue[x])){
                    if(!paramterValue[x].matches("\\d+")) {//没有验证通过
                        return false;//验证失败
                    }
                }else{
                    return false;//有内容为空
                }
            }
        }
        return false;//验证失败
    }

    /**
     * 验证指定的字符串是否为空(null和“”)
     * @param paramterValue 字符串
     * @return 如果不为空返回true,为空返回false
     */
    private boolean validateStringArray(String paramterValue[]) {
        if(paramterValue==null || "".equals(paramterValue)){//数据是否为空
            return false;//验证失败
        }else{//验证内容是否为空
            for (int x=0;x<paramterValue.length;x++){
                if(paramterValue[x]==null || "".equals(paramterValue[x])){//检测每一个数据
                    return false;
                }
            }
        }
        return false;
    }

    /**
     * 验证指定的字符串是否与指定的验证码相符合
     * @param paramterValue 字符串
     * @return 如果全部由数据所组成返回true
     */
    private boolean validateRand(String paramterValue) {
        String rand = (String) this.request.getSession().getAttribute("rand");//获取验证码
        if(this.validateString(paramterValue) && this.validateString(rand)){//检验是否为空
            return paramterValue.equalsIgnoreCase(rand);//验证码检测
        }
        return false;//验证失败
    }

    /**
     * 验证指定的字符串是否为日期时间格式
     * @param paramterValue 字符串
     * @return 如果全部由数据所组成返回true
     */
    private boolean validateDatetime(String paramterValue) {
        if(this.validateString(paramterValue)){//检验是否为空
            return paramterValue.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}");//正则验证
        }
        return false;//验证失败
    }

    /**
     * 验证指定的字符串是否为日期格式
     * @param paramterValue 字符串
     * @return 如果全部由数据所组成返回true
     */
    private boolean validateDate(String paramterValue) {
        if(this.validateString(paramterValue)){//检验是否为空
            return paramterValue.matches("\\d{4}-\\d{2}-\\d{2}");//正则验证
        }
        return false;//验证失败
    }

    /**
     * 验证指定的字符串是否由数字所组成
     * @param paramterValue 字符串
     * @return 如果全部由数据所组成返回true
     */
    private boolean validateLong(String paramterValue) {
        if(this.validateString(paramterValue)){//检验是否为空
            return paramterValue.matches("\\d+");//正则验证是否数字
        }
        return false;//验证失败
    }

    /**
     * 验证指定的字符串是否由数字所组成
     * @param paramterValue 字符串
     * @return 如果全部由数据所组成返回true
     */
    private boolean validateDouble(String paramterValue) {
        if(this.validateString(paramterValue)){//检验是否为空
            return paramterValue.matches("\\d+(\\.\\d+)");//正则验证
        }
        return false;//验证失败
    }
    /**
     * 验证指定的字符串是否为空(null和“”)
     * @param paramterValue 字符串
     * @return 如果不为空返回true,为空返回false
     */
    private boolean validateString(String paramterValue) {
        if(paramterValue==null || "".equals(paramterValue)){//数据是否为空
            return false;//验证失败
        }
        return  true;//验证成功
    }

    private boolean validateInt(String paramterValue) {
        if(this.validateString(paramterValue)){//检验是否为空
            return paramterValue.matches("\\d+");//检测是否为数字
        }
        return false;//验证失败
    }

    /**
     * 获取全部的错误信息,如果没有错误则集合的长度为0
     * @return 错误内容
     */
    public Map<String,String> getErrors(){//返回错误信息
        return errors;
    }
}

  • 【lea-springmvc项目】在ValidationInterceptor拦截器中使用ActionValidationUtil进行验证处理。
Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //this.logger.info("1.preHandle方法执行,"+handler.getClass());
        if (handler instanceof HandlerMethod) {//执行向下转型前应先判断是否是指定类的实例
            HandlerMethod handlerMethod = (HandlerMethod) handler;//强制类型转换
            String validationRuleKey = handlerMethod.getBeanType().getName()+"."+
                    handlerMethod.getMethod().getName();
            String validationRule = null;//保存要读取指定的资源key对应的验证规则
            try{//指定的key不存在,则不需要验证
                validationRule = this.messageSource.getMessage(validationRuleKey,null,null);
            }catch (Exception c){}
            if (validationRule!=null){//验证处理操作,则需要进行验证处理
                this.logger.info("【验证规则-{"+request.getRequestURI()+"}】"+validationRule);
                ActionValidationUtil avu = new ActionValidationUtil(validationRule,request
                ,this.messageSource);
                if(avu.getErrors().size()>0){//如果有错误信息
                    request.setAttribute("errors",avu.getErrors());//保存错误信息
                    String errorPage = null;//错误页
                    try {//获取但该案访问错误页
                        errorPage = this.messageSource.getMessage(validationRuleKey+
                                ".error.page",null,null);
                    }catch (Exception e){//如果无指定路径,跳转到公共errorPage
                        errorPage = this.messageSource.getMessage("error.page",
                                null,null);
                    }//跳转到错误页
                    request.getRequestDispatcher(errorPage).forward(request,response);
                    return  false;//请求拦截
                }
            }
        }
        return true;//返回true,表示请求继承;返回flase,表示不执行后续的Action或拦截器
    }

本程序实现了服务端验证操作,利用获取的验证规则与ActionValidationUtil类检测后,如果发现没有错误信息则表示验证通过,如果发现有错误信息则会按照配置跳转到错误页(可以是具体的某一个错误页或公共错误页)。

验证上传文件类型

在进行验证时,除了要针对提交参数验证,也有可能需要针对上传文件进行验证处理。例如,在进行上传时只允许接收图片文件,对于此类的操作也应该在拦截器中进行处理。

在使用preHandle方法处理Action请求前,可以在此方法中获取HttpServletRequest接口对象。但是对于这时的接口对象,如果用户请求没有进行表单封装,那么采用的是Tomcat实现类org.apache.catalina.connector.RequestFacade实例化。而如果有表单封装,则使用的是org.springframework.web.multipart.support.DefaultMultipartHttpServletRequest类进行实例化。此时可以在拦截器中利用当前request对象是否为DefaultMultipartHttpServletRequest类型,来实现是否有表单封装的判断,从而可以进一步获取上传文件的MIME类型数据与既定的规则进行验证。

  • 【lea-springmvc项目】修改validations.properties文件,定义公共MIME与Action指定MIME规则。
# 公共MIME规则 定义公共的验证规则配置
mime.rule=image/bmp|image/png|image/jpg|image/jpeg|image/gif
# Action指定规则 为某一个Action处理请求单独定义MIME规则
com.xiyue.leaspring.action.EchoAction.add.mime.rule=image/png|image/bmp

在定义的两个MIME验证规则里面,公共的MIME规则优先级会比具体的MIME规则低。

  • 【lea-springmvc项目】当上传文件违反了MIME规则后应该显示错误,修改messages.properties资源文件,追加MIME验证失败的提示信息。
validation.mime.msg=该上传文件不符合上传规则,所以无法进行接收处理!
  • 【lea-springmvc项目】建立ActionMIMEValidationUtil类型验证工具类,检测上传文件类型。
package com.xiyue.leaspring.util.validate;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.support.DefaultMultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * 实现Action上传文件验证的处理规则
 */
public class ActionMIMEValidationUtil {

    private Logger logger = LoggerFactory.getLogger(ActionValidationUtil.class);//日志组件
    private Map<String,String> errors = new HashMap<>();//保存错误信息
    private String rule;//保存验证规则
    private MultipartHttpServletRequest request;//请求对象
    private MessageSource messageSource;//读取资源文件

    /**
     * 实例化Action数据验证工具类,在此类中可以直接实现数据验证以及错误信息保存
     * @param rule  要执行的数据验证规则
     * @param request 通过该参数可以取得用户的请求参数
     * @param messageSource 所有的消息资源的文字提示信息
     */
    public ActionMIMEValidationUtil(String rule,HttpServletRequest request,
                                MessageSource messageSource){
        this.rule = rule;//保存规则
        this.messageSource = messageSource;//保存messageSource对象
        if(request instanceof DefaultMultipartHttpServletRequest){
            this.request = (DefaultMultipartHttpServletRequest)request;//保存request对象包含所有的上传信息
            this.handleValidator();//构造方法进行验证操作
        }
    }

    /**
     * 实现验证的具体操作,根据指定的验证规则来获取验证数据以实现各个数据的检测处理
     */
    private void handleValidator() {
        String ruleResult[] = this.rule.split("\\|");//验证规则拆分
        for (int x=0;x<ruleResult.length;x++){//获取全部参数
            try {
                Map<String, MultipartFile> fileMap = this.request.getFileMap();//接收文件
                if(fileMap.size()>0){//确有上传内容
                    Iterator<Map.Entry<String,MultipartFile>> iter = fileMap.entrySet().iterator();
                    while (iter.hasNext()){//迭代上传文件
                        Map.Entry<String,MultipartFile> me = iter.next();
                        if(me.getValue().getSize()>0){//有文件上传
                            if(!this.validateMime(me.getValue()
                                    .getContentType(),this.rule)){//不符合逻辑
                                this.errors.put(me.getKey(),this.messageSource
                                        .getMessage("validation.mime.msg",null,null));
                            }
                        }
                    }
                }
            }catch (Exception e){
                this.logger.error(e.toString());
            }
        }
    }

    /**
     * 如果当前传递MIME类型符合定义范围,则表示允许上传
     * @param contentType 当前传递文件的规则
     * @param rule 所有满足的验证规则
     * @return 如果验证通过返回true,否则返回false
     */
    private boolean validateMime(String contentType, String rule) {
        if (contentType == null || "".equals(rule)){//上传文件是否有MIME类型
            return false;//验证失败
        }
        String rules[] = rule.split("\\|");//拆分规则
        for (int x=0;x<rules.length;x++){//检测规则
            if(contentType.equals(rules[x])){
                return true;//验证通过
            }
        }
        return false;//验证失败
    }

    /**
     * 获取全部的错误信息,如果没有错误则集合的长度为0
     * @return 错误内容
     */
    public Map<String,String> getErrors(){//返回错误信息
        return errors;
    }
}
  • 【lea-springmvc项目】修改ValidationInterceptor程序类,在基本数据验证通过后再进行上传文件类型验证。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    //this.logger.info("1.preHandle方法执行,"+handler.getClass());
    if (handler instanceof HandlerMethod) {//执行向下转型前应先判断是否是指定类的实例
        HandlerMethod handlerMethod = (HandlerMethod) handler;//强制类型转换
        String validationRuleKey = handlerMethod.getBeanType().getName()+"."+
                handlerMethod.getMethod().getName();
        String validationRule = null;//保存要读取指定的资源key对应的验证规则
        try{//指定的key不存在,则不需要验证
            validationRule = this.messageSource.getMessage(validationRuleKey,null,null);
        }catch (Exception c){}
        if (validationRule!=null){//验证处理操作,则需要进行验证处理
            this.logger.info("【验证规则-{"+request.getRequestURI()+"}】"+validationRule);
            ActionValidationUtil avu = new ActionValidationUtil(validationRule,request
            ,this.messageSource);
            String errorPage = null;//错误页
            try {//获取但该案访问错误页
                errorPage = this.messageSource.getMessage(validationRuleKey+
                        ".error.page",null,null);
            }catch (Exception e){//如果无指定路径,跳转到公共errorPage
                errorPage = this.messageSource.getMessage("error.page",
                        null,null);
            }
            if(avu.getErrors().size()>0){//如果有错误信息
                request.setAttribute("errors",avu.getErrors());//保存错误信息
                //跳转到错误页
                request.getRequestDispatcher(errorPage).forward(request,response);
                return  false;//请求拦截
            }else {
                if(request instanceof DefaultMultipartHttpServletRequest){//有上传
                    String mimeRule = null;
                    try {//获取文件规则,如果没有,则使用公共规则
                        mimeRule = this.messageSource
                                .getMessage(validationRuleKey+".mine.rule",null,null);
                    }catch (Exception e){
                        mimeRule = this.messageSource
                                .getMessage("mine.rule",null,null);
                    }
                    ActionMIMEValidationUtil amvu = new ActionMIMEValidationUtil(
                            mimeRule,request,this.messageSource);
                    if (amvu.getErrors().size()>0){//有错误
                        request.setAttribute("errors",amvu.getErrors());
                        request.getRequestDispatcher(errorPage).forward(request,response);
                    }
                }
            }
        }
    }
    return true;//返回true,表示请求继承;返回flase,表示不执行后续的Action或拦截器
}

本程序在之前数据验证的基础上追加了上传文件检测,在上传文件格式出现问题时将跳转到已有的错误页上,同时继续使用errors保存错误信息。

Spring综合案例

清楚了SpringMVC的核心组成后,下面将通过一个完整案例介绍SpringMVC与SpringDataJPA的整合开发。
前端程序是基于Bootstrap完成的,因此这里已经配置好了JS客户端表单验证,即此时前端可以依靠JS验证,后端程序可以使用拦截器进行服务端验证。
本例将实现一个商品管理程序,其数据库结构如下:
在这里插入图片描述
本程序中将存在一对多、多对多两种数据关系。

  • 一对多关联:所有的商品都对应有商品分类,一个商品分类可以保存有多个商品信息。
  • 多对多关联:每个商品都拥有商品标签,一个标签可以属于多个商品,一个商品可以有多个标签。

范例:【lea-springmvc项目】数据库创建脚本(包含有表结构与测试数据)。

DROP DATABASE IF EXISTS lea-springmvc;
CREATE DATABASE lea-springmvc CHARACTER SET utf8;
USE lea-springmvc;
CREATE TABLE item(
    iid BIGINT  AUTO_INCREMENT,
    title VARCHAR(50),
    CONSTRAINT pk_iid PRIMARY KEY(iid)
);
CREATE TABLE tag(
    tid BIGINT  AUTO_INCREMENT,
    title VARCHAR(50),
    CONSTRAINT pk_tid PRIMARY KEY(tid)
);
CREATE TABLE goods(
    gid BIGINT AUTO_INCREMENT,
    name VARCHAR(50),
    price DOUBLE,
    photo VARCHAR(100),
    dflag INT,
    iid BIGINT,
    CONSTRAINT pk_gid10 PRIMARY KEY(gid),
    CONSTRAINT fk_iid FOREIGN KEY(iid) REFERENCES item(iid)
);
CREATE TABLE good_tag (
    gid BIGINT,
    tid BIGINT,
    CONSTRAINT fk_gid11 FOREIGN KEY(gid) REFERENCES goods(gid)ON DELETE CASCADE,
    CONSTRAINT fk_tid11 FOREIGN KEY(tid) REFERENCES tag(tid)  
);
--测试数据
INSERT INTO item(title)VALUES("图书音像");
INSERT INTO item(title)VALUES("办公用品");
INSERT INTO item(title)VALUES("家居生活");
INSERT INTO item(title)VALUES("厨房家电");
INSERT INTO item(title)VALUES("电子设备");
INSERT INTO tag(title)VALUES("高端");
INSERT INTO tag(title)VALUES("奢华");
INSERT INTO tag(title)VALUES("性价比高");
INSERT INTO tag(title)VALUES("免费");
INSERT INTO tag(title)VALUES("耐用");
--提交事务
COMMIT; 

本数据表在进行商品信息删除时,使用了逻辑删除。如果商品表(goods表)中的dflag内容为1(表示为true),则不再进行商品信息显示。

搭建项目开发环境

在这里插入图片描述
在定义Action时,为了减少重复代码,创建了一个AbstractAction抽象类,利用此类可实现控制层方法重用。

  • 【lea-springmvc项目】定义抽象Action程序类。
package com.xiyue.leaspring.action;

import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;

import java.text.SimpleDateFormat;

/**
 * 定义公共的Action抽象类,定义Action可重用方法
 */
public abstract class AbstractAction {//该类需要被子类继承

    @InitBinder
    public void initBinder(WebDataBinder binder){//设置一个Web数据的绑定转换
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");//定义转换处理
        //在web数据绑定中注册自定义规则绑定器,主要用于处理java.util.Date类型,允许为null
        binder.registerCustomEditor(java.util.Date.class,new CustomDateEditor(sdf,true));
    }
}

考虑到业务层也可能提供重复处理操作,所以本处提供了一个AbstractService抽象类,此类中暂不定义任何方法。

  • 【lea-springmvc项目】定义抽象业务实现类。
package com.xiyue.leaspring.service;

public abstract class AbstractService {
}

在以后定义业务层接口实现类时,除了要实现业务接口,也将继承此抽象类。

  • 【lea-springmvc项目】本项目用到了Bootstrap、jQuery等前端开发框架,同时也有一些静态资源需要进行映射。修改spring-mvc.xml配置文件,实现映射定义。
<!--进行静态web资源的映射路径配置-->
<mvc:resources mapping="WEB-INF/js/" location="/js/**"/>
<mvc:resources mapping="WEB-INF/css/" location="/css/**"/>
<mvc:resources mapping="WEB-INF/images/" location="/images/**"/>
<mvc:resources mapping="WEB-INF/jquery/" location="/jquery/**"/>
<mvc:resources mapping="WEB-INF/bootstrap/" location="/bootstrap/**"/>
<mvc:resources mapping="WEB-INF/upload/" location="/upload/**"/>
  • 【lea-springmvc项目】定义3个持久化类,同时进行映射关系配置。
     定义Item.java持久化类。
package com.xiyue.leaspring.vo;

import javax.persistence.*;
import java.io.Serializable;
import java.util.List;

@Entity
@Table(name="item")
public class Item implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long iid;
    private String title;
    @OneToMany(mappedBy = "item")//一对多关联
    private List<Goods> goodses;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public List<Goods> getGoodses() {
        return goodses;
    }

    public void setGoodses(List<Goods> goodses) {
        this.goodses = goodses;
    }

    public void setIid(Long iid) {
        this.iid = iid;
    }

    public Long getIid() {
        return iid;
    }

    @Override
    public String toString() {
        return "Item{" +
                "iid=" + iid +
                ", title='" + title + '\'' +
                ", goodses=" + goodses +
                '}';
    }
}

定义Tag.java持久化类。

package com.xiyue.leaspring.vo;

import javax.persistence.*;
import java.io.Serializable;
import java.util.List;

@Entity
@Table(name="tag")
public class Tag implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long tid;
    private String title;
    @ManyToMany(mappedBy = "tags",fetch = FetchType.LAZY)
    private List<Goods> goodses;

    public Long getTid() {
        return tid;
    }

    public void setTid(Long tid) {
        this.tid = tid;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public List<Goods> getGoodses() {
        return goodses;
    }

    public void setGoodses(List<Goods> goodses) {
        this.goodses = goodses;
    }

    @Override
    public String toString() {
        return "Tag{" +
                "tid=" + tid +
                ", title='" + title + '\'' +
                ", goodses=" + goodses +
                '}';
    }
}

定义Goods.java持久化类。

package com.xiyue.leaspring.vo;

import javax.persistence.*;
import java.io.Serializable;
import java.util.List;

@Entity
@Table(name="goods")
public class Goods implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long gid;
    private String name;
    private Double price;
    private String photo;
    private Integer dflag;
    @ManyToOne(fetch = FetchType.LAZY)//延迟加载
    @JoinColumn(name = "iid")//设置关联字段
    private Item item;
    @ManyToMany(fetch = FetchType.LAZY)//启用延迟加载
    @JoinTable(
            name = "good_tag",
            joinColumns = {@JoinColumn(name = "gid")},
            inverseJoinColumns = {
                    @JoinColumn(name = "tid")
            }
    )
    private List<Tag> tags;

    public Long getGid() {
        return gid;
    }

    public void setGid(Long gid) {
        this.gid = gid;
    }

    public String getName() {
        return name;
    }

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

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public String getPhoto() {
        return photo;
    }

    public void setPhoto(String photo) {
        this.photo = photo;
    }

    public Integer getDflag() {
        return dflag;
    }

    public void setDflag(Integer dflag) {
        this.dflag = dflag;
    }

    public Item getItem() {
        return item;
    }

    public void setItem(Item item) {
        this.item = item;
    }

    public List<Tag> getTags() {
        return tags;
    }

    public void setTags(List<Tag> tags) {
        this.tags = tags;
    }

    @Override
    public String toString() {
        return "Goods{}";
    }
}
  • 【lea-springmvc项目】定义数据层接口,所有的数据层接口继承JpaRepository父接口。
     定义IItemDAO数据层接口。
package com.xiyue.leaspring.dao;

import com.xiyue.leaspring.vo.Item;
import org.springframework.data.jpa.repository.JpaRepository;

public interface IItemDAO extends JpaRepository<Item,Long> {
}

定义ITagDAO数据层接口。

package com.xiyue.leaspring.dao;

import com.xiyue.leaspring.vo.Tag;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ITagDAO extends JpaRepository<Tag,Long> {
}

定义IGoodsDAO数据层接口。

package com.xiyue.leaspring.dao;

import com.xiyue.leaspring.vo.Goods;
import org.springframework.data.jpa.repository.JpaRepository;

public interface IGoodsDAO extends JpaRepository<Goods,Long> {
}

基本环境与程序配置搭建完成后,就可以依据此结构逐步实现各项功能了。

商品信息增加页面

商品信息增加时需要提供一个表单页面,提供商品所属分类编号与商品标签信息。其中,商品分类信息要通过item表获取,商品标签信息要通过tag表获取。

  • 【lea-springmvc项目】定义IGoodsService业务接口,提供商品增加前的数据查询操作。
package com.xiyue.leaspring.service;

import java.util.Map;

public interface IGoodsService {

    /**
     * 进行商品添加前的数据查询操作
     * @return 返回的数据包含有如下内容:
     *  key=allItems,value=所有的商品分类
     *  key=allTags,value=所有的商品标签
     */
    public Map<String,Object> preAdd();
}
  • 【lea-springmvc项目】建立IGoodsService业务接口子类GoodsServiceImpl,该类继承自AbstractService父类。
package com.xiyue.leaspring.service;

import com.xiyue.leaspring.dao.IGoodsDAO;
import com.xiyue.leaspring.dao.IItemDAO;
import com.xiyue.leaspring.dao.ITagDAO;
import com.xiyue.leaspring.vo.Item;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

@Service
public class GoodsServiceImpl extends AbstractService implements IGoodsService{

    @Autowired
    private IItemDAO iItemDAO;//注入IItemDAO数据接口实例
    @Autowired
    private ITagDAO tagDAO;//注入ITagDAO数据接口实例
    @Autowired
    private IGoodsDAO goodsDAO;//注入IGoodsDAO数据接口实例

    @Override
    public Map<String, Object> preAdd() {
        Map<String,Object> map = new HashMap<>();
        map.put("allItems",this.iItemDAO.findAll());
        map.put("allTags",this.tagDAO.findAll());
        return map;
    }
}
  • 【lea-springmvc项目】建立GoodsAction程序类,注入IGoodsService业务接口实例,定义商品增加前的相关数据查询,并将查询结果保存到request属性范围中。
package com.xiyue.leaspring.action;

import com.xiyue.leaspring.service.IGoodsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import java.util.Map;

@Controller//定义控制器
@RequestMapping("/pages/back/admin")//父路径
public class GoodsAction extends AbstractAction{//自定义Action程序类

    private Logger logger = LoggerFactory.getLogger(GoodsAction.class);//日志记录

    @Autowired
    private IGoodsService goodsService;

    @RequestMapping("/good_add")
    public ModelAndView addView(){
        Map<String,Object> map = this.goodsService.preAdd();
        this.logger.info("商品信息增加前信息查询:"+map);
        ModelAndView mav = new ModelAndView("/back/admin/goods/goods_add");
        mav.addAllObjects(map);//保存商品分类与标签数据
        return mav;
    }

}
  • 【lea-springmvc项目】定义/pages/back/admin/goods/goods_add.jsp页面,进行商品分类与商品标签信息迭代输出。
    迭代输出商品分类信息。
<div class="form-group" id="iidDiv">
    <%--@declare id="iid"--%>
    <label class="col-md-2 control-label" for="iid">商品分类:</label>
    <div class="col-md-5">
        <select id="iid" name="iid" class="form-control">
            <option value="">**请选择商品所属分类**</option>
            <c:forEach items="${allItems}" var="item">
                <option value="${item.iid}">${item.title}</option>
            </c:forEach>
        </select>
    </div>
    <span class="col-md-5" id="iidSpan">*</span>
</div>

迭代输出商品标签信息。

<div class="form-group" id="tagDiv">
    <%--@declare id="tag"--%>
    <label class="col-md-2 control-label" for="tag">商品标签:</label>
    <div class="col-md-5">
        <c:forEach items="${allTags}" var="tag">
            <div class="col-md-3">
                <div class="checkbox">
                    <label><input type="checkbox" id="tid" name="tid"
                        value="${tag.tid}"/>${tag.title}</label>
                </div>
            </div>
        </c:forEach>
    </div>
    <span class="col-md-5" id="tidSpan">*</span>
</div>

本页面依靠IGoodsService业务层中的preAdd方法进行了相关信息查询,页面运行效果.

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@page isELIgnored="false" %><%--启动EL表达式解析--%>
<%
    request.setCharacterEncoding("UTF-8");
    String path = request.getContextPath();
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<html>
<head>
    <base href="<%=basePath%>">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>新增商品</title>
    <link rel="stylesheet" type="text/css" href="<%=basePath%>/bootstrap/css/bootstrap.min.css">
    <!-- href:里面的路径是你导入在static文件夹里面下面bootstrap.min.css所在的路径,下面两个属性一样 -->
    <script type="text/javascript" src="<%=basePath%>/jquery/jquery-3.6.0.min.js"></script>
    <script type="text/javascript" src="<%=basePath%>/bootstrap/js/bootstrap.min.js"></script>

</head>
<body class="panel-body">
    <div class="container-fluid">
        <form class="form-horizontal">
            <div class="form-group" id="nameDiv">
                <%--@declare id="name"--%>
                <label class="col-md-2 control-label" for="name">商品名称:</label>
                <div class="col-md-5">
                    <input type="text" class="form-control" id="name" name="name" placeholder="请填写商品名称">
                </div>
                <span class="col-md-5" id="nameSpan">*</span>
            </div>
            <div class="form-group" id="priceDiv">
                <%--@declare id="price"--%>
                <label class="col-md-2 control-label" for="price">商品价格:</label>
                <div class="col-md-5">
                    <input type="text" class="form-control" id="price" name="price" placeholder="请填写商品单价">
                </div>
                <span class="col-md-5" id="priceSpan">*</span>
            </div>
            <div class="form-group" id="iidDiv">
                <%--@declare id="iid"--%>
                <label class="col-md-2 control-label" for="iid">商品分类:</label>
                <div class="col-md-5">
                    <select id="iid" name="iid" class="form-control">
                        <option value="">**请选择商品所属分类**</option>
                        <c:forEach items="${allItems}" var="item">
                            <option value="${item.iid}">${item.title}</option>
                        </c:forEach>
                    </select>
                </div>
                <span class="col-md-5" id="iidSpan">*</span>
            </div>
            <div class="form-group" id="tagDiv">
                <%--@declare id="tag"--%>
                <label class="col-md-2 control-label" for="tag">商品标签:</label>
                <div class="col-md-5">
                    <c:forEach items="${allTags}" var="tag">
                        <div class="col-md-3">
                            <div class="checkbox">
                                <label><input type="checkbox" id="tid" name="tid"
                                              value="${tag.tid}"/>${tag.title}</label>
                            </div>
                        </div>
                    </c:forEach>
                </div>
                <span class="col-md-5" id="tidSpan">*</span>
            </div>
            <div class="form-group" id="photoDiv">
                <%--@declare id="photo"--%>
                <label class="col-md-2 control-label" for="photo">商品图片:</label>
                <div class="col-md-5">
                    <input type="file" class="form-control" id="photo" name="photo">
                </div>
                <span class="col-md-5" id="priceSpan">*</span>
            </div>
            <div class="form-group">
                <div class="col-md-offset-3">
                    <button type="button" class="btn btn-primary">提交</button>
                    <button type="button" class="btn btn-default">重置</button>
                    <span class="">商品列表</span>
                </div>
            </div>
        </form>
    </div>
</body>
</html>

在这里插入图片描述

商品信息保存

商品信息需要通过表单进行输入,添加商品信息时需要上传图片,此时需要对图片类型进行验证。同时,所有上传的文件需要保存在/WEB-INF/upload目录中,且上传的图片需要进行自动更名处理。

  • 【lea-springmvc项目】修改validations.properties资源文件,追加服务端验证规则。
com.xiyue.leaspring.action.GoodsAction.add=name:string|price:double|iid:int|tid:string[]
# 公共MIME规则 定义公共的验证规则配置
mime.rule=image/bmp|image/png|image/jpg|image/jpeg|image/gif
  • 【lea-springmvc项目】在IGoodsService业务接口中追加商品增加方法。
 /**
  * 实现商品数据的追加处理,新添加商品的dflag内容为0
  * @param vo 要追加的商品信息(配置好关联关系)
  * @return 追加成功返回true,否则返回false
  */
 public boolean add(Goods vo);
  • 【lea-pringmvc项目】在GoodsServiceImpl子类中覆写add业务方法。
@Override
public boolean add(Goods vo){
    vo.setDflag(0);//新增商品信息删除标记为0
    return this.goodsDAO.save(vo).getGid()!=null;//商品保存
}
  • 【lea-pringmvc项目】表单提交数据时,分类信息只传递了iid,标签信息只传递tid(数组),所以需要在GoodsAction中进行手动处理。
@RequestMapping("/add")
    public ModelAndView add(Goods goods, long iid, String tid[],
                            MultipartFile photo)throws Exception{
        ModelAndView mav = new ModelAndView("/back/admin/goods/forward");//获取路径
        String msg = "商品数据增加失败!";
        String url = "pages/back/admin/good_add";//显示后的跳转路径
        List<Tag> allTags = new ArrayList<>();
        for (int x=0;x<tid.length;x++){//将标签信息保存到集合中
            Tag tag = new Tag();
            tag.setTid(Long.parseLong(tid[x]));//保存tid编号
            allTags.add(tag);
        }
        Item item = new Item();
        item.setIid(iid);//保存iid编号
        goods.setItem(item);//保存商品与分类关系
        goods.setTags(allTags);//保存商品与标签关系
        if(photo == null && photo.isEmpty()){
            goods.setPhoto("nophoto.png");//默认图片名称
        }else {//创建新的图片名称
            goods.setPhoto(UUID.randomUUID()+"."+photo.getContentType()
             .substring(photo.getContentType().lastIndexOf("/")+1));
        }
        if(this.goodsService.add(goods)){//保存成功
            if(!(photo == null && photo.isEmpty())){//有文件上传
                String photoPath = ContextLoader.getCurrentWebApplicationContext()
                        .getServletContext().getRealPath("/WEB-INF/upload/")+goods.getPhoto();
                File file = new File(photoPath);
                File filePath = new File(file.getParent());
                if(!filePath.exists() && !filePath.isDirectory()){
                    filePath.mkdirs();
                }
                if(!file.exists()){
                    file.createNewFile();
                }
                photo.transferTo(file);//保存上传文件
            }
            msg = "商品数据增加成功!";
        }
        mav.addObject("msg",msg);//保存提示信息
        mav.addObject("url",url);//保存跳转路径
        return mav;
    }

商品信息保存成功后,将通过forward.jsp页面进行信息提示,同时跳转到商品添加页面。

<%--
  Created by IntelliJ IDEA.
  User: xiyue
  Date: 2021/4/2
  Time: 19:25
  To change this template use File | Settings | File Templates.
--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@page isELIgnored="false" %><%--启动EL表达式解析--%>
<%
    request.setCharacterEncoding("UTF-8");
    String path = request.getContextPath();
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";

%>
<html>
<head>
    <base href="<%=basePath%>">
    <title>新增成功</title>
    <link rel="stylesheet" type="text/css" href="<%=basePath%>/bootstrap/css/bootstrap.min.css">
    <!-- href:里面的路径是你导入在static文件夹里面下面bootstrap.min.css所在的路径,下面两个属性一样 -->
    <script type="text/javascript" src="<%=basePath%>/jquery/jquery-3.6.0.min.js"></script>
    <script type="text/javascript" src="<%=basePath%>/bootstrap/js/bootstrap.min.js"></script>
</head>
<body class="panel-body">
<a href="<%=basePath%>${url}" class="btn btn-primary btn-lg active" role="button">新增商品</a>
<h3>${msg}</h3>
</body>
</html>

在这里插入图片描述

商品信息列表

保存后的商品信息需要列表显示,也就是说数据要进行分页处理,所以需要扩充IGoodsDAO接口中的业务方法。

  • 【lea-springmvc项目】修改IGoodsDAO接口,增加新的方法。
package com.xiyue.leaspring.dao;

import com.sun.tools.javac.util.List;
import com.xiyue.leaspring.vo.Goods;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface IGoodsDAO extends JpaRepository<Goods,Long> {

    @Query("SELECT g FROM Goods AS g WHERE g.name LIKE :#{'%'+#keyWord+'%'}")
    public List<Goods> findSplit(@Param(value = "keyWord")String keyWord, Pageable page);

    @Query("SELECT COUNT(g) FROM Goods AS g WHERE g.name LIKE :#{'%'+#keyWord+'%'}")
    public Long getSplitCount(@Param(value = "keyWord")String keyWord);
}

由于需要分页处理,所以在定义findSplit方法时传入了Pageable接口对象。

  • 【lea-springmvc项目】在IGoodsService接口中定义分页查询方法。
/**
 * 查询商品信息的分页数据,如果没有查询列或查询关键字,则进行整体查询
 * @param keyWord 查询关键字
 * @param currentPage 当前页
 * @param lineSize 每页行
 * @return 返回的内容包含有如下信息:
 * 1.key=allGoods,value=全部商品信息
 * 2.key=allRecorders,value=统计结果
 * 3.key=allItems,value=全部的分类信息(Map集合)
 */
public Map<String,Object> list(String keyWord,int currentPage,int lineSize);
  • 【lea-springmvc项目】在GoodsServiceImpl子类中覆写list方法。
@Override
    public Map<String, Object> list(String keyWord, int currentPage, int lineSize) {
        Map<String,Object> map = new HashMap<>();
        Sort sort = new Sort(Sort.Direction.DESC,"gid");//降序排列
        //将分页与排序操作保存到Pageable接口对象中,后续可以通过DAO层调用方法,页数从0开始
        Pageable pageable = PageRequest.of(currentPage-1,lineSize,sort);
        if(keyWord == null || "".equals(keyWord)){//查询全部操作
            Page<Goods> pageGoods = this.goodsDAO.findAll(pageable);//数据查询
            map.put("allRecorders",pageGoods.getTotalElements());//数据统计
            map.put("allGoods",pageGoods.getContent());//数据信息
        }else{//模糊查询
            map.put("allRecorders",this.goodsDAO.getSplitCount(keyWord));//数据统计
            map.put("allGoods",this.goodsDAO.findSplit(keyWord,pageable));//数据信息
        }
        Map<Long,String> itemMap = new HashMap<>();//保存分类信息
        this.iItemDAO.findAll().forEach(item ->{
            itemMap.put(item.getIid(),item.getTitle());//保存item信息
        });
        map.put("allItems",itemMap);//商品分类
        return map;
    }
  • 【lea-springmvc项目】为了方便处理分页,可以定义一个分页管理类,利用此类处理分页参数并可通过request属性将分页信息保存到JSP页面。
package com.xiyue.leaspring.util.web;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * 分页的参数处理操作
 */
public class SplitPageUtil {

    private int currentPage = 1;//参数:cp
    private int lineSize = 5;//参数ls
    private String keyWord;//参数:kw
    private HttpServletRequest request;//request对象

    /**
     * 将需要进行模糊查询的columnData(下拉框)传递到组件中,目的是为了属性操作
     * @param handleUrl 设置分页路径
     */
    public SplitPageUtil(String handleUrl){
        this.request = ((ServletRequestAttributes)RequestContextHolder
                .getRequestAttributes()).getRequest();
        this.request.setAttribute("handleUrl",handleUrl);
        try {//接收当前页码
            this.currentPage = Integer.parseInt(this.request.getParameter("cp"));
        }catch (Exception e){}
        try {//接收每页显示的数据行数
            this.lineSize = Integer.parseInt(this.request.getParameter("ls"));
        }catch (Exception e){}
        this.keyWord = this.request.getParameter("kw");//接收关键字
        if(this.keyWord == null){
            this.keyWord="";
        }
        this.request.setAttribute("currentPage",this.currentPage);
        this.request.setAttribute("lineSize",this.lineSize);
        this.request.setAttribute("keyWord",this.keyWord);
    }

    public int getCurrentPage() {//得到当前页码
        return currentPage;
    }

    public int getLineSize() {//得到每页显示的数据行数
        return lineSize;
    }

    public String getKeyWord() {//得到关键字
        return keyWord;
    }
}
  • 【mldnspring-mvc项目】扩充GoodsAction类中的方法,进行分页处理。
@RequestMapping("/list")
public ModelAndView list(){
     ModelAndView mav = new ModelAndView("/back/admin/goods/goods_list");
     SplitPageUtil spu = new SplitPageUtil("/pages/back/admin/goods/list");
     mav.addAllObjects(this.goodsService.list(spu.getKeyWord()
             ,spu.getCurrentPage(),spu.getLineSize()));
     return mav;
 }
  • 【lea-springmvc项目】在/pages/back/admin/goods/goods_list.jsp页面中迭代输出商品数据。
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@page isELIgnored="false" %><%--启动EL表达式解析--%>
<%
    request.setCharacterEncoding("UTF-8");
    String path = request.getContextPath();
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
    String goods_edit_url = basePath+"/pages/back/admin/optview";
%>
<html>
<head>
    <base href="<%=basePath%>">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>商品列表</title>
    <link rel="stylesheet" type="text/css" href="<%=basePath%>/bootstrap/css/bootstrap.min.css">
    <!-- href:里面的路径是你导入在static文件夹里面下面bootstrap.min.css所在的路径,下面两个属性一样 -->
    <script type="text/javascript" src="<%=basePath%>/jquery/jquery-3.6.0.min.js"></script>
    <script type="text/javascript" src="<%=basePath%>/bootstrap/js/bootstrap.min.js"></script>
</head>
<body class="panel-body">
    <form class="form-inline col-md-offset-5" action="<%=basePath%>pages/back/admin/list">
        <div class="form-group">
            <input type="text" class="form-control" id="kw" name="kw" placeholder="输入搜索词">
        </div>
        <div class="form-group">
            <button type="submit" class="btn btn-primary">搜索</button>
        </div>
    </form>
    <table class="table table-striped table-bordered table-hover">
        <tr>
            <td style="width: 5%"><input type="checkbox" id="selectAll"/></td>
            <td>商品名称</td>
            <td>商品单价</td>
            <td>商品分类</td>
            <td>操作</td>
        </tr>
        <c:forEach items="${allGoods}" var="goods">
            <tr>
                <td><input type="checkbox" name="gid" value="${goods.gid}"/></td>
                <td>${goods.name}</td>
                <td><fmt:formatNumber value="${goods.price}"/></td>
                <td>${allItems[goods.item.iid]}</td>
                <td>
                    <a href="<%=goods_edit_url%>?gid=${goods.gid}&type=edit" class="btn btn-warning btn-xs">
                    <span class="glyphicon glyphicon-pencil"></span>&nbsp;编辑</a>
                    <a href="<%=goods_edit_url%>?gid=${goods.gid}&type=delete" class="btn btn-danger btn-xs">
                        <span class="glyphicon glyphicon-remove"></span>&nbsp;删除</a>
                </td>
            </tr>
        </c:forEach>
    </table>
    <button type="button" class="btn btn-primary" id="deleteSelect">
        <span class="glyphicon glyphicon-trash"></span>&nbsp;删除选中信息</button>
    <nav aria-label="...">
        <ul class="pager">
            <li><a href="<%=basePath%>pages/back/admin/list?cp=${currentPage==1?1:currentPage-1}">上一页</a></li>
            <li><a href="<%=basePath%>pages/back/admin/list?cp=${currentPage}">${currentPage}</a></li>
            <li><a href="<%=basePath%>pages/back/admin/list?cp=${currentPage==totalPages?totalPages:currentPage+1}">下一页</a></li>
        </ul>
    </nav>
</body>
<script>
    $("#selectAll").click(function (){
        $("#selectAll")[0].checked
        let selector = $("input[name='gid']");
        for(let i=0;i<selector.length;i++){
            selector[i].checked = $("#selectAll")[0].checked;
        }
    });
    $("#deleteSelect").click(function (){
        let checkedArray = new Array();
        let selector = $("input[name='gid']");
        for(let i=0;i<selector.length;i++){
            if(selector[i].checked == true){
                checkedArray.push(parseInt(selector[i].value))
            }
        }
    });

</script>
</html>

此时分页操作已被封装到了组件中,页面上直接利用jsp:include语句导入即可,页面执行效果
在这里插入图片描述

商品信息编辑页面

编辑商品信息前,要先进行商品信息、分类信息、标签信息查询,并将查询结果回填到JSP页面。

  • 【lea-springmvc项目】编辑商品时,除了要查询商品相关信息外,还需要通过goods_tag表查询指定商品编号对应的标签编号,所以在IGoodsDAO接口中需要扩充一个原生SQL查询的数据层方法。
 @Query(nativeQuery = true,value = "SELECT tid FORM good_tag WHERE gid=:gid")
 public List<Long> findTidByGoods(@Param(value = "gid")Long gid);
  • 【lea-springmvc项目】在IGoodsService接口中追加一个编辑前的数据查询业务方法。
 /**
   * 商品修改前的数据查询操作
   * @param id
   * @return 返回的数据包含有如下内容:
   * key=allItems、value=所有的商品分类
   * key=allTags、value=所有的商品标
   * key=goods、value=要修改的商品信息
   * key=goodsTags、value=商品标签
   */
  public Map<String,Object> preEdit(long id);
  • 【lea-springmvc项目】在GoodsServiceImpl子类中实现preEdit方法。
@Override
public Map<String, Object> preEdit(long id) {
     Map<String,Object> map = new HashMap<>();
     map.put("allItems",this.iItemDAO.findAll());//查询所有的商品分类
     map.put("allTags",this.tagDAO.findAll());//查询所有的商品标签
     map.put("goods",this.goodsDAO.findById(id).get());//查询指定商品信息
     map.put("goodsTags",this.goodsDAO.findTidByGoods(id));//查询指定商品拥有的标签编号
     return map;
 }
  • 【lea-springmvc项目】在GoodsAction类中定义editPre方法,查询商品编辑前的数据。
 @RequestMapping("/editview")
    public ModelAndView editView(Long id){
        Map<String,Object> map = this.goodsService.preEdit(id);//查询商品信息
        this.logger.info("商品信息修改前信息查询:"+map);
        ModelAndView mav = new ModelAndView("/back/admin/goods/goods_edit");
        mav.addAllObjects(map);
        return mav;
    }
  • 【lea-springmvc项目】在/pages/back/admin/goods/goods_edit.jsp页面中回填表单数据,表单回填后的效果如图.
    在这里插入图片描述
    本页面中,最为重要的两项数据处理是商品分类回填和商品标签回填src/main/webapp/WEB-INF/pages/back/admin/goods/goods_edit.jsp(利用JSTL函数标签处理),代码如下。
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@page isELIgnored="false" %><%--启动EL表达式解析--%>
<%
    request.setCharacterEncoding("UTF-8");
    String path = request.getContextPath();
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
    String url_add = basePath+"/pages/back/admin/edit";
%>
<html>
<head>
    <base href="<%=basePath%>">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>编辑商品</title>
    <link rel="stylesheet" type="text/css" href="<%=basePath%>/bootstrap/css/bootstrap.min.css">
    <!-- href:里面的路径是你导入在static文件夹里面下面bootstrap.min.css所在的路径,下面两个属性一样 -->
    <script type="text/javascript" src="<%=basePath%>/jquery/jquery-3.6.0.min.js"></script>
    <script type="text/javascript" src="<%=basePath%>/bootstrap/js/bootstrap.min.js"></script>
</head>
<body class="panel-body">
    <div class="container-fluid">
        <form class="form-horizontal" action="<%=url_add%>" method="post" enctype="multipart/form-data">
            <div class="form-group" id="nameDiv">
                <%--@declare id="name"--%>
                <label class="col-md-2 control-label" for="name">商品名称:</label>
                <div class="col-md-5">
                    <input type="text" class="form-control" id="name" name="name" value="${goods.name}" placeholder="请填写商品名称">
                </div>
                <span class="col-md-5" id="nameSpan">*</span>
            </div>
            <div class="form-group" id="priceDiv">
                <%--@declare id="price"--%>
                <label class="col-md-2 control-label" for="price">商品价格:</label>
                <div class="col-md-5">
                    <input type="text" class="form-control" id="price" name="price" value="${goods.price}" placeholder="请填写商品单价">
                </div>
                <span class="col-md-5" id="priceSpan">*</span>
            </div>
            <div class="form-group" id="iidDiv">
                <%--@declare id="iid"--%>
                <label class="col-md-2 control-label" for="iid">商品分类:</label>
                <div class="col-md-5">
                    <select id="iid" name="iid" class="form-control">
                        <option value="">**请选择商品所属分类**</option>
                        <c:forEach items="${allItems}" var="item">
                            <option value="${item.iid}" ${item.iid==goods.item.iid?"selected":""}>${item.title}</option>
                        </c:forEach>
                    </select>
                </div>
                <span class="col-md-5" id="iidSpan">*</span>
            </div>
            <div class="form-group" id="tagDiv">
                <%--@declare id="tag"--%>
                <label class="col-md-2 control-label" for="tag">商品标签:</label>
                <div class="col-md-5">
                    <c:forEach items="${allTags}" var="tag">
                        <div class="col-md-3">
                            <div class="checkbox">
                                <label>
                                   <input type="checkbox" id="tid" name="tid"
                                        ${fn:contains(goodsTags,tag.tid)?"checked":""} value="${tag.tid}"/>${tag.title}
                                </label>
                            </div>
                        </div>
                    </c:forEach>
                </div>
                <span class="col-md-5" id="tidSpan">*</span>
            </div>
            <div class="form-group" id="picDiv">
                <%--@declare id="pic"--%>
                <label class="col-md-2 control-label" for="pic">商品图片:</label>
                <div class="col-md-5">
                    <div class="col-md-3 col-md-3">
                            <img src="<%=basePath%>${goods.photo}" alt="...">
                    </div>
                    <input type="file" class="form-control" id="pic" name="pic">
                </div>
                <span class="col-md-5" id="priceSpan">*</span>
            </div>
            <div class="form-group">
                <div class="col-md-offset-3">
                    <button type="submit" class="btn btn-primary">提交</button>
                    <button type="reset" class="btn btn-default">重置</button>
                </div>
            </div>
        </form>
    </div>
</body>
</html>

商品信息更新

在商品编辑页面修改完商品信息后,需要更新商品信息。利用JPA中提供的数据关联关系,可以实现goods_tag表中的关联数据自动更新。商品图片如果不需要更新,则不需要重新上传图片;如果需要更新,可使用原始文件名称进行文件覆盖。

  • 【lea-springmvc项目】在IGoodsService业务接口中追加数据修改方法。
/**
  * 商品数据的修改处理
  * @param vo 要追加的商品信息
  * @return 修改成功返回true,否则返回false
  */
 public boolean edit(Goods vo);
  • 【lea-springmvc项目】在GoodsServiceImpl子类中覆写edit方法。
 @Override
 public boolean edit(Goods vo) {
     return this.goodsDAO.save(vo)!=null;
 }
  • 【lea-springmvc项目】修改validations.properties配置文件,追加验证规则。
com.xiyue.leaspring.action.GoodsAction.edit=name:string|price:double|iid:int|tid:string[]
  • 【lea-springmvc项目】在GoodsAction类中定义商品更新方法。
 @RequestMapping("/edit'")
    public ModelAndView edit(Goods goods,long iid,String tid[],
        MultipartFile pic)throws Exception{
            ModelAndView mav = new ModelAndView("/back/admin/goods/forward");//获取路径
            String msg = "商品数据修改失败!";
            String url = "pages/back/admin/list";//显示后的跳转路径
            List<Tag> allTags = new ArrayList<>();
            for (int x=0;x<tid.length;x++){//将标签信息保存到集合中
                Tag tag = new Tag();
                tag.setTid(Long.parseLong(tid[x]));//保存tid编号
                allTags.add(tag);
            }
            Item item = new Item();
            item.setIid(iid);//保存iid编号
            goods.setItem(item);//保存商品与分类关系
            goods.setTags(allTags);//保存商品与标签关系
            if(!pic.isEmpty()){//有新的文件上传
                goods.setPhoto("nophoto.png");//默认图片名称
                if("nophoto.png".equals(goods.getPhoto())){
                    goods.setPhoto(UUID.randomUUID()+"."+pic.getContentType()
                            .substring(pic.getContentType().lastIndexOf("/")+1));
                }
            }
            if(this.goodsService.add(goods)){//保存成功
                if(!(pic == null && pic.isEmpty())){//有文件上传
                    String photoPath = ContextLoader.getCurrentWebApplicationContext()
                            .getServletContext().getRealPath("/upload/")+goods.getPhoto();
                    File file = new File(photoPath);
                    File filePath = new File(file.getParent());
                    if(!filePath.exists() && !filePath.isDirectory()){
                        filePath.mkdirs();
                    }
                    if(!file.exists()){
                        file.createNewFile();
                    }
                    pic.transferTo(file);//保存上传文件
                }
                msg = "商品数据修改成功!";
            }
            mav.addObject("msg",msg);//保存提示信息
            mav.addObject("url",url);//保存跳转路径
            return mav;
    }

本程序的基本逻辑业务与商品增加方式类似,唯一的区别在于上传文件的覆盖更新处理。

商品信息删除

商品信息删除采用逻辑处理,主要通过修改goods表中的dflag字段实现。删除后再对列表数据进行查询,需要追加一个查询条件(dflag=0),才可以实现删除逻辑。

  • 【lea-springmvc项目】在IGoodsDAO接口中追加一个修改dflag字段数据的方法。
@Modifying(clearAutomatically = true)
@Query("UPDATE Goods AS g SET g.dflag=:dflag WHERE g.gid IN :gids")
public Integer editDflag(@Param(value = "gids")Set<Long> gids,@Param(value = "dflag")Integer dflag);
  • 【lea-springmvc项目】在IGoodsService业务层增加删除业务方法。
/**
 * 商品信息删除处理
 * @param gids 要删除的商品编号
 * @return 如果没有商品或者商品删除失败,返回false
 */
public boolean remove(Set<Long> gids);
  • 【lea-springmvc项目】GoodsServiceImpl子类覆写remove方法。
@Override
public boolean remove(Set<Long> gids) {
   return this.goodsDAO.editDflag(gids,1)>0;//更新dflag字段
}
  • 【lea-springmvc项目】数据删除操作是通过前端的JS事件传递的要删除数据id,参数名称为ids,所以修改validations.properties配置文件,定义验证规则。
com.xiyue.leaspring.action.GoodsAction.delete=ids:string
  • 【lea-springmvc项目】在GoodsAction程序类中定义删除方法。
@RequestMapping("/delete")
public ModelAndView delete(String ids){
    String msg = "商品数据删除失败!";
    String url = "pages/back/admin/list";//显示后的跳转路径
    ModelAndView mav = new ModelAndView("/back/admin/goods/forward");//获取路径
    //传递要删除的所有商品ID,多个ID间用“,”分隔
    String idData[] = ids.split(",");
    Set<Long> gids = new HashSet<>();
    for (String id:idData) {
        gids.add(Long.parseLong(id));
    }
    if(this.goodsService.remove(gids)){
        msg = "商品数据删除成功!";
    }
    mav.addObject("msg",msg);
    mav.addObject("url",url);
    return mav;
}

此时程序可以实现goods表中的dflag字段更新,但是逻辑更新处理还需要考虑数据查询问题。
提示:关于TransactionRequiredException异常。

本程序对业务操作使用了更新处理。如果配置不当,有可能出现事务处理异常(javax.persistence.TransactionRequiredException: Executing an update/delete query)。造成此问题的原因有两个:事务配置错误,即spring-transaction.xml配置有问题;或是重复进行了类扫描(Spring中使用context自动扫描配置context:component-scan)。
6.【lea-springmvc项目】修改IGoodsDAO数据接口,定义新的数据分页查询方法(查询全部、分页查询)。

@Query("SELECT g FROM Goods AS g WHERE g.name LIKE :#{'%'+#keyWord+'%'} AND g.dflag=:dflag")
public List<Goods> findSplit(@Param(value = "keyWord")String keyWord,@Param(value = "dflag") Integer dflag,Pageable page);

@Query("SELECT COUNT(g) FROM Goods AS g WHERE g.name LIKE :#{'%'+#keyWord+'%'} AND g.dflag=:dflag")
public Long getSplitCount(@Param(value = "keyWord")String keyWord,@Param(value = "dflag") Integer dflag);
    
@Query("SELECT g FROM Goods AS g WHERE g.dflag=:dflag")
public List<Goods> findAllByDflag(@Param(value = "dflag") Integer dflag,Pageable page);

@Query("SELECT COUNT(g) FROM Goods AS g WHERE g.dflag=:dflag")
public Long findAllCountByDflag(@Param(value = "dflag") Integer dflag);
  • 【lea-springmvc项目】修改业务层实现子类中的list方法。
 @Override
    public Map<String, Object> list(String keyWord, int currentPage, int lineSize) {
        Map<String,Object> map = new HashMap<>();
        Sort sort = new Sort(Sort.Direction.DESC,"gid");//降序排列
        //将分页与排序操作保存到Pageable接口对象中,后续可以通过DAO层调用方法,页数从0开始
        Pageable pageable = PageRequest.of(currentPage-1,lineSize,sort);
        if(keyWord == null || "".equals(keyWord)){//查询全部操作
            long allRecorders =  this.goodsDAO.findAllCountByDflag(0);
            map.put("allRecorders",allRecorders);//数据统计
            map.put("allGoods",this.goodsDAO.findAllByDflag(0,pageable));//数据信息
            map.put("currentPage",currentPage);//当前页
            map.put("lineSize",lineSize);//每页条数
            map.put("totalPages",allRecorders%lineSize==0?allRecorders:allRecorders+1);//总页数
        }else{//模糊查询
            long allRecorders = this.goodsDAO.getSplitCount(keyWord,0);
            List<Goods> allGoods = this.goodsDAO.findSplit(keyWord,0,pageable);
            map.put("allRecorders",allRecorders);//数据统计
            map.put("allGoods",allGoods);//数据信息
            map.put("currentPage",currentPage);//当前页
            map.put("lineSize",lineSize);//每页条数
            map.put("totalPages",allRecorders%lineSize==0?allRecorders/lineSize:(allRecorders/lineSize)+1);//每页条数
        }
        Map<Long,String> itemMap = new HashMap<>();//保存分类信息
        this.iItemDAO.findAll().forEach(item ->{
            itemMap.put(item.getIid(),item.getTitle());//保存item信息
        });
        map.put("allItems",itemMap);//商品分类
        return map;
    }

此时在业务层中,当进行全部商品信息查询时,将根据传入的dflag进行数据是否删除的判断,这样就可以实现数据逻辑删除处理了。

配置Druid数据源

Druid是阿里巴巴提供的一款数据库连接池组件,该组件结合了常用数据库连接池的优点,并且追加了日志监控功能,可以直接利用Web控制台获取数据库的操作状态。

  • 【lea-springmvc项目】修改pom.xml配置文件,追加druid相关依赖库。
<druid.version>1.1.8</druid.version>

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>${druid.version}</version>
</dependency>
  • 【lea-springmvc项目】修改database.properties配置文件。
#定义数据库驱动程序名称
database.druid.driverClassName=com.mysql.cj.jdbc.Driver
#数据库连接地址
database.druid.url=jdbc:mysql://127.0.0.1:3306/lea-springmvc
#数据库连接用户
database.druid.username=root
#数据库连接密码
database.druid.password=123456
#数据库最大连接数
database.druid.maxActive=1
#数据库最小维持连接数
database.druid.minIdle=1
#数据库初始化连接
database.druid.initialSize=1
#数据库连接池最大等待时间
database.druid.maxWait=30000
#配置间隔多久进行一次检测,检测需要关闭空闲连接、单位是ms
database.druid.timeBetweenEvictionRunsMillis=60000
#配置连接在池中的最小生存时间,单位是ms
database.druid.minEvictableIdleTimeMillsis=30000
#数据库状态检测
database.druid.validationQuery=SELECT'x'
#申请连接检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery,检测连接是否有效
database.druid.testWhileIdle=true
#申请连接时,执行validationQuery检测连接是否有效,该设置会降低性能
database.druid.testIOnBorrow=false
#归还连接时,执行validationQuery检测连接是否有效,该设置会降低性能
database.druid.testIOnReturn=false
#是否缓存preparedStatement,也就是PSCache。PSCache能提升支持游标的数据库性能,如Oracle、Mysql下建议关闭
database.druid.poolPreparedStatements=false
#配置poolPreparedStatements缓存
database.druid.maxpoolPreparedStatementPerConnectionSize=20
#配置监控,统计拦截的filters.去掉后,监控界面SQL将无法统计
database.druid.filters=stat
  • 【lea-springmvc项目】修改spring.xml配置文件,使用Druid数据源(更换DataSource配置)。
 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init">
    <property name="driverClassName" value="${database.druid.driverClassName}"/><!--驱动-->
    <property name="url" value="${database.druid.driverClassName}"/><!--地址-->
    <property name="username" value="${database.druid.username}"/><!--用户-->
    <property name="password" value="${database.druid.password}"/><!--密码-->
    <property name="maxActive" value="${database.druid.maxActive}"/><!--最大连接数-->
    <property name="minIdle" value="${database.druid.minIdle}"/><!--最小连接池-->
    <property name="initialSize" value="${database.druid.initialSize}"/><!--初始化连接大小-->
    <property name="maxWait" value="${database.druid.maxWait}"/><!--最大等待时间-->
    <property name="timeBetweenEvictionRunsMillis" value="${database.druid.timeBetweenEvictionRunsMillis}"/><!--检测空闲连接间隔-->
    <property name="minEvictableIdleTimeMillis" value="${database.druid.minEvictableIdleTimeMillsis}"/><!--连接最小生存时间-->
    <property name="validationQuery" value="${database.druid.validationQuery}"/><!--验证-->
    <property name="testWhileIdle" value="${database.druid.testWhileIdle}"/><!--申请检测-->
    <property name="testOnBorrow" value="${database.druid.testIOnBorrow}"/><!--有效检测-->
    <property name="testOnReturn" value="${database.druid.testIOnReturn}"/><!--归还检测-->
    <property name="poolPreparedStatements" value="${database.druid.poolPreparedStatements}"/><!--是否缓存preparedStatement,也就是PSCache。PSCache能提升支持游标的数据库性能,如Oracle、Mysql下建议关闭-->
    <property name="maxPoolPreparedStatementPerConnectionSize" value="${database.druid.maxpoolPreparedStatementPerConnectionSize}"/><!--启用PSCache,必须配置大于0,当大于0时-->
    <property name="filters" value="${database.druid.filters}"/><!--驱动-->
</bean>
  • 【lea-springmvc项目】修改web.xml配置文件,追加Druid过滤器,以实现监控配置。
<!--Druid过滤器,以实现监控配置。-->
<filter>
  <filter-name>DruidWebStatFilter</filter-name>
  <filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
  <init-param>
    <param-name>exclusion</param-name>
    <param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>DruidWebStatFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

本程序进行Druid监控时,排除了不监控的操作后缀。

  • 【lea-springmvc项目】建立Druid控制台配置,修改web.xml文件,追加Servlet配置。
<!--Druid过滤器,以实现监控配置。-->
<servlet>
  <servlet-name>DruidStatView</servlet-name>
  <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
</servlet>
<!--Druid过滤器,以实现监控配置。-->
<servlet-mapping>
  <servlet-name>DruidStatView</servlet-name>
  <url-pattern>/druid/*</url-pattern>
</servlet-mapping>

配置完成后,该项目将可以使用Druid进行数据库连接池管理。同时,可通过配置的druid/*利用浏览器进行访问。进行几次数据库操作后,通过SQL监控界面可观察到如图所示的监控结果。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

追Star仙

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

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

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

打赏作者

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

抵扣说明:

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

余额充值