My spring boot application wants to use Webclient to make an http request (XML request body) and receives XML response. Hence I created another spring boot application with jackson-dataformat-xml and created an endpoint to receive and return XML as below.
spring-boot-version=2.2.5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
#PostMapping(value = "/api",
consumes = MediaType.APPLICATION_XML_VALUE,
produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<MyXmlResponse> trip(#RequestBody MyXmlRequest request) throws Exception {
MyXmlResponse response = new MyXmlResponse();
response.setStatus("SUCCESS");
response.setTripID(request.getTripID());
return ResponseEntity.ok().body(response);
}
It works perfect and obviously no JaxB annotations are required as I use jackson-dataformat-xml. Also the request XML can be case-insensitive.
Now, in my first application I want to consume this API via webclient. I read that Spring webflux do not support Jackson-dataformat-xml yet. Hence I have to annotate my classes with Jaxb annotations.
spring-boot-version=2.2.5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
webClient.post()
.uri(URI.create("url-to-api-endpoint"))
.body(Mono.just(myXmlRequest), MyXmlRequest.class)
.exchange()
.doOnSuccess(response -> {
HttpStatus statusCode = response.statusCode();
log.info("Status code of external system request {}", statusCode);
})
.doOnError(onError -> {
log.error("Error on connecting to external system {}", onError.getMessage());
})
.flatMap(response -> response.bodyToMono(MyXmlResponse.class))
.subscribe(this::handleResponse);
Above code throws an exception as follows
org.springframework.webreactive.function.UnsupportedMediaTypeException: Content type 'application/xml' not supported for bodyType=com.example.MyXmlRequest
at org.springframework.web.reactive.function.BodyInserters.unsupportedError(BodyInserters.java:391)
I fixed this problem by annotating with XmlRootElement as follows
#Getter #Setter #NoArgsConstructor #ToString
#XmlRootElement()
public class MyXmlRequest {
private String attribute1;
}
On the next attempt I got another error as follows
reactor.core.Exceptions$ErrorCallbackNotImplemented: org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'application/xml' not supported for bodyType=com.example.MyXmlResponse
Caused by: org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'application/xml' not supported for bodyType=com.example.MyXmlResponse
This could be solved by annotating MyXmlResponse with XmlRootElement as follows
#Getter #Setter #NoArgsConstructor #ToString
#XmlRootElement()
public class MyXmlResponse {
private String attr1;
private String attr2;
}
This time I get unmarshallexception as follows
reactor.core.Exceptions$ErrorCallbackNotImplemented: org.springframework.core.codec.DecodingException: Could not unmarshal XML to class com.example.MyXmlResponse; nested exception is javax.xml.bind.UnmarshalException
- with linked exception:
[com.sun.istack.internal.SAXParseException2; lineNumber: 1; columnNumber: 15; unexpected element (uri:"", local:"MyXmlResponse"). Expected elements are <{}myXmlResponse>]
Caused by: org.springframework.core.codec.DecodingException: Could not unmarshal XML to class com.example.MyXmlResponse; nested exception is javax.xml.bind.UnmarshalException
- with linked exception:
I fixed it with additional attributes passed to annotation as follows.
#XmlRootElement(name = "MyXmlResponse", namespace = "")
public class MyXmlResponse {
In future, my XML structures going to be tremendously complex. I want to know if I am doing it the right way.
Related
My project is a simple spring boot application which doesn't have a main/#SpringBootApplication class. It is used as a dependency library for other modules. I am trying to write the unit tests for the classes present in this project like below and getting the below pasted error. Any quick help is much appreciated.
pom dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<!-- exclude junit 4 -->
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- junit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
As this project doesn't have main class, to get the spring application context using below configuration class.
#Configuration
public class TestServiceConfig {
#Bean
public TestService productService() {
return Mockito.mock(TestService.class);
}
#Bean
public MongoDriverService productMongo() {
return Mockito.mock(MongoDriverService.class);
}
}
Below is my test class which is throwing exception. Actual java class has a method called getPlanCode(which takes 6 arguments) and returns void. In this method mongo object is used for connecting the db so that I used #InjectMocks on service object.
public class ValidationServiceTest {
#Mock
MongoDriverService mongo;
#InjectMocks
TestService service;
#Test
#DisplayName("Test Get Plan Code positive")
public void getPlanCodeTest() {
doNothing().when(service).getPlanCode(anyString(), anyString(), any(Batch.class), any(BatchFile.class), any(Document.class), anyString());
service.getPlanCode(anyString(), anyString(), any(Batch.class), any(BatchFile.class), any(Document.class), anyString());
verify(service, times(1)).getPlanCode(anyString(), anyString(), any(Batch.class), any(BatchFile.class), any(Document.class), anyString());
}
}
Below is the exception
12:51:33.829 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - After test method: context [DefaultTestContext#45b4c3a9 testClass = DefaultMedicareBFTAccumsValidationServiceTest, testInstance = com.anthem.rxsmart.service.standalone.batchvalidation.DefaultMedicareBFTAccumsValidationServiceTest#14dda234, testMethod = getPlanCodeTest#DValidationServiceTest, testException = org.mockito.exceptions.misusing.NotAMockException:
Argument passed to when() is not a mock!
Example of correct stubbing:
service is not a mock since you are using #InjectMocks ( assume you are using #RunWith(MockitoRunner.class) or #ExtendWith but you are hiding that for whatever reasons).
What #InjectMocks does, is create of a new instance of TestService and literally inject mocks into it (mocked required dependencies). So service is a real thing, not a mock
IMO this test makes not sense as you are suppose to test your implementation of singular entity contract, not to test mocks...
Your test case and assertions are pointless as it is like "call method A and check if I just called method A" while you should check and validate eg return value of a call, or if some methods of mocks have been called eg if Mongo was queried with proper arguments. I just hope it is a really bad example, not real test scenario
Also test setup is wrong as you show us that you want to use #Configuration class with #Bean but then you are using #Mock in the test which will create brand new mocks for you. In other words - that config is not used at all
Posting this answer just for the developers who are in same understanding state.
#Test
#DisplayName("Test Get Plan Code positive")
public void getPlanCodeTest() {
service = new ValidationService(mongo);
Mockito.when(mongo.aggregateIterable("test", pipeline)).thenReturn(tierFilterDocs);
service.getPlanCode("", "", null, batchFile, null, "");
verify(mongo, times(1)).aggregateIterable("test", pipeline);
}
I have updated my test case so it solves the purpose now. Now no need of the Configuration file as I am mocking the object in test class itself.
I am facing a issue with the circuit breaker implementation using Spring Cloud Resilience4j.
Following some tutorial, I have tried to add the necessary dependencies in the project.
Also, tried to add the configurations but, still the circuit is not opening and fallback method is not getting called.
For the use case, I am calling an external API from my service and if that external API is down then after few calls I need to enable the circuit breaker.
Please find the code pieces from the different files.
I am a newbie to circuit breaker pattern. Any help will be highly appreciated.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2020.0.4</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependencies>
</project>
Application properties
resilience4j.circuitbreaker.instances.test-api.register-health-indicator=true
resilience4j.circuitbreaker.instances.test-api.minimum-number-of-calls=4
resilience4j.circuitbreaker.instances.test-api.failure-rate-threshold=50
resilience4j.circuitbreaker.instances.test-api.permitted-number-of-calls-in-half-open-state=3
resilience4j.circuitbreaker.instances.test-api.wait-duration-in-open-state=30s
resilience4j.circuitbreaker.instances.test-api.automatic-transition-from-open-to-half-open-enabled=true
resilience4j.circuitbreaker.instances.test-api.record-exceptions=com.testapi.exception.ServiceUnavailableError
Service Class Code Piece
#CircuitBreaker(name = "test-api", fallbackMethod = "storeResponseFallback")
public TestResponse storeResponse(String apiURL, HttpEntity<String> entityrequest) {
TestResponse testResponse = new TestResponse();
Optional<ResponseEntity<TestResponse>> response = Optional.empty();
Future<ResponseEntity<TestResponse>> responseFuture;
ExecutorService executor = Executors.newFixedThreadPool(10);
log.debug("Calling Extrenal API, Request Body: {}", entityrequest.toString());
try {
//Service call returns a future
responseFuture = executor.submit(() -> restTemplate.postForEntity(apiURL, entityrequest, TestResponse.class));
response = Optional.ofNullable(responseFuture.get());
log.info("Got response from external API");
if ((response.isPresent()) && (response.get().hasBody())) {
testResponse = response.get().getBody();
}
} catch (Exception exception) {
log.error("External api call got failed with an error");
Thread.currentThread().interrupt();
throw new ServiceUnavailableError();
}
return testResponse;
}
public TestResponse storeResponseFallback(ServiceUnavailableError ex) {
log.error("Executing Fallback Method For General exceptions");
throw new ServiceUnavailableError();
}
ServiceUnavailableError Java file
#Data
#AllArgsConstructor
#NoArgsConstructor
public class ServiceUnavailableError extends RuntimeException{
private static final long serialVersionUID = 2382122402994502766L;
private String message;
}
The signature of your fallback method is wrong. It should contain all the parameters of the actual method ( in your case storeResponseFallback is the fallback method and storeResponse is the actual method), along with the exception. Please make sure to remove the try catch block. You do not want to handle the exception yourself, rather you should let circuit breaker to handle it for you.
Please take a look at the following code which is from given link
https://resilience4j.readme.io/docs/getting-started-3
#CircuitBreaker(name = BACKEND, fallbackMethod = "fallback")
public Mono<String> method(String param1) {
return Mono.error(new NumberFormatException());
}
private Mono<String> fallback(String param1, IllegalArgumentException e) {
return Mono.just("test");
}
Try using the following yaml file
I used the following configuration with your existing code,I used yaml instead of properties file. this seems to stay in open state and call only the fallback method.
resilience4j.circuitbreaker:
configs:
default:
slidingWindowSize: 4
permittedNumberOfCallsInHalfOpenState: 10
waitDurationInOpenState: 10000
failureRateThreshold: 60
eventConsumerBufferSize: 10
registerHealthIndicator: true
someShared:
slidingWindowSize: 3
permittedNumberOfCallsInHalfOpenState: 10
instances:
test-api:
baseConfig: default
waitDurationInOpenState: 500000
backendB:
baseConfig: someShared
Here is the updated fallback method
public TestResponse storeResponseFallback(String apiURL, String entityrequest, java.lang.Throwable t) {
log.error("Executing Fallback Method For General exceptions "+t.getMessage());
return new TestResponse("Frm Fallback");// Making sure to send a blank response
}
I have prepared the video, where I have defined main service and target service and I am preparing the bean from config and making use of Try.of() please check the video if it help.
you will see that the fallback method is working fine.
https://www.youtube.com/watch?v=8yJ0xek6l6Y&t=31s
After adding cache2k to my project some #SpringBootTest's stopped working with an error:
java.lang.IllegalStateException: Cache already created: 'cache'
Below I provide the minimal example to reproduce:
Go to start.spring.io and create a simplest Maven project with Cache starter, then add cache2k dependencies:
<properties>
<java.version>1.8</java.version>
<cache2k-version>1.2.2.Final</cache2k-version>
</properties>
<dependencies>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-api</artifactId>
<version>${cache2k-version}</version>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-core</artifactId>
<version>${cache2k-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-spring</artifactId>
<version>${cache2k-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Now configure the simplest cache:
#SpringBootApplication
#EnableCaching
public class CachingDemoApplication {
public static void main(String[] args) {
SpringApplication.run(CachingDemoApplication.class, args);
}
#Bean
public CacheManager springCacheManager() {
SpringCache2kCacheManager cacheManager = new SpringCache2kCacheManager();
cacheManager.addCaches(b -> b.name("cache"));
return cacheManager;
}
}
And add any service (which we will #MockBean in one of our tests:
#Service
public class SomeService {
public String getString() {
System.out.println("Executing service method");
return "foo";
}
}
Now two #SpringBootTest tests are required to reproduce the issue:
#SpringBootTest
#RunWith(SpringRunner.class)
public class SpringBootAppTest {
#Test
public void getString() {
System.out.println("Empty test");
}
}
#RunWith(SpringRunner.class)
#SpringBootTest
public class WithMockedBeanTest {
#MockBean
SomeService service;
#Test
public void contextLoads() {
}
}
Notice that the 2nd test has mocked #MockBean. This causes an error (stacktrace below).
Caused by: java.lang.IllegalStateException: Cache already created: 'cache'
at org.cache2k.core.CacheManagerImpl.newCache(CacheManagerImpl.java:174)
at org.cache2k.core.InternalCache2kBuilder.buildAsIs(InternalCache2kBuilder.java:239)
at org.cache2k.core.InternalCache2kBuilder.build(InternalCache2kBuilder.java:182)
at org.cache2k.core.Cache2kCoreProviderImpl.createCache(Cache2kCoreProviderImpl.java:215)
at org.cache2k.Cache2kBuilder.build(Cache2kBuilder.java:837)
at org.cache2k.extra.spring.SpringCache2kCacheManager.buildAndWrap(SpringCache2kCacheManager.java:205)
at org.cache2k.extra.spring.SpringCache2kCacheManager.lambda$addCache$2(SpringCache2kCacheManager.java:143)
at java.util.concurrent.ConcurrentHashMap.compute(ConcurrentHashMap.java:1853)
at org.cache2k.extra.spring.SpringCache2kCacheManager.addCache(SpringCache2kCacheManager.java:141)
at org.cache2k.extra.spring.SpringCache2kCacheManager.addCaches(SpringCache2kCacheManager.java:132)
at com.example.cachingdemo.CachingDemoApplication.springCacheManager(CachingDemoApplication.java:23)
at com.example.cachingdemo.CachingDemoApplication$$EnhancerBySpringCGLIB$$2dce99ca.CGLIB$springCacheManager$0(<generated>)
at com.example.cachingdemo.CachingDemoApplication$$EnhancerBySpringCGLIB$$2dce99ca$$FastClassBySpringCGLIB$$bbd240c0.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363)
at com.example.cachingdemo.CachingDemoApplication$$EnhancerBySpringCGLIB$$2dce99ca.springCacheManager(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
... 52 more
If you remove #MockBean, both tests will pass.
How can I avoid this error in my test suite?
Your second test represents a different ApplicationContext altogether so the test framework will initiate a dedicated one for it. If cache2k is stateful (for instance sharing the CacheManager for a given classloader if it already exists), the second context will attempt to create a new CacheManager while the first one is still active.
You either need to flag one of the test as dirty (see #DirtiesContext) which will close the context and shut down the CacheManager, or you can replace the cache infrastructure by an option that does not require all that, see #AutoConfigureCache.
If cache2k works in such a way that it requires you to dirty the context, I'd highly recommend to swap it using the later options.
Since I do not want any custom behavior in test, but just want to get rid of this error, the solution is to create CacheManager using unique name like this:
#Bean
public CacheManager springCacheManager() {
SpringCache2kCacheManager cacheManager = new SpringCache2kCacheManager("spring-" + hashCode());
cacheManager.addCaches(b -> b.name("cache"));
return cacheManager;
}
I encountered the same error when using cache2k with Spring Dev Tools, and ended up with the following code as the solution:
#Bean
public CacheManager cacheManager() {
SpringCache2kCacheManager cacheManager = new SpringCache2kCacheManager();
// To avoid the "Caused by: java.lang.IllegalStateException: Cache already created:"
// error when Spring DevTools is enabled and code reloaded
if (cacheManager.getCacheNames().stream()
.filter(name -> name.equals("cache"))
.count() == 0) {
cacheManager.addCaches(
b -> b.name("cache")
);
}
return cacheManager;
}
I can't force Spring to use Kotlin module for Jackson.
The problem is that Jackson can't parse JSON into data class.
//Exception
2018-02-23 13:29:09.046 ERROR 24730 --- [nio-9300-exec-1] o.a.c.c.C.[.[.[.[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [/services] threw exception [Request processing failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [*.model.User]: Constructor threw exception; nested exception is java.lang.IllegalArgumentException: Parameter specified as non-null is null: method *.model.User.<init>, parameter name] with root cause
java.lang.IllegalArgumentException: Parameter specified as non-null is null: method *.User.<init>, parameter name
//JSON
{
"name": "name",
"surname": "surname",
"email": "email",
"password": "pswd"
}
//Model
#Entity
#Table
data class User(
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
var userId: Long?,
var name: String,
var surname: String,
var email: String,
var password: String,
(...)
): Resource() {
(...)
}
I tried to config Jackson, but it didn't help much. What's strange, inside #Bean method where I configure ObjectMapper, everything works fine.
Also, when I added default values for non-nullable fields, they weren't overwritten.
#Configuration
class JacksonConfig {
#Bean
fun mappingJackson2HttpMessageConverter(): MappingJackson2HttpMessageConverter {
val mapper = ObjectMapper().registerKotlinModule()
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
var user = mapper.readValue<User>("{\n" +
"\t\"name\": \"name\",\n" +
"\t\"surname\": \"surname\",\n" +
"\t\"email\": \"email\",\n" +
"\t\"password\": \"pswd\"\n" +
"}")
return MappingJackson2HttpMessageConverter(mapper)
}
}
What might be important is that model is in the different project than the application itself.
Kotlin version is 1.20
Jackson dependencies:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version> <!--2.9.4-->
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
<version>${jackson.version}</version>
</dependency>
And what I tried to this moment:
All of these answers
Similiar problem but probably different case anyway
And some other, not worth noticing.
I had structure like:
abstract class AbstractController (...) {
fun save(#RequestMapping entity: T) {
(...)
}
And to enable it I used
class DeriveredController (...) {
#PostMapping
override fun save(entity: Derivered) {
(...)
}
}
Problem was with lack of #RequestMapping in derivered function.
I'm new to Spring Integration and am trying to develop an application which simply (1) polls a folder for new files with extension .dat containing data in csv format (2) initialize for each line a domain POJO object of class RecordDTO and finally (3) sends this object as payload to a REST service with a POST.
For this I'm trying to use Java DSL for Spring Integration.
The problem I'm getting is the following VerifyError / HttpRequestExecutingMessageHandler overrides final method onInit. exception.
2016-07-22 10:01:38.965 ERROR 4460 --- [ main] o.s.boot.SpringApplication : Application startup failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'upcase' defined in eu.softeco.examples.SpringBootIntegrationTestApplication: Initialization of bean failed; nest
ed exception is java.lang.VerifyError: class org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler overrides final method onInit.()V
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
...
Caused by: java.lang.VerifyError: class org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler overrides final method onInit.()V
at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_45]
at java.lang.ClassLoader.defineClass(ClassLoader.java:760) ~[na:1.8.0_45]
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) ~[na:1.8.0_45]
...
Below are relevant details concerning my code and maven configuration. Please not that
1) the problem is at startup, before any data is put in the input folder
2) if I replace the final Http outboundGateway step with the simple (commented) System.out.println, everything works fine.
Following are code/config details. This is my main Spring Boot application class with Spring Integration Flow definition:
#SpringBootApplication
#EnableIntegration
#IntegrationComponentScan
public class SpringBootIntegrationTestApplication {
...
public static void main(String[] args) {
SpringApplication.run(SpringBootIntegrationTestApplication.class, args);
}
/**
* Inbound FileReadingMessageSource
*/
#Bean
#InboundChannelAdapter(channel = "upcase.input", poller = #Poller(fixedDelay = "4000"))
public MessageSource<File> fileReadingMessageSource() {
FileReadingMessageSource source = new FileReadingMessageSource();
source.setDirectory(new File(INBOUND_PATH));
source.setFilter(new SimplePatternFileListFilter("*.dat"));
return source;
}
/**
* Spring Integration Java DSL Flow
*/
#Bean
public IntegrationFlow upcase() {
FileToStringTransformer fileToStringTranformer = Transformers.fileToString();
fileToStringTranformer.setDeleteFiles(true);
return flow -> flow
// get string contents from fie
.transform(fileToStringTranformer)
// split into individual lines
.split( s -> s.applySequence(true).get().getT2().setDelimiters("\n"))
// cleanup lines from trailing returns
.transform( (String s) -> s.replace("\n", "").replace("\r", "") )
// convert csv string to RecordDTO object
.transform("#recordFromCsvTransformer.transform(payload)")
// print on System.out
// .handle(m -> System.out.println(m.getPayload()))
// send to
.handle(Http.outboundGateway("http://localhost:8080/records")
.httpMethod(HttpMethod.POST)
.expectedResponseType(RecordDTO.class));
}
}
Below the RecordFromCsvTransformer class
#Component
public class RecordFromCsvTransformer
{
#Transformer
public RecordDTO transform(String csvline) throws Exception {
RecordDTO record = new RecordDTO();
... parse csv and initialize record's fields...
return record;
}
}
And Finally the relevant parts of pom.xml (dependencies);
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.RC1</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
...
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-file</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-http</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
...
</dependencies>
...
As a side question in general, can someone suggest me some good tutorial / getting started guide to learn Spring Integration with Java Annotation/Java DSL? So far I've only found either introductory guide based on Spring Integration XML configuration or material about Java Annotations/DSL but that already requires prior knowledge of Spring Integration.
<version>2.1.0.RELEASE</version>
That version of Spring Integration is mismatched with the spring-integration-core version brought in transitively by maven.
You need to use the same version of all spring-integration-* files - check which version of spring-integration-file is being pulled in by boot and use the same version.