Spring Cloud Bus custom event stepping

Posted by psychotomus on Fri, 15 Nov 2019 11:52:20 +0100

This paper is based on Spring Cloud Greenwich.SR3, Spring Boot 2.1.10.RELEASE

By chance, I found that Spring Cloud also has this component. By combining with Message Oriented Middleware (RabbitMQ or Kafka), the published Spring events can be passed to other JVM projects. It feels very good, so I wrote a demo to try. The code can Reference here PS: this code is after pit filling.

As a result, no matter how you try, RabbitMQ's console can't observe the message passing, which means that no message is sent from the project side.

What's more, if you search the tutorial of Bus on Google, it's all Spring Cloud Config related. It's useless

Finally, I found a piece of code in BusAutoConfiguration

@EventListener(classes = RemoteApplicationEvent.class)
public void acceptLocal(RemoteApplicationEvent event) {
    if (this.serviceMatcher.isFromSelf(event)
        && !(event instanceof AckRemoteApplicationEvent)) {
        this.cloudBusOutboundChannel.send(MessageBuilder.withPayload(event).build());
    }
}

It seems that Spring will have a judgment before publishing events to MQ

public boolean isFromSelf(RemoteApplicationEvent event) {
    String originService = event.getOriginService();
    String serviceId = getServiceId();
    return this.matcher.match(originService, serviceId);
}

public String getServiceId() {
    return this.id;
}

After debugging, it is found that the originService=producer-1, serviceId=producer-8111-random string here is obviously not equal.

Here, the id is assigned through the construction method. The only place for the whole project to call the construction method is in the BusAutoConfiguration configuration

@Bean
public ServiceMatcher serviceMatcher(@BusPathMatcher PathMatcher pathMatcher,
                                     BusProperties properties, Environment environment) {
    String[] configNames = environment.getProperty(CLOUD_CONFIG_NAME_PROPERTY,
                                                   String[].class, new String[] {});
    ServiceMatcher serviceMatcher = new ServiceMatcher(pathMatcher,
                                                       properties.getId(), configNames);
    return serviceMatcher;
}

Obviously, the configuration file BusProperties is read here. However, you will find that no matter what the id here is configured into, it will change it to the form above. The culprit is as follows

//BusEnvironmentPostProcessor.java
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
                                   SpringApplication application) {
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("spring.cloud.bus.id", getDefaultServiceId(environment));
    addOrReplace(environment.getPropertySources(), map);
}

private String getDefaultServiceId(ConfigurableEnvironment environment) {
    return "${vcap.application.name:${spring.application.name:application}}:${vcap.application.instance_index:${spring.application.index:${local.server.port:${server.port:0}}}}:${vcap.application.instance_id:${random.value}}";
}

Spring replaced that configuration with a custom ID generation!

Once the cause is found, the solution is simple. When publishing an event, use the BusProperties ID to ensure that the if is true

@RequestMapping("/hello")
@RestController
public class HelloController {

    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private BusProperties busProperties;

    @GetMapping("/test")
    public String test(@RequestParam("message") String message) {
        String id = busProperties.getId();
        applicationContext.publishEvent(new CustomApplicationEvent(this, id, null, message));
        return "Sending succeeded!";
    }
}

Topics: Programming Spring RabbitMQ kafka jvm