手写SpringBoot(四)之bean动态加载
本节巩固一下@ConditionalOnClass的相关逻辑,主要新增@ConditionalOnMissingBean注解
SpringBoot配置了很多类,如果用户不希望使用springboot自动配置的那个类怎么办?
如果用户强行配置了一个相同的类,SpringBoot会选择使用哪一个呢?
这就需要按需加载了,如果用户配置了相同的类,则SpringBoot就不加载自己配置的那个。这就是ConditionalOnMissingBean的由来,如果这个bean缺失了,则SpringBoot将这个Bean配置上,如果这个Bean由用户配置了,则自己配置的bean作弃。
前置工作,将MyConditionalOnClass,MyClassCondition 迁移到my-spring-boot-configuration模块下面
ConditionalOnMissingBean 逻辑实现
定义@MyConditionalOnMissingBean注解
package cn.axj.springboot.my.annotation; import cn.axj.springboot.my.condition.MyMissingBeanCondition; import org.springframework.context.annotation.Conditional; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE,ElementType.METHOD}) @Conditional(MyMissingBeanCondition.class) public @interface MyConditionalOnMissingBean { String value(); }
- 实现MissBean的判断逻辑,新增
MyMissingBeanCondition
package cn.axj.springboot.my.condition; import cn.axj.springboot.my.annotation.MyConditionalOnMissingBean; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; import java.util.Map; import java.util.Objects; public class MyMissingBeanCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(MyConditionalOnMissingBean.class.getName()); /** * 获取{@link MyConditionalOnMissingBean}注解中的属性值 * 例如:@MyConditionalOnMissingBean(value = "com.example.MyBean") * 则可以通过annotationAttributes.get("value")获取到"com.example.MyBean" */ String beanName = (String) annotationAttributes.get("value"); ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); Object bean = null; try { //这里简单点,根据beanName获取bean,实际中需要更复杂的逻辑来判断是否缺失 bean = beanFactory.getBean(beanName); return false; } catch (BeansException e) { BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory(); try { if(Objects.isNull(parentBeanFactory)){ return true; } bean = parentBeanFactory.getBean(beanName); return false; } catch (BeansException ex) { return true; } } } }
至此@ConditionalOnMissingBean逻辑开发完成
测试
在UserApplication中配置tomcatWebServer
@Bean public TomcatWebServer tomcatWebServer(){ System.out.println("userApplication tomcatWebServer init"); return new TomcatWebServer(); }
在my-spring-boot模块WebServerAutoConfiguration中,配置tomcatWebServer中加上注解
@ConditionalOnMissingBean
@Bean @MyConditionalOnClass("org.apache.catalina.startup.Tomcat") @MyConditionalOnMissingBean("tomcatWebServer") public TomcatWebServer tomcatWebServer() { System.out.println("mySpringboot tomcatWebServer init"); return new TomcatWebServer(); }
启动user-application,
userApplication tomcatWebServer init 三月 29, 2024 5:20:31 下午 org.springframework.context.annotation.ConfigurationClassPostProcessor enhanceConfigurationClasses 信息: Cannot enhance @Configuration bean definition 'userApplication' since its singleton instance has been created too early. The typical cause is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor return type: Consider declaring such methods as 'static'. 启动TomcatWeb容器 三月 29, 2024 5:20:31 下午 org.apache.coyote.AbstractProtocol init 信息: Initializing ProtocolHandler ["http-nio-8080"] 三月 29, 2024 5:20:31 下午 org.apache.catalina.core.StandardService startInternal 信息: Starting service [Tomcat] 三月 29, 2024 5:20:31 下午 org.apache.catalina.core.StandardEngine startInternal 信息: Starting Servlet engine: [Apache Tomcat/9.0.75] 三月 29, 2024 5:20:32 下午 org.apache.catalina.util.SessionIdGeneratorBase createSecureRandom 警告: Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [320] milliseconds. 三月 29, 2024 5:20:32 下午 org.apache.coyote.AbstractProtocol start 信息: Starting ProtocolHandler ["http-nio-8080"]
可以看到只有userApplication tomcatWebServer init打印,没有mySpringboot tomcatWebServer init打印,证明spring-boot只加载了用户定义的那个tomcatWebServer