Is it a good idea to declare an injector and pass it as a state parameter when integrating typed Akka Actor with Spring - spring

I could not find a neat way to integrate Spring with typed Actors.
Instead of using extensions I had declared an injector service
import akka.actor.typed.ActorSystem;
import com.akka.demo.a010.ASimpleSpringService;
import com.akka.demo.a010.AkkaSimpleBehaviour;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
#Data
public class ActorInjector {
#Autowired
private ASimpleSpringService aSimpleSpringService;
#Autowired
private ActorInjector _self;
public ActorSystem<AkkaSimpleBehaviour.Command> createAkkaSimpleBehaviour(String actorName) {
return ActorSystem.create(AkkaSimpleBehaviour.create(_self), actorName);
}
}
This service autowires itself and pass that reference to a simple actor.
My Actor definition is as follows.
import akka.actor.typed.ActorRef;
import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.AbstractBehavior;
import akka.actor.typed.javadsl.ActorContext;
import akka.actor.typed.javadsl.Behaviors;
import akka.actor.typed.javadsl.Receive;
import com.akka.demo.a010.config.ActorInjector;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
#Slf4j
public class AkkaSimpleBehaviour extends AbstractBehavior<AkkaSimpleBehaviour.Command> {
private final List<String> messages = new ArrayList<>();
private ActorRef<List<String>> sender;
private ActorInjector actorInjector;
public interface Command extends Serializable {
}
#AllArgsConstructor
public static class TellMeSomething implements Command {
private static final long serialVersionUID = -7796709831949054890L;
#Getter
private final String message;
}
#AllArgsConstructor
public static class CollectTheResults implements Command {
private static final long serialVersionUID = 1643210899551075153L;
#Getter
private final ActorRef<List<String>> sender;
}
private AkkaSimpleBehaviour(ActorContext<Command> context, ActorInjector actorInjector) {
super(context);
this.actorInjector = actorInjector;
}
public static Behavior<Command> create(ActorInjector actorInjector) {
return Behaviors.setup(ctx -> new AkkaSimpleBehaviour(ctx,actorInjector));
}
#Override
public Receive<Command> createReceive() {
return newReceiveBuilder().onMessage(TellMeSomething.class, message -> {
messages.add(message.getMessage());
actorInjector.getASimpleSpringService().logSomething("*-*-*-*-*-*-*-*-*-*");
return Behaviors.same();
}).onMessage(CollectTheResults.class, message -> {
this.sender = message.getSender();
if (messages.size() == 4) {
this.sender.tell(messages);
}
return Behaviors.same();
}).build();
}
}
After passing the injector service I can get my autowired dependencies from that service like :
actorInjector.getASimpleSpringService().logSomething("*-*-*-*-*-*-*-*-*-*");
My ASimpleService is just a dummy service which logs an output.
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
#Service
#Slf4j
public class ASimpleSpringService {
public void logSomething(String message){
log.info(message);
}
}
Then in a simple RestController I am using the system as follows:
import akka.actor.typed.ActorSystem;
import akka.actor.typed.javadsl.AskPattern;
import com.akka.demo.a010.config.ActorInjector;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
#RestController
#Slf4j
public class MyRestController {
#Autowired
private ActorInjector actorInjector;
#GetMapping("/hello-akka")
public void greetings() {
ActorSystem<AkkaSimpleBehaviour.Command> exampleActor = actorInjector.createAkkaSimpleBehaviour("anActor");
exampleActor.tell(new AkkaSimpleBehaviour.TellMeSomething("hello"));
exampleActor.tell(new AkkaSimpleBehaviour.TellMeSomething("Who are you"));
exampleActor.tell(new AkkaSimpleBehaviour.TellMeSomething("create a child"));
exampleActor.tell(new AkkaSimpleBehaviour.TellMeSomething("Here is some message"));
CompletionStage<List<String>> result = AskPattern.ask(exampleActor, AkkaSimpleBehaviour.CollectTheResults::new, Duration.ofSeconds(10), exampleActor.scheduler());
result.whenComplete((reply, failure) -> {
if(reply != null){
log.info("The system responds in time");
} else {
log.error("The system does not respond in time");
exampleActor.terminate();
throw new RuntimeException("The system does not respond in time");
}
exampleActor.terminate();
});
try{
List<String> messages = result.toCompletableFuture().get();
messages.forEach(log::info);
} catch (InterruptedException | ExecutionException e){
e.printStackTrace();
}
}
}
My question is : I am planning to put all of my services into my ActorInjector service and these services will be injected into my actors. I am not familiar with Akka states and its side-effects however I know that it may be a bad idea to store all of these singleton services as actor states. Is it a good idea to store these services as an actor parameter ? What kind of side effects can I experience by doing this way? Can you point me the way?

Related

Detecting the insertion of data into the table and calling the method

I have an issue with detecting adding a new row to the table. I want to trigger a method from some service (Spring boot) when somebody executes an insert query on the database (Postgres)
Somebody told me I can use #Scheduled annotation and check if something was added using a repository. I have to make some changes instantly (by using another method). The scheduled method should run every 5 seconds to do this instantly. Of course, this is a really bad idea because it will kill the database someday and it's not efficient.
How can I do this better?
You can write concrete implementer of org.hibernate.integrator.spi.Integrator. and give it to hibernate.integrator_provider
From ServiceRegistry we can get EventListenerRegistry and then append listener of type EventType.POST_INSERT. More events here.
Main Reference Hibernate Integrator Ref
As per the query, I have also added how to call the service method from the listener class.
Here is how I have done it:
package com.example.samplejdbctemplatecall;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.boot.Metadata;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.service.spi.EventListenerGroup;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.event.spi.PostInsertEvent;
import org.hibernate.event.spi.PostInsertEventListener;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.jpa.boot.spi.IntegratorProvider;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
#RequestMapping(path = "/entity-listener")
#RestController
public class SampleLogController {
private final SampleLogRepository sampleLogRepository;
private final SampleLogEntries sampleLogEntiries;
#Autowired
public SampleLogController(SampleLogRepository sampleLogRepository, SampleLogEntries sampleLogEntiries) {
this.sampleLogRepository = sampleLogRepository;
this.sampleLogEntiries = sampleLogEntiries;
}
// This is usually post method but for test purpose creating new log with uuid random and inserting
#GetMapping(path = "insert")
public SampleLog insertNewEntry() {
final String uuid = UUID.randomUUID().toString();
final SampleLog sampleLog = new SampleLog();
sampleLog.setMessage(uuid);
return sampleLogRepository.save(sampleLog);
}
#GetMapping(path = "list-recent-inserts")
public Map<Long, String> entries() {
return sampleLogEntiries.data();
}
}
#Slf4j
#Component
class HibernateConfig implements HibernatePropertiesCustomizer {
private final JpaEventListenerIntegrator jpaEventListenerIntegrator;
#Autowired
HibernateConfig(JpaEventListenerIntegrator jpaEventListenerIntegrator) {
this.jpaEventListenerIntegrator = jpaEventListenerIntegrator;
}
#Override
public void customize(Map<String, Object> hibernateProperties) {
log.warn("Called hibernate configuration");
hibernateProperties.put("hibernate.integrator_provider",
(IntegratorProvider) () -> Collections.singletonList(jpaEventListenerIntegrator));
}
}
#Configuration
class SampleConfiguration {
#Bean
SampleLogEntries sampleEntries() {
return new SampleLogEntries();
}
}
class SampleLogEntries {
private final ConcurrentMap<Long, String> map = new ConcurrentHashMap<>();
public void add(SampleLog sampleLog) {
this.map.put(sampleLog.getId(), sampleLog.getMessage());
}
public Map<Long, String> data() {
return Collections.unmodifiableMap(this.map);
}
}
#Repository
interface SampleLogRepository extends CrudRepository<SampleLog, Long> {
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
class SampleLog {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String message;
}
#Service
#Slf4j
class JpaEventListenerIntegrator implements Integrator, PostInsertEventListener {
private final SampleLogEntries sampleLogEntiries;
#Autowired
JpaEventListenerIntegrator(SampleLogEntries sampleLogEntiries) {
this.sampleLogEntiries = sampleLogEntiries;
}
#Override
public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);
eventListenerRegistry
.appendListeners(EventType.POST_INSERT, this);
}
#Override
public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);
EventListenerGroup<PostInsertEventListener> eventListenerGroup = eventListenerRegistry
.getEventListenerGroup(EventType.POST_INSERT);
log.info("listener attached were: " + eventListenerGroup.getClass().getSimpleName());
log.error("disintegrate : " + getClass().getCanonicalName());
eventListenerGroup.clearListeners();
}
#Override
public void onPostInsert(PostInsertEvent event) {
log.info("Inserted : " + event.getEntity());
final Object entity = event.getEntity();
if (entity instanceof SampleLog) {
sampleLogEntiries.add((SampleLog) entity);
}
}
#Override
public boolean requiresPostCommitHanding(EntityPersister persister) {
return false;
}
}
The answer from #silentsudo is the best one if you only ever use JPA to update the table in question, and if you only have one process updating it.
The issue is that since you are being notified via the JPA interceptor, you won't be notified of any updates that happen outside of your JAP repository.
If you need these other notifications, then you can use Postgres' LISTEN/NOTIFY without polling by using an alternate postgresql JDBC driver, pgjdbc-ng, which implements async notifications.
With this method, you create a trigger in the database to send the notification, so you will be notified of other's updates as well. See https://www.openmakesoftware.com/postgresql-listen-notify-events-example

How to us a constructor with parameters in a method used by Spring Boot's #RestController annotation to create a request handler

I bought this new book to try to learn Spring Boot quickly. It started out well, and I easily created a REST API. But then we added CrudRepository, and I'm seeing issues with the code as described in the book. Also, there is no code available to download because the author took it down from Oreily's git repo in order to fix some things...
The issue is that if I try to build the code as the book describes (without a default constructor) I get a Java error complaining that there is no default constructor. If I add a default constructor, it builds, but Spring uses it instead of the new constructor, that requires a parameter to be passed. So when I actually call the API, like if I call the /coffees endpoint, I get a java.lang.NullPointerException: null
So how is Spring supposed to know which constructor to use, and how could it pass in values for this parameter?
Here is the controller:
package com.bw.restdemo;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/coffees")
class RestAPIDemoController {
private final CoffeeRepository coffeeRepository;
public RestAPIDemoController(CoffeeRepository coffeeRepository) {
this.coffeeRepository = coffeeRepository;
this.coffeeRepository.saveAll(List.of(
new Coffee("Cafe Cereza"),
new Coffee("Freedom Fuel"),
new Coffee("Cold Brew"),
new Coffee("Sumatra")
));
}
public RestAPIDemoController() {
this.coffeeRepository = null;
};
//#RequestMapping(value = "/coffees", method = RequestMethod.GET)
#GetMapping
Iterable<Coffee> getCoffees() {
return coffeeRepository.findAll();
}
#GetMapping("/{id}")
Optional<Coffee> getCoffeeById(#PathVariable String id) {
return coffeeRepository.findById(id);
}
#PostMapping
Coffee postCoffee(#RequestBody Coffee coffee) {
return coffeeRepository.save(coffee);
}
#PutMapping("/{id}")
ResponseEntity<Coffee> putCoffee(#PathVariable String id, #RequestBody Coffee coffee) {
return (!coffeeRepository.existsById(id))
? new ResponseEntity<>(coffeeRepository.save(coffee), HttpStatus.CREATED)
: new ResponseEntity<>(coffeeRepository.save(coffee), HttpStatus.OK);
}
#DeleteMapping("/{id}")
void deleteCoffee(#PathVariable String id) {
coffeeRepository.deleteById(id);
}
}
Here is where I'm defining the interface:
package com.bw.restdemo;
import org.springframework.data.repository.CrudRepository;
interface CoffeeRepository extends CrudRepository<Coffee, String> {
}
And here's the main class -- apologies for the class stuffed at the bottom.
package com.bw.restdemo;
import java.util.UUID;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class RestDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RestDemoApplication.class, args);
}
}
#Entity
class Coffee {
#Id
private String id;
private String name;
public Coffee(String id, String name) {
this.id = id;
this.name = name;
}
public void setId(String id) {
this.id = id;
}
public Coffee(String name) {
this(UUID.randomUUID().toString(), name);
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
CoffeeRepository interface is missing #Repository Annotation.
Update:
Add #Repository Annotation at CoffeeRepository
Remove the default constructor from RestAPIDemoController.
package com.bw.restdemo;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
interface CoffeeRepository extends CrudRepository<Coffee, String> {
}
Explanation
In spring framework, #Component annotation marks a java class as a bean so the component-scanning mechanism can pick it up and pull it into the application context. As #Repository serves as a specialization of #Component , it also enable annotated classes to be discovered and registered with application context.
More at HowToDoInJava - #Repository annotation in Spring Boot

ApplicationContext failed to load

I am new to testing. I have created a test case to perform a test on rest API using JUNIT Mockito. In my code I have created a test on method itemGetByid(), but when I run the test I get ApplocationContext error message. I don't know where I am going wrong.
Item Controller Test class
package com.example.demo.controller;
import com.example.demo.entities.Item;
import com.example.demo.entities.User;
import com.example.demo.service.ItemService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import java.util.*;
import static org.junit.Assert.*;
#RunWith(SpringJUnit4ClassRunner.class)
#WebMvcTest(ItemController.class)
public class ItemControllerTest {
#Autowired
MockMvc mockmvc;
#Mock
ItemService itemService;
#InjectMocks
ItemController itemController;
#Test
public void itemGetById() {
Item item = new Item();
Mockito.when(itemController.getById(10L)).thenReturn(item);
Item i = itemController.getById(10L);
assertEquals(i, item);
}
}
Item entity class
package com.example.demo.entities;
import lombok.Data;
import javax.persistence.*;
#Data
#Entity
#Table(name = "Item")
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long itemId;
private Long customerId;
private String itemName;
private int itemPrice;
}
Item controller
package com.example.demo.controller;
import com.example.demo.entities.Item;
import com.example.demo.entities.User;
import com.example.demo.service.ItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
#RestController
#RequestMapping("item")
public class ItemController {
private ItemService itemService;
#Autowired
public ItemController(ItemService itemService) {
this.itemService = itemService;
}
#PostMapping("/post")
public Item post(#RequestBody Item item) {
return itemService.post(item);
}
#GetMapping
public Iterable<Item> getAll() {
return itemService.get();
}
#GetMapping("/get")
public Item getById(Long id) {
return itemService.getItem(id);
}
#DeleteMapping("/delete")
public void deleteAll() {
itemService.deleteAll();
}
}
Item Service
package com.example.demo.service;
import com.example.demo.entities.Item;
import com.example.demo.userepository.ItemRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;
#Service
public class ItemService {
ItemRepository itemRepository;
#Autowired
public ItemService(ItemRepository itemRepository) {
this.itemRepository = itemRepository;
}
public Item post(#RequestBody Item item) {
return itemRepository.save(item);
}
public Iterable<Item> get() {
return itemRepository.findAll();
}
public void deleteAll() {
itemRepository.deleteAll();
}
public Item getItem(Long id) {
return itemRepository.findById(id).get();
}
}
Item Repository
package com.example.demo.userepository;
import com.example.demo.entities.Item;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface ItemRepository extends CrudRepository<Item, Long> {
}
For starters, you are using SpringJUnit4ClassRunner. Try the MockitoJunitRunner.
An ItemController instance is never created in the test, so that needs to be fixed as well.
The line below will fail because itemController is not a mock.
**Mockito.when(itemController.getById(10L)).thenReturn(item);**
If itemController is converted to a mock, with the when statement, the test method doesnt validate anything because the test tells Mockito to return the item in the when statement.
Assuming the intent is to validate the ItemController getById method, the Mockito.when statement needs to describe the call to the Mock ItemService.

Can't integrate Spring Boot Data with Neo4j

I just started with Spring Boot Data Neo4j and trying to finish the movie tutorial. I got this ERROR but not really sure how to debug. Error summary : (1) Error creating bean (2)Could not autowire field. Any help, thank you.
My files structure as follows :
SpringBootApplication.java
package com.test.springdataneothree;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.test.services.MovieService;
#EnableAutoConfiguration
#SpringBootApplication
public class SpringdataneothreeApplication implements CommandLineRunner {
#Autowired
MovieService movieService;
public static void main(String[] args) {
SpringApplication.run(SpringdataneothreeApplication.class, args);
}
#Override
public void run(String... arg0) throws Exception {
System.out.println("Main Spring Boot Class");
movieService.countMovie();
}
}
Movie.java
package com.test.movie;
import org.neo4j.ogm.annotation.GraphId;
import org.neo4j.ogm.annotation.NodeEntity;
#NodeEntity(label ="Movie")
public class Movie {
#GraphId
private Long id;
String title;
int released;
String tagline;
public Movie() { }
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getReleased() {
return released;
}
public void setReleased(int released) {
this.released = released;
}
public String getTagline() {
return tagline;
}
public void setTagline(String tagline) {
this.tagline = tagline;
}
}
MovieService.java
package com.test.services;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.test.repositories.MovieRepository;
#Service("MovieService")
public class MovieService {
#Autowired
MovieRepository movieRepository;
public void countMovie() {
movieRepository.count();
}
}
MovieRepository.java
package com.test.repositories;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.repository.GraphRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.test.movie.Movie;
#Repository
public interface MovieRepository extends GraphRepository<Movie> {
Movie findById(long id);
Movie findByTitle(#Param("title") String title);
#Query("MATCH (m:Movie) WHERE m.title =~ ('(?i).*'+{title}+'.*') RETURN m")
Collection<Movie> findByTitleContaining(#Param("title") String title);
#Query("MATCH (m:Movie)<-[:ACTED_IN]-(a:Person) RETURN m.title as movie, collect(a.name) as cast LIMIT {limit}")
List<Map<String,Object>> graph(#Param("limit") int limit);
}
MyNeo4jConfiguration.java
package com.test.configuration;
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.config.Neo4jConfiguration;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#EnableTransactionManagement
#EnableScheduling
#ComponentScan(basePackages = {"com.test"})
#Configuration
#EnableNeo4jRepositories(basePackages = "com.test.repositories")
public class MyNeo4jConfiguration extends Neo4jConfiguration {
#Override
public SessionFactory getSessionFactory() {
return new SessionFactory("com.test.movie");
}
}
You have a problem with auto wiring: the annotation on the MovieService is wrong, it should be:
#Service("movieService")
or just
#Service
You are trying to inject a bean with the capitalised name, and the context can't find it (the injection is done by name). Another solution is to use the #Qualifier annotation on the auto wired field, like
#Autowired
#Qualifier("MovieService")
MovieService movieService;

Method with #JmsListener not executed when setting custom message converter

Hy,
Having these three classes I have found a problem:
package xpadro.spring.jms;
import javax.jms.ConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.converter.MarshallingMessageConverter;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
#SpringBootApplication
public class JmsJavaconfigApplication {
public static void main(String[] args) {
SpringApplication.run(JmsJavaconfigApplication.class, args);
}
#Bean
public Jaxb2Marshaller jaxb2Marshaller(){
Jaxb2Marshaller jaxb = new Jaxb2Marshaller();
Class array[] = new Class[1];
array[0]= xpadro.spring.jms.model.Order.class;
jaxb.setClassesToBeBound(array);
return jaxb;
}
#Bean
#Autowired
public MarshallingMessageConverter marshallingMessageConverter( Jaxb2Marshaller marshaller){
MarshallingMessageConverter mc = new MarshallingMessageConverter();
mc.setMarshaller(marshaller);
mc.setUnmarshaller(marshaller);
return mc;
}
#Bean
#Autowired
public JmsTemplate init(MarshallingMessageConverter messageConverter,ConnectionFactory connectionFactory){
JmsTemplate template = new JmsTemplate();
template.setMessageConverter(messageConverter);
template.setConnectionFactory(connectionFactory);
return template;
}
}
Next, the model:
package xpadro.spring.jms.model;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "order")
#XmlAccessorType(XmlAccessType.FIELD)
public class Order implements Serializable {
private static final long serialVersionUID = -797586847427389162L;
#XmlElement(required = true)
private String id="";
public Order() {
}
public Order(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
Next, the listener:
package xpadro.spring.jms.listener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import xpadro.spring.jms.model.Order;
import xpadro.spring.jms.service.StoreService;
#Component
public class SimpleListener {
private final StoreService storeService;
#Autowired
public SimpleListener(StoreService storeService) {
this.storeService = storeService;
}
#JmsListener(destination = "simple.queue")
public void receiveOrder(Order order) {
storeService.registerOrder(order);
}
}
The class that sends messages:
package xpadro.spring.jms.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;
import xpadro.spring.jms.model.Order;
#Service
public class ClientServiceImpl implements ClientService {
private static final String SIMPLE_QUEUE = "simple.queue";
private static final String IN_QUEUE = "in.queue";
private final JmsTemplate jmsTemplate;
#Autowired
public ClientServiceImpl(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
#Override
public void addOrder(Order order) {
//MessageRegistered with a bean
jmsTemplate.convertAndSend(SIMPLE_QUEUE, order);
}
#Override
public void registerOrder(Order order) {
//MessageRegistered with a bean
jmsTemplate.convertAndSend(IN_QUEUE, order);
}
}
The point is that listener(method receiveOrder()) is not executed when I set a custom message converter in the class JmsJavaconfigApplication. When I dont set it, spring boot sets a SimpleMessageConverter and the listener is executed.
Any wrong or missed configuration?
Thanks
You need to provide an marshalling converter to the #JmsListener to unmarshall the message, by configuring the listener container factory see the documentation.

Resources