5. 自定义WebHandler与默认DispatcherHandler加载冲突问题

1. 背景

      前几篇文章介绍了神禹的原理,神禹是通过扩展webflux来扩展功能的,扩展的方式是通过定义自己的WebHandler,也就是ShenyuWebHandler。我觉得这个思路非常好,正好这几天也在琢磨跟流量出口业务相关的系统设计,所以也来实践一把,实践的过程中遇到个问题,自定义WebHandler实现与webflux默认webHandler实现DispatcherHandler冲突,分享一下解决过程。

2. 问题

      我定义了自己的Handler叫EngineHandler,实现了WebHandler,代码如下:

public class EngineHandler implements WebHandler {

      项目启动过程中报错如下:
在这里插入图片描述
      从错误上来看,是因为在spring容器中已经有一个叫webHandler的bean了,它在EnableWebFluxConfiguration类中,这时候我在往容器中放入一个同样名称的bean,失败了!
      解决方法是设置spring.main.allow-bean-definition-overriding=true。
      这个方式虽然能让项目不报错,但是它并不解决问题。因为我想在容器中放入的是我定义的bean,也就是EngineConfiguration类中的EngineHandler,不是webflux默认的DispatcherHandler。

3. 解决方案

3.1 方案一

      既然容器中不是我想要的WebHandler,那我就给它改成我想要的Handler,怎么改呢?我实现了自己的BeanDefinitionRegistryPostProcessor,在postProcessBeanDefinitionRegistry(…)方法中对handler进行了修改,代码如下:

@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        final String webHandler = "webHandler";
        final String engineHandler = "engineHandler";

        if (registry.containsBeanDefinition(webHandler) && registry.containsBeanDefinition(engineHandler)) {
            registry.removeBeanDefinition(webHandler);
            registry.registerBeanDefinition(webHandler, registry.getBeanDefinition(engineHandler));
            registry.removeBeanDefinition(engineHandler);
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }
}

      亲测此方案可行。但是我觉得这样不太优雅,神禹是怎么解决这个问题的呢?

3.2 方案二

      通过对神禹源代码的研究,我发现神禹解决这个问题没有使用额外的代码或者配置的方式,仿佛是和神禹的工程结构有关系。
      神禹项目的工程结构和我的项目结构不同,我把项目内容放到了一个工程中,神禹是通过封装starter的形式组织的,它的结构是这样的在这里插入图片描述      shenyu-bootstrap中引入了一个starter,即shenyu-spring-boot-starter-gateway,ShenyuWebHandler的配置类ShenyuConfiguration就在shenyu-spring-boot-starter-gateway中。
      由此我猜测spring-boot加载bean是按照一定的顺序来的,它先加载工程中的bean,在加载starter中的bean,我按照神禹项目的代码结构,重新组织了我的代码,测试后发现问题解决。
在这里插入图片描述

      从错误上看,已经是自定义handler在覆盖默认handler了。
      代码验证一下。
在这里插入图片描述

4. Spring-Boot 加载bean顺序

4.1 先加载工程中的bean还是jar包里的bean

      这里先给答案,spring-boot先扫描工程下的bean,在加载starter里的configurations,具体可参见org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass

@Nullable
protected final SourceClass doProcessConfigurationClass(
      ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
      throws IOException {
    //省略了一些代码
   // Process any @ComponentScan annotations
   //此处是扫描启动类所在包路径下所有bean的方法
   Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
         sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
   if (!componentScans.isEmpty() &&
         !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
      for (AnnotationAttributes componentScan : componentScans) {
         // The config class is annotated with @ComponentScan -> perform the scan immediately
         Set<BeanDefinitionHolder> scannedBeanDefinitions =
               this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
         // Check the set of scanned definitions for any further config classes and parse recursively if needed
         for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
            BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
            if (bdCand == null) {
               bdCand = holder.getBeanDefinition();
            }
            if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
               parse(bdCand.getBeanClassName(), holder.getBeanName());
            }
         }
      }
   }

   // Process any @Import annotations
   // 此处加载的是starter里的configurations
   processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
   //省略了一些代码
   // No superclass -> processing is complete
   return null;
}

4.2 两个starter都有name为webHandler的bean,用哪一个呢

      两个starter的jar包,都有name为webHandler的bean,一个是DispatcherHandler的实例,一个是自定义的EngineHandler的实例,用哪一个呢?拿神禹来说,为什么每次加载的都是ShenyuWebHandler的实例,不是默认的DispatcherHandler的实例呢?
      原因是configurations是按照一定的顺序加载的,这是我测试项目中ConfigurationClass的顺序,标号0-3的是我自己的工程代码,标号22是我自定义starter里的配置类,剩余的是webflux框架代码。

0 = {ConfigurationClass@3689} “ConfigurationClass: beanName ‘checkAliveController’, class path resource [org/example/gateway/controller/CheckAliveController.class]”
1 = {ConfigurationClass@3690} “ConfigurationClass: beanName ‘checkUriPlugin’, class path resource [org/example/gateway/plugins/CheckUriPlugin.class]”
2 = {ConfigurationClass@3691} “ConfigurationClass: beanName ‘resultPlugin’, class path resource [org/example/gateway/plugins/ResultPlugin.class]”
3 = {ConfigurationClass@3692} “ConfigurationClass: beanName ‘gatewayExampleApplication’, org.example.gateway.GatewayExampleApplication”
4 = {ConfigurationClass@3693} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/context/PropertyPlaceholderAutoConfiguration.class]”
5 = {ConfigurationClass@3694} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryConfiguration$EmbeddedNetty.class]”
6 = {ConfigurationClass@3695} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfiguration.class]”
7 = {ConfigurationClass@3696} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration$Jackson2ObjectMapperBuilderCustomizerConfiguration.class]”
8 = {ConfigurationClass@3697} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration.class]”
9 = {ConfigurationClass@3698} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration$ParameterNamesModuleConfiguration.class]”
10 = {ConfigurationClass@3699} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration$JacksonObjectMapperConfiguration.class]”
11 = {ConfigurationClass@3700} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.class]”
12 = {ConfigurationClass@3701} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/http/codec/CodecsAutoConfiguration$DefaultCodecsConfiguration.class]”
13 = {ConfigurationClass@3702} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/http/codec/CodecsAutoConfiguration$JacksonCodecConfiguration.class]”
14 = {ConfigurationClass@3703} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/http/codec/CodecsAutoConfiguration.class]”
15 = {ConfigurationClass@3704} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/web/reactive/error/ErrorWebFluxAutoConfiguration.class]”
16 = {ConfigurationClass@3705} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration E n a b l e W e b F l u x C o n f i g u r a t i o n . c l a s s ] " 17 = C o n f i g u r a t i o n C l a s s @ 3706 " C o n f i g u r a t i o n C l a s s : b e a n N a m e ′ n u l l ′ , c l a s s p a t h r e s o u r c e [ o r g / s p r i n g f r a m e w o r k / b o o t / a u t o c o n f i g u r e / w e b / r e a c t i v e / W e b F l u x A u t o C o n f i g u r a t i o n EnableWebFluxConfiguration.class]" 17 = {ConfigurationClass@3706} "ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration EnableWebFluxConfiguration.class]"17=ConfigurationClass@3706"ConfigurationClass:beanNamenull,classpathresource[org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationWebFluxConfig.class]”
18 = {ConfigurationClass@3707} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration$WelcomePageConfiguration.class]”
19 = {ConfigurationClass@3708} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.class]”
20 = {ConfigurationClass@3709} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfiguration$AnnotationConfig.class]”
21 = {ConfigurationClass@3710} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfiguration.class]”
22 = {ConfigurationClass@3711} “ConfigurationClass: beanName ‘null’, class path resource [org/example/gateway/config/EngineConfiguration.class]”
23 = {ConfigurationClass@3712} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/aop/AopAutoConfiguration$ClassProxyingConfiguration.class]”
24 = {ConfigurationClass@3713} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/aop/AopAutoConfiguration.class]”
25 = {ConfigurationClass@3714} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/availability/ApplicationAvailabilityAutoConfiguration.class]”
26 = {ConfigurationClass@3715} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/cache/GenericCacheConfiguration.class]”
27 = {ConfigurationClass@3716} “ConfigurationClass: beanName ‘null’, java.lang.Object”
28 = {ConfigurationClass@3717} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/cache/SimpleCacheConfiguration.class]”
29 = {ConfigurationClass@3718} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/cache/NoOpCacheConfiguration.class]”
30 = {ConfigurationClass@3719} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/cache/CacheAutoConfiguration.class]”
31 = {ConfigurationClass@3720} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/context/ConfigurationPropertiesAutoConfiguration.class]”
32 = {ConfigurationClass@3721} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/context/LifecycleAutoConfiguration.class]”
33 = {ConfigurationClass@3722} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfiguration.class]”
34 = {ConfigurationClass@3723} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.class]”
35 = {ConfigurationClass@3724} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.class]”
36 = {ConfigurationClass@3725} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration$NettyWebServerFactoryCustomizerConfiguration.class]”
37 = {ConfigurationClass@3726} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration.class]”
38 = {ConfigurationClass@3727} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfiguration$ReactorNetty.class]”
39 = {ConfigurationClass@3728} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfiguration.class]”
40 = {ConfigurationClass@3729} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientAutoConfiguration$WebClientCodecsConfiguration.class]”
41 = {ConfigurationClass@3730} “ConfigurationClass: beanName ‘null’, class path resource [org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientAutoConfiguration.class]”

      我们看下第四个PropertyPlaceholderAutoConfiguration类定义

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class PropertyPlaceholderAutoConfiguration {
   @Bean
   @ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
   public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
      return new PropertySourcesPlaceholderConfigurer();
   }
}

      @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE),表明它有最高优先级。
      在看下第五个 ReactiveWebServerFactoryConfiguration$EmbeddedNetty.class类定义

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(ReactiveWebServerFactory.class)
@ConditionalOnClass({ HttpServer.class })
static class EmbeddedNetty {
   @Bean
   @ConditionalOnMissingBean
   ReactorResourceFactory reactorServerResourceFactory() {
      return new ReactorResourceFactory();
   }

   @Bean
   NettyReactiveWebServerFactory nettyReactiveWebServerFactory(ReactorResourceFactory resourceFactory,
         ObjectProvider<NettyRouteProvider> routes, ObjectProvider<NettyServerCustomizer> serverCustomizers) {
      NettyReactiveWebServerFactory serverFactory = new NettyReactiveWebServerFactory();
      serverFactory.setResourceFactory(resourceFactory);
      routes.orderedStream().forEach(serverFactory::addRouteProviders);
      serverFactory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList()));
      return serverFactory;
   }
}

      @ConditionalOnMissingBean(ReactiveWebServerFactory.class)
      @ConditionalOnClass({ HttpServer.class })
      表明在没有ReactiveWebServerFactory.class类型的bean与加载类有HttpServer时加载EmbeddedNetty

      在看下第六个 ReactiveWebServerFactoryAutoConfiguration.class类定义

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ReactiveHttpInputMessage.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ReactiveWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
      ReactiveWebServerFactoryConfiguration.EmbeddedTomcat.class,
      ReactiveWebServerFactoryConfiguration.EmbeddedJetty.class,
      ReactiveWebServerFactoryConfiguration.EmbeddedUndertow.class,
      ReactiveWebServerFactoryConfiguration.EmbeddedNetty.class })
public class ReactiveWebServerFactoryAutoConfiguration {

      spring-boot就是通过这种方式来控制configurations的加载顺序的。
      从大方向上来说,它会首先加载框架类的configurations。
      我们把自定义的WebHandler放到starter中,默认会在框架类之后加载。

5. 总结

      本文描述了webflux中自定义WebHandler实现与默认DispatcherHandler实现加载冲突的问题,并提供了两种解决方式。一种是bean加载完成后替换成需要的bean,一种是在自定义starter中配置configuration类,覆盖框架中configuration类的方式,并分析了spring-boot第二种方式的实现原理。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值