I'm using spring-cloud-starter-openfeign
and spring-cloud-starter-loadbalancer
in a demo project, here is the project's structor
project
|-common
|-service-a
|-service-b
I define 2 feign-clients for service-a and service-b in common, and common will be depended by them, here are the clients' code
// Here are two simple service-client, these two clients will be
// implemented by their controller. For example, service-a will have a
// controller class [ServiceAController implements FeignClientServiceA]
// so on as the service-b
@FeignClient("service-a")
public interface FeignClientServiceA {
// This method is simply log the ${str} and return
@GetMapping("/echo/{str}")
String echo(@PathVariable String str);
// This method will log the ${str} and then
// call another client's echo method with param ${str}
@GetMapping("/echo-rest/{str}")
String echoRest(@PathVariable String str);
}
// Similar as service-a
@FeignClient("service-b")
public interface FeignClientServiceB {
@GetMapping("/echo/{str}")
String echo(@PathVariable String str);
@GetMapping("/echo-rest/{str}")
String echoRest(@PathVariable String str);
}
In this case, when I start the Application of service-a and service-b, it goes on well when send request to /echo/{str} and /echo-rest/{str} both a and b. But when I try to define my customize LoadBalancer, I got error.
@Configuration
public class CustomizeConfiguration {
@Bean
@ConditionalOnMissingBean
public CustomizeLoadBalancer reactorServiceInstanceLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new CustomizeLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
The CustomizeLoadBalancer
is a completely copy of RoundBobinLoadBalancer
and do some log. But when I started the Application of service-a and send request /echo-rest/{str}
, I got an error
java.lang.NullPointerException: Cannot invoke "Object.hashCode()" because "key" is null
at java.base/java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:936) ~[na:na]
at java.base/java.util.concurrent.ConcurrentHashMap.containsKey(ConcurrentHashMap.java:964) ~[na:na]
at org.springframework.cloud.context.named.NamedContextFactory.getContext(NamedContextFactory.java:119) ~[spring-cloud-context-4.0.4.jar:4.0.4]
at org.springframework.cloud.context.named.NamedContextFactory.getProvider(NamedContextFactory.java:212) ~[spring-cloud-context-4.0.4.jar:4.0.4]
at org.springframework.cloud.context.named.ClientFactoryObjectProvider.delegate(ClientFactoryObjectProvider.java:115) ~[spring-cloud-context-4.0.4.jar:4.0.4]
at org.springframework.cloud.context.named.ClientFactoryObjectProvider.getIfAvailable(ClientFactoryObjectProvider.java:64) ~[spring-cloud-context-4.0.4.jar:4.0.4]
at com.example.loadbalancer.config.CustomizeLoadBalancer.choose(CustomizeLoadBalancer.java:40) ~[classes/:na]
at com.example.loadbalancer.config.CustomizeLoadBalancer.choose(CustomizeLoadBalancer.java:17) ~[classes/:na]
at org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient.choose(BlockingLoadBalancerClient.java:163) ~[spring-cloud-loadbalancer-4.0.4.jar:4.0.4]
at org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient.execute(FeignBlockingLoadBalancerClient.java:118) ~[spring-cloud-openfeign-core-4.0.4.jar:4.0.4]
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:100) ~[feign-core-12.4.jar:na]
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:70) ~[feign-core-12.4.jar:na]
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:96) ~[feign-core-12.4.jar:na]
at jdk.proxy2/jdk.proxy2.$Proxy84.echo(Unknown Source) ~[na:na]
at com.example.loadbalancer.controller.ServiceAController.echoRest(ServiceAController.java:32) ~[classes/:na]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-6.0.13.jar:6.0.13]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-6.0.13.jar:6.0.13]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.0.13.jar:6.0.13]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884) ~[spring-webmvc-6.0.13.jar:6.0.13]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-6.0.13.jar:6.0.13]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.0.13.jar:6.0.13]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081) ~[spring-webmvc-6.0.13.jar:6.0.13]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974) ~[spring-webmvc-6.0.13.jar:6.0.13]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011) ~[spring-webmvc-6.0.13.jar:6.0.13]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.0.13.jar:6.0.13]
I got the cause, the expression String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
is null, and then I start to set breakpoint and compare the source and customize loadbalancer, and finally I find the clue
The expression will goes to PropertySourcePropertyResolver#getProperty
, I check the propertySourceList inside, and found the difference.
Defaultly, the propertySourceList
will have 16 size, but after I inject my CustomizeLoadBalancer and replace the source, it turns to 15.
The lacking one is MapPropertySource {name='loadbalancer'}
, in service-a project, it contains one mapping loadbalancer.client.name -> service-b
I don't known what was happened cause the decrease, why the replace will cause a 'catastrophic' result? Is there any solution?