一、SpringBoot介绍

1.1 Spring缺点分析

Spring是一个非常优秀的轻量级框架,以IOC(控制反转)和AOP(面向切面)为思想内核,极大简化了JAVA企业级项目的开发。

虽然Spring的组件代码是轻量级的,但它的配置却是重量级的。使用Spring进行项目开发需要在配置文件中写很多代码,所有这些配置都代表了开发时的损耗。

除此之外,Spring项目的依赖管理也是一件耗时耗力的事情。在环境搭建时,需要分析要导入哪些库的坐标,而且还需要分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题就会严重阻碍项目的开发进度。比如Spring5.0以上只能使用Junit4.12以上的版本。

总结

Spring的缺点:

  1. 配置过于繁琐。
  2. 引入的依赖过多,版本控制复杂。

 1.2 什么是SpringBoot

SpringBoot对Spring的缺点进行改善和优化,基于约定大于配置的思想,简化了Spring的开发,所谓简化是指简化了Spring中大量的配置文件和繁琐的依赖引入,提供了大量的默认配置。所以SpringBoot是一个服务于框架的框架,它不是对Spring功能的增强,而是提供了一种快速使用Spring框架的方式。

SpringBoot的优点:

  1. 配置简单
  2. 依赖引入简单
  3. 提供了一些大型项目的非功能特性,如嵌入式服务器,安全指标,健康监测等。

 1.3 SpringBoot核心功能

自动配置

SpringBoot项目自动提供最优配置,同时可以修改默认值满足特定的要求。

起步依赖

SpringBoot的依赖是基于功能的,而不是普通项目的依赖是基于JAR包的。SpringBoot将完成一个功能所需要的所有坐标打包到一起,并完成了版本适配,我们在使用某功能时只需要引入一个依赖即可。


二、SpringBoot入门

2.1 通过IDEA脚手架搭建项目

 1、在IDEA中新建项目,项目类型为Spring Initializr,选择JDK版本和搭建网站后点击下一步。

 2、选择项目参数后,点击下一步

 3、选择SpringBoot版本和需要的起步依赖,点击下一步。

4、确定项目位置完成项目搭建

注意:如果是jdk8最好创建的spring boot的版本是2.xxxx。 

  2.2 SpringBoot项目结构

 POM文件

1、SpringBoot项目必须继承spring-boot-starter-parent,即所有的SpringBoot项目都是spring-boot-starter-parent的子项目。spring-boot-starter-parent中定义了常用配置、依赖、插件等信息,供SpringBoot项目继承使用。

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

2、SpringBoot项目中可以定义起步依赖,起步依赖不是以jar包为单位,而是以功能为单位。

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

3、spring-boot-maven-plugin插件是将项目打包成jar包的插件。该插件打包后的SpringBoot项目无需依赖web容器,可以直接使用JDK运行

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

启动类

启动类的作用是启动SpringBoot项目,运行启动类的main方法即可启动SpringBoot项目。在运行web项目的时候可以将项目放到内置的tomcat中运行,不需要使用外置的tomcat。

package com.zj.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

配置文件

由于SpringBoot极大简化了Spring配置,所以只有一个application.properties配置文件,且Spring的自动配置功能使得大部分的配置都有默认配置,该文件的功能是覆盖默认配置信息,该文件不写任何信息都可以启动项目。

2.3 编写JAVA代码

之前搭建的SpringBoot项目已经都整合了SpringMVC,我们编写一个控制器进行测试:

@Controller
public class MyController {
  @RequestMapping("/hello")
  @ResponseBody
  public String hello(){
    System.out.println("hello springboot!");
    return "hello springboot!";
   }
}

 启动类在启动时会做注解扫描(@Controller、@Service、@Repository......),扫描位置为同包或者子包下的注解,所以我们要在启动类同级或同级包下编写代码。


三、SpringBoot原理分析

3.1 起步依赖

 查看spring-boot-starter-parent起步依赖

1、按住Ctrl点击pom.xml中的spring-boot-starter-parent,跳转到了spring-boot-starter-parentpom.xml,发现spring-boot-starter-parent的父工程是spring-boot-dependencies

如果在pom文件中ctrl键突然失效了,无法查看源码,那可能是没有下载文档。

3.2 自动配置

3.3 核心注解

四、SpringBoot注册Web组件

4.1 注册servlet(有了controller谁还用servlet)

由于SpringBoot项目没有web.xml文件,所以无法在web.xml中注册web组件,SpringBoot有自己的方式注册web组件。

 注册方式一

1、编写servlet

@WebServlet("/first")
public class FirstServlet extends HttpServlet {
  public void doGet(HttpServletRequest request, HttpServletResponse response){
    System.out.println("First Servlet........");
   }
}

2、启动类扫描web组件

@WebServlet("/first")
public class FirstServlet extends HttpServlet {

  public void doGet(HttpServletRequest request, HttpServletResponse response){
    System.out.println("First Servlet........");
   }

}

注册方式二

1、编写servlet

public class SecondServlet extends HttpServlet {
  public void doGet(HttpServletRequest request, HttpServletResponse response){
    System.out.println("Second Servlet........");
   }
}

2、使用配置类注册servlet

@Configuration
public class ServletConfig {
  //ServletRegistrationBean可以注册Servlet组件,将其放入Spring容器中即可注册Servlet
  @Bean
  public ServletRegistrationBean getServletRegistrationBean(){
    // 注册Servlet组件
    ServletRegistrationBean bean = new ServletRegistrationBean(new SecondServlet());
    // 添加Servlet组件访问路径
    bean.addUrlMappings("/second");
    return bean;
   }
}

4.2 注册Filter

 注册方式一

1、编写filter

//过滤的是first请求
@WebFilter(urlPatterns = "/first")
public class FirstFilter implements Filter {

  public void init(FilterConfig filterConfig) throws ServletException { }
  @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException, IOException {
    System.out.println("进入First Filter");
    filterChain.doFilter(servletRequest,servletResponse);
    System.out.println("离开First Filter");
   }
  @Override
  public void destroy() { }
}

2、启动类扫描web组件

@SpringBootApplication
//SpringBoot启动时扫描注册@WebServlet、@WebFilter注解标注的Web组件
@ServletComponentScan
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

注册方式二

1、编写filter

@Configuration
public class FilterConfig {
  @Bean
  public FilterRegistrationBean getFilterRegistrationBean(){
    // 注册filter组件
    FilterRegistrationBean bean = new FilterRegistrationBean(new SecondFilter());
    // 添加过滤路径
    bean.addUrlPatterns("/second");
    return bean;
   }
}

4.3 注册Listener

 注册方式一

1、编写Listener

这里选择注册上下文监听器

@WebListener
public class FirstListener implements ServletContextListener {
  @Override
  public void contextInitialized(ServletContextEvent sce) {
    System.out.println("First Listener Init......");
   }


  @Override
  public void contextDestroyed(ServletContextEvent sce) {


   }
}

2、启动类扫描web组件

@SpringBootApplication
//SpringBoot启动时扫描注册@WebServlet、@WebFilter、@WebListener注解标注的Web组件
@ServletComponentScan
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

注册方式二

1、编写Listener

public class SecondListener implements ServletContextListener {
  @Override
  public void contextInitialized(ServletContextEvent sce) {
    System.out.println("Second Listener Init......");
   }


  @Override
  public void contextDestroyed(ServletContextEvent sce) {


   }
}

2、使用配置类注册Listener

@Configuration
public class ListenerConfig {
  @Bean
  public ServletListenerRegistrationBean getServletListenerRegistrationBean(){
    ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean(new SecondListener());
    return bean;
   }
}

五、SpringBoot访问静态资源

5.1 静态资源相关目录

SpringBoot项目中没有WebApp目录,只有src目录。在src/main/resources下面有statictemplates两个文件夹。SpringBoot默认在static目录中存放静态资源,而templates中放动态页面。

 static目录

SpringBoot通过/resources/static目录访问静态资源,在resources/static中编写html页面:

1、html页面

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>测试html</title>
</head>
<body>
<h1>我的HTML</h1>
<img src="/img/img.png">
</body>
</html>

2、目录结构

templates目录

在SpringBoot中不推荐使用JSP作为动态页面,而是默认使用Thymeleaf编写动态页面。templates目录是存放Thymeleaf页面的目录,稍后。

 5.2 静态资源其他存放位置

除了/resources/static目录,SpringBoot还会扫描以下位置的静态资源:

  • /resources/META‐INF/resources/
  • /resources/resources/
  • /resources/public/

我们还可以在配置文件自定义静态资源位置,例如在resources目录下创建suibian文件:

在SpringBoot配置文件进行自定义静态资源位置配置:

spring:
  web:
    resources:
      static-locations: classpath:/suibian/,classpath:/static/

注意:

  1. 该配置会覆盖默认静态资源位置,如果还想使用之前的静态资源位置,还需要配置在后面。
  2. SpringBoot2.5之前的配置方式为:spring.resources.static-locations

六、Thymeleaf

6.1 Thymeleaf入门

Thymeleaf是一款用于渲染XML/HTML5内容的模板引擎,类似JSP。它可以轻易的与SpringMVC等Web框架进行集成作为Web应用的模板引擎。在SpringBoot中推荐使用Thymeleaf编写动态页面。

Thymeleaf最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个Web应用。

Thymeleaf在有网络和无网络的环境下皆可运行,它即可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。没有数据时,Thymeleaf的模板可以静态地运行;当有数据返回到页面时,Thymeleaf标签会动态地替换掉静态内容,使页面动态显示。

1、创建springboot项目,并选择添加的依赖。

2、在template目录编写html页面

<!DOCTYPE html>
<!-- 引入thymeleaf命名空间,方便使用thymeleaf属性 -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>thymeleaf入门</title>
</head>
<body>
<!-- 静态页面显示默认信息,动态页面使用后端传来的msg数据代替 -->
<!-- thymeleaf支持el表达式 -->
<h2 th:text="${msg}">默认信息</h2>
</body>
</html>

需要注意的是使用thyme leaf需要在html页面中添加 <html lang="en" xmlns:th="http://www.thymeleaf.org">

thymeleaf语法检查比较严格,有些时候写对了还是爆红,我们只需要将语法检查取消就行了:

3、template中的动态html文件不能直接访问,需要编写Controller跳转到页面中

@Controller
public class PageController {

  // 页面跳转,无返回值时默认跳转到和访问路径相同的页面。
  @GetMapping("/index")
  public void showPage(Model model){
    model.addAttribute("msg","Hello Thymeleaf");
   }
}

4、启动项目,访问http://localhost:8080/index

6.2 变量输出

语法作用
th:text将model中的值作为内容放入标签中(其实就是request域中的数据,session、application域中的数据都行)
th:value将model中的值放入input标签的value属性中

 1、准备模型数据

  // 页面跳转,无返回值时默认跳转到和访问路径相同的页面。
  @GetMapping("/index")
  public void showPage(HttpServletRequest model){
    model.setAttribute("msg","Hello Thymeleaf");
   }

2、在视图展示model中的值

<h2 th:text="${msg}">默认信息</h2>
<input th:value="${msg}">

3、 启动项目,访问http://localhost:8080/index

 6.3 操作字符串

 Thymeleaf提供了一些内置对象可以操作数据,内置对象可直接在模板中使用,这些对象是以#引用的,操作字符串的内置对象为strings。

方法说明
${#strings.isEmpty(key)}判断字符串是否为空,如果为空返回true,否则返回false
${#strings.contains(msg,'T')}判断字符串是否包含指定的子串,如果包含返回true,否则返回false
${#strings.startsWith(msg,'a')}判断当前字符串是否以子串开头,如果是返回true,否则返回false
${#strings.endsWith(msg,'a')}判断当前字符串是否以子串结尾,如果是返回true,否则返回false
${#strings.length(msg)}返回字符串的长度
${#strings.indexOf(msg,'h')}查找子串的位置,并返回该子串的下标,如果没找到则返回-1
${#strings.substring(msg,2,5)}截取子串,用法与JDK的subString方法相同
${#strings.toUpperCase(msg)}字符串转大写
${#strings.toLowerCase(msg)}字符串转小写

1、html

<span th:text="${#strings.isEmpty(msg)}"></span><!--判空-->
<br>
<span th:text="${#strings.contains(msg,'s')}"></span><!--包含-->
<br>
<span th:text="${#strings.toUpperCase(msg)}"></span><!--转大写-->

2、控制器

  // 页面跳转,无返回值时默认跳转到和访问路径相同的页面。
  @GetMapping("/index")
  public void showPage(HttpServletRequest model){
    model.setAttribute("msg","Hello Thymeleaf");
   }

3、访问

 6.4 操作时间

操作时间的内置对象为dates

方法说明
${#dates.format(key)}格式化日期,默认的以浏览器默认语言为格式化标准
${#dates.format(key,'yyyy/MM/dd')}按照自定义的格式做日期转换
${#dates.year(key)}取年
${#dates.month(key)}取月
${#dates.day(key)}取日

1、html

<span th:text="${#dates.format(date)}"></span><!--按照浏览器的格式转换时间-->
<br>
<span th:text="${#dates.format(date,'yyyy-MM-dd')}"></span><!--按照给定格式转换时间-->
<br>
<span th:text="${#dates.year(date)}"></span><!--获取年-->
<br>
<span th:text="${#dates.month(date)}"></span><!--获取月份-->
<br>
<span th:text="${#dates.day(date)}"></span><!--获取天-->

2、控制器

  // 页面跳转,无返回值时默认跳转到和访问路径相同的页面。
  @GetMapping("/index")
  public void showPage(Model model){
      //Date参数:130表示距离1900后30年也就是2030年
    model.addAttribute("date",new Date(130,1,1));
   }

3、访问

 6.5 条件判断

语法作用
th:if条件判断

1、html

<span th:if="${sex}== '女'">
    性别:女
</span>
<span th:if="${sex} == '男'">
    性别:男
</span>

2、控制器

  // 页面跳转,无返回值时默认跳转到和访问路径相同的页面。
  @GetMapping("/index")
  public void showPage(Model model){
      model.addAttribute("sex","女");

  }

3、访问

语法作用
th:switch/th:caseth:switch/th:case与Java中的switch语句等效。th:case="*"表示Java中switch的default,即没有case的值为true时显示th:case="*"的内容。

 1、html

<div th:switch="${id}">
  <span th:case="1">ID为1</span>
  <span th:case="2">ID为2</span>
  <span th:case="3">ID为3</span>
  <span th:case="*">ID为*</span>
</div>

2、控制器

  // 页面跳转,无返回值时默认跳转到和访问路径相同的页面。
  @GetMapping("/index")
  public void showPage(Model model){
      model.addAttribute("id","12");
  }

3、访问

6.6 遍历集合 

语法作用
th:each迭代器,用于循环迭代集合

1、pojo

public class User {
    private String id;
    private String name;
    private int age;
    //get/set/构造略
}

 2、控制器

  // 页面跳转,无返回值时默认跳转到和访问路径相同的页面。
  @GetMapping("/index")
  public void showPage(Model model){
      List<User> users = new ArrayList<>();
      users.add(new User("1","谷梦琪",23));
      users.add(new User("2","邴梓童",22));
      users.add(new User("3","孔新月",25));
      model.addAttribute("users",users);
  }

 3、html

<table border="1" width="50%">
    <tr>
        <th>ID</th>
        <th>Name</th>
        <th>Age</th>
    </tr>
    <!-- 遍历集合的每一项起名为user -->
    <tr th:each="user : ${users}">
        <td th:text="${user.id}"></td>
        <td th:text="${user.name}"></td>
        <td th:text="${user.age}"></td>
    </tr>
</table>

4、访问

 6.7 遍历时使用状态变量

thymeleaf将遍历的状态变量封装到一个对象中,通过该对象的属性可以获取状态变量:

状态变量含义
index当前迭代器的索引,从0开始
count当前迭代对象的计数,从1开始
size被迭代对象的长度
odd/even布尔值,当前循环是否是偶数/奇数,从0开始
first布尔值,当前循环的是否是第一条,如果是返回true,否则返回false
last布尔值,当前循环的是否是最后一条,如果是则返回true,否则返回false

1、html

<table border="1" >
    <!--冒号前的第一个对象是遍历出的对象,第二个对象是封装状态变量的对象-->
    <tr th:each="user,status : ${users}">
        <td th:text="${user.id}"></td>
        <td th:text="${user.name}"></td>
        <td th:text="${user.age}"></td>
        <td th:text="${status.index}"></td>
        <td th:text="${status.count}"></td>
        <td th:text="${status.size}"></td>
        <td th:text="${status.odd}"></td>
        <td th:text="${status.even}"></td>
        <td th:text="${status.first}"></td>
        <td th:text="${status.last}"></td>
    </tr>
</table>

2、控制器

  // 页面跳转,无返回值时默认跳转到和访问路径相同的页面。
  @GetMapping("/index")
  public void showPage(Model model){
      List<User> users = new ArrayList<>();
      users.add(new User("1","谷梦琪",23));
      users.add(new User("2","邴梓童",22));
      users.add(new User("3","孔新月",25));
      model.addAttribute("users",users);
  }

3、访问

6.8 遍历map

1、控制器

  // 页面跳转,无返回值时默认跳转到和访问路径相同的页面。
  @GetMapping("/index")
  public void showPage(Model model){
      
      Map<String,User> map = new HashMap<>();
      map.put("user1",new User("1","谷梦琪",23));
      map.put("user2",new User("2","邴梓童",22));
      map.put("user3",new User("3","孔新月",25));
      model.addAttribute("map",map);
      
  }

2、html

<table border="1" width="50%">
    <tr>
        <th>ID</th>
        <th>Name</th>
        <th>Age</th>
        <th>Key</th>
    </tr>
    <!-- 遍历出的是一个键值对对象,key获取键,value获取值 -->
    <tr th:each="m : ${map}">
        <td th:text="${m.value.id}"></td>
        <td th:text="${m.value.name}"></td>
        <td th:text="${m.value.age}"></td>
        <td th:text="${m.key}"></td>
    </tr>
</table>

3、访问

6.9 获取域中的数据

 thymeleaf也可以获取request,session,application域中的数据,方法如下:

1、控制器

  // 页面跳转,无返回值时默认跳转到和访问路径相同的页面。
  @GetMapping("/index")
  public void showPage(HttpServletRequest request, HttpSession session){
      request.setAttribute("req","HttpServletRequest");
      session.setAttribute("ses","HttpSession");
      session.getServletContext().setAttribute("app","application");
  }

2、html

<!--第一种,使用内置对象request获取request对象的方法-->
<span th:text="${#request.getAttribute('req')}"></span>
<!--第二种,使用httpServletRequest内置对象获取数据-->
<span th:text="${#httpServletRequest.getAttribute('req')}"></span>
<hr>
<!--第一种获取session数据-->
<span th:text="${session.ses}"></span>
<!--第二种使用内置对象获取数据-->
<span th:text="${#httpSession.getAttribute('ses')}"></span>
<hr>
<!--第一种获取application对象的方法-->
<span th:text="${application.app}"></span>
<!--第二种使用内置对象获取数据-->
<span th:text="${#servletContext.getAttribute('app')}"></span>

3、访问

6.10 URL写法

 在Thymeleaf中路径的写法为@{路径}

<a th:href="@{http://www.baidu.com}">百度</a>

静态传参 

1、控制器

        //访问index.html
        @RequestMapping("/index")
        public void index(){ }

      // 返回json格式的数据
      @GetMapping("/show")
      @ResponseBody
      public String showPage(String id,String name){
          return id + "-" + name;
      }

2、html

<a th:href="@{show?id=1&name='张三'}">静态传参1</a>
<a th:href="@{show(id=2,name=lisi)}">静态传参2</a>

 动态传参

1、控制器

        //访问index.html
        @RequestMapping("/index")
        public void index(Model model){
            model.addAttribute("id",1);
            model.addAttribute("name", "张三 ");
        }

      // 返回json格式的数据
      @GetMapping("/show")
      @ResponseBody
      public String showPage(String id,String name){
          return id + "-" + name;
      }

2、html

<a th:href="@{'show?id='+${id}+'&name='+${name}}">动态传参1</a>
<a th:href="@{show(id=${id},name=${name})}">动态传参2</a>

 6.11 RESTful风格URL写法

1、控制器

        //访问index.html
        @RequestMapping("/index")
        public void index(Model model){
            model.addAttribute("id",1);
            model.addAttribute("name", "张三 ");
        }

      // restful风格
      @GetMapping("/show/{id}/{name}")
      @ResponseBody
      public String showPage(@PathVariable  String id, @PathVariable String name){
          return id + "-" + name;
      }

2、html

<a th:href="@{show/{id}/{name}(id=${id},name=${name})}">restful风格传参</a>

6.12 Thymeleaf相关配置

 在Springboot配置文件中可以进行Thymeleaf相关配置,但是一般不需要配置,使用默认的就行

配置项含义
spring.thymeleaf.prefix视图前缀
spring.thymeleaf.suffix视图后缀
spring.thymeleaf.encoding编码格式
spring.thymeleaf.servlet.content-type响应类型
spring.thymeleaf.cache=false页面缓存,配置为false则不启用页面缓存,方便测试
spring:
  thymeleaf:
   prefix: classpath:/templates/
   suffix: .html
   encoding: UTF-8
   cache: false
   servlet:
    content-type: text/html

七、SpringBoot热部署

热部署,就是在应用正在运行的时候升级软件,却不需要重新启动应用。即修改完代码后不需要重启项目即可生效。在SpringBoot中,可以使用DevTools工具实现热部署 

1、添加DevTools依赖

<!-- 热部署工具 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-devtools</artifactId>
  <optional>true</optional>
</dependency>

2、在idea中设置自动编译

点击File-->Settings

3、在Idea设置自动运行

快捷键Ctrl+Shift+Alt+/后点击Registry,勾选complier.automake.allow.when.app.running

 八、SpringBoot整合Mybatis

 Spring整合MyBatis时需要进行大量配置,而SpringBoot整合MyBatis则可以简化很多配置:

1、准备数据库

2、创建SpringBoot项目,添加MyBatis起步依赖和Mysql驱动依赖

3、编写实体类

public class User {
    private int id;
    private String username;
    private String sex;
    private String address;
    private int account;
}

4、编写Mapper接口

@Mapper
public interface UserMapper {

    List<User> findUsers();

}

5、在resources目录下编写mapper映射文件(接口和映射文件所在的文件的路径保持一致,并且映射文件所在的目录文件要一级一级建)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zj.mapper.UserMapper">
    <select id="findUsers" resultType="com.zj.pojo.User">
          select * from user;
    </select>
</mapper>

6、编写配置文件

#数据源
spring:
  datasource:
    driver-class-name:  com.mysql.cj.jdbc.Driver
    url: jdbc://mysql:///mybatis?serverTimezone=UTC
    username: root
    password: 123456


mybatis:
  #Mybatis配置映射文件的位置
  mapper-locations: com/zj/mapper/*Mapper.xml
  #实体类起别名
  type-aliases-package: com.zj.pojo


#日志格式
logging:
  pattern:
    console: '%d{HH:mm:ss.SSS} %clr(%-5level) ---  [%-15thread] %cyan(%-50logger{50}):%msg%n'

7、编写测试类

package com.zj.mapper;

import com.zj.mapper.UserMapper;
import com.zj.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.List;


@SpringBootTest //springboot测试类注解,在运行测试代码时加载容器
public class UserMapperTest {

    @Resource
    private UserMapper userMapper;

     @Test
    public void testUsers(){
         List<User> users = userMapper.findUsers();
         for (User user : users) {
             System.out.println(user);
         }
     }

}

 九、参SpringBoot参数校验

9.1 校验简单数据类型

 SpringBoot自带了validation工具可以从后端对前端传来的参数进行校验,用法如下:

1、引入validation起步依赖,或者在创建项目的时候引入该模块。

2、控制器

@Controller
@Validated  //该控制器开启参数校验
public class TestController {

    @RequestMapping("/test1")
    @ResponseBody
    //在参数校验,表示该参数不为空。当参数为空时会报异常
    public String test1(@NotBlank(message = "用户名不能为空") String name){
        return "test1:"+name;
    }
}

9.2 异常处理

当抛出ConstraintViolationException异常后,我们可以使用SpringMVC的异常处理器,也可以使用SpringBoot自带的异常处理机制。

当程序出现了异常,SpringBoot会使用自带的BasicErrorController对象处理异常。该处理器会默认跳转到/resources/templates/error.html页面()。

1、引入依赖,在参数校验的基础上还需要thymeleaf依赖。

 2、编写错误页面

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>错误页面</title>
</head>
<body>
只要出现异常都会跳转到该页面
<h1>服务器开小差了!</h1>
</body>
</html>

3、控制器

@Controller
@Validated  //该控制器开启参数校验
public class TestController {

    @RequestMapping("/test1")
    @ResponseBody
    //在参数校验,表示该参数不为空。当参数为空时会报异常
    public String test1(@NotBlank(message = "用户名不能为空") String name){
        return "test1:"+name;
    }
}

4、访问

9.3 校验相关注解

注解作用
@NotNull判断包装类是否为null
@NotBlank判断字符串是否为null或者是空串(去掉首尾空格)
@NotEmpty判断集合是否为空(在判断泛型是基本数据类型的时候还需要加@RequestParam)
@Length判断字符的长度(最大或者最小)
@Min判断数值最小值
@Max判断数值最大值
@Email判断邮箱是否合法

 1、控制器

package com.zj.controller;

import org.hibernate.validator.constraints.Length;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.validation.constraints.*;
import java.util.List;

@Controller
@Validated  //该控制器开启参数校验
public class TestController {

    @RequestMapping("/test1")
    @ResponseBody
    //在参数校验,表示该参数不为空。当参数为空时会报异常
    public String test1(@NotBlank(message = "用户名不能为空")
                        @Length(min = 1,max = 5) String name,
                        @NotNull @Min(1) @Max(100) Integer age,
                        @NotEmpty @RequestParam  List<String> address,
                        @NotBlank @Email String email){
        return "请求成功";
    }
}

2、访问

 9.3 校验对象类型

SpringBoot也可以校验对象参数中的每个属性,用法如下:

 1、添加实体类

public class Student {
  @NotNull(message = "id不能为空")
  private Integer id;
  @NotBlank(message = "姓名不能为空")
  private String name;
  // 省略getter/setter/tostring
}

2、控制器

    @RequestMapping("/test2")
    @ResponseBody
    // 校验的对象参数前添加@Validated,并将异常信息封装到BindingResult对象中
    public String t3(@Validated Student student, BindingResult result) {
        // 判断是否有参数异常
        if (result.hasErrors()) {
            // 所有参数异常
            List<ObjectError> list = result.getAllErrors();
            // 遍历参数异常,输出异常信息
            for (ObjectError err : list) {
                FieldError fieldError = (FieldError) err;
                System.out.println(fieldError.getDefaultMessage());
            }
            return "参数异常";
        }
        System.out.println(student);
        return "请求成功!";
    }

3、访问

十、SpringBoot指标监控 

10.1 添加Actuator功能

Spring Boot Actuator可以帮助程序员监控和管理SpringBoot应用,比如健康检查、内存使用情况统计、线程使用情况统计等。我们在SpringBoot项目中添加Actuator功能,即可使用Actuator监控项目,用法如下:

1、在被监控的项目中添加Actuator起步依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2、编写配置文件

#开启所有监控端点
management.endpoints.web.exposure.include=*

3、访问项目:http://localhost:8080/actuator

通过URL可以调用actuator的功能:

URL查看的数据
/env环境属性
/health健康检查
/mappings显示所有的@RequestMapping路径
/loggers日志
/info定制信息
/metrics查看内存、CPU核心等系统参数
/trace用户请求信息

 例如查询健康数据,访问http://localhost:8080/actuator/health

10.2 创建SpringBootAdmin服务端项目

Actuator使用JSON格式展示了大量指标数据,不利于我们查看,我们可以使用可视化工具Spring Boot Admin查看actuator生成指标数据。Spring Boot Admin是一个独立的项目,我们需要创建并运行该项目。

1、创建SpringBoot项目,添加SpringMVC和Spring Boot Admin服务端起步依赖

 2、修改配置文件

# 端口号
server.port=8081
#日志格式
logging.pattern.console=%d{HH:mm:ss.SSS} %clr(%-5level) ---  [%-15thread] %cyan(%-50logger{50}):%msg%n

3、修改启动类

@SpringBootApplication
@EnableAdminServer //开启Spring Boot Admin服务端
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

10.3 连接SpringBootAdmin项目

在被监控的项目中连接Spring Boot Admin项目,才能使用Spring Boot Admin查看指标数据。

1、被监控项目添加Spring Boot Admin客户端起步依赖

<dependency>
  <groupId>de.codecentric</groupId>
  <artifactId>spring-boot-admin-starter-client</artifactId>
  <version>2.6.0</version>
</dependency>

2、修改配置文件

#连接服务端
spring.boot.admin.client.url=http://localhost:8081

 此时Spring Boot Admin即可连接被监控的项目

十一、SpringBoot日志管理

11.1 logback日志配置 

SpringBoot默认使用Logback组件作为日志管理。Logback是log4j创始人设计的一个开源日志组件。在SpringBoot项目中我们不需要额外的添加Logback的依赖,因为在spring-boot-parent中已经包含了Logback的依赖。

 1、在/resources下添加Logback配置文件logback.xml

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
  <!--定义日志文件的存储地址-->
  <property name="LOG_HOME" value="${catalina.base}/logs/"/>
  
  <!-- 控制台输出 -->
  <appender name="Stdout" class="ch.qos.logback.core.ConsoleAppender">
    <!-- 日志输出编码 -->
    <layout class="ch.qos.logback.classic.PatternLayout">
      <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
      <pattern>%d{MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
      </pattern>
    </layout>
  </appender>
  
  <!-- 按照每天生成日志文件 -->
  <appender name="RollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!--日志文件输出的文件名-->
      <FileNamePattern>${LOG_HOME}/server.%d{yy99-MM-dd}.log</FileNamePattern>
      <MaxHistory>30</MaxHistory>
    </rollingPolicy>
    <layout class="ch.qos.logback.classic.PatternLayout">
      <!--格式化输出:%d表示时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
      </pattern>
    </layout>
    <!--日志文件最大的大小-->
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <MaxFileSize>10MB</MaxFileSize>
    </triggeringPolicy>
  </appender>


  <!-- 日志输出级别 -->
  <root level="info">
    <appender-ref ref="Stdout"/>
    <appender-ref ref="RollingFile"/>
  </root>
</configuration>

注:Logback配置文件名为logback-test.xml或logback.xml,如果classpath下没有这两个文件,LogBack会自动进行最小化配置。

2、在代码中打印日志 

package com.zj.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class LogController {

    private final static Logger logger = LoggerFactory.getLogger(LogController.class);

    @RequestMapping("/log")
    @ResponseBody
    public String showLogMessage(){
        //每次访问在控制台、日志文件中输出日志
        logger.info("记录日志:logMessage方法执行");
        return "hello Logback";
    }
}

3、访问

如果日志过多,可以屏蔽一些包的日志,在配置文件中配置

 #屏蔽org包中的日志输出
logging.level.org=off

11.2 log4j2安全漏洞

补充:Log4j2安全漏洞

在2021年12月,Log4j2爆出了极其严重的安全漏洞,攻击者可以让记录的日志包含指定字符串,从而执行任意程序。很多大型网站,如百度等都是此次Log4j漏洞的受害者,很多互联网企业连夜做了应急措施。

Log4j2.0到2.14.1全部存在此漏洞,危害范围极其广泛,Log4j2.15.0-rc1中修复了这个 bug。

因Log4j2漏洞的反复无常,导致某些公司已经切换到Logback来记录日志,但在Log4j2漏洞爆出后,Logback也爆出漏洞:在Logback1.2.7及之前的版本中,具有编辑配置文件权限的攻击者可以制作恶意配置,允许从LDAP服务器加载、执行任意代码。

解决方案为将Logback升级到安全版本:Logback1.2.9+

SpringBoot2.6.2以上的Logback版本已经升到了1.2.9,Log4j2的版本也升到了2.17.0,所以我们使用SpringBoot2.6.2以上版本无需担心Log4j2和Logback安全漏洞。

十二、SpringBoot项目部署

12.1 项目打包

SpringBoot项目是依赖于Maven构建的,但打包时如果只依赖Maven打包工具则会打包不完整,我们还需要在SpringBoot项目中引入SpringBoot打包插件 ,有些版本是自动带着该插件的。

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
  </plugins>
</build>

如果加上这个插件仍无法打包的话再加上下面的这个插件:

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <!--修改版本-->
                <version>3.1.0</version>
            </plugin>

打包好的jar在target目录下:

运行jar包:

  1. 进入jar包所在目录,使用cmd打开命令行窗口

  2. 输入命令:java -jar jar包名

 12.2 多环境配置

 在真实开发中,在不同环境下运行项目往往会进行不同的配置,比如开发环境使用的是开发数据库,测试环境使用的是测试数据库,生产环境使用的是生产数据库。SpringBoot支持不同环境下使用不同的配置文件,用法如下:

配置文件名:

application-环境名.properties/yml

1、application-dev.properties/yml 开发环境配置文件

# 开发环境端口号为8080
server:
  port: 8080

2、application-test.properties/yml 测试环境配置文件

# 测试环境端口号为8081
server:
  port: 8081

3、application-prod.properties/yml 生产环境配置文件

# 生产环境端口号为80
server:
  port: 80

运行jar包时选择环境:

java -jar jar包名 --spring.profiles.active=环境名

十三、SpringBoot容器化部署

13.1 安装docker环境

为了节约资源,在生产环境中我们更多的是使用Docker容器部署SpringBoot应用,首先我们准备Docker环境:

 1、准备一台centos7系统的虚拟机,连接虚拟机。

2、关闭虚拟机防火墙

# 关闭运行的防火墙
systemctl stop firewalld.service


# 禁止防火墙自启动
systemctl disable firewalld.service

13.2 Dockerfile制作镜像

由于在SpringBoot中嵌入了Web容器,所以在制作SpringBoot项目的镜像时无需依赖Web容器,基于JDK制作镜像即可,接下来我们使用Dockerfile制作镜像:

1、进入opt目录

cd /opt

2、使用rz命令(或者xftp)将项目Jar包上传至虚拟机

使用rz前提是下载lrsz,yum -y install lrzsz

3、编写DockerFile

# 基于JDK11
FROM openjdk:8
# 作者
MAINTAINER zj
# 拷贝到容器opt目录
ADD sb_logback.jar /opt
#保留端口
EXPOSE 8080
# 启动容器后执行的命令
CMD java -jar /opt/sb_logback.jar

4、构建镜像

docker build -t sb_logback .

5、查看所有的镜像,出现springbootdocker代表镜像构建成功

 6、使用镜像创建并启动容器

docker run -d -p 8080:8080 sb_logback

7、访问查看是否启动成功

13.3 Maven插件制作镜像

除了DockerFile,我们还可以使用Maven插件制作镜像。使用方法如下:

1、开启远程docker服务

# 修改docker配置文件
vim /lib/systemd/system/docker.service


# 在ExecStart=后添加配置,远程访问docker的端口为2375
ExecStart=/usr/bin/dockerd-current -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock \
     --add-runtime docker-runc=/usr/libexec/docker/docker-runc-current \
     --default-runtime=docker-runc \
     --exec-opt native.cgroupdriver=systemd \
     --userland-proxy-path=/usr/libexec/docker/docker-proxy-current \
     --init-path=/usr/libexec/docker/docker-init-current \
     --seccomp-profile=/etc/docker/seccomp.json \
     $OPTIONS \
     $DOCKER_STORAGE_OPTIONS \
     $DOCKER_NETWORK_OPTIONS \
     $ADD_REGISTRY \
     $BLOCK_REGISTRY \
     $INSECURE_REGISTRY \
     $REGISTRIES




# 重启docker
systemctl daemon-reload
systemctl restart docker

2、在项目的pom文件中添加docker-maven-plugin插件

     <!-- docker-maven-plugin-->
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>docker-maven-plugin</artifactId>
                <version>1.2.2</version>
                <configuration>
                    <!-- Docker路径 -->
                    <dockerHost>http://192.168.25.101:2375</dockerHost>
                    <!-- Dockerfile定义 -->
                    <baseImage>openjdk:8</baseImage>
                    <!-- 作者 -->
                    <maintainer>zj</maintainer>
                    <resources>
                        <resource>
                            <!-- 复制jar包到docker容器指定目录 -->
                            <targetPath>/opt</targetPath>
                            <!-- 从哪个包拷贝文件,target包 -->
                            <directory>${project.build.directory}</directory>
                            <!-- 拷贝哪个文件 -->
                            <include>${project.build.finalName}.jar</include>
                        </resource>
                    </resources>
                    <workdir>/</workdir>
                    <entryPoint>["java", "-jar", "${project.build.finalName}.jar","--spring.profiles.active=dev"]</entryPoint>
                    <forceTags>true</forceTags>
                    <!-- 镜像名 -->
                    <imageName>${project.artifactId}</imageName>
                    <!-- 镜像版本 -->
                    <imageTags>
                        <imageTag>${project.version}</imageTag>
                    </imageTags>
                </configuration>
            </plugin>

3、使用maven的package命令给项目打包

4、使用maven的docker插件制作镜像

 5、查看所有的镜像

 6、创建并访问容器

十四、Spring Task

定时任务即系统在特定时间执行一段代码,它的场景应用非常广泛:

  1. 购买游戏的月卡会员后,系统每天给会员发放游戏资源。

  2. 管理系统定时生成报表。

  3. 定时清理系统垃圾。

  4. ......

定时任务的实现主要有以下几种方式:

  1. Java自带的java.util.Timer类,这个类允许调度一个java.util.TimerTask任务。使用这种方式可以让程序按照某一个频度执行,但不能在指定时间运行。一般用的较少。
  2. Quartz。这是一个功能比较强大的的调度器,可以让程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂。
  3. Spring3.0以后自带Spring Task,可以将它看成一个轻量级的Quartz,使用起来比 Quartz简单许多,在课程中我们使用Spring Task实现定时任务

14.1 入门案例

1、创建SpringBoot项目,在启动类开启定时任务。

@SpringBootApplication
@EnableScheduling  //开启定时任务
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

2、编写定时任务类

@Component
public class MyTask {
    //定时方法,每秒实行一次
    @Scheduled(cron="* * * * * *")
    public void task1(){
        //当前时间
        System.out.println("当前时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }
}

3、运行

14.2 Cron表达式

Spring Task依靠Cron表达式配置定时规则。Cron表达式是一个字符串,分为6或7个域,每一个域代表一个含义,以空格隔开。有如下两种语法格式:

  1. Seconds Minutes   Hours  DayofMonth  Month DayofWeek    Year
  2. Seconds    Minutes   Hours    DayofMonth     Month DayofWeek

 Seconds(秒):域中可出现   - * 四个字符,以及0-59的整数

  • *:表示匹配该域的任意值,在Seconds域使用*,表示每秒钟都会触发
  • ,:表示列出枚举值。在Seconds域使用5,20,表示在5秒和20秒各触发一次。
 @Scheduled(cron="5,15,30,40 * * * * *") //每分钟的第5,15,30,40秒执行一次
  • -:表示范围。在Seconds域使用5-20,表示从5秒到20秒每秒触发一次
@Scheduled(cron="5-20 * * * * *")    //定时方法,每分钟的第5到20秒执行
  • /:表示起始时间开始触发,然后每隔固定时间触发一次。在Seconds域使用5/20, 表示5秒触发一次,25秒,45秒分别触发一次。
 @Scheduled(cron="5/10 * * * * *")//定时方法,在当前分钟的第5开始执行,每间隔10秒执行一次

Minutes(分):域中可出现, - * /四个字符,以及0-59的整数

Hours(时):域中可出现, - * /四个字符,以及0-23的整数

DayofMonth(日期):域中可出现, - * / ? L W C八个字符,以及1-31的整数

  • C:表示和当前日期相关联。在DayofMonth域使用5C,表示在5日后的那一天触发,且每月的那天都会触发。比如当前是10号,那么每月15号都会触发。 

@Scheduled(cron="0 0 0 5c * * *")//今天是1号,那么本月的6号0点和以后每月的6号0点都执行
  • L:表示最后,在DayofMonth域使用L,表示每个月的最后一天触发。

@Scheduled(cron="0 0 0 L * * *")//本月的最后一天0点执行
  • W:表示工作日,在DayofMonth域用15W,表示最接近这个月第15天的工作日触发,如果15号是周六,则在14号即周五触发;如果15号是周日,则在16号即周一触发;如果15号是周二则在当天触发。

    注:

    1. 该用法只会在当前月计算,不会到下月触发。比如在DayofMonth域用31W,31号是周日,那么会在29号触发而不是下月1号。
    2. 在DayofMonth域用LW,表示这个月的最后一个工作日触发。

Month(月份):域中可出现, - * /四个字符,以及1-12的整数或JAN-DEC的单词缩写

@Scheduled(cron="0 0 0 * 6-8 * *")//6-8月每天晚上的0点执行

DayofWeek(星期):可出现, - * / ? L # C八个字符,以及1-7的整数或SUN-SAT 单词缩写,1代表星期天,7代表星期六

  • C:在DayofWeek域使用2C,表示在2日后的那一天触发,且每周的那天都会触发。比如当前是周一,那么每周三都会触发。
  • L :在DayofWeek域使用L,表示在一周的最后一天即星期六触发。在DayofWeek域使用5L,表示在一个月的最后一个星期四触发。
  • #:用来指定具体的周数,#前面代表星期几,#后面代表一个月的第几周,比如5#3表示一个月第三周的星期四。
  • ?:在无法确定是具体哪一天时使用,用于DayofMonth和DayofWeek域。例如在每月的20日零点触发1次,此时无法确定20日是星期几,写法如下:0 0 0 20 * ?;或者在每月的最后一个周日触发,此时无法确定该日期是几号,写法如下:0 0 0 ? * 1L

Year(年份):域中可出现, - * /四个字符,以及1970~2099的整数。该域可以省略,表示每年都触发。

14.3 Cron实战案例

含义表达式
每隔5分钟触发一次0 0/5 * * * *
每小时触发一次0 0 * * * *
每天的7点30分触发0 30 7 * * *
周一到周五的早上6点30分触发0 30 7 ? * 2-6
每月最后一天早上10点触发0 0 10 L * ?
每月最后一个工作日的18点30分触发0 30 18 LW * ?
2030年8月每个星期六和星期日早上10点触发0 0 10 ? 8 1,7 2030
每天10点、12点、14点触发0 0 10,12,14 * * *
朝九晚五工作时间内每半小时触发一次0 0 0/30 9-17 ? * 2-6
每周三中午12点触发一次0 0 12 ? * 4
每天12点触发一次0 0 12 * * *
每天14点到14:59每分钟触发一次0 * 14 * * *
每天14点到14:59每5分钟触发一次0 0/5 14 * * *
每天14点到14:05每分钟触发一次0 0-5 14 * * *
每月15日上午10:15触发0 15 10 15 * ?
每月最后一天的上午10:15触发0 15 10 L * ?
每月的第三个星期五上午10:15触发0 15 10 ? * 6#3

14.4 @Scheduled

@Scheduled写在方法上方,指定该方法定时执行。常用参数如下:

  • cron:cron表达式,定义方法执行的时间规则。

  • fixedDelay:任务立即执行,之后每隔多久执行一次,单位是毫秒,上一次任务结束后计算下次执行的时间。

// 立即执行,任务结束后每5秒执行一次
@Scheduled(fixedDelay=5000)
public void task1() throws InterruptedException {
  SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
  Thread.sleep(1000);
  System.out.println(sdf.format(new Date()));
}
  • fixedRate:任务立即执行,之后每隔多久执行一次,单位是毫秒,上一次任务开始后计算下次执行的时间。
// 立即执行,之后每5秒执行一次
@Scheduled(fixedRate=5000)
public void task2() throws InterruptedException {
  SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
  Thread.sleep(1000);
  System.out.println(sdf.format(new Date()));
}
  • initialDelay:项目启动后不马上执行定时器,根据initialDelay的值延时执行。
// 项目启动3秒后执行,之后每5秒执行一次。
@Scheduled(fixedRate=5000,initialDelay = 3000)
public void task3() throws InterruptedException {
  SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
  Thread.sleep(1000);
  System.out.println(sdf.format(new Date()));
}

14.5 多线程任务

Spring Task定时器默认是单线程的,如果项目中使用多个定时器,使用一个线程会造成效率低下。代码如下:

@Scheduled(cron="* * * * * *")
private void task1() throws InterruptedException {
  System.out.println(Thread.currentThread().getId()+"线程执行任务1");
  Thread.sleep(5000);
}


@Scheduled(cron="* * * * * *")
private void task2() {
  System.out.println(Thread.currentThread().getId()+"线程执行任务2");
}

 任务1较浪费时间,会阻塞任务2的运行。此时我们可以给Spring Task配置线程池。

@Configuration
public class SchedulingConfig implements SchedulingConfigurer {
  public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
   // 创建线程池
   taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5));
  }
}

此时任务1不会阻塞任务2的运行。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐