目录
- 1. SpringMVC概述
- 2. SpringMVC注解式开发
- 3. SpringMVC集成Spring集成Mybatis整合开发
- 4. SpringMVC核心技术
1. SpringMVC概述
1.1 SpringMVC是什么?
基于spring框架,spring框架的一个模块,专门负责简化web开发,可以理解为servlet的一个升级。所有的web开发都是基于servlet,框架就是在其基础上加入一些功能,让开发更方便。
同spring一样,springmvc也是一个容器,用@Controller来创建、管理控制器对象。这个对象可以接收参数,显示处理结果,在功能表现上与servlet一样,但是要注意的是,这样的一个对象实质上不是servlet,只是一个普通类的对象,只是springmvc赋予了控制器对象一些额外的功能。
其中M代表Model(数据),V代表View(视图),C代表Controller(控制器类)。
实质上,就像上面说的,所有web开发的底层都是servlet,springmvc中有一个对象是servlet:DispatcherServlet(中央调度器,也称作前端控制器Front controller),它本质上其实是一个容器提供的写好的servlet,其负责接收用户的所有请求,之后转发给Controller对象(也称后端控制器Back controller),最后是Controller对象处理请求。
1.2 学习SpringMVC有什么用?
1.3 SpringMVC的处理流程
即SpringMVC接收请求,到处理完成的过程(背诵):
- 用户发起请求some.do
- 中央调度器接收请求,将请求转发给处理器映射器
- 处理器映射器:实质上是SpringMVC框架的一种对象,框架把实现了HandlerMapping接口的类都称作映射器(可以有多个)
- 作用:根据请求,从SpringMVC容器对象中获取处理器对象(类似getBean()的过程),将其放到一个叫做处理器执行链(HandlerExecutionChain)的类中保存,这个类中还以List形式保存着项目中所有的拦截器。之后,将执行链返回中央调度器。
得到执行链的方法:
HandlerExecutionChain mappedHandler = getHandler(processedRequest);
- 中央调度器将执行链交给处理器适配器(可以有多个)
- 处理器适配器:SpringMVC框架的一种对象,实现了HandlerAdapter接口
- 作用:执行处理器方法。
调用适配器的方法:
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
执行处理器方法:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
- 中央调度器拿到结果后将其交给视图解析器解析(可以有多个)
- 视图解析器:SpringMVC的一种对象,实现了ViewResolver接口
- 作用:加上配置的前后缀,组成视图完整路径,并创建一个View对象。View是表示视图的接口,在框架中,并非用字符串(a.jsp, a.html等)表示视图,而是使用VIew和它的实现类。在其中,InternalResourceView就是用来表示jsp的视图类,视图解析器在需要的时候会创建它的对象,对象中有特定的属性url来记录完整路径:/WEB-INF/view/show.jsp
- 中央调度器将解析结果(View对象)拿到,调用其自己的方法,将Model数据放入到request作用域,然后执行视图的forward方法,返回请求结果。
2. SpringMVC注解式开发
示例项目:
2.1 创建web项目
2.1.1 填充目录结构
2.2.2 更改web.xml版本为最新
2.2 加依赖
<?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>org.example</groupId>
<artifactId>01-HelloSpringMVC</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<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>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--servlet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!--SpringMVC依赖,包含了spring要用到的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
</dependencies>
<build>
</build>
</project>
2.3 在web.xml中注册DispatcherServlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--
注册SpringMVC的核心对象DispatcherServlet,该对象在被创建的过程中会同时创建SpringMVC容器
对象,去读取SpringMVC配置文件,把配置文件中的对象都创建好并放入到ServletContext中,当用户
发起请求时就可以直接用那些对象了,所以我们希望在tomcat服务器启动后做的第一件事就是
创建DispatcherServlet对象
-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--
在<servlet>标签中使用<load-on-startup>标签来表明该servlet被创建的优先级,
一般为正整数,数字越小,优先级越高
-->
<load-on-startup>1</load-on-startup>
<!--
中央调度器创建后,会读取spring的配置文件,但是默认的读取路径是在WEB-INF下的,但是
我们的配置文件一般都放在专门的resources下,所以我们需要修改默认的读取路径,
在<servlet>中使用<init-param>标签修改
-->
<init-param>
<!--指定更改位置的文件的类型:配置文件-->
<param-name>contextConfigLocation</param-name>
<!--指定被更改位置的文件的位置-->
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!--
使用*.do则表示所有的以.do结尾的请求都交给<servlet-name>所标识的servlet
使用如/reg这样具体的标识,则表示只有明确表示请求地址是/reg的请求会交给该servlet
使用/:
如果单纯使用/,则该servlet会处理所有的请求,这样我们就不用重复写xxx.do了,但是
它同时会替代掉tomcat默认的servlet,来处理静态文件和所有未被映射到的请求,
如果该servlet没有处理静态资源(html, js, 图片, css)的能力,则这些访问都会404,
解决办法是在springmvc配置文件中加入能添加相应功能的标签
-->
<!-- <url-pattern>*.do</url-pattern>-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
2.3.1 url-pattern设为/,静态资源无法访问的问题
2.3.1.1 解决方案1
在springmvc配置文件中配置:<mvc:default-servlet-handler/>
<!--
<mvc:default-servlet-handler/>与@RequestMapping有冲突,所以
声明了<mvc:default-servlet-handler/>的情况下,必须声明注解驱动,将.do请求交由
自定义业务方法处理
-->
<mvc:annotation-driven/>
<!--
在url-pattern设为/的时候,声明如下标签,增加一个处理器对象DefaultHttpRequestHandler,
使得静态资源的访问交由该对象转发给tomcat自带的默认的servlet处理,此方式需要依赖服务器
-->
<mvc:default-servlet-handler/>
2.3.1.2 解决方案2
在springmvc配置文件中配置:<mvc:resources/>
<!--
声明如下标签,增加一个处理器对象ResourceHttpRequestHandler,将静态资源的访问交给它处理,
不依赖服务器,
mapping:访问静态资源的uri地址,如/images/p1.jpg,使用目录+通配符**的方式指定
location:静态资源在项目中的位置,指定具体文件的上一级目录即可,如/images/
-->
<mvc:resources mapping="/images/**" location="/images/"/>
<mvc:resources mapping="/html/**" location="/html/"/>
<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:resources mapping="/static/**" location="/static/"/>
同样的,需要加入注解驱动,支持动态请求的处理。
可以将所有的静态资源均放入到同一个目录static下,这样就不用大量的写<mvc:resources/>
2.4 创建控制器类
package controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class MyController {
/*
使用springmvc框架后我们就可以使用自定义的方法来处理请求
方法自定义,有多种返回值,常用的是ModelAndView
有多种参数
方法名称自定义
*/
/*
需要在方法名上加入@RequestMapping注解来表示此方法是控制器方法(处理器方法)
作用:请求映射,将一个请求地址与一个控制器方法绑定在一起,一个请求指定一个方法处理。
属性:value,是一个String数组,值为请求地址的uri,唯一值,需要加"/"
method,控制请求地址允许的访问方式,如只允许get方式,则设值为RequestMethod.GET,
post同理
位置:在方法名上(常用),在类名上
返回值:ModelAndView
Model:数据,表示请求处理完成后,要展现给用户的数据
View:视图,如jsp等
*/
@RequestMapping(value = "/some.do")
public ModelAndView doSome() {
ModelAndView mv = new ModelAndView();
//框架会在最后将加入的这些值放入到request作用域中
mv.addObject("msg", "service处理后的结果");
//指定视图,就是指定展现结果的jsp名称的完整路径,框架会对视图执行forward操作
mv.setViewName("show.jsp");
return mv;
}
}
2.5 创建发起请求的页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<a href="some.do">请求</a>
</body>
</html>
2.6 创建显示结果的jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<p>${msg}</p>
</body>
</html>
2.7 创建SpringMVC的配置文件
<?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"
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">
<!--
如果使用注解开发,使用了创建对象的注解,如@Controller,
则需要声明组件扫描器,来告诉框架
-->
<context:component-scan base-package="controller"/>
</beans>
2.8 总结
流程可以总结为:
请求页面发起some.do请求
→Tomcat服务器收到请求,通过web.xml知道需要转发给中央调度器
→中央调度器通过SpringMVC配置文件知道处理该请求的类的位置,并根据注解转发给相应方法
→框架通过执行相应方法,把得到的ModelAndView结果进行处理,转发给显示结果的jsp
2.9 定义请求规则
2.9.1 资源文件的访问限制
在上述示例中,我们创建的jsp文件都放在了webapp根目录下,这样会导致在地址栏输入资源名可以跳过登录直接访问资源的隐患,为此,除了使用在CRM项目中的“登录验证”方案外,我们还可以将jsp资源文件放到WEB-INF目录下,该目录下所有的文件都无法通过地址栏访问,而我们则可以通过更改控制器类中的路径代码的方式,在后台访问这些文件以完成前后台通信。
对于多个不同的资源文件的访问请求,我们可以通过配置视图解析器的方式,简化掉重复的路径代码。
在WEB-INF下创建新目录,将限制访问的资源放入该目录中:
更改控制器类中的路径代码:
mv.setViewName("/WEB-INF/view/show.jsp");
在SpringMVC配置文件中配置视图解析器:
<!--
声明视图解析器对象,来指定统一的路径,由框架提供,无需id。
声明后,在控制器路径代码中,只需填写资源文件的自定义名称即可
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--声明前缀,即资源文件名称前的完整路径-->
<property name="prefix" value="/WEB-INF/view/"/>
<!--声明后缀,即资源文件名称的扩展名-->
<property name="suffix" value=".jsp"/>
</bean>
路径代码可简化为:
mv.setViewName("show");
2.9.2 利用@RequestMapping声明模块名称,简化路径代码
对于同一模块的不同功能的访问,会存在访问路径的重复,如activity模块,有some和other两个功能,那么请求地址为/activity/some.do和/activity/other.do,有大量重复,这时,可以使用@RequestMapping注解放在类名上的方式,简化掉重复的模块名称。
在类名上方添加注解:
package controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
/*
@RequestMapping放在类名上:
value:所有请求地址的公共部分,也就是模块的名称
位置:类名的上方
*/
@Controller
@RequestMapping("/activity")
public class MyController {
@RequestMapping(value = "/some.do")
public ModelAndView doSome() {
ModelAndView mv = new ModelAndView();
mv.addObject("msg", "service处理后的结果");
mv.setViewName("show");
return mv;
}
}
发起请求的页面改为:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<a href="activity/some.do">请求</a>
</body>
</html>
2.10 控制器方法的参数
在控制器类方法的形参位置可定义如下特定类型的形参,前三者自动控制框架获取相应参数,这些参数在被调用时由框架自动赋值,在方法内即可直接使用,第四类使用逐个接收或对象接收的方式获取,逐个接收与对象接收可同时在同一个控制器方法中使用,不冲突:
- HttpServletRequest
- HttpServletResponse
- HttpSession
- 用户请求中所携带的请求参数(基本数据类型,java对象,List、Map集合或数组等)
2.10.1 直接使用
控制器类代码:
package controller;
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;
import javax.servlet.http.HttpSession;
@Controller
@RequestMapping("/activity")
public class MyController {
/*
在形参位置定义特定类型的参数,可以在方法中直接使用
*/
@RequestMapping(value = "/some.do")
public ModelAndView doSome(HttpServletRequest request,
HttpServletResponse response,
HttpSession session) {
ModelAndView mv = new ModelAndView();
mv.addObject("msg", "service处理后的结果" + request.getParameter("name"));
mv.setViewName("show");
return mv;
}
}
2.10.2 逐个接收
@Controller
@RequestMapping("/activity")
public class MyController {
/*
逐个接收:
需要收到的参数有多少个,就定义多少个形参来接收:
框架自动赋值,但是需要形参名与参数名一致,与位置无关。如果不一样,则需要在形参前加
注解@RequestParam("请求参数的名字"),否则会报空指针异常。同时,加了注解的形参必
须传值,否则网页会出现400异常,除非设置该注解的另一个属性required,该属性默认为真,
设为false,则对是否传值无要求,如
@RequestParam(value = "rname", required = false)
在某些情况中,形参定义的类型不允许空值,如int类型,但是却传进来了空值,
会发生状态码400错误,此时,将int改为Integer包装类,允许空值的类型即可。
*/
@RequestMapping(value = "/some.do")
// public ModelAndView doSome(String name, int age) {
public ModelAndView doSome(String name, Integer age) {
ModelAndView mv = new ModelAndView();
mv.addObject("name", name);
mv.addObject("age", age);
mv.setViewName("show");
return mv;
}
使用post方式传值时,会遇到乱码的问题,这时可以使用框架自带的过滤器CharacterEncodingFilter解决乱码问题,在web.xml中配置即可:
<!--声明框架自带的过滤器,解决乱码问题-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--重置过滤器声明的三个属性,分别为
encoding : 字符集
forceRequestEncoding : 设为true,强制请求对象使用encoding编码的值
forceResponseEncoding : 设为true,强制响应对象使用encoding编码的值-->
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2.10.3 对象接收
需要额外创建属性与参数相符的对象类,在vo中创建:
package vo;
public class Student {
//属性名和请求中的参数名需要保持一致
private String name;
private String age;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
控制器方法:
@RequestMapping("/object.do")
public ModelAndView doObject(Student student) {
ModelAndView mv = new ModelAndView();
mv.addObject("name", student.getName());
mv.addObject("age", student.getAge());
mv.setViewName("show");
return mv;
}
2.11 控制器方法的返回值
处理器方法的返回值表示请求的处理结果
- ModelAndView:包括数据和视图,对视图执行forward,用于传递数据且跳转新页面。
- String:仅表示视图,可以是单纯的逻辑名称(文件名),也可以是完整的视图路径(没有视图解析器的情况下),对视图执行forward,用于不传递数据,只跳转新页面。
- Void(不常用):不需要跳转页面时,可使用void,如ajax请求,其使用响应对象传递请求结果,同时,使用ajax就代表只需要数据而不需要视图(新页面)。
- Object:仅表示数据,直接在页面显示的数据。加入@ResponseBody注解,可以帮助我们进行响应对象返回数据的操作。
2.11.1 返回值String
@Controller
@RequestMapping("/activity")
public class MyController {
@RequestMapping(value = "/some.do")
public String doSome(String name, Integer age) {
return "show";
}
}
2.11.2 返回值Void
需要使用ajax,就需要jQuery库文件及Jackson依赖。
<!--jackson依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
在jsp中引入js库文件:
<script type="text/javascript" src="js/jquery-3.5.1.js"></script>
jsp代码:
<script type="text/javascript">
$(function () {
$("#btn").click(function () {
//alert("success")
$.ajax({
url:"activity/void.do",
data:{
name : "zhangsan",
age : 22
},
type:"post",
dataType:"json",
success: function (resp) {
alert(resp)
}
})
});
});
</script>
控制器类方法:
@Controller
@RequestMapping("/activity")
public class MyController {
@RequestMapping(value = "/void.do")
public void returnVoid(String name, String age, HttpServletResponse response) throws IOException {
System.out.println(name + age);
//假设service已返回一个Student
Student student = new Student();
student.setName(name);
student.setAge(age);
response.setContentType("application/json;charset=utf-8");
String json;
ObjectMapper om = new ObjectMapper();
json = om.writeValueAsString(student);
System.out.println(json);
PrintWriter pw = response.getWriter();
pw.print(json);
pw.flush();
pw.close();
}
2.11.3 返回值Object
主要步骤:
- 加入处理json的工具库的依赖,springmvc默认使用jackson
- 在springmvc配置文件中加入
<mvc:annotation-driven>
注解驱动,帮助我们完成json字符串的赋值 - 在控制器类方法上加入@ResponseBody注解,帮助我们完成响应对象设置编码方式,传递数据给浏览器的操作
内部实际上是通过HttpMessageConverter接口完成数据转换。
HttpMessageConverter:消息转换器接口,定义了java转json,转xml等的方法:
canwrite(), write()
其有多种实现类:
常用的StringHttpMessageConverter, MappingJackson2HttpMessageConverter
具体实现了java对象转为json、xml等等对象的转换。
依赖:
<!--jackson依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
注解驱动:
<mvc:annotation-driven/>
控制器类方法:
/*
控制器方法返回Student对象,通过框架辅助转为json,响应浏览器的ajax请求
@ResponseBody:把控制器方法的返回值转为json,再通过response对象输出给浏览器
位置:在方法上方,与其他注解没有顺序要求
返回值是String时,因为有该注解的存在,此时的String表示文本数据,而非视图,此时因为
返回的不是json格式,所以在ajax中dataType类型指定需要去除,并且此时会产生乱码的现象,
解决方法是在@RequestMapping注解的属性中指定具体编码格式,如
@RequestMapping(value = "/object.do", produces = "text/plain;charset=utf-8")
需要注意的是,此问题无法通过过滤器的方式解决,因为通过@ResponseBody,结果不经过过滤器
*/
@RequestMapping(value = "/object.do")
@ResponseBody
public Student returnObject(String name, String age) {
//假设service已返回一个Student
Student student = new Student();
student.setName(name);
student.setAge(age);
return student;
}
框架的处理流程:
- 将返回的对象,带入注解驱动创建好的ArrayList中的每个实现类的canwrite()方法,找出返回true的
- 调用返回true的类的write()方法把对象转换为json
- 调用@ResponseBody的功能把结果输出到浏览器
同样的,返回对象是集合也可以,在jsp遍历显示即可,是一样的原理。
3. SpringMVC集成Spring集成Mybatis整合开发
- SpringMVC——界面层
- Spring——业务层
- MyBatis——持久层
3.1 选择表文件
3.2 创建web项目
3.3 加依赖,加插件
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--springmvc依赖,包含了spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--spring依赖,有了springmvc依赖就不需要了-->
<!-- <dependency>-->
<!-- <groupId>org.springframework</groupId>-->
<!-- <artifactId>spring-context</artifactId>-->
<!-- <version>5.2.5.RELEASE</version>-->
<!-- </dependency>-->
<!--spring事务依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--web项目必须的jsp和servlet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1.3-b06</version>
</dependency>
<!--jackson依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!--mybatis-spring集成依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<!--连接池依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<build>
<!--捆绑插件-->
<resources><!--固定写法,粘贴复制即可-->
<resource>
<directory>src/main/java</directory><!--需要告诉Maven一同拷贝的文件所在的目录-->
<includes><!--使目录下的.properties和.xml文件都被扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
3.4 编写web.xml
更新web.xml版本:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>crm</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>xxxController</servlet-name>
<servlet-class>xxx.xxxController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>xxxController</servlet-name>
<url-pattern>/xxx/xxx.do</url-pattern>
</servlet-mapping>
</web-app>
3.4.1 注册SpringMVC的中央调度器DispatcherServlet
记得声明配置文件的位置,及服务器启动时创建的优先级
<!--注册中央调度器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:conf/dispatcherServlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
3.4.2 注册Spring的监听器ContextLoaderListener
记得声明配置文件的位置
<!--
注册监听器,用于在服务器启动时创建spring的bean对象,记得使用<context-param>标签
声明spring配置文件的位置
-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:conf/applicationContext.xml</param-value>
</context-param>
3.4.3 注册字符集过滤器
<!--注册过滤器,重置其属性将字符集改为utf-8-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3.5 创建各级目录包
3.6 编写各容器配置文件
3.6.1 SpringMVC配置文件
使用注解开发就需要声明组件扫描器及注解驱动
<!--声明spring的组件扫描器,用于注解开发-->
<context:component-scan base-package="controller"/>
<!--声明mvc的注解驱动-->
<mvc:annotation-driven/>
<!--声明视图解析器对象,简化资源文件名,无需id-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
3.6.2 数据库属性的配置文件
jdbc.url=jdbc:mysql://localhost:3306:springmysql/serverTimezone=GMT
jdbc.username=root
jdbc.password=root
3.6.3 Spring配置文件
使用注解开发就需要声明组件扫描器
<!--
将数据库的配置信息写在一个独立的配置文件中,便于修改
通过property-placeholder让spring知道配置文件的位置
-->
<context:property-placeholder location="classpath:conf/jdbc.properties"/>
<!--声明数据源DataSource,连接数据库-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--
声明mybatis提供的mybatis-spring集成中的SqlSessionFactoryBean类,通过之前声明的数据源,
结合mybatis的主配置文件,这个类可以在内部创建SqlSessionFactory
-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:conf/mybatis-config.xml"/>
</bean>
<!--
声明Dao对象:MapperScannerConfigurer:
通过之前声明的SqlSessionFactory对象可以自动在内部调用getMapper()
对属性中指定的dao包中的每个dao接口生成代理对象,无需声明id
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="dao"/>
</bean>
<!--声明service-->
<!--声明组件扫描器,用于注解开发-->
<context:component-scan base-package="service"/>
3.6.4 mybatis主配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--控制mybatis全局行为-->
<settings>
<!--设置mybatis输出日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--设置别名,这样在dao映射配置文件中的返回类型中,就可以用别名代替全限定名称-->
<typeAliases>
<!--name:实体类所在的包名,这样该包下所有的类的类名就是别名-->
<package name="domain"/>
</typeAliases>
<!--因为要使用新的连接池,所以以下这一块就不再需要了-->
<!-- <environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydata"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>-->
<!--配置dao映射配置文件的位置-->
<mappers>
<!--使用mapper就需要每个配置文件都写一次-->
<!-- <mapper resource="mapper/StudentMapper.xml"/>-->
<!--使用package则只需要写包名的全限定名称即可,这样,这个包中所有的映射文件一次性全加载-->
<package name="dao"/>
</mappers>
</configuration>
3.7 填充各级目录
3.7.1 编写实体类
package domain;
public class Student {
private Integer id;
private String name;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
3.7.2 编写Dao接口和mapper文件
package dao;
import domain.Student;
import java.util.List;
public interface StudentDao {
int addStudent(Student student);
List<Student> queryStudent();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dao.StudentDao">
<insert id="addStudent">
insert into student(
name,
age
) values (
#{name},
#{age}
)
</insert>
<select id="queryStudent" resultType="Student">
select id, name, age from student
</select>
</mapper>
3.7.3 编写service接口及其实现类
package service;
import domain.Student;
import java.util.List;
public interface StudentService {
int addStudent(Student student);
List<Student> queryStudent();
}
package service.impl;
import dao.StudentDao;
import domain.Student;
import org.springframework.stereotype.Service;
import service.StudentService;
import javax.annotation.Resource;
import java.util.List;
@Service
public class StudentServiceImpl implements StudentService {
@Resource
private StudentDao studentDao;
@Override
public int addStudent(Student student) {
int count = studentDao.addStudent(student);
return count;
}
@Override
public List<Student> queryStudent() {
List<Student> sList = studentDao.queryStudent();
return sList;
}
}
3.7.4 编写控制器类
package controller;
import domain.Student;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import service.StudentService;
import javax.annotation.Resource;
import java.util.List;
@Controller
public class StudentController {
@Resource
private StudentService studentService;
/*
在设置了视图解析器的前提下,服务器内部的访问均会受到解析器的影响,
能访问的资源文件均在解析器的限定之下,那么当我们需要访问非解析器指定的目录中的文件时,
在视图字符串前加入关键字forward或redirect,通过该关键字,可以令该行代码对资源文件的
访问不受解析器的影响,如
mv.setViewName("forward:queryStudent.jsp");
mv.setViewName("redirect:queryStudent.jsp");
这时,资源文件的名称就要求是有前缀路径和后缀扩展名的格式
*/
@RequestMapping("/addStudent.do")
public ModelAndView addStudent(Student student) {
ModelAndView mv = new ModelAndView();
String tips = "注册失败";
int count = studentService.addStudent(student);
if (count > 0) {
tips = "学生" + student.getName() + "注册成功";
}
mv.addObject("msg", tips);
mv.setViewName("result");
return mv;
}
@RequestMapping("/queryStudent.do")
@ResponseBody
public List<Student> queryStudent() {
List<Student> sList = studentService.queryStudent();
return sList;
}
}
在关键字重定向中,框架会把Model中的简单类型的数据,转为string,作为目标jsp页面的请求参数使用,这样就可以在重定向的转发类型之下完成数据的传递。
在目标jsp页面中可以使用EL表达式${param.key}的方式获得数据值。另外,重定向方式不能访问WEB-INF/下的资源
3.8 编写jsp页面
请求页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/";
%>
<html>
<head>
<base href="<%=basePath%>">
<title>学生管理</title>
</head>
<body>
<table>
<tr>
<td><a href="addStudent.jsp">注册</a></td>
</tr>
<tr>
<td><a href="queryStudent.jsp"> 查询</a></td>
</tr>
</table>
</body>
</html>
跳转页面addStudent.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/";
%>
<html>
<head>
<base href="<%=basePath%>">
<title>Title</title>
</head>
<body>
<form action="addStudent.do" method="post">
<table>
<tr>
<td><input type="text" name="name" placeholder="姓名"/> </td>
</tr>
<tr>
<td><input type="text" name="age" placeholder="年龄"/> </td>
</tr>
<tr>
<td><input type="submit" value="提交"/> </td>
</tr>
</table>
</form>
</body>
</html>
跳转页面queryStudent.jsp:
需要用到ajax的情况下,需要先引入js库,然后确保jackson依赖已添加,再加入引用代码才能使用。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/";
%>
<html>
<head>
<base href="<%=basePath%>">
<title>Title</title>
<script type="text/javascript" src="js/jquery-3.5.1.js"></script>
<script>
$(function () {
$("#btn").click(function () {
queryStudent();
});
queryStudent();
});
function queryStudent() {
$.ajax({
url:"queryStudent.do",
type:"post",
dataType:"json",
success: function (resp) {
$("#info").html("");
$.each(resp, function (index, element) {
$("#info").append("<tr>")
.append("<td>" + element.id + "</td>")
.append("<td>" + element.name + "</td>")
.append("<td>" + element.age + "</td>")
.append("</tr>")
});
}
})
}
</script>
</head>
<body>
<input type="button" id="btn" value="查询所有学生信息">
<div id="div">
<table id="table">
<thead>
<tr>
<td>编号</td>
<td>姓名</td>
<td>年龄</td>
</tr>
</thead>
<tbody id="info"></tbody>
</table>
</div>
</body>
</html>
4. SpringMVC核心技术
4.1关键字:重定向和转发
在设置了视图解析器的前提下,服务器内部的访问均会受到解析器的影响,能访问的资源文件均在解析器的限定之下,那么当我们需要访问非解析器指定的目录中的文件时,在视图字符串前加入关键字forward或redirect,通过该关键字,可以令该行代码对资源文件的访问不受解析器的影响,如
mv.setViewName(“forward:queryStudent.jsp”);
mv.setViewName(“redirect:queryStudent.jsp”);
这时,资源文件的名称就要求是有前缀路径和后缀扩展名的格式。
4.2 异常处理
自定义异常类,在类名上加入注解@ControllerAdvice,在方法名上加入注解@ExceptionHandler(value=“异常类.class”),可创建一个处理所有异常的异常处理类,在方法上的注解中注明处理的异常类型。这样做的好处是可以将异常处理代码与源代码分离,不用在源代码中写try/catch,体现了aop,解耦合的思想。同样的,这样的对象的创建也在mvc配置文件中完成。
自定义异常类:
package exception;
public class UserException extends Exception {
public UserException() {
super();
}
public UserException(String message) {
super(message);
}
}
package exception;
public class NameException extends UserException{
public NameException() {
super();
}
public NameException(String message) {
super(message);
}
}
package exception;
public class AgeException extends UserException{
public AgeException() {
super();
}
public AgeException(String message) {
super(message);
}
}
定义全局异常处理类:
package handler;
import exception.AgeException;
import exception.NameException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
@ControllerAdvice
public class GlobalExceptionHandler {
/*
该方法与处理器方法相同,返回值有多种。
形参exception,必填,表示Controller抛出的异常对象。
异常处理逻辑:
1.在日志中记录异常,发生时间,发生在哪里,异常错误内容
2.发送通知,邮件/短信/微信通知相关人员
3.给用户提示
*/
@ExceptionHandler(NameException.class)
public ModelAndView doNameException(Exception exception) {
ModelAndView mv = new ModelAndView();
mv.addObject("nameException", exception);
mv.setViewName("NameError");
return mv;
}
@ExceptionHandler(AgeException.class)
public ModelAndView doAgeException(Exception exception) {
ModelAndView mv = new ModelAndView();
mv.addObject("ageException", exception);
mv.setViewName("AgeError");
return mv;
}
/*
配备属性为空的注解的方法作为所有其他异常的处理方法
*/
@ExceptionHandler()
public ModelAndView doOther(Exception exception) {
ModelAndView mv = new ModelAndView();
mv.addObject("exception", exception);
mv.setViewName("DefaultError");
return mv;
}
}
控制器方法:
package controller;
import exception.AgeException;
import exception.NameException;
import exception.UserException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class StudentController {
@RequestMapping("/exception.do")
public ModelAndView doException(String name, Integer age) throws UserException {
if (!"zs".equals(name)) {
throw new NameException("只有zs可以登录");
}
if (age > 80) {
throw new AgeException("年龄不能大于80");
}
ModelAndView mv = new ModelAndView();
mv.addObject("name", name);
mv.addObject("age", age);
mv.setViewName("result");
return mv;
}
}
mvc配置文件声明组件扫描器:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--声明spring的组件扫描器,用于注解开发-->
<context:component-scan base-package="controller"/>
<context:component-scan base-package="handler"/>
<!--声明mvc的注解驱动-->
<mvc:annotation-driven/>
<!--声明视图解析器对象,简化资源文件名,无需id-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
4.3 拦截器
自定义拦截器类实现框架中的HandlerInterceptor接口,在mvc配置文件中声明,指定需要拦截的uri地址。
多用在用户登录处理,权限检查,记录日志等模块。
其实是多个Controller中的功能集中到一处,同样体现了aop的思想
拦截器和过滤器的区别:
- 过滤器是servlet中的对象,拦截器是框架中的对象
- 过滤器实现Filter接口,拦截器实现HandlerInterceptor
- 过滤器在拦截器之前执行
- 过滤器是tomcat创建的,拦截器是springmvc创建的
- 过滤器只有一个执行时间点,拦截器有三个执行时间点
- 过滤器可以处理多个文件:jsp,js,html等,拦截器只拦截对Controller的访问,如果你的请求无法被中央调度器接收,就也不会执行拦截器
拦截器:
package handler;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Interceptor implements HandlerInterceptor {
/*
Object handler:被拦截的控制器对象
该方法在控制器执行前执行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object object = request.getSession().getAttribute("name");
String user = "";
if (object != null) {
user = (String) object;
}
if (!"zs".equals(user)) {
request.getRequestDispatcher("/tips.jsp").forward(request, response);
return false;
}
return true;
}
/*
ModelAndView modelAndView:处理器方法的返回值
可对控制器方法产生的结果进行修改,影响最后的执行结果。
主要作用是对结果进行二次修正
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/*
Exception ex:程序中发生的异常
一般用于资源回收
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
模拟用户登录:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/";
%>
<html>
<head>
<base href="<%=basePath%>">
<title>账户登录</title>
</head>
<body>
<%
request.getSession().setAttribute("name", "zs");
%>
</body>
</html>
模拟用户注销:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/";
%>
<html>
<head>
<base href="<%=basePath%>">
<title>账户注销</title>
</head>
<body>
<%
request.getSession().removeAttribute("name");
%>
</body>
</html>
配置拦截器:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--声明spring的组件扫描器,用于注解开发-->
<context:component-scan base-package="controller"/>
<context:component-scan base-package="handler"/>
<!--声明mvc的注解驱动-->
<mvc:annotation-driven/>
<!--声明视图解析器对象,简化资源文件名,无需id-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!--
声明拦截器
多个拦截器在框架内以List集合的形式保存,先声明的先保存,先拦截
-->
<mvc:interceptors>
<mvc:interceptor>
<!--指定拦截的uri地址,可使用通配符-->
<mvc:mapping path="/**"/>
<!--声明拦截器对象-->
<bean class="handler.Interceptor"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>