SpringBoot自动装配 @Import

1 注解说明

​ 我们知道 Spring 最核心的内容就是 IOC,包括 AOP 也是依托于IOC,而提到 IOC 就必然离不开 bean。 将 bean 实例注入到 IOC 容器中的两个常见的注解便是 @Component 和 @Bean。

1.1 注解 @Component

​ @Component 放置在类上面,需要配合@ComponentScan 注解使用,默认是单例,可配合 @Scope (“”) 设置为其他作用域。这个应用太过平常,这里不再赘述,包括它的一些常见的派生注解

@Controller@Service@Repository@Configuration

1.2 注解 @Bean

​ 与@Component不同,@Bean 放在方法上,能够根据配置参数生成bean 实例,如果未通过 @Bean(name=“”) 指定 bean 的名称,则默认与标注的方法名相同。@Bean 注解可以指定初始化和销毁方法,如 @Bean (name=“myBean”,initMethod=“myInit”,destroyMethod=“myDestory”)。

​ @Bean 注解同样默认作用域为单例 singleton 作用域,可通过 @Scope (“”) 设置为其他作用域。

1.3 注解 @Import

​ 除了常见了@Component 和 @Bean,业务代码里几乎不会用到的 @Import 注解同样可以将实例加入 IOC 容器中,这个@Import 注解就是 springboot 自动装配原理的主角。

​ @Import 只能用在类上,尤其适用于导入第三方包的bean对象。使用方法如下:

​ 例如,有DemoOne.java 和DemoTwo.java

@Import({ DemoOne.class,DemoTwo.class... })

​ 通过 “DemoOne.class” 参数,可以直接将DemoOne 的实例注入到 IOC 容器中;此外如果DemoTwo.java 实现Spring 提供的以下两个特殊的接口之一,也可以间接的注入DemoTwo中自定义配置的其他类。

​ 1、实现 ImportSelector 接口的类

​ 2、实现 ImportBeanDefinitionRegistrar 接口的类

2 @Import注解使用

​ 我们知道 springboot中,默认Application.java 所在包下的 Component都会被注入IOC容器。
在这里插入图片描述
如果我们在Application.java的一个平级目录下创建一个 ClonetxConfiguration,那么idea就会提示 Application context not configured for this file。

@Configuration
//Application context not configured for this file
public class ClonetxConfiguration {

    @Bean
    public ClonetxBean clonetxBean(){
        ClonetxBean clonetxBean = new ClonetxBean();
        clonetxBean.setValue("123");
        return clonetxBean;
    }
}

​ 上面我们提到,@Configuration 是 @Component 的派生注解,那么想要令其生效,就可以通过增加包扫描路径配置。@ComponentScan 示例如下:

@SpringBootApplication
@ComponentScan(basePackages = {"cn.clonetx","com.cloneli"})
public class Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
    }
}

get http://localhost:8080/aaa 会打印 123

@RestController
public class TestController {

    @Resource
    private ClonetxBean clonetxBean;

    @GetMapping("aaa")
    public void te() {
        System.out.println(clonetxBean.getValue());
    }
}

2.1 @Import 直接导入

​ 除了上面的方方式,我们也可以通过 @Import 来达到目的,这种方式不需要@Component,所有有没有 @Configuration 注解都不影响。

@SpringBootApplication
//@ComponentScan(basePackages = {"cn.clonetx","com.cloneli"})
@Import(value = {ClonetxConfiguration.class})
public class Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
    }
}
//@Configuration 
public class ClonetxConfiguration {

    @Bean
    public ClonetxBean clonetxBean(){
        ClonetxBean clonetxBean = new ClonetxBean();
        clonetxBean.setValue("123");
        return clonetxBean;
    }
}

get http://localhost:8080/aaa 一样会打印 123

2.2 实现 ImportSelector

​ 实现 ImportSelector 接口并在接口 selectImports 方法中返回配置类的全类名数组,间接注册 selectImports方法返回的class全类名数组。

public class MyImportSelector implements ImportSelector {
  
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        
        return new String[]{ClonetxConfiguration.class.getName()};
    }
}

@Import 的配置也需要改为 ImportSelector 的实现类,由于selectImports方法返回的是数组,显然可以一次配置多个。

@SpringBootApplication
//@Import(value = {ClonetxConfiguration.class})
@Import(value = {MyImportSelector.class})
public class Application {

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

get http://localhost:8080/aaa 仍然会打印 123

2.3 实现 ImportBeanDefinitionRegistrar

​ 这种类似于 ImportSelector 的用法,但可以更灵活自定义 Bean 的描述信息注册给 Ioc 容器,如示例:

public class MyImportBeanDefitionRegister implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //指定bean定义信息
        RootBeanDefinition beanDefinition = new RootBeanDefinition(CommonBean.class);
        MutablePropertyValues propertys = new MutablePropertyValues();
        //设置属性值 number
        propertys.add("number","this is number");
        //设置属性值 value
        PropertyValue property = new PropertyValue("value","this is value");
        propertys.addPropertyValue(property);
        beanDefinition.setPropertyValues(propertys);
        //注册bean,指定bean名字(id)
        registry.registerBeanDefinition("commonBean", beanDefinition);

        BeanDefinition rootBeanDefinition = new RootBeanDefinition(ClonetxConfiguration.class);
        registry.registerBeanDefinition("clonetxConfiguration", rootBeanDefinition);

    }
}

public class CommonBean {

    private String value;
    private String number;
	//省略setter、getter
  
}

同样,@Import 的配置需要改为 MyImportBeanDefitionRegister.class

@SpringBootApplication
@Import(value = {MyImportBeanDefitionRegister.class})
public class Application {

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

get http://localhost:8080/aaa 仍然会打印 123 和 this is number;this is value

@RestController
public class TestController {

    @Resource
    private ClonetxBean clonetxBean;

    @Resource
    private CommonBean commonBean;

    @GetMapping("aaa")
    public void te() {
        System.out.println(clonetxBean.getValue());
        System.out.println(commonBean.getNumber() +"; "+ commonBean.getValue());
    }
}

2.4 @EnableXXX

​ @Import 在 Spring 的源码中是很普遍的,尤其是实现接口 ImportSelector的方式,但是在普通业务开发中很少直接使用,我们常用的是 @EnableXXX 注解。如果查看这些注解的定义就会发现,其实它们都是使用的@Import

@EnableCaching
@EnableAsync
@EnableConfigurationProperties
@EnableScheduling

那我们就自己尝试定义一个@Enable 注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({HelloConfiguration.class})
public @interface EnableHello {

}
public class HelloConfiguration {

    @Bean
    public Hello hello() {
        Hello hello = new Hello();
        hello.setName("java");
        return hello;
    }
}
public class Hello {

    private String name;

    public void sayHello(){
        System.out.println("Hello "+ name);
    }
	//省略setter和 getter
}

最后来测试一下,get http://localhost:8080/aaa 就会打印 Hello java

@RestController
@EnableHello
public class TestController {

    @Resource
    private Hello hello;

    @GetMapping("aaa")
    public void te() {
        hello.sayHello();
    }
}

3 自动装配原理

​ 观察@SpringBootApplication 注解。我们简单说一下@Inherited 和 @Target(ElementType.TYPE),后面着重分析@EnableAutoConfiguration

@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 {

3.1 @Inherited

​ 被 @Inherited修饰的注解可以被子类继承 。我们知道@SpringBootApplication 注解放置在 Application.java 上面,如果这时候我们新增一个ApplicationImpl.java 继承Application.java,仍然可以正常启动,这是因为子类对象通过反射也可以拿到 @SpringBootApplication 注解。

//@SpringBootApplication 
public class ApplicationImpl extends Application {

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

也就是说,ApplicationImpl 默认就是被 @SpringBootApplication 修饰的。

比如我们业务常用的事务注解。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

3.2 @SpringBootConfiguration

​ 这个没什么特别的,实际上就是个 @Configuration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}

3.3 @EnableAutoConfiguration

这里我们看到了亲切的 @Import 注解,@AutoConfigurationPackage我们后面再说。

@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

3.4 AutoConfigurationImportSelector

​ 查看源码我们可以知道,AutoConfigurationImportSelector 实现了 DeferredImportSelector 接口,这里截取了部分代码。通过断点会发现一个奇怪的现象,它并没有执行 ImportSelector 的实现 selectImports 方法,而是执行了内部类AutoConfigurationGroup 的 process 方法,具体为什么可以去查看 springboot 启动流程的源码寻找答案,这里就不展开了。

​ 无论执行哪个方法,都是通过调用 getAutoConfigurationEntry 方法来处理的,分析 getAutoConfigurationEntry 的逻辑,首先是根据“org.springframework.boot.autoconfigure.EnableAutoConfiguration”某个地方获取了一个全类名数组,然后又对这个数据进行了排除和过滤操作,最后返回了剩下的全类名数组。

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
        ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
     ......  
     //N ImportSelector 的实现,但实际并没有执行
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
            
    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 从"META-INF/spring.factories" 中找到对应的后选配置类全路径
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        // 去重
        configurations = removeDuplicates(configurations);
        // 去掉我们排除的配置
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        // 过滤掉不存在的配置类
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }    
            
    //N 获取侯选配置
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        Class<?> springFactoriesLoaderFactoryClass = getSpringFactoriesLoaderFactoryClass();
        ClassLoader beanClassLoader = getBeanClassLoader();
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(springFactoriesLoaderFactoryClass,
                beanClassLoader);
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }
    
    //根据 org.springframework.boot.autoconfigure.EnableAutoConfiguration 查找        
    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
    }        
    ......
    private static class AutoConfigurationGroup implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
        ......
         @Override
        public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
            Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
                    () -> String.format("Only %s implementations are supported, got %s",
                            AutoConfigurationImportSelector.class.getSimpleName(),
                            deferredImportSelector.getClass().getName()));
            AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
                    .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
            this.autoConfigurationEntries.add(autoConfigurationEntry);
            for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                this.entries.putIfAbsent(importClassName, annotationMetadata);
            }
        }
        ......
    }
}

​ 进一步跟踪到 getCandidateConfigurations 方法可以知道,上面提到的某个地方是在SpringFactoriesLoader中,我们继续查看SpringFactoriesLoader的相关代码。

3.5 SpringFactoriesLoader

​ 到这里我们就知道,它是通过查询 jar 的resources 下面的 “META-INF/spring.factories” 来获取全类名的。

public final class SpringFactoriesLoader {
    ......
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        //N 从扫描的到的spring.factories 中找到 factoryClassName 所对应的配置值,即配置类的全限定名集合
        //N 然后 SpringBoot 通过这些全限定名进行类加载(反射),将这些自动配置类添加到 Spring 容器中。
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

    //N 扫描ClassPath下的所有jar包的"META-INF/spring.factories" 文件
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try {
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }    

}    

3.6 META-INF/spring.factories

我们打开springboot自动配置的jar包看下spring.factories 的内容
在这里插入图片描述
这里截取了部分代码,观察 Auto Configure 部分,可以发现它的 key 值就是org.springframework.boot.autoconfigure.EnableAutoConfiguration,value 是我们常用组件的配置类全类名,例如redis、RabbitMQ等等。

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
......

3.7 自定义spring.factories

通过上面的分析我们已经知道,springboot 使用@Import 的手段,那么就自定义一个spring.factories试试。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d9KEwSPR-1656393963763)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220627180016000.png)]

# my spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.clonetx.ClonetxConfiguration
@SpringBootApplication
public class Application {

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

get http://localhost:8080/aaa 成功打印 123

3.8 @AutoConfigurationPackage

​ 到这里,如果不去深究 springboot 的启动流程,自动装配的内容也就差不多了,最后我们再来看下注解@AutoConfigurationPackage,可以看到还是 @Import 的应用。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

​ 这里是通过实现 ImportBeanDefinitionRegistrar 接口的方式注册bean的。首先获取了@SpringBootApplication注解标注类的包路径,也就是Application.java 所在的包路径,然后调用 register 方法,给IOC注册了一系列的bean,这一系列的bean其实就是包下所有的Component ,这就是为什么主类所在包下的bean会被默认注入IOC

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

   @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata, 
                                       BeanDefinitionRegistry registry) {
      //metadata 其实就是 @AutoConfigurationPackage,但是因为我们最终使用的组合后的 @SpringBootApplication,所以这里获取的包命就是 Application 所在的包名
      register(registry, new PackageImport(metadata).getPackageName());
   }

   @Override
   public Set<Object> determineImports(AnnotationMetadata metadata) {
      return Collections.singleton(new PackageImport(metadata));
   }

}

​ 这里额外说一下,上文提到@SpringBootApplication 因为组合了 @Inherited,所以是可以被子类继承的,所以ApplicationImpl 所在的包路径下的bean 一样会被注入IOC容器。

public abstract class AutoConfigurationPackages {

    private static final String BEAN = AutoConfigurationPackages.class.getName();
    ......
    public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        if (registry.containsBeanDefinition(BEAN)) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        }
        else {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }
    }
    ......
}    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值