作者简介,普修罗双战士,一直追求不断学习和成长,在技术的道路上持续探索和实践。
多年互联网行业从业经验,历任核心研发工程师,项目技术负责人。
欢迎 点赞✍评论⭐收藏
SpringBoot 领域知识
链接 | 专栏 |
---|---|
SpringBoot 专业知识学习一 | SpringBoot专栏 |
SpringBoot 专业知识学习二 | SpringBoot专栏 |
SpringBoot 专业知识学习三 | SpringBoot专栏 |
SpringBoot 专业知识学习四 | SpringBoot专栏 |
SpringBoot 专业知识学习五 | SpringBoot专栏 |
在 Spring Boot 中,自动配置(Auto-configuration)是一种基于条件化(Conditional)的配置方式。它可以根据应用的 classpath、依赖和配置来自动配置 Spring 应用程序,而无需进行过多的手动配置。
Spring Boot 的自动配置实际上是通过 Spring 的条件化注解来实现的,
主要有以下条件注解:
@ConditionalOnClass
@ConditionalOnMissingClass
@ConditionalOnBean
@ConditionalOnMissingBean
@ConditionalOnProperty
@ConditionalOnResource
@ConditionalOnWebApplication
等。
这些注解通过判断应用所引入的类、Bean 等是否存在,或者应用的配置属性、资源、Web 应用类型是否符合特定的条件,来决定是否生效该自动配置。
如果满足条件,就会创建对应的 Bean、配置信息等。如果不满足,对应的 Bean、配置信息就会忽略或移除。
Spring Boot 还提供了 @EnableAutoConfiguration
注解,它可以根据应用的 classpath
来自动启动并配置 Spring Boot 应用程序的各种组件和功能。
具体来说,它会从 META-INF/spring.factories
中加载自动配置类,并根据自动配置的条件判断来决定是否生效。
通过自动配置,Spring Boot 可以让开发者专注于业务开发而无需关心繁琐的配置。同时也能提高开发效率、降低开发成本和错误率。因此,自动配置是 Spring Boot 框架的一个重要特性。
在 Spring Boot 中,条件注解(Conditional Annotations)是一种用于根据特定条件来装配 Bean 和配置组件的机制。条件注解允许我们基于运行时环境、配置属性或其他条件来动态地确定是否应该创建特定的 Bean。
Spring Boot 提供了一系列内置的条件注解,例如:
1. @ConditionalOnClass
:当类路径中存在指定的类时,条件匹配。
2. @ConditionalOnMissingClass
:当类路径中不存在指定的类时,条件匹配。
3. @ConditionalOnBean
:当 Spring 上下文中存在指定的 Bean 时,条件匹配。
4. @ConditionalOnMissingBean
:当 Spring 上下文中不存在指定的 Bean 时,条件匹配。
5. @ConditionalOnProperty
:当指定的配置属性存在且值满足条件时,条件匹配。
6. @ConditionalOnResource
:当指定的资源存在时,条件匹配。
7. @ConditionalOnWebApplication
:当应用程序是一个 Web 应用程序时,条件匹配。
这些条件注解可以根据应用的具体情况以及自定义的条件进行组合和定制。它们可以应用在配置类、Bean 方法上,通过条件判断来决定是否加载、注册相关组件。
当满足条件时,Bean 或配置会被创建和应用;当不满足条件时,它们会被忽略或者被移除。
条件注解的工作原理是基于 Spring 的条件化(Conditional)机制。条件注解本质上是一个条件化注解,它会在应用启动时进行条件判断,并根据条件结果来决定是否启用相关的 Bean 或配置。
只有满足特定条件的注解才会生效。这样可以根据不同的条件来定制化地配置应用,提供更灵活的自动装配机制。
以下给你举几个条件注解的示例。
1. @ConditionalOnClass
:当类路径中存在指定的类时,条件匹配。
@Configuration
@ConditionalOnClass(MyClass.class)
public class MyConfiguration {
// ... 定义需要配置的 Bean 或组件 ...
}
在这个例子中,MyConfiguration
类只有在类路径中存在 MyClass.class
类时才会被装配为配置类。否则,该配置类将被忽略。
2. @ConditionalOnBean
:当 Spring 上下文中存在指定的 Bean 时,条件匹配。
@Configuration
@ConditionalOnBean(DataSource.class)
public class MyConfiguration {
// ... 定义需要配置的 Bean 或组件 ...
}
在这个例子中,MyConfiguration
类只有当 Spring 上下文中存在 DataSource
Bean 时,才会被装配为配置类。如果不存在该 Bean,该配置类将被忽略。
3. @ConditionalOnProperty
:当指定的配置属性存在且值满足条件时,条件匹配。
@Configuration
@ConditionalOnProperty(prefix = "myapp", name = "enabled", havingValue = "true")
public class MyConfiguration {
// ... 定义需要配置的 Bean 或组件 ...
}
在这个例子中,MyConfiguration
类只有当名为 myapp.enabled
的配置属性值为 true
时,才会被装配为配置类。如果配置属性不存在或者值不满足条件,该配置类将被忽略。
在 Spring Boot 中,@ConditionalOnBean
和 @ConditionalOnMissingBean
是两个条件注解,用于在装配 Bean 或配置组件时进行条件判断。它们的区别如下所示:
1. @ConditionalOnBean
:该注解的条件是判断 Spring 上下文中是否存在指定的 Bean。如果存在指定的 Bean,则条件匹配,相关的 Bean 或配置组件会被加载和应用。
@Configuration
@ConditionalOnBean(DataSource.class)
public class MyConfiguration {
// ... 定义需要配置的 Bean 或组件 ...
}
上面的例子中,只有当 Spring 上下文中存在 DataSource
Bean 时,MyConfiguration
类才会被装配为配置类。
2. @ConditionalOnMissingBean
:该注解的条件是判断 Spring 上下文中是否不存在指定的 Bean。如果不存在指定的 Bean,则条件匹配,相关的 Bean 或配置组件会被加载和应用。
@Configuration
@ConditionalOnMissingBean(DataSource.class)
public class MyConfiguration {
// ... 定义需要配置的 Bean 或组件 ...
}
上面的例子中,只有当 Spring 上下文中不存在 DataSource
Bean 时,MyConfiguration
类才会被装配为配置类。
简而言之:
@ConditionalOnBean
用于判断 Bean 是否存在,条件匹配时加载和应用相关的组件;
@ConditionalOnMissingBean
则用于判断 Bean 是否不存在,条件匹配时加载和应用相关的组件。
总结一下它们的区别,并列出一个简单的表格:
条件注解 | 条件判断说明 | 条件匹配时是否加载配置类 |
---|---|---|
@ConditionalOnBean |
判断是否存在指定的 Bean,存在则条件匹配 | 是,加载相关的 Bean 或配置类 |
@ConditionalOnMissingBean |
判断是否不存在指定的 Bean,不存在则条件匹配 | 是,加载相关的 Bean 或配置类 |
总之,这两个注解是常用的 Spring Boot 条件注解之一,它们通常与其它条件注解、自动配置类等一起使用,帮助您更加灵活地控制应用程序的自动配置和启动过程。
在 Spring Boot 中,自动配置属性的解析是通过 @ConfigurationProperties
注解实现的。@ConfigurationProperties
注解用于指定属性的前缀,并根据属性的命名规则自动映射到对应的实体类中。
以下是 Spring Boot 自动配置属性解析的流程:
1. 在自动配置类中,使用 @ConfigurationProperties
注解来指定属性的前缀。
@ConfigurationProperties(prefix = "myapp")
public class MyAppProperties {
private String name;
private int age;
// ... 其他属性的getter和setter方法 ...
}
上面的例子中,所有以 myapp
开头的属性都会映射到 MyAppProperties
类中。
2. 在应用的 application.properties
或 application.yml
文件中,设置对应的属性值。
myapp.name=My Application
myapp.age=20
3. 在启动时,自动配置会将应用配置文件中的属性值解析并映射到 MyAppProperties
实体类的相关属性中。
4. 在启动过程中,可以通过 @Autowired
或 @Resource
等注解将 MyAppProperties
注入到其他需要使用它的类中。
@RestController
public class MyController {
@Autowired
private MyAppProperties myAppProperties;
}
上面的例子中,MyAppProperties
的实例会被注入到 MyController
类中,从而可以直接使用其中定义的属性。
通过以上流程,Spring Boot 实现了自动配置属性的解析。使用 @ConfigurationProperties
注解,结合应用配置文件,可以方便地将应用的配置信息自动映射到指定的实体类中,从而实现属性的自动配置。
想要自定义 Spring Boot 的自动配置类,有三种常用的方式:
1. 最简单的方式是通过在 @Configuration
注解的类中定义 @Bean
或 @Conditional
方法来重写 Spring Boot 的默认配置。这种方式可以覆盖某些默认配置,而不会影响其它自动配置。
2. 另外一种方式是通过创建一个新的自动配置类来覆盖默认的自动配置类。 这种方式可以更改更多的默认配置,但也可能会影响其它自动配置。
3. 最后一种方式是通过创建一个外部的配置类来修改默认的配置。 外部配置类可以通过 SpringApplication.setDefaultProperties(...)
方法来设置默认配置,或者通过在 application.properties
或 application.yml
中添加配置来修改默认配置。
下面我们一一介绍这三种方式:
1. 通过定义 @Bean
或 @Conditional
方法来重写默认配置
这种方式是最简单的方式,只需要在 @Configuration
注解的类中定义特定的 @Bean
或 @Conditional
方法即可。
例如,下面的示例代码定义了一个新的 DataSource
Bean,并覆盖默认的 DataSourceAutoConfiguration
自动配置:
@Configuration
public class DataSourceConfig {
@Bean
@ConditionalOnMissingBean
public DataSource dataSource() {
// ... 创建并返回一个新的 DataSource ...
}
}
这里使用 @ConditionalOnMissingBean
注解来判断当前应用程序上下文中是否已存在 DataSource
Bean。
如果已存在,则不会创建新的 DataSource
,否则将会创建一个新的 DataSource
Bean,并使用它来替换默认的 DataSourceAutoConfiguration
自动配置。
2. 通过创建新的自动配置类来覆盖默认的自动配置类
这种方式需要创建一个新的自动配置类,并使用 @ConditionalOnClass
、@ConditionalOnBean
、@ConditionalOnProperty
等注解来控制该自动配置类的生效范围。
例如,下面的示例代码创建了一个新的 AwesomeServiceAutoConfiguration
自动配置类,并覆盖了默认的 MyServiceAutoConfiguration
自动配置:
@Configuration
@ConditionalOnClass(AwesomeService.class)
@EnableConfigurationProperties(AwesomeServiceProperties.class)
public class AwesomeServiceAutoConfiguration {
@Autowired
private AwesomeServiceProperties properties;
@Bean
@ConditionalOnMissingBean
public AwesomeService awesomeService() {
// ... 根据配置创建并返回 AwesomeService ...
}
}
这里通过 @ConditionalOnClass
注解来判断当前类路径下是否存在 AwesomeService
类。
如果存在,则自动配置将会生效,否则自动配置将不会生效。 然后使用 @EnableConfigurationProperties
注解来启用该自动配置类所需的配置类,最后定义 awesomeService()
方法来创建 AwesomeService Bean,并使用 @ConditionalOnMissingBean
注解来判断当前应用程序上下文中是否已存在该 Bean。
如果已存在,则不会创建新的 AwesomeService Bean,否则将会创建一个新的 AwesomeService Bean,并用它来替换默认的 MyServiceAutoConfiguration
自动配置。
3. 通过创建外部的配置类来修改默认的配置
这种方式需要在 application.properties
或 application.yml
中添加配置。
例如,下面的示例代码使用 myapp.server.port
属性来修改默认的服务端口号配置:
server.port=8080
myapp.server.port=8888
在这个示例中,我们添加了一个名为 myapp.server.port
的新属性,它将会覆盖默认的 server.port
服务端口号配置。
总之,以上三种方式都是常用的自定义 Spring Boot 自动配置的方法。选择哪种方式,应该根据具体情况和需求来决定。
Spring Boot 可以处理简单的循环依赖,但是对于复杂的循环依赖则需要使用特殊的处理方式。
简单的循环依赖是指基于属性的 setter 依赖或构造函数注入时的循环依赖。Spring Boot 可以通过提前暴露其中一个 bean 实例,例如通过将某个依赖的 bean 实例包装在一个 ObjectHolder
中来暴露,从而解决简单的循环依赖。
对于最简单的情况,例如一个类 A 依赖于另一个类 B,而 B 又依赖于 A 实例的一个属性,在构造函数注入时,Spring Boot 会自动将一个不完整的 A 实例暴露给 B,然后在构造函数完成之后,再将完整的 A 实例注入到 B 中。
但是,如果存在复杂的循环依赖,例如循环依赖环,即 A -> B -> C -> A,Spring Boot 就不能处理了。
在这种情况下,通常需要使用 延迟注入
来解决循环依赖。延迟注入是指不在初始化时立即注入 bean,而是在使用时进行注入。
Spring Boot 中可以使用 @Lazy
注解来实现延迟注入。
例如:
@Component
public class A {
@Autowired
@Lazy
private B b;
// ...
}
@Component
public class B {
@Autowired
private C c;
// ...
}
@Component
public class C {
@Autowired
private A a;
// ...
}
在上面的示例中,A 依赖于 B,B 依赖于 C,而 C 又依赖于 A。这种循环依赖环无法通过默认的 Spring Boot 依赖注入机制处理。
但是,通过使用 @Lazy
注解,我们可以将 A 中的 B 延迟注入,直到需要使用它时再进行注入。这样就可以解决循环依赖问题了。
总之,Spring Boot 可以处理 简单的循环依赖
,但对于 复杂的循环依赖
,需要使用特殊的处理方式,例如 延迟注入
。在实际应用中,应该尽量避免使用复杂的循环依赖,保持依赖注入的单向性,有助于提高应用程序的可维护性。
除了使用延迟注入,还有一些其他的方法可以处理 Spring Boot 中的复杂循环依赖问题,例如使用 构造函数注入
、通过接口实现注入
等。下面是一些解决循环依赖问题的常用方法:
1. 使用构造函数注入
对于复杂的循环依赖问题,使用构造函数注入是一种常见的解决方案。通过使用构造函数注入,可以强制 Spring Boot 在初始化 bean 的过程中显式指定所有必需的依赖项,从而解决循环依赖问题。
示例代码如下:
@Component
public class A {
private B b;
@Autowired
public A(B b) {
this.b = b;
}
// ...
}
@Component
public class B {
private C c;
@Autowired
public B(C c) {
this.c = c;
}
// ...
}
@Component
public class C {
private A a;
@Autowired
public C(A a) {
this.a = a;
}
// ...
}
在上面的示例中,每个 bean 都将其依赖项通过构造函数注入进来。这样就可以避免循环依赖问题。
2. 通过接口实现注入
另一种处理复杂循环依赖的方法是使用接口实现注入。通过使用接口注入,可以实现循环依赖的两个 bean 分别依赖于自己的扩展接口,而不是依赖于对方的类实现。
示例代码如下:
public interface AInterface {
void setB(BInterface b);
}
@Component
public class A implements AInterface {
private BInterface b;
@Override
@Autowired
public void setB(BInterface b) {
this.b = b;
}
// ...
}
public interface BInterface {
void setC(CInterface c);
}
@Component
public class B implements BInterface {
private CInterface c;
@Override
@Autowired
public void setC(CInterface c) {
this.c = c;
}
// ...
}
public interface CInterface {
void setA(AInterface a);
}
@Component
public class C implements CInterface {
private AInterface a;
@Override
@Autowired
public void setA(AInterface a) {
this.a = a;
}
// ...
}
在上面的示例中,A、B 和 C 分别实现了自己的扩展接口,然后通过接口注入各自的依赖关系。这种方法通过抽象和分离依赖关系,使得 Spring Boot 可以轻松地处理循环依赖问题。
总之,处理 Spring Boot 中的循环依赖问题需要根据具体情况进行选择。常用的解决方法包括使用延迟注入、构造函数注入和通过接口实现注入等。
在实际应用中,应该尽量避免出现复杂的循环依赖问题,保证依赖注入的单向性,有助于提高应用程序的可维护性。
在 Spring Boot 中实现热部署有多种方法,下面介绍其中两种常用的方法:
1. 使用 Spring Boot DevTools
Spring Boot DevTools 是一个开发工具,可以帮助实现热部署。它使用了两个关键技术:
要启用 Spring Boot DevTools,只需要将以下依赖项添加到项目的 pom.xml
文件中:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<optional>trueoptional>
dependency>
使用 DevTools 启动应用程序后,当你修改了任何源代码文件、资源文件或配置文件时,它会自动重新加载应用程序。你可以在 IDE 中编辑文件并保存,或者使用命令行工具进行相应的更改。
2. 使用 Spring Loaded
Spring Loaded 是一个开源项目,用于支持在运行时动态加载修改后的类。它通过 Java 的 HotSwap 技术实现热交换。
要使用 Spring Loaded,首先需要将其引入项目的开发依赖中:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>springloadedartifactId>
<version>1.2.8.RELEASEversion>
<scope>providedscope>
dependency>
然后,在该项目的启动脚本中加入 -javaagent
参数,指定 Spring Loaded 的代理模块:
-javaagent:path/to/springloaded-{version}.jar
启动项目后,当你修改了相应的类文件时,Spring Loaded 会在后台自动加载并重新定义该类,实现热部署。
需要注意的是,Spring Boot DevTools 和 Spring Loaded 都是用于开发环境,在生产环境中并不推荐使用。在生产环境中,可以使用 Docker、Kubernetes 等部署技术来实现无缝的应用程序更新和滚动升级。
在 Spring Boot 中,配置的优先级是按照以下规则确定的:
1. 命令行参数(Command Line Arguments):命令行参数提供了最高的优先级。可以在运行 Spring Boot 应用程序时使用 --
的格式来传递参数,
例如
java -jar myproject.jar --server.port=8080
。
2. 系统属性(System Properties):可以通过在 JVM 启动时使用 -D
的方式来设置系统属性。
例如:
java -Dserver.port=8080 -jar myproject.jar
。
3. 环境变量(Environment Variables):可以通过设置环境变量来配置应用程序。Spring Boot 将环境变量作为默认的属性源,同时支持使用下划线(_
)或点号(.
)来分隔层次结构。
例如,设置环境变量
SERVER_PORT=8080
。
4. 配置文件(Application Properties):Spring Boot 默认支持使用 .properties
或 .yml
文件来配置应用程序。可以通过在 src/main/resources
或 classpath:/config
目录下添加相应的配置文件来覆盖默认配置。针对不同的环境,可以使用不同的文件名。
例如,
application-dev.properties
、application-prod.yml
,或者使用spring.profiles.active
属性来指定活动的配置文件。可以使用spring.config.name
和spring.config.location
属性来定制配置文件的名称和位置。
5. 默认配置(Default Configuration):Spring Boot 提供了一组默认的配置,例如默认的数据库连接池、默认的端口号等。当没有其他配置来源时,应用程序将使用这些默认配置。
在优先级较高的配置选项中,更具体的配置会覆盖更一般的配置。例如,命令行参数会覆盖配置文件中的参数,而配置文件中的参数会覆盖默认配置。
在 Spring Boot 中实现异步调用有多种方法,下面介绍其中两种常用的方法:
1. 使用 @Async
注解
Spring Boot 提供了 @Async
注解,它可以应用在方法上,用来指示该方法是一个异步方法。在使用 @Async
注解时,需要注意以下几点:
@EnableAsync
注解,以启用异步执行功能。@Async
注解,以标识该方法应该在单独的线程中执行。Future
,其中 T
是返回结果的类型。下面是一个示例:
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.concurrent.Future;
@Component
public class MyService {
@Async
public void asyncTask() {
// 异步执行的任务
}
@Async
public Future<String> asyncTaskWithResult() {
// 异步执行的任务,并返回结果
return new AsyncResult<>("Task completed");
}
}
2. 使用 CompletableFuture
Java 8 引入了 CompletableFuture
类,它提供了更灵活的方式来处理异步操作。在 Spring Boot 中,可以使用 CompletableFuture
来实现异步调用。下面是一个示例:
import org.springframework.stereotype.Component;
import java.util.concurrent.CompletableFuture;
@Component
public class MyService {
public CompletableFuture<String> asyncTask() {
return CompletableFuture.supplyAsync(() -> {
// 异步执行的任务
return "Task completed";
});
}
}
在调用该方法时,会立即返回一个 CompletableFuture
对象,可以通过该对象获取异步操作的结果。
需要注意的是,上述方法需要在 Spring Boot 启动类上添加 @EnableAsync
注解启用异步执行功能,或者在配置类中使用 @EnableAsync
注解进行相应配置。
使用异步调用可以提高系统的吞吐量和响应性,但也需要注意合理使用线程池,避免线程资源的过度消耗。
在 Spring Boot 中实现跨域请求有多种方法,下面介绍其中两种常用的方法:
1. 使用 @CrossOrigin
注解
@CrossOrigin
注解是 Spring Framework 提供的一种简单的方式,用于处理跨域请求。通过在控制器类或方法上添加 @CrossOrigin
注解,可以指定允许的跨域请求的来源、方法和其他选项。
下面是一些示例:
@RestController
public class MyController {
@GetMapping("/api/data")
@CrossOrigin(origins = "http://example.com")
public String getData() {
// 处理请求并返回数据
}
@PostMapping("/api/save")
@CrossOrigin(origins = {"http://example.com", "http://another-domain.com"})
public void saveData(@RequestBody Data data) {
// 处理请求并保存数据
}
}
在上述示例中,@CrossOrigin
注解指定了允许跨域请求的来源,可以用字符串或字符串数组形式指定多个来源。还可以通过其他选项,如 allowedHeaders
、allowCredentials
、exposedHeaders
等来自定义跨域请求的行为。
2. 使用 Web 安全配置类
除了使用 @CrossOrigin
注解,还可以通过 Web 安全配置类进行全局配置,以处理跨域请求。可以创建一个继承自 WebSecurityConfigurerAdapter
的配置类,并覆盖 addCorsMappings
方法,并在其中配置跨域请求的细节。
下面是一个示例:
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
在上述示例中,通过覆盖 configure
方法并调用 http.cors()
,启用了跨域请求的支持。此外,通过创建一个 CorsConfigurationSource
的 bean,并在其中设置允许跨域请求的来源和方法。