How to define customize LoadBalancer class correctly and register it to Spring context in springcloud-loadbalancer?

45 views Asked by At

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?

0

There are 0 answers