Spring注解之@Import和@ImportResource

前言

为什么研究这两个注解,是因为在看Spring Boot源码的时候,对于其中大量的EnableXXX都使用了@Import注解,所以决定研究下这个注解,顺便写了一点关于@ImportRescource注解的东西,希望对大家有帮助。

@Import

简介:功能类似XML配置的,用来导入配置类,可以导入带有@Configuration注解的配置类或实现了ImportSelector/ImportBeanDefinitionRegistrar,或者导入普通的POJO(Spring会将其注册成Spring Bean,导入POJO需要使用Spring 4.2以上)。

下面注解源码,源码有比较详细的英文解释:

/**
 * Indicates one or more {@link Configuration @Configuration} classes to import.
 *
 * <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.
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.0
 * @see Configuration
 * @see ImportSelector
 * @see ImportResource
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

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

}

关于Configuration和POJO,没什么好说的,就是简单的配置。这里说下ImportSelector和ImportBeanDefinitionRegistrar的作用(虽然开发过程中很少会使用到,但是Spring和Spring Boot的源码大面积使用了这个功能)。

ImportSelector

  • 根据AnnotationMetadata(实质上是引入@Import注解的类的注解信息),来判断是否引入哪些配置类,通过字符串数组的方式返回配置类的全限定名。

DeferredImportSelector

  • ImportSelector的子接口,区别是,他会在所有的@Configuration类加载完成之后再加载返回的配置类。ImportSelector会在当前Configuration类加载之前去加载返回的配置类。
  • 可以使用@Order注解或者Ordered接口来指定DeferredImportSelector的加载顺序。
  • 并且提供了新的方法getImportGroup()用来跨DeferredImportSelector实现自定义Configuration的加载顺序。

ImportBeanDefinitionRegistrar

  • 根据AnnotationMetadata(实质上是引入@Import注解的类的注解信息), 来注册BeanDenfinition,通过BeanDefinitionRegistry实例的register可以注册BeanDefinition。

三个接口的使用例子在文章最后面。

@ImportResource

和@Import类似,区别就是@ImportResource导入的是配置文件。

同样源码如下

/**
 * Indicates one or more resources containing bean definitions to import.
 *
 * <p>Like {@link Import @Import}, this annotation provides functionality similar to
 * the {@code <import/>} element in Spring XML. It is typically used when designing
 * {@link Configuration @Configuration} classes to be bootstrapped by an
 * {@link AnnotationConfigApplicationContext}, but where some XML functionality such
 * as namespaces is still necessary.
 *
 * <p>By default, arguments to the {@link #value} attribute will be processed using a
 * {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader GroovyBeanDefinitionReader}
 * if ending in {@code ".groovy"}; otherwise, an
 * {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader XmlBeanDefinitionReader}
 * will be used to parse Spring {@code <beans/>} XML files. Optionally, the {@link #reader}
 * attribute may be declared, allowing the user to choose a custom {@link BeanDefinitionReader}
 * implementation.
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @since 3.0
 * @see Configuration
 * @see Import
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface ImportResource {

    /**
     * Alias for {@link #locations}.
     * @see #locations
     * @see #reader
     */
    @AliasFor("locations")
    String[] value() default {};

    /**
     * Resource locations from which to import.
     * <p>Supports resource-loading prefixes such as {@code classpath:},
     * {@code file:}, etc.
     * <p>Consult the Javadoc for {@link #reader} for details on how resources
     * will be processed.
     * @since 4.2
     * @see #value
     * @see #reader
     */
    @AliasFor("value")
    String[] locations() default {};

    /**
     * {@link BeanDefinitionReader} implementation to use when processing
     * resources specified via the {@link #value} attribute.
     * <p>By default, the reader will be adapted to the resource path specified:
     * {@code ".groovy"} files will be processed with a
     * {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader GroovyBeanDefinitionReader};
     * whereas, all other resources will be processed with an
     * {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader XmlBeanDefinitionReader}.
     * @see #value
     */
    Class<? extends BeanDefinitionReader> reader() default BeanDefinitionReader.class;

}
  • default和locations作用相同,都是用来指定配置文件的位置。
  • 关于reader,一般情况下们不需要指定这个属性,默认情况,如果配置文件是.groovy结尾的将会使用GroovyBeanDefinitionReader来进行解析,其他的将会使用XmlBeanDefinitionReader来进行解析。一般需要显示的指定reader的情况是开发者需要实现自己的配置文件方式,比如说自己实现了一个JSONBeanDefinitionReader用来解析JSON的配置文件以达到可以将Spring以JSON的方式进行配置(开发中使用较少)。
  • 一般情下,Spring还是推荐使用@Import而不是@ImportResource。

附页-@Import的测试代码

public class Main {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(Config.class, Config1.class);
        context.refresh();
    }

    public static class BeanDemo0 {

        private Integer id;
        private String name;

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }


    @Configuration
    @Import({MyImportSelector.class, MyImportBeanDefinitionRegistrar.class, MyImportSelector1.class})
    public static class Config {
        @Bean
        public BeanDemo0 beanDemo() {
            System.out.println("----beanDemo----");
            BeanDemo0 beanDemo0 = new BeanDemo0();
            beanDemo0.setId(1);
            beanDemo0.setName("123");
            return beanDemo0;
        }

    }

    @Configuration
    public static class Config_1 {

        @Bean
        public BeanDemo0 beanDemo_1() {
            System.out.println("----beanDemo_1----");
            BeanDemo0 beanDemo0 = new BeanDemo0();
            beanDemo0.setId(1);
            beanDemo0.setName("123");
            return beanDemo0;
        }

    }

    @Configuration
    public static class Config0 {

        @Bean
        public BeanDemo0 beanDemo0() {
            System.out.println("----beanDemo0----");
            BeanDemo0 beanDemo0 = new BeanDemo0();
            beanDemo0.setId(1);
            beanDemo0.setName("123");
            return beanDemo0;
        }

    }

    @Configuration
    public static class Config00 {

        @Bean
        public BeanDemo0 beanDemo00() {
            System.out.println("----beanDemo00----");
            BeanDemo0 beanDemo0 = new BeanDemo0();
            beanDemo0.setId(1);
            beanDemo0.setName("123");
            return beanDemo0;
        }

    }

    @Configuration
    public static class Config000 {

        @Bean
        public BeanDemo0 beanDemo000() {
            System.out.println("----beanDemo000----");
            BeanDemo0 beanDemo0 = new BeanDemo0();
            beanDemo0.setId(1);
            beanDemo0.setName("123");
            return beanDemo0;
        }

    }

    @Configuration
    public static class Config0000 {

        @Bean
        public BeanDemo0 beanDemo0000() {
            System.out.println("----beanDemo0000----");
            BeanDemo0 beanDemo0 = new BeanDemo0();
            beanDemo0.setId(1);
            beanDemo0.setName("123");
            return beanDemo0;
        }

    }

    @Configuration
    @Import({MyImportSelector2.class})
    public static class Config1 {

        @Bean
        public BeanDemo0 beanDemo1() {
            System.out.println("----beanDemo1----");
            BeanDemo0 beanDemo0 = new BeanDemo0();
            beanDemo0.setId(1);
            beanDemo0.setName("123");
            return beanDemo0;
        }

    }

    @Configuration
    public static class Config2 {

        @Bean
        public BeanDemo0 beanDemo2() {
            System.out.println("----beanDemo2----");
            BeanDemo0 beanDemo0 = new BeanDemo0();
            beanDemo0.setId(1);
            beanDemo0.setName("123");
            return beanDemo0;
        }

    }

    public static class MyImportSelector implements ImportSelector {

        /**
         * AnnotationMetadata表示了使用@Import引入MyImportSelector的类的注解meta信息。
         * 在这个例子中,annotationMetadata可以获取到Config类上的注解详细信息。
         */
        @Override
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            /*System.out.println("class name --> " + annotationMetadata.getClass().getSimpleName());
            System.out.println("className --> " + annotationMetadata.getClassName());
            Set<String> annotationTypes = annotationMetadata.getAnnotationTypes();
            System.out.println(annotationTypes);*/
//            System.out.println("" + annotationMetadat);
            System.out.println("----MyImportSelector-----");
            return new String[] {"cn.xtt.study.spring.imports.Main.Config_1"};
        }
    }

    /**
     * after all config
     */
    public static class MyImportSelector1 implements DeferredImportSelector {

        @Override
        public Class<? extends Group> getImportGroup() {
            return MyGroup.class;
        }

        @Override
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            System.out.println("----DeferredImportSelector1-----");
            return new String[]{"cn.xtt.study.spring.imports.Main.Config0", "cn.xtt.study.spring.imports.Main.Config00"};
        }
    }

    public static class MyImportSelector2 implements DeferredImportSelector {

        @Override
        public Class<? extends Group> getImportGroup() {
            return MyGroup.class;
        }

        @Override
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            System.out.println("----DeferredImportSelector2-----");
            return new String[]{"cn.xtt.study.spring.imports.Main.Config000", "cn.xtt.study.spring.imports.Main.Config0000"};
        }
    }

    public static class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

        @Override
        public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
            System.out.println("----MyImportBeanDefinitionRegistrar----");
            /*System.out.println(annotationMetadata);
            System.out.println("class name --> " + annotationMetadata.getClass().getSimpleName());
            System.out.println("className --> " + annotationMetadata.getClassName());

            System.out.println(registry);
            System.out.println("registry name " + registry.getClass().getSimpleName());*/
            registry.registerBeanDefinition("config2", new RootBeanDefinition(Config2.class));
        }
    }

    private static class MyGroup implements DeferredImportSelector.Group {

        private List<Entry> entries = new ArrayList<>(2);

        @Override
        public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
            for (String configClassName : selector.selectImports(metadata)) {
                entries.add(new Entry(metadata, configClassName));
            }
            entries.sort(new Comparator<Entry>() {
                @Override
                public int compare(Entry o1, Entry o2) {
                //按照配置类,全限定名的长度,从名称长的开始加载
                    return o2.getImportClassName().length() - o1.getImportClassName().length();
//                    return o1.getImportClassName().length() - o2.getImportClassName().length();
                }
            });
        }

        @Override
        public Iterable<Entry> selectImports() {
            return entries;
        }
    }

}

运行结果:

----MyImportSelector-----  
----DeferredImportSelector1-----  
----DeferredImportSelector2-----  
----MyImportBeanDefinitionReistrar----  
----beanDemo_1----  
----beanDemo----  
----beanDemo1----  
----beanDemo0000----  
----beanDemo000----  
----beanDemo00----  
----beanDemo0----  
----beanDemo2----  

版权声明:本文为xt8469原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。