SpringBoot自动配置

一、Condition条件判断功能

Condition 是在Spring 4.0 增加的条件判断功能,其主要作用是判断条件是否满足,从而决定是否初始化并向容器注入Bean对象。通过@Conditional注解及其一系列的其他相关注解实现。

在Spring Boot中,条件匹配(Condition Evaluation)是一个非常重要的特性,它允许开发者根据特定的条件来决定是否创建和配置Bean。这一机制在Spring框架中是通过@Conditional注解及其一系列的变体来实现的。

以下是一些关于Spring Boot中条件匹配的基础知识:

1.@Conditional 注解

@Conditional 注解可以放在类或者方法上。当你放置在配置类上时,只有当所有指定的条件都满足时,配置类中的Bean才会被创建。放置在Bean声明的方法上时,只有当条件满足时,对应的Bean才会被注册。

@Configuration
@Conditional(YourCondition.class)
public class YourConfig {
    // ...
}
2. 条件类

matches 方法两个参数:

context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等。

metadata:元数据对象,用于获取注解属性

你需要创建一个实现了Condition接口的类,来定义你的条件。

public class YourCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 你的条件逻辑
        return true; // 或者 false
    }
}
3. 内置条件注解

Spring Boot提供了一系列内置的条件注解,可直接使用:

  • @ConditionalOnBean: 当指定的Bean存在时。
  • @ConditionalOnMissingBean: 当指定的Bean不存在时。
  • @ConditionalOnClass: 当指定的字节码文件存在时。
  • @ConditionalOnMissingClass: 当指定的字节码文件不存在时。
  • @ConditionalOnProperty: 当指定的属性有指定的值时。
  • @ConditionalOnResource: 当指定的资源存在时。
  • @ConditionalOnWebApplication: 当项目是一个Web应用程序时。
  • @ConditionalOnNotWebApplication: 当项目不是一个Web应用程序时。

4. 示例1

假设我们只有在类SomeService存在时才想要创建一个Bean。

@Configuration
public class ExampleConfig {
    @Bean
    @ConditionalOnClass(SomeService.class)
    public SomeBean someBean() {
        return new SomeBean();
    }
}

在这个例子中,SomeBean对象只会在SomeService类存在时被创建。

5.示例2

在Spring IOC容器中有一个User的Bean对象:

1.导入Jedis坐标后,加载该User;没导入,则不加载,通过注解实现。

  • 创建一个User对象
public class User {
}
  • 在配置类UserConfig中加载User对象
@Configuration
public class UserConfig {
    @Bean
    @Conditional(value = ClassCondition.class)
    public User user(){
        return new User();
    }
}
  • 在ClassCondition类中判断Jedis坐标是否导入
public class ClassCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //固定输入坐标
       boolean flag=true;
       try {
           Class<?> aClass = Class.forName("redis.clients.jedis.Jedis");
       } catch (ClassNotFoundException e) {
           flag=false;
       }
       return flag;
    }
}

2.动态指定文件,通过自定义注解实现

  • 自定义注解
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(value = ClassCondition.class)
public @interface ConditiononClass {
    String[] value();
}
  • 配置类UserConfig,使用自定义注解@ConditiononClass实现

动态指定坐标,将坐标保存在String类型的数组中,只有数组中所有的坐标都被导入时,才会加载对象

@ConditiononClass("redis.clients.jedis.Jedis")
public User user1(){
    return new User();
}
@ConditiononClass(value = {"redis.clients.jedis.Jedis","com.alibaba.fastjson.JSON"})
public User user1(){
    return new User();
}
  • 在ClassCondition类中判断String类型的数组中的坐标是否导入
public class ClassCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
         //动态坐标或坐标数组
        Map<String, Object> map = metadata.getAnnotationAttributes(ConditiononClass.class.getName());
        System.out.println(map);
        String[] classname = (String[])map.get("value");

        boolean flag=true;
        try {
            for (String name:classname){
                Class<?> aClass = Class.forName(name);
            }
        } catch (ClassNotFoundException e) {
            flag=false;
        }
        return flag;
    }
}

3.启动类

@SpringBootApplication
public class SpringbootCondition01Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootCondition01Application.class, args);
        //固定坐标在condition输入,使用原有注解实现
        Object user = context.getBean("user");
        System.out.println(user);
        //不存在坐标:NoSuchBeanDefinitionException: No bean named 'user' available
        //存在坐标:com.example.springboot_condition_01.domain.User@726a6b94

        //动态输入坐标,在config配置类使用自定义注解@ConditiononClass(坐标数组)输入,使用自定义注解实现
       Object user1 = context.getBean("user1");
       System.out.println(user1);
        Object user2 = context.getBean("user2");
        System.out.println(user2);
    }

}

二、@Enable注解

SpringBoot中提供了很多Enable开头的注解,都用于启动某些功能。其底层原理是使用@Import注解导入一些配置类,实现Bean对象的加载。使用@Import导入的类会被Spring加载到IOC容器中。

源代码分析:

@Import提供4中用法:

1. 导入Bean
  • 创建enable模块

创建一个User的Bean对象

创建一个配置类,加载User对象

  • 测试模块

依赖enable的模块

<dependency>
  <groupId>com.apesource</groupId>
  <artifactId>enable</artifactId>
  <version>0.0.1SNAPSHOT</version>
</dependency>

在启动类测试获取User

@SpringBootApplication
@Import(User.class)
public class SpringbootEnable01Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootEnable01Application.class, args);
        User user = run.getBean(User.class);
        System.out.println(user);
    }

}

方法二:在enable模块写一个@Enable注解

在测试模块的启动类上方通过@EnableUser注解导入Bean对象

2. 导入配置类

在启动类上方导入enable模块的config配置类

也可以在启动类上扫描enable模块的config配置类

3. 导入 ImportSelector 实现类
  • enable模块:

创建一个Student 的对象

MyImportSelector类,实现了ImportSelector接口,重写接口中的selectImports方法,往String[]中传入两个Bean对象

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.example.springboot_enable_02_other.domain.User","com.example.springboot_enable_02_other.domain.Student"};
    }
}
  • 测试模块
@SpringBootApplication

//ImportSelector实现类
@Import(MyImportSelector.class)

public class SpringbootEnable01Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootEnable01Application.class, args);

        //ImportSelector实现类
        User user = run.getBean(User.class);
        System.out.println(user);
        Student student = run.getBean(Student.class);
        System.out.println(student);
    }

}
4. 导入 ImportBeanDefinitionRegistrar 实现类
  • enable模块

MyImportBeanDefinitionRegister类,实现了ImportBeanDefinitionRegistrar接口,重写接口中的registerBeanDefinitions方法,往String[]中传入两个Bean对象

public class MyImportBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //AnnotationMetadata注解
        //BeanDefinitionRegistry向spring容器中注入

        //1.获取user的definition对象
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
        //2.通过beanDefinition属性信息,向spring容器中注册id为user的对象
        registry.registerBeanDefinition("user",beanDefinition);
    }
}
  • 测试模块
@SpringBootApplication

//ImportBeanDefinitionRegister实现类
@Import(MyImportBeanDefinitionRegister.class)
public class SpringbootEnable01Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootEnable01Application.class, args);
        //ImportBeanDefinitionRegister实现类
       User user = run.getBean(User.class);
       System.out.println(user);
    }

}

三、@EnableAutoConfiguration 注解

@SpringBootApplication

标明这个类是一个主启动类

@SpringBootApplication注解内部详细代码如下

@SpringBootConfiguration

作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;

@ComponentScan

作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中

@EnableAutoConfiguration:开启自动配置功能

当SpringBoot扫描到@EnableAutoConfiguration注解时,会将所有包名为autoconfigure的包下的META-INF/spring.factories文件中org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的value里的所有xxxConfiguration类加载到IOC容器中。

而xxxAutoConfiguration类一般都会有@ConditionalOnxxx注解,通过这些条件注解来判断是否真正的创建xxxConfiguration对象。

@AutoConfigurationPackage :自动配置包

Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器


spring boot处理@EnableAutoConfiguration源码分析

EnableAutoConfiguration进入AutoConfigurationImportSelector

AutoConfigurationImportSelector实现了DeferredImportSelector接口,而DeferredImportSelector这个接口实现了ImportSelector接口

所以在AutoConfigurationImportSelector类重写了selectImports方法

进入getAutoConfigurationEntry方法

进入getCandidateConfigurations方法

getCandidateConfigurations会到classpath下的读取META-INF/spring.factories文件的配置,并返回一个字符串数组。

当SpringBoot应用启动时,会自动加载这些配置类

在所有包名叫做autoConfiguration的包下面都有META-INF/spring.factories文件

总结原理:

  • @EnableAutoConfiguration 注解内部使用 @Import(AutoConfigurationImportSelector.class) 来加载配置类。
  • 配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当 SpringBoot应用启动时,会自动加载这些配置类,初始化Bean。但并不是所有的Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean

四、自定义启动器

自定义redisstarter,要求当导入redis坐标时,SpringBoot自动创建Jedis的Bean

直接设置port、host

1.1 创建redisspringbootautoconfigure模块
  • 导入Jedis坐标
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
</dependency>
  • 初始化Jedis的Bean
@Configuration
public class RedisAutoconfiguration {
    @Bean
    public Jedis jedis(){
        return new Jedis("localhost",6379);
    }
}
  • 在resources中定义METAINF/spring.factories文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.apesource.redisspringbootautoconfigure.RedisAutoconfiguration
1.2 创建redisspringbootstarter模块

依赖redisspringbootautoconfigure的模块

<dependency>
  <groupId>com.apesource</groupId>
  <artifactId>redisspringbootautoconfigure</artifactId>
  <version>0.0.1SNAPSHOT</version>
</dependency>
1.3 测试模块
  • 引入自定义的redisstarter依赖
<dependency>
  <groupId>com.apesource</groupId>
  <artifactId>redisspringbootstarter</artifactId>
  <version>0.0.1SNAPSHOT</version>
</dependency>
  • 测试获取Jedis的Bean,操作redis。
@SpringBootApplication
public class SpringbootStarter01Application {
    public static void main(String[] args) {

        ConfigurableApplicationContext run = SpringApplication.run(SpringbootStarter01Application.class, args);
        Jedis bean = run.getBean(Jedis.class);
        System.out.println(bean);
    }

}

//BinaryJedis{Connection{DefaultJedisSocketFactory{localhost:6379}}}

在application.yml文件设置port、host

2.1 测试类
spring:
  redis:
    host: localhost
    port: 6060
2.2 redisspringbootautoconfigure模块
  • 批量注入写在yml文件中的host和port
//批量注入
@ConfigurationProperties(prefix = "spring.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;
    }
}
  • 初始化Jedis的Bean时传入redisProperties对象(即:port、host),通过get方法获得对应的值
@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoconfiguration {
    @Bean
    public Jedis jedis(RedisProperties redisProperties){
        return new Jedis(redisProperties.getHost(),redisProperties.getPort());
    }
}
2.3 在测试类中获取,方法与上面相同

得到结果:BinaryJedis{Connection{DefaultJedisSocketFactory{localhost:6060}}}

  • 21
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值