15-spring中ImportBeanDefinitionRegistrar的应用

ImportBeanDefinitionRegistrar

  • 在文章01-spring 基础和组件使用的第九小节中提到了注册Bean的几种方法,其中一种是通过@Import注解,@Import注解的参数可以是一个类,也可以是一个ImportSelector接口,也可以是ImportBeanDefinitionRegistrar接口。如果是一个类,会创建一个该类对应的Bean到IOC容器,如果是ImportSelector接口的实现类,那就会根据实现的逻辑创建对应的类型Bean到容器,如果是一个ImportBeanDefinitionRegistrar接口实现类,那么也会根据该实现类的逻辑来创建Bean。这给了我们很大的灵活度,尤其是ImportBeanDefinitionRegistrar接口,在框架内部实现的比较多,本文会给出一个该接口的简单解析和利用该接口注册Bean的应用场景。

一、解析

  • ImportBeanDefinitionRegistrar接口不是直接注册Bean到IOC容器,它的执行时机比较早,准确的说更像是注册Bean的定义信息以便后面的Bean的创建。我们先通过源码注释来简单解读。
/**
 * 这个接口可以被那些在处理Configuration类型的过程中需要注册额外Bean定义信息的类来实现。
 * Interface to be implemented by types that register additional bean definitions when
 * processing @{@link Configuration} classes. Useful when operating at the bean definition
 * level (as opposed to {@code @Bean} method/instance level) is desired or necessary.
 *
 * 和@Configuration注解@ImportSelector注解用于,这个类型的类可以提供给@Import注解(也可以
 * 直接被ImportSelector返回)
 * <p>Along with {@code @Configuration} and {@link ImportSelector}, classes of this type
 * may be provided to the @{@link Import} annotation (or may also be returned from an
 * {@code ImportSelector}).
 *
 * 可以同时实现Aware系列接口并且方法调用的优先级高于registerBeanDefinitions
 * <p>An {@link ImportBeanDefinitionRegistrar} may implement any of the following
 * {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
 * methods will be called prior to {@link #registerBeanDefinitions}:
 * <ul>
 * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
 * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
 * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
 * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
 * </ul>
 *
 * <p>See implementations and associated unit tests for usage examples.
 *
 * @author Chris Beams
 * @since 3.1
 * @see Import
 * @see ImportSelector
 * @see Configuration
 */
public interface ImportBeanDefinitionRegistrar {

	/**
	 * 基于导入的@Configuration类给出的注解元信息,在有必要的情况下注册Bean的定义信息
	 * 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.
	 * @param importingClassMetadata annotation metadata of the importing class
	 * @param registry current bean definition registry
	 */
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
  • ImportBeanDefinitionRegistrar接口只有一个方法,在使用中通常是重写该方法,在方法中注册对应Bean的信息,具体我们看下面的使用的例子。

二、应用

2.1 Spring框架

  • 前面我们看来Aop的应用,我们就先看Aop中对ImportBeanDefinitionRegistrar的使用
2.1.1 Aop-AutoProxyRegistrar
  • Aop核心功能点,详细注释如下。最终注册的是InfrastructureAdvisorAutoProxyCreator.class类型的Bena定义信息,BeanName是AUTO_PROXY_CREATOR_BEAN_NAME=“org.springframework.aop.config.internalAutoProxyCreator”;
/**
 * 通过BeanDefinitionRegistry注册一个自动代理创建器。后面一句话大概意思是会基于mode和proxyTargetClass
 * 这两个属性来创建Bean
 * Registers an auto proxy creator against the current {@link BeanDefinitionRegistry}
 * as appropriate based on an {@code @Enable*} annotation having {@code mode} and
 * {@code proxyTargetClass} attributes set to the correct values.
 *
 * @author Chris Beams
 * @since 3.1
 * @see EnableAspectJAutoProxy
 */
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

	private final Log logger = LogFactory.getLog(getClass());

	/**
	 * 注册,扩展和配置标准的自动代理创建器,通过寻找申明了mode和proxyTargetClass的注解,如果mode是PROXY,则会注册一个自动代理创建器(APC)
	 * 如果proxyTargetClass设置为true,则会使用CGLIB创建代理
	 * Register, escalate, and configure the standard auto proxy creator (APC) against the
	 * given registry. Works by finding the nearest annotation declared on the importing
	 * {@code @Configuration} class that has both {@code mode} and {@code proxyTargetClass}
	 * attributes. If {@code mode} is set to {@code PROXY}, the APC is registered; if
	 * {@code proxyTargetClass} is set to {@code true}, then the APC is forced to use
	 * subclass (CGLIB) proxying。
	 * <p>Several {@code @Enable*} annotations expose both {@code mode} and
	 * {@code proxyTargetClass} attributes. It is important to note that most of these
	 * capabilities end up sharing a {@linkplain AopConfigUtils#AUTO_PROXY_CREATOR_BEAN_NAME
	 * single APC}. For this reason, this implementation doesn't "care" exactly which
	 * annotation it finds -- as long as it exposes the right {@code mode} and
	 * {@code proxyTargetClass} attributes, the APC can be registered and configured all
	 * the same.
	 */
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		boolean candidateFound = false;
		Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();
		for (String annoType : annoTypes) {
			AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);
			if (candidate == null) {
				continue;
			}
			Object mode = candidate.get("mode");
			Object proxyTargetClass = candidate.get("proxyTargetClass");
			if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
					Boolean.class == proxyTargetClass.getClass()) {
				candidateFound = true;
				if (mode == AdviceMode.PROXY) {
				    //JDK代理方式
					AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
					if ((Boolean) proxyTargetClass) {
						AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
						return;
					}
				}
			}
		}
		if (!candidateFound) {
			String name = getClass().getSimpleName();
			logger.warn(String.format("%s was imported but no annotations were found " +
					"having both 'mode' and 'proxyTargetClass' attributes of type " +
					"AdviceMode and boolean respectively. This means that auto proxy " +
					"creator registration and configuration may not have occurred as " +
					"intended, and components may not be proxied as expected. Check to " +
					"ensure that %s has been @Import'ed on the same class where these " +
					"annotations are declared; otherwise remove the import of %s " +
					"altogether.", name, name, name));
		}
	}

}

//AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);最终调用的是:
registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
//AUTO_PROXY_CREATOR_BEAN_NAME="org.springframework.aop.config.internalAutoProxyCreator";
说明最终注册的是:registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
2.1.2 Aop-AspectJAutoProxyRegistrar
  • 我们在12-springAop 源码初探的文章中分析过AspectJAutoProxyRegistrar的源码,其最终注册的是AnnotationAwareAspectJAutoProxyCreator.class,BeanName是AUTO_PROXY_CREATOR_BEAN_NAME=“org.springframework.aop.config.internalAutoProxyCreator”;推测这两个注解的功能是在这一个Bean中实现的,因此最终这个两个类实际上注册的Bean定义信息是一样的。(不过代码中会判断,不会重复注册)
/**
 * Registers an {@link org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
 * AnnotationAwareAspectJAutoProxyCreator} against the current {@link BeanDefinitionRegistry}
 * as appropriate based on a given @{@link EnableAspectJAutoProxy} annotation.
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.1
 * @see EnableAspectJAutoProxy
 */
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

	/**
	 * Register, escalate, and configure the AspectJ auto proxy creator based on the value
	 * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
	 * {@code @Configuration} class.
	 */
	@Override
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

		AnnotationAttributes enableAspectJAutoProxy =
				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
		if (enableAspectJAutoProxy != null) {
			if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			}
			if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
				AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
			}
		}
	}
}

 
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
//最后调用的是
registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);

2.2 开源框架Apollo

  • 在开源框架分布式配置中心 Apollo中也使用到了ImportBeanDefinitionRegistrar。Apollo是和Spring集成的,在Spring或者SpringBoot中使用Apollo来做配置中心是比较方便的,在应用这边相当于一个Apollo客户端,使用的时候也是通过一个注解来开启Apollo客户端的功能,该注解是@EnableApolloConfig,如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ApolloConfigRegistrar.class)
public @interface EnableApolloConfig {
  /**
   * Apollo namespaces to inject configuration into Spring Property Sources.
   */
  String[] value() default {ConfigConsts.NAMESPACE_APPLICATION};

  /**
   * The order of the apollo config, default is {@link Ordered#LOWEST_PRECEDENCE}, which is Integer.MAX_VALUE.
   * If there are properties with the same name in different apollo configs, the apollo config with smaller order wins.
   * @return
   */
  int order() default Ordered.LOWEST_PRECEDENCE;
}
  • 在启动类上使用了该注解即开启了Apollo客户端的功能,我们看到使用该注解实际上也是Import了一个类ApolloConfigRegistrar,他也是实现了ImportBeanDefinitionRegistrar接口。这个用法和Aop中的@EnableAspectJAutoProxy注解是非常类似的,推测这个ApolloConfigRegistrar类也是用于注册一些Bean的定义信息来实现先关Bean的注册,我们简单看看代码。
public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //1.前面是获取相关的信息,我们可以先不关注
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata
                .getAnnotationAttributes(EnableApolloConfig.class.getName()));
        String[] namespaces = attributes.getStringArray("value");
        int order = attributes.getNumber("order");
        PropertySourcesProcessor.addNamespaces(Lists.newArrayList(namespaces), order);

        Map<String, Object> propertySourcesPlaceholderPropertyValues = new HashMap<>();
        // to make sure the default PropertySourcesPlaceholderConfigurer's priority is higher than PropertyPlaceholderConfigurer
        propertySourcesPlaceholderPropertyValues.put("order", 0);

        /*
          2.下面才是最关键的部分,这里实际上就在注册了很多Bean的定义信息,比如SpringValueProcessor.class,他是用于处理字符串            类型的配置,ApolloJsonValueProcessor.class用于
          处理Json形式的配置,在BeanRegistrationUtil.registerBeanDefinitionIfNotExists方法中最终会调用
          registry.registerBeanDefinition(beanName, beanDefinition)这个注册Bean定义信息的方法
          */
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
                PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues);

        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class.getName(),
                PropertySourcesProcessor.class);

        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
                ApolloAnnotationProcessor.class);

        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class);
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class.getName(), SpringValueDefinitionProcessor.class);

        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(),
                ApolloJsonValueProcessor.class);
    }
}

2.3 业务场景

  • 在我的业务场景中也使用到了ImportBeanDefinitionRegistrar接口,在项目中我们需要将应用部署到容器化环境(微服务架构,使用Eureka作为注册中心),也需要使用K8S作为注册中心,但是这2种注册机制实际上都是实现了SpringCloud的服务发现接口,并且通过不同的自动配置类来实现Bean的装配的,我们可以通过简单的改造让应用适配2种部署场景,下面是部分关键代码。
@Configuration
public class DiscoveryRegisterBean implements ImportBeanDefinitionRegistrar {

    private static final Logger LOG = LoggerFactory.getLogger(DiscoveryRegisterBean.class);

    /**
     * 注册类型
     */
    private static final String EUREKA_TYPE = "eureka";
    private static final String K8S_TYPE = "k8s";


    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        String registerType = getRegisterType();
        if (!isValidRegisterType(registerType)) {
            LOG.error("registerType is not valid , project will register to default register center... ");
        }

        if (K8S_TYPE.equalsIgnoreCase(registerType)) {
            registryK8sIntellif(registry);
        } else {
            registryEurekaIntellif(registry);
        }
    }


    /**
     * 加载Eureka注册类
     *
     * @param registry 注册对象
     */
    private void registryEurekaIntellif(BeanDefinitionRegistry registry) {
        DiscoveryRegistrationUtil.registerBeanDefinitionIfNotExists(registry,
                EurekaClientAutoConfiguration.class.getName(),
                EurekaClientAutoConfiguration.class);
        DiscoveryRegistrationUtil.registerBeanDefinitionIfNotExists(registry,
                RibbonEurekaAutoConfiguration.class.getName(),
                RibbonEurekaAutoConfiguration.class);
    }
 
    /**
     * 加载K8S注册类
     *
     * @param registry 注册对象
     */
    private void registryK8sIntellif(BeanDefinitionRegistry registry) {
        DiscoveryRegistrationUtil.registerBeanDefinitionIfNotExists(registry,
                KubernetesDiscoveryClientAutoConfiguration.class.getName(),
                KubernetesDiscoveryClientAutoConfiguration.class);
    }
    
    //省略其他代码...
}


//使用时通过注解开启该功能即可

@Target({java.lang.annotation.ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableDiscoveryClient
@EnableEurekaClient
@Import(DiscoveryRegisterBean.class)
public @interface EnableDiscoveryClientI {
}

三、小结

  • ImportBeanDefinitionRegistrar注解可以比较灵活的实现Bean定义信息的注册,实际上我们在使用的时候都是通过实现该接口来达到按照指定条件注册bean定义信息,来达到注入对应的Bean到IOC容器的目的,可以灵活的按照自己的逻辑注册,且可以通过注解来开启对应的功能。文中给出了几个简单的使用场景算是抛砖引玉,有兴趣可以继续了解并且加以运用,另外在SpringCloud框架的Feign客户端中FeignClientsRegistrar类也实现了ImportBeanDefinitionRegistrar接口并完成看FeignClients相关Bean定义信息的解析和注册,有兴趣可以继续研究。
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
logback-springSpring Framework 的一个模块,提供了与 Spring 集成的 Logback 支持。它允许您通过 Spring 环境配置文件来配置 Logback,以便根据当前的 Spring profile 自动加载不同的日志配置。 在 logback-spring ,您可以使用以下方式配置 Spring profile 相关的日志配置: 1. 通过在 logback.xml 或 logback-spring.xml 配置文件使用 `springProfile` 标签,如下所示: ``` <springProfile name="dev"> <!-- Dev profile logging configuration here --> </springProfile> <springProfile name="prod"> <!-- Prod profile logging configuration here --> </springProfile> ``` 2. 通过在 application.properties 或 application.yml 配置文件配置日志属性,如下所示: ``` logging.level.com.example=DEBUG logging.file=myapp.log logging.pattern.console=%d{HH:mm:ss.SSS} %-5level [%thread] %logger{15} - %msg%n logging.pattern.file=%d{HH:mm:ss.SSS} %-5level [%thread] %logger{15} - %msg%n ``` 在这个示例,`logging.level` 属性用于配置日志级别,`logging.file` 属性用于配置日志文件名称,`logging.pattern.console` 和 `logging.pattern.file` 属性用于配置日志输出格式。您可以根据需要在不同的 profile 设置这些属性。 需要注意的是,如果您同时使用了 logback.xml 和 application.properties 或 application.yml,那么 application.properties 或 application.yml 的日志属性将覆盖 logback.xml 的属性。如果您想要根据 Spring profile 加载不同的 logback.xml 文件,则需要将 logback.xml 文件命名为 logback-{profile}.xml,例如 logback-dev.xml 或 logback-prod.xml,然后在应用程序启动时指定 active profile。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值