4、@Conditional按照条件向Spring中期中注册Bean
/* * @author Phillip Webb * @author Sam Brannen * @since 4.0 * @see Condition */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Conditional { /** * All {@link Condition}s that must {@linkplain Condition#matches match} * in order for the component to be registered. */ Class<? extends Condition>[] value(); }
这个接口是Spirng4提供出来的。在SpringBoot底层大量的用到了这个接口来按照条件注册Bean。
从注解的属性value来看,我们可以传入Condition
条件,因此我们可以传入系统自带的,也可以我们自己去实现这个接口,按照我们的需求来注册Bean
从上图可以看出,SpringBoot工程中对此接口有大量的实现。本文通过自己的实现,来看看根据条件注册Bean的强大之处。
比如我们要实现如下功能:
如果系统是windows,给容器中注入"bill",如果系统是linux,给容器中注入"linus"
public class WindowCondition implements Condition{ /** * @param context 判断条件 * @param metadata 注释信息 * @return boolean */ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); String property = environment.getProperty("os.name"); if (property.contains("Windows")) { return true; } return false; } }
需要注意的是,context还有以下方法:
// 能获取ioc使用的beanfactory ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); // 能获取到类加载器 ClassLoader classLoader = context.getClassLoader(); // 获取到环境变量 Environment environment = context.getEnvironment(); // 获取到Bean定义的注册类 BeanDefinitionRegistry registry = context.getRegistry();
LinuxCondition类的写法略。配置类如下:
@Configuration public class MainConfig2 { @Conditional({WindowCondition.class}) @Bean("bill") public Person person01() { return new Person("Bill Gates", 60); } @Conditional({LinuxCondition.class}) @Bean("linux") public Person person02() { return new Person("linus", 45); } }
运行:(测试时候可以设置运行时参数:-Dos.name=linux)
结果我们会发现,注册的Bean已经按照我们的条件去注册了
备注:@Conditonal注解不仅可以标注在方法上,还可以标注在类上。如果标注在配置类上,那么若不生效的话,这个配置类所有就将不会再生效了
5、@Improt快速导入一个组件
@Improt快速导入特别重要,在SpringBoot自动装配的过程中起到了非常关键的作用
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Import { /** * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar} * or regular component classes to import. */ Class<?>[] value(); }
@Import可以导入第三方包,或则自己写的类,比较方便,Id默认为全类名(这个需要注意)
比如新建一个类Color
:
public class Color { }
配置类上:
@Import({Color.class}) @Configuration public class MainConfig2 {}
6、ImportSelector和ImportBeanDefinitionRegistrar
ImportSelector:
从注解中的注释中可以看出,import除了导入具体的实体类外,还可以导入实现了指定接口的类。现在我们自己来实现一个,编写一个MyImportSelector类实现ImportSelector接口
public class MyImportSelector implements ImportSelector{ // 返回值就导入容器组件的全类名 // AnnotationMetadata:当前类标注的@Import注解类的所有注解信息 @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[] {"com.cuzz.bean.Car"}; } }
在配置类中,通过@Import导入
@Import({Color.class, MyImportSelector.class}) @Configuration public class MainConfig2 {}
这样子我们发现,Car类已经被导入进去了。
ImportBeanDefinitionRegistrar:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { /** * @param importingClassMetadata 当前类的注解信息 * @param registry 注册类 */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 查询容器 boolean b = registry.containsBeanDefinition("com.cuzz.bean.Car"); // 如果有car, 注册一个汽油类 if (b == true) { // 需要添加一个bean的定义信息 RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Petrol.class); // 注册一个bean, 指定bean名 registry.registerBeanDefinition("petrol", rootBeanDefinition); } } }
配置类:
@Import({Color.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class}) @Configuration public class MainConfig2 {}
在SpringBoot中的使用,举个栗子:
注解@ServletComponentScan
的解析,从下面代码可以看出:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(ServletComponentScanRegistrar.class) public @interface ServletComponentScan {}
是ServletComponentScanRegistrar注册进去和解析的。在看看这个类:
class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar {}
它就是个标准的ImportBeanDefinitionRegistrar
。然后在方法registerBeanDefinitions
这里面做了很多事:比如添加注解的后置处理器等等