一文精通SpringBoot动态自动装配原理(8)

前言

        在SpringBoot面世之前,我们在传统的SSM、SSH垂直框架中其实也经常使用注解开发,如Spring、SPringMVC、Mybatis的框架都支持注解,实际开发中比如Bean也能实现无配置化注入,但是这种注入一般是经过“人工干预”的,换句话说这种注入还是我们手动进行装配的,那时候还没有达到SpringBoot这种自动装配的效果。也正是因为Spring配置繁琐、依赖繁琐的缘故,为了更方便快捷的使用Spring,实现快速敏捷开发,Spring在2014.4推出了SpringBoot,其实在我看来,SpringBoot本质上并不算是一个新的框架,或者说一个新的技术,SpringBoot实际上只是提供了一种快速使用Spring的方式和途径,尽管这么说,SpringBoot的强大却是不可否认的。因为其提供的自动装配、起步依赖、辅助功能等突出的优点,使得开发变得简单快捷,而在这些逆天的功能背后,SpringBoot四大组件扮演着举足轻重的角色。本文通过实例演示加源码分析的方式,详细介绍SpringBoot四大组件之一的自动装配原理(auto-configuration)。--本文SpringBoot版本2.5.4,全文2W字,示例加源码分析,一文精通SpringBoot自动装配,建议收藏保存。

自动装配原理

spring.factories配置文件

首先来看SpringBoot提供的一个自动配置的包:

点进去,包内有一个META-INF文件夹,文件夹内有一个spring.factories文件,如下图:

        EnableAutoConfiguration定义了很多的配置类,大概有130个左右,SpringBoot启动的时候加载META-INF/spring.factories配置文件,然后获取EnableAutoConfiguration配置的值,作为自动配置类导入到容器中,由自动配置类帮我们进行自动配置工作。下面我们简单看一下这些配置类是什么样的,随便看一下spring.factories中的Redis配置,如下:

点进去看一眼类的源码,头部代码如下:

        @Configuration说明这是个配置类(通常和@Bean配合使用),这个配置不熟悉的请自行补课哈,这个@ConditionalOnClass暂时可以理解为条件判断注解,和其同级别的还有@ConditionalOnBean、@ConditionalOnProperty等等,具体用法后面详述。

        @Import有4种使用方式,分别为导入Bean、导入配置类、导入ImportSelector的实现类、导入ImportBeanDefinitionRegistrar的实现类,具体用法后面详述。

spring.factories配置文件加载时机

        读取该文件是在SpringBoot启动时候加载的,入口是项目主配置类中的@SpringBootApplication注解,下面跟踪一下@SpringBootApplication内部源码,看看具体在什么地方读取的。

点进去@SpringBootApplication注解,内部注解集合如下,有一个@EnableAutoConfiguration注解:

点进去@EnableAutoConfiguration注解,内部注解集合如下,有一个@Import({AutoConfigurationImportSelector.class})注解:

进入AutoConfigurationImportSelector.java类源码,内部有一个返回值为String[]的方法selectImports(AnnotationMetadata annotationMetadata) {}:


继续跟踪上图2处方法,方法体源码如下:继续跟踪上图3方法,方法体源码如下,就是这个地方读取META-INF/spring.factories配置文件:        上面说了,SpringBoot启动的时候加载META-INF/spring.factories配置文件,获取EnableAutoConfiguration配置的值,作为自动配置类导入到容器中,由自动配置类帮我们进行自动配置工作。这里提出一个问题:是否EnableAutoConfiguration配置的值作为自动配置类全部导入到容器中?既然我这么问,答案肯定是否认的,而是要根据实际项目引入的pom起步依赖、项目配置文件(yml)、用户自定义配置类等动态配置。实现这种动态的核心就是@Enable*、@Import、@ConditionalOn*注解,下面我将通过实例详细讲解他们的使用原理。在讲解之前我们先来思考一个问题:SpringBoot工程是否可以直接获取jar包中定义的Bean?这个问题是SpringBoot自动装配设计原理的背景,我们可以通过以下例子验证该问题。

案例

创建springboot-enable模块代表我们的springBoot项目、创建springboot-embedded模块代表三方jar包,如下图:

编写springboot-embedded







定义Bean类User.java

package com.embedded.domain; 
public class User {
    //该类是模拟三方jar中的一个Bean
}

定义User的配置类UserConfig.java:

package com.embedded.config;
import com.embedded.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration//表明这是个配置类
public class UserConfig {
    @Bean//标记为Bean对象
    public User user(){
        return new User();
    }
}

 pom.xml什么都不用引入,原生即可,如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.embedded</groupId>
    <artifactId>springboot-embedded</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-embedded</name>
    <description>springboot-embedded</description>
</project>

因为springboot-embedded这个模块不需要启动,只是模拟三方jar,除了上述三步,模块中别的全部都可以删了。

编写springboot-enable

pom.xml中引入springboot-embedded的坐标依赖,模拟导入三方jar,如下:

<dependency>
    <groupId>com.itlean</groupId>
    <artifactId>springboot-embedded</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

编写启动类:

package com.itlean; 
import com.embedded.domain.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
        User user = (User)context.getBean("user");
        System.out.println(user);
    }
}

        说明一下,启动类中的run方法实际上是有返回值的,返回的其实就是Spring的上下文对象,通过getBean方法可以直接获取IOC中的Bean。启动springboot-enable项目,发现报错如下:

        说明SpringBoot无法直接获取第三方jar中定义的Bean,那么SpringBoot为了实现自动装配是如何解决这个问题的呢,我们暂时不纠结SpringBoot是如何解决的,先来看看我是怎么解决这个问题的,接下来我继续采用代码演示的方式由浅入深介绍。

@Enable*注解详解

下文介绍三种方式,解决SpringBoot不能直接获取到第三方jar中定义的Bean的问题。

1、第一种方式(扫包范围)

        SpringBoot之所以不能直接获取到第三方jar中定义的Bean,根本原因是没有加载到配置类,从而不能创建匹配的Bean对象,既然是这个原因,那是不是我指定下扫包范围,让项目启动的时候扫到UserConfig.java这个配置类就行了呢,有思路就来试试,修改springboot-enable启动类,添加扫包范围注解,将三方jar中的UserConfig.java所在包进行手动扫描:

package com.itlean;
import com.embedded.domain.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan("com.embedded.config") //UserConfig.java手动扫描加入IOC容器
public class App {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
        User user = (User)context.getBean("user");
        System.out.println(user);
    }
}

        启动springboot-enable,发现可以正常获取到User对象,但是这种方式太粗放了,我用你一个对象,完了我还得知道这个对象在三方jar中所在的包,如果所有的都这么搞,程序员要疯了,该方式果断弃用!

2、第二种方式(@Import注解)

        我们知道使用@Import注解导入的配置类会直接被加载到IOC容器中并创建匹配的Bean对象,那么我们是不是可以在项目启动时候,将User.java的配置类UserConfig.java直接使用@Import注解导入呢,有思路我们再来试试,修改springboot-enable启动类如下:

package com.itlean;
import com.embedded.config.UserConfig;
import com.embedded.domain.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
@SpringBootApplication
@Import(UserConfig.class)//直接将User的配置类UserConfig.class导入
public class App {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
        User user = (User)context.getBean("user");
        System.out.println(user);
    }
}

        启动springboot-enable,发现可以正常获取到User对象,这种方式要比方式一简洁很多了,不再需要知道被使用的Bean的包路径和进行扫包范围配置,但是还是要记住配置类的名字,我用一个对象,还需要知道这个对象对应的配置类叫什么,显然还是不够友好。

3、第三种方式(封装@Import注解)

        以上两种方式都是在调用方做处理,也就是说,调用方想尽办法解决矛盾,这种思维肯定是不好的,问题应该在提供方就解决了,别人才能舒心乐意用你的产品是吧,第三种就是在三方jar里面做处理,我们可以在三方jar里面对方式二中@Import注解进行封装优化,对外暴露一个更简洁的注解,如下方式,既然调用方想使用我的User对象,又不想太麻烦,那我提供方直接定义一个见名识意的注解,调用方使用注解就可以直接使用我三方jar中的User对象。

在springboot-embedded中新定义一个注解类如下:

package com.embedded.annotation;
import com.embedded.config.UserConfig;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(UserConfig.class)// 将UserConfig.class引入,调用方就不需要知道包路径和配置类名称了
public @interface EnableUser {
}

修改调用方springboot-enable启动类如下:

package com.itlean;
import com.embedded.annotation.EnableUser;
import com.embedded.domain.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
@EnableUser //这种使用起来已经非常简洁了,而且见文识义
public class App {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
        User user = (User)context.getBean("user");
        System.out.println(user);
    }
}

        启动springboot-enable,可以获取User对象,这种注解看起来是不是更加精简了,而且见文识义,实际开发中,都是使用这种方式。

        在上面的例子中,细心的小伙伴是不是已经明白我的用意是什么了,是的,我就是为了引处@Enable*这个注解,在SpringBoot中有很多@Enable*注解,这类注解是开启某些功能的注解,就像本例中开启User对象一样。我们再回过头来看本文最开始介绍的SpringBoot项目主配置类的@SpringBootAppliction注解,其内部注解组合中有一个@EnableAutoConfiguration注解,@EnableAutoConfiguration就是开启自动配置的注解。

        细心的小伙伴又在对比中发现了,SpringBoot项目主配置类的@SpringBootAppliction注解内部的@EnableAutoConfiguration注解内部又使用了@Import({AutoConfigurationImportSelector.class})注解,和本文中的@EnableUser内部使用@Import(UserConfig.class)非常的像,没错,SpringBoot中@Enable*这类注解底层就是使用@Import方式来实现Bean的注入。既然说到@Import注解,那么这个注解有哪些使用方式呢,下文我将详述其具体用法。

@Import注解详解

@Import注解有4种使用方式:

① 导入Bean;
② 导入配置类;
③ 导入 ImportSelector 实现类,一般用于加载配置文件中的类;
④ 导入 ImportBeanDefinitionRegistrar 实现类。

1、导入Bean

        上文讲解@Enable*注解时,为了解决SpringBoot不能直接获取三方jar中的Bean的第二种方式是在调用方导入对应User这个Bean的配置类UserConfig.class,其实也可以直接在调用方导入对应User这个Bean。

修改springboot-enable的启动类如下,启动后正常获取到User对象,但是这种方式导入的Bean不能使用别名获取,只能根据类型获取:

package com.itlean;
import com.embedded.domain.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
import java.util.Map;
@SpringBootApplication
@Import(User.class)//直接导入三方jar中的对象
public class App {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
        //此处不能用别名获取了,因为SpringBoot注入IOC容器的时候不一定就叫user,要根据类型获取
        User user = context.getBean(User.class);
        System.out.println(user);
        //获取IOC中所有User类型的对象
        Map<String, User> users = context.getBeansOfType(User.class);
        System.out.println(users);
    }
}

2、导入配置类

        其实就是上文讲解@Enable*注解时,解决SpringBoot不能直接获取三方jar中的Bean的第二种方式,调用方导入对应User这个Bean的配置类UserConfig.class,不过此处我会变动一下配置类,看看你是否会有什么疑问。

springboot-embedded中新定义一个Bean为Role.java:

package com.embedded.domain;
public class Role {
}

springboot-embedded中修改配置类UserConfig.java:

package com.embedded.config;
import com.embedded.domain.Role;
import com.embedded.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class UserConfig {
    @Bean
    public User user(){
        return new User();
    }
    @Bean
    public Role role(){
        return new Role();
    }
}

修改springboot-enable的启动类如下:

package com.itlean;
import com.embedded.config.UserConfig;
import com.embedded.domain.Role;
import com.embedded.domain.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
import java.util.Map;
@SpringBootApplication
@Import(UserConfig.class)//直接导入三方jar中的对象的配置类
public class App {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
        User user = (User)context.getBean("user");
        System.out.println(user);
        Role role = (Role)context.getBean("role");
        System.out.println(role);
        //获取IOC中所有User类型的对象
        Map<String, User> users = context.getBeansOfType(User.class);
        System.out.println(users);
        Map<String, Role> roles = context.getBeansOfType(Role.class);
        System.out.println(roles);
    }
}

     

         细心的你一定会发现UserConfig.java中少了@Configuration注解,没错,调用方使用@Import导入配置类方式情况下,三方jar配置Bean的配置类可以不加@Configuration注解,细心的你一定也发现了这个配置类中配置了两个Bean,一个User一个Role,这种方式是否有弊端呢,答案是肯定有的,调用方如果只想使用User对象,导入一个UserConfig.class配置类,结果却把Role对象也注入IOC了,如果一个配置类里面配置了几十上百个Bean,一下子全部注入到IOC了,有一些我们用不到的也一并注入了,肯定是不合理的。

3、导入 ImportSelector 实现类

        因为导入配置类存在的弊端,导入ImportSelector实现类的方式应运而生,ImportSelector是一个接口,我们先来看一下源码:

package org.springframework.context.annotation;
import java.util.function.Predicate;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.Nullable;
public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);
    @Nullable
    default Predicate<String> getExclusionFilter() {
        return null;
    }
}

        ImportSelector接口源码类中有String[] selectImports(AnnotationMetadata var1)方法,该方法返回值是Bean的全限定路径名,这个方法需要我们来实现它。

在springboot-embedded模块中新自定义一个名为MyImportSelecto.java的实现类:

package com.embedded.config;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        //入参为要创建的Bean的全限定路径名
        return new String[]{"com.embedded.domain.User","com.embedded.domain.Role"};
    }
}

修改springboot-enable的启动类如下:

package com.itlean;
import com.embedded.config.MyImportSelector;
import com.embedded.config.UserConfig;
import com.embedded.domain.Role;
import com.embedded.domain.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
import java.util.Map;
@SpringBootApplication
@Import(MyImportSelector.class)//直接导入三方jar中的对象的ImportSelector接口的实现类MyImportSelector配置类
public class App {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
        //此处不能用别名获取了,因为SpringBoot注入IOC容器的时候不一定就叫user,要根据类型获取
        User user = (User)context.getBean(User.class);
        System.out.println(user);
        Role role = (Role)context.getBean(Role.class);
        System.out.println(role);
        //获取IOC中所有User类型的对象
        Map<String, User> users = context.getBeansOfType(User.class);
        System.out.println(users);
        Map<String, Role> roles = context.getBeansOfType(Role.class);
        System.out.println(roles);
    }
}

 

        该方式可以将需要注入的Bean的全路径限定名以数组方式在MyImportSelector.java中动态指定,比如配置在配置文件中,根据实际业务动态读取,更加可控灵活。现在我们再次回过头来看本文最开始介绍的SpringBoot项目主配置类的@SpringBootAppliction注解,其内部注解组合中有一个@EnableAutoConfiguration注解,而@EnableAutoConfiguration注解内部又使用@Import({AutoConfigurationImportSelector.class})注解,这个AutoConfigurationImportSelector其实就是@Import第三种使用方式。简单看一下AutoConfigurationImportSelector.java源码,AutoConfigurationImportSelector.java实现DeferredImportSelector.java,DeferredImportSelector.java实现ImportSelector.java,源码截图如下,这样整体串下来,@Enable*注解和@Import注解的暧昧关系是不是瞬间明了。

4、导入ImportBeanDefinitionRegistrar实现类

首先来看一下ImportBeanDefinitionRegistrar.java接口源码:

package org.springframework.context.annotation;
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) {
    }
}

        源码内部有一个default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {}方法,现在我们实现它。

在springboot-embedded中新自定义一个名为MyImportBeanDefinitionRegistrar.java的实现类:

package com.embedded.config;
import com.embedded.domain.User;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AbstractBeanDefinition userBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
        registry.registerBeanDefinition("user", userBeanDefinition);
    }
}

修改springboot-enable的启动类如下:

package com.itlean;
import com.embedded.config.MyImportBeanDefinitionRegistrar;
import com.embedded.config.MyImportSelector;
import com.embedded.config.UserConfig;
import com.embedded.domain.Role;
import com.embedded.domain.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
import java.util.Map;
@SpringBootApplication
@Import(MyImportBeanDefinitionRegistrar.class)
public class App {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
        User user1 = (User)context.getBean(User.class);
        User user2 = (User)context.getBean("user");//MyImportBeanDefinitionRegistrar中定义了别名
        System.out.println(user1);
        System.out.println(user2);
        //获取IOC中所有User类型的对象
        Map<String, User> users = context.getBeansOfType(User.class);
        System.out.println(users);
    }
}

        讲解完@Enable*和@Import注解,大家整体对SpringBoot的自动装配大致有了一个概念,其实就是SpringBoot在启动的时候,@SpringBootApplication注解内部的@EnableAutoConfiguration开启了自动配置功能,读取spring-boot-autoConfigure-2.5.4.jar中的META-INF/spring.factories配置文件,然后获取EnableAutoConfiguration配置的值,再采用@Import的第三种使用方式(导ImportSelector实现类作为自动配置类)将各个配置类导入到IOC容器中,最后创建配置类匹配的对象。但是在最终创建对象之前还有一个非常重要的步骤,就是@Condition条件判断注解,SpringBoot就是通过它进行判断那些类需要创建哪些类不需要创建,,从而实现动态装配的。

@Condition详解

        @Condition注解Spring4中新增的条件判断注解,使用该注解之后,在做依赖注入的时候,会检测是否满足某个条件再来决定是否要注入某个类。SpringBoot基于spring4的这个注解,实现了多个不同用处的判断条件注解,提供给开发人员使用,SpringBoot在自动装配过程中也用到了这一注解。具体是在SpringBoot注解包spring-boot-autoconfigure包下面:

我们随便点开Redis的配置类进行简单说明,包路径如下图:

        如果SpringBoot提供的这些注解无法满足我们的要求,也可以使用@Conditional自定义条件注解。此处简单列举几个SpringBoot已经提供的如下:

        1.@ConditionalOnBean :匹配给定的class类型或Bean的名字是否在SpringBeanFactory中存在;

        2.@ConditionalOnClass:匹配给定的class类型是否在类路径(classpath)中存在;

        3.@ConditionalOnProperty: 配置文件配置了指定的信息时;

        4.@ConditionalOnJava :匹配JDK的版本,其中range枚举类型有两个可选值; 

        5.@ConditionalOnMissingBean:spring上下文中不存在指定bean时;

        6.@ConditionalOnWebApplication:在web环境下创建。

这只是其中一部分,下面我们选择@ConditionalOnProperty演示一下这类注解的使用方式,另外再手写一个自定义@Conditional条件注解,感受一下这类注解到底是怎么使用的。

@ConditionalOnProperty演示

下配置文件中有如下配置才创建User对象:

itLean: keep moveing

在springboot-embedded中修改自定义配置类UserConfig.java如下:

package com.config;
import com.condition.ClassCondition;
import com.condition.ConditionOnClass;
import com.domain.User;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfig {
    @Bean
    // 含义是:当配置文件中存在key=itLean, value=keep moveing 的配置时候才会创建user的Bean
    @ConditionalOnProperty(name = "itLean", havingValue = "keep moveing")
    public User user(){
        return new User();
    }
}

在springboot-enable配置文件application.yml中添加如下配置:

itLean: keep moveing

springboot-enable启动类如下:

package com;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
        User user = (User)context.getBean("user");
        System.out.println(user);
    }
}

        如果将配置文件内配置信息删除,或者修改参数,则不满足Condition判断条件从而不会创建User,启动后无法获取User对象,以上就是对SpringBoot提供的现有@ConditionalOn*注解的简单用法示例。

自定义@Conditional条件注解

需求:项目中引入Jedis坐标的时候才创建User对象。

在springboot-embedded中新建MyConditionalOnClass.java条件判断类:

package com.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class MyConditionalOnClass implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        boolean flag = true;
        try {
            /*
            导入Jedis坐标后,加载该Bean,否则不加载,
            如果Jedis坐标导入,Jedis类字节码会被加载到内存中,我们通过字节码反射获取类对象,
            如果获取异常,说明当前环境中没有Jedis坐标
             */
            Class.forName("redis.clients.jedis.Jedis");
        } catch (ClassNotFoundException e) {
            flag = false;
        }
        return flag;
    }
}

在springboot-embedded中修改自定义配置类UserConfig.java:

package com.domain.config;
import com.condition.ClassCondition;
import com.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfig {
    /*
    MyConditionalOnClass是我们自定义的Condition条件判断类,
    类中重写org.springframework.context.annotation.Condition接口中的matches方法,返回Boolean类型结果
    @Conditional(UserCondition.class) 返回true则创建当前User这个Bean,否则不创建
     */
    @Bean
    @Conditional(MyConditionalOnClass.class)
    public User user(){
        return new User();
    }
}

springboot-enable启动类如下:

package com;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
        User user = (User)context.getBean("user");
        System.out.println(user);
    }
}

不导入Jedis坐标,启动springboot-enable后控制台输出如下:

导入Jedis坐标:

<dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
</dependency>

再次启动springboot-enable后控制太输出如下:

         说明我们自定义的Condition条件判断生效了,以上就是@ConditionOn*注解的使用。

SpringBoot动态自动装配原理总结

        SpringBoot在启动的时候,@SpringBootApplication注解内部的@EnableAutoConfiguration开启自动配置功能,读取spring-boot-autoConfigure-2.5.4.jar中的META-INF/spring.factories配置文件,然后获取EnableAutoConfiguration配置的值,再采用@Import的第三种使用方式(导ImportSelector实现类作为自动配置类)将各个配置类导入到IOC容器中,接着根据@ConditionalOn*注解进行条件判断确定是否创建对应匹配的类,如果@ConditionalOn*返回为true,最后才创建对象。

学到了就点赞收藏吧,一起加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值