文章目录
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:beanName′null′,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第二种方式的实现原理。