I have an app, which connects to an API every day and fetches a data (schedulers run every 24h). I would like to add such a functionality:
after user registration call schedulers and force fetching data right now. Could you recommend the best approach in Spring?
#Component
public class GetMyFeeEstimateScheduler extends Scheduler {
#Scheduled(fixedDelay = DELAY)
#Transactional
public void fetchGetMyFeeEstimate() throws Exception {
fetchData();
}
#PostMapping("/signup")
public void signUp(#RequestBody SignUpRequest signUpRequest) {
// ...
// CALL_SCHEDULERS
}
I would prefer to expose the fetchData() as a public method in a service class and then call from the controller and from scheduler.
Another option is to call fetchGetMyFeeEstimate directly from the controller.
Related
Is this the correct way to use #Async in Spring Boot?
#Service
class someServiceImpl {
...
public someResponseDTO getUsers(int userId) {
// Do some logic
...
// Call external API with another service method from another service impl
anotherService.emailUserInTheBackground(userId);
return someResponseDTO;
}
...
}
#Service
public class AnotherService {
#Async
public void emailUserInTheBackground(int userId) {
// This might take a while...
...
}
}
Since emailUserInTheBackground() has #Async annotation and void return type, does it block the line return someResponseDTO at all?
All I wanted is to return the response to the caller without waiting because emailUserInTheBackground() takes too long to complete and isn't directly tied to the response object.
Yes that is the correct way to run a task in the background, you can mimick the thread blocking behavior by introducing a delay.
#SpringBootApplication
#EnableAsync
public class MyApplication {
public static void main(String[] arg) {
SpringApplication.run(MyApplication.class);
}
}
then you need to mark the emailUserInTheBackground method with #Async annotation.
#Service
class AnotherService {
#Async
public void emailUserInTheBackground(int userId) {
try {
TimeUnit.SECONDS.sleep(10);
System.out.println("Print from async: "+ Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Now add one more logger after a method call, you'll see getUsers(...) call completing first in a different thread even though the emailService thread is blocked for 10 seconds.
anotherService.emailUserInTheBackground(userId);
System.out.println("Print from service: "+ Thread.currentThread().getName());
you can also use CompletableFuture to run a task in the background.
public someResponseDTO getUsers(int userId) {
// some other task
...
// Call external API with another service method from another service impl
CompletableFuture.runAsync(() -> anotherService.emailUserInTheBackground(userId))
return someResponseDTO;
}
The relevant behavior of #Async is documented in the Spring documentation:
You can provide the #Async annotation on a method so that invocation of that method occurs asynchronously. In other words, the caller returns immediately upon invocation, while the actual execution of the method occurs in a task that has been submitted to a Spring TaskExecutor.
In the case you're describing, since the emailUserInTheBackground method is annotated with #Async and Spring's asynchronous method execution capability is enabled, the emailUserInTheBackground method will return immediately, and the call will be processed in a separate thread. The someResponseDTO value will be be returned from the getUsers method while the emailUserInTheBackground method continues to be processed in the background.
this is a question more about communication between service and controller annotated classes in spring boot. I have a RestController class that exposes a POST mapping which calls a method in the Service class. Now this method may take a long time running; hence there is a need to send some kind of feedback to the controller.
Is there any mechanism which allows a service to call/update a method/variable in the controller?
one of the most simplest ways is passing some lamda function from the controller to the service and call it from the service like this
Controller Class
#RestController
public class controller {
#Autowired
Service service;
public void foo() {
service.foo(..parms, (message/*any params you want*/) -> {
// here the body that will receive the message from the service
System.out.print(message);
});
}
}
Service Class
public class Service {
// updateStatus here is the function you will send the update to the controller from
public void foo(...params, updateStatus) {
updateStatus("starting the process...");
// do some code
updateStatus("in progress...");
// do some code
updateStatus("completed");
}
}
I have Spring event Publisher and listener where the requirement is as below:
Trigger the event from some method.
Listen the event from listener class
As soon as event is listened Call another micro-service
So currently i am using openfeign to call another micro-service.
This call works fine when the listener method runs Synchronously and it fails when listener method is marked as #Async.
Below is the sample code:
Fiegn Interface configuration:
#AuthorizedFeignClient(name = "another-microservice")
public interface CustomFeignInterface {
#PostMapping("api/call")
public ResponseEntity<String> callMethod(#RequestBody Foo foo);
}
Spring event Listener:
#Component
public class FooEventListener {
#Autowired
private CustomFeignInterface customFeignInt;
#Async
#EventListener
public void handleCustomEvent(Foo foo) {
try {
ResponseEntity<String> res = customFeignInt.callMethod(foo);
} catch (Exception e) {
e.printStackTrace();
}
}
}
As per above code Call to another micro-service works perfect when i remove #Async but fails when i add #Async to FooEventListener.handleCustomEvent(..) method.
Somewhere i found that Feign does not support Async calls:
https://github.com/OpenFeign/feign/issues/361
But still i need async behavior how can i achieve that?
I need to send the email/sms/events as a background async task inside spring boot rest.
My REST controller
#RestController
public class UserController {
#PostMapping(value = "/register")
public ResponseEntity<Object> registerUser(#RequestBody UserRequest userRequest){
// I will create the user
// I need to make the asyn call to background job to send email/sms/events
sendEvents(userId, type) // this shouldn't block the response.
// need to send immediate response
Response x = new Response();
x.setCode("success");
x.setMessage("success message");
return new ResponseEntity<>(x, HttpStatus.OK);
}
}
How can I make sendEvents without blocking the response (no need to get the return just a background task) ?
sendEvents- call the sms/email third part api or send events to kafka topic.
Thanks.
Sounds like a perfect use case for the Spring #Async annotation.
#Async
public void sendEvents() {
// this method is executed asynchronously, client code isn't blocked
}
Important: #Async works only on the public methods and can't be called from inside of a single class. If you put the sendEvents() method in the UserController class it will be executed synchronously (because the proxy mechanism is bypassed). Create a separate class to extract the asynchronous operation.
In order to enable the async processing in your Spring Boot application, you have to mark your main class with appropriate annotation.
#EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application.class);
application.run(args);
}
}
Alternatively, you can also place the #EnableAsync annotation on any #Configuration class. The result will be the same.
I am currently implementing Spring Security in my application. I did manage to put #Secured annotation on my service that getAllUsers() from the database, and it is working fine as long as the user is identified (depending on his rights, he can get or not the list of users).
But I have a #Scheduled method in charge of indexing all users, and when it is launched it call the same protected getAllUsers() method, and obviously crashes as it is not logged in : I get the following exception :
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
I'm currently thinking of one possible solution, which would be to mark the internal methods with a custom annotation, which would be retrieved by a custom AccessDecisionVoter allowing the caller to call the protected method.
I'm looking for best practice for this kind of usecase
Because method is #Secured and spring expect security authentication object in context. Here is working example of AccessDecisionVoter Spring-security - AccessDecisionVoter-impl wont be invoked
or if u will have filters or smth which will depends on user context values this one should be ok
#Scheduled
public void method() {
try {
ScheduledAuthenticationUtil.configureAuthentication();
// do work
}
catch(Exception e) {
e.printStackTrace();
}
finally {
ScheduledAuthenticationUtil.cleanAuthentication();
}
}
private static class ScheduledAuthenticationUtil {
public static void configureAuthentication() {
// inject auth obj into SecurityContextHolder
}
public static void cleanAuthentication() {
// SecurityContextHolder clean authentication
}
}
I assume your service class looks like :
public class MyServiceImpl implements MyService {
...
#Secured
public Xxx getAllUsers() {
...
// call DAO
...
return xxx;
}
...
}
And you call myService.getAllUsers() from the #Scheduledclass.
The simplest way is to split getAllUsers and make the service class inherit from 2 interfaces, one containing the secured method, and one that would contain a publically accessible version :
public class MyServiceImpl implements MyService, MyScheduledService {
...
#Secured
public Xxx getAllUsers() {
return restrictedGetAllUsers;
}
public Xxx restrictedGetAllUsers() {
...
// call DAO
...
return xxx;
}
...
}
public interface MyService {
Xxx getAllUsers();
}
public interface MyScheduledService extends MyService {
Xxx restrictedGetAllUsers();
}
Then in your controller class :
#Autowired MyService myService => will call only getAllUsers()
and in your #Scheduled class :
#Autowired MyScheduledService myService => will call restrictedGetAllUsers()
All that may seem overcomplicated, but as your scheduled class and you controller have no reason to call the service methods the same way, it make sense to present them two different interfaces with different security requirements.
I went with kxyz answer, improved with a service that run a piece of code by setting the wanted Authorities before running the code, and putting back the previous authorities when the code is done :
public void runAs(Runnable runnable, GrantedAuthority... authorities) {
Authentication previousAuthentication = SecurityContextHolder.getContext().getAuthentication();
configureAuthentication(authorities);
try {
runnable.run();
} finally {
configureAuthentication(previousAuthentication);
}
}
protected void configureAuthentication(GrantedAuthority... authorities) {
Authentication authentication = new UsernamePasswordAuthenticationToken("system", null, Arrays.asList(authorities));
configureAuthentication(authentication);
}
protected void configureAuthentication(Authentication authentication) {
SecurityContextHolder.getContext().setAuthentication(authentication);
}
Reffers to PhilippeAuriach answer - there is a better way to run new thread with authorites - with spring security extended runnable method, context from main thread is copied into delegated runnable
public void authorizedExecute(Runnable runnable) {
new Thread(new DelegatingSecurityContextRunnable(runnable)).start();
}