0
点赞
收藏
分享

微信扫一扫

Spring注解:@Import

杰森wang 2021-10-15 阅读 88
技术分享

在Spring中,有很多方式可以注册组件,比较常用的就是@Repository、@Service、@Controller、@Component,以及可以使用@Bean导入第三方组件等等。

@Import,快速注入组件

在Spring中,还为我们提供了一个注解,就是@Import,该注解可以快速的帮我们注册一个组件。比如,我们可以先创建一个类Phone.class,想让其注册进Spring容器中,可以使用@Import的方式。

public class Phone {
}

主配置类
@Configuration
public class MainConfig {

}
测试方法:

@Test
public void testImport() {
    ApplicationContext applicationContext =
            new AnnotationConfigApplicationContext(MainConfig.class);
    //getBeanDefinitionNames方法的作用是:获取容器中的Bean的名称
    String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
    for (String name : beanDefinitionNames) {
        System.out.println(name);
    }
}

测试结果,目前只有主配置类本身被注册进容器。

那么如何让我们刚刚创建的Phone.class作为组件注册进容器呢?当然了,这里使用@Import的方式。
只需要在主配置上添加@Import注解,并指定组件即可。

@Configuration
@Import(Phone.class)
public class MainConfig {

}

测试结果:

mainConfig
com.caiyq.spring.annotation.bean.Phone

结果打印的Phone的全限定类名,就是该组件的id。
点进去@Import的源码看一下:

@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();

}

和之前学过的@Conditional注解类似的,在@Import注解中,也可以传入多个Class类型组件,即一个Class数组。
那么接下来我们可以再创建几个组件试一下。

public class Ono {
}
public class Two {
}
@Configuration
@Import({Phone.class, One.class, Two.class})
public class MainConfig {

}

测试结果:@Import中的类都会被注册。

mainConfig
com.caiyq.spring.annotation.bean.Phone
com.caiyq.spring.annotation.bean.One
com.caiyq.spring.annotation.bean.Two
ImportSelector

在@Import注解中,还可以传入ImportSelector,该类的作用是导入选择器,其中的selectImports方法返回的是一个数组,即需要注册的组件的全限定类名。先来看一下源码:

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.
     * @return the class names, or an empty array if none
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);

    /**
     * Return a predicate for excluding classes from the import candidates, to be
     * transitively applied to all classes found through this selector's imports.
     * <p>If this predicate returns {@code true} for a given fully-qualified
     * class name, said class will not be considered as an imported configuration
     * class, bypassing class file loading as well as metadata introspection.
     * @return the filter predicate for fully-qualified candidate class names
     * of transitively imported configuration classes, or {@code null} if none
     * @since 5.2.4
     */
    @Nullable
    default Predicate<String> getExclusionFilter() {
        return null;
    }
}

再创建两个类作为组件

public class Three {
}
public class Four {
}

自定义ImportSelector

public class MyImportSelector implements ImportSelector {

    /**
     * 
     * @param importingClassMetadata 当前标注@Import注解的类的所有注册信息
     * @return 返回值是需要导入到容器中的组件全限定类名
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {"com.caiyq.spring.annotation.bean.Three", "com.caiyq.spring.annotation.bean.Four"};
    }
}

在主配置类中@Import加上自定义的MyImportSelector。

@Configuration
@Import({Phone.class, One.class, Two.class, MyImportSelector.class})
public class MainConfig {

}

测试结果:除了快速导入的组件,还有通过自定义的ImportSelector注册的组件。

mainConfig
com.caiyq.spring.annotation.bean.Phone
com.caiyq.spring.annotation.bean.One
com.caiyq.spring.annotation.bean.Two
com.caiyq.spring.annotation.bean.Three
com.caiyq.spring.annotation.bean.Four
ImportBeanDefinitionRegistrar

在@Import的源码注释中提示了,@Import中除了可以传入ImportSelector,还可以传入ImportBeanDefinitionRegistrar,它的作用就是手动注册Bean到容器中。我们可以先点进去看一下源码:

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.
     * <p>The default implementation delegates to
     * {@link #registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)}.
     * @param importingClassMetadata annotation metadata of the importing class
     * @param registry current bean definition registry
     * @param importBeanNameGenerator the bean name generator strategy for imported beans:
     * {@link ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR} by default, or a
     * user-provided one if {@link ConfigurationClassPostProcessor#setBeanNameGenerator}
     * has been set. In the latter case, the passed-in strategy will be the same used for
     * component scanning in the containing application context (otherwise, the default
     * component-scan naming strategy is {@link AnnotationBeanNameGenerator#INSTANCE}).
     * @since 5.2
     * @see ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR
     * @see ConfigurationClassPostProcessor#setBeanNameGenerator
     */
    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
            BeanNameGenerator importBeanNameGenerator) {

        registerBeanDefinitions(importingClassMetadata, registry);
    }

    /**
     * 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.
     * <p>The default implementation is empty.
     * @param importingClassMetadata annotation metadata of the importing class
     * @param registry current bean definition registry
     */
    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    }
}

这是一个接口,有两个默认方法,即注册Bean的方法。所以接下来,我们可以自定义一个类,来实现ImportBeanDefinitionRegistrar接口,覆写其中的方法。
在创建该类之前,老规矩,再写一个类,作为组件,等会儿就用该类来测试。

public class Five {
}

自定义ImportBeanDefinitionRegistrar

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     *
     * @param importingClassMetadata 当前类的注解信息
     * @param registry BeanDefinition注册类,把所有需要添加到容器中的Bean,调用BeanDefinitionRegistry的registerBeanDefinition方法手动注册进来
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean one = registry.containsBeanDefinition("one");
        boolean two = registry.containsBeanDefinition("two");
        if (one && two) {
            //指定Bean的定义信息
            RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Five.class);
            registry.registerBeanDefinition("five", rootBeanDefinition);
        }
    }
}

将该类添加到@Import中

@Configuration
@Import({Phone.class, One.class, Two.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MainConfig {

}

在该类中,可以直接调用registerBeanDefinition方法来注册我们需要注册的组件,也可以加一些业务逻辑,比如这里我加了一些判断,当注册信息中包含One和Two组件的时候,我们才去注册Five。那么或许你会疑问,为什么是使用containsBeanDefinition方法判断呢?registry中到底包含了哪些信息呢?在这里,可以打个断点debug看一下其中信息,registry中包含了我们在主配置类中,注册进来的所有组件信息。这里就不演示debug流程了,可以自己看下。
好了,接下来先运行下测试,看下结果:

mainConfig
com.caiyq.spring.annotation.bean.Phone
com.caiyq.spring.annotation.bean.One
com.caiyq.spring.annotation.bean.Two
com.caiyq.spring.annotation.bean.Three
com.caiyq.spring.annotation.bean.Four

事实上,Five类并没有被注册进来。但是在打印结果中,是当前所有注册的组件,没有我们在判断逻辑中写的one和two,所以那个判断条件是不成立的,所以Five组件没有被注册进来,我们之前使用@Import快速导入One和Two的时候,其id是全限定类名,所以在这里,使用containsBeanDefinition方法判断是否有注册组件的时候,需要填写正确,应该是:

boolean one = registry.containsBeanDefinition("com.caiyq.spring.annotation.bean.One");
boolean two = registry.containsBeanDefinition("com.caiyq.spring.annotation.bean.Two");

再次测试下结果,发现Five类已经被注册进来,其id就是registry.registerBeanDefinition("five", rootBeanDefinition)中的id。

mainConfig
com.caiyq.spring.annotation.bean.Phone
com.caiyq.spring.annotation.bean.One
com.caiyq.spring.annotation.bean.Two
com.caiyq.spring.annotation.bean.Three
com.caiyq.spring.annotation.bean.Four
five
举报

相关推荐

0 条评论