添加@RefreshScope后报‘object is not an instance of declaring class’的原因与解决
以下代码可正常运行:
@Configuration public class RouteLocatorConfig { @Bean public RouteLocator routeLocator(RouteLocatorProvider routeLocatorProvider) { return routeLocatorProvider.getRouteLocator(); } }
但给RouteLocator的@Bean方法添加@RefreshScope后,应用启动失败:
@Configuration public class RouteLocatorConfig { @Bean @RefreshScope public RouteLocator routeLocator(RouteLocatorProvider routeLocatorProvider) { return routeLocatorProvider.getRouteLocator(); } }
相关依赖类说明
RouteLocatorProvider是自定义@Component类,由WebClientConfig扫描:
@Configuration @ComponentScan(basePackages = "by.afinny.apigateway") public class WebClientConfig { @Bean @LoadBalanced public WebClient.Builder webClientBuilder() { return WebClient.builder(); } }
RouteLocatorProvider核心实现:
@Component public class RouteLocatorProvider { private final EurekaClient eurekaClient; private final WebClient.Builder webClientBuilder; private final RouteLocatorBuilder routeLocatorBuilder; private final OpenAPIV3Parser apiParser; @Getter private RouteLocator routeLocator; public RouteLocatorProvider(EurekaClient eurekaClient, WebClient.Builder webClientBuilder, RouteLocatorBuilder routeLocatorBuilder) { this.eurekaClient = eurekaClient; this.webClientBuilder = webClientBuilder; this.routeLocatorBuilder = routeLocatorBuilder; this.apiParser = new OpenAPIV3Parser(); } @PostConstruct public void registerListener() { eurekaClient.registerEventListener(event -> { if (event instanceof CacheRefreshedEvent) { refreshRoutings(); } }); } private void refreshRoutings() { List<Application> applications = findOtherRegisteredApplications(); Map<Application, SwaggerParseResult> docs = mapToDocs(applications); routeLocator = buildRouteLocatorFrom(docs); } // 简化后的辅助方法 private List<Application> findOtherRegisteredApplications() { return Collections.emptyList(); } private Map<Application, SwaggerParseResult> mapToDocs(List<Application> applications) { return Collections.emptyMap(); } private RouteLocator buildRouteLocatorFrom(Map<Application, SwaggerParseResult> appsToDocs) { return routeLocatorBuilder.routes().build(); } }
异常信息
添加@RefreshScope后触发的核心异常:
org.springframework.context.ApplicationContextException: Failed to start bean 'eurekaAutoServiceRegistration' ... Caused by: java.lang.IllegalArgumentException: object is not an instance of declaring class at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:281) at org.springframework.cloud.context.scope.GenericScope$LockedScopedProxyFactoryBean.invoke(GenericScope.java:482) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:244) at jdk.proxy2/jdk.proxy2.$Proxy107.getRoutes(Unknown Source) ...
对比验证
若改写RouteLocator的Bean方法为以下形式,应用可正常启动:
@Bean @RefreshScope public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder) { return routeLocatorBuilder.routes().build(); }
RefreshScope代理机制冲突:给
RouteLocator的Bean添加@RefreshScope后,Spring会为该Bean生成基于JDK动态代理的代理对象(因为RouteLocator是接口)。但RouteLocatorProvider中直接持有的RouteLocator是通过routeLocatorBuilder创建的具体实现类,并非Spring管理的代理对象。反射调用类型不匹配:应用启动时,
WeightCalculatorWebFilter会调用所有RouteLocator的getRoutes()方法。此时调用的是RefreshScope生成的代理对象,代理内部通过反射调用目标实例(即RouteLocatorProvider中持有的RouteLocator)的方法时,出现代理类型与实际实例类型不匹配的问题,触发object is not an instance of declaring class异常。生命周期不一致:
RouteLocatorProvider是单例Bean,内部的routeLocator实例在@PostConstruct或Eureka事件触发时被创建;而RefreshScope要求Bean在每次刷新时重新实例化,两者生命周期不匹配,导致代理无法正确管理RouteLocator实例。
方案一:调整Bean创建逻辑,每次刷新重新生成RouteLocator
修改RouteLocatorConfig,让Bean方法每次刷新时都通过RouteLocatorProvider的公共方法重新生成RouteLocator,而非直接返回provider中已持有的实例:
@Configuration public class RouteLocatorConfig { @Bean @RefreshScope public RouteLocator routeLocator(RouteLocatorProvider routeLocatorProvider) { List<Application> applications = routeLocatorProvider.findOtherRegisteredApplications(); Map<Application, SwaggerParseResult> docs = routeLocatorProvider.mapToDocs(applications); return routeLocatorProvider.buildRouteLocatorFrom(docs); } }
同时将RouteLocatorProvider中的findOtherRegisteredApplications、mapToDocs、buildRouteLocatorFrom方法改为公共方法,移除内部的routeLocator成员变量。
方案二:给RouteLocatorProvider添加@RefreshScope
将@RefreshScope添加到RouteLocatorProvider类上,让整个Provider成为可刷新Bean,内部的routeLocator实例会随Provider刷新重新创建:
@Component @RefreshScope public class RouteLocatorProvider { // 原有代码不变 }
对应的RouteLocatorConfig保持如下写法:
@Configuration public class RouteLocatorConfig { @Bean public RouteLocator routeLocator(RouteLocatorProvider routeLocatorProvider) { return routeLocatorProvider.getRouteLocator(); } }
方案三:触发RefreshScope主动刷新
修改RouteLocatorProvider,移除内部持有的routeLocator实例,改为在Eureka事件触发时主动刷新RouteLocator Bean:
@Component public class RouteLocatorProvider { private final EurekaClient eurekaClient; private final RefreshScope refreshScope; public RouteLocatorProvider(EurekaClient eurekaClient, RefreshScope refreshScope) { this.eurekaClient = eurekaClient; this.refreshScope = refreshScope; } @PostConstruct public void registerListener() { eurekaClient.registerEventListener(event -> { if (event instanceof CacheRefreshedEvent) { refreshScope.refresh("routeLocator"); } }); } }
对应的RouteLocatorConfig保持带@RefreshScope的写法:
@Configuration public class RouteLocatorConfig { @Bean @RefreshScope public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder) { return routeLocatorBuilder.routes().build(); } }
内容的提问来源于stack exchange,提问作者Sergey Zolotarev

