在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