1. 程式人生 > >SpringBoot的@Enable* 註解的工作原理

SpringBoot的@Enable* 註解的工作原理

    SpringBoot 提供了@EnableAutoConfiguration@EnableConfigurationProperties@EnableAsync等註解用來啟用某些特性。

工作原理

    每個以 Enable 開頭的註解中,都有 @Import 註解,這個註解是用來匯入一個或者多個類(由Spring管理),或者配置類(配置類中的beanSpring管理),因此 @Import 可以代替 @Component@Configuration等註解。
    在 Import 原始碼中定義了Class陣列的value屬性,官方註釋是可以將帶有Configuration

註解或者實現了ImportSelectorImportBeanDefinitionRegistrar介面的類,還有其他符合要求的class交給Spring管理。
    也就是說,SpringBoot@Enable*的實現是在@Import註解中指定需要的配置類,包括帶有@Configuration的類,實現了ImportSelector或者ImportBeanDefinitionRegistrar介面的類並在實現的方法中將需要的Bean註冊到Spring容器中。

例如:

@EnableAutoConfiguration 的配置類中是實現了 ImportSelector 介面
@EnableConfigurationProperties
的配置類中實現了 ImportSelector 介面 @EnableAsync 配置類中實現了 ImportSelector 介面 @EnableWebMvc 的配置類中使用了 @Configuration 註解

與配置啟用相關的類和註解

  • Import 註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
    Class<?>[] value();
}
  • ImportSelector介面
public
interface ImportSelector { /** * 實現此方法,Spring 會將所有返回值註冊到容器中 */ String[] selectImports(AnnotationMetadata importingClassMetadata); }
  • ImportBeanDefinitionRegistrar 介面
public interface ImportBeanDefinitionRegistrar {
    /**
    * 實現此方法,需要手動將 Bean 註冊到 Spring 容器中
    */
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}

案例

  • ImportSelector 案例
// 註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(value = BeanImportSelector.class)
public @interface EnableBean {
}
// 準備兩個類
public class UserDto {
}
public class UserVo {
}
// 配置類
public class BeanConfiguration {
    @Bean
    public UserDto userDto1(){
        return new UserDto();
    }
    @Bean
    public UserVo userVo1(){
        return new UserVo();
    }
}
// ImportSelector 實現類
public class BeanImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 將 bean 注入到 spring 容器
        return new String[] { UserVo.class.getName(), "com.p7.boot.enable.test.dto.UserDto",
                BeanConfiguration.class.getName() };
    }
}

// 啟動類
@SpringBootApplication
// 啟動特性
@EnableBean
public class App {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
        System.out.println(context.getBeansOfType(UserDto.class));
        System.out.println(context.getBeansOfType(UserVo.class));
        context.close();
    }
}
  • ImportBeanDefinitionRegistrar案例,繼續使用上面的案例
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(value = LogImportBeanDefinitionRegistrar.class)
public @interface EnableLog {
    String[] packages();
}

// 啟動 @EnableLog 特性,在載入 Bean 時,根據包名列印日誌
public class LogBeanPostProcessor implements BeanPostProcessor {
    List<String> packageList;
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 遍歷 LogImportBeanDefinitionRegistrar 穿過來的所有包名
        for (String packageName : packageList) {
            if (bean.getClass().getName().startsWith(packageName)) {
                System.out.println(bean.getClass().getName() + " Log ...");
            }
        }
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    public List<String> getPackageList() {
        return packageList;
    }
    public void setPackageList(List<String> packageList) {
        this.packageList = packageList;
    }
}

// 實現 ImportBeanDefinitionRegistrar 介面
public class LogImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 獲取 EnableLog 的所有屬性
        Map<String, Object> attr = importingClassMetadata.getAnnotationAttributes(EnableLog.class.getName());
        // 得到 packages 屬性所有的值
        String[] packages = (String[]) attr.get("packages");
        List<String> packageList = Arrays.asList(packages);
        // 生成 LogBeanPostProcessor 物件,並將所有包含的包傳給該物件
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(LogBeanPostProcessor.class.getName());
        builder.addPropertyValue("packageList", packageList);
        // 將 LogBeanPostProcessor 物件註冊到 Spring 中
        registry.registerBeanDefinition(LogBeanPostProcessor.class.getName(), builder.getBeanDefinition());
    }
}

// 啟動類
@SpringBootApplication
@EnableBean
// 配置 com.p7.boot.enable.test.vo 下所有的類註冊到 Spring 容器前,列印日誌
@EnableLog(packages = "com.p7.boot.enable.test.vo")
public class App {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
        context.close();
    }
}