5.SpringMVC

267 阅读19分钟

图片.png

SpringMVC简介

  1. 浏览器发送请求,给后端服务器时,Servlet只能接收、产生响应数据;并不能数据处理

图片.png

  1. 将Servlet拆分成3层架构

web:页面数据收集、产生页面 service:数据处理 dao:数据持久化(存储数据)

图片.png

这种开发模式,一种servlet只能处理一个请求

  1. MVC模式 将servlet模式又拆分成了3块儿 浏览器请求时,将请求发给控制器,由控制器调用service层,再由service业务层调用dao数据层、得到数据后,组织出最终展示的数据模型,并将页面抽取出来,由页面和数据模型一起配合 = 最终页面;最终反馈给用户。

图片.png

View:现在基本上用异步调用方式 后端服务器整合后,生成Model对象;但是Model本质是一个java对象,是不能返回给页面的。
将Model对象调整为JSON格式,这样就能和前端页面进行数据交互。

SpringMVC用来:负责controller对应的功能开发,并将操作完的数据转换为JSON格式,交给前端页面

图片.png

SpringMVC入门案例

图片.png

  1. 创建web工程(Maven结构)

图片.png

图片.png

  1. 导入坐标(还有tomcat插件)

图片.png

  1. 定义处理请求的功能类(UserController)
//被Spring管理的bean对象
@Controller
public class UserController{
//使用注解处理请求、并设置名称;
//后续url通过.../save就可以请求这个servlet
    @RequestMapping("/save")
    public void save(){
        System.out.prinln("user save...")
    }
}
  1. Spring配置类 SpringMvcConfig
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig{
    
}
  1. 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;暂时不用
    }
}
  1. 创建TomCat服务器

图片.png

  1. 补充

控制器中的MVC方法不会空返回,都会返回JSON数据:键值对形式

@Controller
public class UserController{
    @RequestMapping("/save")
    @ResponseBody
    public String save(){
        return "{'info':'SpringMvc'}"
    }
}

@ResponseBody:告诉MVC,save中返回的数据就是响应体、不是页面!否则会404

图片.png

案例流程分析

  1. 整体逻辑分析 图片.png

  2. 执行ServletContainersInitConfig类,初始化web容器,并创建对象 SpringMvc对象最终是一个WebApplicationContext对象,这个对象会被加载到ServletContext中

图片.png

  1. 加载SpringMvcConfig并通过@ComponentScan执行对应的bean

图片.png

图片.png

图片.png 3. 加载UserController,每个@RequestMapping的名称对应一个具体方法

图片.png 4. 执行getServletMappings方法,全部由SpringMVC来处理。

图片.png

设定所有进入tomcat拦截的请求,全部由SpringMVC来处理。

  1. 单次请求过程分析

图片.png

我们定义过@RequestMapping("/save"),就由对应的save()方法执行...

SpringMVC简介

图片.png

  1. 因为功能不同,如何避免Spring错误 加载到 SpringMVC的bean--加载Spring控制的bean的时候排除掉SpringMVC控制的bean?

图片.png

解决方案例子

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介绍

图片.png

设置请求映射路径

我有两个Controller类,里面同时存有一个/save路径已经save()方法。
如果直接执行的话,是会报错的。

多人开发时,每个人设置不同的请求路径,冲突问题如何解决?---设置模块名 作为 请求路径前缀 @RequestMapping("/user/save")---@RequestMapping("/save") 写上了/user前缀

图片.png

但是一旦需要表名前缀的方法过多,就会造成页面的混乱。

通过在最上面书写@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接收参数

  1. 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'}";
    }
}

图片.png 2. Post请求

Post请求在Body中编辑

图片.png

乱码的处理

POST请求使用过滤器实现乱码 ServletContainersInitConfig.java
通过Alt+Insert,弹出:并选中

图片.png

@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种类型参数传递

  1. 如果我再postman中从参数名:name,而UserController中接收参数的名字是:username,这样是会报错的。\

我们要求发送和接收的参数名相同,这样才能映射进去;但是通过具体指定可以避免
@RequestParam("name") String userName

public String commonParam(@RequestParam("name") String userName, @RequestParam(age) int userAge){
  1. 但是如果参数过多,难道我们要写这么多注解吗?会很混乱的,这个时候,我们通过实体类来接收参数。

图片.png

//用实体类作为形参,来接收参数
@RequestMapping("/pojoContainPojoParam")
@ResponseBody
public String pojoContainPojoParam(User user){
    System.out.println("User==>"+user);
    return "{'module':'user'}";
}
  1. 如果传递的参数是一个引用类型怎么办?只需要在实体类中定义即可,控制层代码仍然用实体类接收

图片.png

图片.png

  1. 拿数组接收传递过来的参数: 命名为相同的keys即可:接收的参数和传递的参数命名要相同

图片.png

图片.png

  1. 集合里面接收参数 需要写上@RequestParam,并且名称也要前后匹配上。

图片.png

图片.png

JSON格式传参【最常用】

前提:引入json坐标

图片.png

PostMan发送json数据

图片.png

需要在SpringMvcConfig中开启JSON-->对象 的功能,不然MVC不认识JSON。

图片.png

  1. 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'}";
    }
  1. json对象(POJO)

图片.png

//   集合参数: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'}";
}
  1. json数组(POJO)

图片.png

//   集合参数: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'}";
}

补充:

图片.png

图片.png

日期类型参数传递

图片.png

  1. PostMan

图片.png

  1. 控制层
//日期参数
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date){
    System.out.println("参数传递 date=》"+date);
    return "{'module':'data param'}";
}

上述这种默认方式,参数直接写成字符串型,接收时,date会将字符串型变成date型

如果用-中划线还可以吗?默认是不行的
因为第一种方式是标准格式,而使用-中划线,不是标准格式。需要指定标准格式: @DateTimeFormat(pattern="yyyy-MM-dd")

图片.png

@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")

图片.png

//日期参数
@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'}";
}

图片.png

响应

@ResponseBody:将对象数据转为JSON数据,具体由类转换器来实现操作

  1. 响应页面 将@ResponseBody注释,表明:页面上不会显示该方法中的内容,需要我们手写/toJumpPage路径才行。
@RequestMapping(value = "/toJumpPage",produces = "application/json;charset=utf-8")
//@ResponseBody
public String toJumpPage(){
    System.out.println("跳转页面");
    return "page.jsp";
}
  1. 响应文本数据 必须要写@ResponseBody,不然MVC会将他认作是 页面
@RequestMapping("/toText")
@ResponseBody
public String toText(){
    System.out.println("返回纯文本数据");
    return "response text";
}
  1. 返回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;
}
  1. 返回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:表现形式状态转换

图片.png

图片.png

图片.png

RESTful入门案例

MVC提供的8种动作,并且ResultMapping后面的书写风格,一般是 加上s

图片.png

  1. 原本的开发方式

图片.png

  1. 使用RESTful改进

图片.png

  1. 传参 写上@PathVariable:来自于路径的参数,取值
    并且需要在value中接收参数

图片.png

图片.png

如何选取?

图片.png

RESTful快速开发

前序:这个注释重复写,怎么简化

图片.png

在类标题上写@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){
    
}

图片.png

RESTful页面数据交互

我们将前端的一些数据保存在webapp中,直接执行会报错,因为在ServletContainersInitConfig中,我们通过getServletMappings对所有的页面都进行了拦截,通过Spring操作。所以我们要重新配置。

图片.png

对这些静态资源,我们应该放行,不要拦截。

  1. 新建一个SpringMvcSupport.java类,专门用来配置资源过滤。 前提:实现WebMvcConfigurationSupportaddResourceHandlers方法

当访问/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/");
    }
}
  1. 将SpringMvcSupport.java做成配置类,让SPringMvcConfig扫描

图片.png

  1. 将页面的功能绑定到controller中

图片.png

SSM整合

整合流程

图片.png

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.配置类

  1. SpringConfig.java配置类

PropertySource:扫描properties文件
Import:扫描第三方配置类

@Configuration
@ComponentScan({"com.itheima.service"})
@PropertySource("jdbc.properties")
@Import({JdbcConfig.class,MyBatisConfig.class})
public class SpringConfig {

}
  1. jdbc.properties
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/ssm_db
jdbc.username = root
jdbc.password = root
  1. 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;
    }
}
  1. 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;
    }
}
  1. Spring整合MVC: SpringMvcConfig.java
@Configuration
@ComponentScan("com.itheima.controller")
@EnableWebMvc
public class SpringMvcConfig {

}
  1. 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[]{"/"};
    }
}

书写操作

  1. domain/Book.java
set、get、toString方法
  1. 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();
}
  1. 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();
}
  1. 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();
    }
}
  1. 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测试

  1. 业务层接口测试/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);
    }
}
  1. 表现层接口测试

保存图书

图片.png

修改图书

图片.png

删除图书

图片.png

查询单个图书

图片.png

查询全部图书

图片.png

补充:事务处理

  1. 开启注解式 事务驱动 @EnableTransactionManagement
@Configuration
@ComponentScan({"com.itheima.service"})
@PropertySource("jdbc.properties")
@Import({JdbcConfig.class,MyBatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {

}
  1. 配置事务管理器

图片.png

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
    DataSourceTransactionManager ds = new DataSourceTransactionManager();
    ds.setDataSource(dataSource);
    return ds;
}
  1. 添加事务,将事务添加到业务层接口上

图片.png

表现层数据封装

前端收到后端传来的数据,但是可能会存在多种不同类型的数据,因此后端需要将这些数据全部存储在data属性中,但是每个data中又不知道这些代码的功能是干嘛的。

将持有不同数据类型的数据,存储在data中,然后用code来标识:是删除的数据,还是查询的数据。

图片.png

根据code = 20041得知,他是一个查询操作,不管是查询一个还是多个。

但是存在查询的数据是空的情况,如何解决?
将为空的数据表示为:20040。

但是既然数据查出来是空,那应该怎么展示给用户呢?

我们定义:封装【特殊消息】到message属性中。

图片.png

后续我们数据封装返回结果类的统一格式(前端后端):

图片.png

数据封装例子(控制层)

Result.java
set、get、构造方法(无参、有一个参数、有两个参数、三个参数都有)

图片.png

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;
}

图片.png

对之前的控制层代码进行改进

图片.png

@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是空。如下:

图片.png

逆向思维:如果我发送http://localhost/books/100,这种一个不存在的id号,那么后端就会查询不到这个信息,那么这个数据的状态码会被标志为20040,后端就会匹配不上,那么返回的就是一个定义后的message信息

图片.png

demo08.gif

异常处理器

图片.png

各个层级均出现异常,异常处理代码书写在哪一层?

所有异常的全部抛出到 表现层 进行处理 表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决--AOP思想

不过我们不用使用AOP也可以,因为SpringMVC给我们提供了异常处理器,可以用来统一的处理项目中出现的异常。

图片.png

将它写在表现层中(/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异常类中进行捕获后的操作,最后将数据返回给前端。

项目异常处理

图片.png

业务异常:规范用户行为、不规范用户行为都是与业务有关
系统异常:服务器中的异常,能预计异常,但是无法避免
其他异常:程序员自己都不知道(忘记操作)哪错了

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,"系统繁忙,请稍后再试!");
    }
}

图片.png

拦截器

拦截器是一种动态的拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行。

图片.png

图片.png

只能对SpringMVC的访问进行增强

图片.png

拦截器入门案例

创建新的包,放在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的管理

图片.png

创建/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,如果后面还有参数的话,就不行,需要重新配置

图片.png

如果这里返回的是false,就会终止原始操作,什么也没有了,只会返回 执行前(preHandle方法)中的输出

图片.png

补充

使用标准接口WebMVCConfigurer简化开发(但是:侵入式较强)

实际上,我们可以直接实现拦截器和过滤器的方法,不需要单独再写一个SpringMVCSupport,并且不需要再次扫描他了。

图片.png

拦截器参数

图片.png

图片.png

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这两个类中,进行操作

图片.png

@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的执行效果一样

图片.png

如果n号的拦截器的PreHandler返回false,那么所有的PostHandler都不会执行,但是n号之前(不包括n号)的所有的afterCompletion都会执行。

图片.png