SpringMVC
新建一个maven项目,并设置pom文件,设置当前项目为web项目,将packaging的属性值设置为war方式,添加spring mvc的依赖包, spring-webmvc(4.3.6),分别添加插件,jdk和tomcat
<?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.qfedu</groupId>
<artifactId>Days17SpringMVC</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- define the project compile level -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!-- 添加tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<path>/</path>
<port>8081</port>
</configuration>
</plugin>
</plugins>
</build>
</project>
在项目中添加web元素,webapp, WEB-INF以及web.xml,其中在web.xml里面要添加spring mvc的引入,添加DispatcherServlet,这个是spring mvc的核心的前端控制器,注意还要设置DispatcherServlet的contextConfigLocation,如果不设置该属性,则Spring MVC会自动的在WEB-INF下查找[servlet-name]-servlet.xml文件来作为SpringMVC的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>aaa</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>aaa</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
配置Spring MVC的配置文件,classpath下的spring-mvc.xml文件,该文件在本案例中分别配置了视图解析器、消息资源、缺省servlet处理器、注解驱动器、上下文包扫描。
视图解析器:InternalResourceViewResolver, 该属性里可以分别配置前缀和后缀,为了保证程序的安全性,可以将页面放在/WEB-INF/view/下,所以前缀可以直接配置为该值。如果没有这方面的需求,不配置该属性,则前缀为/,代表的是webapp目录,后缀可以根据项目需要设置为.jsp或者.html
消息资源:ReloadableResourceBundleMessageSource,该bean的配置有一个要求,id必须叫做messageSource,Spring MVC框架会读取该id所对应的bean对象来读取资源配置文件,里面设置了basename属性,用作读取该文件,该文件的配置只需要文件名,不能加后缀,为了更好的实现国际化,我们可以在msg文件后面拼接语言和国家,比如msg_zh_CN, msg_en_US以及其他国家的语言均可以按照这种方式来设定。有些ide环境可能只认识resources,则可以将msg文件放入resources目录下,否则不同的ide环境找不到该文件
缺省servlet处理器:mvc:default-servlet-handler,该配置可以保证Spring MVC项目可以直接访问静态资源,比如可以直接访问index.html
注解驱动器:mvc:annotation-driven,该配置使得当前项目可以使用注解来完成配置。在控制器类之上,可以添加Controller注解,里面还有RequestMapping,GetMapping,PostMapping,PathVariable等注解,可以完成各自的功能
上下文的包扫描:context:component-scan,使用该配置,可以使得该basePackage所对应的包下的所有Component组件直接被扫描出来使用,前提是需要在类之上添加@Component注解,但是我们的Controller以及后面要用的Service和Repositoy也都是Component组件,所以可以直接被扫描出来进行使用
该配置文件还配置了两个bean,里面是name和class,那么要注意,name里对应的值是url,name里面允许存放特殊字符,因为路径字符串前面会有一个路径符号/,所以这里只能使用name而不能使用id,意思是该url请求发出来之后,会自动交给后面的控制器类来实现处理的功能,该控制器类是实现了Controller接口的类,该类中有一个返回值为ModelAndView对象的方法名为handlerRequest的包含HttpServletRequest和HttpServletResponse两个参数的方法。ModelAndView对象是一个可以同时包含视图和模型对象的对象,但是在使用的过程中,有时候只需要显示页面,有时候可能在显示页面的同时,还需要数据的传递。注意:Controller接口与Controller注解是两个不同的东西。
<?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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--
配置SpringMVC的视图解析器
可以分别指定前缀和后缀
prefix: /WEB-INF/view/,那么控制器那边会在虚拟视图前拼接该字符串
suffix:.jsp .html,那么控制器那边会在虚拟视图后面拼接该字符串
拼接完字符串的效果
/WEB-INF/view/index.html
/WEB-INF/view/detail.jsp
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/" />
<!--<property name="suffix" value=".jsp" />-->
</bean>
<!--配置缺省的servlet处理器,静态资源可以直接被访问-->
<mvc:default-servlet-handler />
<!--
上下文的组件扫描
-->
<context:component-scan base-package="com.qfedu.controller" />
<!--
配置注解驱动
-->
<mvc:annotation-driven />
<!--
bean的id属性值不能包含特殊字符
name可以,所以路径需要使用name来标识一个控制器的路径
指定name对应路径交给哪个控制器来进行具体的处理
-->
<bean name="/ProductInput" class="com.qfedu.controller.ProductInputController" />
<bean name="/SaveProductController" class="com.qfedu.controller.SaveProductController" />
<!--
配置MessageSource,消息源,该id必须叫做messageSource,由SpringMVC框架自动读取该id对应的消息资源来讲加载相对应的配置文件
-->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="/WEB-INF/msg" />
</bean>
</beans>
新增一个Emp的bean类
package com.qfedu.bean;
public class Emp {
private int eid;
private String name;
private double salary;
public Emp() {
}
public Emp(int eid, String name, double salary) {
this.eid = eid;
this.name = name;
this.salary = salary;
}
@Override
public String toString() {
return "Emp{" +
"eid=" + eid +
", name='" + name + '\'' +
", salary=" + salary +
'}';
}
public int getEid() {
return eid;
}
public void setEid(int eid) {
this.eid = eid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
在controller包下新增EmpController类,用到的注解有Controller,RequestMapping,GetMapping, PostMapping,PathVariable
Controller,代表当前类是一个控制器类,注意,通过查看源码,我们发现该类也是一个Component,所以刚刚的配置包扫描可以直接扫描到当前类,并将其作为一个组件来使用
RequestMapping,请求映射,目的是将某一个请求,映射到具体方法之上。该注解可以使用在类之上,也可以使用在方法之上。如果类和方法都有该配置,那么访问该方法的时候,需要同时拼接类之上的路径和方法之上的路径才能够访问该具体的方法。该注解可以使用method来区分不同的请求,method = RequestMethod.POST,或者GET可以分别来处理post和get请求
PostMapping和GetMapping也代表请求映射,使用起来会更直观,分别代表处理post和get的请求方式,但是这俩属性只能用于spring 4.3之后的版本。
PathVariable:路径变量,可以用来做路径传参功能,该功能相对于问号传参更加方便,可以直接指定变量的数据类型,而无需再做数据类型的转换,也可以实现传入多个参数,/{abc}/{xyz},方法里面可以使用 public String getPath(@PathVariable int abc,@PathVariable String xyz)方式来接收。注意路径传参会多一级目录,要注意访问路径
该类中的updateEmp(Emp e)方法再特别说一下:该方法可以自动接收表单里面的数据并将其封装为一个Emp对象,注意表单中的控件名一定要和Bean中的Emp类的属性要完全一致,否则找不到某些属性,这个也是Spring MVC中非常便利的地方,可以省去类型转换和封装对象的过程
该类中的方法都参数均很灵活,在需要的地方添加参数就可以直接使用
该类中的方法都返回值为String的都代表最终的展示页面。如果带有redirect,则代表重定向,意思是重定向到某一个具体的请求。
一个控制器里可以同时存在相同的路径url但是是不同的请求方式
关于校验这里,第一个GetMapping("/saveEmp")代表以get方式请求该资源,里面写了一个ModelAndView对象,传了三个参数,第一个是viewname,视图名,拼接上前后缀可以得到真正的物理视图,来打开该物理视图所对应的页面,第二个参数为modelname,模型名,相当于给模型起名字,这里要注意,该模型名意识要被叫做bean对象的小写形式Emp(emp),第三个参数为modelObject,模型对象,将该对象通过模型名传递给第一个参数viewname所对应的页面,在那个页面中可以渲染该数据
关于校验的第二个PostMapping("/saveEmp"),该注解的意思是页面上的表单通过post请求将saveEmp的请求来在这里进行处理。该方法包含有三个参数,第一个是Emp对象,可以自动封装表单中的属性为Bean对象,第二个参数为BindingResult对象,该对象我们通过源码可以发现是Spring中的Errors的子接口,可以用来接收并存储错误信息,这个对象可以接收从EmpValidate校验类中产生的错误信息,存储以交给错误页面的f:errors标签来展示错误信息,第三个参数是Model对象,可以用来储存对象,目的是可以使的bean对象的错误数据进行回显
package com.qfedu.controller;
import com.qfedu.bean.Emp;
import com.qfedu.service.IEmpService;
import com.qfedu.service.impl.EmpServiceImpl;
import com.qfedu.validate.EmpValidate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.List;
@Controller
@RequestMapping("/emp")
public class EmpController {
private IEmpService empService = new EmpServiceImpl();
/**
* 如果有了users请求,那么该方法会被调用,返回值为将来要渲染的页面
* @return
*/
@RequestMapping("/emps")
public String getUsersPage(Model model, HttpSession session){
List<Emp> list = empService.getAllEmps();
model.addAttribute("list", list);
session.setAttribute("list", list);
return "emp.jsp";
}
@RequestMapping("/getEmpByEid")
public String getEmpByEid(HttpServletRequest request, Model model){
String seid = request.getParameter("eid");
int eid = seid == null ? -1 : Integer.parseInt(seid);
Emp emp = empService.getEmpByEid(eid);
model.addAttribute("emp", emp);
return "updateEmp.jsp";
}
//@RequestMapping(value = "/updateEmp", method = RequestMethod.POST)
@PostMapping("/updateEmp")
//public String updateEmp(HttpServletRequest request){
public String updateEmp(Emp e){
//System.out.println(request.getParameter("eid"));
System.out.println(e);
boolean flag = empService.updateEmp(e);
if(flag){
return "redirect:/emp/emps";
}
return "";
}
@GetMapping("/deleteByEid/{eid}")
public String deleteByEid(@PathVariable int eid){
//System.out.println(eid);
boolean flag = empService.deleteEmpByEid(eid);
if(flag){
return "redirect:/emp/emps";
}
return "";
}
@GetMapping("/saveEmp")
public ModelAndView saveEmp(){
return new ModelAndView("saveEmp.jsp", "emp", new Emp());
}
/**
* 完成表单中Emp对象的存储
* @param e 要存储的Emp对象
* @param errors,收集错误信息的对象
* @param model
* @return
*/
@PostMapping("/saveEmp")
public String saveEmp(Emp e, BindingResult errors, Model model){
/**
* 调用自己写好的校验类来完成对于Emp对象的校验
*/
EmpValidate ev = new EmpValidate();
ev.validate(e, errors);
if(errors.hasErrors()){
model.addAttribute("emp", e);
return "saveEmp.jsp";
}
return "redirect:/emp/emps";
}
}
IEmpService.java, Service接口
package com.qfedu.service;
import com.qfedu.bean.Emp;
import java.util.List;
public interface IEmpService {
List<Emp> getAllEmps();
Emp getEmpByEid(int eid);
boolean updateEmp(Emp emp);
boolean deleteEmpByEid(int eid);
}
EmpServiceImpl.java, service实现类
package com.qfedu.service.impl;
import com.qfedu.bean.Emp;
import com.qfedu.dao.impl.EmpDaoImpl;
import com.qfedu.dao.IEmpDao;
import com.qfedu.service.IEmpService;
import java.util.List;
public class EmpServiceImpl implements IEmpService {
private IEmpDao empDao = new EmpDaoImpl();
@Override
public List<Emp> getAllEmps() {
return empDao.getAllEmps();
}
@Override
public Emp getEmpByEid(int eid) {
return empDao.getEmpByEid(eid);
}
@Override
public boolean updateEmp(Emp emp) {
return empDao.updateEmp(emp);
}
@Override
public boolean deleteEmpByEid(int eid) {
return empDao.deleteEmpByEid(eid);
}
}
IEmpDao.java Dao接口
package com.qfedu.dao;
import com.qfedu.bean.Emp;
import java.util.List;
public interface IEmpDao {
List<Emp> getAllEmps();
Emp getEmpByEid(int eid);
boolean updateEmp(Emp emp);
boolean deleteEmpByEid(int eid);
}
EmpDaoImpl.java dao的实现类,使用List模拟一套数据源,可以完成对于Emp对象的CRUD操作,注意如果服务器重新启动,则数据会恢复到最原始的状态
package com.qfedu.dao.impl;
import com.qfedu.bean.Emp;
import com.qfedu.dao.IEmpDao;
import java.util.ArrayList;
import java.util.List;
public class EmpDaoImpl implements IEmpDao {
private static List<Emp> emps = new ArrayList<>();
static {
for(int i = 0; i < 20; i++){
emps.add(new Emp(i + 1, "name " + i, 8000 + i * 100));
}
}
@Override
public List<Emp> getAllEmps() {
return emps;
}
@Override
public Emp getEmpByEid(int eid) {
return emps.get(eid - 1);
}
@Override
public boolean updateEmp(Emp emp) {
try{
emps.set(emp.getEid() - 1, emp);
return true;
}catch (Exception e){
e.printStackTrace();
}
return false;
}
@Override
public boolean deleteEmpByEid(int eid) {
try{
emps.remove(eid -1 );
return true;
}catch (Exception e){
e.printStackTrace();
}
return false;
}
}
EmpValidate.java,用来对于Emp做校验使用,有非空校验,有合法性校验。
package com.qfedu.validate;
import com.qfedu.bean.Emp;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
/**
* 用来完成对于Emp类的校验,有非空校验,合法性校验
*
* 里面包含两个方法,supports和validate
* supports方法的意思是当前类用来对于哪个类实现校验
* Emp.class.isAssignableFrom(clazz);意思是完成对与Emp类的校验
* validate方法,完成真正的校验,一定是满足了supports方法之后才会进入该方法来进行校验
*/
public class EmpValidate implements Validator {
/**
* 指定当前类是否支持指定类型的校验
* @param clazz
* @return
*/
@Override
public boolean supports(Class<?> clazz) {
return Emp.class.isAssignableFrom(clazz);
}
/**
* 真正的校验方法
* @param target,校验对象
* @param errors,存储错误信息
*/
@Override
public void validate(Object target, Errors errors) {
Emp e = (Emp) target;
/**
*
* 非空校验
*
* 使用ValidationUtils工具类来实现对于某些非空字段的校验,该方法包含三个参数:
* 1. 错误对象,用来收集并存储错误信息
* 2. 要校验的字段
* 3. 错误码,在msg配置文件中配置的key信息
*/
ValidationUtils.rejectIfEmpty(errors, "eid", "emp.eid");
ValidationUtils.rejectIfEmpty(errors, "name", "emp.name");
ValidationUtils.rejectIfEmpty(errors, "salary", "emp.salary");
double salary = e.getSalary();
/**
* 合法性校验,使用errors对象的rejectValue()方法完成合法性校验,里面包含了两个参数
*
* 1. field,在哪个字段上完成校验
* 2. 错误码,会在msg文件中找到该key对应的错误信息
*/
if(salary < 0){
errors.rejectValue("salary", "emp.salary.invalidate");
}
}
}
/WEB-INF/view/emp.jsp文件,用来展示所有的员工信息的页面,该页面包含两个超链接,修改和删除,修改使用的时候问号传参,删除使用的是路径传参
<%--
Created by IntelliJ IDEA.
User: james
Date: 2020/3/3
Time: 4:04 PM
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>user</title>
</head>
<body>
<h1>this is users page.</h1>
<c:if test="${list != null}">
<table border="1" align="center" width="80%">
<tr>
<th>eid</th>
<th>name</th>
<th>salary</th>
<th>manage</th>
</tr>
<c:forEach items="${list}" var="e">
<tr>
<td> ${e.eid}</td>
<td> ${e.name}</td>
<td> ${e.salary}</td>
<td> <a href="/emp/getEmpByEid?eid=${e.eid}">update</a> <a href="/emp/deleteByEid/${e.eid}">delete</a></td>
</tr>
</c:forEach>
</table>
</c:if>
</body>
</html>
/WEB-INF/view/updateEmp.jsp
<%--
Created by IntelliJ IDEA.
User: james
Date: 2020/3/3
Time: 4:39 PM
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>update Emp</title>
</head>
<body>
<h1>this is emp update page.</h1>
<form method="post" action="/emp/updateEmp">
eid:<input type="text" name="eid" value="${emp.eid}" readonly="readonly" /><br />
name:<input type="text" name="name" value="${emp.name}" /><br />
salary:<input type="text" name="salary" value="${emp.salary}" /><br />
<%--salary:<input type="text" name="birth.year" value="${emp.salary}" /><br />--%>
<input type="submit" value="submit" /><br />
</form>
</body>
</html>
/WEB-INF/view/saveEmp.jsp, 该页面要注意,引入了Spring MVC的form标签,表单使用的就是SpringMVC的form表单
f:form:使用的是Spring MVC的form标签,里面有一个属性叫做commandName,这个值是从后端传递过来的对象名,注意要与bean的小写方式一致
f:input类似于html中的input标签,但是将name换成了path,代表的是属性名
f:errors,这个标签可以用来展示如果当前表单有错误信息时,可以在对应的域之上进行回显,一般都被放在对应的f:input标签之后,用来描述该属性的错误信息
<%--
Created by IntelliJ IDEA.
User: james
Date: 2020/3/4
Time: 4:02 PM
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="f" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--<form action="" method="post">
eid:<input type="text" name="eid" />
</form>--%>
<f:form method="post" action="/emp/saveEmp" commandName="emp">
eid:<f:input path="eid" /><font color="red"><f:errors path="eid" /></font> <br>
name:<f:input path="name" /><font color="red"> <f:errors path="name" /></font><br>
salary:<f:input path="salary" /><font color="red"> <f:errors path="salary" /></font><br>
<input type="submit" value="submit" /> <br>
</f:form>
</body>
</html>