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 失败,结果如下:

  1. 通过@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);
    }
}
  1. 通过@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);
    }
}
  1. 通过@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 种用法:

  1. 导入 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,输出结果如下图所示:

  1. 导入配置类
    在这之前啊,先创建一个 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是什么东西呢,有什么作用,它是ClassMetadataAnnotatedTypeMetadata的子接口,具有两者共同能力,并且新增了访问注解的相关方法,可以拿到被@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 ApplicationContextInitializerApplicationRunnerCommandLineRunnerSpringApplicationRunListener
\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 其中ApplicationContextInitializerSpringApplicationRunListener需要配置,在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,其中遇到很多问题要翻源码和官方文档来解决,学习阻力很大。

  • 38
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值