I defined a Spring Boot App as a Verticle as follows:
#SpringBootApplication
public class SpringAppVerticle extends AbstractVerticle {
private Vertx myVertx;
#Override
public void start() {
SpringApplication.run(SpringAppVerticle.class);
System.out.println("SpringAppVerticle started!");
this.myVertx = vertx;
}
#RestController
#RequestMapping(value = "/api/hello")
public class RequestController {
#RequestMapping(method = RequestMethod.GET, produces = "application/json")
public void getEcho() {
JsonObject message = new JsonObject()
.put("text", "Hello world!");
myVertx.eventBus().send(EchoServiceVerticle.ADDRESS, message, reply -> {
JsonObject replyBody = (JsonObject) reply.result().body();
System.out.println(replyBody.encodePrettily());
});
}
}
}
I have a second non-Spring Verticle that is basically a echo service:
public class EchoServiceVerticle extends AbstractVerticle {
public static final String ADDRESS = "echo-service";
#Override
public void start() {
System.out.println("EchoServiceVerticle started!");
vertx.eventBus().consumer(EchoServiceVerticle.ADDRESS, message -> {
System.out.println("message received");
JsonObject messageBody = (JsonObject) message.body();
messageBody.put("passedThrough", "echo-service");
message.reply(messageBody);
});
}
}
The problem is that I get a nullpointer at line myVertx.eventbus().send in SpringAppVerticle class as the myVertx variable is null.
How do I properly instantiate a Vertx variable in a Spring context in order that I can exchange message between my both verticles?
My project can be found here: https://github.com/r-winkler/vertx-spring
The reason of the exception is the following:
SpringAppVerticle bean that is created during spring init is another object than starts the spring boot application. So you have two objects, one that has start() method invoked and another one that doesn't. Second one actually handles requests. So what you need is to register verticles as spring beans.
For samples of vertx/spring interoperability please refer to vertx examples repo.
P.S. I've created a pull request to your repo to make your example work.
Related
I am trying to create a simple spring boot app which takes list of routes and process it parallelly in routebuilder. I am using proceduretemplate to call my routes by defining my startroute: direct start. When i hit i am getting org.apache.camel.component.direct.DirectConsumerNotAvailableException: No consumers available on endpoint: Endpoint[direct://start].Exchange.[]. I am unable to figure out the issue here.below is my code.
TestController.java
#RestController
#RequestMapping(value = "/service")
#Component
public class TestController {
#EndpointInject(uri = "direct:start")
private ProducerTemplate template;
#RequestMapping(value = "/test",method =RequestMethod.GET)
public void getAccountDetails(){
ArrayList<String> callList = new ArrayList<String>();
callList.add("direct:phone");
callList.add("direct:sms");
callList.add("direct:email");
template.sendBody(callList);
}
CamelRoute.java
#Component
public class CamelRoute extends RouteBuilder {
final String BASE_ROUTE = "direct:start";
public void configure() throws Exception {
from(BASE_ROUTE).recipientList(body()).setParallelProcessing(true);
from("direct:phone").log("customer call made");
from("direct:sms").log("phone call made");
from("direct:email").log("email call made");
}
version
camel-spring-boot-starter', version: '2.17.0'
Thanks in advance.
I have a Spring Boot project that I'm using to receive events from an Amazon SQS queue. I've been using the Spring Cloud AWS project to make this easier.
The problem is this: the Spring Boot application starts up just fine, and appears to instantiate all the necessary beans just fine. However, when the method that is annotated with SqsListener is invoked, all the event handler's dependent beans are null.
Another thing that's important to note: I have two methods of propagating the event: 1) thru a POST web service call, and 2) thru the Amazon SQS. If I choose to run the event as a POST call with the same data in the POST body, it works just fine. The injected dependencies are only ever null whenever the SQSListener method is invoked by the SimpleMessageListenerContainer.
Classes:
#Service("systemEventsHandler")
public class SystemEventsHandler {
// A service that this handler depends on
private CustomService customService;
private ObjectMapper objectMapper;
#Autowired
public SystemEventsHandler(CustomService customService, ObjectMapper objectMapper) {
this.matchStatusSvc = matchStatusSvc;
this.objectMapper = objectMapper;
}
public void handleEventFromHttpCall(CustomEventObject event) {
// Whenever this method is called, the customService is
// present and the method call completes just fine.
Assert.notNull(objectMapper, "The objectMapper that was injected was null");
customService.handleEvent(event);
}
#SqsListener(value = "sqsName", deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS)
private void handleEventFromSQSQueue(#NotificationMessage String body) throws IOException {
// Whenever this method is called, both the objectMapper and
// the customService are null, causing the invocation to
// fail with a NullPointerException
CustomEventObject event = objectMapper.readValue(body, CustomEventObject.class);
matchStatusSvc.scoresheetUploaded(matchId);
}
}
The controller (for when I choose to run the event as a POST). As stated above, it works just fine whenever I run it as a POST call.
#RestController
#RequestMapping("/events")
public class SystemEventsController {
private final SystemEventsHandler sysEventSvc;
#Autowired
public SystemEventsController(SystemEventsHandler sysEventSvc) {
this.sysEventSvc = sysEventSvc;
}
#RequestMapping(value = "", method = RequestMethod.POST)
public void handleCustomEvent(#RequestBody CustomEventObject event) {
sysEventSvc.handleEventFromHttpCall(event);
}
}
Pertinent config:
#Configuration
public class AWSSQSConfig {
#Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer(AmazonSQSAsync amazonSQS) {
SimpleMessageListenerContainer msgListenerContainer = simpleMessageListenerContainerFactory(amazonSQS).createSimpleMessageListenerContainer();
msgListenerContainer.setMessageHandler(queueMessageHandler(amazonSQS));
return msgListenerContainer;
}
#Bean
public SimpleMessageListenerContainerFactory simpleMessageListenerContainerFactory(AmazonSQSAsync amazonSQS) {
SimpleMessageListenerContainerFactory msgListenerContainerFactory = new SimpleMessageListenerContainerFactory();
msgListenerContainerFactory.setAmazonSqs(amazonSQS);
msgListenerContainerFactory.setMaxNumberOfMessages(10);
msgListenerContainerFactory.setWaitTimeOut(1);
return msgListenerContainerFactory;
}
#Bean
public QueueMessageHandler queueMessageHandler(AmazonSQSAsync amazonSQS) {
QueueMessageHandlerFactory queueMsgHandlerFactory = new QueueMessageHandlerFactory();
queueMsgHandlerFactory.setAmazonSqs(amazonSQS);
QueueMessageHandler queueMessageHandler = queueMsgHandlerFactory.createQueueMessageHandler();
return queueMessageHandler;
}
#Bean(name = "amazonSQS", destroyMethod = "shutdown")
public AmazonSQSAsync amazonSQSClient() {
AmazonSQSAsyncClient awsSQSAsyncClient = new AmazonSQSAsyncClient(new DefaultAWSCredentialsProviderChain());
return awsSQSAsyncClient;
}
}
Other info:
Spring boot version: Dalston.RELEASE
Spring cloud AWS version:
1.2.1.RELEASE
Both the spring-cloud-aws-autoconfigure and spring-cloud-aws-messaging packages are on the classpath
Any thoughts?
As spencergibb suggested in his comment above, changing the method's visibility from private to public worked.
We are facing a problem with Hystrix Command in a Spring Boot / Cloud microservice. We have a Spring Component containing a method annotated with #RabbitListener. When a new message arrives, the method delegates the invocation to NotificationService::processNotification().
The NotificationService is a bean annotated with #Service. The method processNotification() can request third party applications. We want to wrap the invocation of third party applications using #HystrixCommand to provide fault tolerance, but due to some reasons the Hystrix Command annotated method is not working.
If we invoke a Controller and the Controller delegates the invocation to a Service method, which in turns have a Hystrix Command , everything works perfectly. The only problem with Hystrix Command arises when the microservices consume a messages and it seems to be Hystrix Command doesn’t trigger the fallback method.
Here is the non-working code:
#Component
public class MessageProcessor {
#Autowired
private NotificationService notificationService;
#RabbitListener(queues = "abc.xyz-queue")
public void onNewNotification(String payload) {
this.notificationService.processNotification(payload);
}
}
#Service
public class NotificationService {
public void processNotification(String payload) {
...
this.notifyThirdPartyApp(notificationDTO);
...
}
#HystrixCommand(fallbackMethod = "notifyThirdPartyAppFallback")
public void notifyThirdPartyApp(NotificationDTO notificationDTO) {
//Do stuff here that could fail
}
public void notifyThirdPartyAppFallback(NotificationDTO notificationDTO) {
// Fallbacl impl goes here
}
}
#SpringBootApplication
#EnableCaching
#EnableCircuitBreaker
#EnableDiscoveryClient
#EnableRabbit
public class NotificationApplication {
public static void main(String[] args) {
SpringApplication.run(NotificationApplication.class, args);
}
}
I'm not sure about your problem without looking at the code.
As another approach you can take: instead of describing this calls with annotations in your service, just extend HystrixCommand and implement api calling logic in it (read more):
public class CommandHelloWorld extends HystrixCommand<String> {
private final String name;
public CommandHelloWorld(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
#Override
protected String run() {
// a real example would do work like a network call here
return "Hello " + name + "!";
}
}
I have the following route configuration:
#Component
public class MyRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("direct:in").to("direct:out");
}
}
When I try to test it:
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyRouteTest.TestConfig.class }, loader = CamelSpringDelegatingTestContextLoader.class)
#MockEndpoints
public class MyRouteTest {
#EndpointInject(uri = "mock:direct:out")
private MockEndpoint mockEndpoint;
#Produce(uri = "direct:in")
private ProducerTemplate producerTemplate;
#Configuration
public static class TestConfig extends SingleRouteCamelConfiguration {
#Bean
#Override
public RouteBuilder route() {
return new MyRoute();
}
}
#Test
public void testRoute() throws Exception {
mockEndpoint.expectedBodiesReceived("Test Message");
producerTemplate.sendBody("Test Message");
mockEndpoint.assertIsSatisfied();
}
}
I get this exception:
org.apache.camel.component.direct.DirectConsumerNotAvailableException:
No consumers available on endpoint: Endpoint[direct://out].
Exchange[Message: Test Message]
It looks like the Mock is not picking up the message from the endpoint.
What am I doing wrong?
The problem is that mock endpoints just intercept the message before delegating to the actual endpoint. Quoted from the docs:
Important: The endpoints are still in action. What happens differently
is that a Mock endpoint is injected and receives the message first and
then delegates the message to the target endpoint. You can view this
as a kind of intercept and delegate or endpoint listener.
The solution to your problem is to tell certain endpoints (the ones that expect a consumer in your case) not to delegate to the actual endpoint. This can easily be done using #MockEndpointsAndSkip instead of #MockEndpoints:
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyRouteTest.TestConfig.class }, loader = CamelSpringDelegatingTestContextLoader.class)
#MockEndpointsAndSkip("direct:out") // <-- turns unit test from red to green ;)
public class MyRouteTest {
// ....
}
This issue because, in your route configuration, there is no route with "direct:out" consumer endpoint.
add a line like some thing below,
from("direct:out").("Anything you want to log");
So that direct:out will consume the exchange and In your test, mock will be able check the received text without any issues. Hope this helps !!
This is my setup:
First service(FlightIntegrationApplication) which invoke second service(BaggageServiceApplication) using FeignClients API and Eureka.
Project on github: https://github.com/IdanFridman/BootNetflixExample
First service:
#SpringBootApplication
#EnableCircuitBreaker
#EnableDiscoveryClient
#ComponentScan("com.bootnetflix")
public class FlightIntegrationApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(FlightIntegrationApplication.class).run(args);
}
}
in one of the controllers:
#RequestMapping("/flights/baggage/list/{id}")
public String getBaggageListByFlightId(#PathVariable("id") String id) {
return flightIntegrationService.getBaggageListById(id);
}
FlightIntegrationService:
public String getBaggageListById(String id) {
URI uri = registryService.getServiceUrl("baggage-service", "http://localhost:8081/baggage-service");
String url = uri.toString() + "/baggage/list/" + id;
LOG.info("GetBaggageList from URL: {}", url);
ResponseEntity<String> resultStr = restTemplate.getForEntity(url, String.class);
LOG.info("GetProduct http-status: {}", resultStr.getStatusCode());
LOG.info("GetProduct body: {}", resultStr.getBody());
return resultStr.getBody();
}
RegistryService:
#Named
public class RegistryService {
private static final Logger LOG = LoggerFactory.getLogger(RegistryService.class);
#Autowired
LoadBalancerClient loadBalancer;
public URI getServiceUrl(String serviceId, String fallbackUri) {
URI uri;
try {
ServiceInstance instance = loadBalancer.choose(serviceId);
uri = instance.getUri();
LOG.debug("Resolved serviceId '{}' to URL '{}'.", serviceId, uri);
} catch (RuntimeException e) {
// Eureka not available, use fallback
uri = URI.create(fallbackUri);
LOG.error("Failed to resolve serviceId '{}'. Fallback to URL '{}'.", serviceId, uri);
}
return uri;
}
}
And this is the second service (baggage-service):
BaggageServiceApplication:
#Configuration
#ComponentScan("com.bootnetflix")
#EnableAutoConfiguration
#EnableEurekaClient
#EnableFeignClients
public class BaggageServiceApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(BaggageServiceApplication.class).run(args);
}
}
BaggageService:
#FeignClient("baggage-service")
public interface BaggageService {
#RequestMapping(method = RequestMethod.GET, value = "/baggage/list/{flight_id}")
List<String> getBaggageListByFlightId(#PathVariable("flight_id") String flightId);
}
BaggageServiceImpl:
#Named
public class BaggageServiceImpl implements BaggageService{
....
#Override
public List<String> getBaggageListByFlightId(String flightId) {
return Arrays.asList("2,3,4");
}
}
When invoking the rest controller of flight integration service I get:
2015-07-22 17:25:40.682 INFO 11308 --- [ XNIO-2 task-3] c.b.f.service.FlightIntegrationService : GetBaggageList from URL: http://X230-Ext_IdanF:62007/baggage/list/4
2015-07-22 17:25:43.953 ERROR 11308 --- [ XNIO-2 task-3] io.undertow.request : UT005023: Exception handling request to /flights/baggage/list/4
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.web.client.HttpClientErrorException: 404 Not Found
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
Any idea ?
Thanks,
ray.
Your code looks backwards to me.
The feign client for the baggage service should be declared in the flight service and the baggage service should have a controller that responds on the URL you map in your baggage service client, you should not implement the interface annotated with #FeignClient.
The setup you have now will not have any controller listening on /baggage/list/{flightId} in the baggage service and no Feign client in flight service - the whole point of Feign is to call methods on an interface instead of manually handling URLs, Spring Cloud takes care of auto-instantiating the interface implementation and will use Eureka for discovery.
Try this (or modify so it fits your real world app):
Flight Service:
FlightIntegrationService.java:
#Component
public class FlightIntegrationService {
#Autowired
BaggageService baggageService;
public String getBaggageListById(String id) {
return baggageService.getBaggageListByFlightId(id);
}
}
BaggageService.java:
#FeignClient("baggage-service")
public interface BaggageService {
#RequestMapping(method = RequestMethod.GET, value = "/baggage/list/{flight_id}")
List<String> getBaggageListByFlightId(#PathVariable("flight_id") String flightId);
}
Baggage Service:
BaggageController.java:
#RestController
public class BaggageController {
#RequestMapping("/baggage/list/{flightId}")
public List<String> getBaggageListByFlightId(#PathVariable String flightId) {
return Arrays.asList("2,3,4");
}
}
Remove BaggageService.java and BaggageServiceImpl.java from the Baggage Service
registryService.getServiceUrl("baggage-service", ... replace with
registryService.getServiceUrl("baggage-service")
make sure that matches the right name
remove the localhost part
or only use the http://local part
It only worked for us if you have just the name of the service listed in eureka dashboard, not both