SpringMVC 概述
三层架构 同步调用
浏览器发送一个请求给后端服务器,后端服务器现在是使用Servlet来接收请求和数据
如果所有的处理都交给Servlet来处理的话,所有的东西都耦合在一起,对后期的维护和扩展极为不利
将后端服务器Servlet拆分成三层,分别是web
、service
和dao
web层主要由servlet来处理,负责页面请求和数据的收集以及响应结果给前端
service层主要负责业务逻辑的处理
dao层主要负责数据的增删改查操作
servlet处理请求和数据的时候,存在的问题是一个servlet只能处理一个请求
针对web层进行了优化,采用了MVC设计模式,将其设计为controller
、view
和Model
controller负责请求和数据的接收,接收后将其转发给service进行业务处理
service根据需要会调用dao对数据进行增删改查
dao把数据处理完后将结果交给service,service再交给controller
controller根据需求组装成Model和View,Model和View组合起来生成页面转发给前端浏览器
这样做的好处就是controller可以处理多个请求,并对请求进行分发,执行不同的业务操作
异步调用
因为是异步调用,后端不需要返回view视图
前端如果通过异步调用的方式进行交互,后台就需要将返回的数据转换成json格式进行返回
SpringMVC主要负责的就是
controller如何接收请求和数据
如何将请求和数据转发给业务层
如何将响应数据转换成json发回到前端
简单案例
创建web工程(Maven结构)
设置tomcat服务器,加载web工程(tomcat插件)
导入坐标(SpringMVC+Servlet)
定义处理请求的功能类(UserController)
设置请求映射(配置映射关系)
将SpringMVC设定加载到Tomcat容器中
开发流程 (1) 创建Maven项目
(2) 补全目录结构
(3) 导入jar包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <?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 > top.qianqianzyk</groupId > <artifactId > springmvc_01_quickstart</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > war</packaging > <dependencies > <dependency > <groupId > javax.servlet</groupId > <artifactId > javax.servlet-api</artifactId > <version > 3.1.0</version > <scope > provided</scope > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-webmvc</artifactId > <version > 5.2.10.RELEASE</version > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.apache.tomcat.maven</groupId > <artifactId > tomcat7-maven-plugin</artifactId > <version > 2.1</version > <configuration > <port > 80</port > <path > /</path > </configuration > </plugin > </plugins > </build > </project >
**说明:**servlet的坐标为什么需要添加<scope>provided</scope>
?
scope是maven中jar包依赖作用范围的描述,
如果不设置默认是compile
在在编译、运行、测试时均有效
如果运行有效的话就会和tomcat中的servlet-api包发生冲突,导致启动报错
provided代表的是该包只在编译和测试的时候用,运行的时候无效直接使用tomcat中的,就可以避免冲突
(4) 创建配置类
1 2 3 4 @Configuration @ComponentScan("top.qianqianzyk.controller") public class SpringMvcConfig {}
(5) 创建Controller类
1 2 3 4 5 6 7 8 9 @Controller public class UserController { @RequestMapping("/save") public void save () { System.out.println("user save ..." ); } }
(6) 使用配置类替换web.xml
将web.xml删除,换成ServletContainersInitConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer { protected WebApplicationContext createServletApplicationContext () { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext (); ctx.register(SpringMvcConfig.class); return ctx; } protected String[] getServletMappings() { return new String []{"/" }; } protected WebApplicationContext createRootApplicationContext () { return null ; } }
(7) 配置Tomcat环境
(8) 启动运行项目
(9) 浏览器访问
页面会报错,报错的原因是后台没有指定返回的页面,目前只需要关注控制台看user save ...
有没有被执行即可
(10) 修改Controller返回值解决上述问题
1 2 3 4 5 6 7 8 9 @Controller public class UserController { @RequestMapping("/save") public String save () { System.out.println("user save ..." ); return "{'info':'springmvc'}" ; } }
报404的错误
出错的原因是,如果方法直接返回字符串,springmvc会把字符串当成页面的名称在项目中进行查找返回,因为不存在对应返回值名称的页面,所以会报404错误,找不到资源
(11) 设置返回数据为json
1 2 3 4 5 6 7 8 9 10 @Controller public class UserController { @RequestMapping("/save") @ResponseBody public String save () { System.out.println("user save ..." ); return "{'info':'springmvc'}" ; } }
注意事项
SpringMVC是基于Spring的,在pom.xml只导入了spring-webmvc
jar包的原因是它会自动依赖spring相关坐标
AbstractDispatcherServletInitializer类是SpringMVC提供的快速初始化Web3.0容器的抽象类
AbstractDispatcherServletInitializer提供了三个接口方法供用户实现
createServletApplicationContext方法,创建Servlet容器时,加载SpringMVC对应的bean并放入WebApplicationContext对象范围中,而WebApplicationContext的作用范围为ServletContext范围,即整个web容器范围
getServletMappings方法,设定SpringMVC对应的请求映射路径,即SpringMVC拦截哪些请求
createRootApplicationContext方法,如果创建Servlet容器时需要加载非SpringMVC对应的bean,使用当前方法进行,使用方式和createServletApplicationContext相同
createServletApplicationContext用来加载SpringMVC环境
createRootApplicationContext用来加载Spring环境
总结
一次性工作
创建工程,设置服务器,加载工程
导入坐标
创建web容器启动类,加载SpringMVC配置,并设置SpringMVC请求拦截路径
SpringMVC核心配置类(设置配置类,扫描controller包,加载Controller控制器bean)
多次工作
定义处理请求的控制器类
定义处理请求的控制器方法,并配置映射路径(@RequestMapping)与返回json数据(@ResponseBody)
解析
启动服务器初始化过程
服务器启动,执行ServletContainersInitConfig类,初始化web容器
执行createServletApplicationContext方法,创建了WebApplicationContext对象
该方法加载SpringMVC的配置类SpringMvcConfig来初始化SpringMVC的容器
加载SpringMvcConfig配置类
执行@ComponentScan加载对应的bean
扫描指定包及其子包下所有类上的注解,如Controller类上的@Controller注解
加载UserController,每个@RequestMapping的名称对应一个具体的方法
此时就建立了 /save
和 save方法的对应关系
执行getServletMappings方法,设定SpringMVC拦截请求的路径规则
/
代表所拦截请求的路径规则,只有被拦截后才能交给SpringMVC来处理请求
单次请求过程
发送请求http://localhost/save
web容器发现该请求满足SpringMVC拦截规则,将请求交给SpringMVC处理
解析请求路径/save
由/save匹配执行对应的方法save()
上面的第五步已经将请求路径和方法建立了对应关系,通过/save就能找到对应的save方法
执行save()
检测到有@ResponseBody直接将save()方法的返回值作为响应体返回给请求方
bean加载控制 问题分析 在入门案例中我们创建过一个SpringMvcConfig
的配置类,然后学习Spring的时候也创建过一个配置类SpringConfig
。这两个配置类都需要加载资源,那么它们分别都需要加载哪些内容?
当前目录结构
controller、service和dao这些类都需要被容器管理成bean对象,那么到底是该让SpringMVC加载还是让Spring加载呢?
SpringMVC加载其相关bean(表现层bean),也就是controller包下的类
Spring控制的bean
业务bean(Service)
功能bean(DataSource,SqlSessionFactoryBean,MapperScannerConfigurer等)
那么该如何让Spring和SpringMVC分开加载各自的内容?
思路分析 环境准备 pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 <?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 > top.qianqianzyk</groupId > <artifactId > springmvc_02_bean_load</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > war</packaging > <dependencies > <dependency > <groupId > javax.servlet</groupId > <artifactId > javax.servlet-api</artifactId > <version > 3.1.0</version > <scope > provided</scope > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-webmvc</artifactId > <version > 5.2.10.RELEASE</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.1.16</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.6</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.47</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > <version > 5.2.10.RELEASE</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis-spring</artifactId > <version > 1.3.0</version > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.apache.tomcat.maven</groupId > <artifactId > tomcat7-maven-plugin</artifactId > <version > 2.1</version > <configuration > <port > 80</port > <path > /</path > </configuration > </plugin > </plugins > </build > </project >
创建对应的配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer { protected WebApplicationContext createServletApplicationContext () { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext (); ctx.register(SpringMvcConfig.class); return ctx; } protected String[] getServletMappings() { return new String []{"/" }; } protected WebApplicationContext createRootApplicationContext () { return null ; } } @Configuration @ComponentScan("top.qianqianzyk.controller") public class SpringMvcConfig {} @Configuration @ComponentScan("top.qianqianzyk") public class SpringConfig {}
编写Controller,Service,Dao,Domain类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Controller public class UserController { @RequestMapping("/save") @ResponseBody public String save () { System.out.println("user save ..." ); return "{'info':'springmvc'}" ; } } public interface UserService { public void save (User user) ; } @Service public class UserServiceImpl implements UserService { public void save (User user) { System.out.println("user service ..." ); } } public interface UserDao { @Insert("insert into tbl_user(name,age)values(#{name},#{age})") public void save (User user) ; } public class User { private Integer id; private String name; private Integer age; }
设置bean加载控制 方式一:修改Spring配置类,设定扫描范围为精准范围
1 2 3 4 @Configuration @ComponentScan({"top.qianqianzyk.service","top.qianqianzyk.dao"}) public class SpringConfig {}
说明:
上述只是通过例子说明可以精确指定让Spring扫描对应的包结构,真正在做开发的时候,因为Dao最终是交给MapperScannerConfigurer
对象来进行扫描处理的,我们只需要将其扫描到service包即可
方式二:修改Spring配置类,设定扫描范围为top.qianqianzyk,排除掉controller包中的bean
1 2 3 4 5 6 7 8 9 @Configuration @ComponentScan(value="top.qianqianzyk", [email protected] ( type = FilterType.ANNOTATION, classes = Controller.class ) ) public class SpringConfig {}
excludeFilters属性:设置扫描加载bean时,排除的过滤规则
type属性:设置排除规则,当前使用按照bean定义时的注解类型进行排除
ANNOTATION:按照注解排除
ASSIGNABLE_TYPE:按照指定的类型过滤
ASPECTJ:按照Aspectj表达式排除,基本上不会用
REGEX:按照正则表达式排除
CUSTOM:按照自定义规则排除
大家只需要知道第一种ANNOTATION即可
classes属性:设置排除的具体注解类,当前设置排除@Controller定义的bean
有了Spring的配置类,要想在tomcat服务器启动将其加载,我们需要修改ServletContainersInitConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer { protected WebApplicationContext createServletApplicationContext () { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext (); ctx.register(SpringMvcConfig.class); return ctx; } protected String[] getServletMappings() { return new String []{"/" }; } protected WebApplicationContext createRootApplicationContext () { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext (); ctx.register(SpringConfig.class); return ctx; } }
对于上述的配置方式,Spring还提供了一种更简单的配置方式,可以不用再去创建AnnotationConfigWebApplicationContext
对象,不用手动register
对应的配置类,如何实现?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer { protected Class<?>[] getRootConfigClasses() { return new Class []{SpringConfig.class}; } protected Class<?>[] getServletConfigClasses() { return new Class []{SpringMvcConfig.class}; } protected String[] getServletMappings() { return new String []{"/" }; } }
请求与响应 设置请求映射路径 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Controller @RequestMapping("/user") public class UserController { @RequestMapping("/save") @ResponseBody public String save () { System.out.println("user save ..." ); return "{'module':'user save'}" ; } @RequestMapping("/delete") @ResponseBody public String save () { System.out.println("user delete ..." ); return "{'module':'user delete'}" ; } } @Controller @RequestMapping("/book") public class BookController { @RequestMapping("/save") @ResponseBody public String save () { System.out.println("book save ..." ); return "{'module':'book save'}" ; } }
请求参数 参数传递 GET发送单个参数 1 2 3 4 5 6 7 8 9 10 @Controller public class UserController { @RequestMapping("/commonParam") @ResponseBody public String commonParam (String name) { System.out.println("普通参数传递 name ==> " +name); return "{'module':'commonParam'}" ; } }
GET发送多个参数 1 2 3 4 5 6 7 8 9 10 11 @Controller public class UserController { @RequestMapping("/commonParam") @ResponseBody public String commonParam (String name,int age) { System.out.println("普通参数传递 name ==> " +name); System.out.println("普通参数传递 age ==> " +age); return "{'module':'commonParam'}" ; } }
如果出现中文乱码的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <build > <plugins > <plugin > <groupId > org.apache.tomcat.maven</groupId > <artifactId > tomcat7-maven-plugin</artifactId > <version > 2.1</version > <configuration > <port > 80</port > <path > /</path > <uriEncoding > UTF-8</uriEncoding > </configuration > </plugin > </plugins > </build >
POST发送参数 和GET一致,不用做任何修改
如果出现中文乱码的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer { protected Class<?>[] getRootConfigClasses() { return new Class [0 ]; } protected Class<?>[] getServletConfigClasses() { return new Class []{SpringMvcConfig.class}; } protected String[] getServletMappings() { return new String []{"/" }; } @Override protected Filter[] getServletFilters() { CharacterEncodingFilter filter = new CharacterEncodingFilter (); filter.setEncoding("UTF-8" ); return new Filter []{filter}; } }
五种类型参数传递 普通参数 如果形参与地址参数名不一致该如何解决?
解决方案:使用@RequestParam注解
1 2 3 4 5 6 7 @RequestMapping("/commonParamDifferentName") @ResponseBody public String commonParamDifferentName (@RequestPaam("name") String userName , int age) { System.out.println("普通参数传递 userName ==> " +userName); System.out.println("普通参数传递 age ==> " +age); return "{'module':'common param different name'}" ; }
注意:写上@RequestParam注解框架就不需要自己去解析注入,能提升框架处理性能
POJO类型参数 设置POJO类,接收参数
1 2 3 4 5 6 7 8 9 10 11 12 13 public class User { private String name; private int age; } @RequestMapping("/pojoParam") @ResponseBody public String pojoParam (User user) { System.out.println("pojo参数传递 user ==> " +user); return "{'module':'pojo param'}" ; }
嵌套POJO类型参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Address { private String province; private String city; } public class User { private String name; private int age; private Address address; } @RequestMapping("/pojoParam") @ResponseBody public String pojoParam (User user) { System.out.println("pojo参数传递 user ==> " +user); return "{'module':'pojo param'}" ; }
数组类型参数 1 2 3 4 5 6 7 8 @RequestMapping("/arrayParam") @ResponseBody public String arrayParam (String[] likes) { System.out.println("数组参数传递 likes ==> " + Arrays.toString(likes)); return "{'module':'array param'}" ; }
集合类型参数 1 2 3 4 5 6 7 @RequestMapping("/listParam") @ResponseBody public String listParam (List<String> likes) { System.out.println("集合参数传递 likes ==> " + likes); return "{'module':'list param'}" ; }
运行时会报错
错误的原因是:SpringMVC将List看做是一个POJO对象来处理,将其创建一个对象并准备把前端的数据封装到对象中,但是List是一个接口无法创建对象,所以报错
解决方案是:使用@RequestParam
注解
1 2 3 4 5 6 7 @RequestMapping("/listParam") @ResponseBody public String listParam (@RequestParam List<String> likes) { System.out.println("集合参数传递 likes ==> " + likes); return "{'module':'list param'}" ; }
集合保存普通参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam绑定参数关系
对于简单数据类型使用数组会比集合更简单些。
JSON数据传输参数 对于JSON数据类型,常见的有三种:
json普通数组([“value1”,”value2”,”value3”,…])
json对象({key1:value1,key2:value2,…})
json对象数组([{key1:value1,…},{key2:value2,…}])
JSON普通数组 pom.xml添加依赖 1 2 3 4 5 <dependency > <groupId > com.fasterxml.jackson.core</groupId > <artifactId > jackson-databind</artifactId > <version > 2.9.0</version > </dependency >
开启SpringMVC注解支持 1 2 3 4 5 6 @Configuration @ComponentScan("com.itheima.controller") @EnableWebMvc public class SpringMvcConfig {}
参数前添加@RequestBody 1 2 3 4 5 6 7 @RequestMapping("/listParamForJson") @ResponseBody public String listParamForJson (@RequestBody List<String> likes) { System.out.println("list common(json)参数传递 list ==> " +likes); return "{'module':'list common for json param'}" ; }
JSON对象数据 1 2 3 4 5 6 @RequestMapping("/pojoParamForJson") @ResponseBody public String pojoParamForJson (@RequestBody User user) { System.out.println("pojo(json)参数传递 user ==> " +user); return "{'module':'pojo for json param'}" ; }
JSON对象数组 1 2 3 4 5 6 @RequestMapping("/listPojoParamForJson") @ResponseBody public String listPojoParamForJson (@RequestBody List<User> list) { System.out.println("list pojo(json)参数传递 list ==> " +list); return "{'module':'list pojo for json param'}" ; }
日期类型参数传递 (1) 编写方法接收日期数据
1 2 3 4 5 6 @RequestMapping("/dataParam") @ResponseBody public String dataParam (Date date) System.out.println("参数传递 date ==> " +date); return "{'module':'data param'}" ; }
(2) 启动Tomcat服务器
(3) 发送请求
2024/10/04
可以直接接收
如果是2024-10-04
,就需要使用@DateTimeFormat
1 2 3 4 5 6 7 8 9 10 @RequestMapping("/dataParam") @ResponseBody public String dataParam (Date date, @DateTimeFormat(pattern="yyyy-MM-dd") Date date1, @DateTimeFormat(pattern="yyyy/MM/dd HH:mm:ss") Date date2) System.out.println("参数传递 date ==> " +date); System.out.println("参数传递 date1(yyyy-MM-dd) ==> " +date1); System.out.println("参数传递 date2(yyyy/MM/dd HH:mm:ss) ==> " +date2); return "{'module':'data param'}" ; }
内部实现原理
前端传递字符串,后端使用日期Date接收
前端传递JSON数据,后端使用对象接收
前端传递字符串,后端使用Integer接收
后台需要的数据类型有很多中
在数据的传递过程中存在很多类型的转换
那么谁来做这个类型转换? SpringMVC
SpringMVC是如何实现类型转换的? SpringMVC中提供了很多类型转换接口和实现类
在框架中,有一些类型转换接口,其中有:
(1) Converter接口
1 2 3 4 5 6 7 8 9 public interface Converter <S, T> { @Nullable T convert (S source) ; }
注意:Converter所属的包为org.springframework.core.convert.converter
框架中有提供很多对应Converter接口的实现类,用来实现不同数据类型之间的转换,如:
请求参数年龄数据(String→Integer)
日期格式转换(String → Date)
(2) HttpMessageConverter接口
该接口是实现对象与JSON之间的转换工作
注意:SpringMVC的配置类把@EnableWebMvc当做标配配置上去,不要省略
响应
环境准备 pom.xml添加Spring依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <?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 > top.qianqianzyk</groupId > <artifactId > springmvc_05_response</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > war</packaging > <dependencies > <dependency > <groupId > javax.servlet</groupId > <artifactId > javax.servlet-api</artifactId > <version > 3.1.0</version > <scope > provided</scope > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-webmvc</artifactId > <version > 5.2.10.RELEASE</version > </dependency > <dependency > <groupId > com.fasterxml.jackson.core</groupId > <artifactId > jackson-databind</artifactId > <version > 2.9.0</version > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.apache.tomcat.maven</groupId > <artifactId > tomcat7-maven-plugin</artifactId > <version > 2.1</version > <configuration > <port > 80</port > <path > /</path > </configuration > </plugin > </plugins > </build > </project >
创建对应的配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer { protected Class<?>[] getRootConfigClasses() { return new Class [0 ]; } protected Class<?>[] getServletConfigClasses() { return new Class []{SpringMvcConfig.class}; } protected String[] getServletMappings() { return new String []{"/" }; } @Override protected Filter[] getServletFilters() { CharacterEncodingFilter filter = new CharacterEncodingFilter (); filter.setEncoding("UTF-8" ); return new Filter []{filter}; } } @Configuration @ComponentScan("top.qianqianzyk.controller") @EnableWebMvc public class SpringMvcConfig {}
编写模型类User
1 2 3 4 5 public class User { private String name; private int age; }
webapp下创建page.jsp
1 2 3 4 5 <html> <body> <h2>Hello Spring MVC!</h2> </body> </html>
编写UserController
1 2 3 4 5 @Controller public class UserController { }
响应页面[了解] 1 2 3 4 5 6 7 8 9 10 11 12 @Controller public class UserController { @RequestMapping("/toJumpPage") public String toJumpPage () { System.out.println("跳转页面" ); return "page.jsp" ; } }
返回文本数据[了解] 1 2 3 4 5 6 7 8 9 10 11 @Controller public class UserController { @RequestMapping("/toText") @ResponseBody public String toText () { System.out.println("返回纯文本数据" ); return "response text" ; } }
响应JSON数据 响应POJO对象 1 2 3 4 5 6 7 8 9 10 11 12 13 @Controller public class UserController { @RequestMapping("/toJsonPOJO") @ResponseBody public User toJsonPOJO () { System.out.println("返回json对象数据" ); User user = new User (); user.setName("" ); user.setAge(15 ); return user; } }
响应POJO集合对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Controller public class UserController { @RequestMapping("/toJsonList") @ResponseBody public List<User> toJsonList () { System.out.println("返回json集合数据" ); User user1 = new User (); user1.setName("" ); user1.setAge(15 ); User user2 = new User (); user2.setName("" ); user2.setAge(12 ); List<User> userList = new ArrayList <User>(); userList.add(user1); userList.add(user2); return userList; } }
Rest风格 简介
传统方式一般是一个请求url对应一种操作,这样做不仅麻烦,也不安全,因为会程序的人读取了你的请求url地址,就大概知道该url实现的是一个什么样的操作
查看REST风格的描述,你会发现请求地址变的简单了,并且光看请求URL并不是很能猜出来该URL的具体功能
所以REST的优点有:
隐藏资源的访问行为,无法通过地址得知对资源是何种操作
书写简化
但是我们的问题也随之而来了,一个相同的url地址即可以是新增也可以是修改或者查询,那么到底我们该如何区分该请求到底是什么操作呢?
按照REST风格访问资源时使用行为动作
区分对资源进行了何种操作
http://localhost/users
查询全部用户信息 GET(查询)
http://localhost/users/1
查询指定用户信息 GET(查询)
http://localhost/users
添加用户信息 POST(新增/保存)
http://localhost/users
修改用户信息 PUT(修改/更新)
http://localhost/users/1
删除用户信息 DELETE(删除)
请求的方式比较多,但是比较常用的就4种,分别是GET
,POST
,PUT
,DELETE
按照不同的请求方式代表不同的操作类型
发送GET请求是用来做查询
发送POST请求是用来做新增
发送PUT请求是用来做修改
发送DELETE请求是用来做删除
注意
:
上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范
REST提供了对应的架构方式,按照这种架构设计项目可以降低开发的复杂性,提高系统的可伸缩性
REST中规定GET/POST/PUT/DELETE针对的是查询/新增/修改/删除,但是我们如果非要用GET请求做删除,这点在程序上运行是可以实现的
但是如果绝大多数人都遵循这种风格,你写的代码让别人读起来就有点莫名其妙了。
描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如:users、books、accounts……
清楚了什么是REST风格后,我们后期会经常提到一个概念叫RESTful
,那什么又是RESTful呢?
后期我们在进行开发的过程中,大多是都是遵从REST风格来访问我们的后台服务,所以可以说咱们以后都是基于RESTful来进行开发的
RESTful入门案例 环境准备 pom.xml添加Spring依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <?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 > top.qianqianzyk</groupId > <artifactId > springmvc_06_rest</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > war</packaging > <dependencies > <dependency > <groupId > javax.servlet</groupId > <artifactId > javax.servlet-api</artifactId > <version > 3.1.0</version > <scope > provided</scope > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-webmvc</artifactId > <version > 5.2.10.RELEASE</version > </dependency > <dependency > <groupId > com.fasterxml.jackson.core</groupId > <artifactId > jackson-databind</artifactId > <version > 2.9.0</version > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.apache.tomcat.maven</groupId > <artifactId > tomcat7-maven-plugin</artifactId > <version > 2.1</version > <configuration > <port > 80</port > <path > /</path > </configuration > </plugin > </plugins > </build > </project >
创建对应的配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer { protected Class<?>[] getRootConfigClasses() { return new Class [0 ]; } protected Class<?>[] getServletConfigClasses() { return new Class []{SpringMvcConfig.class}; } protected String[] getServletMappings() { return new String []{"/" }; } @Override protected Filter[] getServletFilters() { CharacterEncodingFilter filter = new CharacterEncodingFilter (); filter.setEncoding("UTF-8" ); return new Filter []{filter}; } } @Configuration @ComponentScan("com.itheima.controller") @EnableWebMvc public class SpringMvcConfig {}
编写模型类User和Book
1 2 3 4 5 6 7 8 9 10 11 public class User { private String name; private int age; } public class Book { private String name; private double price; }
编写UserController和BookController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 @Controller public class UserController { @RequestMapping("/save") @ResponseBody public String save (@RequestBody User user) { System.out.println("user save..." +user); return "{'module':'user save'}" ; } @RequestMapping("/delete") @ResponseBody public String delete (Integer id) { System.out.println("user delete..." + id); return "{'module':'user delete'}" ; } @RequestMapping("/update") @ResponseBody public String update (@RequestBody User user) { System.out.println("user update..." + user); return "{'module':'user update'}" ; } @RequestMapping("/getById") @ResponseBody public String getById (Integer id) { System.out.println("user getById..." + id); return "{'module':'user getById'}" ; } @RequestMapping("/findAll") @ResponseBody public String getAll () { System.out.println("user getAll..." ); return "{'module':'user getAll'}" ; } } @Controller public class BookController { @RequestMapping(value = "/books",method = RequestMethod.POST) @ResponseBody public String save (@RequestBody Book book) { System.out.println("book save..." + book); return "{'module':'book save'}" ; } @RequestMapping(value = "/books/{id}",method = RequestMethod.DELETE) @ResponseBody public String delete (@PathVariable Integer id) { System.out.println("book delete..." + id); return "{'module':'book delete'}" ; } @RequestMapping(value = "/books",method = RequestMethod.PUT) @ResponseBody public String update (@RequestBody Book book) { System.out.println("book update..." + book); return "{'module':'book update'}" ; } @RequestMapping(value = "/books/{id}",method = RequestMethod.GET) @ResponseBody public String getById (@PathVariable Integer id) { System.out.println("book getById..." + id); return "{'module':'book getById'}" ; } @RequestMapping(value = "/books",method = RequestMethod.GET) @ResponseBody public String getAll () { System.out.println("book getAll..." ); return "{'module':'book getAll'}" ; } }
修改RESTful风格 新增 1 2 3 4 5 6 7 8 9 10 @Controller public class UserController { @RequestMapping(value = "/users",method = RequestMethod.POST) @ResponseBody public String save () { System.out.println("user save..." ); return "{'module':'user save'}" ; } }
删除 1 2 3 4 5 6 7 8 9 10 @Controller public class UserController { @RequestMapping(value = "/users",method = RequestMethod.DELETE) @ResponseBody public String delete (Integer id) { System.out.println("user delete..." + id); return "{'module':'user delete'}" ; } }
将请求路径更改为/users
访问该方法使用 DELETE: http://localhost/users
访问成功,但是删除方法没有携带所要删除数据的id,所以针对RESTful的开发,如何携带数据参数?
传递路径参数 前端发送请求的时候使用:http://localhost/users/1
,路径中的1
就是我们想要传递的参数。
后端获取参数,需要做如下修改:
修改@RequestMapping的value属性,将其中修改为/users/{id}
,目的是和路径匹配
在方法的形参前添加@PathVariable注解
1 2 3 4 5 6 7 8 9 10 @Controller public class UserController { @RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE) @ResponseBody public String delete (@PathVariable Integer id) { System.out.println("user delete..." + id); return "{'module':'user delete'}" ; } }
思考两个问题:
(1)如果方法形参的名称和路径{}
中的值不一致,该怎么办?
(2)如果有多个参数需要传递该如何编写?
前端发送请求的时候使用:http://localhost/users/1/tom
,路径中的1
和tom
就是我们想要传递的两个参数。
后端获取参数,需要做如下修改:
1 2 3 4 5 6 7 8 9 10 @Controller public class UserController { @RequestMapping(value = "/users/{id}/{name}",method = RequestMethod.DELETE) @ResponseBody public String delete (@PathVariable Integer id,@PathVariable String name) { System.out.println("user delete..." + id+"," +name); return "{'module':'user delete'}" ; } }
修改 1 2 3 4 5 6 7 8 9 10 @Controller public class UserController { @RequestMapping(value = "/users",method = RequestMethod.PUT) @ResponseBody public String update (@RequestBody User user) { System.out.println("user update..." + user); return "{'module':'user update'}" ; } }
将请求路径更改为/users
访问该方法使用 PUT: http://localhost/users
访问并携带参数:
根据ID查询 1 2 3 4 5 6 7 8 9 10 @Controller public class UserController { @RequestMapping(value = "/users/{id}" ,method = RequestMethod.GET) @ResponseBody public String getById (@PathVariable Integer id) { System.out.println("user getById..." +id); return "{'module':'user getById'}" ; } }
将请求路径更改为/users
访问该方法使用 GET: http://localhost/users/666
查询所有 1 2 3 4 5 6 7 8 9 10 @Controller public class UserController { @RequestMapping(value = "/users" ,method = RequestMethod.GET) @ResponseBody public String getAll () { System.out.println("user getAll..." ); return "{'module':'user getAll'}" ; } }
将请求路径更改为/users
访问该方法使用 GET: http://localhost/users
小结 RESTful入门案例,我们需要学习的内容如下:
(1)设定Http请求动作(动词)
@RequestMapping(value=””,method = RequestMethod.POST|GET|PUT|DELETE)
(2)设定请求参数(路径变量)
@RequestMapping(value=”/users/{id}”,method = RequestMethod.DELETE)
@ReponseBody
public String delete(@PathVariable Integer id){}
关于接收参数,现在有三个注解@RequestBody
、@RequestParam
、@PathVariable
,这三个注解之间的区别和应用分别是什么?
区别
@RequestParam用于接收url地址传参或表单传参
@RequestBody用于接收json数据
@PathVariable用于接收路径参数,使用{参数名称}描述路径参数
应用
后期开发中,发送请求参数超过1个时,以json格式为主,@RequestBody应用较广
如果发送非json格式数据,选用@RequestParam接收请求参数
采用RESTful进行开发,当参数数量较少时,例如1个,可以采用@PathVariable
RESTful快速开发 问题1:每个方法的@RequestMapping注解中都定义了访问路径/books,重复性太高
问题2:每个方法的@RequestMapping注解中都要使用method属性定义请求方式,重复性太高
问题3:每个方法响应json都需要加上@ResponseBody注解,重复性太高
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @RestController @RequestMapping("/books") public class BookController { @PostMapping public String save (@RequestBody Book book) { System.out.println("book save..." + book); return "{'module':'book save'}" ; } @DeleteMapping("/{id}") public String delete (@PathVariable Integer id) { System.out.println("book delete..." + id); return "{'module':'book delete'}" ; } @PutMapping public String update (@RequestBody Book book) { System.out.println("book update..." + book); return "{'module':'book update'}" ; } @GetMapping("/{id}") public String getById (@PathVariable Integer id) { System.out.println("book getById..." + id); return "{'module':'book getById'}" ; } @GetMapping public String getAll () { System.out.println("book getAll..." ); return "{'module':'book getAll'}" ; } }
RESTful案例 需求分析 需求一:图片列表查询,从后台返回数据,将数据展示在页面上
需求二:新增图片,将新增图书的数据传递到后台,并在控制台打印
步骤分析:
1.搭建项目导入jar包
2.编写Controller类,提供两个方法,一个用来做列表查询,一个用来做新增
3.在方法上使用RESTful进行路径设置
4.完成请求、参数的接收和结果的响应
5.使用PostMan进行测试
6.将前端页面拷贝到项目中
7.页面发送ajax请求
8.完成页面数据的展示
环境准备 pom.xml添加Spring依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <?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 > top.qianqianzyk</groupId > <artifactId > springmvc_07_rest_case</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > war</packaging > <dependencies > <dependency > <groupId > javax.servlet</groupId > <artifactId > javax.servlet-api</artifactId > <version > 3.1.0</version > <scope > provided</scope > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-webmvc</artifactId > <version > 5.2.10.RELEASE</version > </dependency > <dependency > <groupId > com.fasterxml.jackson.core</groupId > <artifactId > jackson-databind</artifactId > <version > 2.9.0</version > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.apache.tomcat.maven</groupId > <artifactId > tomcat7-maven-plugin</artifactId > <version > 2.1</version > <configuration > <port > 80</port > <path > /</path > </configuration > </plugin > </plugins > </build > </project >
创建对应的配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer { protected Class<?>[] getRootConfigClasses() { return new Class [0 ]; } protected Class<?>[] getServletConfigClasses() { return new Class []{SpringMvcConfig.class}; } protected String[] getServletMappings() { return new String []{"/" }; } @Override protected Filter[] getServletFilters() { CharacterEncodingFilter filter = new CharacterEncodingFilter (); filter.setEncoding("UTF-8" ); return new Filter []{filter}; } } @Configuration @ComponentScan("top.qianqianzyk.controller") @EnableWebMvc public class SpringMvcConfig {}
编写模型类Book
1 2 3 4 5 6 7 public class Book { private Integer id; private String type; private String name; private String description; }
编写BookController
1 2 3 4 5 @Controller public class BookController { }
后台接口开发 (1) 编写Controller类并使用RESTful进行配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @RestController @RequestMapping("/books") public class BookController { @PostMapping public String save (@RequestBody Book book) { System.out.println("book save ==> " + book); return "{'module':'book save success'}" ; } @GetMapping public List<Book> getAll () { System.out.println("book getAll is running ..." ); List<Book> bookList = new ArrayList <Book>(); Book book1 = new Book (); book1.setType("计算机" ); book1.setName("SpringMVC入门教程" ); book1.setDescription("小试牛刀" ); bookList.add(book1); Book book2 = new Book (); book2.setType("计算机" ); book2.setName("SpringMVC实战教程" ); book2.setDescription("一代宗师" ); bookList.add(book2); Book book3 = new Book (); book3.setType("计算机丛书" ); book3.setName("SpringMVC实战教程进阶" ); book3.setDescription("一代宗师呕心创作" ); bookList.add(book3); return bookList; } }
(2) 使用PostMan进行测试
页面访问处理 (1) 拷贝静态页面
将前端页面
拷贝到项目的webapp
目录下
(2) 访问pages目录下的books.html
打开浏览器输入http://localhost/pages/books.html
出现错误? 因为SpringMVC拦截了静态资源,根据/pages/books.html去controller找对应的方法,找不到所以会报404的错
SpringMVC为什么会拦截静态资源呢?
解决方案? SpringMVC需要将静态资源进行放行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Configuration public class SpringMvcSupport extends WebMvcConfigurationSupport { @Override protected void addResourceHandlers (ResourceHandlerRegistry registry) { registry.addResourceHandler("/pages/**" ).addResourceLocations("/pages/" ); registry.addResourceHandler("/js/**" ).addResourceLocations("/js/" ); registry.addResourceHandler("/css/**" ).addResourceLocations("/css/" ); registry.addResourceHandler("/plugins/**" ).addResourceLocations("/plugins/" ); } } @Configuration @ComponentScan({"top.qianqianzyk.controller","top.qianqianzyk.config"}) @EnableWebMvc public class SpringMvcConfig {} 或者 @Configuration @ComponentScan("com.itheima") @EnableWebMvc public class SpringMvcConfig {}
(3) 修改books.html页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 <!DOCTYPE html > <html > <head > <meta charset ="utf-8" > <title > SpringMVC案例</title > <link rel ="stylesheet" href ="../plugins/elementui/index.css" > <link rel ="stylesheet" href ="../plugins/font-awesome/css/font-awesome.min.css" > <link rel ="stylesheet" href ="../css/style.css" > </head > <body class ="hold-transition" > <div id ="app" > <div class ="content-header" > <h1 > 图书管理</h1 > </div > <div class ="app-container" > <div class ="box" > <div class ="filter-container" > <el-input placeholder ="图书名称" style ="width: 200px;" class ="filter-item" > </el-input > <el-button class ="dalfBut" > 查询</el-button > <el-button type ="primary" class ="butT" @click ="openSave()" > 新建</el-button > </div > <el-table size ="small" current-row-key ="id" :data ="dataList" stripe highlight-current-row > <el-table-column type ="index" align ="center" label ="序号" > </el-table-column > <el-table-column prop ="type" label ="图书类别" align ="center" > </el-table-column > <el-table-column prop ="name" label ="图书名称" align ="center" > </el-table-column > <el-table-column prop ="description" label ="描述" align ="center" > </el-table-column > <el-table-column label ="操作" align ="center" > <template slot-scope ="scope" > <el-button type ="primary" size ="mini" > 编辑</el-button > <el-button size ="mini" type ="danger" > 删除</el-button > </template > </el-table-column > </el-table > <div class ="pagination-container" > <el-pagination class ="pagiantion" @current-change ="handleCurrentChange" :current-page ="pagination.currentPage" :page-size ="pagination.pageSize" layout ="total, prev, pager, next, jumper" :total ="pagination.total" > </el-pagination > </div > <div class ="add-form" > <el-dialog title ="新增图书" :visible.sync ="dialogFormVisible" > <el-form ref ="dataAddForm" :model ="formData" :rules ="rules" label-position ="right" label-width ="100px" > <el-row > <el-col :span ="12" > <el-form-item label ="图书类别" prop ="type" > <el-input v-model ="formData.type" /> </el-form-item > </el-col > <el-col :span ="12" > <el-form-item label ="图书名称" prop ="name" > <el-input v-model ="formData.name" /> </el-form-item > </el-col > </el-row > <el-row > <el-col :span ="24" > <el-form-item label ="描述" > <el-input v-model ="formData.description" type ="textarea" > </el-input > </el-form-item > </el-col > </el-row > </el-form > <div slot ="footer" class ="dialog-footer" > <el-button @click ="dialogFormVisible = false" > 取消</el-button > <el-button type ="primary" @click ="saveBook()" > 确定</el-button > </div > </el-dialog > </div > </div > </div > </div > </body > <script src ="../js/vue.js" > </script > <script src ="../plugins/elementui/index.js" > </script > <script type ="text/javascript" src ="../js/jquery.min.js" > </script > <script src ="../js/axios-0.18.0.js" > </script > <script > var vue = new Vue ({ el : '#app' , data :{ dataList : [], formData : {}, dialogFormVisible : false , dialogFormVisible4Edit :false , pagination : {}, }, created ( ) { this .getAll (); }, methods : { resetForm ( ) { this .formData = {}; }, openSave ( ) { this .dialogFormVisible = true ; this .resetForm (); }, saveBook () { axios.post ("/books" ,this .formData ).then ((res )=> { }); }, getAll ( ) { axios.get ("/books" ).then ((res )=> { this .dataList = res.data ; }); }, } }) </script > </html >
SSM整合 流程分析 (1) 创建工程
创建一个Maven的web工程
pom.xml添加SSM需要的依赖jar包
编写Web项目的入口配置类,实现AbstractAnnotationConfigDispatcherServletInitializer
重写以下方法
getRootConfigClasses() :返回Spring的配置类->需要SpringConfig配置类
getServletConfigClasses() :返回SpringMVC的配置类->需要SpringMvcConfig配置类
getServletMappings() : 设置SpringMVC请求拦截路径规则
getServletFilters() :设置过滤器,解决POST请求中文乱码问题
(2)SSM整合[重点是各个配置的编写]
SpringConfig
标识该类为配置类 @Configuration
扫描Service所在的包 @ComponentScan
在Service层要管理事务 @EnableTransactionManagement
读取外部的properties配置文件 @PropertySource
整合Mybatis需要引入Mybatis相关配置类 @Import
第三方数据源配置类 JdbcConfig
构建DataSource数据源,DruidDataSouroce,需要注入数据库连接四要素, @Bean @Value
构建平台事务管理器,DataSourceTransactionManager,@Bean
Mybatis配置类 MybatisConfig
构建SqlSessionFactoryBean并设置别名扫描与数据源,@Bean
构建MapperScannerConfigurer并设置DAO层的包扫描
SpringMvcConfig
标识该类为配置类 @Configuration
扫描Controller所在的包 @ComponentScan
开启SpringMVC注解支持 @EnableWebMvc
(3)功能模块[与具体的业务模块有关]
创建数据库表
根据数据库表创建对应的模型类
通过Dao层完成数据库表的增删改查(接口+自动代理)
编写Service层[Service接口+实现类]
@Service
@Transactional
整合Junit对业务层进行单元测试
@RunWith
@ContextConfiguration
@Test
编写Controller层
接收请求 @RequestMapping @GetMapping @PostMapping @PutMapping @DeleteMapping
接收数据 简单、POJO、嵌套POJO、集合、数组、JSON数据类型
@RequestParam
@PathVariable
@RequestBody
转发业务层
响应结果
整合配置 (1) 创建Maven的web项目
(2) 添加依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 <?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 > top.qianqianzyk</groupId > <artifactId > springmvc_08_ssm</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > war</packaging > <dependencies > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-webmvc</artifactId > <version > 5.2.10.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > <version > 5.2.10.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-test</artifactId > <version > 5.2.10.RELEASE</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.6</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis-spring</artifactId > <version > 1.3.0</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.47</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.1.16</version > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.12</version > <scope > test</scope > </dependency > <dependency > <groupId > javax.servlet</groupId > <artifactId > javax.servlet-api</artifactId > <version > 3.1.0</version > <scope > provided</scope > </dependency > <dependency > <groupId > com.fasterxml.jackson.core</groupId > <artifactId > jackson-databind</artifactId > <version > 2.9.0</version > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.apache.tomcat.maven</groupId > <artifactId > tomcat7-maven-plugin</artifactId > <version > 2.1</version > <configuration > <port > 80</port > <path > /</path > </configuration > </plugin > </plugins > </build > </project >
(3) 创建项目包结构
config目录存放的是相关的配置类
controller编写的是Controller类
dao存放的是Dao接口,因为使用的是Mapper接口代理方式,所以没有实现类包
service存的是Service接口,impl存放的是Service实现类
resources:存入的是配置文件,如Jdbc.properties
webapp:目录可以存放静态资源
test/java:存放的是测试类
(4) 创建SpringConfig配置类
1 2 3 4 5 6 7 @Configuration @ComponentScan({"top.qianqianzyk.service"}) @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MyBatisConfig.class}) @EnableTransactionManagement public class SpringConfig {}
(5) 创建JdbcConfig配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean public DataSource dataSource () { DruidDataSource dataSource = new DruidDataSource (); dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } @Bean public PlatformTransactionManager transactionManager (DataSource dataSource) { DataSourceTransactionManager ds = new DataSourceTransactionManager (); ds.setDataSource(dataSource); return ds; } }
(6) 创建MybatisConfig配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class MyBatisConfig { @Bean public SqlSessionFactoryBean sqlSessionFactory (DataSource dataSource) { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean (); factoryBean.setDataSource(dataSource); factoryBean.setTypeAliasesPackage("top.qianqianzyk.domain" ); return factoryBean; } @Bean public MapperScannerConfigurer mapperScannerConfigurer () { MapperScannerConfigurer msc = new MapperScannerConfigurer (); msc.setBasePackage("top.qianqianzyk.dao" ); return msc; } }
(7) 创建jdbc.properties
1 2 3 4 jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssm_db jdbc.username=root jdbc.password=root
(8) 创建SpringMVC配置类
1 2 3 4 5 @Configuration @ComponentScan("top.qianqianzyk.controller") @EnableWebMvc public class SpringMvcConfig {}
(9) 创建Web项目入口配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer { protected Class<?>[] getRootConfigClasses() { return new Class []{SpringConfig.class}; } protected Class<?>[] getServletConfigClasses() { return new Class []{SpringMvcConfig.class}; } protected String[] getServletMappings() { return new String []{"/" }; } @Override protected Filter[] getServletFilters() { CharacterEncodingFilter filter = new CharacterEncodingFilter (); filter.setEncoding("utf-8" ); return new Filter []{filter}; } }
功能模块开发 (1) 创建数据库及表
1 2 3 4 5 6 7 8 9 10 create database ssm_db character set utf8;use ssm_db; create table tbl_book( id int primary key auto_increment, type varchar (20 ), name varchar (50 ), description varchar (255 ) ) insert into `tbl_book`(`id`,`type`,`name`,`description`) values (1 ,'计算机理论' ,'Spring实战 第五版' ,'Spring入门经典教程,深入理解Spring原理技术内幕' ),(2 ,'计算机理论' ,'Spring 5核心原理与30个类手写实践' ,'十年沉淀之作,手写Spring精华思想' ),(3 ,'计算机理论' ,'Spring 5设计模式' ,'深入Spring源码刨析Spring源码中蕴含的10大设计模式' ),(4 ,'计算机理论' ,'Spring MVC+Mybatis开发从入门到项目实战' ,'全方位解析面向Web应用的轻量级框架,带你成为Spring MVC开发高手' ),(5 ,'计算机理论' ,'轻量级Java Web企业应用实战' ,'源码级刨析Spring框架,适合已掌握Java基础的读者' ),(6 ,'计算机理论' ,'Java核心技术 卷Ⅰ 基础知识(原书第11版)' ,'Core Java第11版,Jolt大奖获奖作品,针对Java SE9、10、11全面更新' ),(7 ,'计算机理论' ,'深入理解Java虚拟机' ,'5个纬度全面刨析JVM,大厂面试知识点全覆盖' ),(8 ,'计算机理论' ,'Java编程思想(第4版)' ,'Java学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉' ),(9 ,'计算机理论' ,'零基础学Java(全彩版)' ,'零基础自学编程的入门图书,由浅入深,详解Java语言的编程思想和核心技术' ),(10 ,'市场营销' ,'直播就这么做:主播高效沟通实战指南' ,'李子柒、李佳奇、薇娅成长为网红的秘密都在书中' ),(11 ,'市场营销' ,'直播销讲实战一本通' ,'和秋叶一起学系列网络营销书籍' ),(12 ,'市场营销' ,'直播带货:淘宝、天猫直播从新手到高手' ,'一本教你如何玩转直播的书,10堂课轻松实现带货月入3W+' );
(2) 编写模型类
1 2 3 4 5 6 7 public class Book { private Integer id; private String type; private String name; private String description; }
(3) 编写Dao接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public interface BookDao { @Insert("insert into tbl_book (type,name,description) values(#{type},#{name},#{description})") public void save (Book book) ; @Update("update tbl_book set type = #{type}, name = #{name}, description = #{description} where id = #{id}") public void update (Book book) ; @Delete("delete from tbl_book where id = #{id}") public void delete (Integer id) ; @Select("select * from tbl_book where id = #{id}") public Book getById (Integer id) ; @Select("select * from tbl_book") public List<Book> getAll () ; }
(4) 编写Service接口和实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Transactional public interface BookService { public boolean save (Book book) ; public boolean update (Book book) ; public boolean delete (Integer id) ; public Book getById (Integer id) ; public List<Book> getAll () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Service public class BookServiceImpl implements BookService { @Autowired private BookDao bookDao; public boolean save (Book book) { bookDao.save(book); return true ; } public boolean update (Book book) { bookDao.update(book); return true ; } public boolean delete (Integer id) { bookDao.delete(id); return true ; } public Book getById (Integer id) { return bookDao.getById(id); } public List<Book> getAll () { return bookDao.getAll(); } }
说明:
(5) 编写Contorller类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @RestController @RequestMapping("/books") public class BookController { @Autowired private BookService bookService; @PostMapping public boolean save (@RequestBody Book book) { return bookService.save(book); } @PutMapping public boolean update (@RequestBody Book book) { return bookService.update(book); } @DeleteMapping("/{id}") public boolean delete (@PathVariable Integer id) { return bookService.delete(id); } @GetMapping("/{id}") public Book getById (@PathVariable Integer id) { return bookService.getById(id); } @GetMapping public List<Book> getAll () { return bookService.getAll(); } }
单元测试 (1) 新建测试类
1 2 3 4 5 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class BookServiceTest {}
(2) 注入Service类
1 2 3 4 5 6 7 8 9 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class BookServiceTest { @Autowired private BookService bookService; }
(3) 编写测试方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class BookServiceTest { @Autowired private BookService bookService; @Test public void testGetById () { Book book = bookService.getById(1 ); System.out.println(book); } @Test public void testGetAll () { List<Book> all = bookService.getAll(); System.out.println(all); } }
统一结果封装 表现层与前端数据传输协议定义 如果随着业务的增长,我们需要返回的数据类型会越来越多。对于前端开发人员在解析数据的时候就比较凌乱了,所以对于前端来说,如果后台能够返回一个统一的数据结果,前端在解析的时候就可以按照一种方式进行解析。开发就会变得更加简单
具体如何来解决这件事,大体的思路为:
为了封装返回的结果数据:创建结果模型类,封装数据到data属性中
为了封装返回的数据是何种操作及是否操作成功:封装操作结果到code属性中
操作失败后为了封装返回的错误信息:封装特殊消息到message(msg)属性中
根据分析,可以设置统一数据返回结果类
1 2 3 4 5 public class Result { private Object data; private Integer code; private String msg; }
**注意:**Result类名及类中的字段并不是固定的,可以根据需要自行增减提供若干个构造方法,方便操作
表现层与前端数据传输协议实现 对于结果封装,我们应该是在表现层进行处理,所以我们把结果类放在controller包下,当然也可以放在domain包,这个都是可以的,具体如何实现结果封装,具体的步骤为
(1) 创建Result类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Result { private Object data; private Integer code; private String msg; public Result () { } public Result (Integer code,Object data) { this .data = data; this .code = code; } public Result (Integer code, Object data, String msg) { this .data = data; this .code = code; this .msg = msg; } }
(2) 定义返回码Code类
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Code { public static final Integer SAVE_OK = 20011 ; public static final Integer DELETE_OK = 20021 ; public static final Integer UPDATE_OK = 20031 ; public static final Integer GET_OK = 20041 ; public static final Integer SAVE_ERR = 20010 ; public static final Integer DELETE_ERR = 20020 ; public static final Integer UPDATE_ERR = 20030 ; public static final Integer GET_ERR = 20040 ; }
**注意:**code类中的常量设计也不是固定的,可以根据需要自行增减,例如将查询再进行细分为GET_OK,GET_ALL_OK,GET_PAGE_OK等
(3) 修改Controller类的返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @RestController @RequestMapping("/books") public class BookController { @Autowired private BookService bookService; @PostMapping public Result save (@RequestBody Book book) { boolean flag = bookService.save(book); return new Result (flag ? Code.SAVE_OK:Code.SAVE_ERR,flag); } @PutMapping public Result update (@RequestBody Book book) { boolean flag = bookService.update(book); return new Result (flag ? Code.UPDATE_OK:Code.UPDATE_ERR,flag); } @DeleteMapping("/{id}") public Result delete (@PathVariable Integer id) { boolean flag = bookService.delete(id); return new Result (flag ? Code.DELETE_OK:Code.DELETE_ERR,flag); } @GetMapping("/{id}") public Result getById (@PathVariable Integer id) { Book book = bookService.getById(id); Integer code = book != null ? Code.GET_OK : Code.GET_ERR; String msg = book != null ? "" : "数据查询失败,请重试!" ; return new Result (code,book,msg); } @GetMapping public Result getAll () { List<Book> bookList = bookService.getAll(); Integer code = bookList != null ? Code.GET_OK : Code.GET_ERR; String msg = bookList != null ? "" : "数据查询失败,请重试!" ; return new Result (code,bookList,msg); } }
(4) 启动服务测试
至此,我们的返回结果就已经能以一种统一的格式返回给前端。前端根据返回的结果,先从中获取code
,根据code判断,如果成功则取data
属性的值,如果失败,则取msg
中的值做提示
统一异常处理 异常的种类及出现异常的原因:
框架内部抛出的异常:因使用不合规导致
数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)
工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)
可以发现在开发的任何一个位置都有可能出现异常,而且这些异常是不能避免的。所以我们就得将异常进行处理
思考
各个层级均出现异常,异常处理代码书写在哪一层? 所有的异常均抛出到表现层进行处理
异常的种类很多,表现层如何将所有的异常都处理到呢? 异常分类
表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决?AOP
对于上面这些问题及解决方案,SpringMVC已经有了一套解决方案:
异常处理器的使用 使用步骤 创建异常处理器类
1 2 3 4 5 6 7 8 9 @RestControllerAdvice public class ProjectExceptionAdvice { @ExceptionHandler(Exception.class) public void doException (Exception ex) { System.out.println("异常出现!" ) } }
确保SpringMvcConfig能够扫描到异常处理器类
让程序抛出异常
修改BookController
的getById方法,添加int i = 1/0
.
1 2 3 4 5 6 7 8 @GetMapping("/{id}") public Result getById (@PathVariable Integer id) { int i = 1 /0 ; Book book = bookService.getById(id); Integer code = book != null ? Code.GET_OK : Code.GET_ERR; String msg = book != null ? "" : "数据查询失败,请重试!" ; return new Result (code,book,msg); }
运行程序,测试
异常处理器类返回结果给前端 1 2 3 4 5 6 7 8 9 10 11 @RestControllerAdvice public class ProjectExceptionAdvice { @ExceptionHandler(Exception.class) public Result doException (Exception ex) { System.out.println("异常出现!" ) return new Result (666 ,null ,"异常出现!" ); } }
启动运行程序,测试
至此,就算后台执行的过程中抛出异常,最终也能按照我们和前端约定好的格式返回给前端
项目异常处理方案 异常分类 异常处理器我们已经能够使用了,那么在项目中该如何来处理异常呢?
因为异常的种类有很多,如果每一个异常都对应一个@ExceptionHandler,那得写多少个方法来处理各自的异常,所以我们在处理异常之前,需要对异常进行一个分类:
业务异常(BusinessException)
规范的用户行为产生的异常
用户在页面输入内容的时候未按照指定格式进行数据填写,如在年龄框输入的是字符串
不规范的用户行为操作产生的异常
系统异常(SystemException)
其他异常(Exception)
将异常分类以后,针对不同类型的异常,要提供具体的解决方案
异常解决方案
业务异常(BusinessException)
系统异常(SystemException)
发送固定消息传递给用户,安抚用户
系统繁忙,请稍后再试
系统正在维护升级,请稍后再试
系统出问题,请联系系统管理员等
发送特定消息给运维人员,提醒维护
记录日志
发消息和记录日志对用户来说是不可见的,属于后台程序
其他异常(Exception)
发送固定消息传递给用户,安抚用户
发送特定消息给编程人员,提醒维护(纳入预期范围内)
记录日志
异常解决方案的具体实现
思路:
1.先通过自定义异常,完成BusinessException和SystemException的定义
2.将其他异常包装成自定义异常类型
3.在异常处理器类中对不同的异常进行处理
(1) 自定义异常类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public class SystemException extends RuntimeException { private Integer code; public Integer getCode () { return code; } public void setCode (Integer code) { this .code = code; } public SystemException (Integer code, String message) { super (message); this .code = code; } public SystemException (Integer code, String message, Throwable cause) { super (message, cause); this .code = code; } } public class BusinessException extends RuntimeException { private Integer code; public Integer getCode () { return code; } public void setCode (Integer code) { this .code = code; } public BusinessException (Integer code, String message) { super (message); this .code = code; } public BusinessException (Integer code, String message, Throwable cause) { super (message, cause); this .code = code; } }
说明:
让自定义异常类继承RuntimeException
的好处是,后期在抛出这两个异常的时候,就不用在try…catch…或throws了
自定义异常类中添加code
属性的原因是为了更好的区分异常是来自哪个业务的
(2) 将其他异常包成自定义异常
假如在BookServiceImpl的getById方法抛异常了,该如何来包装呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 public Book getById (Integer id) { if (id == 1 ){ throw new BusinessException (Code.BUSINESS_ERR,"!" ); } try { int i = 1 /0 ; }catch (Exception e){ throw new SystemException (Code.SYSTEM_TIMEOUT_ERR,"服务器访问超时,请重试!" ,e); } return bookDao.getById(id); }
具体的包装方式有:
方式一:try{}catch(){}
在catch中重新throw我们自定义异常即可。
方式二:直接throw自定义异常即可
我们还需要在Code类中再新增需要的属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Code { public static final Integer SAVE_OK = 20011 ; public static final Integer DELETE_OK = 20021 ; public static final Integer UPDATE_OK = 20031 ; public static final Integer GET_OK = 20041 ; public static final Integer SAVE_ERR = 20010 ; public static final Integer DELETE_ERR = 20020 ; public static final Integer UPDATE_ERR = 20030 ; public static final Integer GET_ERR = 20040 ; public static final Integer SYSTEM_ERR = 50001 ; public static final Integer SYSTEM_TIMEOUT_ERR = 50002 ; public static final Integer SYSTEM_UNKNOW_ERR = 59999 ; public static final Integer BUSINESS_ERR = 60002 ; }
(3) 处理器类中处理自定义异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @RestControllerAdvice public class ProjectExceptionAdvice { @ExceptionHandler(SystemException.class) public Result doSystemException (SystemException ex) { return new Result (ex.getCode(),null ,ex.getMessage()); } @ExceptionHandler(BusinessException.class) public Result doBusinessException (BusinessException ex) { return new Result (ex.getCode(),null ,ex.getMessage()); } @ExceptionHandler(Exception.class) public Result doOtherException (Exception ex) { return new Result (Code.SYSTEM_UNKNOW_ERR,null ,"系统繁忙,请稍后再试!" ); } }
(4) 运行程序
拦截器
(1) 浏览器发送一个请求会先到Tomcat的web服务器
(2) Tomcat服务器接收到请求以后,会去判断请求的是静态资源还是动态资源
(3) 如果是静态资源,会直接到Tomcat的项目部署目录下去直接访问
(4) 如果是动态资源,就需要交给项目的后台代码进行处理
(5) 在找到具体的方法之前,我们可以去配置过滤器(可以配置多个),按照顺序进行执行
(6) 然后进入到到中央处理器(SpringMVC中的内容),SpringMVC会根据配置的规则进行拦截
(7) 如果满足规则,则进行处理,找到其对应的controller类中的方法进行执行,完成后返回结果
(8) 如果不满足规则,则不进行处理
(9) 这个时候,如果我们需要在每个Controller方法执行的前后添加业务,具体该如何来实现?
这个就是拦截器要做的事
拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行
作用:
在指定的方法调用前后执行预先设定的代码
阻止原始方法的执行
总结:拦截器就是用来做增强
可以发现
所以这个时候,就有一个问题需要思考:拦截器和过滤器之间的区别是什么?
归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强
简单案例 环境准备 创建一个Web的Maven项目
pom.xml添加SSM整合所需jar包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <?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 > top.qianqianzyk</groupId > <artifactId > springmvc_12_interceptor</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > war</packaging > <dependencies > <dependency > <groupId > javax.servlet</groupId > <artifactId > javax.servlet-api</artifactId > <version > 3.1.0</version > <scope > provided</scope > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-webmvc</artifactId > <version > 5.2.10.RELEASE</version > </dependency > <dependency > <groupId > com.fasterxml.jackson.core</groupId > <artifactId > jackson-databind</artifactId > <version > 2.9.0</version > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.apache.tomcat.maven</groupId > <artifactId > tomcat7-maven-plugin</artifactId > <version > 2.1</version > <configuration > <port > 80</port > <path > /</path > </configuration > </plugin > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-compiler-plugin</artifactId > <configuration > <source > 8</source > <target > 8</target > </configuration > </plugin > </plugins > </build > </project >
创建对应的配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer { protected Class<?>[] getRootConfigClasses() { return new Class [0 ]; } protected Class<?>[] getServletConfigClasses() { return new Class []{SpringMvcConfig.class}; } protected String[] getServletMappings() { return new String []{"/" }; } @Override protected Filter[] getServletFilters() { CharacterEncodingFilter filter = new CharacterEncodingFilter (); filter.setEncoding("UTF-8" ); return new Filter []{filter}; } } @Configuration @ComponentScan({"top.qianqianzyk.controller"}) @EnableWebMvc public class SpringMvcConfig { }
创建模型类Book
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class Book { private String name; private double price; public String getName () { return name; } public void setName (String name) { this .name = name; } public double getPrice () { return price; } public void setPrice (double price) { this .price = price; } @Override public String toString () { return "Book{" + "书名='" + name + '\'' + ", 价格=" + price + '}' ; } }
编写Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @RestController @RequestMapping("/books") public class BookController { @PostMapping public String save (@RequestBody Book book) { System.out.println("book save..." + book); return "{'module':'book save'}" ; } @DeleteMapping("/{id}") public String delete (@PathVariable Integer id) { System.out.println("book delete..." + id); return "{'module':'book delete'}" ; } @PutMapping public String update (@RequestBody Book book) { System.out.println("book update..." +book); return "{'module':'book update'}" ; } @GetMapping("/{id}") public String getById (@PathVariable Integer id) { System.out.println("book getById..." +id); return "{'module':'book getById'}" ; } @GetMapping public String getAll () { System.out.println("book getAll..." ); return "{'module':'book getAll'}" ; } }
拦截器开发 (1) 创建拦截器类
让类实现HandlerInterceptor接口,重写接口中的三个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Component public class ProjectInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle..." ); return true ; } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle..." ); } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion..." ); } }
**注意:**拦截器类要被SpringMVC容器扫描到。
(2) 配置拦截器类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Configuration public class SpringMvcSupport extends WebMvcConfigurationSupport { @Autowired private ProjectInterceptor projectInterceptor; @Override protected void addResourceHandlers (ResourceHandlerRegistry registry) { registry.addResourceHandler("/pages/**" ).addResourceLocations("/pages/" ); } @Override protected void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(projectInterceptor).addPathPatterns("/books" ); } }
(3) SpringMVC添加SpringMvcSupport包扫描
1 2 3 4 5 6 @Configuration @ComponentScan({"top.qianqianzyk.controller","top.qianqianzyk.config"}) @EnableWebMvc public class SpringMvcConfig { }
(4) 运行程序测试
如果发送http://localhost/books/100
会发现拦截器没有被执行,原因是拦截器的addPathPatterns
方法配置的拦截路径是/books
,我们现在发送的是/books/100
,所以没有匹配上,因此没有拦截,拦截器就不会执行。
(5) 修改拦截器拦截规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Configuration public class SpringMvcSupport extends WebMvcConfigurationSupport { @Autowired private ProjectInterceptor projectInterceptor; @Override protected void addResourceHandlers (ResourceHandlerRegistry registry) { registry.addResourceHandler("/pages/**" ).addResourceLocations("/pages/" ); } @Override protected void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(projectInterceptor).addPathPatterns("/books" ,"/books/*" ); } }
这个时候,如果再次访问http://localhost/books/100
,拦截器就会被执行。
拦截器中的preHandler
方法,如果返回true,则代表放行,会执行原始Controller类中要请求的方法,如果返回false,则代表拦截,后面的就不会再执行了
(6) 简化SpringMvcSupport的编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Configuration @ComponentScan({"top.qianqianzyk.controller"}) @EnableWebMvc public class SpringMvcConfig implements WebMvcConfigurer { @Autowired private ProjectInterceptor projectInterceptor; @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(projectInterceptor).addPathPatterns("/books" ,"/books/*" ); } }
这样就可以不用再写SpringMvcSupport
类了
当有拦截器后,请求会先进入preHandle方法,
如果方法返回true,则放行继续执行后面的handle[controller的方法]和后面的方法
如果返回false,则直接跳过后面方法的执行
拦截器参数 前置处理方法 原始方法之前运行preHandle
1 2 3 4 5 6 public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle" ); return true ; }
request:请求对象
response:响应对象
handler:被调用的处理器对象,本质上是一个方法对象,对反射中的Method对象进行了再包装
使用request对象可以获取请求数据中的内容,如获取请求头的Content-Type
1 2 3 4 5 public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String contentType = request.getHeader("Content-Type" ); System.out.println("preHandle..." +contentType); return true ; }
使用handler参数,可以获取方法的相关信息
1 2 3 4 5 6 public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HandlerMethod hm = (HandlerMethod)handler; String methodName = hm.getMethod().getName(); System.out.println("preHandle..." +methodName); return true ; }
后置处理方法 原始方法运行后运行,如果原始方法被拦截,则不执行
1 2 3 4 5 6 public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle" ); }
前三个参数和上面的是一致的
modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整
完成处理方法 拦截器最后执行的方法,无论原始方法是否执行
1 2 3 4 5 6 public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion" ); }
前三个参数与上面的是一致的
ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理
这三个方法中,**最常用的是preHandle,**在这个方法中可以通过返回值来决定是否要进行放行,我们可以把业务逻辑放在该方法中,如果满足业务则返回true放行,不满足则返回false拦截
拦截器链配置 配置多个拦截器 (1) 创建拦截器类
实现接口,并重写接口中的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Component public class ProjectInterceptor2 implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle...222" ); return false ; } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle...222" ); } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion...222" ); } }
(2) 配置拦截器类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Configuration @ComponentScan({"top.qianqianzyk.controller"}) @EnableWebMvc public class SpringMvcConfig implements WebMvcConfigurer { @Autowired private ProjectInterceptor projectInterceptor; @Autowired private ProjectInterceptor2 projectInterceptor2; @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(projectInterceptor).addPathPatterns("/books" ,"/books/*" ); registry.addInterceptor(projectInterceptor2).addPathPatterns("/books" ,"/books/*" ); } }
(3) 运行程序,观察顺序
当配置多个拦截器时,形成拦截器链
拦截器链的运行顺序参照拦截器添加顺序为准
当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行
当拦截器运行中断,仅运行配置在前面的拦截器的afterCompletion操作
preHandle:与配置顺序相同,必定运行
postHandle:与配置顺序相反,可能不运行
afterCompletion:与配置顺序相反,可能不运行
注解快查 SpringMVC入门案例 @Controller
名称
@Controller
类型
类注解
位置
SpringMVC控制器类定义上方
作用
设定SpringMVC的核心控制器bean
@RequestMapping
名称
@RequestMapping
类型
类注解或方法注解
位置
SpringMVC控制器类或方法定义上方
作用
设置当前控制器方法请求访问路径
相关属性
value(默认),请求访问路径
@ResponseBody
名称
@ResponseBody
类型
类注解或方法注解
位置
SpringMVC控制器类或方法定义上方
作用
设置当前控制器方法响应内容为当前返回值,无需解析
说明:
该注解可以写在类上或者方法上
写在类上就是该类下的所有方法都有@ReponseBody功能
当方法上有@ReponseBody注解后
方法的返回值为字符串,会将其作为文本内容直接响应给前端
方法的返回值为对象,会将对象转换成JSON响应给前端
此处又使用到了类型转换,内部还是通过Converter接口的实现类完成的,所以Converter除了前面所说的功能外,它还可以实现:
对象转Json数据(POJO -> json)
集合转Json数据(Collection -> json)
bean加载控制 @ComponentScan
名称
@ComponentScan
类型
类注解
位置
类定义上方
作用
设置spring配置类扫描路径,用于加载使用注解格式定义的bean
相关属性
excludeFilters:排除扫描路径中加载的bean,需要指定类别(type)和具体项(classes) includeFilters:加载指定的bean,需要指定类别(type)和具体项(classes)
请求参数传递 @RequestParam
名称
@RequestParam
类型
形参注解
位置
SpringMVC控制器方法形参定义前面
作用
绑定请求参数与处理器方法形参间的关系
相关参数
required:是否为必传参数 defaultValue:参数默认值
JSON数据传输 @EnableWebMvc
名称
@EnableWebMvc
类型
配置类注解
位置
SpringMVC配置类定义上方
作用
开启SpringMVC多项辅助功能
@RequestBody
名称
@RequestBody
类型
形参注解
位置
SpringMVC控制器方法形参定义前面
作用
将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次
日期类型数据传输
名称
@DateTimeFormat
类型
形参注解
位置
SpringMVC控制器方法形参前面
作用
设定日期时间型数据格式
相关属性
pattern:指定日期时间格式字符串
Rest @PathVariable
名称
@PathVariable
类型
形参注解
位置
SpringMVC控制器方法形参定义前面
作用
绑定路径参数与处理器方法形参间的关系,要求路径参数名与形参名一一对应
@RestController
名称
@RestController
类型
类注解
位置
基于SpringMVC的RESTful开发控制器类定义上方
作用
设置当前控制器类为RESTful风格, 等同于@Controller与@ResponseBody两个注解组合功能
@GetMapping @PostMapping @PutMapping @DeleteMapping
名称
@GetMapping @PostMapping @PutMapping @DeleteMapping
类型
方法注解
位置
基于SpringMVC的RESTful开发控制器方法定义上方
作用
设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作, 例如@GetMapping对应GET请求
相关属性
value(默认):请求访问路径
异常处理 @RestControllerAdvice
名称
@RestControllerAdvice
类型
类注解
位置
Rest风格开发的控制器增强类定义上方
作用
为Rest风格开发的控制器类做增强
**说明:**此注解自带@ResponseBody注解与@Component注解,具备对应的功能
@ExceptionHandler
名称
@ExceptionHandler
类型
方法注解
位置
专用于异常处理的控制器方法上方
作用
设置指定异常的处理方案,功能等同于控制器方法, 出现异常后终止原始控制器执行,并转入当前方法执行
说明: 此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常
感谢
springMVC文档
黑马程序员授课视频