Using #SubscribeMapping annotated method for RPC-like behavior when return value is deferred - spring

I really like #SubscribeMapping approach to implement RPC-like semantic with STOMP-over-Websocket.
Unfortunately its "magic" requires that annotated method returns a value. But what if return value is not readily available? I want to avoid blocking inside the method waiting for it. Instead I'd like to pass a callback that will publish a value when it's ready. I thought I could use messaging template's convertAndSendToUser() inside a callback to do that. Turns out #SubscribeMapping handling is quite special and is not possible with instance of SimpMessageSendingOperations.
I was able to achieve my goal by calling handleReturnValue() on a SubscriptionMethodReturnValueHandler, but the overall mechanics of this is very tedious if not hackish (like providing dummy instance of MethodParameter to handleReturnValue()):
public class MessageController {
private final SubscriptionMethodReturnValueHandler subscriptionMethodReturnValueHandler;
#Autowired
public MessageController(SimpAnnotationMethodMessageHandler annotationMethodMessageHandler) {
SubscriptionMethodReturnValueHandler subscriptionMethodReturnValueHandler = null;
for (HandlerMethodReturnValueHandler returnValueHandler : annotationMethodMessageHandler.getReturnValueHandlers()) {
if (returnValueHandler instanceof SubscriptionMethodReturnValueHandler) {
subscriptionMethodReturnValueHandler = (SubscriptionMethodReturnValueHandler) returnValueHandler;
break;
}
}
this.subscriptionMethodReturnValueHandler = subscriptionMethodReturnValueHandler;
}
#SubscribeMapping("/greeting/{name}")
public void greet(#DestinationVariable String name, Message<?> message) throws Exception {
subscriptionMethodReturnValueHandler.handleReturnValue("Hello " + name, new MethodParameter(Object.class.getMethods()[0], -1), message);
}
}
So my question is simple: Is there a better way?

Related

Mono response from a method which returns void

I have a service method which does not result anything, but can return an HttpException.
example
class Service{
public void myService() throws HttpException{
//do something
}
}
My calling class has a method which is supposed to return a Mono. This method calls myService().
class Caller{
#Autowire
Service service;
public Mono<Response> callMyService(){
return Mono.just("abc")
.doOnSuccess(service.myService())
.thenReturn(new Response()); //this should return Mono<Response>
}
}
My question, is how can I write callMyService() in a good way? Mono.just("abc") doesn't seem right implementation.
You should use Mono<Void> for this purpose. This mono will not forward any data, it will only signal error or completion.
You can create it using then()
Also, remember that doOnSuccess() is side effect. You should not use it for data processing, maybe use map() or flatMap(). For you case, maybe you can use Mono.fromCallable(()->service.myService()), but that may not be correct depending on what service actually does.

How to mock a particular method of a spring bean

I have a spring bean with multiple APIs. Mocking the bean doesn't serve my purpose as I would like to verify fetchFromDb() called only once on multiple calls to getCachedData() with the same input. This is to make sure the result is cached.
Is it possible to mock fetchFromDb() on bean 'market' while calling getCachedData()?
Sample Class
#Configuration("market")
public class AllMarket {
#Autowired
private CacheManager cachedData;
public boolean getCachedData(LocalDate giveDate) {
//check if it exists in cache
if(Objects.nonNull(checkCache(giveDate)) {
return checkCache(giveDate);
}
//fetch from database
boolean bool = fetchFromDb(givenDate);
cacheData(giveDate, bool);
return bool;
}
public boolean checkCache(LocalDate giveDate) {
return cacheManager.getData(givenDate);
}
public boolean fetchFromDb(LocalDate givenDate) {
//return the data from database
}
public void cacheData(LocalDate givenDate, boolean bool) {
cacheManager.addToCache(givenDate, bool);
}
}
You can use Mockito.spy() for this kind of test. In this case you should spy your AllMarket instance and stub fetchFromDb. At the end you can Mockito.verify that fetchFromDb was called exactly once. It will look something like this:
AllMarket spy = spy(allMarket);
when(spy.fetchFromDb(givenDate)).thenReturn(true); //you have boolean as a return type
...
verify(spy, times(1)).fetchFromDb(givenDate);
For more information, you can see Official Mockito doc
Maybe mockito argument captor could asist you. It lets you to capture method input and how many times method was called, also may other functions. Please check https://www.baeldung.com/mockito-annotations.

Any way to find other annotations on methods that are annotated with EventListeners using the resulting ApplicationListener returned from the context?

It looks like the ApplicationListenerMethodAdapter hides the method it is annotated for making it impossible to look if that method potentially contains other Annotations. There is some other way around this?
if i have an event listener like this
#EventListener
#SomeOtherAnnotation
public void onSomeEvent(SomeEvent e) {
...
}
and a custom event multicaster
public class CustomEventMulticaster extends SimpleApplicationEventMulticaster {
public <T extends ApplicationEvent> void trigger(final T event,
Function<ApplicationListener<T>, Boolean> allowListener) {
...
}
}
i'd like to do something like trigger only if some annotation exists
customEventMulticaster.trigger(someEvent, (listener) -> {
return listener.getClass().getAnnotation(SomeOtherAnnotation.class) == null;
})
There is a hacky solution - just as case study - but please don't go that way.
Since your application listener is in fact ApplicationListenerMethodAdapter you can use reflection to get method or targetMethod from that class. From there you can get method annotations.
More or less (not checked, pure notepad here)
customEventMulticaster.trigger(someEvent, (listener) -> {
Field f=((ApplicationListenerMethodAdapter)listener).getDeclaredField("method"); // or 'targetMethod' - consult ApplicationListenerMethodAdapter to get the difference
f.setAccessible(true);
Method m=f.get(listener); // cast again if required
anno=m.getAnnotation(yourAnno); // here you can access annotation
return anno == null;
})
To make this at least to pretend ot be safe, add nullchecks and check if listener is indeed castable to ApplicationListenerMethodAdapter

Cache not refreshing when being called from a asynchrounous function in Spring

I am calling a function which has CacheEvict annotation on it. This is being called from a function that is itself executed asynchronously.
It seems that the cache is not being evicted after the function has been executed.
Here is sample code
#Async("executor1")
public void function1()
{
// do something
anotherFunction("name", 123, 12);
// do something more
}
#CacheEvict(cacheNames = {"cache1", "cache2", "cache3"}, key = "#testId")
public List<Integer> anotherFunction(String name, int testId, int packageId)
{
// some code here
}
What I want is that entries corresponding to testId should be cleared from all the caches.
However, in another call, I can see old entries of cache1. function1 is being called from the controller. Both these functions are present inside the service. Now, Is this configuration correct? If yes, What may be the possible reasons that cache is not being cleared?
Any help appreciated. Thanks in advance.
I think your problem is that Spring proxies are not reentrant. To implement Async and CacheEvict, Spring creates a proxy. So, in your example, the call stack will be:
A -> B$$proxy.function1() -> B.function1() -> B.anotherFunction()
B$$proxy contains the logic for async and eviction. Which won't apply when calling directly anotherFunction. In fact, even if you remove the #Async, it will still don't work.
A trick you can use is to inject the proxied bean into the class. To delegate to the proxy of the class instead this.
public class MyClass {
private MyClass meWithAProxy;
#Autowired
ApplicationContext applicationContext;
#PostConstruct
public void init() {
meWithAProxy = applicationContext.getBean(MyClass.class);
}
#Async("executor1")
public void function1() {
meWithAProxy.anotherFunction("name", 123, 12);
}
#CacheEvict(cacheNames = "cache1", key = "#testId")
public List<Integer> anotherFunction(String name, int testId, int packageId) {
return Collections.emptyList();
}
}
It works. But there's a catch. If you now call anotherFunction directly, it won't work. I consider this to be a Spring bug and will file it as is.

Why is MassTransit using ConstructorHandling.AllowNonPublicDefaultConstructor for message deserialization?

I'm trying to incorporate MassTransit in a project that also uses NHibernate. NHibernate requires me to have at least a default constructor with protected internal visibility.
I run into the following problem. Messages can be published without any problem, however the handlers receive the message objects with uninitialized members. After some period of debugging and inspection of the MassTransit sources I found out that this is caused by the fact that MassTransit uses the setting ConstructorHandling.AllowNonPublicDefaultConstructor during deserialization, which causes my protected internal default constructor to be called instead of the parametrized constructor. I managed to reproduce this behavior, see code below.
What's the reason behind MassTransit's use of AllowNonPublicDefaultConstructor, and is there any way to change this behavior?
class Program
{
public class TestClass
{
private readonly string _someString;
public string SomeString {
get { return _someString; }
}
public TestClass(string someString)
{
_someString = someString;
}
protected internal TestClass()
{
_someString = "uninitialized";
}
}
static void Main(string[] args)
{
var obj = new TestClass("Hello World");
var serializerSettings = new JsonSerializerSettings
{
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
ContractResolver = new ... // MassTransit contract resolver that includes private setters
};
string serializedObject = JsonConvert.SerializeObject(obj, serializerSettings);
var deserializedObj = JsonConvert.DeserializeObject<TestClass>(serializedObject, serializerSettings);
// deserializedObj.SomeString == "uninitialized"
}
}
Messages shouldn't have any logic in them at all. Messages are contracts. Any logic in them will only end up bitting you again and again. :( We will always use a default, no parameter, constructor. If there isn't one we won't deserialize your message.
We suggest that you alway consume interfaces instead of concrete types to help enforce the removal of logic from message types. But if you really want to have this behaviour you'll need to write your own serializer.
If you want to discuss further, I suggest you join the mailing list: groups.google.com/group/masstransit-discuss.

Resources