第一章 初识SpringMVC
1.1 SpringMVC概述
-
SpringMVC是Spring子框架
-
SpringMVC是Spring 为【展现层|表示层|表述层|控制层】提供的基于 MVC 设计理念的优秀的 Web 框架,是目前最主流的MVC 框架。
-
SpringMVC是非侵入式:可以使用注解让普通java对象,作为请求处理器【Controller】。
-
SpringMVC是用来代替Servlet
Servlet作用
1. 处理请求 - 将数据共享到域中 2. 做出响应 - 跳转页面【视图】
1.2 SpringMVC处理请求原理简图
- 请求
- DispatcherServlet【前端控制器】
- 将请求交给Controller|Handler
- Controller|Handler【请求处理器】
- 处理请求
- 返回数据模型
- ModelAndView
- Model:数据模型
- View:视图对象或视图名
- DispatcherServlet渲染视图
- 将数据共享到域中
- 跳转页面【视图】
- 响应
第二章 SpringMVC搭建框架
2.1 搭建SpringMVC框架
-
创建工程【web工程】
-
导入jar包
<!--spring-webmvc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.1</version> </dependency> <!-- 导入thymeleaf与spring5的整合包 --> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> <version>3.0.12.RELEASE</version> </dependency> <!--servlet-api--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency>
-
编写配置文件
-
web.xml注册DispatcherServlet
- url配置:/
- init-param:contextConfigLocation,设置springmvc.xml配置文件路径【管理容器对象】
- <load-on-startup>:设置DispatcherServlet优先级【启动服务器时,创建当前Servlet对象】,未设置时为接收到第一个请求时创建Servlet对象
<?xml version="1.0" encoding="UTF-8" ?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="4.0" 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"> <!-- 注册DispatcherServlet--> <servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 设置springmvc.xml配置文件路径【管理容器对象】--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <!-- 设置加载优先级【启动服务器时,创建当前Servlet对象】--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
-
springmvc.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/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 开启组件扫描--> <context:component-scan base-package="com.atguigu"></context:component-scan> <!-- 装配视图解析器--> <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver"> <!--配置字符集属性--> <property name="characterEncoding" value="UTF-8"/> <!--配置模板引擎属性--> <property name="templateEngine"> <!--配置内部bean--> <bean class="org.thymeleaf.spring5.SpringTemplateEngine"> <!--配置模块的解析器属性--> <property name="templateResolver"> <!--配置内部bean--> <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"> <property name="characterEncoding" value="UTF-8"/> <!--配置前缀--> <property name="prefix" value="/WEB-INF/pages/"/> <!--配置后缀--> <property name="suffix" value=".html"/> </bean> </property> </bean> </property> </bean> </beans>
-
-
编写请求处理器【Controller|Handler】
- 使用@Controller注解标识请求处理器
- 使用@RequestMapping注解标识处理方法【URL】
@Controller //表示当前类为一个请求处理器类 public class HelloController { //配置url 【/】映射到WEB-INF/index.html @RequestMapping("/") public String toIndex(){ //返回逻辑视图名index //物理视图名= 视图前缀+逻辑视图名+视图后缀 return "index"; } }
-
准备页面进行,测试
第三章 @RequestMapping详解
@RequestMapping注解作用:为指定的类或方法设置相应URL
3.1 @RequestMapping注解位置
-
书写在类上面
- 作用:为当前类设置映射URL
- 注意:不能单独使用,需要与方法上的@RequestMapping配合使用
@Controller @RequestMapping("/test") public class HelloController { //映射的请求路径为:/test/toEmpListPage @RequestMapping("/toEmpListPage") public String toEmpListPage() { return "success"; } }
-
书写在方法上面
- 作用:为当前方法设置映射URL
- 注意:可以单独使用
@Controller public class HelloController { //映射的请求路径为:/toEmpListPage @RequestMapping("/toEmpListPage") public String toEmpListPage() { return "success"; } }
3.2 @RequestMapping注解属性
-
value属性
- 类型:String[]
- 作用:设置URL信息
@Controller public class HelloController { //映射的请求路径为:/toEmpListPage @RequestMapping(value = "/toEmpListPage") public String toEmpListPage() { return "success"; } }
-
path属性
- 类型:String[]
- 作用:与value属性作用一致
@Controller public class HelloController { //映射的请求路径为:/toEmpListPage @RequestMapping(path = "/toEmpListPage") public String toEmpListPage() { return "success"; } }
-
method属性
-
类型:RequestMethod[]
public enum RequestMethod { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE }
-
作用:为当前URL【类或方法】设置请求方式【POST、DELETE、PUT、GET】
-
注意:
- 默认情况:所有请求方式均支持
- 如请求方式不支持,会报如下错误
- 405【Request method 'GET' not supported】
@Controller public class HelloController { @RequestMapping(value = "/toEmpListPage",method = RequestMethod.GET) public String toEmpListPage() { return "success"; } }
-
-
params
- 类型:String[]
- 作用:为当前URL设置请求参数
- 注意:如设置指定请求参数,但URL中未携带指定参数,会报如下错误
- 400【Parameter conditions "lastName" not met for actual request parameters:】
@Controller public class HelloController { //设置指定请求参数lastName @RequestMapping(value = "/toEmpListPage",params = "lastName") public String toEmpListPage() { return "success"; } }
-
headers
- 类型:String[]
- 作用:为当前URL设置请求头信息
- 注意:如设置指定请求头,但URL中未携带请求头,会报如下错误
- 404:请求资源未找到
-
示例代码
@RequestMapping(value = {"/saveEmp","/insertEmp"}, method = RequestMethod.GET, params = "lastName=lisi", headers = "User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36") public String saveEmp(){ System.out.println("添加员工信息!!!!"); return SUCCESS; }
@RequestMapping(method = RequestMethod.POST) public @interface PostMapping {} @RequestMapping(method = RequestMethod.GET) public @interface GetMapping {} @RequestMapping(method = RequestMethod.PUT) public @interface PutMapping {} @RequestMapping(method = RequestMethod.DELETE) public @interface DeleteMapping {}
3.3 @RequestMapping支持Ant 风格的路径(了解)
-
常用通配符
a) 【?】:匹配一个字符
b) 【*】:匹配任意字符
c) 【**】:匹配多层路径
-
示例代码
@RequestMapping("/testAnt/**") public String testAnt(){ System.out.println("==>testAnt!!!"); return SUCCESS; }
第四章 @PathVariable 注解
4.1 @PathVariable注解位置
@Target(ElementType.PARAMETER)
- 书写的位置在在参数前面
4.2 @PathVariable注解作用
-
获取URL中占位符参数
-
占位符语法:{}
-
示例代码
<a th:href="@{/EmpController/testPathVariable/1001}">测试PathVariable注解</a><br>
/** * testPathVariable * @return */ @RequestMapping("/testPathVariable/{empId}") public String testPathVariable(@PathVariable("empId") Integer empId){ System.out.println("empId = " + empId); return SUCCESS; }
4.3 @PathVariable注解属性
-
value属性
- 类型:String
- 作用:设置占位符中的参数名
-
name属性
- 类型:String
- 作用:与name属性的作用一致
-
required属性
- 类型:boolean
- 作用:设置当前参数是否必须入参【默认值:true】
- true:表示当前参数必须入参,如未入参会报如下错误
- Missing URI template variable 'empId' for method parameter of type Integer
- false:表示当前参数不必须入参,如未入参,会装配null值
- true:表示当前参数必须入参,如未入参会报如下错误
<a th:href="@{/testPathVariable/1001}">测试PathVariable注解</a><br/>
//测试@PathVariable注解 @RequestMapping("/testPathVariable/{empId}") public String testPathVariable(@PathVariable(value = "empId", required = false) Integer empId) { System.out.println("empId = " + empId); return "success"; }
第五章 REST【RESTful】风格CRUD
5.1 REST的CRUD与传统风格CRUD对比
-
传统风格CRUD
- 功能 URL 请求方式
- 增 /insertEmp POST
- 删 /deleteEmp?empId=1001 GET
- 改 /updateEmp POST
- 查 /selectEmp?empId=1001 GET
-
REST风格CRUD
- 功能 URL 请求方式
- 增 /emp POST
- 删 /emp/1001 DELETE
- 改 /emp PUT
- 查 /emp/1001 GET
5.2 REST风格CRUD优势
-
REST:Representational State Transfer,表现层状态转换。表现层状态转换是基于超文本传输协议(HTTP)之上而确定的一组约束和属性,是一种设计提供万维网网络服务的软件构建风格。
-
REST风格的网络服务允许客户端发出以同一资源标识符访问和操作网络资源的请求,即将服务器中的资源通过URI的方式来定位。
-
REST风格的URL四种请求方式
- GET:用来获取资源
- POST:用来新建资源
- PUT:用来更新资源
- DELETE:用来删除资源
-
REST风格优势
-
提高网站排名
- 排名方式
- 竞价排名
- 技术排名
- 排名方式
-
便于第三方平台对接
-
5.3 实现PUT&DELETE提交方式步骤
-
注册过滤器HiddenHttpMethodFilter
<!--注册HiddenHttpMethodFilter--> <!--Http请求方法过滤器--> <filter> <filter-name>HiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>HiddenHttpMethodFilter</filter-name> <!--所有请求均需要经过过滤器--> <url-pattern>/*</url-pattern> </filter-mapping>
-
设置表单的提交方式为POST
-
设置参数:_method=PUT或_method=DELETE
<h3>修改员工信息-PUT方式提交</h3> <form th:action="@{/emp}" method="post"> <input type="hidden" name="_method" value="PUT"> <input type="submit" value="修改员工信息"> </form> <h3>删除员工信息-DELETE方式提交</h3> <form th:action="@{/emp/1001}" method="post"> <input type="hidden" name="_method" value="DELETE"> <input type="submit" value="删除员工信息"> </form>
//修改员工信息 @RequestMapping(value = "/emp",method = RequestMethod.PUT) public String updateEmp() { System.out.println("==>修改员工信息!"); return "success"; } //删除员工信息 @RequestMapping(value = "/emp/{empId}",method = RequestMethod.DELETE) public String deleteEmp(@PathVariable("empId")Integer empId) { System.out.println("==>删除员工信息!empId:"+empId); return "success"; }
5.4 源码解析HiddenHttpMethodFilter
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = DEFAULT_METHOD_PARAM;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter(requestToUse, response);
}
/**
* Simple {@link HttpServletRequest} wrapper that returns the supplied method for
* {@link HttpServletRequest#getMethod()}.
*/
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
@Override
public String getMethod() {
return this.method;
}
}
总结:
- 使用 HiddenHttpMethodFilter 过滤器时,表单的提交方式必须为POST方式,且input标签的type属性为hidden,name属性值为_method,value属性值为提交的方式(PUT或DELETE等方式)
第六章 SpringMVC处理请求数据
使用Servlet处理请求数据
- 请求参数
- String param = request.getParameter();
- 请求头
- request.getHeader();
- Cookie
- request.getCookies();
6.1 处理请求参数
-
默认情况:可以将请求参数名,与入参参数名一致的参数,自动入参【自动类型转换】
-
SpringMVC支持POJO入参
-
要求:请求参数名与POJO的属性名必须保持一致
-
示例代码
<form th:action="@{/saveEmp}" method="POST"> id:<input type="text" name="id"><br> LastName:<input type="text" name="lastName"><br> Email:<input type="text" name="email"><br> Salary:<input type="text" name="salary"><br> <input type="submit" value="添加员工信息"> </form>
/** * 获取请求参数POJO * @return */ @RequestMapping(value = "/saveEmp",method = RequestMethod.POST) public String saveEmp(Employee employee){ System.out.println("employee = " + employee); return SUCCESS; }
-
-
@RequestParam注解
-
作用:如请求参数与入参参数名不一致时,可以使用@RequestParam注解设置入参参数名
-
属性
- value
- 类型:String
- 作用:设置需要入参的参数名
- name
- 类型:String
- 作用:与value属性作用一致
- required
- 类型:Boolean
- 作用:设置当前参数,是否必须入参
- true【默认值】:表示当前参数必须入参,如未入参会报如下错误
- 400【Required String parameter 'sName' is not present】
- false:表示当前参数不必须入参,如未入参,装配null值
- true【默认值】:表示当前参数必须入参,如未入参会报如下错误
- defaultValue
- 类型:String
- 作用:当装配数值为null时,指定当前defaultValue默认值
- value
-
示例代码
<h3>获取请求参数</h3> <a th:href="@{/requestParam1(sName='zs')}">测试处理请求参数</a>
/** * 获取请求参数 * @return */ @RequestMapping("/requestParam1") public String requestParam1(@RequestParam(value = "sName",required = false, defaultValue = "zhangsan") String stuName, Integer stuAge){ System.out.println("stuName = " + stuName); System.out.println("stuAge = " + stuAge); return SUCCESS; }
-
6.2 处理请头
-
语法:@RequestHeader注解
-
属性
- value
- 类型:String
- 作用:设置需要获取请求头名称
- name
- 类型:String
- 作用:与value属性作用一致
- required
- 类型:boolean
- 作用:【默认值true】
- true:设置当前请求头是否为必须入参,如未入参会报如下错误
- 400【Required String parameter 'sName' is not present】
- false:表示当前参数不必须入参,如未入参,装配null值
- true:设置当前请求头是否为必须入参,如未入参会报如下错误
- defaultValue
- 类型:String
- 作用:当装配数值为null时,指定当前defaultValue默认值
- value
-
示例代码
<a th:href="@{/testGetHeader}">测试获取请求头</a>
/** * 获取请求头 * @return */ @RequestMapping(value = "/testGetHeader") public String testGetHeader(@RequestHeader("Accept-Language")String al, @RequestHeader("Referer") String ref){ System.out.println("al = " + al); System.out.println("ref = " + ref); return SUCCESS; }
6.3 处理Cookie信息
-
语法:@CookieValue获取Cookie数值
-
属性
- value
- 类型:String
- 作用:设置需要获取Cookie名称
- name
- 类型:String
- 作用:与value属性作用一致
- required
- 类型:boolean
- 作用:【默认值true】
- true:设置当前Cookie是否为必须入参,如未入参会报如下错误
- 400【Required String parameter 'sName' is not present】
- false:表示当前Cookie不必须入参,如未入参,装配null值
- true:设置当前Cookie是否为必须入参,如未入参会报如下错误
- defaultValue
- 类型:String
- 作用:当装配数值为null时,指定当前defaultValue默认值
- value
-
示例代码
<a th:href="@{/setCookie}">设置Cookie信息</a><br> <a th:href="@{/getCookie}">获取Cookie信息</a><br>
/** * 设置Cookie * @return */ @RequestMapping("/setCookie") public String setCookie(HttpSession session){ //方式一: //Cookie cookie = new Cookie(); //方式二:使用session System.out.println("session.getId() = " + session.getId()); return SUCCESS; } /** * 获取Cookie * @return */ @RequestMapping("/getCookie") public String getCookie(@CookieValue("JSESSIONID")String cookieValue){ System.out.println("cookieValue = " + cookieValue); return SUCCESS; }
6.4 使用原生Servlet-API
- 将原生Servlet相关对象,入参即可
@RequestMapping("/useRequestObject")
public String useRequestObject(HttpServletRequest request) {
//获取Servlet上下文对象
ServletContext servletContext = request.getServletContext();
//获取某个文件的真实路径
String realPath = servletContext.getRealPath("WEB-INF/pages/index.html");
System.out.println("realPath = " + realPath);
return "success";
}
第七章 SpringMVC处理响应数据
7.1 处理响应数据方式一:使用ModelAndView
-
使用ModelAndView对象作为方法返回值类型,处理响应数据
-
Model主要用于向请求域共享数据
-
View主要用于设置视图,实现页面跳转
-
ModelAndView有Model和View的功能
-
-
底层实现原理
- 数据共享到request域
- 跳转路径方式:转发
-
ModelAndView是模型数据与视图对象的集成对象,源码如下
public class ModelAndView { /** View instance or view name String. */ //view代表view对象或viewName【建议使用viewName】 @Nullable private Object view; /** Model Map. */ //ModelMap集成LinkedHashMap,存储数据 @Nullable private ModelMap model; /** 设置视图名称 */ public void setViewName(@Nullable String viewName) { this.view = viewName; } /** * 获取视图名称 */ @Nullable public String getViewName() { return (this.view instanceof String ? (String) this.view : null); } /** 获取数据,返回Map【无序,model可以为null】 */ @Nullable protected Map<String, Object> getModelInternal() { return this.model; } /** * 获取数据,返回 ModelMap【有序】 */ public ModelMap getModelMap() { if (this.model == null) { this.model = new ModelMap(); } return this.model; } /** * 获取数据,返回Map【无序】 */ public Map<String, Object> getModel() { return getModelMap(); } /** 设置数据 */ public ModelAndView addObject(String attributeName, @Nullable Object attributeValue) { getModelMap().addAttribute(attributeName, attributeValue); return this; } }
-
示例代码
@GetMapping("/testMvResponsedata") public ModelAndView testMvResponsedata(){ ModelAndView mv = new ModelAndView(); //设置数据【将数据共享到Request域中(request\session\servletContext)】 mv.addObject("stuName","zhangsan"); //设置逻辑视图名【转发或重定向】 mv.setViewName("response_success"); return mv; }
<!--response_success.html--> <h2>响应数据成功界面</h2> stuName:<span th:text="${stuName}"></span>
7.2 处理响应数据方式二:使用Model、ModelMap、Map
-
使用Model、ModelMap、Map作为方法入参,处理响应数据
-
示例代码
/** * 使用Map、Model、ModelMap处理响应数据 * @return */ @GetMapping("/testMapResponsedata") public String testMapResponsedata(Map<String,Object> map /* Model model ModelMap modelMap*/){ map.put("stuName","zhangsan"); // model.addAttribute("stuName","lisi"); // modelMap.addAttribute("stuName","wangwu"); return "response_success"; }
-
Model、ModelMap、Map类型的参数其实本质上都是 BindingAwareModelMap 类型的
public interface Model{} public class ModelMap extends LinkedHashMap<String, Object> {} public class ExtendedModelMap extends ModelMap implements Model {} public class BindingAwareModelMap extends ExtendedModelMap {}
7.3 SpringMVC中域对象
-
SpringMVC封装数据,默认使用request域对象
-
向session域中共享数据
-
方式一
/** * 测试响应数据【其他域对象】 * @return */ @GetMapping("/testScopeResponsedata") public String testScopeResponsedata(HttpSession session){ session.setAttribute("stuName","xinlai"); return "response_success"; }
-
方式二
@Controller @SessionAttributes(value = "stuName") //将request域中数据,同步到session域中 public class TestResponseData { /** * 使用ModelAndView处理响应数据 * @return */ @GetMapping("/testMvResponsedata") public ModelAndView testMvResponsedata(){ ModelAndView mv = new ModelAndView(); //设置逻辑视图名 mv.setViewName("response_success"); //设置数据【将数据共享到域中(request\session\servletContext)】 mv.addObject("stuName","zhouxu"); return mv; } }
-
向application域中共享数据
@RequestMapping("/testApplication") public String testApplication(HttpSession session){ ServletContext application = session.getServletContext(); application.setAttribute("testApplicationScope", "hello,application"); return "success"; }
-
第八章 SpringMVC处理请求响应乱码
8.1 源码解析CharacterEncodingFilter
public class CharacterEncodingFilter extends OncePerRequestFilter {
//需要设置字符集
@Nullable
private String encoding;
//true:处理请乱码
private boolean forceRequestEncoding = false;
//true:处理响应乱码
private boolean forceResponseEncoding = false;
public String getEncoding() {
return this.encoding;
}
public boolean isForceRequestEncoding() {
return this.forceRequestEncoding;
}
public void setForceResponseEncoding(boolean forceResponseEncoding) {
this.forceResponseEncoding = forceResponseEncoding;
}
public void setForceEncoding(boolean forceEncoding) {
this.forceRequestEncoding = forceEncoding;
this.forceResponseEncoding = forceEncoding;
}
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String encoding = getEncoding();
if (encoding != null) {
if (isForceRequestEncoding() || request.getCharacterEncoding() == null) {
request.setCharacterEncoding(encoding);
}
if (isForceResponseEncoding()) {
response.setCharacterEncoding(encoding);
}
}
filterChain.doFilter(request, response);
}
}
8.2 处理请求与响应乱码
-
SpringMVC底层默认处理响应乱码
-
SpringMVC处理请求乱码步骤
- 注册CharacterEncodingFilter
- 注册CharacterEncodingFilter必须是第一Filter位置
- 为CharacterEncodingFilter中属性encoding赋值
- 为CharacterEncodingFilter中属性forceRequestEncoding赋值
- 注册CharacterEncodingFilter
-
示例代码
<!-- 必须是第一过滤器位置--> <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> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
第九章 源码解析SpringMVC工作原理
9.1 Controller中方法的返回值问题
-
无论方法返回是ModelAndView还是String,最终SpringMVC底层,均会封装为ModelAndView对象
//DispatcherServlet的1061行代码 ModelAndView mv = null; mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
-
SpringMVC解析mv【ModelAndView】
//DispatcherServlet的1078行代码 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
-
ThymeleafView对象中344行代码【SpringMVC底层处理响应乱码】
//computedContentType="text/html;charset=UTF-8" response.setContentType(computedContentType);
-
WebEngineContext对象中783行代码【SpringMVC底层将数据默认共享到request域】
this.request.setAttribute(name, value);
9.2 视图及视图解析器源码
-
视图解析器将View从ModelAndView中解析出来
-
ThymeleafViewResolver的837行代码
//底层使用反射的方式,newInstance()创建视图对象 final AbstractThymeleafView viewInstance = BeanUtils.instantiateClass(getViewClass());
-
第十章 SpringMVC视图及视图解析器
10.1 视图解析器对象【ViewResolver】
-
概述:ViewResolver接口的实现类或子接口,称之为视图解析器
-
作用:将ModelAndView中的View对象解析出来
- 在springMVC中无论方法返回的是ModelAndView还是String,最终底层封装为ModelAndView
10.2 视图对象【View】
-
概述:
- View接口的实现类或子接口,称之为视图对象
- SpringMVC中所有视图对象【View】均实现的View接口
-
作用:视图渲染
- 将数据共享域中【request、session、application(ServletContext)】
- 跳转路径【转发或重定向】
第十一章 视图控制器&重定向&加载静态资源
11.1 视图控制器
-
语法:view-controller
- 如果希望不通过处理器的方法直接响应SpringMVC渲染的页面,则可以使用<mvc:view-controller>标签实现
-
步骤
- SpringMVC配置文件中添加<mvc:view-controller>标签:为指定URL映射html页面
- 添加<mvc:annotation-driven>
- 有20+种功能
- 配置了<mvc:view-controller>标签之后会导致其他请求路径都失效,添加<mvc:annotation-driven>解决
<!--添加视图控制器--> <mvc:view-controller path="/" view-name="index"></mvc:view-controller> <mvc:view-controller path="/toRestPage" view-name="rest_page"></mvc:view-controller> <!--解决控制器及静态资源加载后续【导致Controller无法正常使用】问题--> <mvc:annotation-driven></mvc:annotation-driven>
11.2 重定向&转发
-
语法:
- 重定向:return "redirect:/xxx.html";
- 转发:return "forward:/xxx.html";
@RequestMapping("/testRedirect") public String testRedirect(){ System.out.println("==>测试重定向"); return "redirect:/redirect_success.html"; //测试转发 //return "forward:/WEB-INF/pages/redirect_success.html"; }
-
注意:直接手动转发或重定向到html页面时会出现404,原因是请求静态资源html文件的请求被DispacherServlet拦截,但没有找到映射该请求的处理方法,但请求静态资源并不需要创建处理请求的方法。
11.3 加载静态资源
-
由DefaultServlet加载静态资源到服务器
- 静态资源:html、css、js等资源
- tomcat->conf->web.xml关键代码如下:
<servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>listings</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
-
发现问题
- DispatcherServlet与DefaultServlet的URL配置均为:/,导致DispatcherServlet中的配置将DefaultServlet配置的/覆盖了【DefaultServlet失效,无法加载静态资源】
-
解决方案
<!-- 解决静态资源加载问题--> <mvc:default-servlet-handler></mvc:default-servlet-handler> <!-- 添加上述标签,会导致Controller无法正常使用,需要添加mvc:annotation-driven解决 --> <mvc:annotation-driven></mvc:annotation-driven>
11.4 源码解析重定向原理
-
创建RedirectView对象【ThymeleafViewResolver的775行代码】
// Process redirects (HTTP redirects) if (viewName.startsWith(REDIRECT_URL_PREFIX)) { vrlogger.trace("[THYMELEAF] View \"{}\" is a redirect, and will not be handled directly by ThymeleafViewResolver.", viewName); final String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length(), viewName.length()); final RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible()); return (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, REDIRECT_URL_PREFIX); }
-
RedirectView视图渲染
-
RedirectView对象URL处理【330行代码】
-
执行重定向【RedirectView的627行代码】
-
第十二章 REST风格CRUD练习
12.1 搭建环境
-
导入相关jar包
<!--spring-webmvc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.1</version> </dependency> <!-- 导入thymeleaf与spring5的整合包 --> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> <version>3.0.12.RELEASE</version> </dependency> <!--servlet-api--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency>
-
编写配置文件
-
web.xml
- CharacterEncodingFilter
- HiddenHttpMethodFilter
- DispatcherServlet
<?xml version="1.0" encoding="UTF-8" ?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="4.0" 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"> <!-- 注册CharacterEncodingFilter--> <!-- 必须是第一过滤器的位置--> <!-- CharacterEncodingFilter处理请求乱码问题--> <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> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--注册HiddenHttpMethodFilter--> <!--Http请求方法过滤器--> <filter> <filter-name>HiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>HiddenHttpMethodFilter</filter-name> <!--所有请求均需要经过过滤器--> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 注册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:springmvc.xml</param-value> </init-param> <!-- 设置加载优先级--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
-
springmvc.xml
- 开启组件扫描
- 装配视图解析器
- 装配视图控制器
- 解决静态资源加载问题
- 装配annotation-driver
<?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"> <!-- 开启组件扫描--> <context:component-scan base-package="com.atguigu"></context:component-scan> <!-- 装配视图解析器--> <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver"> <!--配置字符集属性--> <property name="characterEncoding" value="UTF-8"/> <!--配置模板引擎属性--> <property name="templateEngine"> <!--配置内部bean--> <bean class="org.thymeleaf.spring5.SpringTemplateEngine"> <!--配置模块的解析器属性--> <property name="templateResolver"> <!--配置内部bean--> <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"> <property name="characterEncoding" value="UTF-8"/> <!--配置前缀--> <property name="prefix" value="/WEB-INF/pages/"/> <!--配置后缀--> <property name="suffix" value=".html"/> </bean> </property> </bean> </property> </bean> <!--添加视图控制器--> <!-- 装配首页控制器--> <mvc:view-controller path="/" view-name="index"></mvc:view-controller> <!-- 解决静态资源加载问题--> <mvc:default-servlet-handler></mvc:default-servlet-handler> <!-- 解决控制器及静态资源加载后续【导致Controller无法正常使用】问题--> <mvc:annotation-driven></mvc:annotation-driven> </beans>
-
-
pojo层
Employee实体类
package com.atguigu.pojo; public class Employee { private Integer id; private String lastName; private String email; private Integer gender; private Department department; public Employee(Integer id, String lastName, String email, Integer gender) { this.id = id; this.lastName = lastName; this.email = email; this.gender = gender; } @Override public String toString() { return "Employee{" + "id=" + id + ", lastName='" + lastName + '\'' + ", email='" + email + '\'' + ", gender=" + gender + ", department=" + department + '}'; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public Integer getGender() { return gender; } public void setGender(Integer gender) { this.gender = gender; } public Department getDepartment() { return department; } public void setDepartment(Department department) { this.department = department; } public Employee() { } public Employee(Integer id, String lastName, String email, Integer gender, Department department) { this.id = id; this.lastName = lastName; this.email = email; this.gender = gender; this.department = department; } }
Department实体类
package com.atguigu.pojo; public class Department { private Integer id; private String departmentName; @Override public String toString() { return "Department{" + "id=" + id + ", departmentName='" + departmentName + '\'' + '}'; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getDepartmentName() { return departmentName; } public void setDepartmentName(String departmentName) { this.departmentName = departmentName; } public Department() { } public Department(Integer id, String departmentName) { this.id = id; this.departmentName = departmentName; } }
-
dao层
EmployeeDao
package com.atguigu.dao; import com.atguigu.pojo.Department; import com.atguigu.pojo.Employee; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import java.util.Collection; import java.util.HashMap; import java.util.Map; @Repository public class EmployeeDao { private static Map<Integer, Employee> employees = null; @Autowired private DepartmentDao departmentDao; static{ employees = new HashMap<Integer, Employee>(); employees.put(1001, new Employee(1001, "E-AA", "aa@163.com", 1,new Department(101,"D-AA"))); employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1,new Department(102,"D-BB"))); employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0,new Department(103,"D-CC"))); employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0,new Department(104,"D-DD"))); employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 1,new Department(105,"D-EE"))); } private static Integer initId = 1006; /** * 添加或修改员工信息 * @param employee */ public void save(Employee employee){ if(employee.getId() == null){ employee.setId(initId++); } employee.setDepartment(departmentDao.getDepartment(employee.getDepartment().getId())); employees.put(employee.getId(), employee); } /** * 获取所有员工信息 * @return */ public Collection<Employee> getAll(){ return employees.values(); } /** * 通过员工id获取信息 * @param id * @return */ public Employee get(Integer id){ return employees.get(id); } /** * 根据员工id删除信息 * @param id */ public void delete(Integer id){ employees.remove(id); } }
DepartmentDao
package com.atguigu.dao; import com.atguigu.pojo.Department; import org.springframework.stereotype.Repository; import java.util.Collection; import java.util.HashMap; import java.util.Map; @Repository public class DepartmentDao { private static Map<Integer, Department> departments = null; static { departments = new HashMap<>(); departments.put(101, new Department(101, "D-AA")); departments.put(102, new Department(102, "D-BB")); departments.put(103, new Department(103, "D-CC")); departments.put(104, new Department(104, "D-DD")); departments.put(105, new Department(105, "D-EE")); } /** * 获取所有部门信息 * @return */ public Collection<Department> getDepartments() { return departments.values(); } /** * 通过部门id获取部门信息 * @param id * @return */ public Department getDepartment(Integer id) { return departments.get(id); } }
12.2 实现功能思路
EmployeeController 控制器文件
package com.atguigu.controller; import com.atguigu.dao.DepartmentDao; import com.atguigu.dao.EmployeeDao; import com.atguigu.pojo.Department; import com.atguigu.pojo.Employee; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import java.util.Collection; import java.util.Map; @Controller public class EmployeeController { @Autowired @Qualifier("employeeDao") private EmployeeDao employeeDao; @Autowired @Qualifier("departmentDao") private DepartmentDao departmentDao; /** * 获取所有员工信息 * @return */ // @RequestMapping(value = "/emps",method = RequestMethod.GET) @GetMapping("/emps") public String getAll(Map<String,Object> map){ //获取所有员工信息 Collection<Employee> emps = employeeDao.getAll(); //将数据共享到域中 map.put("emps", emps); return "emp_list"; } /** *跳转添加页面 */ @GetMapping("/toSaveEmp") public String toSaveEmp(Map<String,Object> map){ //查询所有部门信息 Collection<Department> departments = departmentDao.getDepartments(); map.put("depts", departments); return "emp_save"; } /** * 添加员工信息 * @param employee * @return */ @PostMapping("/emps") public String saveEmp(Employee employee) { System.out.println("employee = " + employee); //添加员工 employeeDao.save(employee); return "redirect:/emps"; } /** * 实现删除员工信息 */ @DeleteMapping("/emps/{empId}") public String deleteEmp(@PathVariable("empId") Integer empId) { //删除员工信息 employeeDao.delete(empId); return "redirect:/emps"; } /** * 跳转到修改页面 * @param empId * @param map * @return */ @GetMapping("/toUpdateEmp/{empId}") public String toUpdateEmp(@PathVariable("empId") Integer empId,Map<String,Object> map) { //通过empId获取员工信息 Employee employee = employeeDao.get(empId); map.put("emp", employee); //获取所有部门信息 Collection<Department> departments = departmentDao.getDepartments(); map.put("depts", departments); return "emp_update"; } /** * 实现修改员工信息 */ @PutMapping("/emps") public String updateEmp(Employee employee){ employeeDao.save(employee); return "redirect:/emps"; } }
实现访问首页
<!-- 装配首页控制器-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
<mvc:annotation-driven></mvc:annotation-driven>
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h2>首页</h2>
<a th:href="@{/emps}">查询所有员工信息</a><br/>
</body>
实现查询功能
/**
* 获取所有员工信息
* @return
*/
//@RequestMapping(value = "/emps",method = RequestMethod.GET)
@GetMapping("/emps")
public String getAll(Map<String,Object> map){
//获取所有员工信息
Collection<Employee> emps = employeeDao.getAll();
//将数据共享到域中
map.put("emps", emps);
return "emp_list";
}
emp_list.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>员工列表</title>
<!-- 使用绝对路径-->
<base th:href="@{/}">
<script src="static/js/vue.js"></script>
</head>
<body>
<div align="center" id="app">
<h2>员工信息列表</h2>
<table border="1" width="600px" height="300px" >
<tr>
<th>ID</th>
<th>LastName</th>
<th>Email</th>
<th>Gender</th>
<th>DeptName</th>
<th>Opr</th>
</tr>
<tr th:each="emp:${emps}" align="center">
<td th:text="${emp.id}"></td>
<td th:text="${emp.lastName}"></td>
<td th:text="${emp.email}"></td>
<td th:text="${emp.gender==1?'男':'女'}"></td>
<td th:text="${emp.department.departmentName}"></td>
<td>
<a th:href="@{/toUpdateEmp/}+${emp.id}">修改</a>
<!--删除功能方式一:-->
<!-- <form th:action="@{/emps/}+${emp.id}" method="post">
<input type="hidden" name="_method" value="DELETE">
<input type="submit" value="删除">
</form>-->
<!--删除功能方式二:-->
<a th:href="@{/emps/}+${emp.id}" @click="deleteEmp">删除</a>
<form id="delForm" th:action="@{/emps/}+${emp.id}" method="post">
<input type="hidden" name="_method" value="DELETE">
</form>
</td>
</tr>
</table>
<a th:href="@{/toSaveEmp}">添加员工</a><br/>
</div>
<script type="text/javascript">
//删除功能方式二:通过js事件实现
new Vue({
el:"#app",
data:{},
methods:{
deleteEmp(){
//获取响应表单
var formEle = document.getElementById("delForm");
formEle.submit();
//取消超链接默认行为
event.preventDefault()
}
}
});
</script>
</body>
</html>
实现添加功能
- 跳转添加页面【查询所有部门信息】
- 实现添加功能
/**
*跳转添加页面
*/
@GetMapping("/toSaveEmp")
public String toSaveEmp(Map<String,Object> map){
//查询所有部门信息
Collection<Department> departments = departmentDao.getDepartments();
map.put("depts", departments);
return "emp_save";
}
/**
* 添加员工信息
* @param employee
* @return
*/
@PostMapping("/emps")
public String saveEmp(Employee employee) {
System.out.println("employee = " + employee);
//添加员工
employeeDao.save(employee);
return "redirect:/emps";
}
emp_save.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>添加员工信息</title>
</head>
<body>
<div align="left">
<h2>添加员工信息</h2>
<form th:action="@{/emps}" method="post">
员工姓名:<input type="text" name="lastName"><br/>
员工邮箱:<input type="text" name="email"><br/>
员工性别:<input type="radio" name="gemder" value="1" checked>男
<input type="radio" name="gemder" value="0">女<br/>
员工部门:
<select name="department.id">
<option th:each="dept:${depts}" th:value="${dept.id}" th:text="${dept.departmentName}"></option>
</select>
<br/>
<input type="submit" value="添加员工">
</form>
</div>
</body>
</html>
实现删除功能
-
方式一:直接使用表单实现DELETE提交方式
-
方式二:使用超链接【a】实现DELETE提交方式
-
使用Vue实现单击超链接,后提交对应表单
-
取消超链接默认行为
-
示例代码
<div align="center" id="app"> <a href="#" @click="deleteEmp">删除</a> <form id="delForm" th:action="@{/emps/}+${emp.id}" method="post"> <input type="hidden" name="_method" value="DELETE"> </form> </div> <script type="text/javascript" src="static/js/vue_v2.6.14.js"></script> <script type="text/javascript"> new Vue({ el:"#app", data:{}, methods:{ deleteEmp(){ alert("hehe"); //获取响应表单 var formEle = document.getElementById("delForm"); formEle.submit(); //取消超链接默认行为 event.preventDefault(); } } }); </script>
/** * 实现删除员工信息 */ @DeleteMapping("/emps/{empId}") public String deleteEmp(@PathVariable("empId") Integer empId) { //删除员工信息 employeeDao.delete(empId); return "redirect:/emps"; }
-
实现修改功能
/**
* 跳转到修改页面
* @param empId
* @param map
* @return
*/
@GetMapping("/toUpdateEmp/{empId}")
public String toUpdateEmp(@PathVariable("empId") Integer empId,Map<String,Object> map) {
//通过empId获取员工信息
Employee employee = employeeDao.get(empId);
map.put("emp", employee);
//获取所有部门信息
Collection<Department> departments = departmentDao.getDepartments();
map.put("depts", departments);
return "emp_update";
}
/**
* 实现修改员工信息
*/
@PutMapping("/emps")
public String updateEmp(Employee employee){
employeeDao.save(employee);
return "redirect:/emps";
}
emp_update.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>修改员工信息</title>
</head>
<body>
<h2>修改员工信息</h2>
<form th:action="@{/emps}" method="post">
<input type="hidden" name="_method" value="PUT">
<!-- 没有传对象id时,会执行添加操作-->
<!-- name属性值为id是因为与对象成员属性名称一致时使用pojo自动入参-->
<input type="hidden" name="id" th:value="${empId}">
员工姓名:<input type="text" name="lastName" th:value="${emp.lastName}"><br/>
员工邮箱:<input type="text" name="email" th:value="${emp.email}"><br/>
员工性别:<input type="radio" name="gender" value="1" th:checked="${emp.gender==1}">男
<input type="radio" name="gender" value="0" th:checked="${emp.gender==0}">女<br/>
员工部门:
<select name="department.id">
<option th:each="dept:${depts}"
th:value="${dept.id}"
th:text="${dept.departmentName}"
th:selected="${dept.id == emp.department.id}"></option>
</select>
<br/>
<input type="submit" value="修改员工">
</form>
</body>
</html>
第十三章 SpringMVC消息转换器
13.1 消息转换器概述
- HttpMessageConverter<T>:消息转换器主要作用
-
将java对象与请求报文及响应报文的相互转换
-
使用HttpMessageConverter<T>将请求信息转化并绑定到处理方法的入参中或将响应结果转为对应类型的响应信息,HttpMessageConverter 提供了两个注解和两个类型
- @RequestBody
- @ResponseBody
- HttpEntity
- ResponseEntity
-
13.2 使用消息转换器处理请求报文
-
使用 @RequestBody 获取请求体
- 使用@RequestBody 获取请求体必须以POST方式提交,不能使用GET方式【GET提交方式没有请求体】
表单页面
<h3>HttpMessageConverter消息转换器</h3> <form th:action="@{/testRequestBody}" method="post"> 用户名:<input type="text" name="username"><br> 密码:<input type="password" name="password"><br> <input type="submit" value="测试RequestBody"> </form>
处理器方法
@PostMapping("/testRequestBody") public String testRequestBody(@RequestBody String requestBody) { System.out.println("请求体内容:" + requestBody); return "success"; }
-
使用 HttpEntity 获取请求体及请求头
表单页面
<h3>HttpEntity</h3> <form th:action="@{/testHttpEntity}" method="post"> 用户名:<input type="text" name="username"><br> 密码:<input type="password" name="password"><br> <input type="submit" value="测试HttpEntity"> </form>
处理器方法
//测试HttpEntity方法 @PostMapping("/testHttpEntity") public String testHttpEntity(HttpEntity<String> httpEntity){ //获取请求头的信息 HttpHeaders headers = httpEntity.getHeaders(); System.out.println("headers = " + headers); //获取请求体内容 String body = httpEntity.getBody(); System.out.println("body = " + body); return "success"; }
13.3 使用消息转换器处理响应报文
-
@ResponseBody
- 作用:用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器
- 类似 response.getWriter().write();
- 位置
- 书写在class类上面【当前类所有方法都返回的是文本,不会跳转页面
- 书写在方法上面
//测试ResponseBody方法 @RequestMapping(value = "/testResponseBody",method = RequestMethod.POST) @ResponseBody public String testResponseBody() { System.out.println("测试ResponseBody方法!!!"); //返回文本,不跳转到success.html页面 //没有@ResponseBody时则会被当作一个视图名称被解析,跳转到success.html页面 return "success"; }
- 作用:用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器
-
ResponseEntity
- 用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文
-
@RestController 注解
- @RestController注解是springMVC提供的一个复合注解,标识在控制器的类上,就相当于为类添加了@Controller注解,并且为其中的每个方法添加了@ResponseBody注解
@RestController public class HttpMessageConverterController {}
13.4 使用消息转换器处理Json格式数据
-
导入 jackson 的jar包
<!--jackson-databind--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.3</version> </dependency>
-
装配MappingJackson2HttpMessageConverter消息转换器
- 必须配置mvc:annotation-driver标签才能装配Jackson的转换器
<!--解决控制器及静态资源加载后续【导致Controller无法正常使用】问题--> <mvc:annotation-driven></mvc:annotation-driven>
-
在需要转换 json 数据的方法上,添加 @ResonseBody
- 需要被转换的数据作为方法的返回值,就会自动转换为Json格式的字符串
//测试Json格式 @RequestMapping("/testJson") @ResponseBody public Employee testJson() { System.out.println("==>处理json格式数据"); //JavaWeb中处理Json数据格式 //Gson gson = new Gson(); //String jsonStr = gson.toJson(employee); // response.getWriter.write(jsonStr); //将Employee对象转换为json格式响应 Employee employee = new Employee(1001, "zs", "zs@163.com", 1); return employee; }
页面效果
{"id":1001,"lastName":"zs","email":"zs@163.com","gender":1}
-
底层实现原理【MappingJackson2HttpMessageConverter】
-
添加jar包
-
装配mvc:annotation-driver
-
添加支持之前
-
添加支持之后
-
13.5 SpringMVC处理Ajax交互
-
前端请求
<div id="app"> <a th:href="@{/testAjax}" @click="testAjax">testAjax</a><br> </div>
-
通过Vue和axion处理点击事件
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script> <script type="text/javascript" th:src="@{/static/js/axios.min.js}"></script> <script type="text/javascript"> var vue = new Vue({ el:"#app", methods:{ testAjax:function (event) { axios({ method:"post", url:event.target.href, params:{ username:"admin", password:"123456" } }).then(function (response) { alert(response.data); }); //取消超链接默认跳转页面 event.preventDefault(); } } }); </script>
-
后端控制器方法
@RequestMapping("/testAjax") @ResponseBody public String testAjax(String username, String password){ System.out.println("username:"+username+",password:"+password); return "hello,ajax"; }
第十四章 SpringMVC文件上传与下载
14.1 文件下载
-
实现文件下载步骤
- 准备文件下载相关资源
- 将ResponseEntity<T>对象,作为方法返回值
- 为ResponseEntity<T>对象设置三个参数
前端页面
<div align="center"> <h2>下载资源</h2> <a th:href="@{/fileDownLoadController(filename='1.jpeg')}">1.jpeg</a> <a th:href="@{/fileDownLoadController(filename='2.jpeg')}">2.jpeg</a> <a th:href="@{/fileDownLoadController(filename='3.jpeg')}">3.jpeg</a> <a th:href="@{/fileDownLoadController(filename='4.jpeg')}">4.jpeg</a> <a th:href="@{/fileDownLoadController(filename='5.jpeg')}">5.jpeg</a> <a th:href="@{/fileDownLoadController(filename='test.txt')}">test.txt</a> </div>
控制器
@RequestMapping("/fileDownLoadController") public ResponseEntity<byte[]> fileDownLoadController(HttpServletRequest request,String filename) { System.out.println("==>fileDownLoadController"); ResponseEntity<byte[]> responseEntity = null; try { //获取文件名称 //获取文件真实路径【(request|session)->ServletContext】 String realPath = request.getServletContext().getRealPath("/WEB-INF/download/" + filename); //创建输入流 FileInputStream fileInputStream = new FileInputStream(realPath); //文件下载byte[],创建字节数组 byte[] bytes = new byte[fileInputStream.available()]; //将流读到字节数组中 fileInputStream.read(bytes); //设置响应头 HttpHeaders headers = new HttpHeaders(); //设置要下载的方式以及下载文件的名称【及文件格式为附件格式,通知服务器下载当前资源,而不是打开资源】 headers.add("Content-Disposition","attachment;filename="+filename); //处理中文文件名问题 headers.setContentDispositionFormData("attachment",new String(filename.getBytes("utf-8"),"ISO-8859-1")); //状态码 responseEntity = new ResponseEntity<>(bytes,headers, HttpStatus.OK); } catch (Exception e) { e.printStackTrace(); } return responseEntity; }
14.2 文件上传
-
实现文件上传思路
-
准备文件上传页面
- 表单的提交方式必须为POST
- 设置表单 enctype 属性值为multipart/form-data
- 表单中包含文件域【type=file】
-
导入jar包
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency>
-
装配解析器:CommonsMultipartResolver
- id必须是:multipartResolver
- 设置上传文件字符集:defaultEncoding=UTF-8
<!--在SpringMVC的配置文件中添加以下配置--> <!--必须通过文件解析器的解析才能将文件转换为MultipartFile对象--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!--设置上传文件字符集--> <property name="defaultEncoding" value="UTF-8"/> </bean>
-
实现步骤
- 将type=file【文件域】直接入参:MultipartFile类型即可
- 获取文件名称
- 获取上传文件真实路径
//实现文件上传 @PostMapping("/fileUploadController") public String fileUploadController(MultipartFile uploadFile, HttpSession session) { try { //获取文件名称 String filename = uploadFile.getOriginalFilename(); //获取上传路径 String realPath = session.getServletContext().getRealPath("WEB-INF/upload/"); //判断上传路径是否存在【若不存在,则创建路径】 File filePath = new File(realPath); if (!filePath.exists()) { filePath.mkdir(); } //实现文件上传【File.separrtor:路径分隔符】 File uFile = new File(filePath + File.separator + filename); uploadFile.transferTo(uFile); } catch (IOException e) { e.printStackTrace(); } return "success"; }
-
优化文件上传
-
允许同名文件上传
-
使用UUID解决文件名重复问题
- UUID是一个32位16进制随机数【特点:唯一性】
//创建UUID String uuid = UUID.randomUUID().toString().replace("-", ""); //使用uuid拼接生成文件名 File uFile = new File(filePath + File.separator + uuid + filename);
-
使用时间戳解决文件名重复问题
- System.currentTimeMillis()
//使用时间戳为文件命名 File uFile2 = new File(filePath + File.separator + System.currentTimeMillis() + filename);
-
-
设置上传文件大小上限
- 在装配CommonsMultipartResolver时,设置上传文件的上限
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="UTF-8"/> <!--有多个文件上传时,设置上传总文件的上传大小限制为100KB,单位为字节--> <property name="maxUploadSize" value="102400"/> <!--有多个文件上传时,设置单个文件上传大小限制为100KB,单位为字节--> <property name="maxUploadSizePerFile" value="102400"/> </bean>
-
-
第十五章 SpringMVC中的拦截器【Interceptor】
15.1 拦截器与过滤器的区别
-
过滤器【Filter】属于web服务器组件
- 过滤器主要作用:过滤Servlet请求
- 执行时机:两处执行时机【Servlet前、Servlet后】
-
拦截器【Interceptor】属于框架【SpringMVC】
- 拦截器主要作用:拦截Controller请求
- 执行时机:三处
- 执行DispatcherServlet之后,Controller之前
- 执行Controller之后,DispatcherServlet之前
- 执行DispatcherServlet之后【渲染视图之后】
15.2 拦截器概述
- SpringMVC可以使用拦截器实现拦截Controller请求,用户可以自定义拦截器来实现特定的功能
- 实现拦截器的两种方式
- 实现接口:HandlerInterceptor
- 继承适配器类:HandlerInterceptorAdapter
- 拦截器中三个方法
- preHandle():控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法
- postHandle():这个方法在业务处理器处理请求之后,渲染视图之前调用。在此方法中可以对ModelAndView中的模型和视图进行处理。
- afterComplation():这个方法在DispatcherServlet完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作
15.3 实现拦截器的步骤
-
实现接口:HandlerInterceptor
-
重写三个方法
package com.atguigu.interceptor; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component public class MyInterceptor implements HandlerInterceptor { /** * 执行1【Controller执行之前】 * @param request * @param response * @param handler * @return * @throws Exception */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("==>MyInterceptor->preHandle!"); return true; } /** * 执行2【Controller执行之后】 * @param request * @param response * @param handler * @param modelAndView * @throws Exception */ public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { System.out.println("==>MyInterceptor->postHandle!"); } /** * 最后执行【渲染视图】 * @param request * @param response * @param handler * @param ex * @throws Exception */ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { System.out.println("==>MyInterceptor->afterCompletion!"); } }
-
在springmvc.xml配置文件中,装配拦截器【全局配置】
全局配置拦截器:所有Controller请求都会拦截
<!--装配拦截器--> <mvc:interceptors> <!--全局配置--> <!--装配方式一:直接写全类名--> <!--<bean class="com.atguigu.interceptor.MyInterceptor"></bean>--> <!--装配方式二:使用 @Component 注解和ref来指定--> <ref bean="myInterceptor"></ref> </mvc:interceptors>
局部配置拦截器:只拦截指定的Controller请求
<!--装配拦截器--> <mvc:interceptors> <!--局部配置--> <mvc:interceptor> <mvc:mapping path="/testJson"/> <ref bean="myInterceptor"></ref> </mvc:interceptor> </mvc:interceptors>
15.4 拦截器工作原理
- 单个拦截器工作原理
- 浏览器向服务器发送请求
- 执行拦截器第一个方法preHandle()
- 执行controller中的方法,处理请求做出响应
- 执行拦截器第二个方法postHandle()
- 执行DispatcherServlet中的方法渲染视图
- 执行拦截器第三个方法afterCompletion()
- 响应
- 多个拦截器工作原理
- 浏览器向服务器发送请求
- 执行拦截器1第一个方法preHandle()
- 执行拦截器2第一个方法preHandle()
- 执行controller中的方法,处理请求做出响应
- 执行拦截器2第二个方法postHandle()
- 执行拦截器1第二个方法postHandle()
- 执行DispatcherServlet中的方法渲染视图
- 执行拦截器2第三个方法afterCompletion()
- 执行拦截器1第三个方法afterCompletion()
- 响应
拦截器的顺序由配置的位置顺序决定的
15.5 拦截器preHandle()方法返回值
- 当第一个拦截器preHandle()方法返回false时,执行当前方法后,程序终止。
- 当不是第一个拦截器preHandle()方法返回false时
- 执行当前拦截器及之前拦截器的preHandle()方法
- 执行之前拦截器的afterCompletion()方法
第十六章 SpringMVC异常处理器
16.1 为什么需要处理异常
- 如程序中出现异常未处理,则会导致程序运行终止【宕机】
- JavaSE阶段异常处理机制
- try-catch-finally
- throw或throws
16.2 SpringMVC中异常处理器
-
SpringMVC通过 HandlerExceptionResolver 处理程序的异常,包括Handler映射、数据绑定以及目标方法执行时发生的异常
-
两个异常处理器实现类
- DefaultHandlerExceptionResolver:默认异常处理器,默认开启,可支持10+多种异常处理
- SimpleMappingExceptionResolver:映射自定义异常处理器,将指定的异常映射到指定页面
-
装配异常处理器【SimpleMappingExceptionResolver】
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <!-- properties的键表示处理器方法执行过程中出现的异常 properties的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面 --> <prop key="java.lang.ArithmeticException">error</prop> </props> </property> <!-- exceptionAttribute属性设置一个属性名,将出现的异常信息在请求域中进行共享 --> <property name="exceptionAttribute" value="ex"></property> </bean>
-
总结
-
出现异常,不会执行postHandle()方法
-
出现异常,也会返回ModelAndView
-
第十七章 SpringMVC工作原理
17.1 扩展三个对象
- HandlerMapping
- 概述:请求处理器映射器对象
- 作用:通过HandlerMapping可以获取HandlerExecutionChain对象
- HandlerMapping 定义一个映射关系,所有请求与请求处理器的映射关系
- HandlerExecutionChain
- 概述:请求处理器执行链对象
- 作用:通过HandlerExecutionChain可以获取HandlerAdapter对象
- 请求处理器执行链对象,由当前请求处理器【Controller】和对应拦截器【Interceptor】组成
- HandlerAdapter
- 概述:请求处理器映射器对象
- 作用:通过HandlerAdapter的对象ha.handle()调用请求处理器中相应
17.2 SpringMVC 工作原理1【URL不存在】
- 请求【浏览器向服务器发送请求,携带URL(/testSpringMVCWorking)】
- 通过DispatcherServlet加载SpringMVC容器对象,从而加载Controller【请求处理器】
- 判断URL是否存在
- 不存在:判断是否配置<mvc:default-servlet-handler>
- 配置:出现404现象,同时提示URL不可用
- 为配置:出现404现象,但不会提示
- 存在
- 不存在:判断是否配置<mvc:default-servlet-handler>
17.3 SpringMVC 工作原理2 【URL存在】
-
请求【浏览器向服务器发送请求,携带URL(/testSpringMVCWorking)】
-
通过DispatcherServlet加载SpringMVC容器对象,从而加载Controller【请求处理器】
- 加载三个对象【HandlerMapping、HandlerExecutionChain、HandlerAdapter】
-
判断URL是否存在【存在】
-
执行Interceptor【拦截器】第一个方法【preHandle()】
//DispatcherServlet的1056行代码 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; }
-
执行Controller【请求处理器】中的相应方法【处理请求,做出响应】
// Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
-
判断Controller中是否存在异常
- 存在异常
- 通过HandlerExceptionResolver 异常处理器处理异常,并返回ModelAndView
- 不存在异常
- Controller返回ModelAndView
- 触发拦截器第二个方法【postHandle()】
- 存在异常
-
通过ViewResolver【视图解析器对象】将View【视图对象】从ModelAndView中解析出来
//DispatcherServlet的1435行代码 if (this.viewResolvers != null) { for (ViewResolver viewResolver : this.viewResolvers) { View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } }
-
View对象开始渲染视图
- 将数据共享
- 路径跳转
-
执行拦截器第三个方法【afterCompletion()】
//DispatcherServlet的1157行代码 if (mappedHandler != null) { // Exception (if any) is already handled.. mappedHandler.triggerAfterCompletion(request, response, null); }
-
响应
17.4 总结
- 请求,通过DispatcherServlet加载SpringMVC容器对象,并加载三个对象,从而加载Controller【请求处理器】
- 判断URL是否存在【存在】
- 存在
- 执行Interceptor【拦截器】第一个方法【preHandle()】
- 执行Controller【请求处理器】中的相应方法【处理请求,做出响应】
- 判断Controller中是否存在异常
- 存在异常
- 通过HandlerExceptionResolver 异常处理器处理异常,并返回ModelAndView
- 不存在异常
- Controller返回ModelAndView
- 触发拦截器第二个方法【postHandle()】
- 存在异常
- 判断Controller中是否存在异常
- 通过ViewResolver【视图解析器对象】将View【视图对象】从ModelAndView中解析出来
- View对象开始渲染视图
- 执行拦截器第三个方法【afterCompletion()】
- 不存在
- 不存在:判断是否配置<mvc:default-servlet-handler>
- 配置:出现404现象,同时提示URL不可用
- 为配置:出现404现象,但不会提示
- 不存在:判断是否配置<mvc:default-servlet-handler>
- 存在
- 响应
- 注意:Controller中存在异常,不会执行postHandle()方法。
第十八章 SSM【Spring+SpringMVC+Mybatis】整合
在线Maven仓库地址: https://mvnrepository.com/
18.1 SSM整合思路
- Spring + SpringMVC
- 容器对象的管理问题
- SpringMVC容器对象,由DispatcherServlet管理
- Spring容器对象,由ContextLoaderListener管理
- 解决组件扫描的冲突问题
- SpringMVC只扫描Controller层
- Spring扫描排除Controller层
- 容器对象的管理问题
- Spring + Mybatis
- 关于数据源、事务管理的代码冲突问题
- 统一交给Spring管理
- Spring管理Mybatis核心对象
- SqlSessionFactory
- Mapper代理对象
- 关于数据源、事务管理的代码冲突问题
18.2 SSM整合步骤
-
Spring + SpringMVC
-
导入jar包
<!--spring-webmvc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.1</version> </dependency> <!-- 导入thymeleaf与spring5的整合包 --> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> <version>3.0.12.RELEASE</version> </dependency> <!--servlet-api--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency>
-
配置文件
-
web.xml
- 注册CharacterEncodingFilter,解决请求乱码问题
- 注册HiddenHttpMethodFilter,支持PUT&DELETE提价【REST风格】
- 注册DispatcherServlet【前端控制器】,管理SpringMVC容器对象
- 注册一个上下文参数【contextConfigLoaction】,设置spring.xml配置文件路径
- 注册ContextLoaderListener,管理Spring容器对象
<?xml version="1.0" encoding="UTF-8" ?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="4.0" 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"> <!-- - 注册一个上下文参数【contextConfigLoaction】,设置spring.xml配置文件路径--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring.xml</param-value> </context-param> <!-- - 注册ContextLoaderListener,管理Spring容器对象--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- - 注册CharacterEncodingFilter,解决请求乱码问题--> <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> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- - 注册HiddenHttpMethodFilter,支持PUT&DELETE提价【REST风格】--> <filter> <filter-name>HiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>HiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- - 注册DispatcherServlet【前端控制器】,管理SpringMVC容器对象--> <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:springMVC.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
-
springMVC.xml
- 开启组件扫描【只扫描Controller层】
- 装配视图解析器【ThymeleafViewResolver】
- 装配视图控制器【view-controller】
- 装配default-servlet-handler,解决静态资源加载问题
- 装配annotation-driven,解决后续问题
- 解决view-controller【导致Controller无法正常使用】问题
- 解决default-servlet-handler【导致静态资源无法加载】问题
- 解决jackson装配消息转换器问题【等23+种】
<?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"> <!-- - 开启组件扫描【只扫描Controller层】--> <context:component-scan base-package="com.atguigu" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- - 装配视图解析器【ThymeleafViewResolver】--> <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver"> <property name="characterEncoding" value="UTF-8"></property> <property name="templateEngine"> <bean class="org.thymeleaf.spring5.SpringTemplateEngine"> <property name="templateResolver"> <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"> <property name="characterEncoding" value="UTF-8"/> <property name="prefix" value="/WEB-INF/pages/"/> <property name="suffix" value=".html"/> </bean> </property> </bean> </property> </bean> <!-- - 装配视图控制器【view-controller】--> <mvc:view-controller path="/" view-name="index"></mvc:view-controller> <!-- - 装配default-servlet-handler,解决静态资源加载问题--> <mvc:default-servlet-handler></mvc:default-servlet-handler> <!-- - 装配annotation-driven,解决后续问题--> <mvc:annotation-driven></mvc:annotation-driven> </beans>
-
spring.xml
- 开启组件扫描【排除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: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="com.atguigu"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> </beans>
-
-
-
Spring + Mybatis
-
导入jar包
-
Spring的jar包
<!--spring-jdbc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.1</version> </dependency> <!--spring-orm--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.3.1</version> </dependency> <!--导入AspectJ框架的jar包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.1</version> </dependency>
-
Mybatis的jar包
<!--导入druid的jar包--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <!--导入mysql的jar包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.37</version> <!--mysql8驱动的jar包--> <!--<version>8.0.26</version>--> </dependency> <!--导入MyBatis的jar包--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <!--导入PageHelper分页插件的jar包--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.8</version> </dependency>
-
Spring与Mybatis整合jar包
<!--Mybatis-spring--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6</version> </dependency>
-
配置文件
-
spring.xml
- 开启组件扫描【排除Controller层】
- 加载外部属性文件
- 装配数据源【DruidDataSource】
- 装配事务管理器【DataSourceTransactionManager】
- 开启声明式事务管理注解支持
- 装配SqlSessionFactoryBean,管理SQLSessionFactory
- 装配MapperScannerConfigurer,管理Mapper代理对象
<?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:tx="http://www.springframework.org/schema/tx" xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring" 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/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd"> <!-- 开启组件扫描【排除Controller层】--> <context:component-scan base-package="com.atguigu"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- - 加载外部属性文件--> <context:property-placeholder location="classpath:db.properties"></context:property-placeholder> <!-- - 装配数据源【DruidDataSource】--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${db.driverClassName}"></property> <property name="url" value="${db.url}"></property> <property name="username" value="${db.username}"></property> <property name="password" value="${db.password}"></property> </bean> <!-- - 装配事务管理器【DataSourceTransactionManager】--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- - 开启声明式事务管理注解支持--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven> <!-- - 装配SqlSessionFactoryBean,管理SQLSessionFactory--> <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 设置数据源--> <property name="dataSource" ref="dataSource"/> <!-- 设置mybatis-config.xml核心配置文件路径--> <property name="configLocation" value="classpath:mybatis-config.xml"/> <!-- 设置类型别名--> <property name="typeAliasesPackage" value="com.atguigu.pojo"/> <!-- 设置映射文件路径--> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <!-- - 装配MapperScannerConfigurer,管理Mapper代理对象--> <mybatis-spring:scan base-package="com.atguigu.mapper"></mybatis-spring:scan> </beans>
-
mybatis-config.xml【核心配置文件】
- 设置别名
- 开启驼峰式命名映射
- 设置PageHelper分页插件
- ......
<?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> <settings> <!-- 开启自动驼峰式命名映射--> <setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 开启延迟加载--> <setting name="lazyLoadingEnabled" value="true"/> <!-- 设置加载的数据是按需加载--> <setting name="aggressiveLazyLoading" value="false"/> </settings> <!-- 添加分页插件--> <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin> </plugins> </configuration>
-
xxxMapper.xml【映射文件】
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.atguigu.mapper.EmployeeMapper"> <select id="selectAllEmps" resultType="employee"> SELECT employee_id, first_name, last_name, email, phone_number, job_id, salary FROM employees </select> </mapper>
-
-
-
18.3 添加分页插件
-
添加PageUtils工具类
package com.atguigu.utils; import javax.servlet.http.HttpServletRequest; import com.atguigu.pojo.Employee; import com.github.pagehelper.PageInfo; /** * @author Chunsheng Zhang * 组件化分页工具类 */ public class PageUtils { /** * 将分页功能所有信息拼接到一个字符串中,作为返回结果,直接传递到jsp页面使用,拼接效果如下: * 首页 上一页 1 2 3 4 5 下一页 尾页 */ public static String getPageInfo(PageInfo<?> pageInfo , HttpServletRequest request ) { //String basePath = "/当前项目名/"; String basePath = request.getContextPath() + "/"; //定义结果字符串StringBuilder StringBuilder builder = new StringBuilder(); //拼接首页 builder.append("<a href='"+basePath+"emps/1'>首页</a>"); builder.append(" "); //拼接上一页 if(pageInfo.isHasPreviousPage()) { builder.append("<a href='"+basePath+"emps/"+(pageInfo.getPageNum()-1)+"'>上一页</a>"); builder.append(" "); }else { builder.append("上一页"); builder.append(" "); } //拼接页码超链接 1 2 3 4 5 int [] nums = pageInfo.getNavigatepageNums(); for (int i : nums) { if(i == pageInfo.getPageNum()) { builder.append("<a style='text-decoration: none;' href='"+basePath+"emps/"+i+"'><font style='color:#FFD700;'>"+ i +"</font></a>"); builder.append(" "); }else { builder.append("<a href='"+basePath+"emps/"+i+"'>"+ i +"</a>"); builder.append(" "); } } //拼接下一页 if(pageInfo.isHasNextPage()) { builder.append("<a href='"+basePath+"emps/"+(pageInfo.getPageNum()+1)+"'>下一页</a>"); builder.append(" "); }else { builder.append("下一页"); builder.append(" "); } //拼接尾页 builder.append("<a href='"+basePath+"emps/"+pageInfo.getPages()+"'>尾页</a>"); return builder.toString(); } }
-
Service层添加声明式事务
package com.atguigu.service.Impl; import com.atguigu.mapper.EmployeeMapper; import com.atguigu.pojo.Employee; import com.atguigu.service.EmployeeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Service("employeeService") public class EmployeeServiceImpl implements EmployeeService { @Autowired @Qualifier("employeeMapper") private EmployeeMapper employeeMapper; //添加声明式事务 @Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.REPEATABLE_READ,readOnly = true) @Override public List<Employee> getEmployees() { return employeeMapper.selectAllEmps(); } }
-
Controller层使用分页插件
package com.atguigu.controller; import com.atguigu.pojo.Employee; import com.atguigu.service.EmployeeService; import com.atguigu.utils.PageUtils; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import javax.servlet.http.HttpServletRequest; import java.util.List; import java.util.Map; @Controller public class EmployeeController { @Autowired @Qualifier("employeeService") private EmployeeService employeeService; @GetMapping("/emps/{pageNo}") public String getAllEmps(@PathVariable("pageNo")Integer pageNo, Map<String,Object> map, HttpServletRequest request) { int pNo = 1; if (pageNo != null) { pNo = pageNo; } //查询前,开启分页 PageHelper.startPage(pNo, 5); List<Employee> employees = employeeService.getEmployees(); //查询后,封装分页 PageInfo<Employee> pageInfo = new PageInfo<>(employees, 5); //使用工具类,获取分页信息 String pageRs = PageUtils.getPageInfo(pageInfo, request); //添加到域中 map.put("pageRs", pageRs); map.put("emps",employees); return "emplist"; } }
-
前端页面展示分页效果
<span th:utext="${pageRs}"></span>
-
项目目录结构
-
实现效果