SpringMVC
源码地址:https://gitee.com/tirklee/lea-springmvc
SpringMVC简介
MVC(Model View Controller)是Java项目以及JavaEE中使用最为广泛的,也是唯一提倡的整体设计模式。传统的Web开发中需要利用反射与大量的配置文件才能实现一个便于维护、可动态扩充的MVC设计模式。为简化MVC设计难度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文件。
- 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> 编辑</a>
<a href="<%=goods_edit_url%>?gid=${goods.gid}&type=delete" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-remove"></span> 删除</a>
</td>
</tr>
</c:forEach>
</table>
<button type="button" class="btn btn-primary" id="deleteSelect">
<span class="glyphicon glyphicon-trash"></span> 删除选中信息</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监控界面可观察到如图所示的监控结果。