文章目录
- [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 日志)
-
- [5.1 日志框架的基础](#5.1 日志框架的基础)
- [5.2 实现范例](#5.2 实现范例)
- [5.3 参考文档](#5.3 参考文档)
- [6. 配置热部署 devtools 工具](#6. 配置热部署 devtools 工具)
-
- [6.1 概述](#6.1 概述)
-
- 什么是热部署和热加载?
- [什么是 LiveLoad?](#什么是 LiveLoad?)
- [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. 常用注解)
-
- @SpringBootApplication
- @EnableAutoConfiguration
- @ImportResource
- @Value
- @ConfigurationProperties(prefix="person")
- @EnableConfigurationProperties
- @RestController
- @RequestMappling("/api/test")
- @RequestParam
- @ResponseBody
- @Bean
- @Service
- @Controller
- @Repository
- @Component
- @PostConstruct
- @PathVariable
- @ComponentScan
- @EnableZuulProxy
- @Autowired
- @Configuration
- @Import(Config.class)
- @Order
- @ConditionalOnExpression
- @ConditionalOnProperty
- @ConditionalOnClass
- @ConditionalOnMissingClass({ApplicationManager.class})
- [@ConditionOnMissingBean(name = "example")](#@ConditionOnMissingBean(name = "example"))
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 的功能。
- 使用 Spring 的 IOC 容器,将对象之间的依赖关系交给 Spring,降低组件之间的耦合性,让我们更专注于应用逻辑。
- 可以提供众多服务,事务管理,WS 等。
- AOP 的很好支持,方便面向切面编程。
- 对主流的框架提供了很好的集成支持,如 Hibernate,Struts2,JPA 等。
- Spring DI 机制降低了业务对象替换的复杂性。
- Spring 属于低侵入,代码污染极低。
- Spring 的高度开放性,并不强制依赖于 Spring,开发者可以自由选择 SPring 部分或全部。
SpringFramework 没有解决什么问题?
虽然 Spring 的组件代码是轻量级的,但它的配置却是重量级的。一开始,Spring 用 XML 配置,而且是很多 XML 配置。Spring 2.5 引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件的显示 XML 配置。Spring 3.0 引入了基于 Java 的配置,这是一种类型安全的可重构配置方式,可以代替 XML。
所有这些配置都代表了开发时的损耗。因为在思考 Spring 特性配置和解决业务问题之间需要进行思维切换,所以编写配置挤占了编写应用程序逻辑的时间。所有框架一样,Spring 实用,但与此同时它要求的回报也不少。
除此之外,项目的依赖管理也是一件耗时耗力的事情。在环境搭建时,需要分析要导入哪些库的坐标,而且还需要分析导入与之有依赖关系的其它库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题就会严重阻碍项目的开发进度。
- JSP 中要写很多代码、控制器过于灵活,缺少一个公用控制器。
- Spring 不支持分布式,这也是 EJB 仍然在用的原因之一。
1.2 SpringBoot 的概述
SpringBoot 解决上述 Spring 的缺点
SpringBoot 对上述 Spring 的缺点进行了改善和优化,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心地投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期。
SpringBoot 的特点
- 为基于 Spring 的开发提供更快的入门体验;
- 开箱即用,没有代码生成,也无需 XML 配置。同时也可以修改默认值来满足特定的需求;
- 提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标、监控检测、外部配置等。
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)。区别层次的目的即为了 "高内聚、低耦合" 的思想。
- 表现层(UI):通俗讲就是展现给用户的界面,即用户在使用一个系统的时候他的所见所得。
- 业务逻辑层(BLL):针对具体问题的操作,也可以锁是对数据层的操作,对数据业务逻辑处理。
- 数据访问层(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 运行测试
添加用户
查询用户列表
4. 定制自己的 Banner
我们在启动 Spring Boot 程序时,有 SpringBoot 的 Banner 信息,那么如何自定义成自己项目的信息呢?
4.1 什么是 Banner
我们在启动 Spring Boot 程序时,有如下 Banner 信息:
那么如何自定义成自己项目的名称呢?
4.2 如何更改 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 的接口类
4.3 文字 Banner 的设计
如何设计上面的文字呢?
一些设计 Banner 的网站
可以通过这个网站进行设计:https://patorjk.com/software/taag
例如:
修改 banner.txt 运行效果如下:
IDEA 中 Banner 的插件
其它工具
https://www.degraeve.com/img2txt.php
https://www.bootschool.net/ascii
4.4 Banner 中其它配置信息
除了文件信息,还有哪些信息可以配置呢?比如 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)
4.5 动画 Banner 的设计
那能不能设置动态的 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 进一步思考
图片 Banner 是如何起作用的?
发现 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 官网
- Logback 官网文档
- 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:
File -> Build,Execution,Deployment -> Compile
勾选:
Mark project automatically
-
设置 2:
- 旧版 IDEA:
ctrl + alt + shift + /
,选择:Registry
,勾选:compiler.automake.allow.when.app.running
- 新版 IDEA:
File -> setting -> Advanced Settings
- 旧版 IDEA:
-
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.exclude
和 restart.include
为前缀的属性。该 include 元素是应该被拉高到 "重启" 的类加载器的项目,以及 exclude 要素是应该向下推入 "Base" 类加载器的项目。该属性的值是应用于类路径的正则表达式模板,如以下示例所示:
yml
restart:
exclude:
companycommonlibs: "/mycorp-common-[\\w\\d-\\.]/(build|bin|out|target)/"
include:
projectcommon: "/mycorp-myproj-[\\w\\d-\\.]+\\.jar"
devtool 是否会被打包进 jar?
devtool 原则上来说应该是只在开发调试的时候使用,而在生产环境运行 jar 包时是不需要的,所以 Spring 打包会不会把它打进 JAR 呢?
- 默认情况下,不会被打包进 JAR
运行打包的应用程序时,开发人员工具会自动禁用。如果通过 java -jar` 或者其它特殊的类加载器进行启动时,都会被认为是 "生产环境的应用"。
- 如果我们期望远程调试应用
生产环境勿用,只有在受信任的网络上运行或使用 SSL 进行保护时,才应启用它。
在这种情况下,devtool 也具备远程调试的能力:远程客户端应用程序旨在从你的 IDE 中运行。你需要 org.springframework.boot.devtools.RemoteSpringApplication
使用与你连接的远程项目相同的类路径运行,应用程序的唯一必须参数是它连接到的远程 URL。
例如,如果使用 IDEA 执行以下操作:
- 选择
Run Configurations ...
从 Run 菜单; - 创建一个新的
Java Application
"启动配置"; - 浏览
springboot-helloworld
项目; - 使用
org.springframework.boot.devtools.RemoteSpringApplication
作为主类; - 添加
http://localhost:8080
到Program 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: false
到 application.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/resources
的 META-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 存在,该注解修饰的代码块不执行。