Hystrix Feign Retry for Timeout not working - spring-boot

I have a Feign Configuration and Hystrix Commands in my project.
below is Feign Config
#Configuration
public class FeignRetryConfig {
#Primary
#Bean
public Feign.Builder feignBuilder(Retryer nephosFeignRetryer) {
return HystrixFeign.builder()
.errorDecoder(new FeignErrorDecoder())
.retryer(nephosFeignRetryer);
}
// retry set to 3 times
#Bean
public Retryer nephosFeignRetryer() {
return new Retryer.Default(10, SECONDS.toMillis(5), 5);
}
#Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
and below is my ErrorDecoder:
public class FeignErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
#Override
public Exception decode(String methodKey, Response response) {
Exception exception = defaultErrorDecoder.decode(methodKey, response);
if (response.status() == 500) {
log.error(String.format("##### Got %s response from %s #######", response.status(),
methodKey));
return new RetryableException(
exception.getMessage(),
exception,
null
);
}
return exception;
}
}
and below is my client:
#FeignClient(name = "TEST-CONFIG", configuration = FeignRetryConfig.class, fallbackFactory =
XYZClientFallbackFactory.class)
public interface TestClient {
#RequestMapping(value = "/test", method = RequestMethod.GET, consumes =
MediaType.APPLICATION_JSON_VALUE)
Observable<String> test();
}
SO from TEST-CONFIG I am throwing IOException ( 500 Error ) to Test, but it does not make any retry. below is my error:
com.netflix.hystrix.exception.HystrixRuntimeException: TestClient#test() failed and fallback failed.
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:815)
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:790)
at rx.internal.operators.OperatorOnErrorResumeNextViaFunction$4.onError(OperatorOnErrorResumeNextViaFunction.java:140)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at com.netflix.hystrix.AbstractCommand$DeprecatedOnFallbackHookApplication$1.onError(AbstractCommand.java:1451)
at com.netflix.hystrix.AbstractCommand$FallbackHookApplication$1.onError(AbstractCommand.java:1376)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: feign.RetryableException: status 500 reading TestClient#test(); content:
{"status":500,"erroritems":[{"code":"RuntimeException","message":"org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection"}]}
at feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:108)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:301)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:297)
at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46)
... 30 common frames omitted
Caused by: feign.FeignException: status 500 reading TestClient#test(); content:
{"status":500,"erroritems":[{"code":"RuntimeException","message":"org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection"}]}
at feign.FeignException.errorStatus(FeignException.java:62)
at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:91)
Can Somebody Help Please, What am I Missing ?

I guess you have hystrix enabled. Try setting
feign.hystrix.enabled: false
and see if it works then; if so it would prove your configuration to be ok. There is a post on hystrix and retrying that suggests that this does not go well together. If you want to keep hystrix enabled (and why should you not), perhaps it is worth looking at spring-retry to circumvent the problem.

Related

How to test complete IntegrationFlow from MessagingGateway to ServiceActivator

In our Spring Boot project we have the following IntegrationFlow configuration
package our.configs;
#Configuration
#EnableIntegration
public class MessageChannelsConfiguration {
public static final String OUTBOUND_CHANNEL = "outboundChannel";
public static final String OUTBOUND_CHANNEL_GROUP_ID = "outboundMessageGroup";
#Bean
IntegrationFlow outboundSnapshotMessageChannel(ChannelMessageStore outboundChannelMessageStore,
OutboundFixMessageService outboundMessageService) {
return f -> f
.channel(c -> c.queue(
OUTBOUND_CHANNEL,
outboundChannelMessageStore,
OUTBOUND_CHANNEL_GROUP_ID))
.handle(outboundMessageService, "processOutboundMessage");
}
#Bean
OutboundMessageService outboundFixMessageService(ObjectMapper objectMapper){
return new OutboundMessageService(objectMapper);
}
#Bean
ChannelMessageStore outboundChannelMessageStore(#Qualifier("dataSource") DataSource dataSource,
ChannelMessageStoreQueryProvider channelMessageStoreQueryProvider) {
JdbcChannelMessageStore jdbcChannelMessageStore = new JdbcChannelMessageStore(dataSource);
jdbcChannelMessageStore.setChannelMessageStoreQueryProvider(channelMessageStoreQueryProvider);
jdbcChannelMessageStore.setRegion("TX_TIMEOUT");
return jdbcChannelMessageStore;
}
#Bean
#Profile({"test"})
ChannelMessageStoreQueryProvider jdbcChannelMessageStoreQueryProvider() {
return new H2ChannelMessageStoreQueryProvider();
}
#Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller(TransactionManager transactionManager,
Initiator clientInitiator) {
return Pollers.fixedRate(500))
.maxMessagesPerPoll(1)
.advice(transactionInterceptor(transactionManager), new CheckSessionPollingAdvise(clientInitiator))
.get();
}
private TransactionInterceptor transactionInterceptor(TransactionManager transactionManager) {
return new TransactionInterceptorBuilder()
.transactionManager(transactionManager)
.propagation(Propagation.NESTED)
.build();
}
}
and the messaging Gateway which is defined as in a separate package then the above configuration
package our.businesslogic;
#MessagingGateway
public interface OutboundMessageGateway {
#Gateway(requestChannel = MessageChannelsConfiguration.OUTBOUND_CHANNEL)
void sendMarkerMessage(Object markerMessage,
#Header(ChannelMessageHeaders.RECIPIENT_ID) String institutionId,
#Header(ChannelMessageHeaders.MESSAGE_TYPE) ChannelMessageType channelMessageType);
#Gateway(requestChannel = MessageChannelsConfiguration.OUTBOUND_CHANNEL)
void sendOrderMessage(Order message,
#Header(ChannelMessageHeaders.RECIPIENT_ID) String institutionId,
#Header(ChannelMessageHeaders.MESSAGE_TYPE) ChannelMessageType channelMessageType);
}
I want to test the behavior of the complete flow including the persistence to the JdbcChannelMessageStore (and later also the transactional scenarios) with JUnit5.
E.g.
#Test
void whenSendTrancheMessage_givenPollingBlockedByAdvise_thenCorrectNumberOfMessagesOnQueue() {
//given
String recipientId = "Mocked-recipient";
List<Order> orders = Arrays.asList(
new Order(),
new Order(),
new Order(),
new Order()
);
//when
clientInitiator.stopConnection(); // Queue will not be read as long as
// there is no connection
orders.forEach(order->
outboundMessageGateway.sendOrderMessage(order,recipientId,ChannelMessageType.SNAPSHOT));
//then
Assertions.assertThat(outboundChannelMessageStore.messageGroupSize(FixMessageChannelsConfiguration.OUTBOUND_CHANNEL_GROUP_ID))
.isEqualTo(orders.size());
}
#Test
void whenSendTrancheMessage_givenPollingIsNotBlocked_thenMessagesAreReceivedByHandler() {
//some test code with mocked ServiceActivator
}
I have tried with two different ways
as an Integration test with #SpringBootTest
as a context specific JUnit test with #ContextConfiguration and #SpringIntegrationTest
In case of 1) my tests are working when called separately, but are failing with the following exception when they are run together with existing integration tests
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springSecurityFilterChain' defined in class p
ath resource [org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]: Bean instantiation vi
a factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servl
et.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is org.h2.jdbc.JdbcSQLNonTransientConnection
Exception: Exception opening port "9092" (port may be in use), cause: "java.net.BindException: Address already in use: JVM_Bind" [900
61-199]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:655)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:483)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowir
eCapableBeanFactory.java:1336)
In case of 2) the following exception is thrown showing problems with the outboundMessageGateway
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
... ... ...
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'outboundMessageGateway': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [our.businesslogic.OutboundMessageGateway]: Specified class is an interface
I would appreciate it very much if someone could help me to solve this issues.
Exception opening port "9092"
You probably need to add a #DirtiesContext along side with the mentioned #SpringBootTest.
Failed to instantiate [our.businesslogic.OutboundMessageGateway]
If you don't use Spring Boot in your test environment, you must be explicit #EnableIntegration and #IntegrationComponentScan must be there to Spring Integration infrastructure available.
See docs for more info: https://docs.spring.io/spring-integration/docs/current/reference/html/overview.html#configuration-enable-integration
Assertions.assertThat(outboundChannelMessageStore.messageGroupSize(FixMessageChannelsConfiguration.OUTBOUND_CHANNEL_GROUP_ID))
.isEqualTo(orders.size());
You can't check this if your handle(outboundMessageService, "processOutboundMessage") is not stopped. Since you have a poller for the queue channel, it is going to pull all the message from the store and lets that handler to process them. So, there is going to be nothing in the store at the moment you try to verify it (or some wrong unexpected number).

Catching exception Feign

I want to handle any exception from feign client, even if service is not available. However I can not catch them using try/catch. This is my feign client:
#FeignClient(name = "api-service", url ="localhost:8888")
public interface ClientApi extends SomeApi {
}
Where api is:
#Path("/")
public interface SomeApi {
#GET
#Path("test")
String getValueFromApi();
}
Usage of client with try/catch:
#Slf4j
#Service
#AllArgsConstructor
public class SampleController implements SomeApi {
#Autowired
private final ClientApi clientApi;
#Override
public String getValueFromApi() {
try {
return clientApi.getValueFromApi();
} catch (Throwable e) {
log.error("CAN'T CATCH");
return "";
}
}
}
Dependencies are in versions:
spring-boot 2.2.2.RELEASE
spring-cloud Hoxton.SR1
Code should work according to How to manage Feign errors?.
I received few long stack traces among them exceptions are :
Caused by: java.net.ConnectException: Connection refused (Connection refused)
Caused by: feign.RetryableException: Connection refused (Connection refused) executing GET http://localhost:8888/test
Caused by: com.netflix.hystrix.exception.HystrixRuntimeException: ClientApi#getValueFromApi() failed and no fallback available.
How to properly catch Feign exeptions, even if client service (in this case localhost:8888) is not available?
Ps. When feign client service is available it works, ok. I am just focused on the exceptions aspect.
A better way to handle the situation where your service is not available is to use a circuit breaker pattern. Fortunately, it is easy using Netflix Hystrix as an implementation of the circuit breaker pattern.
First of all, you need to enable Hystrix for feign clients in application configuration.
application.yml
feign:
hystrix:
enabled: true
Then you should write a fallback class for the specified feign client interface.
In this case getValueFormApi method in fallback class will act mostly like catch block that you wrote(with exception when circuit will be in open state and original method will not be attempted).
#Component
public class ClientApiFallback implements ClientApi {
#Override
public String getValueFromApi(){
return "Catch from fallback";
}
}
Lastly, you just need to specify the fallback class for your feign client.
#FeignClient(name = "api-service", url ="localhost:8888", fallback = ClientApiFallback.class)
public interface ClientApi extends SomeApi {
}
That way your method getValueFromApi is fail safe. If,
for any reason, any uncaught exceptions escape from getValueFromApi the ClientApiFallback method will be called.
To enable circuit breaker and also configure your application to deal with unexpected errors, you need to:
1.- Enable the circuit breaker itself
#SpringBootApplication
#EnableFeignClients("com.perritotutorials.feign.client")
#EnableCircuitBreaker
public class FeignDemoClientApplication {
2.- Create your fallback bean
#Slf4j
#Component
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PetAdoptionClientFallbackBean implements PetAdoptionClient {
#Setter
private Throwable cause;
#Override
public void savePet(#RequestBody Map<String, ?> pet) {
log.error("You are on fallback interface!!! - ERROR: {}", cause);
}
}
Some things you must keep in mind for fallback implementations:
Must be marked as #Component, they are unique across the application.
Fallback bean should have a Prototype scope because we want a new one to be created for each exception.
Use constructor injection for testing purposes.
3.- Your ErrorDecoder, to implement fallback startegies depending on the HTTP error returned:
public class MyErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() >= 400 && response.status() <= 499) {
return new MyCustomBadRequestException();
}
if (response.status() >= 500) {
return new RetryableException();
}
return defaultErrorDecoder.decode(methodKey, response);
}
}
4.- In your configuration class, add the Retryer and the ErrorDecoder into the Spring context:
#Bean
public MyErrorDecoder myErrorDecoder() {
return new MyErrorDecoder();
}
#Bean
public Retryer retryer() {
return new Retryer.Default();
}
You can also add customization to the Retryer:
class CustomRetryer implements Retryer {
private final int maxAttempts;
private final long backoff;
int attempt;
public CustomRetryer() {
this(2000, 5); //5 times, each 2 seconds
}
public CustomRetryer(long backoff, int maxAttempts) {
this.backoff = backoff;
this.maxAttempts = maxAttempts;
this.attempt = 1;
}
public void continueOrPropagate(RetryableException e) {
if (attempt++ >= maxAttempts) {
throw e;
}
try {
Thread.sleep(backoff);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
#Override
public Retryer clone() {
return new CustomRetryer(backoff, maxAttempts);
}
}
If you want to get a functional example about how to implement Feign in your application, read this article.

Getting I/O error while executing JUnit test case for Spring Controller

I am executing a test case to call spring controller (GET method). However, It throws below I/O error.
org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8039": Connect to localhost:8039 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect; nested exception is org.apache.http.conn.HttpHostConnectException: Connect to localhost:8039 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:674)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:636)
Caused by: java.net.ConnectException: Connection refused: connect
at java.net.DualStackPlainSocketImpl.connect0(Native Method)
at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79)
Below is the test case class that i am executing which throws the above error.
public class GetRuleSetsTests extends PreferencesAdminClientTestApplicationTests<GetRuleSetsResponse>{
#Test
public void testSuccess() throws Exception
{
final String mockedResponseJson = rawJsonFromFile("com/cnanational/preferences/client/rule-sets/getRuleSetsResponse.json");
MockRestServiceServer mockServer = mockServer();
mockServer.expect(requestTo(dummyUri()))
.andExpect(method(HttpMethod.GET))
.andExpect(queryParam("ruleSetDescription", "TestRuleDescription"))
.andRespond(withSuccess(
mockedResponseJson,
MediaType.APPLICATION_JSON));
ServiceClientResponse<GetRuleSetsResponse> response = executeDummyRequest();
mockServer.verify();
assertThat(response.isSuccessful(), equalTo(true));
GetRuleSetsResponse programResponse = response.getParsedResponseObject();
assertThat(programResponse.getRuleSets().size(), equalTo(2));
}
#Override
public URI dummyUri() {
return UriComponentsBuilder.fromUri(baseUri())
.path(this.endpointProperties.getRuleSets())
.build()
.toUri();
}
}
What am i missing? Any inputs appreciated.
If you have configured your test environment properly to run MockRestServiceServer
(by that, I mean #RunWith(SpringRunner.class) and #RestClientTest(ClassUnderTestThatCallsTheMockServer.class)), make sure that you are not instantiating your mock server with = new MockServer(), instead just use an instance that is #Autowired from the spring context (because that instance is configured out of the box).
I see that you have a lot of inheritance and overridden methods in your tests, calling things with this.returnSomething..., so make sure that you are not instantiating things outside of the spring context.
Here is a simple example of a mock server to get some posts:
#RunWith(SpringRunner.class)
#RestClientTest(PostClient.class)
public class PostClientMockTest {
// class under test
#Autowired
private PostClient postClient;
// autowired mock server from the spring context
#Autowired
private MockRestServiceServer mockRestServiceServer;
#Test
public void readPosts() throws Exception {
String mockJsonResponse = "My response";
mockRestServiceServer.expect(requestTo("https://myurl.com/posts?userId=1"))
.andRespond(withSuccess(mockJsonResponse, MediaType.APPLICATION_JSON_UTF8));
List<Post> posts = postClient.readPosts(1);
assertEquals(9, posts.size());
mockRestServiceServer.verify();
}
}
Hope this helps

How to avoid the MessageDeliveryException?

I'm trying to send a simple message through tcp but I can't even manage that using spring integration... I'm really getting bored with that ...
So I tried using TcpOutboundGateway and TcpInboudGateway in client mode but I get a MessageDeliveryException.
Here is my code:
#EnableIntegration
#IntegrationComponentScan
#Configuration
public class TcpClientConfiguration {
#Bean
public TcpNetClientConnectionFactory clientConnectionFactory() {
TcpNetClientConnectionFactory factory = new TcpNetClientConnectionFactory("localhost", 7015);
return factory;
}
#Bean
public DirectChannel outputChannel() {
return new DirectChannel();
}
#Bean
public MessageChannel replyChannel() {
return new DirectChannel();
}
// #Bean
// public TcpOutboundGateway tcpOutGateway(AbstractClientConnectionFactory clientConnectionFactory) {
// TcpOutboundGateway outGateway = new TcpOutboundGateway();
// outGateway.setConnectionFactory(clientConnectionFactory);
// outGateway.setOutputChannel(outputChannel());
// return outGateway;
// }
#Bean
public TcpInboundGateway tcpInboundGateway(AbstractClientConnectionFactory clientConnectionFactory) {
TcpInboundGateway inGateway = new TcpInboundGateway();
inGateway.setConnectionFactory(clientConnectionFactory);
inGateway.setClientMode(true);
inGateway.setRequestChannel(outputChannel());
inGateway.setReplyChannel(replyChannel());
return inGateway;
}
}
And the scheduled method to send the message :
#Component
public class SimulatorTask {
#Autowired
DirectChannel outputChannel;
#Scheduled( fixedDelay = 3000 )
public void sendMsg() {
outputChannel.send(new GenericMessage<>("Hello world!"));
}
}
The error I get :
2018-05-03 13:42:44.578 ERROR 11144 --- [ask-scheduler-7] o.s.integration.handler.LoggingHandler : org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel 'application.outputChannel'.; nested exception is org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers, failedMessage=GenericMessage [payload=Hello world!, headers={id=ed173189-b102-6f85-5fe5-d901f4585140, timestamp=1525347764578}], failedMessage=GenericMessage [payload=Hello world!, headers={id=ed173189-b102-6f85-5fe5-d901f4585140, timestamp=1525347764578}]
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:77)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:445)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:394)
at be.thingsplay.fmb920simulator.tcp.SimulatorTask.sendMsg(SimulatorTask.java:20)
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.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65)
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers, failedMessage=GenericMessage [payload=Hello world!, headers={id=ed173189-b102-6f85-5fe5-d901f4585140, timestamp=1525347764578}]
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:138)
at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:105)
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:73)
... 16 more
I'm really getting bored with Spring...
So, what happens is that you are sending message successfully. The message does successfully get to the outputChannel which you chose to be a DirectChannel.
DirectChannel by definition requires a subscriber, which I don't see in your configuration (such as #Transformer or #ServiceActivator or any other type of MessageHandler), and exception is telling you exactly that.
So, if you just want to validate that the message is sent you may want to chose different implementation of channel. For example, you may choose QueueChannel which will buffer the messages until they are polled from it, or PublishSubscribeChannel which will drop messages if there are no subscribers.
Or, add a subscriber.
#ServiceActivator(inputChannel="outputChannel", outputChannel="replyChannel")
public Message echo(Message message) {
return message;
}

Spring session tries serialize service with session scope

I have a service
#Service
#Scope(value = SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyService {
public void method() throws MyException {
throw new MyException();
}
}
And a Rest Controller:
#RestController
public class MyController {
#Autowired
private MyService myService;
#RequestMapping(value = "/do", method = RequestMethod.POST)
public String do() {
myService.method();
return null;
}
#ExceptionHandler(MyException.class)
public ResponseEntity<MyException> exceptionHandler(MyException e) {
return new ResponseEntity<MyException>(e, HttpStatus.UNAUTHORIZED);
}
}
And configuration file:
#EnableRedisHttpSession
public class AppConfig {
}
When I request do method of controller, service throws MyException, ExceptionHandler catch it. But after exceptionHandler return, I have an Exception:
org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [my.example.MyService$$EnhancerBySpringCGLIB$$442fa3ef_5]
at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.serialize(JdkSerializationRedisSerializer.java:92) ~[spring-data-redis-1.8.0.RELEASE.jar:na]
at org.springframework.data.redis.core.AbstractOperations.rawHashValue(AbstractOperations.java:171) ~[spring-data-redis-1.8.0.RELEASE.jar:na]
at org.springframework.data.redis.core.DefaultHashOperations.putAll(DefaultHashOperations.java:129) ~[spring-data-redis-1.8.0.RELEASE.jar:na]
at org.springframework.data.redis.core.DefaultBoundHashOperations.putAll(DefaultBoundHashOperations.java:86) ~[spring-data-redis-1.8.0.RELEASE.jar:na]
at org.springframework.session.data.redis.RedisOperationsSessionRepository$RedisSession.saveDelta(RedisOperationsSessionRepository.java:778) ~[spring-session-1.3.0.RELEASE.jar:na]
at org.springframework.session.data.redis.RedisOperationsSessionRepository$RedisSession.access$000(RedisOperationsSessionRepository.java:670) ~[spring-session-1.3.0.RELEASE.jar:na]
at org.springframework.session.data.redis.RedisOperationsSessionRepository.save(RedisOperationsSessionRepository.java:388) ~[spring-session-1.3.0.RELEASE.jar:na]
at org.springframework.session.data.redis.RedisOperationsSessionRepository.save(RedisOperationsSessionRepository.java:245) ~[spring-session-1.3.0.RELEASE.jar:na]
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.commitSession(SessionRepositoryFilter.java:245) ~[spring-session-1.3.0.RELEASE.jar:na]
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.access$100(SessionRepositoryFilter.java:217) ~[spring-session-1.3.0.RELEASE.jar:na]
at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:170) ~[spring-session-1.3.0.RELEASE.jar:na]
...
Caused by: org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [my.example.MyService$$EnhancerBySpringCGLIB$$442fa3ef_5]
at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:68) ~[spring-core-4.3.6.RELEASE.jar:4.3.6.RELEASE]
at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:35) ~[spring-core-4.3.6.RELEASE.jar:4.3.6.RELEASE]
at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.serialize(JdkSerializationRedisSerializer.java:90) ~[spring-data-redis-1.8.0.RELEASE.jar:na]
... 33 common frames omitted
Caused by: java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [my.example.MyService$$EnhancerBySpringCGLIB$$442fa3ef_5]
at org.springframework.core.serializer.DefaultSerializer.serialize(DefaultSerializer.java:43) ~[spring-core-4.3.6.RELEASE.jar:4.3.6.RELEASE]
at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:63) ~[spring-core-4.3.6.RELEASE.jar:4.3.6.RELEASE]
... 35 common frames omitted
While debugging, I've got that session tries to serialize Myservice object to key "sessionAttr:scopedTarget.myService"
I do not whant to make MyService class Serializeble. Is there any another solutions?
Your bean should be serializable like below:
#Service
#Scope(value = SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyService implements Serializable {
public void method() throws MyException {
throw new MyException();
}
}
The reason Is that using redis your bean must be ready to be serialized
the bean have travel in the network to redis
the bean have serialized in redis data store
Probably you should think of don't use redis as session store, infact i belive that you need that a fresh instance of bean for every session and for this reaso a centralized session datastore may be not effective, a classical approach may be better for this propouse.
I hope that this reflection may be usefull for you to understand why I consider that you should be think to an anoder solutions.

Resources