SpringBoot 入门

文章目录

  • [1. 简介](#1. 简介)
    • [1.1 SpringFramework 解决了什么问题?没有解决什么问题?](#1.1 SpringFramework 解决了什么问题?没有解决什么问题?)
      • [SpringFramework 解决了什么问题?](#SpringFramework 解决了什么问题?)
      • [SpringFramework 没有解决什么问题?](#SpringFramework 没有解决什么问题?)
    • [1.2 SpringBoot 的概述](#1.2 SpringBoot 的概述)
      • [SpringBoot 解决上述 Spring 的缺点](#SpringBoot 解决上述 Spring 的缺点)
      • [SpringBoot 的特点](#SpringBoot 的特点)
      • [SpringBoot 的核心功能](#SpringBoot 的核心功能)
  • [2. HelloWorld](#2. HelloWorld)
    • [2.1 创建 SpringBoot Web 应用](#2.1 创建 SpringBoot Web 应用)
    • [2.2 初始化后内容](#2.2 初始化后内容)
    • [2.3 添加一个控制层](#2.3 添加一个控制层)
    • [2.4 运行你的第一个程序](#2.4 运行你的第一个程序)
    • [2.5 一些思考](#2.5 一些思考)
      • [为什么我们添加一个 starter-web 模块便可以了呢?](#为什么我们添加一个 starter-web 模块便可以了呢?)
      • [我们如何更改更多 Server 的配置呢?比如 Tomcat Server](#我们如何更改更多 Server 的配置呢?比如 Tomcat Server)
      • [SpringBoot 还提供了哪些 starter 模块呢?](#SpringBoot 还提供了哪些 starter 模块呢?)
  • [3. 对 Hello World 进行 MVC 分层](#3. 对 Hello World 进行 MVC 分层)
    • [3.1 经典的 MVC 三层架构](#3.1 经典的 MVC 三层架构)
    • [3.2 用 Package 解耦三层结构](#3.2 用 Package 解耦三层结构)
    • [3.3 运行测试](#3.3 运行测试)
  • [4. 定制自己的 Banner](#4. 定制自己的 Banner)
    • [4.1 什么是 Banner](#4.1 什么是 Banner)
    • [4.2 如何更改 Banner](#4.2 如何更改 Banner)
    • [4.3 文字 Banner 的设计](#4.3 文字 Banner 的设计)
      • [一些设计 Banner 的网站](#一些设计 Banner 的网站)
      • [IDEA 中 Banner 的插件](#IDEA 中 Banner 的插件)
      • 其它工具
    • [4.4 Banner 中其它配置信息](#4.4 Banner 中其它配置信息)
    • [4.5 动画 Banner 的设计](#4.5 动画 Banner 的设计)
    • [4.6 进一步思考](#4.6 进一步思考)
      • [图片 Banner 是如何起作用的?](#图片 Banner 是如何起作用的?)
  • [5. 添加 Logback 日志](#5. 添加 Logback 日志)
  • [6. 配置热部署 devtools 工具](#6. 配置热部署 devtools 工具)
    • [6.1 概述](#6.1 概述)
    • [6.2 配置 devtools 实现热部署](#6.2 配置 devtools 实现热部署)
      • [POM 配置](#POM 配置)
      • [IDEA 配置](#IDEA 配置)
      • [application.yml 配置](#application.yml 配置)
      • [使用 LiveLoad](#使用 LiveLoad)
    • [6.3 进一步理解](#6.3 进一步理解)
      • [devtool 的原理?为何会自动重启?](#devtool 的原理?为何会自动重启?)
      • [devtool 是否会被打包进 jar?](#devtool 是否会被打包进 jar?)
      • [devtool 为何会默认禁用缓存选项?](#devtool 为何会默认禁用缓存选项?)
      • [devtool 是否可以给所有 Springboot 应用做全局的配置?](#devtool 是否可以给所有 Springboot 应用做全局的配置?)
      • [如果不用 devtool,还有什么选择?](#如果不用 devtool,还有什么选择?)
  • [7. 常用注解](#7. 常用注解)

1. 简介

前面我们已经了解了 SpringFramework,那为什么有了 SpringFramework 还会诞生 SpringBoot?简单而言,因为虽然 Spring 的组件代码是轻量级的,但它的配置却是重量级的;所以 SpringBoot 的设计策略是通过开箱即用约定大于配置来解决配置重的问题。

1.1 SpringFramework 解决了什么问题?没有解决什么问题?

需要概括性的理解 SpringFramework 解决了什么问题,没有解决什么问题?

SpringFramework 解决了什么问题?

​ Spring 是 Java 企业版(Java Enterprise Edition,JEE,也称 J2EE)的轻量级代替品。无需开发重量级的 EnterpriseJavaBean(EJB),Spring 为企业级 Java 开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,用简单的 Java 对象(Plain Old Java Object,POJO)实现了 EJB 的功能。

  1. 使用 Spring 的 IOC 容器,将对象之间的依赖关系交给 Spring,降低组件之间的耦合性,让我们更专注于应用逻辑。
  2. 可以提供众多服务,事务管理,WS 等。
  3. AOP 的很好支持,方便面向切面编程。
  4. 对主流的框架提供了很好的集成支持,如 Hibernate,Struts2,JPA 等。
  5. Spring DI 机制降低了业务对象替换的复杂性。
  6. Spring 属于低侵入,代码污染极低。
  7. Spring 的高度开放性,并不强制依赖于 Spring,开发者可以自由选择 SPring 部分或全部。

SpringFramework 没有解决什么问题?

​ 虽然 Spring 的组件代码是轻量级的,但它的配置却是重量级的。一开始,Spring 用 XML 配置,而且是很多 XML 配置。Spring 2.5 引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件的显示 XML 配置。Spring 3.0 引入了基于 Java 的配置,这是一种类型安全的可重构配置方式,可以代替 XML。

​ 所有这些配置都代表了开发时的损耗。因为在思考 Spring 特性配置和解决业务问题之间需要进行思维切换,所以编写配置挤占了编写应用程序逻辑的时间。所有框架一样,Spring 实用,但与此同时它要求的回报也不少。

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

  1. JSP 中要写很多代码、控制器过于灵活,缺少一个公用控制器。
  2. Spring 不支持分布式,这也是 EJB 仍然在用的原因之一。

1.2 SpringBoot 的概述

SpringBoot 解决上述 Spring 的缺点

​ SpringBoot 对上述 Spring 的缺点进行了改善和优化,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心地投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期。

SpringBoot 的特点

  1. 为基于 Spring 的开发提供更快的入门体验;
  2. 开箱即用,没有代码生成,也无需 XML 配置。同时也可以修改默认值来满足特定的需求;
  3. 提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标、监控检测、外部配置等。

​ SpringBoot 不是对 Spring 功能上的增强,而是提供了一种快速使用 Spring 的方式。

SpringBoot 的核心功能

  • 起步依赖:起步依赖本质上是一个 Maven 项目对象模型(Project Object Model,POM),定义了对其它库的依赖传递,这些东西加在一起即支持某项功能;

​ 简单来说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能。

  • 自动配置

​ SpringBoot 的自动配置是一个运行时(更准确地说,是应用程序启动时)的过程,考虑了众多因素,才决定 Spring 配置应该用哪个,不该用哪个。该过程是 Spring 自动完成的。

2. HelloWorld

我们了解了 SpringBoot 和 SpringFramework 的关系之后,我们可以开始创建一个 Hello World 级别的项目了。

2.1 创建 SpringBoot Web 应用

为快速进行开发,推荐使用 IDEA 这类开发工具,它将大大提升你学习和开发的效率。

  • 选择 Spring Boot,填写 GAV 三元组

Group:是公司或组织的名称,是一个命名空间的概念。

Artifat:当前项目的唯一标识。

Version:项目的版本号,一般 xx-SNAPSHOT 表示非稳定版。

​ Spring 提供的初始化项目的工具。

​ 当然也可以在 Spring Initializr 中初始化你项目工程(但官网已不支持 SpringBoot 的 2.X 版本初始化)。

如果想进行 SpringBoot 2.X 版本的初始化,可在 https://start.aliyun.com/ 进行初始化项目工程。

  • 选择初始化模板

​ Spring Initialize 可以帮助你选择常见的功能模块的 starter 包,就可以初始化项目了。

2.2 初始化后内容

pom.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.13</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>io.zhanbo</groupId>
    <artifactId>springboot-helloworld</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <java.version>1.8</java.version>
        <springboot.version>2.6.13</springboot.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2.3 添加一个控制层

​ 我们添加如下代码,启动即可启动一个 WEB 服务,通过浏览器访问 /hello,并返回 Hello World。

java 复制代码
@RestController
public class Controller {
    @GetMapping("/hello")
    public ResponseEntity<String> hello() {
        return new ResponseEntity<>("Hello World", HttpStatus.OK);
    }
}

2.4 运行你的第一个程序

​ 点击 SpringBootHelloWorldApplication 入口的绿色按钮,运行程序。

​ 运行后,你将看到如下信息:表明我们启动程序成功(启动了一个内嵌的 Tomcat 容器,服务端口在 8080)

​ 这时候我们便可以通过浏览器访问服务。

2.5 一些思考

到此,你会发现一个简单的 web 程序居然完成了。这里你需要一些思考:

为什么我们添加一个 starter-web 模块便可以了呢?

​ 我们安装 Maven Helper 的插件,用来查看 spring-boot-starter-web 模块的依赖。

​ 我们看下这个模块的依赖,你便能初步窥探出模块支撑。

我们如何更改更多 Server 的配置呢?比如 Tomcat Server

​ 为什么 Tomcat 默认端口是 8080 呢?如前文所述,SpringBoot 最强大的地方在于约定大于配置,只要你引入某个模块的 xx-start 包,它将自动注入配置,提供了这个模块的功能;比如这里我们在 POM 中添加了如下的包。

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

​ 它内嵌了 Tomcat 并且提供了默认的配置,比如默认端口是 8080

​ 我们可以在 Application.properties 或者 application.yml 中配置

​ 特别的,如果你添加了如下包:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

​ 并且你的 IDE 支持,可以自动给你配置提示。

​ 你也可以(cmd + 点击)进入具体的配置文件。

SpringBoot 还提供了哪些 starter 模块呢?

​ Spring Boot 推荐的基础 POM 文件:

名称 说明
spring-boot-starter 核心 POM,包含自动配置支持、日志库和对 YAML 配置文件的支持。
spring-boot-starter-amqp 通过 spring-rabbit 支持 AMQP。
spring-boot-starter-aop 包含 spring-aop 和 AspectJ 来支持面向切面编程(AOP)。
spring-boot-starter-batch 支持 Spring Batch,包含 HSQLDB。
spring-boot-starter-data-jpa 包含 spring-data-jpa、spring-orm 和 Hibernate 来支持 JPA。
spring-boot-starter-data-mongodb 包含 spring-data-mongodb 来至此 MongoDB。
spring-boot-starter-data-rest 通过 spring-data-rest-webmvc 支持以 REST 方式暴露 SpringData 仓库。
spring-boot-starter-jdbc 支持使用 JDBC 访问数据库。
spring-boot-starter-security 包含 Spring-security。
spring-boot-starter-test 包含常见的测试所需的依赖,如 JUnit、Hamcrest、Mockito 和 spring-test 等。
spring-boot-starter-velocity 支持使用 Velocity 作为模板引擎。
spring-boot-starter-web 支持 Web 应用开发,包含 Tomcat 和 spring-mvc。
spring-boot-starter-websocket 支持使用 Tomcat 开发 WebSocket 应用。
spring-boot-starter-ws 支持 Spring Web Services。
spring-boot-starter-actuator 添加适用于生成环境的功能,如性能指标和检测等功能。
spring-boot-starter-remote-shell 添加远程 SSH 支持。
spring-boot-starter-jetty 使用 Jetty 而不是默认的 Tomcat 作为应用服务。
spring-boot-starter-log4j 添加 Log4j 的支持。
spring-boot-starter-logging 使用 Spring Boot 默认的日志框架 Logback。
spring-boot-starter-tomcat 使用 Spring Boot 默认的 Tomcat 作为应用服务器。

​ 所有这些 POM 依赖的好处在于为开发 Spring 应用提供了一个良好的基础。Spring Boot 所选择的是第三方库是经过考虑的,是比较适合产品开发的选择。但是 Spring Boot 也通过了不同的选项,比如日志框架可以用 Logback 或 Log4j,应用服务器可以用 Tomcat 或 Jetty。

3. 对 Hello World 进行 MVC 分层

本节结合常见的 MVC 分层思路学习常见的包结构划分。

3.1 经典的 MVC 三层架构

​ 三层架构(3-tier application)通常意义上的三层架构就是将整个业务应用划分为:表现层(UI)、业务逻辑层(BLL)、数据访问层(DAL)。区别层次的目的即为了 "高内聚、低耦合" 的思想。

  1. 表现层(UI):通俗讲就是展现给用户的界面,即用户在使用一个系统的时候他的所见所得。
  2. 业务逻辑层(BLL):针对具体问题的操作,也可以锁是对数据层的操作,对数据业务逻辑处理。
  3. 数据访问层(DAL):该层所做事务直接操作数据库,针对数据的增删改查等。

3.2 用 Package 解耦三层结构

​ 我们这里设计一个常见的用户增删改查项目,通常来说对应的包结构如下:

controller

​ 表示层

java 复制代码
@RestController
@RequestMapping("user")
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("add")
    public User add(User user) {
        userService.addUser(user);
        return user;
    }

    @GetMapping("list")
    public List<User> list() {
        return userService.list();
    }
}

service

​ 业务逻辑层

java 复制代码
@Service
public class IUserServiceImpl implements UserService {
    @Autowired
    private UserRepository userDao;

    @Override
    public void addUser(User user) {
        userDao.save(user);
    }

    @Override
    public List<User> list() {
        return userDao.findAll();
    }
}

dao

​ 数据访问层,数据放在内存中。

java 复制代码
@Repository
public class UserRepository {
    private List<User> userDemoList = new ArrayList<>();

    public void save(User user) {
        userDemoList.add(user);
    }

    public List<User> findAll() {
        return userDemoList;
    }
}

entity

​ model 实体层

java 复制代码
public class User {
    private int userId;

    private String userName;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

3.3 运行测试

​ 添加用户

​ 查询用户列表

我们在启动 Spring Boot 程序时,有 SpringBoot 的 Banner 信息,那么如何自定义成自己项目的信息呢?

​ 我们在启动 Spring Boot 程序时,有如下 Banner 信息:

​ 那么如何自定义成自己项目的名称呢?

更改 Banner 有如下几种方式:

  • banner.txt 配置(最常用)

​ 在 application.yml 中添加配置

yml 复制代码
spring:
  banner:
    charset: UTF-8
    location: classpath:banner.txt

​ 在 resource 下创建 banner.txt,内容自定义:

txt 复制代码
----- Hello World -----
    This is Test !
-----------------------

​ 修改后,重启的 app 的效果

  • SpringApplication 启动时设置参数
java 复制代码
        SpringApplication application = new SpringApplication();
        application.setBannerMode(Banner.Mode.OFF);
        SpringApplication.run(SpringbootHelloWorldApplication.class, args);

​ SpringApplication 还可以设置自定义的 Banner 的接口类

如何设计上面的文字呢?

​ 可以通过这个网站进行设计:https://patorjk.com/software/taag

​ 例如:

​ 修改 banner.txt 运行效果如下:

其它工具

https://www.degraeve.com/img2txt.php

https://www.bootschool.net/ascii

除了文件信息,还有哪些信息可以配置呢?比如 Spring 默认还带有 SpringBoot 当前的版本号信息。

​ 在 banner.txt 中,还可以进行一些设置:

java 复制代码
# SpringBoot 的版本号
${spring-boot.version}

# SpringBoot 的版本号前面加 v,前后带括号
${spring-boot.formatted-version}

# MANIFEST.MF 文件中的版本号
${application.version}

# MANIFEST.MF 文件的版本号前面加 v,前后带括号
${application.formatted-version}

# MANIFEST.MF 文件中的程序名称
${application.title}

# ANSI 样色或样式等
${Ansi.NAME} (or ${AnsiColor.NAME}, ${AnsiBackground.NAME}, ${AnsiStyle.NAME})

​ 简单测试如下:(注意必须打包出Jar, 才会生成resources/META-INF/MANIFEST.MF)

那能不能设置动态的 Banner 呢?比如一个图片?

​ SpringBoot2 是支持图片形式的 Banner,

yml 复制代码
spring:
  main:
    banner-mode: console
    show-banner: true
  banner:
    charset: UTF-8
    image:
      margin: 0
      height: 10
      invert: false
      location: classpath:banner.png

​ 效果如下:(需要选择合适的照片,不然效果不好,所以这种方式很少使用)

​ 注意:格式不能太大,不然会报错。

java 复制代码
org.springframework.boot.ImageBanner     : Image banner not printable: class path resource [banner.gif] (class java.lang.ArrayIndexOutOfBoundsException: '4096')

4.6 进一步思考

发现 SpringBoot 可以把图片转换成 ASCII 图案,那么它是怎么做的呢?以此为例,我们看下 Spring 的 Banner 是如何生成的呢?

  • 获取 Banner
    • 优先级是环境变量中的 image 优先,格式在 IMAGE_EXTENSION 中
    • 然后才是 banner.txt
    • 没有的话就用 SpringBootBanner
  • 如果是图片
    • 获取图片 Banner(属性配置等)
    • 转换成 ascii

获取 banner

java 复制代码
class SpringApplicationBannerPrinter {
    static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";
    static final String BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location";
    static final String DEFAULT_BANNER_LOCATION = "banner.txt";
    static final String[] IMAGE_EXTENSION = new String[]{"gif", "jpg", "png"};
    private static final Banner DEFAULT_BANNER = new SpringBootBanner();
    private final ResourceLoader resourceLoader;
    private final Banner fallbackBanner;
    
    ...
        
    // 获取 Banner,优先级是环境变量中的 Image' 优先,格式在 IMAGE_EXTENSION 中,然后才是 banner.txt
    private Banner getBanner(Environment environment) {
        Banners banners = new Banners();
        banners.addIfNotNull(this.getImageBanner(environment));
        banners.addIfNotNull(this.getTextBanner(environment));
        if (banners.hasAtLeastOneBanner()) {
            return banners;
        } else {
            return this.fallbackBanner != null ? this.fallbackBanner : DEFAULT_BANNER;
        }
    }
    
    ...
}

获取图片 Banner

java 复制代码
private Banner getImageBanner(Environment environment) {
    String location = environment.getProperty("spring.banner.image.location");
    if (StringUtils.hasLength(location)) {
        Resource resource = this.resourceLoader.getResource(location);
        return resource.exists() ? new ImageBanner(resource) : null;
    } else {
        for(String ext : IMAGE_EXTENSION) {
            Resource resource = this.resourceLoader.getResource("banner." + ext);
            if (resource.exists()) {
                return new ImageBanner(resource);
            }
        }

        return null;
    }
}

获取图片的配置等

java 复制代码
private void printBanner(Environment environment, PrintStream out) throws IOException {
    int width = (Integer)this.getProperty(environment, "width", Integer.class, 76);
    int height = (Integer)this.getProperty(environment, "height", Integer.class, 0);
    int margin = (Integer)this.getProperty(environment, "margin", Integer.class, 2);
    boolean invert = (Boolean)this.getProperty(environment, "invert", Boolean.class, false);
    AnsiColors.BitDepth bitDepth = this.getBitDepthProperty(environment);
    PixelMode pixelMode = this.getPixelModeProperty(environment);
    Frame[] frames = this.readFrames(width, height);// 读取图片的帧

    for(int i = 0; i < frames.length; ++i) {
        if (i > 0) {
            this.resetCursor(frames[i - 1].getImage(), out);
        }

        this.printBanner(frames[i].getImage(), margin, invert, bitDepth, pixelMode, out);
        this.sleep(frames[i].getDelayTime());
    }

}

转换成 ascii

java 复制代码
private void printBanner(BufferedImage image, int margin, boolean invert, AnsiColors.BitDepth bitDepth, PixelMode pixelMode, PrintStream out) {
    AnsiElement background = invert ? AnsiBackground.BLACK : AnsiBackground.DEFAULT;
    out.print(AnsiOutput.encode(AnsiColor.DEFAULT));
    out.print(AnsiOutput.encode(background));
    out.println();
    out.println();
    AnsiElement lastColor = AnsiColor.DEFAULT;
    AnsiColors colors = new AnsiColors(bitDepth);

    for(int y = 0; y < image.getHeight(); ++y) {
        for(int i = 0; i < margin; ++i) {
            out.print(" ");
        }

        for(int x = 0; x < image.getWidth(); ++x) {
            Color color = new Color(image.getRGB(x, y), false);
            AnsiElement ansiColor = colors.findClosest(color);
            if (ansiColor != lastColor) {
                out.print(AnsiOutput.encode(ansiColor));
                lastColor = ansiColor;
            }

            out.print(this.getAsciiPixel(color, invert, pixelMode)); // 像素点转换成字符输出
        }

        out.println();
    }

    out.print(AnsiOutput.encode(AnsiColor.DEFAULT));
    out.print(AnsiOutput.encode(AnsiBackground.DEFAULT));
    out.println();
}

5. 添加 Logback 日志

SpringBoot 开发中如何选用日志框架呢?出于性能等原因,Logback 目前是 SpringBoot 应用日志的标配;当然有时候在生产环境中也会考虑和三方中间件采用统一处理方式。

5.1 日志框架的基础

​ 在学习这一部分时需要了解一些日志框架的发展和基础,同时了解日志配置时考虑的因素。

关于日志框架(日志门面)

Java 日志库是最能体现 Java 库在进化中的渊源关系的,在理解时重点理解日志框架本身日志门面,以及比较好的实践等。要关注其历史渊源和设计(比如桥接),而具体在使用时查询接口即可,否者会陷入 JUL(Java Util Log),JCL(Commons Loggings),Log4j,SLF4J,Logback,Log4j2 傻傻分不清楚的境地。

​ 关于日志框架(日志门面)可以看:日志类库详解

配置时考虑点

在配置日志时需要考虑哪些因素?

  • 支持日志路径,日志 level 等配置
  • 日志控制配置通过 application.yml 下发
  • 按天生成日志,当前的日志 > 50 MB 回滚
  • 最多保存 10 天日志
  • 生成的日志中 Pattern 自定义
  • Pattern 中添加用户自定义的 MDC 字段,比如用户信息(当前日志是由哪个用户的请求产生),request 信息。此种方式可以通过 AOP 切面控制,在 MDC 中添加 requestID,在 spring-logback.xml 中配置 Pattern
  • 根据不同的运行环境设置 Profile - dev,test,product
  • 对控制台,Err 和全量日志分别配置
  • 对第三方包路径日志控制

5.2 实现范例

application.yml

yml 复制代码
server:
  port: 8080

logging:
  level:
    root: debug
  file:
    path: classpath:/logs/log
    
spring:
  application:
    name: springboot-logback
    
debug: false

Spring-logback.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!-- 日志根目录 -->
    <springProperty scope="context" name="LOG_HOME" source="logging.path" defaultValue="/data/logs/springboot-logback"/>

    <!-- 日志级别 -->
    <springProperty scope="context" name="LOG_ROOT_LEVEL" source="logging.level.root" defaultValue="DEBUG"/>

    <!-- 标识这个 "STDOUT" 将会添加到这个 logger -->
    <springProperty scope="context" name="STDOUT" source="log.stdout" defaultValue="STDOUT"/>

    <!-- 日志文件名称 -->
    <property name="LOG_PREFIX" value="spring-boot-logback"/>

    <!-- 日志文件编码 -->
    <property name="LOG_CHARSET" value="UTF-8"/>

    <!-- 日志文件路径 + 日期 -->
    <property name="LOG_DIR" value="${LOG_HOME}/%d{yyyyMMdd}"/>

    <!-- 对日志进行格式化 -->
    <property name="LOG_MSG" value="- | [%X{requestUUID}] | [%d{yyyyMMdd HH:mm:ss.SSS}] | [%level] | [${HOSTNAME}] | [%thread] | [%logger{36}] | --> %msg|%n "/>

    <!-- 文件大小,默认 10 MB -->
    <property name="MAX_FILE_SIZE" value="50MB"/>

    <!-- 配置日志的滚动时间,表示只保留最近 10 天的日志 -->
    <property name="MAX_HISTORY" value="10"/>

    <!-- 输出到控制台 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 输出的日志内容格式化 -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>${LOG_MSG}</pattern>
        </layout>
    </appender>

    <!-- 输出到文件 -->
    <appender name="0" class="ch.qos.logback.core.rolling.RollingFileAppender"/>

    <!-- 定义 ALL 日志的输出方式 -->
    <appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 日志文件路径,日志文件名称 -->
        <File>${LOG_HOME}/all_${LOG_PREFIX}.log</File>
        <!-- 设置滚动策略,当天的日志大小超过 ${MAX_FILE_SIZE} 文件大小时,新的内容写入新的文件,默认大小为 10MB -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

            <!-- 日志文件路径,新的 ALL 日志文件名称,"i" 是个变量 -->
            <FileNamePattern>${LOG_DIR}/all_${LOG_PREFIX}%i.log</FileNamePattern>

            <!-- 配置日志的滚动时间,表示只保留最近 10 天的日志 -->
            <MaxHistory>${MAX_HISTORY}</MaxHistory>

            <!-- 当天的日志大小超过 ${MAX_FILE_SIZE} 文件大小时候,新的内容写入新的文件,默认大小为 10MB -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>

        <!-- 输出的日志内容格式化 -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>${LOG_MSG}</pattern>
        </layout>
    </appender>

    <!-- 定义 ERROR 日志的输出方式 -->
    <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 下面为配置只输出 error 级别的日志 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMismatch>DENY</onMismatch>
            <onMatch>ACCEPT</onMatch>
        </filter>

        <!-- 日志文件路径,日志文件名称 -->
        <File>${LOG_HOME}/err_${LOG_PREFIX}.log</File>

        <!-- 设置滚动策略,当天的日志大小超过 ${MAX_FILE_SIZE} 文件大小时候,新的内容写入新的文件,默认大小为 10MB -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志文件路径,新的 ERR 日志文件名称,"i" 是个变量 -->
            <FileNamePattern>${LOG_DIR}/err_${LOG_PREFIX}%i.log</FileNamePattern>

            <!-- 配置日志的滚动时间,表示只保留最近 10 天的日志 -->
            <MaxHistory>${MAX_HISTORY}</MaxHistory>

            <!-- 当天的日志大小超过 ${MAX_FILE_SIZE} 文件大小时候,新的内容写入新的文件,默认大小为 10MB -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>

        <!-- 输出的日志内容格式化 -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>${LOG_MSG}</pattern>
        </layout>
    </appender>

    <!-- additivity 设为 false,则 logger 内容不附加至 root,配置以配置包下的所有类的日志的打印,级别是 ERROR -->
    <logger name="org.springframework" level="ERROR"/>
    <logger name="org.apache.commons" level="ERROR"/>

    <!-- ${LOG_ROOT_LEVEL} 日志级别-->
    <root level="${LOG_ROOT_LEVEL}">
        <!-- 标识这个 "${STDOUT}" 将会添加到这个 logger -->
        <appender-ref ref="${STDOUT}"/>
        <!-- FILE_ALL 日志输出添加到 logger -->
        <appender-ref ref="FILE_ALL"/>
        <!-- FILE_ERROR 日志输出添加到 logger -->
        <appender-ref ref="FILE_ERROR"/>
    </root>
</configuration>

​ Profile 相关的配置:

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
	<include resource="org/springframework/boot/logging/logback/base.xml"/>
    <!-- roll by day -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/springboot-logback.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    
    <!-- dev -->
    <logger name="org.springframework.web" level="INFO"/>
    <root level="INFO">
        <appender-ref ref="FILE"/>
    </root>
    
    <!-- test or production -->
    <springProfile name="test,prod">
        <logger name="org.springframework.web" level="INFO"/>
        <logger name="io.zhanbo.springboot" level="INFO"/>
        <root level="INFO">
            <appender-ref ref="FILE"/>
        </root>
    </springProfile>
    
</configuration>

5.3 参考文档

  • Logback 官网

Chapter 6: Layouts

  • Logback 官网文档

Logback Manual

  • Logback 中 Encoder Pattern
xml 复制代码
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>

6. 配置热部署 devtools 工具

在 SpringBoot 开发调试中,如果每行代码的修改都需要重新启动再调试,可能比较费时间;SpringBoot团队针对此问题提供了 spring-boot-devtools(简称 devtools)插件,它视图提升开发调试的效率。

6.1 概述

什么是热部署和热加载?

热部署和热加载是在应用正常运行的时候,自动更新(重新加载或者替换 class 等)应用的一种能力。(PS:spring-boot-devtools 提供的方案也是要重启的,只是无需手动重启能实现加载而已。)

​ 严格意义上,我们需要区分下热部署和热加载。对于 Java 项目而言:

  • 热部署
    • 在服务器运行时重新部署项目;
    • 它是直接重新加载整个应用,这种方式会释放内存,比热加载更加干净彻底,但同时也更费时间。
  • 热加载
    • 在运行时重新加载 class,从而升级应用;
    • 热加载的实现原理主要依赖 Java 类加载机制,在实现方式可以概括为在容器启动的时候起一条后台线程,定时的检测类文件的时间戳变化,如果类的时间戳变掉了,则将类重新载入;
    • 对比反射机制,反射是在运行时获取类信息,通过动态的调用来改变程序行为;热加载则是在运行时通过重新加载改变类信息,直接改变程序行为。

什么是 LiveLoad?

​ LiveLoad 是提供浏览器客户端自动加载更新的工具,分为 LiveLoad 服务器和 LiveLoad 浏览器插件两部分;devtools 中已经集成了 LiveLoad 服务器,所以如果我们开发的是 web 应用,并且期望浏览器自动刷新,这时候可以考虑 LiveLoad。

​ 同一时间只能运行一个 LiveReload 服务器。开始应用程序之前,请确保没有其它 LiveReload 服务器正在运行。如果从 IDE 启动多个应用程序,则只有第一个应用程序将支持 LiveReload。

6.2 配置 devtools 实现热部署

我们通过如下配置来实现自动重启方式的热部署

POM 配置

​ 添加依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional> <!-- 可以防止将 devtools 依赖传递到其它模块中 -->
</dependency>

IDEA 配置

如果你使用 IDEA 开发工具,通常有如下两种方式:

  • 方式一:无任何配置时,手动触发重启更新(Ctrl + F9)

(也可以用 mvn compile 编译触发重启更新)

  • 方式二:IDEA 需开启运行时编译,自动重启更新

    1. 设置 1:File -> Build,Execution,Deployment -> Compile

      勾选:Mark project automatically

    2. 设置 2:

      1. 旧版 IDEA:ctrl + alt + shift + /,选择:Registry,勾选:compiler.automake.allow.when.app.running
      2. 新版 IDEA:File -> setting -> Advanced Settings

application.yml 配置

yml 复制代码
spring:
  devtools:
    restart:
      enabled: true # 开启热部署
      additional-paths: src/main/java # 重启目录
      exclude: WEB-INF/** # 排除目录
  thymeleaf:
    cache: false # 关闭 thymeleaf 缓存

使用 LiveLoad

​ spring-boot-devtools 模块包含嵌入式 LiveReload 服务器,可以在资源更改时用于触发浏览器刷新。LiveReload 浏览器拓展程序支持 Chrome,Firefox 和 Safari。

​ 如果你不想在应用程序运行时启动 LiveReload 服务器,则可以将 spring.devtools.livereload.enabled 属性设置为 false。

​ 同一时间只能运行一个 LiveReload 服务器。开始应用程序之前,请确保没有其它 LiveReload 服务器正在运行。如果从 IDE 启动多个应用程序,则只有第一个应用程序将支持 LiveReload。

6.3 进一步理解

虽然一些开发者会使用 devtool 工具,但是很少有人能够深入理解的;让我们理解如下几个问题,帮助你进一步理解。

devtool 的原理?为何会自动重启?

为什么同样是重启应用,为什么不手动重启,而是建议使用 spring-boot-devtools 进行热部署重启?

​ spring-boot-devtools 使用了两个类加载器 ClassLoader,一个 ClassLoader 加载不会发生更改的类(第三方 jar 包),另一个 ClassLoader(restart ClassLoader)加载会更改的类(自定义的类)。

后台启动一个**文件监听线程(Filter Watcher),监测的目录中的文件发生变动时,原来的 restart ClassLoader 被丢弃,将会重新加载新的 restart ClassLoader**。

​ 因为文件变动后,第三方 jar 包不再重新加载,只加载自定义的类,加载的类比较少,所以重启比较快。

这也是为什么,同样是重启应用,为什么不手动重启,建议使用 spring-boot-devtools 进行热部署重启。

​ 在自动重启中有几点需要注意:

  • 自动重启会记录日志的:记录在什么情况下重启的日志

​ 可以通过如下关闭:

yml 复制代码
spring:
  devtools:
    restart:
      log-condition-evaluation-delta: false
  • 排除一些不需要自动重启的资源

​ 某些资源在更改时不一定需要触发重新启动。默认情况下,改变资源 /META-INF/maven/META-INF/resources/resources/static/public/templates 不触发重新启动,但却会触发现场重装。如果要自定义这些排除项,可以使用该 spring.devtools.restart.exclude 属性。例如,要仅排除 /static/public 可以设置以下属性:

yml 复制代码
spring:
  devtools:
    restart:
      exclude: "static/**,public/**"

​ 如果要保留这些默认值并添加其它排除项,请改用该 spring.devtools.restart.additional-exclude 属性。

  • 自定义重启类加载器

​ 重启功能是通过使用两个类加载器来实现的。对于大多数应用程序,这种方法效果很好。但是,它有时会导致类加载问题。

​ 默认情况下,IDE 中的任何打开项目都使用 "重启" 类加载器加载,任何常规 .jar 文件都使用 "基本" 类加载器加载。如果你处理一个多模块项目,并且不是每个模块都导入到你的 IDE 中,你可能需要自定义一些东西。为此,你可以创建一个 META-INF/spring-devtools.properties 文件。

​ 该 spring-devtools.properties 文件可以包含以 restart.excluderestart.include 为前缀的属性。该 include 元素是应该被拉高到 "重启" 的类加载器的项目,以及 exclude 要素是应该向下推入 "Base" 类加载器的项目。该属性的值是应用于类路径的正则表达式模板,如以下示例所示:

yml 复制代码
restart:
  exclude:
    companycommonlibs: "/mycorp-common-[\\w\\d-\\.]/(build|bin|out|target)/"
  include:
    projectcommon: "/mycorp-myproj-[\\w\\d-\\.]+\\.jar"

更多信息查看:Developer Tools :: Spring Boot

devtool 是否会被打包进 jar?

devtool 原则上来说应该是只在开发调试的时候使用,而在生产环境运行 jar 包时是不需要的,所以 Spring 打包会不会把它打进 JAR 呢?

  • 默认情况下,不会被打包进 JAR

​ 运行打包的应用程序时,开发人员工具会自动禁用。如果通过 java -jar` 或者其它特殊的类加载器进行启动时,都会被认为是 "生产环境的应用"。

  • 如果我们期望远程调试应用

生产环境勿用,只有在受信任的网络上运行或使用 SSL 进行保护时,才应启用它。

​ 在这种情况下,devtool 也具备远程调试的能力:远程客户端应用程序旨在从你的 IDE 中运行。你需要 org.springframework.boot.devtools.RemoteSpringApplication 使用与你连接的远程项目相同的类路径运行,应用程序的唯一必须参数是它连接到的远程 URL。

​ 例如,如果使用 IDEA 执行以下操作:

  1. 选择 Run Configurations ... 从 Run 菜单;
  2. 创建一个新的 Java Application "启动配置";
  3. 浏览 springboot-helloworld 项目;
  4. 使用 org.springframework.boot.devtools.RemoteSpringApplication 作为主类;
  5. 添加 http://localhost:8080Program arguments(或任何你的远程 URL)。

​ 正在运行的远程客户端可能类似于以下列表:

  .   ____          _                                              __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _          ___               _      \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` |        | _ \___ _ __  ___| |_ ___ \ \ \ \
 \\/  ___)| |_)| | | | | || (_| []::::::[]   / -_) '  \/ _ \  _/ -_) ) ) ) )
  '  |____| .__|_| |_|_| |_\__, |        |_|_\___|_|_|_\___/\__\___|/ / / /
 =========|_|==============|___/===================================/_/_/_/
 :: Spring Boot Remote ::  (v2.6.13)

2024-12-26 15:08:31.173  INFO 25848 --- [           main] o.s.b.devtools.RemoteSpringApplication   : Starting RemoteSpringApplication v2.6.13 using Java 1.8.0_351 on LAPTOP-T8OAQR71 with PID 25848 (D:\Maven\apache-maven-3.6.1\mvn_resp\org\springframework\boot\spring-boot-devtools\2.6.13\spring-boot-devtools-2.6.13.jar started by user in F:\Code\springboot-helloworld)
2024-12-26 15:08:31.176  INFO 25848 --- [           main] o.s.b.devtools.RemoteSpringApplication   : No active profile set, falling back to 1 default profile: "default"
2024-12-26 15:08:31.324  WARN 25848 --- [           main] o.s.b.d.r.c.RemoteClientConfiguration    : The connection to http://localhost:8080 is insecure. You should use a URL starting with 'https://'.
2024-12-26 15:08:31.366  WARN 25848 --- [           main] o.s.b.d.a.OptionalLiveReloadServer       : Unable to start LiveReload server
2024-12-26 15:08:31.379  INFO 25848 --- [           main] o.s.b.devtools.RemoteSpringApplication   : Started RemoteSpringApplication in 0.52 seconds (JVM running for 0.853)

devtool 为何会默认禁用缓存选项?

SpringBoot 支持的一些库使用缓存来提高性能。例如,模板引擎缓存已编译的模板以避免重复解析模板文件。此外,Spring MVC 可以在提供静态资源时向响应添加 HTTP 缓存标头。

​ 虽然缓存在生产中非常有益,但在开发过程中可能会适得其反,使用 spring-boot-devtools 模块时是不需要手动设置这些属性的,因为 spring-boot-devtools 会自动进行设置。

​ 那么会自动设置哪些配置呢?可以在 devtools-property-defaults.properties 找到对应的默认配置。

properties 复制代码
server.error.include-binding-errors=always
server.error.include-message=always
server.error.include-stacktrace=always
server.servlet.jsp.init-parameters.development=true
server.servlet.session.persistent=true
spring.freemarker.cache=false
spring.groovy.template.cache=false
spring.h2.console.enabled=true
spring.mustache.cache=false
spring.mvc.log-resolved-exception=true
spring.reactor.debug=true
spring.template.provider.cache=false
spring.thymeleaf.cache=false
spring.web.resources.cache.period=0
spring.web.resources.chain.cache=false

​ 当然如果你不想让应用属性被 spring-boot-devtools 默认设置,可以通过 spring:devtools:add-properties: falseapplication.yml 中。

devtool 是否可以给所有 Springboot 应用做全局的配置?

可以通过将 spring-boot-devtools.yml 文件添加到 $HOME/.config/spring-boot 目录来配置全局 devtools 设置。

​ 添加到这些文件的任何属性都适用于你机器上使用 devtools 的所有 Spring Boot 应用程序。例如,要将重新启动配置为始终使用触发器文件,你需要将以下属性添加到你的 spring-boot-devtools 文件中:

yml 复制代码
spring:
	devtools:
		restart:
			trigger-file: ".reloadtrigger"

如果不用 devtool,还有什么选择?

在实际的开发过程中,通常不会用 devtool 工具,因为:

  • devtool 本身基于重启方式,这种仍然不是真正的热替换方案,JRebel 才是(它是收费的);
  • 开发调试最重要的还是一种权衡
    • 自动重启的开销如果和手动重启没有什么太大差别,那么还不如手动重启(按需重启);
    • 多数情况下,如果是 方法内部的修改或者静态资源的修改,在 IDEA 中是可以通过 Rebuild (Ctrl +Shift + F9)进行热更新的。
  • 此外还有一个工具 spring loaded,可实现修改类文件的热部署,具体可看:spring-projects/spring-loaded: Java agent that enables class reloading in a running JVM

7. 常用注解

@SpringBootApplication

java 复制代码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Configuration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
    Class<?>[] exclude() default {};
}

​ 定义在 main 方法入口类处,用于启动 springboot 应用项目。

@EnableAutoConfiguration

​ 让 springboot 根据类路径中的 jar 包依赖当前项目进行自动配置。

​ 在 src/main/resourcesMETA-INF/spring.factories

java 复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
若有多个自动配置,用 "," 隔开

@ImportResource

​ 加载 xml 配置,一般是放在启动 main 类上。

java 复制代码
@ImportResource("classpath*:/spring/*.xml")  单个

@ImportResource({"classpath*:/spring/1.xml","classpath*:/spring/2.xml"})   多个

@Value

application.properties 定义属性,直接使用 @Value 注入即可。

java 复制代码
public class A{
	 @Value("${push.start:0}")    如果缺失,默认值为0
     private Long  id;
}

@ConfigurationProperties(prefix="person")

​ 可以新建一个 properties 文件,ConfigurationProperties 的属性 prefix 指定 properties 的配置的前缀,通过 location 指定 properties 文件的位置。

java 复制代码
@ConfigurationProperties(prefix="person")
public class PersonProperties {
	/* name 和 age 属性必须保持与application.properties中的属性一致*/
	private String name ;
	private int age;
}

@EnableConfigurationProperties

​ 用 @EnableConfigurationProperties 注解使得 @ConfigurationProperties 生效,并从 IOC 容器中获取 bean。

@RestController

​ 组合 @Controller@ResponseBody,当开发一个和页面交互数据的控制时,比如 bbs-web 的 api 接口需要此注解。

@RequestMappling("/api/test")

​ 用来映射 web 请求(访问路径和参数)、处理类和方法,可以注解在类或方法上。注解在方法上的路径会继承注解在类上的路径。

​ produces 属性:定制返回的 response 的媒体类型和字符集,或需返回值是 json 对象。

java 复制代码
@RequestMapping(value="/api2/copper",produces="application/json;charset=UTF-8",method = RequestMethod.POST)

@RequestParam

​ 获取 request 请求的参数值。

java 复制代码
public List<User> getAllUser(@RequestParam(value = "pageIndex", required = false) Integer pageIndex,
                            @RequestParam(value = "pageSize", required = false) Integer pageSize)

@ResponseBody

​ 支持将返回值放在 response 体内,而不是返回一个页面。比如 Ajax 接口,可以用此注解返回数据而不是页面。此注解可以放置在返回值前或方法前。

java 复制代码
另一个玩法,可以不用@ResponseBody。
继承FastJsonHttpMessageConverter类并对writeInternal方法扩展,在spring响应结果时,再次拦截、加工结果
// stringResult: json返回结果
//HttpOutputMessage outputMessage

 byte[] payload = stringResult.getBytes();
 outputMessage.getHeaders().setContentType(META_TYPE);
 outputMessage.getHeaders().setContentLength(payload.length);
 outputMessage.getBody().write(payload);
 outputMessage.getBody().flush();

@Bean

@Bean(name = "bean 的名字", initMethod = "初始化时调用方法的名字", destoryMethod = "close")

​ 定义在方法上,在容器内初始化一个 bean 实例类。

java 复制代码
@Bean(destroyMethod="close")
@ConditionalOnMissingBean
public PersonService registryService() {
    return new PersonService();
}

@Service

​ 用于标注业务层组件。

@Controller

​ 用于标注控制层组件(如 struts 中的 action)。

@Repository

​ 用于标注数据访问组件,即 DAO 组件。

@Component

​ 泛指组件,当组件不好归类时,我们可以使用这个注解进行标注。

@PostConstruct

​ spring 容器初始化时,要执行该方法。

java 复制代码
@PostConstruct  
public void init() {   
}   

@PathVariable

​ 用来获取请求 url 中的动态参数。

java 复制代码
@Controller  
public class TestController {  

     @RequestMapping(value="/user/{userId}/roles/{roleId}",method = RequestMethod.GET)  
     public String getLogin(@PathVariable("userId") String userId,  
         @PathVariable("roleId") String roleId){
           
         System.out.println("User Id : " + userId);  
         System.out.println("Role Id : " + roleId);  
         return "hello";  
     
     }  
}

@ComponentScan

​ 注解会告知 Spring 扫描指定的包来初始化 Spring。

java 复制代码
@ComponentScan(basePackages = "io.zhanbo.xx")

@EnableZuulProxy

​ 路由网关的主要目的是为了让所有的微服务对外只有一个接口,我们只需访问一个网关地址,即可由网关将所有的请求代理到不同的服务中。Spring Cloud 通过 Zuul 来实现的,支持自动路由映射到在 Eureka Server 上注册的服务。Spring Cloud 提供了注解 @EnableZuulProxy 来启动路由代理。

@Autowired

​ 在默认情况下使用 @Autowired 注解进行自动注入时,Spring 容器中匹配的候选 Bean 数目必须由且仅有一个。当找不到一个匹配的 Bean 时,Spring 容器将抛出 BeanCreationException 异常,并指出必须至少拥有一个匹配的 Bean。

​ 当不能确定 SPring 容器中一定拥有某个类的 Bean 时,可以在需要自动注入该类 Bean 的地方可以使用 @Autowired(required = false),这等于告诉 Spring:在找不到匹配 Bean 时不报错。

@Configuration

​ 注解作用在类上,表明该类是一个配置信息类,可以给这个配置类也起一个名称。

java 复制代码
@Configuration("name")//表示这是一个配置信息类,可以给这个配置类也起一个名称
@ComponentScan("spring4")//类似于xml中的<context:component-scan base-package="spring4"/>
public class Config {

    @Autowired//自动注入,如果容器中有多个符合的bean时,需要进一步明确
    @Qualifier("compent")//进一步指明注入bean名称为compent的bean
    private Compent compent;

    @Bean//类似于xml中的<bean id="newbean" class="spring4.Compent"/>
    public Compent newbean(){
        return new Compent();
    }   
}

@Import(Config.class)

​ 导入 Config 配置类里实例化的 bean。

java 复制代码
@Configuration
public class CDConfig {

    @Bean   // 将SgtPeppers注册为 SpringContext中的bean
    public CompactDisc compactDisc() {
        return new CompactDisc();  // CompactDisc类型的
    }
}

@Configuration
@Import(CDConfig.class)  //导入CDConfig的配置
public class CDPlayerConfig {

    @Bean(name = "cDPlayer")
    public CDPlayer cdPlayer(CompactDisc compactDisc) {  
         // 这里会注入CompactDisc类型的bean
         // 这里注入的这个bean是CDConfig.class中的CompactDisc类型的那个bean
        return new CDPlayer(compactDisc);
    }
}

@Order

​ @Order(1),值越小,优先级越高,越先运行。

@ConditionalOnExpression

​ 开关为 true 的时候才实例化 bean。

java 复制代码
@Configuration
@ConditionalOnExpression("${enabled:false}")
public class BigpipeConfiguration {
    @Bean
    public OrderMessageMonitor orderMessageMonitor(ConfigContext configContext) {
        return new OrderMessageMonitor(configContext);
    }
}

@ConditionalOnProperty

​ 这个注解能够控制某个 @Configuration 是否生效。具体操作是通过其两个属性 name 以及 havingValue 来实现的,其中 name 用来从 application.properties 中读取某个属性值,如果该值为空,则返回 false;如果值不为空,则将该值与 havingValue 指定的值进行比较,如果一样则返回 true;否则返回 false。如果返回值为 false,则该 configuration 不生效;为 true 则生效。

@ConditionalOnClass

​ 该注解的参数对应的类必须存在,否则不解析该注解修饰的配置类。

java 复制代码
@Configuration
@ConditionalOnClass({Gson.class})
public class GsonAutoConfiguration {
    public GsonAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean
    public Gson gson() {
        return new Gson();
    }
}

@ConditionalOnMissingClass({ApplicationManager.class})

​ 如果存在它修饰的类的 bean,则不需要再创建这个 bean。

@ConditionOnMissingBean(name = "example")

​ 表示如果 name 为 "example" 的 bean 存在,该注解修饰的代码块不执行。

相关推荐
桂月二二12 分钟前
Java与容器化:如何使用Docker和Kubernetes优化Java应用的部署
java·docker·kubernetes
liuxin3344556616 分钟前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
海绵波波10719 分钟前
flask后端开发(10):问答平台项目结构搭建
后端·python·flask
小马爱打代码42 分钟前
设计模式详解(建造者模式)
java·设计模式·建造者模式
栗子~~1 小时前
idea 8年使用整理
java·ide·intellij-idea
2301_801483691 小时前
Maven核心概念
java·maven
网络风云2 小时前
【魅力golang】之-反射
开发语言·后端·golang
Q_19284999062 小时前
基于Spring Boot的电影售票系统
java·spring boot·后端
陈无左耳、2 小时前
Spring Boot应用开发实战:从入门到精通
spring boot
我要学编程(ಥ_ಥ)2 小时前
初始JavaEE篇 —— 网络原理---传输层协议:深入理解UDP/TCP
java·网络·tcp/ip·udp·java-ee