SpringBoot学习笔记
声明:本人写文章的目的仅仅是起记录作用(本人健忘)本文参考黑马和众多网友,如有侵权联系删改
文章内容主要来源:黑马程序员SpringBoot教程
SpringBoot 原理分析
SpringBoot自动配置
@Conditional注解
\qquad
我们都知道,当我们将相关依赖引入后,SpringBoot 会自动为我们在容器中创建对应的Bean,那么这个功能是怎么实现的呢?就是通过 Condition,Condition 是 SpringBoot 4.0 中增加的条件判断功能,通过这个功能可以选择性的创建 Bean。
\qquad
以 RedisTemplate 对象为例,先引入 Redis 的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
\qquad 创建一个简单的 User 对象
public class User {
}
\qquad
创建一个 ClassCondition 对象,将 Condition 接口的 match() 方法实现一下,设置一个 flag 用于判断 org.springframework.data.redis.core.RedisTemplate.class
文件是否存在,若存在,flag = true
,若不存在,flag = flase
,最后返回 flag,根据 flag 的取值来决定对应的 Bean 是否创建。
public class ClassCondition implements Condition {
/**
*
* @param context 上下文对象,可以获取 environment、IOC容器、ClassLoader对象
* @param metadata 注解元对象,可以获取注解定义的属性值
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//1.需求:导入 Redis 坐标后创建 Bean
//思路:判断 org.springframework.data.redis.core.RedisTemplate.class 文件是否存在
boolean flag = true;
try {
Class.forName("org.springframework.data.redis.core.RedisTemplate");
} catch (ClassNotFoundException e) {
flag = false;
}
return flag;
}
}
\qquad 创建一个 UserConfig 类,将 User 表示为 Bean,并用 Conditional 注解实现选择性创建 Bean,对应文件用我们刚刚写好的 ClassCondition 类。
@Configuration
public class UserConfig {
@Bean
@Conditional(ClassCondition.class)
public User user() {
return new User();
}
}
\qquad
运行 Application 启动文件,运行结果如下图:
\qquad
通过上图我们可以看到 User 对象已经成功创建,也就是说 Spring 的容器将其成功实例化为 Bean 了,那么我们现在将 Redis 的依赖注释掉,更新 pom 文件后再运行,结果如下:
\qquad
报了NoSuchBeanDefinitionException
的错,说明 User 对象没有被创建,也就是说 Spring 容器没有将 User 对象实例化为 Bean。
\qquad
我们可以将上述判断改进一下,将其改进为判断导入通过注解属性值 value 指定坐标后创建 Bean。首先我们先创建一个注解类,在注解类的上面加入 Conditional 注解和它的三个元注解 Target、Retention、Documented。
@Target({ElementType.TYPE, ElementType.METHOD})//作用域: 1.类 2.方法
@Retention(RetentionPolicy.RUNTIME)//生效时机:运行时
@Documented//生成 JavaDoc 的文档
@Conditional(ClassCondition.class)
public @interface ConditionOnClass {
String[] value();
}
\qquad 接下来将 ClassCondition 的逻辑做一下对应的改变
public class ClassCondition implements Condition {
/**
*
* @param context 上下文对象,可以获取 environment、IOC容器、ClassLoader对象
* @param metadata 注解元对象,可以获取注解定义的属性值
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//1.需求:导入通过注解属性值 value 指定坐标后创建 Bean
//思路:获取注解属性值 value
Map<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());
boolean flag = true;
//System.out.println(map);
String[] value = (String[]) map.get("value");
try {
for (String className : value) {
Class.forName(className);
}
} catch (ClassNotFoundException e) {
flag = false;
}
return flag;
}
}
\qquad UserConfig 类里面也改为我们自定义的注解
@Configuration
public class UserConfig {
@Bean
// @Conditional(ClassCondition.class)
@ConditionOnClass("org.springframework.data.redis.core.RedisTemplate")
public User user() {
return new User();
}
}
\qquad
导入 Redis 的坐标更新 pom 文件后再来运行,运行结果如下:
\qquad
从上图我们可以看出,User 类对应的 Bean 可以被成功创建。将 Redis 坐标从 pom 文件中移除更新 pom 文件后再运行,运行结果如下:
\qquad
通过上图我们可以看出 Spring 容器并没有为 User 类创建对应的 Bean。
内置 Web 服务器
\qquad
SpringBoot 的 Web 环境中默认使用Tomcat作为内置服务器 SpringBoot 的内置服务器是可以进行切换的。
\qquad
那么怎么知道 SpringBoot 有多少服务器供选择呢?如何切换 SpringBoot 的内置服务器呢?
- 查看可供选择的服务器
\qquad
在引入的依赖中找到 spring-boot-autoconfigure
包,在org/springframework/boot/autoconfigure/web/embeded
路径下可以看到有四种服务器可供选择,分别为:Jetty、Netty、Tomcat、Undertow
- 切换内置 Web 服务器
\qquad 直接在 pom 文件中设置。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
\qquad 默认为Tomcat。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 排除 tomcat 依赖 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入 jetty 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
\qquad 以 Jetty 为例,先排除 Tomcat 依赖,再引入对应服务的依赖即可,想换成别的服务的操作也类似。
@Enable* 注解
\qquad
SpringBoot 中提供了很多 Enable 开头的注解,这些注解都是用于动态启用某些功能的,其底层原理是使用 @Import 注解导入一些配置类,实现 Bean 的动态加载。
\qquad
举个栗子,SpringBoot 工程师不能直接获取 jar 包中定义的 Bean 的,那么怎么才能获取呢?
\qquad
准备工作:
\qquad
创建一个新的名为SpringBootDemo00-Tools
的工程,在com.tyut.wyc.Domain
包下创建一个名为User
的实体类。
public class User {
}
\qquad
在com.tyut.wyc.Config
包下创建一个名为UserConfig
的配置类。
@Configuration
public class UserConfig {
@Bean
public User user() {
return new User();
}
}
\qquad
创建一个新的名为SpringBootDemo02-Enable
的工程,导入刚刚创建好的工程的坐标
<dependency>
<groupId>com.tyut.wyc</groupId>
<artifactId>SpringBootDemo00-Tools</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
\qquad 在启动类里获取上下文对象,通过上下文对象获取 Bean
@SpringBootApplication
public class SpringBootDemo02EnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemo02EnableApplication.class, args);
Object user = context.getBean("user");
System.out.println(user);
}
}
\qquad
运行启动类,获取 Bean 失败,结果如下:
- 通过
@ComponentScan("配置类所在包名")
注解获取(@ComponentScan
主要就是定义扫描的路径从中找出标识了需要装配的类自动装配到spring的bean容器中)
@SpringBootApplication
@ComponentScan("com.tyut.wyc.Config")
public class SpringBootDemo02EnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemo02EnableApplication.class, args);
Object user = context.getBean("user");
System.out.println(user);
}
}
- 通过
@Import(配置类的字节码文件)
注解获取(使用 @Import 注解导入的类会被 Spring 加载到 IOC 容器中)
@SpringBootApplication
@Import(UserConfig.class)
public class SpringBootDemo02EnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemo02EnableApplication.class, args);
Object user = context.getBean("user");
System.out.println(user);
}
}
- 通过
@Enable*
自定义注解获取(底层其实就是把 @Import 封装了一下)
\qquad
首先在SpringBootDemo00-Tools
模块下的com.tyut.wyc.Config
包下创建一个名为EnableUser
的注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(UserConfig.class)
public @interface EnableUser {
}
\qquad
然后在SpringBootDemo02-Enable
模块的启动类中引入@Enable
注解
@SpringBootApplication
@EnableUser
public class SpringBootDemo02EnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemo02EnableApplication.class, args);
Object user = context.getBean("user");
System.out.println(user);
}
}
\qquad
以上三种方式都可以成功获取到SpringBootDemo00-Tools
模块中定义好的 Bean,运行结果如下:
@Import注解
\qquad 使用 @Import 注解导入的类会被 Spring 加载到 IOC 容器中,而 @Import 提供 4 种用法:
- 导入 Bean
@SpringBootApplication
@Import(User.class)
public class SpringBootDemo02EnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemo02EnableApplication.class, args);
Object user = context.getBean("user");
System.out.println(user);
}
}
\qquad
运行结果显示找不到 Bean,如下图所示:
\qquad
那么这是为什么呢?试试通过类型来获取 Bean。
@SpringBootApplication
@Import(User.class)
public class SpringBootDemo02EnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemo02EnableApplication.class, args);
User user = context.getBean(User.class);
System.out.println(user);
}
}
\qquad
运行结果如图所示,可以成功获取。
\qquad
那么为什么不能通过名字获取呢,注意看最后的输出结果,@前面的名字和之前的不一样,之前是User@XXX
,现在是com.tyut.wyc.Domain.User@XXX
,应该是自己导入的 Bean,名字不叫user
,那么我们来看一下它叫什么,是不是叫com.tyut.wyc.Domain.User
。
@SpringBootApplication
@Import(User.class)
public class SpringBootDemo02EnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemo02EnableApplication.class, args);
User user = context.getBean(User.class);
System.out.println(user);
Map<String, User> type = context.getBeansOfType(User.class);
System.out.println(type);
}
}
\qquad
名字果然叫com.tyut.wyc.Domain.User
,输出结果如下图所示:
- 导入配置类
在这之前啊,先创建一个 Admin 实体类
public class Admin {
}
\qquad
然后在UserConfig
配置类中声明
@Configuration
public class UserConfig {
@Bean
public User user() {
return new User();
}
@Bean
public Admin admin() {
return new Admin();
}
}
\qquad 在启动类中获取一下这两个 Bean
@SpringBootApplication
@Import(UserConfig.class)
public class SpringBootDemo02EnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemo02EnableApplication.class, args);
User user = context.getBean(User.class);
System.out.println(user);
Admin admin = context.getBean(Admin.class);
System.out.println(admin);
}
}
\qquad
可以正常获取这两个 Bean,运行结果如下:
\qquad
注意:在这里,配置类中的@Configure
是可以不写的,我的理解是这样的,也不知道对不对。@Configure
注解相当xml
文件里的<beans>
标签,就是告诉 Spring,我这边有定义好的 Bean,你来帮我导入一下。而@Import
注解相当于xml
文件里的<Import>
标签,就是告诉 Spring 我要导入什么,既然我已经用@Improt
注解告诉 Spring 我的 Bean 是在哪个文件中定义好的,那就不需要用@Configure
注解来喊 Spring 来帮我导入了。
3. 导入 ImportSelector
实现类,一般用于加载配置文件中的类
\qquad
如果一个类实现了ImportSelector
接口,并且在配置类中被@Import
加入到 Spring 容器中以后。Spring 容器就会把ImportSelector
接口方法返回的字符串数组中的类 new 出来对象然后放到工厂中去。
\qquad
先看一下ImportSelector
的源代码,它提供了一个方法 selectImports
可以返回一个字符串数组。
import java.util.function.Predicate;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.Nullable;
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
\qquad
那么这个AnnotationMetadata
是什么东西呢,有什么作用,它是ClassMetadata
和AnnotatedTypeMetadata
的子接口,具有两者共同能力,并且新增了访问注解的相关方法,可以拿到被@Import
注解过的类的元数据。可以简单理解为它是对注解的抽象,我们看一下源码。
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata {
//拿到当前类上所有的注解的全类名(注意是全类名)
default Set<String> getAnnotationTypes() {
return (Set)this.getAnnotations().stream().filter(MergedAnnotation::isDirectlyPresent).map((annotation) -> {
return annotation.getType().getName();
}).collect(Collectors.toCollection(LinkedHashSet::new));
}
// 拿到指定的注解类型
//annotationName:注解类型的全类名
default Set<String> getMetaAnnotationTypes(String annotationName) {
MergedAnnotation<?> annotation = this.getAnnotations().get(annotationName, MergedAnnotation::isDirectlyPresent);
return !annotation.isPresent() ? Collections.emptySet() : (Set)MergedAnnotations.from(annotation.getType()).stream().map((mergedAnnotation) -> {
return mergedAnnotation.getType().getName();
}).collect(Collectors.toCollection(LinkedHashSet::new));
}
// 是否包含指定注解 (annotationName:全类名)
default boolean hasAnnotation(String annotationName) {
return this.getAnnotations().isDirectlyPresent(annotationName);
}
//用于判断注解类型自己是否被某个元注解类型所标注
//依赖于AnnotatedElementUtils#hasMetaAnnotationTypes
default boolean hasMetaAnnotation(String metaAnnotationName) {
return this.getAnnotations().get(metaAnnotationName, MergedAnnotation::isMetaPresent).isPresent();
}
//类里面只有有一个方法标注有指定注解,就返回true
//getDeclaredMethods获得所有方法, AnnotatedElementUtils.isAnnotated是否标注有指定注解
default boolean hasAnnotatedMethods(String annotationName) {
return !this.getAnnotatedMethods(annotationName).isEmpty();
}
Set<MethodMetadata> getAnnotatedMethods(String annotationName);
Set<MethodMetadata> getDeclaredMethods();
static AnnotationMetadata introspect(Class<?> type) {
return StandardAnnotationMetadata.from(type);
}
}
\qquad
参考文章链接:Spring元数据Metadata的使用,注解编程之AnnotationMetadata,ClassMetadata、MetadataReaderFactory【享学Spring】
\qquad
首先在SpringBootDemo00-Tools
模块下的com.tyut.wyc.Config
包下创建一个名为MyImportSelector
的类实现ImportSelector
接口。
package com.tyut.wyc.Config;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//包名后续可动态生成
return new String[]{"com.tyut.wyc.Domain.User","com.tyut.wyc.Domain.Admin"};
}
}
\qquad
然后在SpringBootDemo02-Enable
模块下的启动类中用@Import
注解导入MyImportSelector
的字节码文件,将返回的字符串数组对应的Bean
导入到 Spring 的 IOC 容器中。
@SpringBootApplication
@Import(MyImportSelector.class)
public class SpringBootDemo02EnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemo02EnableApplication.class, args);
User user = context.getBean(User.class);
System.out.println(user);
Admin admin = context.getBean(Admin.class);
System.out.println(admin);
}
}
\qquad
可以正常获取这两个 Bean,运行结果如下:
4. 导入 ImportBeanDefinitionRegister
实现类
\qquad
简单的说ImportBeanDefinitionRegistrar
,可以让我们自己注册 Bean 到 Spring IOC 容器中。
\qquad
老规矩,先看源代码:
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.type.AnnotationMetadata;
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
this.registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
\qquad
从源代码中可以看到,里面一共两个方法而且都被default
注解了,能看得出来用的确实不多。但是这个方法却拥有ImportSelector
接口内方法的一切功能,而且更牛。这两个方法是重载方法,而且第一个方法调用了第二个方法,方法一是方法二得扩展。
\qquad
其中,第一个参数是AnnotationMetadata
,这个参数和ImportSelector
中的一样,可以拿到被@Import
注解过的类的元数据。第二个参数是BeanDefinitionRegistry
,它可以通过void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
方法来创建 Bean,但是前提要有一个BeanDefinition
,我们可以通过BeanDefinitionBuilder
对象的工厂方法rootBeanDefinition()
,来初始化一个新的、基于类的抽象 Bean 定义构建器。这个构建器允许我们通过链式调用的方式来配置即将生成的BeanDefinition
的各种属性,比如设置 Bean 的类型(Class)、构造函数参数、属性值等。
\qquad
参考文章链接:Spring框架中ImportBeanDefinitionRegistrar的应用、Spring扩展机制之ImportBeanDefinitionRegistrar
\qquad
首先在 SpringBootDemo00-Tools 模块下的 com.tyut.wyc.Config 包下创建一个名为MyImportBeanDefinitionRegistrar
的类实现ImportBeanDefinitionRegistrar
接口。重写一下接口的registerBeanDefinitions
方法,注意我们这里只注册了 User。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
registry.registerBeanDefinition("user",beanDefinition);
}
}
\qquad
然后在 SpringBootDemo02-Enable 模块下的启动类中用@Import注解导入MyImportBeanDefinitionRegistrar
的字节码文件,让 Spring 启动过程创建 beanDefinition
时调用registerBeanDefinitions
方法,传入BeanDefinitionRegistry
注册器,然后我们根据自己的需求,来往 Spring 里面注册BeanDefinition
,从而做到往 IOC 容器中注册 Bean 的过程。
@SpringBootApplication
@Import(MyImportBeanDefinitionRegistrar.class)
public class SpringBootDemo02EnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemo02EnableApplication.class, args);
User user = context.getBean(User.class);
System.out.println(user);
Admin admin = context.getBean(Admin.class);
System.out.println(admin);
}
}
\qquad
注意我们前面只注册了 User,没有注册 Admin,正常我们能够取到 User 但拿不到 Admin,运行结果如下:
\qquad
那么我们现在来看一下 SpringBoot 最核心的注解@SpringBootApplication
是怎么完成自动配置的,以下是 @SpringBoot
注解的源代码:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "nameGenerator"
)
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
\qquad
我们可以看到,其中包含了@EnableAutoConfiguration
,其作用是动态启用自动加载,通过前面的阐述,我们知道 @Enable*
类注解,其底层都是通过@Import
注解实现的,查看其源代码如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
\qquad
我们发现,其中果然是通过@Import
注解导入了相关自动配置的文件AutoConfigurationImportSelector.class
观察其后缀,不难发现其是通过@Import
注解的第三种用法导入的,那么具体是不是呢,看一下AutoConfigurationImportSelector.class
的源码:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
private static final AutoConfigurationImportSelector.AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationImportSelector.AutoConfigurationEntry();
private static final String[] NO_IMPORTS = new String[0];
private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
private ConfigurableListableBeanFactory beanFactory;
private Environment environment;
private ClassLoader beanClassLoader;
private ResourceLoader resourceLoader;
private AutoConfigurationImportSelector.ConfigurationClassFilter configurationClassFilter;
public AutoConfigurationImportSelector() {
}
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
public Predicate<String> getExclusionFilter() {
return this::shouldExclude;
}
private boolean shouldExclude(String configurationClassName) {
return this.getConfigurationClassFilter().filter(Collections.singletonList(configurationClassName)).isEmpty();
}
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
public Class<? extends Group> getImportGroup() {
return AutoConfigurationImportSelector.AutoConfigurationGroup.class;
}
protected boolean isEnabled(AnnotationMetadata metadata) {
return this.getClass() == AutoConfigurationImportSelector.class ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true;
}
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
String name = this.getAnnotationClass().getName();
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
Assert.notNull(attributes, () -> {
String var10000 = metadata.getClassName();
return "No auto-configuration attributes found. Is " + var10000 + " annotated with " + ClassUtils.getShortName(name) + "?";
});
return attributes;
}
protected Class<?> getAnnotationClass() {
return EnableAutoConfiguration.class;
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).getCandidates();
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
List<String> invalidExcludes = new ArrayList(exclusions.size());
Iterator var4 = exclusions.iterator();
while(var4.hasNext()) {
String exclusion = (String)var4.next();
if (ClassUtils.isPresent(exclusion, this.getClass().getClassLoader()) && !configurations.contains(exclusion)) {
invalidExcludes.add(exclusion);
}
}
if (!invalidExcludes.isEmpty()) {
this.handleInvalidExcludes(invalidExcludes);
}
}
protected void handleInvalidExcludes(List<String> invalidExcludes) {
StringBuilder message = new StringBuilder();
Iterator var3 = invalidExcludes.iterator();
while(var3.hasNext()) {
String exclude = (String)var3.next();
message.append("\t- ").append(exclude).append(String.format("%n"));
}
throw new IllegalStateException(String.format("The following classes could not be excluded because they are not auto-configuration classes:%n%s", message));
}
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
Set<String> excluded = new LinkedHashSet();
excluded.addAll(this.asList(attributes, "exclude"));
excluded.addAll(this.asList(attributes, "excludeName"));
excluded.addAll(this.getExcludeAutoConfigurationsProperty());
return excluded;
}
protected List<String> getExcludeAutoConfigurationsProperty() {
Environment environment = this.getEnvironment();
if (environment == null) {
return Collections.emptyList();
} else if (environment instanceof ConfigurableEnvironment) {
Binder binder = Binder.get(environment);
return (List)binder.bind("spring.autoconfigure.exclude", String[].class).map(Arrays::asList).orElse(Collections.emptyList());
} else {
String[] excludes = (String[])environment.getProperty("spring.autoconfigure.exclude", String[].class);
return excludes != null ? Arrays.asList(excludes) : Collections.emptyList();
}
}
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}
private AutoConfigurationImportSelector.ConfigurationClassFilter getConfigurationClassFilter() {
if (this.configurationClassFilter == null) {
List<AutoConfigurationImportFilter> filters = this.getAutoConfigurationImportFilters();
Iterator var2 = filters.iterator();
while(var2.hasNext()) {
AutoConfigurationImportFilter filter = (AutoConfigurationImportFilter)var2.next();
this.invokeAwareMethods(filter);
}
this.configurationClassFilter = new AutoConfigurationImportSelector.ConfigurationClassFilter(this.beanClassLoader, filters);
}
return this.configurationClassFilter;
}
protected final <T> List<T> removeDuplicates(List<T> list) {
return new ArrayList(new LinkedHashSet(list));
}
protected final List<String> asList(AnnotationAttributes attributes, String name) {
String[] value = attributes.getStringArray(name);
return Arrays.asList(value);
}
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
List<AutoConfigurationImportListener> listeners = this.getAutoConfigurationImportListeners();
if (!listeners.isEmpty()) {
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
Iterator var5 = listeners.iterator();
while(var5.hasNext()) {
AutoConfigurationImportListener listener = (AutoConfigurationImportListener)var5.next();
this.invokeAwareMethods(listener);
listener.onAutoConfigurationImportEvent(event);
}
}
}
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
}
private void invokeAwareMethods(Object instance) {
if (instance instanceof Aware) {
if (instance instanceof BeanClassLoaderAware) {
BeanClassLoaderAware beanClassLoaderAwareInstance = (BeanClassLoaderAware)instance;
beanClassLoaderAwareInstance.setBeanClassLoader(this.beanClassLoader);
}
if (instance instanceof BeanFactoryAware) {
BeanFactoryAware beanFactoryAwareInstance = (BeanFactoryAware)instance;
beanFactoryAwareInstance.setBeanFactory(this.beanFactory);
}
if (instance instanceof EnvironmentAware) {
EnvironmentAware environmentAwareInstance = (EnvironmentAware)instance;
environmentAwareInstance.setEnvironment(this.environment);
}
if (instance instanceof ResourceLoaderAware) {
ResourceLoaderAware resourceLoaderAwareInstance = (ResourceLoaderAware)instance;
resourceLoaderAwareInstance.setResourceLoader(this.resourceLoader);
}
}
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory);
this.beanFactory = (ConfigurableListableBeanFactory)beanFactory;
}
protected final ConfigurableListableBeanFactory getBeanFactory() {
return this.beanFactory;
}
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
protected ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}
public void setEnvironment(Environment environment) {
this.environment = environment;
}
protected final Environment getEnvironment() {
return this.environment;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
protected final ResourceLoader getResourceLoader() {
return this.resourceLoader;
}
public int getOrder() {
return 2147483646;
}
protected static class AutoConfigurationEntry {
private final List<String> configurations;
private final Set<String> exclusions;
private AutoConfigurationEntry() {
this.configurations = Collections.emptyList();
this.exclusions = Collections.emptySet();
}
AutoConfigurationEntry(Collection<String> configurations, Collection<String> exclusions) {
this.configurations = new ArrayList(configurations);
this.exclusions = new HashSet(exclusions);
}
public List<String> getConfigurations() {
return this.configurations;
}
public Set<String> getExclusions() {
return this.exclusions;
}
}
private static class ConfigurationClassFilter {
private final AutoConfigurationMetadata autoConfigurationMetadata;
private final List<AutoConfigurationImportFilter> filters;
ConfigurationClassFilter(ClassLoader classLoader, List<AutoConfigurationImportFilter> filters) {
this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);
this.filters = filters;
}
List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
boolean skipped = false;
Iterator var6 = this.filters.iterator();
int i;
while(var6.hasNext()) {
AutoConfigurationImportFilter filter = (AutoConfigurationImportFilter)var6.next();
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
for(i = 0; i < match.length; ++i) {
if (!match[i]) {
candidates[i] = null;
skipped = true;
}
}
}
if (!skipped) {
return configurations;
} else {
List<String> result = new ArrayList(candidates.length);
String[] var12 = candidates;
int var14 = candidates.length;
for(i = 0; i < var14; ++i) {
String candidate = var12[i];
if (candidate != null) {
result.add(candidate);
}
}
if (AutoConfigurationImportSelector.logger.isTraceEnabled()) {
int numberFiltered = configurations.size() - result.size();
AutoConfigurationImportSelector.logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
}
return result;
}
}
}
private static class AutoConfigurationGroup implements Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
private final Map<String, AnnotationMetadata> entries = new LinkedHashMap();
private final List<AutoConfigurationImportSelector.AutoConfigurationEntry> autoConfigurationEntries = new ArrayList();
private ClassLoader beanClassLoader;
private BeanFactory beanFactory;
private ResourceLoader resourceLoader;
private AutoConfigurationMetadata autoConfigurationMetadata;
private AutoConfigurationGroup() {
}
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
});
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
Iterator var4 = autoConfigurationEntry.getConfigurations().iterator();
while(var4.hasNext()) {
String importClassName = (String)var4.next();
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
public Iterable<Entry> selectImports() {
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
} else {
Set<String> allExclusions = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
Set<String> processedConfigurations = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new));
processedConfigurations.removeAll(allExclusions);
return this.sortAutoConfigurations(processedConfigurations, this.getAutoConfigurationMetadata()).stream().map((importClassName) -> {
return new Entry((AnnotationMetadata)this.entries.get(importClassName), importClassName);
}).toList();
}
}
private AutoConfigurationMetadata getAutoConfigurationMetadata() {
if (this.autoConfigurationMetadata == null) {
this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
}
return this.autoConfigurationMetadata;
}
private List<String> sortAutoConfigurations(Set<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
return (new AutoConfigurationSorter(this.getMetadataReaderFactory(), autoConfigurationMetadata)).getInPriorityOrder(configurations);
}
private MetadataReaderFactory getMetadataReaderFactory() {
try {
return (MetadataReaderFactory)this.beanFactory.getBean("org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory", MetadataReaderFactory.class);
} catch (NoSuchBeanDefinitionException var2) {
return new CachingMetadataReaderFactory(this.resourceLoader);
}
}
}
}
\qquad
可以发现这个类实现了DeferredImportSelector
接口,再看一下这个接口的源代码:
public interface DeferredImportSelector extends ImportSelector {
@Nullable
default Class<? extends DeferredImportSelector.Group> getImportGroup() {
return null;
}
public interface Group {
void process(AnnotationMetadata metadata, DeferredImportSelector selector);
Iterable<DeferredImportSelector.Group.Entry> selectImports();
public static class Entry {
private final AnnotationMetadata metadata;
private final String importClassName;
public Entry(AnnotationMetadata metadata, String importClassName) {
this.metadata = metadata;
this.importClassName = importClassName;
}
public AnnotationMetadata getMetadata() {
return this.metadata;
}
public String getImportClassName() {
return this.importClassName;
}
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
} else if (other != null && this.getClass() == other.getClass()) {
DeferredImportSelector.Group.Entry entry = (DeferredImportSelector.Group.Entry)other;
return this.metadata.equals(entry.metadata) && this.importClassName.equals(entry.importClassName);
} else {
return false;
}
}
public int hashCode() {
return this.metadata.hashCode() * 31 + this.importClassName.hashCode();
}
public String toString() {
return this.importClassName;
}
}
}
}
\qquad
好了,终于到头了,这个接口继承了ImportSelector
接口,说明@SpringBootApplication
最终是通过ImportSelector
实现的项目启动时的类的加载。
总结
\qquad
那么现在来说一下 SpringBoot 是如何完成自动配置的:
\qquad
其实 SpringBoot 的自动配置依赖于它启动类上的核心注解 @SpringBootApplication
我们可以将其看作是@SpringBootConfiguration
、@EnableAutoConfiguration
和@ComponentScan
这三个注解的集合。 那么这三个注解根据官方文档的描述作用分别为:
\qquad
@SpringBootConfiguration
:允许在上下文对象中注册额外的 Bean 或导入其他配置类。
\qquad
@EnableAutoConfiguration
:开启 SpringBoot 的自动配置。
\qquad
@ComponentScan
:扫描指定包下的 Bean 并注册到上下文对象中。
\qquad
其中核心注解就是@EnableAutoConfiguration
,底层通过@Import
注解导入AutoConfigurationImportSelector
的字节码文件,其中的核心方法为selectImports()
,源代码如下:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
\qquad
该方法用于获取符合所有条件类的全限定名,这些类需要加载到 IOC 容器中去。内部的核心代码为getAutoConfigurationEntry()
它是负责加载自动配置类的,它干了些什么事情呢,源码如下:
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
//1.
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
//2.
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//3.
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//4.
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
\qquad
第一步:判断自动装配开关是否打开,默认在 application 文件中设置。
\qquad
第二步:用于获取@SpringBootConfiguration
注解中的 exclude 和 excludeName。
\qquad
第三步:获取需要自动配置的所有配置类读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring2 的早期版本是在 META/spring.factories)
\qquad
第四步:过滤掉不需要的 Bean(不是所有配置的 Bean 都会载入,底层是通过前面说的@Conditional*
注解实现的,想要其生效必须在 pom 文件中引入依赖)
自定义 Starer
\qquad
通过以上的了解我们可以自定义一个 Starter,创建一个新的名为redis-spring-boot-autoconfigration
的模块,在 pom 文件中引入 redis 依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
\qquad
在com.tyut.wyc.redis.config
包下创建一个名为RedisProperties
的配置类,用于动态传参。
@ConfigurationProperties(prefix = "redis")
public class RedisProperties {
private String host = "localhost";
private int port = 6379;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
\qquad
在同一个包下创建一个名为RedisAutoConfiguration
的配置类,用于加载参数和注册 Bean。
@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
@Bean
public Jedis jedis (RedisProperties redisProperties) {
return new Jedis(redisProperties.getHost(),redisProperties.getPort());
}
}
\qquad
最重要的,在src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件下写入自己的自动配置类的全路径类名,com.tyut.wyc.redis.config.RedisAutoConfiguration
\qquad
创建一个新的名为redis-spring-boot-starter
的模块,在 pom 文件中引入刚刚创建好的模块。
<dependency>
<groupId>com.tyut.wyc</groupId>
<artifactId>redis-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
\qquad
在SpringBootDemo02-Enable
模块的 pom 文件中引入自己创建的 starter。
<dependency>
<groupId>com.tyut.wyc</groupId>
<artifactId>redis-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
\qquad 在启动类中尝试获取 Jedis 对象。
@SpringBootApplication
public class SpringBootDemo02EnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemo02EnableApplication.class, args);
Jedis jedis = context.getBean(Jedis.class);
System.out.println(jedis);
}
}
\qquad
运行结果如下:
SpringBoot 监听机制
\qquad
什么是时间监听机制,具体可以看这篇文章,我觉得写的真心不错
SpringBoot 事件发布监听机制使用、分析、注意点 (一篇到位)
基本使用
\qquad SpringBoot的监听机制,其实是对Java提供的事件监听机制的封装。
\qquad
Java中的事件监听机制定义了以下几个角色:
\qquad
事件:Event
,继承java.util.EventObject
类的对象
\qquad
事件源:Source
,任意对象Object
\qquad
监听器:Listener
,实现java.util.EventListener
接口的对象
\qquad
SpringBoot 在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作。
\qquad
ApplicationContextInitializer
、ApplicationRunner
、CommandLineRunner
、SpringApplicationRunListener
\qquad
首先新建一个名为springboot-listener
的模块,在com/tyut/wyc/springbootlistener/listener
包下分别创建自己的对应上面四个类的监听器(注意要实现上面的接口,告知 SpringBoot 我在用你的东西,守你的规矩)
\qquad
实现ApplicationContextInitializer
接口的监听器MyApplicationContextInitializer
@Component
public class MyApplicationContextInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
System.out.println("ApplicationContextInitializer...initialize");
}
}
\qquad
实现ApplicationRunner
接口的监听器MyApplicationRunner
@Component
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner...run");
System.out.println(Arrays.asList(args.getSourceArgs()));
}
}
\qquad
实现CommandLineRunner
接口的监听器MyCommandLineRunner
@Component
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner...run");
System.out.println(Arrays.asList(args));
}
}
\qquad
实现SpringApplicationRunListener
接口的监听器MySpringApplicationRunListener
public class MySpringApplicationRunListener implements SpringApplicationRunListener{
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
System.out.println("starting...项目启动中");
}
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
System.out.println("environmentPrepared...环境对象开始准备");
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("contextPrepared...上下文对象开始准备");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("contextLoaded...上下文对象开始加载");
}
@Override
public void started(ConfigurableApplicationContext context, Duration timeTaken) {
System.out.println("started...上下文对象加载完成");
}
@Override
public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
System.out.println("running...项目启动完成,开始运行");
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("failed...项目启动失败");
}
}
\qquad
其中ApplicationContextInitializer
和SpringApplicationRunListener
需要配置,在src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件内的配置如下:
com.tyut.wyc.springbootlistener.listener.MySpringApplicationRunListener
com.tyut.wyc.springbootlistener.listener.MyApplicationContextInitializer
\qquad
启动项目,运行结果如下:
SpringBoot 启动流程分析
\qquad 这里推荐去看这篇文章:springboot启动流程-springboot(3)
SpringBoot 监控
\qquad
SpringBoot 自带监控功能Actuator,可以帮助实现对程序内部运行情况监控,比如监控状况、Bean加载情况、配置属性、日志信息等。
\qquad
使用步骤:
\qquad
1. 导入依赖坐标
\qquad
2. 访问http://localhost:8080/acruator(/*)
\qquad
新建一个名为springboot-actuator
的模块,在 pom 文件中引入依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
\qquad 如果是 SpringBoot 2,已经可以通过前端页面访问了,如果是 SpringBoot 3 版本需要手动在配置文件中开启一下 endpoints,我把所有 endpoints 都打开了,properties.yml 文件的配置信息如下:
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
\qquad
现在去前端访问:
\qquad
把 json 数据格式化一下:
\qquad
看一下 health:
\qquad
把 json 数据格式化一下:
\qquad
有个问题,这些数据不好分析啊,不用担心,其实 SpringBoot 都写好了,用 SpringBoot Admin 这个开源的项目可以帮助我们对数据进行更好的分析。
\qquad
新建一个名为springboot-admin-client
的模块,在 pom 文件中引入依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
\qquad
在 properties.yml
文件中配置传送数据的地址和暴露点:
spring:
boot:
admin:
client:
url: http://localhost:9000
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
\qquad
新建一个名为springboot-admin-client
的模块,在 pom 文件中引入依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>
\qquad 在配置文件中声明访问端口(这里和前面配置的接收数据地址的端口号一样)
server:
port: 9000
\qquad
将两个项目启动起来后在前端访问页面:
\qquad
应用墙还可以看已启动时长:
\qquad
还可以看线程的运行状态(绿色代表运行,黄色代表等待):
\qquad
细节这边还可以看内存和 CPU 情况:
\qquad
文章暂时先告一段落,本文初衷是当时快速入门了一下 SpringBoot,一来是怕到时候忘记又花很大时间成本去学习,二来是想对原理进行进一步的了解,本文涉及原理部分较浅。
\qquad
还有就是推荐学习 SpringBoot 2 版本,一来是遇到很多问题网上找不到解答,低版本遇到的问题大部分网友都又遇到并且在网上有很多解决办法,二来就是后期还可以和 SpringCloud 集成。本文用的是目前最新版的 SpringBoot 3.2.3,其中遇到很多问题要翻源码和官方文档来解决,学习阻力很大。