SpringMVC简介
- 浏览器发送请求,给后端服务器时,Servlet只能接收、产生响应数据;并不能数据处理
- 将Servlet拆分成3层架构
web:页面数据收集、产生页面 service:数据处理 dao:数据持久化(存储数据)
这种开发模式,一种servlet只能处理一个请求
- MVC模式 将servlet模式又拆分成了3块儿 浏览器请求时,将请求发给控制器,由控制器调用service层,再由service业务层调用dao数据层、得到数据后,组织出最终展示的数据模型,并将页面抽取出来,由页面和数据模型一起配合 = 最终页面;最终反馈给用户。
View:现在基本上用异步调用方式 后端服务器整合后,生成Model对象;但是Model本质是一个java对象,是不能返回给页面的。
将Model对象调整为JSON格式,这样就能和前端页面进行数据交互。
SpringMVC用来:负责controller对应的功能开发,并将操作完的数据转换为JSON格式,交给前端页面
SpringMVC入门案例
- 创建web工程(Maven结构)
- 导入坐标(还有tomcat插件)
- 定义处理请求的功能类(UserController)
//被Spring管理的bean对象
@Controller
public class UserController{
//使用注解处理请求、并设置名称;
//后续url通过.../save就可以请求这个servlet
@RequestMapping("/save")
public void save(){
System.out.prinln("user save...")
}
}
- Spring配置类 SpringMvcConfig
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig{
}
- MVC配置类(启动服务器时,自动加载MVC) ServletContainersInitonfig
public class ServletConfig extends AbstractDispatcherServletInitializer{
//继承他,需要实现3个抽象方法
protected WebApplicationContext createServletApplicationContext(){
//加载SpringMVC容器对象
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);//将配置注册到容器中
return ctx
}
protected String[] getServletMappings(){
//哪些请求需要MVC处理
return new String[]{"/"};
//这句话表示:所有的请求都归MVC处理
}
protected WebApplicationContext createRootApplicationContext(){
//加载 除了Spring配置容器之外的 对象
return null;暂时不用
}
}
- 创建TomCat服务器
- 补充
控制器中的MVC方法不会空返回,都会返回JSON数据:键值对形式
@Controller
public class UserController{
@RequestMapping("/save")
@ResponseBody
public String save(){
return "{'info':'SpringMvc'}"
}
}
@ResponseBody:告诉MVC,save中返回的数据就是响应体、不是页面!否则会404
案例流程分析
-
整体逻辑分析
-
执行ServletContainersInitConfig类,初始化web容器,并创建对象 SpringMvc对象最终是一个WebApplicationContext对象,这个对象会被加载到ServletContext中
- 加载SpringMvcConfig并通过@ComponentScan执行对应的bean
3. 加载UserController,每个@RequestMapping的名称对应一个具体方法
4. 执行getServletMappings方法,全部由SpringMVC来处理。
设定所有进入tomcat拦截的请求,全部由SpringMVC来处理。
- 单次请求过程分析
我们定义过@RequestMapping("/save"),就由对应的save()方法执行...
SpringMVC简介
- 因为功能不同,如何避免Spring错误 加载到 SpringMVC的bean--加载Spring控制的bean的时候排除掉SpringMVC控制的bean?
解决方案例子
SpringConfig.java;Spring配置类
@Configuration
//@ComponentScan({"com.itheima.service"},{"com.itheima.dao"})
精准扫描包
设定排除某些包,个过滤器一样
@ComponentScan(value = "com.itheima",
(excludeFilters =@ComponentScan.Filter)
)
@ComponentScan(value = "com.itheima",
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Controller.class
//根据注解过滤,书写要过滤的注解类型
))
SpringMVCConfig.java
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig extends AbstractDispathcerServletInitializer{
protected WebApplicationContext createServletApplicationContext(){
//SpringMvc环境,加载SpringMvcConfig
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfig.class);
return ctx;
}
protected WebApplicationContext createRootApplicationContext(){
//Spring环境,加载SpringConfig
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
protected String[] getServletMappings(){
return new String[]{"/"};
}
}
测试类App
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
ctx.getBean(UserController.class)
补充
通过继承extend AbstractAnnotationConfigDispatchServletInitializer,不再是extends AbstractDispathcerServletInitializer;
然后实现这个接口的3个方法\
protected Class<?>[] getRootConfigClasses(){
return new Class[]{SpringConfig.class};
}
protected Class<?>[] getServletConfigClasses(){
return new Class[]{SpringMvcConfig.class};
}
protected String[] getServletMappings(){
return new String[]{"/"};
}
PostMan介绍
设置请求映射路径
我有两个Controller类,里面同时存有一个/save路径已经save()方法。
如果直接执行的话,是会报错的。
多人开发时,每个人设置不同的请求路径,冲突问题如何解决?---设置模块名 作为 请求路径前缀
@RequestMapping("/user/save")---@RequestMapping("/save")写上了/user前缀
但是一旦需要表名前缀的方法过多,就会造成页面的混乱。
通过在最上面书写@RequestMapping("/user"),后面用到后缀的方法可以省略/user
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(){
System.out.println("User Save...");
//返回一个JSON对象
return "{'module':'user save'}";
}
@RequestMapping("/delete")
@ResponseBody
public String delete(){
System.out.println("User Delete...");
return "{'module':'user delete'}";
}
}
Get和Post接收参数
- Get请求
通过 ?参数='XX' 来设置参数值 多个参数是: 参数=XX&参数=xx
@Controller
public class UserController {
@RequestMapping("/commonParam")
@ResponseBody
//通过形参,接收请求过来的参数
public String commonParam(String name,int age){
System.out.println("普通参数传递:name ==》"+name );
return "{'module':'common Param'}";
}
}
2. Post请求
Post请求在Body中编辑
乱码的处理
POST请求使用过滤器实现乱码 ServletContainersInitConfig.java
通过Alt+Insert,弹出:并选中
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
GET请求解决乱码
@RequestMapping(value = "/commonParam",produces = "application/json;charset=utf-8")
@Controller
public class UserController {
@RequestMapping(value = "/commonParam",produces = "application/json;charset=utf-8")
@ResponseBody
public String commonParam(String name,int age){
System.out.println("普通参数传递:name ==》"+name + '\t'+ age);
return "{'module':'common Param'}";
}
}
5种类型参数传递
- 如果我再postman中从参数名:
name,而UserController中接收参数的名字是:username,这样是会报错的。\
我们要求发送和接收的参数名相同,这样才能映射进去;但是通过具体指定可以避免
@RequestParam("name") String userName
public String commonParam(@RequestParam("name") String userName, @RequestParam(age) int userAge){
- 但是如果参数过多,难道我们要写这么多注解吗?会很混乱的,这个时候,我们通过实体类来接收参数。
//用实体类作为形参,来接收参数
@RequestMapping("/pojoContainPojoParam")
@ResponseBody
public String pojoContainPojoParam(User user){
System.out.println("User==>"+user);
return "{'module':'user'}";
}
- 如果传递的参数是一个引用类型怎么办?只需要在实体类中定义即可,控制层代码仍然用实体类接收
- 拿数组接收传递过来的参数: 命名为相同的keys即可:接收的参数和传递的参数命名要相同
- 集合里面接收参数 需要写上@RequestParam,并且名称也要前后匹配上。
JSON格式传参【最常用】
前提:引入json坐标
PostMan发送json数据
需要在SpringMvcConfig中开启JSON-->对象 的功能,不然MVC不认识JSON。
- json数组
Postman:
["game","music","travel"]
因为JSON的数据,postman是在Body中书写的,所以接收数据的时候,不再是使用@RequestParam了,而是使用@RequestBody
// 集合参数:json格式
@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对象(POJO)
// 集合参数:json格式--POJO类
@RequestMapping("/pojoParamForJson")
@ResponseBody
public String listParamForJson(@RequestBody User user){
System.out.println("list common(json)参数传递 list==》"+user);
return "{module:'list common for json param'}";
}
- json数组(POJO)
// 集合参数:json格式--集合中存储 POJO类
@RequestMapping("/listPojoParamForJson")
@ResponseBody
public String listPojoParamForJson(@RequestBody List<User> user){
System.out.println("list common(json)参数传递 list==》"+user);
return "{module:'list common for json param'}";
}
补充:
日期类型参数传递
- PostMan
- 控制层
//日期参数
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date){
System.out.println("参数传递 date=》"+date);
return "{'module':'data param'}";
}
上述这种默认方式,参数直接写成字符串型,接收时,date会将字符串型变成date型
如果用-中划线还可以吗?默认是不行的
因为第一种方式是标准格式,而使用-中划线,不是标准格式。需要指定标准格式:
@DateTimeFormat(pattern="yyyy-MM-dd")
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date1,@DateTimeFormat(pattern="yyyy-MM-dd")Date date2){
System.out.println("参数传递 date1=》"+date1);
System.out.println("参数传递 date2(yyyy-MM-dd)=》"+date2);
return "{'module':'data param'}";
}
如果带上具体时间呢?
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
//日期参数
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(
Date date1,
@DateTimeFormat(pattern="yyyy-MM-dd")Date date2,
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")Date date3
)
{
System.out.println("参数传递 date1=》"+date1);
System.out.println("参数传递 date2(yyyy-MM-dd)=》"+date2);
System.out.println("参数传递 date3(yyyy-MM-dd HH:mm:ss)=》"+date3);
return "{'module':'data param'}";
}
响应
@ResponseBody:将对象数据转为JSON数据,具体由类转换器来实现操作
- 响应页面 将@ResponseBody注释,表明:页面上不会显示该方法中的内容,需要我们手写/toJumpPage路径才行。
@RequestMapping(value = "/toJumpPage",produces = "application/json;charset=utf-8")
//@ResponseBody
public String toJumpPage(){
System.out.println("跳转页面");
return "page.jsp";
}
- 响应文本数据 必须要写@ResponseBody,不然MVC会将他认作是 页面
@RequestMapping("/toText")
@ResponseBody
public String toText(){
System.out.println("返回纯文本数据");
return "response text";
}
- 返回POJO对象 转JSON对象,必须写@ResponseBody
@RequestMapping("/toJsonPOJO")
@ResponseBody
public User toJsonPOJO(){
System.out.println("返回json对象数据");
User user = new User();
user.setName("itcast");
user.setAge(15);
return user;
}
- 返回POJO集合对象
@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<~>();
userList.add(user1);
userList.add(user2);
return userList;
}
REST风格
REST:表现形式状态转换
RESTful入门案例
MVC提供的8种动作,并且ResultMapping后面的书写风格,一般是 加上s
- 原本的开发方式
- 使用RESTful改进
- 传参
写上
@PathVariable:来自于路径的参数,取值
并且需要在value中接收参数
如何选取?
RESTful快速开发
前序:这个注释重复写,怎么简化
在类标题上写@RequestMapping("/books"),就不用再方法中,再写value="/books"。如果以前是value="/books/{id}"改写为value=/{id}
对于@RequestBody来说也是,在类上直接写@RequestBody,就不用再到方法上书写了。
更简便的:@RestController直接可以代替@Controller和@ResponseBody。
以前我们写@RequestMapping(method = RequestMethod.POST),现在直接写@PostMapping就可以代替了
delete、put、get也同样==》XxxMapping
@RestController
@RequestMapping("/books")
@PostMapping
public String save(@RequestBody Book book){
}
RESTful页面数据交互
我们将前端的一些数据保存在webapp中,直接执行会报错,因为在ServletContainersInitConfig中,我们通过getServletMappings对所有的页面都进行了拦截,通过Spring操作。所以我们要重新配置。
对这些静态资源,我们应该放行,不要拦截。
- 新建一个SpringMvcSupport.java类,专门用来配置资源过滤。
前提:实现
WebMvcConfigurationSupport的addResourceHandlers方法
当访问/pages/下的任何一个资源的时候,我不走mvc,而是走/pages进行访问
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
//当访问/pages/???的时候,不要走mvc,而是走/page
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/pages/**").addResourceLocations("/css/");
registry.addResourceHandler("/pages/**").addResourceLocations("/plugins/");
}
}
- 将SpringMvcSupport.java做成配置类,让SPringMvcConfig扫描
- 将页面的功能绑定到controller中
SSM整合
整合流程
1.导入jar包
<?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.itheima</groupId>
<artifactId>springmvc_09_result</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<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>
</project>
2.配置类
- SpringConfig.java配置类
PropertySource:扫描properties文件
Import:扫描第三方配置类
@Configuration
@ComponentScan({"com.itheima.service"})
@PropertySource("jdbc.properties")
@Import({JdbcConfig.class,MyBatisConfig.class})
public class SpringConfig {
}
- jdbc.properties
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/ssm_db
jdbc.username = root
jdbc.password = root
- JdbcConfig.java
用来配置数据源的,Druid
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.passwowrd}")
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;
}
}
- MyBatisConfig.java
造SqlSessionFactoryBean对象
他需要外部DataSource,以及包扫描(实体类)、和映射扫描接口
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setTypeAliasesPackage("com.itheima.domain");
return factoryBean;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.itheima.dao");
return msc;
}
}
- Spring整合MVC: SpringMvcConfig.java
@Configuration
@ComponentScan("com.itheima.controller")
@EnableWebMvc
public class SpringMvcConfig {
}
- ServletConfig
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
书写操作
- domain/Book.java
set、get、toString方法
- dao/BookDao.java(接口) 书写增删改查方法
public interface BookDao {
//这个values中的值,是Book实体类中的值
//@Insert("insert into tbl_book values(null,#{type},#{name},#{description})")
//这里前面的属性是数据库中字段的属性,后面的是Book中的属性
@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();
}
- service/BookService.java(接口)
public interface BookService {
// 一般和BookDao中的接口一样,但是【增删改】返回的是boolean类型。
public boolean save(Book book);
public boolean update(Book book);
public boolean delete(Integer id);
public Book getById(Integer id);
public List<Book> getAll();
}
- service/BookServiceImpl.java(接口实现类)
@Service
public class BookServiceImpl implements BookService {
//实现BookService中的方法,一定会用到dao接口中的增删改查方法,所以这里要依赖注入
@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();
}
}
- controller/BookController.java
@RestController//= response + Controller
@RequestMapping("/books")//RESTful风格
public class BookController {
@Autowired //想用BookService中方法,就需要依赖注入
private BookService bookService;
@PostMapping//这些数据来自于请求得到的JSON数据,就需要@RequestBody接收
public boolean save(@RequestBody Book book) {
return bookService.save(book);
}
@PutMapping
public boolean update(@RequestBody Book book) {
return bookService.update(book);
}
@DeleteMapping("/{id}")//接收请求参数的JSON数据,需要通过@PathVariable
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();
}
}
接口测试
业务层接口通过JUnit测试、表现层接口通过PostMan测试
- 业务层接口测试/test/java/com.itheima.service/BookServiceTest
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class BookServiceTest {
@Autowired
private BookService bookService;
@Test
public void testGetById(){
//根据Id查询book信息,返回的是一个Book类型,用他来接收
Book book = bookService.getById(1);
System.out.println(book);
}
@Test
public void testGetAll(){
List<Book> all = bookService.getAll();
System.out.println(all);
}
}
- 表现层接口测试
保存图书
修改图书
删除图书
查询单个图书
查询全部图书
补充:事务处理
- 开启注解式 事务驱动
@EnableTransactionManagement
@Configuration
@ComponentScan({"com.itheima.service"})
@PropertySource("jdbc.properties")
@Import({JdbcConfig.class,MyBatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
- 配置事务管理器
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager ds = new DataSourceTransactionManager();
ds.setDataSource(dataSource);
return ds;
}
- 添加事务,将事务添加到业务层接口上
表现层数据封装
前端收到后端传来的数据,但是可能会存在多种不同类型的数据,因此后端需要将这些数据全部存储在data属性中,但是每个data中又不知道这些代码的功能是干嘛的。
将持有不同数据类型的数据,存储在data中,然后用code来标识:是删除的数据,还是查询的数据。
根据code = 20041得知,他是一个查询操作,不管是查询一个还是多个。
但是存在查询的数据是空的情况,如何解决?
将为空的数据表示为:20040。
但是既然数据查出来是空,那应该怎么展示给用户呢?
我们定义:封装【特殊消息】到message属性中。
后续我们数据封装返回结果类的统一格式(前端后端):
数据封装例子(控制层)
Result.java
set、get、构造方法(无参、有一个参数、有两个参数、三个参数都有)
public class Result {
//描述统一格式中的数据
private Object data;
//描述统一格式中的编码,用于区分操作,可以简化配置0或1表示成功失败
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;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
Code.java
正确和错误的常量定义:
//状态码
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;
}
对之前的控制层代码进行改进
@RestController//= response + Controller
@RequestMapping("/books")//RESTful风格
public class BookController {
@Autowired //想用BookService中方法,就需要依赖注入
private BookService bookService;
@PostMapping//这些数据来自于请求得到的JSON数据,就需要@RequestBody接收
public Result save(@RequestBody Book book) {
//判断,如果插入成功,那么save返回的结果是true
//true,就代表data中有数据,就可以返回正确的Code值。
//插入我们一般返回的是true或者false,所以data里面存储的也是true或false;
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}")//接收请求参数的JSON数据,需要通过@PathVariable
public Result delete(@PathVariable Integer id) {
boolean flag = bookService.delete(id);
return new Result(flag ? Code.DELETE_OK:Code.DELETE_ERR,flag);
}
//判断null不是"",因为可能查询出来的是空的。
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id) {
//这里就是真的可以查询出数据,所以data中存储是数据,不再是true或者false
Book book = bookService.getById(id);
Integer code = book != null ? Code.GET_OK : Code.GET_ERR;
//如果msg不为空的话,说明不是特殊message,就直接展示数据即可,不需要展示message
//如果msg为空的话,前端人员不能直接展示空数据,而是展示message存储的特殊语句。
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);
}
}
我前端发送一个存在并且有数据的url,我后端根据url向数据库进行增删改查操作,如果全部都有数据,那么返回给前端的数据中的data都是有数据的,而且message是空。如下:
逆向思维:如果我发送http://localhost/books/100,这种一个不存在的id号,那么后端就会查询不到这个信息,那么这个数据的状态码会被标志为20040,后端就会匹配不上,那么返回的就是一个定义后的message信息
异常处理器
各个层级均出现异常,异常处理代码书写在哪一层?
所有异常的全部抛出到 表现层 进行处理 表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决--AOP思想
不过我们不用使用AOP也可以,因为SpringMVC给我们提供了异常处理器,可以用来统一的处理项目中出现的异常。
将它写在表现层中(/controller/ProjectExceptionAdvice)
@ExceptionHandler:专门用来拦截异常的
@RestControllerAdvice//Rest风格的controller
//@Controller
public class ProjectExceptionAdvice {
//拦截异常:Exception.class:表示所有的异常;可以写算术异常
@ExceptionHandler(Exception.class)
public Result doException(Exception ex){
//通过Exception来抓到异常,进行处理
System.out.println("异常,逮到你了!");
//抓到异常后,将信息返回给前端
return new Result(20040,null,"出异常了前端,别请求我");
}
}
在BookController中随便写一个异常
public Result getById(){
int i = 1/0;
}
当前端请求到一个存在异常的错误时,后端通过@ExceptionHandler来抓到异常,然后再ProjectExceptionAdvice异常类中进行捕获后的操作,最后将数据返回给前端。
项目异常处理
业务异常:规范用户行为、不规范用户行为都是与业务有关
系统异常:服务器中的异常,能预计异常,但是无法避免
其他异常:程序员自己都不知道(忘记操作)哪错了
com.itheima.exception.SystemException.java
//这种运行时异常可以暂时不处理,他会自己往上抛。如果不继承RuntimeException,每一次都要throws Exception
public class SystemException extends RuntimeException{
private Integer code;
public SystemException(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 Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
BusinessException.java(区分系统异常和业务异常)
public class BusinessException extends RuntimeException{
private Integer code;
public BusinessException(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;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
对可能造成异常的地方,进行异常处理
异常就会被跑到表现层
public Book getById(Integer id){
//将可能出现的异常进行包裹(try...catch),转换成自定义异常
try{
int i = 1 / 0;
}catch (ArithmeticException e){
throw new SystemException(编码,"错误消息提示",异常对象)
throw new SystemException(COde.SYSTEM_TIMEOUT_ERR,"服务器异常,请重试",e)
}
return bookDao.getById(id);
}
异常处理器:controller/ProjectExceptionAdvice.java
//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {
//@ExceptionHandler用于设置当前处理器类对应的异常类型
@ExceptionHandler(SystemException.class)
public Result doSystemException(SystemException ex){
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
//是我上面有问题的代码中,写的code,null:异常了肯定是没数据的
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 doOtherException(Exception ex){
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
return new Result(Code.SYSTEM_UNKNOW_ERR,null,"系统繁忙,请稍后再试!");
}
}
拦截器
拦截器是一种动态的拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行。
只能对SpringMVC的访问进行增强
拦截器入门案例
创建新的包,放在controller中:/controller/interceptor/ProjectInterceptor.java
在拦截的时候,做一些事情,就在这里面书写
@Component
public class ProjectInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("拦截之前,做的操作,并且返回值必须为true,这里是强制为true");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("拦截之后,做的操作");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("在PostHandle之后,再执行的一些操作");
}
}
让SpringMvcConfig来扫描他、受到Spring的管理
创建/config/SpringMvcConfig;以前用来做过滤访问的静态资源的
@Conguration
public class SpringMvcConfig extends WebMvcConfigurationSupport{
@Autowired
private ProjectInterceptor projectInterceptor
//过滤器
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry){
registry.addResourceHandler("/paths/**").addResourcesLocations("/pages/")
}
//拦截器
@Override
protected void addInterceptors(InterceptorRegistry registry){
//指定使用哪个通知类
registry.addInterceptor(projectInterceptor).addPathPatterns("/books");
//当我调用books这个路径的时候,进行拦截哦
}
}
SpringMvcConfig
导入配置类,如果是外部的,还需要在Import
@ComponentScan({"com.itheima.controller","com.itheima.config"})
但是拦截器,只能纯纯的拦截/books,如果后面还有参数的话,就不行,需要重新配置
如果这里返回的是false,就会终止原始操作,什么也没有了,只会返回 执行前(preHandle方法)中的输出
补充
使用标准接口WebMVCConfigurer简化开发(但是:侵入式较强)
实际上,我们可以直接实现拦截器和过滤器的方法,不需要单独再写一个SpringMVCSupport,并且不需要再次扫描他了。
拦截器参数
public class ProjectInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
获取参数中的request参数,请求的头部中的Content-Type
String contentType = request.getHeader("Content-Type")
//handler打印出来是 描述
HandlerMethod hm = (HandlerMethod)handler;
//通过HandlerMethod生成的对象,拿到原始执行的对象
hm.getMethod().
return true;
}
连接器链
可以配置多个拦截器,形成 拦截器链
SpringMvcConfig.java:前面我们说过,可以不用再创建一个拦截器类,直接写在配置类中,并且只要实现WebMvcConfigurer就行
具体拦截后要做什么,在控制层的/controller/ProjectInterceptor或者/ProjectInterceptor2这两个类中,进行操作
@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer{
@Autowired
private ProjectInterceptor projectInterceptor;
@Autowired
private ProjectInterceptor2 projectInterceptor;
//重写拦截器
@Override
public void addInterceptors(InterceptorRegistry registry){
添加拦截器,并对books路径下的所有url进行拦截
registry.addInterceptor(projectInterceptro).addPathPatterns("/books","/books/*")
registry.addInterceptor(projectInterceptro2).addPathPatterns("/books","/books/*")
}
}
当我们配了多个拦截器,PreHandle先执行的是最前面的拦截器
PostHandle是先执行后的拦截器,在执行前面的拦截器
afterCompletion和PostHandle的执行效果一样
如果n号的拦截器的PreHandler返回false,那么所有的PostHandler都不会执行,但是n号之前(不包括n号)的所有的afterCompletion都会执行。