文章目录
1、SpringMVC概述
- SpringMVC是隶属于Spring框架的一部分,主要是用来进行Web开发,是对Servlet进行了封装。
-
浏览器发送一个请求给后端服务器,后端服务器现在是使用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发回到前端
介绍了这么多,对SpringMVC进行一个定义
-
SpringMVC是一种基于Java实现MVC模型的轻量级Web框架
-
优点
- 使用简单、开发便捷(相比于Servlet)
- 灵活性强
2、SpringMVC入门案例
因为SpringMVC是一个Web框架,将来是要替换Servlet,所以先来回顾下以前Servlet是如何进行开发的?
1.创建web工程(Maven结构)
2.设置tomcat服务器,加载web工程(tomcat插件)
3.导入坐标(Servlet)
4.定义处理请求的功能类(UserServlet)
5.设置请求映射(配置映射关系)
SpringMVC的制作过程和上述流程几乎是一致的,具体的实现流程是什么?
1.创建web工程(Maven结构)
2.设置tomcat服务器,加载web工程(tomcat插件)
3.导入坐标(SpringMVC+Servlet)
4.定义处理请求的功能类(UserController)
5.设置请求映射(配置映射关系)
6.将SpringMVC设定加载到Tomcat容器中
2.1、案例制作
步骤一:创建Maven项目
步骤二:导入坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springmvc_quickstart</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!--导入SpringMVC与servlet-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</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中的,就避免冲突
步骤三:创建配置类
package com.chuhe.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
//创建springmvc的配置文件,加载controller对应的bean
@Configuration
@ComponentScan("com.chuhe.controller")
public class SpringMvcConfig {
}
步骤四:创建Controller类
package com.chuhe.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
//定义controller,使用@Controller定义bean
@Controller
public class UserController {
//设置当前操作的访问路径
@RequestMapping("/save")
//设置当前操作的返回值类型
@ResponseBody
public String save(){
System.out.println("use save...");
return "{'module':'springmvc'}";
}
}
步骤五:使用配置类替换web.xml
将web.xml删除,换成ServletContainersInitConfig
package com.chuhe.config;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
//定义一个servlet容器启动配置类,在里面加载spring的配置
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
//加载SprivgMVC配置
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext ctx=new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfig.class);
return ctx;
}
//设置哪些请求归属springMVC处理
@Override
protected String[] getServletMappings() {
return new String[]{"/"};//所有请求归SpringMVC管理
}
//加载spring容器配置
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
}
步骤六:启动tomcat,浏览器输入http://localhost/save
进行访问
2.2、工作流程解析
2.2.1、启动服务器初始化过程
-
服务器启动,执行ServletContainersInitConfig类,初始化web容器
♠ 功能类似于以前的web.xml
-
执行createServletApplicationContext方法,创建了WebApplicationContext对象
♠ 该方法加载SpringMVC的配置类SpringMvcConfig来初始化SpringMVC的容器
-
加载SpringMvcConfig配置类
//创建springmvc的配置文件,加载controller对应的bean @Configuration @ComponentScan("com.chuhe.controller") public class SpringMvcConfig { }
-
执行@ComponentScan加载对应的bean
♠ 扫描指定包及其子包下所有类上的注解,如Controller类上的@Controller注解
-
加载UserController,每个@RequestMapping的名称对应一个具体的方法
//定义controller,使用@Controller定义bean @Controller public class UserController { //设置当前操作的访问路径 @RequestMapping("/save") //设置当前操作的返回值类型 @ResponseBody public String save(){ System.out.println("use save..."); return "{'module':'springmvc'}"; } }
♠ 此时就建立了
/save
和 save方法的对应关系 -
执行getServletMappings方法,设定SpringMVC拦截请求的路径规则
protected String[] getServletMappings() { return new String[]{"/"};//所有请求归SpringMVC管理 }
/
代表所拦截请求的路径规则,只有被拦截后才能交给SpringMVC来处理请求
2.2.2、单次请求过程
- 发送请求
http://localhost/save
- web容器发现该请求满足SpringMVC拦截规则,将请求交给SpringMVC处理
- 解析请求路径/save
- 由/save匹配执行对应的方法save()
♠ 上面的第五步已经将请求路径和方法建立了对应关系,通过/save就能找到对应的save方法。 - 执行save()
- 检测到有@ResponseBody直接将save()方法的返回值作为响应体返回给请求方
2.3、bean加载控制
- 入门案例中我们创建过一个
SpringMvcConfig
的配置类。 - 学习Spring的时候也创建过一个配置类
SpringConfig
。 - 这两个配置类都需要加载资源,那么它们分别都需要加载哪些内容?
项目目录结构:
config
目录存入的是配置类,写过的配置类有:
♠ ServletContainersInitConfig
♠ SpringConfig
♠ SpringMvcConfig
♠ JdbcConfig
♠ MybatisConfig
controller
目录存放的是SpringMVC的controller类service
目录存放的是service接口和实现类dao
目录存放的是dao/Mapper接口
controller、service和dao这些类都需要被容器管理成bean对象,那么到底是该让SpringMVC加载还是让Spring加载呢?
- SpringMVC加载其相关bean(表现层bean),也就是controller包下的类
- Spring控制的bean
♠ 业务bean(Service)
♠ 功能bean(DataSource,SqlSessionFactoryBean,MapperScannerConfigurer等)
清楚谁该管哪些bean以后,接下来要解决的问题是如何让Spring和SpringMVC分开加载各自的内容。
-
在SpringMVC的配置类
SpringMvcConfig
中使用注解@ComponentScan
,我们只需要将其扫描范围设置到controller即可,如//创建springmvc的配置文件,加载controller对应的bean @Configuration @ComponentScan("com.chuhe.controller") public class SpringMvcConfig { }
-
在Spring的配置类
SpringConfig
中使用注解@ComponentScan
,当时扫描的范围中其实是已经包含了controller,如:@Configuration @ComponentScan("com.chuhe") public class SpringConfig { }
Spring已经多把SpringMVC的controller类也给扫描到,如何避免Spring错误加载到SpringMVC的bean?
2.3.1、避免Spring错误加载SpringMVC的bean
针对上面的问题,解决方式就是:加载Spring控制的bean的时候排除掉SpringMVC控制的bean。
具体排除方法:
- 方式一:Spring加载的bean设定扫描范围为精准范围,例如service包、dao包等
- 方式二:Spring加载的bean设定扫描范围为com.itheima,排除掉controller包中的bean
- 方式三:不区分Spring与SpringMVC的环境,加载到同一个环境中[了解即可]
2.3.2、设置bean加载控制
-
方式一:Spring加载的bean设定扫描范围为精准范围
@Configuration @ComponentScan({"com.chuhe.service","chuhe.dao"}) public class SpringConfig { }
说明:
上述只是通过例子说明可以精确指定让Spring扫描对应的包结构,真正在做开发的时候,因为Dao最终是交给
MapperScannerConfigurer
对象来进行扫描处理的,我们只需要将其扫描到service包即可。 -
方式二:修改Spring配置类,设定扫描范围为com.itheima,排除掉controller包中的bean
@Configuration @ComponentScan(value="com.chuhe", excludeFilters=@ComponentScan.Filter( type = FilterType.ANNOTATION,//设置排除规则,这里是按注解排除 classes = Controller.class ) ) public class SpringConfig { }
-
excludeFilters
属性:设置扫描加载bean时,排除的过滤规则 -
type
属性:设置排除规则,当前使用按照bean定义时的注解类型进行排除♠
ANNOTATION
:按照注解排除
♠ASSIGNABLE_TYPE
:按照指定的类型过滤
♠ASPECTJ
:按照Aspectj表达式排除,基本上不会用
♠REGEX
:按照正则表达式排除
♠CUSTOM
:按照自定义规则排除大家只需要知道第一种ANNOTATION即可
-
classes
属性:设置排除的具体注解类,当前设置排除@Controller
定义的bean
即UserController.java
-
出现的问题
- Spring配置类扫描的包是
com.chuhe
。- SpringMVC的配置类,
SpringMvcConfig
上有一个@Configuration注解,也会被Spring扫描到。- SpringMvcConfig上又有一个@ComponentScan,把controller类又给扫描进来了。
- 所以如果不把@ComponentScan注释掉,Spring配置类将Controller排除,但是因为扫描到SpringMVC的配置类,又将其加载回来。
- 解决方案,也简单,把SpringMVC的配置类移出Spring配置类的扫描范围即可。比如放在其他目录下。
2.3.3、修改ServletContainersInitConfig加载Spring的配置类
修改ServletContainersInitConfig在tomcat服务器启动时加载Spring的配置类
//定义一个servlet容器启动配置类,在里面加载spring的配置
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
//加载SprivgMVC配置
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext ctx=new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfig.class);
return ctx;
}
//设置哪些请求归属springMVC处理
@Override
protected String[] getServletMappings() {
return new String[]{"/"};//所有请求归SpringMVC管理
}
//加载spring容器配置
@Override
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext ctx=new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
}
2.3.4、ServletContainersInitConfig的简化方式
对于上述的配置方式,Spring还提供了一种更简单的配置方式,可以不用再去创建AnnotationConfigWebApplicationContext
对象,不用手动register
对应的配置类,如下:
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
//加载spring容器配置
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
//加载SprivgMVC配置
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
//设置哪些请求归属springMVC处理
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
3、请求与响应
3.1、请求映射路径
3.1.1、环境准备
-
步骤一:创建一个Web的Maven项目
-
步骤二:pom.xml添加依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>springmvc_request</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </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>
-
步骤三:创建对应的配置类
package com.chuhe.config; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[0]; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringMvcConfig.class}; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } }
package com.chuhe.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan("com.chuhe.controller") public class SpringMvcConfig { }
-
步骤四:编写BookController和UserController
package com.chuhe.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller //请求路径前缀 @RequestMapping("/user") public class UserController { @RequestMapping("/save") @ResponseBody public String save(){ System.out.println("user save..."); return "{'module':'use Save'}"; } @RequestMapping("/delete") @ResponseBody public String delete(){ System.out.println("user delete..."); return "{'module':'user delete'}"; } }
package com.chuhe.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping("/book") public class BookController { @RequestMapping("/save") @ResponseBody public String save(){ System.out.println("book save..."); return "{'module':'book save'}"; } }
@RequestMapping
设置当前控制器方法请求访问路径,如果设置在类上统一设置当前控制器方法请求访问路径前缀。
- 步骤五:运行
浏览器访问:http://localhost/book/save
浏览器访问:http://localhost/user/save
3.2、请求参数
3.2.1、环境准备
-
步骤一:创建一个Maven项目
-
步骤二:添加依赖坐标
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>springmvc_request_param</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>80</port> <path>/</path> </configuration> </plugin> </plugins> </build> </project>
-
步骤三:创建对应的配置类
package com.chuhe.config; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class SevletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[0]; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringMvcConfig.class}; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } }
package com.chuhe.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan("com.chuhe.controller") public class SpringMvcConfig { }
-
步骤四:编写UserController
@Controller public class UserController { @RequestMapping("/commonParam") @ResponseBody public String commonParam(){ return "{'module':'user commonParam'}"; } }
-
步骤五:编写模型类,User和Address
public class Address { private String province; private String city; //setter...getter...略 }
public class User { private String name; private int age; //setter...getter...略 }
3.2.2、参数传递
3.2.2.1、GET发送单个参数
-
发送请求
-
接收参数
@Controller public class UserController { @RequestMapping("/commonParam") @ResponseBody public String commonParam(String name){ System.out.println("普通参数传递:"+name); return "{'module':'user commonParam'}"; } }
3.2.2.2、GET发送多个参数
-
请求参数
-
接收参数
@Controller public class UserController { @RequestMapping("/commonParam") @ResponseBody public String commonParam(String name,int age){ System.out.println("普通参数传递:"+name); System.out.println("普通参数传递:"+age); return "{'module':'user commonParam'}"; } }
3.2.2.3、GET请求中文乱码
-
发送请求
http://localhost:80/commonParam?name=锄禾&age=18
-
后台接收乱码
-
解决方法
Tomcat8.5以后的版本已经处理了中文乱码的问题,但是IDEA中的Tomcat插件目前只到Tomcat7,所以需要修改pom.xml来解决GET请求中文乱码问题。<build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>80</port><!--tomcat端口号--> <path>/</path><!--虚拟目录--> <uriEncoding>UTF-8</uriEncoding><!--访问路径编解码字符集--> </configuration> </plugin> </plugins> </build>
3.2.2.4、POST发送参数
- 发送请求
- 接收参数:
和GET一致,不用做任何修改
3.2.2.5、POST请求中文乱码
GET请求中文乱码的方案,解决不了POST请求中文乱码。
-
解决方案:配置过滤器
Ctrl+O,添加getServletFilters方法
package com.chuhe.config; import org.springframework.web.filter.CharacterEncodingFilter; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; import javax.servlet.Filter; public class SevletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[0]; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringMvcConfig.class}; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } //POST请求乱码处理 @Override protected Filter[] getServletFilters() { CharacterEncodingFilter filter=new CharacterEncodingFilter(); filter.setEncoding("UTF-8"); return new Filter[]{filter}; } }
3.2.3、五种类型参数传递
GET或POST来发送请求和数据,比较复杂的参数传递,常见的参数种类有:
- 普通参数
- POJO类型参数
- 嵌套POJO类型参数
- 数组类型参数
- 集合类型参数
3.2.3.1、普通参数
普通参数: url地址传参,地址参数名与形参变量名相同,定义形参即可接收参数。
-
请求参数与接收参数名称相同
-
请求参数与接收参数名称不一致:
解决方案:使用@RequestParam注解
请求参数
接收参数
@Controller public class UserController { @RequestMapping("/commonParam") @ResponseBody public String commonParam(@RequestParam("username")String name, int age){ System.out.println("普通参数传递:"+name); System.out.println("普通参数传递:"+age); return "{'module':'user commonParam'}"; } }
注意:写上@RequestParam注解框架就不需要自己去解析注入,能提升框架处理性能。
3.2.3.2、POJO数据类型
简单数据类型一般处理的是参数个数比较少的请求,如果参数比较多,那么后台接收参数的时候就比较复杂,这个时候可以考虑使用POJO数据类型。
-
POJO参数:请求参数名与形参对象属性名相同,定义POJO类型形参即可接收参数。
-
请求参数
-
接收参数
//POJO参数:请求参数与形参对象中的属性对应即可完成参数传递 @RequestMapping("/pojoParam") @ResponseBody public String pojoParam(User user){ System.out.println(user.getName()); System.out.println(user.getAge()); return "{'module':'user pojoParam'}"; }
注意:
- POJO参数接收,前端GET和POST发送请求数据的方式不变。
- 请求参数key的名称要和POJO中属性的名称一致,否则无法封装。
3.2.3.3、 嵌套POJO类型参数
嵌套POJO参数: 请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数。
-
请求参数:
-
处理参数:
@RequestMapping("/pojoParam") @ResponseBody public String pojoParam(User user){ System.out.println(user.getName()); System.out.println(user.getAge()); Address address = user.getAddress(); System.out.println(address.getProvince()); System.out.println(address.getCity()); return "{'module':'user pojoParam'}"; }
注意:
请求参数key的名称要和POJO中属性的名称一致,否则无法封装。
3.2.3.4、数组类型参数
数组参数: 请求参数名与形参对象属性名相同且请求参数为多个,定义数组类型即可接收参数
-
请求参数
-
接收参数
@RequestMapping("/arrParam") @ResponseBody public String arrParam(String[] likes){ System.out.println(Arrays.toString(likes)); return "{'module':'user arrParam'}"; }
3.2.3.5、集合类型参数
-
请求参数
-
接收参数
@RequestMapping("/listParam") @ResponseBody public String listParam( List<String> likes){ System.out.println(likes); return "{'module':'user listParam'}";
接收请求时,服务器报错信息如下:
错误的原因是:SpringMVC将List看做是一个POJO对象来处理,将其创建一个对象并准备把前端的数据封装到对象中,但是List是一个接口无法创建对象,所以报错。
解决方案是:使用@RequestParam
注解
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> likes){
System.out.println(likes);
return "{'module':'user listParam'}";
}
- 集合保存普通参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam绑定参数关系
- 对于简单数据类型使用数组会比集合更简单些。
知识点1:@RequestParam
名称 | @RequestParam |
---|---|
类型 | 形参注解 |
位置 | SpringMVC控制器方法形参定义前面 |
作用 | 绑定请求参数与处理器方法形参间的关系 |
相关参数 | required:是否为必传参数 defaultValue:参数默认值 |
3.3、响应json数据
现在比较流行的开发方式为异步调用。前后台以异步方式进行交换,传输的数据使用的是JSON。
对于JSON数据类型,我们常见的有三种:
- json普通数组:
["value1","value2","value3",...]
- json对象:
{key1:value1,key2:value2,...}
- json对象数组:
[{key1:value1,...},{key2:value2,...}]
3.3.1、JSON普通数组
-
步骤一:SpringMVC默认使用的是jackson来处理json的转换,所以需要在pom.xml添加jackson依赖。
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency>
-
步骤二:开启SpringMVC注解支持
在SpringMVC的配置类中开启SpringMVC的注解支持,这里面就包含了将JSON转换成对象的功能。@Configuration @ComponentScan("com.chuhe.controller") //开启json数据类型自动转换 @EnableWebMvc public class SpringMvcConfig { }
-
步骤四:请求参数
-
步骤四:接收参数
@RequestMapping("/listParamJson") @ResponseBody //使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据 public String listParamJson(@RequestBody List<String> likes){ System.out.println(likes); return "{'module':'list common for json param'}"; }
3.3.2、JSON对象数据
一、POJO对象数据
请求数据:
{
"name":"chuhe",
"age":15
}
- 请求数据
-
接收数据
@RequestMapping("/pojoParamJson") @ResponseBody public String pojoParamJson(@RequestBody User user){ System.out.println(user.getName()); System.out.println(user.getAge()); return "{'module':'pojo for json param'}"; }
二、嵌套POJO
请求参数
{
"name":"chuhe",
"age":18,
"address":{
"province":"江苏",
"city":"徐州"
}
}
3.3.3、JSON对象数组
- 请求参数
- 接收参数
@RequestMapping("/listPojoParamJson") @ResponseBody public String listPojoParamJson(@RequestBody List<User> list){ System.out.println(list); return "{'module':'list pojo for json param'}"; }
@RequestBody与@RequestParam区别
区别
- @RequestParam用于接收url地址传参,表单传参【application/x-www-form-urlencoded】 *
@RequestBody用于接收json数据【application/json】应用
- 后期开发中,发送json格式数据为主,@RequestBody应用较广
- 如果发送非json格式数据,选用@RequestParam接收请求参数
3.4、日期类型参数传递
-
请求参数
-
接收参数
@RequestMapping("/dateParam") @ResponseBody public String dateParam(Date d1, @DateTimeFormat(pattern="yyy-MM-dd")Date d2, @DateTimeFormat(pattern="yyyy年MM月dd日 HH:mm:ss")Date d3){ System.out.println(d1); System.out.println(d2); System.out.println(d3); return "{'module':'date param'}"; }
注意:SpringMVC的配置类把@EnableWebMvc当做标配配置上去,不要省略
3.5、响应
对于响应,主要就包含两部分内容:
- 响应页面
- 响应数据
- 文本数据
- json数据
3.5.1、环境准备
-
步骤一:创建一个Web的Maven项目。
-
步骤二:pom.xm添加依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>springmvc_response</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </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.2</version> <configuration> <port>80</port> <path>/</path> </configuration> </plugin> </plugins> </build> </project>
-
步骤三:创建对应的配置类
package com.chuhe.config; import org.springframework.web.filter.CharacterEncodingFilter; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; import javax.servlet.Filter; public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[0]; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringMvcConfig.class}; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } //POST请求乱码处理 @Override protected Filter[] getServletFilters() { CharacterEncodingFilter filter=new CharacterEncodingFilter(); filter.setEncoding("UTF-8"); return new Filter[]{filter}; } }
package com.chuhe.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @Configuration @ComponentScan("com.chuhe.controller") //开启json数据类型自动转换 @EnableWebMvc public class SpringMvcConfig { }
-
步骤四:编写模型类User
public class User { private String name; private int age; //getter...setter...toString省略 }
-
步骤五:webapp下创建page.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>Hello SpringMvc!</h1> </body> </html>
-
步骤六:编写UserController
@Controller public class UserController { }
3.5.2、响应页面
@Controller
public class UserController {
@RequestMapping("/toJumpPage")
//注意
//1.此处不能添加@ResponseBody,如果加了该注入,会直接将page.jsp当字符串返回前端
//2.方法需要返回String
public String toJumpPage(){
System.out.println("跳转页面");
return "page.jsp";
}
}
浏览器访问:
http://localhost/toJumpPage
3.5.3、响应文本数据
@Controller
public class UserController {
@RequestMapping("/toText")
//注意此处该注解就不能省略,如果省略了,会把response text当前页面名称去查找,如果没有回报404错误
@ResponseBody
public String toText(){
System.out.println("返回纯文本数据");
return "response text";
}
}
浏览器访问:http://localhost/toText
3.5.4、响应JSON数据
3.5.4.1、响应POJO对象
@Controller
public class UserController {
@RequestMapping("/toJsonPojo")
@ResponseBody
public User toJsonPojo(){
System.out.println("返回json对象数据");
User user=new User();
user.setName("锄禾");
user.setAge(18);
return user;
}
}
浏览器访问:
http://localhost/toJsonPojo
3.5.4.2、响应POJO集合对象
@Controller
public class UserController {
@RequestMapping("/toJsonList")
@ResponseBody
public List<User> toJsonList(){
System.out.println("返回json集合数据");
User user1=new User();
user1.setName("唐青枫");
user1.setAge(20);
User user2=new User();
user2.setName("曲无忆");
user2.setAge(19);
List<User> list=new ArrayList<User>();
list.add(user1);
list.add(user2);
return list;
}
}
浏览器访问:
http://localhost/toJsonList
4、Rest风格
REST(Representational State Transfer),表现形式状态转换,它是一种软件架构风格。
当我们想表示一个网络资源的时候,可以使用两种方式:
- 传统风格资源描述形式
http://localhost/user/getById?id=1
查询id为1的用户信息http://localhost/user/saveUser
保存用户信息
- REST风格描述形式
http://localhost/user/1
http://localhost/user
传统方式一般是一个请求url对应一种操作,这样做不仅麻烦,也不安全,因为会程序的人读取了你的请求url地址,就大概知道该url实现的是一个什么样的操作。
查看REST风格的描述,你会发现请求地址变的简单了,并且光看请求URL并不是很能猜出来该URL的具体功能。
一个相同的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
。在进行开发的过程中,大多是都是遵从REST风格来访问我们的后台服务,所以可以说以后都是基于RESTful来进行开发的。
4.1、新增POST
@Controller
public class UserController {
//新增
@RequestMapping(value = "/users",
method = RequestMethod.POST,
produces = "application/json;charset=utf-8")
@ResponseBody
public String save(@RequestBody User user){
System.out.println(user);
return"{'module':'user新增'}";
}
}
4.2、修改
@Controller
public class UserController {
//修改
@RequestMapping(value = "/users",
method = RequestMethod.PUT,
produces ="application/json;charset=utf-8")
@ResponseBody
public String update(@RequestBody User user){
System.out.println(user);
return"{'module':'user修改'}";
}
}
4.3、删除
@Controller
public class UserController {
//删除
@RequestMapping(value = "/users/{id}",
method=RequestMethod.DELETE,
produces = "application/json;charset=utf-8")
@ResponseBody
public String delete(@PathVariable Integer id){
System.out.println(id);
return"{'module':'user删除'}";
}
}
传递路径参数
前端发送请求的时候使用:http://localhost/users/1
,路径中的1
就是我们想要传递的参数。
后端获取参数,需要做如下修改:
- 修改@RequestMapping的value属性,将其中修改为
/users/{id}
,目的是和路径匹配 - 在方法的形参前添加@PathVariable注解
方法形参的名称和路径{}
中的值不一致
@Controller
public class UserController {
//删除
@RequestMapping(value = "/users/{id}",
method=RequestMethod.DELETE,
produces = "application/json;charset=utf-8")
@ResponseBody
public String delete(@PathVariable("id") Integer userId){
System.out.println(userId);
return"{'module':'user删除'}";
}
}
前端请求传两个参数
前端发送请求的时候使用:http://localhost/users/1/tom
,路径中的1
和tom
就是我们想要传递的两个参数。
@Controller
public class UserController {
//删除
@RequestMapping(value = "/users/{id}/{name}",
method=RequestMethod.DELETE,
produces = "application/json;charset=utf-8")
@ResponseBody
public String delete(@PathVariable Integer id,@PathVariable String name){
System.out.println(id);
System.out.println(name);
return"{'module':'user删除'}";
}
}
4.4、根据ID查询
@Controller
public class UserController {
//根据ID查询
@RequestMapping(value = "/users/{id}",
method = RequestMethod.GET,
produces = "application/json;charset=utf-8")
@ResponseBody
public String getById(@PathVariable Integer id){
System.out.println(id);
return "{'module':'user根据ID查询'}";
}
}
4.5、查询全部
@Controller
public class UserController {
//查询所有
@RequestMapping(value = "/users",
method = RequestMethod.GET,
produces = "application/json;charset=utf-8")
@ResponseBody
public String getAll(){
return "{'module':'user查询所有'}";
}
}
4.6、@RequestBody
、@RequestParam
、@PathVariable
,这三个注解之间的区别和应用
- 区别
- @RequestParam用于接收url地址传参或表单传参
- @RequestBody用于接收json数据
- @PathVariable用于接收路径参数,使用{参数名称}描述路径参数
- 应用
- 后期开发中,发送请求参数超过1个时,以json格式为主,@RequestBody应用较广
- 如果发送非json格式数据,选用@RequestParam接收请求参数
- 采用RESTful进行开发,当参数数量较少时,例如1个,可以采用@PathVariable接收请求路径变量,通常用于传递id值
4.7、RESTful快速开发
上述Rest风格的开发,存在三个问题:
-
问题1:每个方法的@RequestMapping注解中都定义了访问路径/users,重复性太高。
-
问题2:每个方法的@RequestMapping注解中都要使用method属性定义请求方式,重复性太高。
-
问题3:每个方法响应json都需要加上@ResponseBody注解,重复性太高。
//@Controller
//@ResponseBody
@RestController//相当于上面两句
@RequestMapping(value = "/users",produces = "application/json;charset=utf-8")
public class UserController {
//新增
//@RequestMapping(method = RequestMethod.POST)
@PostMapping
public String save(@RequestBody User user){
System.out.println(user);
return"{'module':'user新增'}";
}
//修改
//@RequestMapping(method = RequestMethod.PUT)
@PutMapping
public String update(@RequestBody User user){
System.out.println(user);
return"{'module':'user修改'}";
}
//删除
//@RequestMapping(value = "/{id}/{name}",method=RequestMethod.DELETE)
@DeleteMapping("/{id}/{name}")
public String delete(@PathVariable Integer id,@PathVariable String name){
System.out.println(id);
System.out.println(name);
return"{'module':'user删除'}";
}
//根据ID查询
//@RequestMapping(value = "/{id}",method = RequestMethod.GET)
@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
System.out.println(id);
return "{'module':'user根据ID查询'}";
}
//查询所有
//@RequestMapping(method = RequestMethod.GET)
@GetMapping
public String getAll(){
return "{'module':'user查询所有'}";
}
}
4.8、RESTful案例
4.8.1、环境准备
-
步骤一:创建一个Maven项目
-
步骤二:pom.xml添加依赖坐标
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>springmvc_rest_cast</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <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.2</version> <configuration> <port>80</port> <path>/</path> </configuration> </plugin> </plugins> </build> </project>
-
步骤三:创建对应的配置类
package com.chuhe.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @Configuration @ComponentScan("com.chuhe.controller") @EnableWebMvc public class SpringMvcConfig { }
package com.chuhe.config; import org.springframework.web.filter.CharacterEncodingFilter; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; import javax.servlet.Filter; public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[0]; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringMvcConfig.class}; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } //POST请求乱码 @Override protected Filter[] getServletFilters() { CharacterEncodingFilter filter=new CharacterEncodingFilter(); filter.setEncoding("UTF-8"); return new Filter[]{filter}; } }
-
步骤四:编写模型类Book
public class Book { private Integer id; private String type; private String name; private String description; //setter...getter...toString略 }
-
步骤五:编写BookController
//@Controller //@ResponseBody @RestController @RequestMapping(value = "/books",produces = "application/json;charset=utf-8") public class BookController { //新增 @PostMapping public String save(@RequestBody Book book){ System.out.println(book); return "{'module':'新增'}"; } //查询所有 @GetMapping public List<Book> getAll(){ List<Book> list=new ArrayList<Book>(); Book b1=new Book(); b1.setId(1); b1.setName("Java"); b1.setType("编程"); b1.setDescription("Java从入门到放弃"); Book b2=new Book(); b2.setId(2); b2.setName("Spring"); b2.setType("编程"); b2.setDescription("Spring从入门到放弃"); list.add(b1); list.add(b2); return list; } }
4.8.2、页面访问处理
-
步骤一:添加静态页面
-
步骤二:访问pages目录下的books.html
打开浏览器输入http://localhost/pages/books.html
报错原因:
服务器提示提示信息如下:
SpringMVC拦截了静态资源,根据/pages/books.html去controller找对应的方法,找不到所以会报404的错误。
SpringMVC为什么会拦截静态资源?
SpringMVC放行静态资源
SpringMVC需要将静态资源进行放行。
-
创建SpringMvcSupport配置文件
@Configuration public class SpringMvcSupport extends WebMvcConfigurationSupport { //设置静态资源访问过滤,当前类需要设置为配置类,并被扫描加载 @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { System.out.println("加载SpringMvcSupport..."); //当访问/pages/????时候,从/pages目录下查找内容 registry.addResourceHandler("/pages/**").addResourceLocations("/pages/"); registry.addResourceHandler("/js/**").addResourceLocations("/js/"); registry.addResourceHandler("/css/**").addResourceLocations("/css/"); registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/"); } }
-
该配置类是在config目录下,SpringMVC扫描的是controller包,所以该配置类还未生效,要想生效需要将SpringMvcConfig配置类进行修改。
@Configuration @ComponentScan({"com.chuhe.controller","com.chuhe.config"}) //@ComponentScan("com.chuhe")//或者增加扫描范围 @EnableWebMvc public class SpringMvcConfig { }
-
步骤三:修改books.html
<!DOCTYPE html> <html> <head> <!-- 页面meta --> <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: {},//分页模型数据,暂时弃用 }, //钩子函数,VUE对象初始化完成后自动执行 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>
5、SSM整合
5.1、流程分析
- 步骤一:创建Maven工程
- 步骤二:pom.xml添加SSM依赖坐标
- 步骤三:编写Web项目的入口配置类,实现
AbstractAnnotationConfigDispatcherServletInitalizer
重写以下方法:
♠getRootConfigClasses()
:返回Spring的配置类->需要SpringConfig
配置类
♠getServletConfigClasses()
:返回SpringMVC的配置类->需要SpringMvcConfig
配置类
♠getServletMappings()
: 设置SpringMVC请求拦截路径规则
♠getServletFilters()
:设置过滤器,解决POST请求中文乱码问题 - 步骤四: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
✈功能模块
♠ 创建数据库表
♠ 根据数据库表创建对应的模型类
♠ 通过Dao层完成数据库表的增删改查(接口+自动代理)
♠ 编写Service层[Service接口+实现类]
✱@Service
✱@Transactional
✱整合Junit对业务层进行单元测试
★@RunWith
★@ContextConfiguration
★@Test
♠ 编写Controller层
✱ 接收请求@RequestMapping
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
✱接收数据 简单、POJO、嵌套POJO、集合、数组、JSON数据类型
★@RequestParam
★@PathVariable
★@RequestBody
✱ 转发业务层
★@Autowired
✱ 响应结果
★@ResponseBody
5.2、整合配置
✈步骤一: 创建Maven项目
✈步骤二:添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.chuhe</groupId>
<artifactId>springmvc_ssm</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<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.5</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.46</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</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.2</version>
<configuration>
<port>80</port>
<path>/</path>
<uriEncoding>utf-8</uriEncoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
✈步骤三: 创建项目包结构
- config目录存放的是相关的配置类
- controller编写的是Controller类
- dao存放的是Dao接口,因为使用的是Mapper接口代理方式,所以没有实现类包
- service存的是Service接口,impl存放的是Service实现类
- resources:存入的是配置文件,如Jdbc.properties
- webapp:目录可以存放静态资源
- test/java:存放的是测试类
✈步骤四: 创建SpringConfig配置类
@Configuration//标识该类为配置类
@ComponentScan("com.chuhe.service")//扫描Service所在的包
@EnableTransactionManagement//在Service层要管理事务
@PropertySource("classpath:jdbc.properties")//读取外部的properties配置文件
@Import({JdbcConfig.class,MybatisConfig.class})//整合Mybatis需要引入Mybatis相关配置类
public class SpringConfig {
}
✈步骤五:创建JdbcConfig配置类
package com.chuhe.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
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;
}
}
✈步骤六:创建MybatisConfig配置类
package com.chuhe.config;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean factoryBean=new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setTypeAliasesPackage("com.chuhe.domain");
return factoryBean;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc=new MapperScannerConfigurer();
msc.setBasePackage("com.chuhe.dao");
return msc;
}
}
✈步骤七:创建jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm_db
jdbc.username=root
jdbc.password=1234
✈步骤八: 创建SpringMVC配置类
@Configuration
@ComponentScan("com.chuhe.controller")
@EnableWebMvc
public class SpringMvcConfig {
}
✈步骤九: 创建Web项目入口配置类
package com.chuhe.config;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import javax.servlet.Filter;
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
//加载Spring配置类
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
//加载SpringMVC配置类
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
//设置SpringMVC请求地址拦截规则
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
//设置POST请求编码
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter=new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
5.3、功能模块开发
✈步骤一:创建数据库及表
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+');
✈步骤二: 编写模型类
public class Book {
private Integer id;
private String type;
private String name;
private String description;
//getter...setter...toString省略
}
✈步骤三:编写Dao接口
package com.chuhe.dao;
import com.chuhe.domain.Book;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
public interface BookDao {
//新增
@Insert("Insert into tbl_book(id,type,name,description)values(null,#{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);
//根据id查询
@Select("select * from tbl_book where id=#{id}")
public Book getById(Integer id);
//查询全部
@Select("select * from tbl_book")
public List<Book> getAll();
}
✈步骤四:编写Service接口和实现类
package com.chuhe.service;
import com.chuhe.domain.Book;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Transactional
public interface BookService {
/**
* 新增
* @param book
* @return
*/
public boolean save(Book book);
/**
* 修改
* @param book
* @return
*/
public boolean update(Book book);
/**
* 根据id删除
* @param id
* @return
*/
public boolean delete(Integer id);
/**
* 根据id查询
* @param id
* @return
*/
public Book getById(Integer id);
/**
* 查询全部
* @return
*/
public List<Book> getAll();
}
package com.chuhe.service.impl;
import com.chuhe.dao.BookDao;
import com.chuhe.domain.Book;
import com.chuhe.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
@Override
public boolean save(Book book) {
bookDao.save(book);
return true;
}
@Override
public boolean update(Book book) {
bookDao.update(book);
return true;
}
@Override
public boolean delete(Integer id) {
bookDao.delete(id);
return true;
}
@Override
public Book getById(Integer id) {
return bookDao.getById(id);
}
@Override
public List<Book> getAll() {
return bookDao.getAll();
}
}
说明:
bookDao在Service中注入的会提示一个红线提示,为什么呢?
BookDao是一个接口,没有实现类,接口是不能创建对象的,所以最终注入的应该是代理对象 。
代理对象是由Spring的IOC容器来创建管理的 。
IOC容器又是在Web服务器启动的时候才会创建 。
IDEA在检测依赖关系的时候,没有找到适合的类注入,所以会提示错误提示 。
但是程序运行的时候,代理对象就会被创建,框架会使用DI进行注入,所以程序运行无影响。
如何解决上述问题?
- 可以不用理会,因为运行是正常的
- 设置错误提示级别
✈步骤五:编写Controller类
package com.chuhe.controller;
import com.chuhe.domain.Book;
import com.chuhe.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.*;
import java.util.List;
//@Configuration
//@ResponseBody
@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);
}
//根据id查询
@GetMapping("/{id}")
public Book getById(@PathVariable Integer id){
return bookService.getById(id);
}
//查询全部
@GetMapping
public List<Book> getAll(){
System.out.println("BookController running...");
return bookService.getAll();
}
}
5.4、单元测试
✈步骤一:新建测试类
✈步骤二:注入Service类
✈步骤三:编写测试方法
package com.chuhe;
import com.chuhe.config.SpringConfig;
import com.chuhe.domain.Book;
import com.chuhe.service.BookService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.swing.*;
@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);
}
}
6、统一结果封装
6.1、表现层与前端数据传输协议定义
随着业务的增长,我们需要返回的数据类型会越来越多。将返回结果的数据进行统一,具体如何来做,大体的思路为:
- 为了封装返回的结果数据:
创建结果模型类,封装数据到data属性中
。 - 为了封装返回的数据是何种操作及是否操作成功:
封装操作结果到code属性中
。 - 操作失败后为了封装返回的错误信息:
封装特殊消息到message(msg)属性中
。
可以设置统一数据返回结果类:
public class Result{
private Object data;
private Integer code;
private String msg;
}
注意: Result类名及类中的字段并不是固定的,可以根据需要自行增减提供若干个构造方法,方便操作。
6.2、表现层与前端数据传输协议实现
对于结果封装,应该是在表现层进行处理,所以把结果类放在controller包,当然也可以放在domain包。
✈步骤一: 创建Result类
package com.chuhe.controller;
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(Object data, Integer code, String msg) {
this.data = data;
this.code = code;
this.msg = msg;
}
//setter...getter...省略
✈步骤二:定义返回码Code类
package com.chuhe.controller;
//定义状态码
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等。
✈步骤三:修改Controller类的返回值
package com.chuhe.controller;
import com.chuhe.domain.Book;
import com.chuhe.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
//@Configuration
//@ResponseBody
@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);
}
//根据id查询
@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> list=bookService.getAll();
String msg=list!=null?"查询成功":"数据查询失败,请重试";
Integer code=list!=null?Code.GET_OK:Code.GET_ERR;
return new Result(code,list,msg);
}
}
至此,我们的返回结果就已经能以一种统一的格式返回给前端。前端根据返回的结果,先从中获取code
,根据code判断,如果成功则取data
属性的值,如果失败,则取msg
中的值做提示。
7、统一异常处理
7.1、异常的种类及出现异常原因
- 框架内部抛出的异常:因使用不合规导致。
- 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)。
- 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)。
- 表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)。
- 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)。
✈各个层级均出现异常,异常处理代码书写在哪一层?
- 所有的异常均抛出到表现层进行处理
✈异常的种类很多,表现层如何将所有的异常都处理到呢?
- 异常分类
✈表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决?
- AOP
7.2、异常处理器的使用
7.2.1、环境准备
✈步骤一: 创建一个Web的Maven项目
✈步骤二: pom.xml添加SSM整合所需jar包
✈步骤三: 创建对应的配置类
✈步骤四: 编写Controller、Service接口、Service实现类、Dao接口和模型类
✈步骤五: resources下提供jdbc.properties配置文件
7.2.2、使用步骤
✈步骤一:创建异常处理器类
package com.chuhe.controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
//@ControllerAdvice
@RestControllerAdvice//声明该类用来做异常处理,Rest风格开发用这个
public class ProjectExceptionAdvice {
//声明这个函数用来处理异常,括号内是要处理的异常类型
@ExceptionHandler(Exception.class)//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
public Result doException(Exception ex){
System.out.println("出异常了.........");
return new Result(666,null,"程序异常");
}
}
注意:要确保SpringMvcConfig能够扫描到异常处理器类
- SpringMvcConfig.java下
@ComponentScan("com.chuhe.controller")
可以扫描到该类。
✈步骤二:让程序抛出异常
知识点:@RestControllerAdvice
名称 | @RestControllerAdvice |
---|---|
类型 | 类注解 |
位置 | Rest风格开发的控制器增强类定义上方 |
作用 | 为Rest风格开发的控制器类做增强 |
说明: 此注解自带@ResponseBody注解与@Component注解,具备对应的功能 。
知识点:@ExceptionHandler
名称 | @ExceptionHandler |
---|---|
类型 | 方法注解 |
位置 | 专用于异常处理的控制器方法上方 |
作用 | 设置指定异常的处理方案,功能等同于控制器方法, 出现异常后终止原始控制器执行,并转入当前方法执行 |
说明: 此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常。
7.3、项目异常处理方案
7.3.1、异常分类
异常处理器我们已经能够使用了,那么在项目中该如何来处理异常呢?
因为异常的种类有很多,如果每一个异常都对应一个@ExceptionHandler,那得写多少个方法来处理各自的异常,所以我们在处理异常之前,需要对异常进行一个分类:
✈业务异常(BusinessException)
-
规范的用户行为产生的异常
✱如,用户在页面输入内容的时候未按照指定格式进行数据填写,如在年龄框输入的是字符串。 -
不规范的用户行为操作产生的异常
✱如,用户故意传递错误数据
✈系统异常(SystemException)
- 项目运行过程中可预计但无法避免的异常
✱比如数据库或服务器宕机
✈其他异常(Exception)
- 编程人员未预期到的异常,如:用到的文件不存在
将异常分类以后,针对不同类型的异常,要提供具体的解决方案。
7.3.2、异常解决方案
✈业务异常(BusinessException)
- 发送对应消息传递给用户,提醒规范操作
✱大家常见的就是提示用户名已存在或密码格式不正确等。
✈系统异常(SystemException)
- 发送固定消息传递给用户,安抚用户
✱系统繁忙,请稍后再试
✱系统正在维护升级,请稍后再试
✱系统出问题,请联系系统管理员等
- 发送特定消息给运维人员,提醒维护
✱可以发送短信、邮箱或者是公司内部通信软件 - 记录日志
✱发消息和记录日志对用户来说是不可见的,属于后台程序
✈ 其他异常(Exception)
- 发送固定消息传递给用户,安抚用户
- 发送特定消息给编程人员,提醒维护(纳入预期范围内)
✱一般是程序没有考虑全,比如未做非空校验等 - 记录日志
7.3.3、异常解决方案的具体实现
✈步骤一:自定义异常类
- exception包下创建
SystemException
类package com.chuhe.exception; //自定义系统异常处理器 //让自定义异常类继承`RuntimeException`的好处是,后期在抛出这两个异常的时候,就不用在try...catch...或throws了 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; } }
- exception包下创建
BusinessException
类package com.chuhe.exception; //自定义系统异常处理器 //让自定义异常类继承`RuntimeException`的好处是,后期在抛出这两个异常的时候,就不用在try...catch...或throws了 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
属性的原因是为了更好的区分异常是来自哪个业务的
✈步骤二:将需要处理的异常抛出
假如在BookServiceImpl 类中,getById方法中,接收到前端发过来的数据,如果数据不符合要求就可以抛异常。
package com.chuhe.service.impl;
import com.chuhe.controller.Code;
import com.chuhe.dao.BookDao;
import com.chuhe.domain.Book;
import com.chuhe.exception.BusinessException;
import com.chuhe.exception.SystemException;
import com.chuhe.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
@Override
public Book getById(Integer id) {
//模拟业务异常,包装成自定义异常
if (id == 1) {
throw new BusinessException(Code.BUSINESS_ERR, "id不允许为1");
}
//模拟系统异常,将可能出现的异常进行包装,转换成自定义异常
try {
int i=1/0;
} catch (Exception e) {
throw new SystemException(Code.SYSTEM_TIMEOUT_ERR,"系统超时,请稍后再试");
}
return bookDao.getById(id);
}
//其它方法略... ...
}
具体的包装方式有:
- 方式一:
try{}catch(){}
在catch中重新throw我们自定义异常即可。- 方式二:直接throw自定义异常即可
BUSINESS_ERR
、SYSTEM_TIMEOUT_ERR
这些异常代码都是在Code类中定义好的。
✈步骤三:处理器类中处理异常
package com.chuhe.controller;
import com.chuhe.exception.BusinessException;
import com.chuhe.exception.SystemException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
//@ControllerAdvice
@RestControllerAdvice//声明该类用来做异常处理,Rest风格开发用这个
public class ProjectExceptionAdvice {
//处理系统异常
//声明这个函数用来处理异常,括号内是要处理的异常类型
@ExceptionHandler(SystemException.class)//处理SystemException异常
public Result doSystemException(SystemException ex){
//记录日志
//发送消息给运维
//发送邮件给开发人员,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());
}
//除了自定义的异常处理,保留对Exception类型的异常处理,用于处理非预期的异常
@ExceptionHandler(Exception.class)
public Result doException(Exception ex){
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
return new Result(Code.SYSTEM_UNKNOW_ERR,null,"系统繁忙,请稍后再试");
}
}
7.3.4、小结
对于异常,不管后台哪一层抛出异常,都会以我们与前端约定好的方式进行返回,前端只需要把信息获取到,根据返回的正确与否来展示不同的内容即可。
项目中的异常处理方式为:
8、拦截器
-
浏览器发送一个请求会先到Tomcat的web服务器。
-
Tomcat服务器接收到请求以后,会去判断请求的是静态资源还是动态资源。
-
如果是静态资源,会直接到Tomcat的项目部署目录下去直接访问。
-
如果是动态资源,就需要交给项目的后台代码进行处理。
-
在找到具体的方法之前,我们可以去配置过滤器(可以配置多个),按照顺序进行执行。
-
然后进入到到中央处理器(SpringMVC中的内容),SpringMVC会根据配置的规则进行拦截。
-
如果满足规则,则进行处理,找到其对应的controller类中的方法进行执行,完成后返回结果。
-
如果不满足规则,则不进行处理。
-
这个时候,如果我们需要在每个Controller方法执行的前后添加业务,这个就是拦截器要做的事。
8.1、拦截器概念
✈拦截器(Interceptor
):是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行。
✈作用:
- 在指定的方法调用前后执行预先设定的代码
- 阻止原始方法的执行
- 总结:拦截器就是用来做增强
✈拦截器和过滤器之间的区别是什么?
- 归属不同:Filter属于
Servlet
技术,Interceptor
属于SpringMVC
技术。 - 拦截内容不同:
Filter
对所有访问进行增强,Interceptor
仅针对SpringMVC
的访问进行增强。
8.2、拦截器入门
8.2.1、拦截器开发
✈步骤一:创建拦截器类
让类实现HandlerInterceptor接口,重写接口中的三个方法。
package com.chuhe.controller.interceptor;
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
//定义拦截器类,实现HandlerInterceptor接口
//注意当前类必须受Spring容器控制
public class ProjectInterceptor implements HandlerInterceptor {
//原始方法调用前执行的内容
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...");
return false;//false为拦截,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容器扫描到。
- preHandle方法返回true时,拦截器不拦截
- preHandle方法返回false时,拦截器会拦截,原始方法会被拦截
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object >handler) throws Exception { System.out.println("preHandle..."); return true; }
preHandle
方法:该方法在控制器的处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回> false 表示中断后续操作。postHandle
方法:该方法在控制器的处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步的修改。afterCompletion
方法:该方法在控制器的处理请求方法执行完成后执行,即视图渲染结束后执行,可以通过此方法实现一些资源清理、记录日志信息等工作。
✈步骤二:配置拦截器类
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
//SpringMvc放行态资源
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
//配置拦截器
//"/books/"最后面的“/”不能省略
registry.addInterceptor(projectInterceptor).addPathPatterns("/books/");
}
}
✈步骤三:SpringMVC添加SpringMvcSupport包扫描
@Configuration
@ComponentScan({"com.chuhe.controller","com.chuhe.config"})
@EnableWebMvc
public class SpringMvcConfig {
}
✈步骤四:运行测试
- preHandle方法返回true时,拦截器不拦截
- preHandle方法返回false时,拦截器会拦截,原始方法会被拦截
✈步骤五:修改拦截器拦截规则
- 访问
http://localhost/books/
,拦截器生效,并可根据preHandle
方法返回值决定是否拦截。 - 访问
http://localhost/books/1
,发现拦截器并未触发,无论preHandle
方法返回何值。 - 原因是拦截器的
addPathPatterns
方法配置的拦截路径是/books/
,我们现在发送的是/books/1
,所以没有匹配上,因此没有拦截,拦截器就不会执行。
修改拦截器拦截规则:
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
//SpringMvc放行静态资源
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
//配置拦截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
}
}
再次访问http://localhost/books/1
,已成功触发拦截器。
踩过的坑:
SpringMvcSupport
类中addInterceptors
方法@Override protected void addInterceptors(InterceptorRegistry registry) { //配置拦截器 registry.addInterceptor(projectInterceptor).addPathPatterns("/books"); }
- 配置成
/books
,访问http://localhost/books/
无法触发拦截器,但访问http://localhost/books
可触发。- 如果要使
http://localhost/books/
和http://localhost/books
均可触发拦截器,可配置成addPathPatterns("/books","/books/")
- 如要使
http://localhost/books/1
也可以触发拦截器,可配置成("/books","/books/*")
,但是http://localhost/books/1/
不会触发拦截器。解决办法:配置成("/books","/books/*","/books/*/")
- 如果不加
/books/*/
,则http://localhost/books/1/
可绕过拦截器,正常执行执行对应的业务处理。
✈步骤六:SpringMvcConfig中配置拦截器
@Configuration
@ComponentScan("com.chuhe.controller")//不需要再扫描"com.chuhe.config"
@EnableWebMvc
//继承WebMvcConfigurer
public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//配置SPringMvc放行静态资源
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//配置拦截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*","/books/*/");
}
}
注意:SpringMvcConfig中配置了拦截器,就不用再写
SpringMvcSupport
类了。
8.3、拦截器的执行流程
8.4、拦截器参数
8.4.1、前置处理方法
原始方法之前运行preHandle
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("preHandle");
return true;
}
- request:请求对象
- response:响应对象
- handler:被调用的处理器对象,本质上是一个方法对象,对反射中的Method对象进行了再包装。
✈使用request对象可以获取请求数据中的内容,如获取请求头的Content-Type
。
✈使用handler参数,可以获取方法的相关信息。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String host = request.getHeader("Host");//获取请求host
System.out.println(host);
HandlerMethod hm=(HandlerMethod)handler;
System.out.println(hm.getMethod().getName());//获取的方法名,比如save
return false;
}
- 这三个方法中,最常用的是preHandle。
- 在这个方法中可以通过返回值来决定是否要进行放行。
- 可以把业务逻辑放在该方法中,如果满足业务则返回true放行,不满足则返回false拦截。
8.4.2、后置处理方法
原始方法运行后运行,如果原始方法被拦截,则不执行
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
前三个参数和上面的是一致的。
✈modelAndView
:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整。现在都是返回json数据,所以该参数的使用率不高。
8.4.3、完成处理方法
拦截器最后执行的方法,无论原始方法是否执行
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
System.out.println("afterCompletion");
}
前三个参数与上面的是一致的。
✈ex
:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理 。因为现在已经有全局异常处理器类,所以该参数的使用率也不高。
8.5、拦截器链配置
8.5.1、配置多个拦截器
✈步骤一:创建拦截器类
@Component
//定义拦截器类,实现HandlerInterceptor接口
//注意当前类必须受Spring容器控制
public class ProjectInterceptor2 implements HandlerInterceptor {
//原始方法调用前执行的内容
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle222...");
return true;
}
//原始方法调用后执行的内容
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle2...");
}
//原始方法调用完成后执行的内容
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion2...");
}
}
✈步骤二:配置拦截器类
@Configuration
@ComponentScan("com.chuhe.controller")//不需要再扫描"com.chuhe.config"
@EnableWebMvc
//继承WebMvcConfigurer
public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired
private ProjectInterceptor projectInterceptor;
@Autowired
private ProjectInterceptor2 projectInterceptor2;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//配置静态资源
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//配置拦截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*","/books/*/");
registry.addInterceptor(projectInterceptor2).addPathPatterns("/books","/books/*","/books/*/");
}
}
注意:
- 拦截器执行的顺序是和配置顺序有关。
- 当配置多个拦截器时,形成拦截器链。
- 拦截器链的运行顺序参照拦截器添加顺序为准
- 当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行。
- 当拦截器运行中断,仅运行配置在前面的拦截器的afterCompletion操作。
—————————————————————————
✈步骤一:
✈步骤二:
✈步骤三:
✈步骤四:
✈步骤五:
✈步骤六:
✈ 一、XXXXXXXXX
- 步骤一:
✱XXXXXXXXXXX
✱XXXXXXXXXXX
★XXXXXXXXXXX
♠ XXXXXXXXXXXX
————————————————————————— - 步骤一:
- 步骤二:
- 步骤三:
- 步骤四:
- 步骤五:
- 步骤六:
—————————————————————————
全角空格
♠ 黑桃
★ 五角星
✱ 六芒星
✈ 飞机
—————————————————————————
✈步骤一:
✈步骤二:
✈步骤三:
✈步骤四:
✈步骤五:
✈步骤六: