How to use RabbitTemplate in all Java classes - java

I edited my code into this configuration:
#SpringBootApplication
public class EndPoint {
String QUEUE_PROCESSING_TRANSACTION = "processing-process-queue";
String QUEUE_DATABASE_TRANSACTION = "database-transa-queue";
......
#Bean
public Queue queueProcessingTransaction() {
return new Queue(QUEUE_PROCESSING_TRANSACTION, true);
}
#Bean
public Queue queueDatabaseEventLogs() {
return new Queue(QUEUE_DATABASE_EVENT_LOGS, true);
}
#Bean
public Binding bindingQueueProcessingTransaction() {
return BindingBuilder.bind(new Queu........
}
#Bean
public CachingConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(HOST);
return connectionFactory;
}
#Bean
public AmqpAdmin amqpAdmin(CachingConnectionFactory connectionFactory) {
RabbitAdmin admin = new RabbitAdmin(connectionFactory);
.........
admin.declareQueue(new Queue(QUEUE_PROCESSING_TRANSACTION, true));
return admin;
}
#Bean
public RabbitTemplate processingTemplate(CachingConnectionFactory connectionFactory) {
RabbitTemplate processingTemplate = new RabbitTemplate(connectionFactory);
processingTemplate.setExchange(EXCHANGE_PROCESSING);
.......
return processingTemplate;
}
Previously I used this configuration into Java class which I extend in second Java class in order to access RabbitTemplate.
How I can use RabbitTemplate in Java classes? Probably there is already implemented facility designed in Spring?

You can just add another bean that creates a template starting from the connection factory:
#Bean
public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
return rabbitTemplate;
}
You can autowire it in container managed classes:
#Autowired private RabbitTemplate rabbitTemplate;

You can inject the RabbitTemplate bean in another Spring Bean and use it, so for example you can create a new Spring Bean (component) like the following:
#Component
public class MyComponent {
#Autowired
private RabbitTemplate template;
public void testRabbitTemplate() {
System.out.println(template);
}
}
Remember that the injection works only if you retrieve MyComponent from the Spring Context (i.e. you must not instantiate it using the new keyword).
You can also inject the same RabbitTemplate in your EndPoint class simply adding the following line into the class body:
#Autowired private RabbitTemplate template;

Related

Multiple rabbitmq connections in Spring auto declare "exchange/queue/binding" in per instance, use RabbitAdmin not work

I prepared two connection factory which named "mainConnectionFactory" and "nettyConnectionFactory", the "mainConnectionFactory" is primary, I used it create two rabbitAdmin and exchange..:
#Bean
#Primary
public RabbitAdmin mainRabbitAdmin(#Autowired #Qualifier("mainConnectionFactory") ConnectionFactory connectionFactory) {
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
rabbitAdmin.setExplicitDeclarationsOnly(true);
return rabbitAdmin;
}
#Bean
public RabbitAdmin nettyRabbitAdmin(#Autowired #Qualifier("nettyConnectionFactory") ConnectionFactory connectionFactory) {
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
rabbitAdmin.setExplicitDeclarationsOnly(true);
return rabbitAdmin;
}
#Bean
public DirectExchange policyVerifyExchange(#Qualifier("nettyRabbitAdmin") #Autowired RabbitAdmin nettyRabbitAdmin){
DirectExchange directExchange = new DirectExchange(CHAT_POLICY_EXCHANGE, true, false);
directExchange.setAdminsThatShouldDeclare(nettyRabbitAdmin);
directExchange.setShouldDeclare(true);
return directExchange;
}
#Bean
public Binding policyVerifyBinding(#Autowired #Qualifier("policyVerifyExchange") DirectExchange userExpExchange,
#Autowired #Qualifier("policyVerifyQueue") Queue userExpQueue,
#Qualifier("nettyRabbitAdmin") #Autowired RabbitAdmin nettyRabbitAdmin){
Binding binding = BindingBuilder.bind(userExpQueue).to(userExpExchange).withQueueName();
binding.setAdminsThatShouldDeclare(nettyRabbitAdmin);
binding.setShouldDeclare(true);
return binding;
}
#Bean
public Queue policyVerifyQueue(#Qualifier("nettyRabbitAdmin") #Autowired RabbitAdmin nettyRabbitAdmin){
Queue queue = new Queue(CHAT_POLICY_QUEUE, true);
queue.setAdminsThatShouldDeclare(nettyRabbitAdmin);
queue.setShouldDeclare(true);
return queue;
}
The "nettyRabbitAdmin initialize()" method is always not work, I follow the debug step in "org.springframework.amqp.rabbit.core.RabbitAdmin#afterPropertiesSet" and I found nettyRabbitAdmin "this.connectionFactory.addConnectionListener" never executed.
Something get wrong?

Why the Spring needs a #Bean on Converter and ClassMapper if I'm setting them directly on RabbitTemplate?

I would like to understand why I need to mark the getMessageConverter and classMapper methods with #Bean even setting them directly in my custom RabbiteTemplate.
Without the #Bean and passing them to private, the message not contains the TypeId correctly in the message.
So, with the #Bean and public methods, I had the correct header on the message:
headers: __TypeId__: OrderProducer
Without them, the message contains the incorrect TypeId:
headers: __TypeId__: com.projet.order.message.OrderProducer
What is the default behavior without the classMapper.
See the code:
#Bean(name = "sendCommandOrderCreate")
public RabbitTemplate sendCommandOrderCreate() {
RabbitTemplate rabbitTemplate = createRabbitTemplate(getMessageConverter());
rabbitTemplate.setExchange("order.exchange");
rabbitTemplate.setRoutingKey("order.cmd.create");
return rabbitTemplate;
}
private RabbitTemplate createRabbitTemplate(final MessageConverter messageConverter) {
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(messageConverter);
return rabbitTemplate;
}
#Bean
public Jackson2JsonMessageConverter getMessageConverter() {
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
jackson2JsonMessageConverter.setClassMapper(classMapper());
return jackson2JsonMessageConverter;
}
#Bean
public DefaultClassMapper classMapper() {
DefaultClassMapper classMapper = new DefaultClassMapper();
Map<String, Class<?>> idClassMapping = new HashMap<>();
idClassMapping.put(OrderProducer.class.getSimpleName(), OrderProducer.class);
idClassMapping.put(OrderConsumer.class.getSimpleName(), OrderConsumer.class);
classMapper.setIdClassMapping(idClassMapping);
return classMapper;
}
And how I'm injecting the bean in a Service:
public OrderService(#Qualifier("sendCommandOrderCreate") RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
I found the problem. I resolved using another Mapper: DefaultJackson2JavaTypeMapper.
private DefaultJackson2JavaTypeMapper classMapper() {
DefaultJackson2JavaTypeMapper classMapper = new DefaultJackson2JavaTypeMapper();
Map<String, Class<?>> idClassMapping = new HashMap<>();
idClassMapping.put(OrderProducer.class.getSimpleName(), OrderProducer.class);
idClassMapping.put(OrderConsumer.class.getSimpleName(), OrderConsumer.class);
classMapper.setIdClassMapping(idClassMapping);
return classMapper;
}
I could found this mapper after debugging the code and looking for the different implementations of ClassMapper. The DefaultClassMapper can't provide the behavior that I was expecting and I cant' explain why declare it with #Bean could change his behavior (maybe is a bug on Spring).

Multiple transaction managers NoUniqueBeanDefinitionException

I'm using Spring boot and I defined the spring.datasource.* properties to enable my datasource. If I only use this it works fine. However, I'm now trying to add JMS to my application as well, using the following config:
#Configuration
#EnableJms
public class TriggerQueueConfig {
private Logger logger = LoggerFactory.getLogger(getClass());
#Value("${jms.host:localhost}")
private String host;
#Value("${jms.port:1414}")
private int port;
#Value("${jms.concurrency.min:3}-${jms.concurrency.max:10}")
private String concurrency;
#Value("${jms.manager}")
private String queueManager;
#Value("${jms.cache:100}")
private int cacheSize;
#Bean
public JmsListenerContainerFactory<?> jmsListenerContainerFactory() throws JMSException {
logger.debug("Setting queue concurrency to {} (min-max)", concurrency);
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(cachedConnectionFactory());
factory.setMessageConverter(messageConverter());
factory.setTransactionManager(transactionManager());
factory.setSessionTransacted(true);
factory.setConcurrency(concurrency);
return factory;
}
#Bean(name = "jmsTransactionManager")
public JmsTransactionManager transactionManager() throws JMSException {
JmsTransactionManager transactionManager = new JmsTransactionManager();
transactionManager.setConnectionFactory(cachedConnectionFactory());
return transactionManager;
}
#Bean
#Primary
public ConnectionFactory cachedConnectionFactory() throws JMSException {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(ibmConnectionFactory());
connectionFactory.setSessionCacheSize(cacheSize);
connectionFactory.setCacheConsumers(true);
return connectionFactory;
}
#Bean
public ConnectionFactory ibmConnectionFactory() throws JMSException {
logger.debug("Connecting to queue on {}:{}", host, port);
MQQueueConnectionFactory connectionFactory = new MQQueueConnectionFactory();
connectionFactory.setHostName(host);
connectionFactory.setPort(port);
connectionFactory.setQueueManager(queueManager);
connectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
return connectionFactory;
}
#Bean
public MessageConverter messageConverter() {
MarshallingMessageConverter converter = new MarshallingMessageConverter();
converter.setMarshaller(marshaller());
converter.setUnmarshaller(marshaller());
return converter;
}
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setPackagesToScan("com.example");
return marshaller;
}
}
The JMS listener I created is working fine. However, when I'm trying to persist data using my repository (Spring Data JPA) in a #Transactional method, I'm getting the following exception:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: expected single matching bean but found 2: transactionManager,jmsTransactionManager
This makes sense, because both transactionmanagers are PlatformTransactionManager's. Usually you would put #Primary on top of the bean that should be the default one. However, in this case I'm using Spring boot's autoconfiguration so I can't add the #Primary on it.
An alternative solution would be to provide the name of the transaction manager with each #Transactional annotation (for example #Transactional("transactionManager"), but this would be a lot of work, and it would make more sense to have a default transactionmanager because the JMS transactionmanager is an exceptional case.
Is there an easy way to define the automatically configured transactionmanager to be used by default?
The Spring boot 'magic' is really only this:
#Bean
#ConditionalOnMissingBean(PlatformTransactionManager.class)
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}
in org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration class.
Notice the #ConditionalOnMissingBean annotation - this will get configured only if a bean of type PlatformTransactionManager doesn't exist. So you can override this by creating your own bean with #Primary annotation:
#Bean
#Primary
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}

Mocking config with spring boot isn't picking up property files

I am trying to write a IntegrationFlow test. It goes something like this:
JMS(in) -> (find previous versions in db) -> reduce(in,1...n) -> (to db) -> JMS(out)
So, no suprise: I want to mock the DB calls; they are Dao beans. But, I also want it to pickup other beans through component scan; I will selectively scan all packages except dao.
Create a test config and mock the Daos. No problem
Follow spring boot instructions for testing to get Component scanned beans. No problem
I just want to verify the sequence of steps and the resultant output as the outbound JMS queue would see it. Can someone just help me fill in the blanks?
This CANT be tough! The use of mocks seems to be problematic because plenty of essential fields are final. I am reading everywhere about this and just not coming up with a clear path. I inherited this code BTW
My error:
org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers
Here is my code
#Configuration
#ImportResource("classpath:retry-context.xml")
public class LifecycleConfig {
#Autowired
private MessageProducerSupport inbound;
#Autowired
private MessageHandler outbound;
#Autowired
#Qualifier("reducer")
private GenericTransformer<Collection<ExtendedClaim>,ExtendedClaim> reducer;
#Autowired
#Qualifier("claimIdToPojo")
private GenericTransformer<String,ClaimDomain> toPojo;
#Autowired
#Qualifier("findPreviousVersion")
private GenericTransformer<ExtendedClaim,Collection<ExtendedClaim>> previousVersions;
#Autowired
#Qualifier("saveToDb")
private GenericHandler<ExtendedClaim> toDb;
#Bean
public DirectChannel getChannel() {
return new DirectChannel();
}
#Bean
#Autowired
public StandardIntegrationFlow processClaim() {
return IntegrationFlows.from(inbound).
channel(getChannel()).
transform(previousVersions).
transform(reducer).
handle(ExtendedClaim.class,toDb).
transform(toPojo).
handle(outbound).get();
}
}
Test Config
#Configuration
public class TestConfig extends AbstractClsTest {
#Bean(name = "claimIdToPojo")
public ClaimIdToPojo getClaimIdToPojo() {
return spy(new ClaimIdToPojo());
}
#Bean
public ClaimToId getClaimToIdPojo() {
return spy(new ClaimToId());
}
#Bean(name = "findPreviousVersion")
public FindPreviousVersion getFindPreviousVersion() {
return spy(new FindPreviousVersion());
}
#Bean(name = "reducer")
public Reducer getReducer() {
return spy(new Reducer());
}
#Bean(name = "saveToDb")
public SaveToDb getSaveToDb() {
return spy(new SaveToDb());
}
#Bean
public MessageProducerSupport getInbound() {
MessageProducerSupport mock = mock(MessageProducerSupport.class);
// when(mock.isRunning()).thenReturn(true);
return mock;
}
#Bean
public PaymentDAO getPaymentDao() {
return mock(PaymentDAO.class);
}
#Bean
public ClaimDAO getClaimDao() {
return mock(ClaimDAO.class);
}
#Bean
public MessageHandler getOutbound() {
return new CaptureHandler<ExtendedClaim>();
}
}
Actual test won't load
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {TestConfig.class, LifecycleConfig.class})
public class ClaimLifecycleApplicationTest extends AbstractClsTest {
#Autowired
private MessageHandler outbound;
#Autowired
#Qualifier("reducer")
private GenericTransformer<Collection<ExtendedClaim>,ExtendedClaim> reducer;
#Autowired
#Qualifier("claimIdToPojo")
private GenericTransformer<String,ClaimDomain> toPojo;
#Autowired
#Qualifier("findPreviousVersion")
private GenericTransformer<ExtendedClaim,Collection<ExtendedClaim>> previousVersions;
#Autowired
#Qualifier("saveToDb")
private GenericHandler<ExtendedClaim> toDb;
#Autowired
private DirectChannel defaultChannel;
#Test
public void testFlow() throws Exception {
ExtendedClaim claim = getClaim();
Message<ExtendedClaim> message = MessageBuilder.withPayload(claim).build();
List<ExtendedClaim> previousClaims = Arrays.asList(claim);
defaultChannel.send(message);
verify(previousVersions).transform(claim);
verify(reducer).transform(previousClaims);
verify(toDb).handle(claim, anyMap());
verify(toPojo).transform(claim.getSubmitterClaimId());
verify(outbound);
}
}
There are a lot of domain-specific object, so I can't test it to reproduce or find some other issue with your code.
But I see that you don't use an #EnableIntegration on your #Configurations classes.

Multiple DataSource and JdbcTemplate in Spring Boot (> 1.1.0)

I would like to inject a specific JdbcTemplatein a Spring Boot project. I tried to follow this example for multiple DataSourceconfiguration : http://spring.io/blog/2014/05/27/spring-boot-1-1-0-m2-available-now
My code does compile and run, but only the DataSource with the #Primaryannotation is taken into account, no matter what I put as #Qualifier in the SqlServiceclass. My relevant code is the following :
DatabaseConfig.java:
#Configuration
public class DatabaseConfig {
#Bean(name = "dsSlave")
#ConfigurationProperties(prefix="spring.mysql_slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "dsMaster")
#Primary
#ConfigurationProperties(prefix="spring.mysql_master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "jdbcSlave")
#Autowired
#Qualifier("dsSlave")
public JdbcTemplate slaveJdbcTemplate(DataSource dsSlave) {
return new JdbcTemplate(dsSlave);
}
#Bean(name = "jdbcMaster")
#Autowired
#Qualifier("dsMaster")
public JdbcTemplate masterJdbcTemplate(DataSource dsMaster) {
return new JdbcTemplate(dsMaster);
}
}
And I did a quick service to try it out :
SqlService.java:
#Component
public class SqlService {
#Autowired
#Qualifier("jdbcSlave")
private JdbcTemplate jdbcTemplate;
public String getHelloMessage() {
String host = jdbcTemplate.queryForObject("select ##hostname;", String.class);
System.out.println(host);
return "Hello";
}
}
It should looks like this:
#Bean(name = "jdbcSlave")
#Autowired
public JdbcTemplate slaveJdbcTemplate(#Qualifier("dsSlave") DataSource dsSlave) {
return new JdbcTemplate(dsSlave);
}
Try to move #Qualifier annotation to the parameter on your #Bean methods for JdbcTemplate.
I guess, when you remove #Primary you end up with error, where more than one appropriate beans are presented

Categories