I have a SpringBoot application. I have a dedicated server, where I shall read data by the HTTP GET requests. I configured the http-outbound-config.xml file for Spring Integration module.
When I run the following code, everything is fine:
http-outbound-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/http https://www.springframework.org/schema/integration/http/spring-integration-http.xsd">
<int:channel id="requestChannel"/>
<int:channel id="replyChannel">
<int:queue capacity='10'/>
</int:channel>
<int-http:outbound-gateway id="outboundGateway"
request-channel="requestChannel"
url="http://server/API.jsp?id=1"
http-method="GET"
expected-response-type="java.lang.String"
charset="UTF-8"
reply-channel="replyChannel"/>
</beans>
Main Application Class:
package test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
import ru.eco.products.waste.egr.Integration;
#SpringBootApplication
#ImportResource("/META-INF/spring/integration/http-outbound-config.xml")
public class Application {
public static void main(String[] args) {
Integration integration = new Integration();
integration.start();
SpringApplication.run(WasteWebClientApplication.class,
args
);
}
}
Integration class:
package test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
#Component
#Configuration
public class Integration {
public void start() {
ClassPathXmlApplicationContext
context = new ClassPathXmlApplicationContext("classpath:META-INF/spring/integration/http-outbound-config.xml");
context.start();
MessageChannel requestChannel = context.getBean("requestChannel", MessageChannel.class);
PollableChannel replyChannel = context.getBean("replyChannel", PollableChannel.class);
Message<?> message = MessageBuilder.withPayload("").build();
requestChannel.send(message);
Message<?> receivedMsg = replyChannel.receive();
System.out.println("RESULT IS : " + receivedMsg.getPayload());
}
}
BUT, when I try to Autowire MessageChannel and PollableChannel, I receive a null pointer exception.
Integration class(not working example):
package test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
#Component
#Configuration
public class Integration {
#Autowired
#Qualifier("requestChannel")
MessageChannel requestChannel;
#Autowired
#Qualifier("replyChannel")
PollableChannel replyChannel;
public void start() {
Message<?> message = MessageBuilder.withPayload("").build();
requestChannel.send(message);
Message<?> receivedMsg = replyChannel.receive();
System.out.println("RESULT IS : " + receivedMsg.getPayload());
}
}
Question 1: Why Autowiring is not working?
Question 2: What is the best way to get data from dedicated server and save it into DB? Such config is ok? I will create a model class for the response and after will save it into DB via JPA.
Question 3: I need reading from server works in Async mode. How can I implement it in Spring Boot? So the main idea here is that I will receive a POST method from the UI, and will launch the integration with web-service. After integration will be finished, I need to notify user.
Question 4: Maybe Camel will be the best solution here?
Stack: Java 11, Spring Boot, thymeleaf + bootstrap.
Thank you for the answer in advance.
Since you do new Integration();, you definitely not going to have dependency injection since inversion of control container is not involved. Although it is fully not clear why do you need that new ClassPathXmlApplicationContext("classpath:META-INF/spring/integration/http-outbound-config.xml") if you already do Spring Boot and that proper #ImportResource.
The best way as you already do with the #ImportResource for that Spring Integration XML config. Then you need to get access to the ApplicationContext of the SpringApplication.run() and getBean(Integration.class) to call your start() method. However you fully need to forget about new Integratio(). Spring is going to manage a bean for that Integration and then dependency injection is going to work.
Async mode can be achieved with an ExecutorChannel in Spring Integration. So, when you send a message to this channel, the logic is going to be processed on a different thread. However it is not clear why you state such a requirement since you still going to block via that replyChannel.receive()... Although this should be addressed in a separate SO thread.
Camel VS Spring Integration question is out of StackOverflow policies.
Thymeleaf and Bootstrap are misleading in the context of this question.
Please, consider to learn how to ask properly here on SO: https://stackoverflow.com/help/how-to-ask
Also read some docs about dependency injection and inversion of control: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core
Related
I am having trouble getting messages from a locally run ActiveMQ. I can produce them onto the queue and my PC also is registered as producer. However, another Spring App on the machine should be configured as a listener. So far it is not working. ActiveMQ is listening on the default ports.
My JMS config for the sender:
package at.dkepr.queueservice;
import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.jms.Queue;
#Configuration
public class JmsConfig {
#Bean
public Queue queue(){
return new ActiveMQQueue("indexing-queue");
}
}
And this is the consumer:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import at.dkepr.entity.UserSearchEntity;
#Component
#EnableJms
public class JmsConsumer {
private final Logger logger = LoggerFactory.getLogger(JmsConsumer.class);
#JmsListener(destination = "indexing-queue", containerFactory = "jmsListenerContainerFactory")
public void receive(UserSearchEntity user){
logger.info(user.getEmail());
}
}
In the application.propertiers I have added the necessary properties:
spring.activemq.broker-url=tcp://localhost:61616
spring.activemq.user=admin
spring.activemq.password=admin
Also the UserSearchEntity implements Serializable.
To the best of my knowledge for this setup I should not even need a config for the consumer. Never the less, I added one.
import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
#Configuration
#EnableJms
public class ConsumerConfig {
#Value("${spring.activemq.broker-url}")
private String brokerUrl;
#Bean
public ActiveMQConnectionFactory activeMQConnectionFactory() {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
activeMQConnectionFactory.setBrokerURL(brokerUrl);
return activeMQConnectionFactory;
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(activeMQConnectionFactory());
factory.setConcurrency("1-3");
return factory;
}
}
I am not getting any error logs. Unfortunately, it is simply doing nothing.
This is a screenshot from the ActiveMQ web console with the enqueued messages:
My consuming application was running when I took this screenshot, but the broker clearly does not recognize it since the "Number of Consumers" is 0.
Edit:
I just tried adding the Listener to the same Spring Application where the Producer is. Surprinsingly, the Listener connected fine. It seems like the problem lies in the different Spring Applications. However, i used the same application.properties for both Spring Apps. The Config File is the same too.
To everyone having the same problem:
For me it was a simple problem with folder structure. For some reason the Application.java for the consumer service was in a subfolder. After i moved the Application.java one folder up, the connection to the ActiveMQ worked.
I'm working on a Spring Boot project. Implementing Back-End code with the data, I've got an error.
Before working on the security, that is, when I've just done with the MemberRepository, MemberService, and MemberController, it worked well. After I worked on the security, that kind of error occurs.
I'm using IntelliJ as the IDE, and the methods were MySQL, Java, Spring Boot, Spring Security, and Maven. The OS is Mac.
This is a part of MemberRepository.java code:
package com.springboot.reserving.member;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.context.annotation.Bean;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
#Repository
public interface MemberRepository extends CrudRepository<Member, Long> { ... }
This is a part of MemberService.java code:
package com.springboot.reserving.member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
#Service
public class MemberService {
#Autowired
MemberRepository memberRepository;
...
}
This is CustomUserDetailService.java code:
package com.springboot.reserving.member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Optional;
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
MemberRepository memberRepo;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return Optional.ofNullable(memberRepo.read(username))
.filter(m -> m != null)
.map(m -> new SecurityMember(m)).get();
}
}
The error message was:
Description:
Field memberRepo in com.springboot.reserving.member.CustomUserDetailsService required a bean of type 'com.springboot.reserving.member.MemberRepository' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.springboot.reserving.member.MemberRepository' in your configuration.
What should I do to fix this error?
One possible reason is that spring doesn't create Spring Data Repository out of the interface.
In a nutshell, spring data project generates a "proxy" in runtime - an implementation of the interface that will contain all the required methods for working with the database.
In order to make is possible you should enable this proxy generation for you DAOs:
This can be done with:
#EnableJpaRepositories(basePackages = "com.springboot.reserving.member")
So make sure you have this annotation on spring boot application class.
I'm learning Spring as it seems to be a very powerful framework. I've already did many getting started guides and now I'm trying with this tutorial. In it, all classes are put in the same package, but to make it more interesting I tried using different packages according to the class (entity, controller, etc.). I was about to test it before the Testing a REST Service section but got an error building the application. This is how my project is structured:
The only difference with the classes in the tutorial is the marked ServletInitializer which comes with the initializr utility (actually I used the one that comes with STS, but it's the same). As far as I understand, it has nothing to do with the problem so the content of this class is irrelevant.
Another minor difference with the tutorial is that the Application class here is called RestServicesApplication but the content is the same.
When I try to build the application (using Gradle's bootRun instead of Maven) I got the following error message:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method init in com.example.restservices.RestServicesApplication required a bean of type 'com.example.repository.AccountRepository' that could not be found.
Action:
Consider defining a bean of type 'com.example.repository.AccountRepository' in your configuration.
:bootRun FAILED
So I tried to annotate AccountRepository with #Bean but it gives me a compilation error saying that the annotation is disallowed for that location. Next I tried with the #Component annotation (also on BookmarkRepository) and adding #ComponentScan("com.example") in RestServicesApplication. After that the error remains but the message changed to
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.example.controller.BookmarkRestController required a bean of type 'com.example.repository.BookmarkRepository' that could not be found.
Action:
Consider defining a bean of type 'com.example.repository.BookmarkRepository' in your configuration.
:bootRun FAILED
I added #Component annotation to BookmarkRestController but the same error message remains. What am I missing here?
Thanks in advance for your answers.
Edit #1
The classes involved in the problem are the following (copied from my project, not the ones in the tutorial, although the differences are minimal):
RestServicesApplication
package com.example.restservices;
import java.util.Arrays;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import com.example.entity.Account;
import com.example.entity.Bookmark;
import com.example.repository.AccountRepository;
import com.example.repository.BookmarkRepository;
#SpringBootApplication
#ComponentScan("com.example")
public class RestServicesApplication {
public static void main(final String[] args) {
SpringApplication.run(RestServicesApplication.class, args);
}
#Bean
CommandLineRunner init(final AccountRepository accountRepository,
final BookmarkRepository bookmarkRepository) {
return (evt) -> Arrays.asList(
"jhoeller,dsyer,pwebb,ogierke,rwinch,mfisher,mpollack,jlong".split(","))
.forEach(
a -> {
final Account account = accountRepository.save(new Account(a,
"password"));
bookmarkRepository.save(new Bookmark(account,
"http://bookmark.com/1/" + a, "A description"));
bookmarkRepository.save(new Bookmark(account,
"http://bookmark.com/2/" + a, "A description"));
});
}
}
BookmarkRestController
package com.example.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.repository.AccountRepository;
import com.example.repository.BookmarkRepository;
#RestController
#RequestMapping("/{userId}/bookmarks")
public class BookmarkRestController {
private final BookmarkRepository bookmarkRepository;
private final AccountRepository accountRepository;
#Autowired
public BookmarkRestController(final BookmarkRepository bookmarkRepository,
final AccountRepository accountRepository) {
this.bookmarkRepository = bookmarkRepository;
this.accountRepository = accountRepository;
}
// #RequestMapping methods...
}
AccountRepository
package com.example.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import com.example.entity.Account;
#Component
public interface AccountRepository extends JpaRepository<Account, Long> {
Optional<Account> findByUsername(String username);
}
BookmarkRepository
package com.example.repository;
import java.util.Collection;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import com.example.entity.Bookmark;
#Component
public interface BookmarkRepository extends JpaRepository<Bookmark, Long> {
Collection<Bookmark> findByAccountUsername(String username);
}
Note: I added the imports so you can see where the classes and annotations come from
Edit #2
I tried another thing: I refactored my Project to fit the tutorial and I put everithing in the same package (com.example.bookmarks) and removed the extra annotations. The Project compiles but when I run the Project I get a 404 HTTP status when trying to accesss a REST service. I'm still interested in make it run with my original structure but I want to let you know that this refactoring makes the Project work.
To have Spring create a bean that implements JpaRepository interface, you need to use Spring JPA namespace and activate the repository support using the appropriate element. In xml:
<jpa:repositories base-package="com.example.repository" />
In annotation:
#EnableJpaRepositories
See this docs
This scans all packages below com.example.repository for interfaces extending JpaRepository and creates a Spring bean for it that is backed by an implementation of SimpleJpaRepository.
I think you have to create package again. You packing looking not right. Recreate your package
I need a component that:
Receives a message, enacts a transformation on both the payload and header of the message (so far it acts like a transformer).
Then, based on the values passed in on the header route to an appropriate channel (acting like a header-router).
I'd like to configure this purely using annotations in java rather than XML, but I'll absolutely take what I can get in that regard. If someone knows the XML solution please pass it along.
If it helps, my use case is that I want the transformation that the transformer enacts on a message payload to be dependent on a custom loaded header value. I also want the channel that is used to sent the message from the transformer to be dependent on the header value.
I am aware of the solution that involves multiple transformers, one per transformation type as defined in the header value, and then a router. I'm trying to avoid this solution and only use at most a single router and single transformer.
Thank you for your help.
In Spring Integration channels act as any other beans. You could use a service activator to invoke a method on any bean. That bean could have the required channels injected. You could use the #Qualifier annotation to select, which channel should be injected, or just autowire a Map<String, MessageChannel> wich would get indexed by the bean name of the channel. It could pass transformed messages to those channels.
A Spring Boot app:
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
import org.springframework.integration.config.EnableIntegration;
#SpringBootApplication
#EnableIntegration
#ImportResource("classpath:int.xml")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
The gateway interface:
package demo;
public interface MyGateway {
public void send(Object o);
}
The service to be invoked:
package demo;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.stereotype.Service;
#Service
public class MyService {
private final Map<String, MessageChannel> channels;
#Autowired
public MyService(Map<String, MessageChannel> channels) {
super();
this.channels = channels;
}
public void transformAndRoute(Message<?> in) {
// do your business logic here
Message<?> out = MessageBuilder.fromMessage(in).build();
// if(something)...
channels.get("fooChannel").send(out);
}
}
Integration config:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
<int:gateway id="myGateway" service-interface="demo.MyGateway"
default-request-channel="inChannel" />
<int:service-activator input-channel="inChannel"
ref="myService" method="transformAndRoute" />
<int:channel id="inChannel" />
<int:logging-channel-adapter id="fooChannel" level="INFO" log-full-message="true"/>
</beans>
And finally a simple integration test:
package demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=DemoApplication.class)
public class MyGatewayIT {
#Autowired
MyGateway myGateway;
#Test
public void test() {
myGateway.send(new Object());
// do your assertions here
}
}
Output:
14:17:19.733 [main] DEBUG o.s.i.handler.LoggingHandler - org.springframework.integration.handler.LoggingHandler#0 received message: GenericMessage [payload=java.lang.Object#6f2cfcc2, headers={id=6791344c-07b4-d420-0d17-e2344f4bf15b, timestamp=1437826639733}]
BUT
The main benefit of developing message based systems is that you create your application out of small, loosely coupled components that are easy to test, reuse and change. Creating a component that plays two roles in a process brakes that rule. Additionally your code is realy tied to Spring Integration if you create it as above. What you could do instead is create a couple of components that each have a single responsibility, and then configure them to act in a chain.
What you could do is create a transformer that modifies the payload and headers of the message. That transformer would encapsulate your business logic and would set a header (say, myRoutingHeader) that can be later used in routing. It would probably be even better to have a transformer for business logic, and a header enricher for adding the header. But let's assume that you are doing it in a single class. Define that class as a bean (say myTransformer). Then in your config :
<int:channel id="inChannel/>
<!-- if performance is important consider using a SpEL expression to
invoke your method instead as they can be configured to be compiled -->
<int:transformer ref="myTransformer" input-channel="inChannel"
method="transform" output-channel="routingChannel"/>
<int:channel id="routingChannel/>
<int:header-value-router input-channel="routingChannel" header-name="myRoutingHeader">
<int:mapping value="foo" channel="fooChannel" />
<int:mapping value="bar" channel="barChannel" />
</int:header-value-router>
sounds like chaining will do it for you
<chain input-chanel="source" output-channel="routing-channel">
<transform/>
<!-- any enrichment process or intermediate process can go here -->
</chain>
We are using Spring Framework (XML Version) 4.0.5.RELAESE in our Java project.
In our application the context.xml is instantiated at the begin of the application start, and provides every properties via dependecy injection.
Now I am wondering what is the best (and commonly used) strategy on where to instantiate it in the test environment. (We have some Unit, Integration and System tests who at least need the databaseBaseConnector bean provided in the context.xml,)
I thought about making an abstract class which every test extends from, but in this case it would be newly instantiated before every test. I would like to a have a solution similiar to the main application, where the context is only instantiated one time, and everything else needed is set via dependency injection.
Researching this topic didn`t help much yet, so I decided to ask the question.
Spring comes with an SpringJUnit4ClassRunner and a #ContextConfiguration -annotation. If you use them, then spring will reuse the same Spring Context in different tests.
So a Spring test class can look like this:
package com.queomedia;
import javax.annotation.Resource;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
#RunWith(SpringJUnit4ClassRunner.class)
//#Transactional
#ContextConfiguration(SpringTestContext.APPLICATION)
public class SpringContextTest {
#Autowire
private ApplicationContext applicationContext;
//Test that the spring context can been loaded
#Test
public void testSpringContextLoad() {
Assert.assertNotNull("application context expected", this.applicationContext);
}
}