spring · 10 10 月, 2021 0

spring 中@Import注解使用详解

最近在看源码过程中,发现在根据spring实现中,很多都使用了@Import注解实现动态bean的注入实现,因此自己总结了一些@Import使用方式,便于对Spring框架的细节的理解。

@Import

在学习@Import注解时,我们首先看下spring中对该注解的描述信息:

/**
 * Indicates one or more <em>component classes</em> to import &mdash; typically
 * {@link Configuration @Configuration} classes.
 *
 * <p>Provides functionality equivalent to the {@code <import/>} element in Spring XML.
 * Allows for importing {@code @Configuration} classes, {@link ImportSelector} and
 * {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component
 * classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}).
 *
 * <p>{@code @Bean} definitions declared in imported {@code @Configuration} classes should be
 * accessed by using {@link org.springframework.beans.factory.annotation.Autowired @Autowired}
 * injection. Either the bean itself can be autowired, or the configuration class instance
 * declaring the bean can be autowired. The latter approach allows for explicit, IDE-friendly
 * navigation between {@code @Configuration} class methods.
 *
 * <p>May be declared at the class level or as a meta-annotation.
 *
 * <p>If XML or other non-{@code @Configuration} bean definition resources need to be
 * imported, use the {@link ImportResource @ImportResource} annotation instead.
 * @since 3.0
 * @see Configuration
 * @see ImportSelector
 * @see ImportResource
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

    /**
     * {@link Configuration @Configuration}, {@link ImportSelector},
     * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
     */
    Class<?>[] value();

}

从注解中知道,主要包含了一下信息:

  • 该注解主要为了实现xml配置中的<import /> 元素的功能,能够动态引入配置相关信息
  • 该注解需要配合@Configuration注解使用
  • 该注解可以与ImportSelectorImportBeanDefinitionRegister配合使用

在了解了@Import基础信息之后,接下来我们就每种使用情况以demo的方式来分别实现。

@Configuration与@Import配合使用

首先我们创建三个需要使用的实体类,便于实现, 分别为Cycle, Rule, ConpositeRuleCycle三个类,分别代码如下:

package org.spring.boot.demo.context.annotations.entity;

import lombok.Data;

@Data
public class Cycle {

    public Cycle() {
    }

    public Cycle(String name) {
        this.name = name;
    }

    private String name;
}
package org.spring.boot.demo.context.annotations.entity;

import lombok.Data;

@Data
public class Rule {

    private String name;

    public Rule() {

    }

    public Rule(String name) {
        this.name = name;
    }
}
package org.spring.boot.demo.context.annotations.entity;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.List;

@Data
@AllArgsConstructor
public class CompositeRuleCycle {

    private Cycle cycle;
    private List<Rule> rule;
}

 

在有了上面的三个实体之后,我们创建一个Configuration类型,配合@Import注解使用,查看效果,具体源码如下:

package org.spring.boot.demo.context.annotations.configuration;

import org.spring.boot.demo.context.annotations.entity.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Optional;

@Configuration
@Import({Cycle.class, Rule.class, CompositeRuleCycle.class})
public class ImportConfiguration {

    @Autowired(required = false)
    private List<Cycle> cycles;

    @Autowired(required = false)
    private List<Rule> rule;

    @Autowired
    private CompositeRuleCycle compositeRuleCycle;

    @Autowired
    private NoOption noOption;

    @PostConstruct
    public void init() {
        Optional
                .ofNullable(cycles)
                .filter(cs -> {
                    cs.forEach(c -> System.out.println(c.toString()));
                    return true;
                })
                .orElse(null);

        Optional.ofNullable(rule)
                .filter(rs -> {
                    rs.forEach(r -> System.out.println(r.toString()));
                    return true;
                })
                .orElse(null);
       System.out.println(compositeRuleCycle);
    }
}

 

创建执行的Application类型,具体源码如下:

package org.spring.boot.demo.context.annotations;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ImportAnnotationApplication {

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

 

通过执行以上代码,我们可以发现, RuleCycle已经通过@Autowired方式注入到了Configuration的类里,并且能够访问。日志输出如下:

Cycle(name=null)
Rule(name=null)
CompositeRuleCycle(cycle=[Cycle(name=null)], rule=[Rule(name=null)])

因此我们通过@Import的方式能够引入Class, 并在@Autowired时,自动的帮助我们创建Bean. 同时我们也注意到了,CompositeRuleCycle在创建Bean的时候,实际上对CycleRule有依赖,该层依赖也通过Spring帮助我们实现了,这本身就是Spring提供的功能。

其实通过@Import的方式执行时,也是将对应的Class解析成对应的BeanDefinition对象,然后通过AnnotationConfigApplicationContext#register方法让spring进行管理。

@Import的猜想

既然@Import能够实现动态的引入bean, 那么我在两个Configuration中,同时@Import相同的Class, 是否在Spring中也会存在两个不同的bean呢,因此,我们再创建一个Configuration, 验证该想法:

@Import({Cycle.class, Rule.class, NoOption.class})
@Configuration
public class MultiImportConfiguration {
}

我们再次执行Application,查看输出日志:

Cycle(name=null) 
Rule(name=null)
CompositeRuleCycle(cycle=[Cycle(name=null)], rule=[Rule(name=null)])

从日志中可以看出,实际上在Spring中只包含了一个bean对象,其实这个BeanDefinition有关,当我们@Import的时候,对应的beanName是与当前类的全限定名称进行绑定,例如:import org.spring.boot.demo.context.annotations.entity.Rule。因此保证了在spring不会有多个bean的出现。

ImportSelector

该注解还可以配合ImportSelector接口进行使用,看下该类的源码信息:

package org.springframework.context.annotation;

import org.springframework.core.type.AnnotationMetadata;

/**
 * Interface to be implemented by types that determine which @{@link Configuration}
 * class(es) should be imported based on a given selection criteria, usually one or
 * more annotation attributes.
 *
 * <p>An {@link ImportSelector} may implement any of the following
 * {@link org.springframework.beans.factory.Aware Aware} interfaces,
 * and their respective methods will be called prior to {@link #selectImports}:
 * <ul>
 * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
 * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}</li>
 * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}</li>
 * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}</li>
 * </ul>
 *
 * <p>{@code ImportSelector} implementations are usually processed in the same way
 * as regular {@code @Import} annotations, however, it is also possible to defer
 * selection of imports until all {@code @Configuration} classes have been processed
 * (see {@link DeferredImportSelector} for details).
 *
 * @author Chris Beams
 * @since 3.1
 * @see DeferredImportSelector
 * @see Import
 * @see ImportBeanDefinitionRegistrar
 * @see Configuration
 */
public interface ImportSelector {

    /**
     * Select and return the names of which class(es) should be imported based on
     * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);

}

从注释中我们了解到,该接口能够帮助我们import多个ClassSpring容器中,同事包含了以下信息:

  • ImportSelector实现类时,同时也可以实现spring的Aware接口,并且Aware接口的执行会在selectorImprots()方法之前
  • 提供了DeferredImportSelector接口,能够在@Configuration执行完成之后,在执行@ConditionalBeanDefinition的引入

为了实现该类,我们创建一个MultiImportSelector的类,具体源码如下:

package org.spring.boot.demo.context.annotations.entity;

import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

import java.util.Set;

public class MultiImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 打印出Annotation中的信息
        System.out.println(importingClassMetadata);
        // 获取当前注解的数据信息
        Set<String> annotationTypes = importingClassMetadata.getAnnotationTypes();
        for (String annotationType : annotationTypes) {
            System.out.println("annotationType: " + annotationType);
        }

        // 获取注解中的属性信息
        Set<String> metaAnnotationTypes = importingClassMetadata.getMetaAnnotationTypes(Import.class.getName());
        for (String metaAnnotationType : metaAnnotationTypes) {
            System.out.println("metaAnnotationType: " + metaAnnotationType);
        }

        return new String[]{
                ImportSelectorEntity.class.getName()
        };
    }
}



package org.spring.boot.demo.context.annotations.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ImportSelectorEntity {

}

 

我们在Configuration中,加入ImportSelectorEntity类型的注入,并输出结果:

package org.spring.boot.demo.context.annotations.configuration;

import org.spring.boot.demo.context.annotations.entity.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Optional;

@Configuration
@Import({Cycle.class, Rule.class, MultiImportSelector.class, CompositeRuleCycle.class})
public class ImportConfiguration {


    @Autowired
    private ImportSelectorEntity importSelectorEntity;

    @PostConstruct
    public void init() {
   
        .....
        System.out.println(compositeRuleCycle);
        System.out.println(importSelectorEntity);
        System.out.println(noOption);
    }
}

 

我们查看输出结果:

Cycle(name=null)
Rule(name=null)
CompositeRuleCycle(cycle=[Cycle(name=null)], rule=[Rule(name=null)])
ImportSelectorEntity()

在结果中,我们可以看到对应的类型已经注入进来,因此通过ImportSelector能够实现多个Class引入的功能,避免了在@Import注解中引入过长的配置信息。

ImportBeanDefinitionRegistrar

在上面的实践中,有一个问题,就是因为我们Entity在创建的时候,是有内部参数的。但是通过以上方式我们都无法实现对构造器参数的设置和修改。因此Spring提供了ImportBeanDefinitionRegistrar接口,该接口主要为了让我们能够更加细粒度的处理我们需要的注册的BeanDefinition对象。我们还是首先来看下该接口的源码信息:

package org.springframework.context.annotation;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.core.type.AnnotationMetadata;

/**
 * Interface to be implemented by types that register additional bean definitions when
 * processing @{@link Configuration} classes. Useful when operating at the bean definition
 * level (as opposed to {@code @Bean} method/instance level) is desired or necessary.
 *
 * <p>Along with {@code @Configuration} and {@link ImportSelector}, classes of this type
 * may be provided to the @{@link Import} annotation (or may also be returned from an
 * {@code ImportSelector}).
 *
 * <p>An {@link ImportBeanDefinitionRegistrar} may implement any of the following
 * {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
 * methods will be called prior to {@link #registerBeanDefinitions}:
 * <ul>
 * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
 * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
 * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
 * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
 * </ul>
 *
 * <p>See implementations and associated unit tests for usage examples.
 *
 * @author Chris Beams
 * @since 3.1
 * @see Import
 * @see ImportSelector
 * @see Configuration
 */
public interface ImportBeanDefinitionRegistrar {

    /**
     * Register bean definitions as necessary based on the given annotation metadata of
     * the importing {@code @Configuration} class.
     * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
     * registered here, due to lifecycle constraints related to {@code @Configuration}
     * class processing.
     * @param importingClassMetadata annotation metadata of the importing class
     * @param registry current bean definition registry
     */
    void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);

}

 

我们从方法可以得知,我们可以在读取到注解的同时,也能够直接获取到BeanDefinitionRegistry 对象,当我们在处理完Class之后,能够直接将BeanDefinition注册到BeanDefinitionRegistry 中。

我们创建MultiImportDefinitionRegistrar类型,具体源码如下:

package org.spring.boot.demo.context.annotations.entity;

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 MultiImportDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(Cycle.class);
        builder.addConstructorArgValue("MultiImportDefinitionRegistrar");
        registry.registerBeanDefinition("MultiImportDefinitionRegistrar.cycle", builder.getBeanDefinition());
    }
}

 

在这个类中,主要包含以下步骤:

  • 通过BeanDefinitionBuilder创建了一个关于CycleBeanDefinition
  • 同时指定Cycle构建器需要传入的name属性的值
  • BeanDefinition注册到Registry中进行保存,并指定beanNameMultiImportDefinitionRegistrar.cycle

其实最后一步,我们可以通过指定不同的beanName来保证Cycle可以在Spring中同时存在多个不同的bean.

此时我们修改Configuration类,具体如下:

package org.spring.boot.demo.context.annotations.configuration;

import org.spring.boot.demo.context.annotations.entity.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Optional;

@Configuration
@Import({Cycle.class, MultiImportSelector.class, CompositeRuleCycle.class, MultiImportDefinitionRegistrar.class})
public class ImportConfiguration {

    @Autowired(required = false)
    private List<Cycle> cycles;

    @Autowired(required = false)
    private List<Rule> rule;

    @Autowired
    private CompositeRuleCycle compositeRuleCycle;

    @Autowired
    private NoOption noOption;

    @Autowired
    private ImportSelectorEntity importSelectorEntity;

    @PostConstruct
    public void init() {
        .....
    }
}

 

再次执行Application类,查看日志输出如下:

Cycle(name=null)
Cycle(name=MultiImportDefinitionRegistrar)
Rule(name=null)
CompositeRuleCycle(cycle=[Cycle(name=null), Cycle(name=MultiImportDefinitionRegistrar)], rule=[Rule(name=null)])
ImportSelectorEntity()

 

此时我们可以发现,在@Autowired时候,实际上Cycle的实例已经包含了多个,并且name与我们设置的值保持一致。

@Import派生注解

在阅读源码的过程中,其实对接spring的框架,使用更多的其实是通过注解派生的方式,实现自己的注解,然后在ImportBeanDefinitionRegistrar 中对注解进行解析,然后创建自定义的BeanDefinition对象。因此我们也通过demo的方式,来实现我们自己的派生注解。

自定义注解

package org.spring.boot.demo.context.annotations.diy;

import org.spring.boot.demo.context.annotations.entity.EntityImportDefinitionRegistrar;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(EntityImportDefinitionRegistrar.class)
public @interface EntityImport {

    String name() default "";

    Class<?> value();
}

 

自定义ImportBeanDefinitionRegistrar

package org.spring.boot.demo.context.annotations.entity;

import org.spring.boot.demo.context.annotations.diy.EntityImport;
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;
import org.springframework.util.StringUtils;

import java.util.Map;

public class EntityImportDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 获取注解属性
        Map<String, Object> map =
                importingClassMetadata.getAnnotationAttributes(EntityImport.class.getName());

        Class<?> c = (Class<?>) map.get("value");
        String name = (String) map.get("name");
        if (StringUtils.isEmpty(name)) {
            name = c.getName();
        }

        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(c);
        builder.addConstructorArgValue(name);
        registry.registerBeanDefinition("default." + name, builder.getBeanDefinition());
    }
}

 

修改Configuraion类

package org.spring.boot.demo.context.annotations.configuration;

import org.spring.boot.demo.context.annotations.diy.EntityImport;
import org.spring.boot.demo.context.annotations.entity.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Optional;

@Configuration
@EntityImport(value = Rule.class, name = "EntityImportRule")
@Import({Cycle.class, MultiImportSelector.class, CompositeRuleCycle.class, MultiImportDefinitionRegistrar.class})
public class ImportConfiguration {

    @Autowired(required = false)
    private List<Cycle> cycles;

    @Autowired(required = false)
    private List<Rule> rule;

    @Autowired
    private CompositeRuleCycle compositeRuleCycle;

    @Autowired
    private NoOption noOption;

    @Autowired
    private ImportSelectorEntity importSelectorEntity;

    @PostConstruct
    public void init() {
        Optional
                .ofNullable(cycles)
                .filter(cs -> {
                    cs.forEach(c -> System.out.println(c.toString()));
                    return true;
                })
                .orElse(null);

        Optional.ofNullable(rule)
                .filter(rs -> {
                    rs.forEach(r -> System.out.println(r.toString()));
                    return true;
                })
                .orElse(null);

        System.out.println(compositeRuleCycle);
        System.out.println(importSelectorEntity);
        System.out.println(noOption);
    }
}

 

执行Applicaion类,查看日志执行结果信息:

Cycle(name=null)
Cycle(name=MultiImportDefinitionRegistrar)
Rule(name=EntityImportRule)
Rule(name=null)
CompositeRuleCycle(cycle=[Cycle(name=null), Cycle(name=MultiImportDefinitionRegistrar)], rule=[Rule(name=EntityImportRule), Rule(name=null)])
ImportSelectorEntity()

至此,关于@Import的用法到此结束。在本章中我们主要总结了该注解的几种用法方式,希望可以帮助到你。