I am writing a java/spring library to include in other projects that are using quartz.
I need it to log some information about the task/calling class everytime a job is executed.
For example, if a quartz job looks like this:
#Bean
public JobDetail jobADetail() {
return JobBuilder.newJob(QuartzTaskA.class)
.withIdentity("sampleJobA")
.storeDurably().build();
}
#Bean
public Trigger jobATrigger(JobDetail jobADetail) {
return TriggerBuilder.newTrigger()
.forJob(jobADetail)
.withIdentity("sampleTriggerA")
.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * ? * * *"))
.build();
}
public class QuartzTaskA implements Job {
#Override
public void execute(JobExecutionContext jobExecutionContext) {
log.info("QuartzA - the time is now {}", dateFormat.format(new Date()));
}
}
I want it to log something like:
Job [QuartzTaskA] with details [sampleJobA] and trigger [sampleTriggerA] is starting
QuartzA - the time is now 12:07:39
I customize the SchedulerFactoryBean with a custom TaskExecutor that does a log before the task.run().
This works and I am able print the additional first line, but I can't figure out how to get the details/context to pass into the log.
#Configuration
public class SchedulerFactoryCustomizer implements SchedulerFactoryBeanCustomizer {
private static final Logger log = LogManager.getLogger(SchedulerFactoryCustomizer.class);
#Override
public void customize(SchedulerFactoryBean schedulerFactoryBean) {
Executor executor = SchedulerFactoryBean.getConfigTimeTaskExecutor();
schedulerFactoryBean.setTaskExecutor(new CustomExecutor(executor);
}
private static class CustomExecutor implements Executor {
final Executor executor;
private CustomExecutor(Executor executor) {
this.executor = executor;
}
#Override
public void execute(Runnable task) {
// This line here. How can I get the details/context to pass in?
//log.info("Job {} with details {} and trigger {} is starting");
task.run();
}
}
}
how can I get the details/context to pass into the log?
You can implement a JobListener/TriggerListener
public class LoggingTriggerListener implements TriggerListener {
#Override
public String getName() {
return null;
}
#Override
public void triggerFired(final Trigger trigger, final JobExecutionContext context) {
}
#Override
public boolean vetoJobExecution(final Trigger trigger, final JobExecutionContext context) {
return false;
}
#Override
public void triggerMisfired(final Trigger trigger) {
}
#Override
public void triggerComplete(final Trigger trigger, final JobExecutionContext context, final Trigger.CompletedExecutionInstruction triggerInstructionCode) {
}
}
It injects jobExecutionContext also.
Related
Below the code which is executed through the cron and from this class, I'm calling my configuration class for manipulating the messages. while manipulating the message I want to interrupt this job through the rest API's.
public class CronJob extends QuartzJobBean implements InterruptableJob
{
private volatile boolean toStopFlag = true;
#Autowired
#Lazy
SchedulerFactoryBean schedulerFactoryBean;
#Autowired
MessageConfiguration messageConfiguration;
#Override
public void executeInternal(JobExecutionContext jobExecutionContext)
throws JobExecutionException
{
Scheduler scheduler = schedulerFactoryBean.getScheduler();
messageConfiguration.excuteMessageConfiguration(jobExecutionContext,
scheduler);
}
#Override
public void interrupt() throws UnableToInterruptJobException
{
System.out.println("Stopping thread... ");
toStopFlag = false;
}
}
************** Trying to interrupt the job*******
Here I'm trying to interrupt the job through the Rest API. Passing a job name statically.
public void interruptJob() {
List<JobExecutionContext> currentlyExecuting = scheduler.getCurrentlyExecutingJobs();
for (JobExecutionContext jobExecutionContext : currentlyExecuting) {
if(jobExecutionContext.getJobDetail().getKey().getName().equals("**JobName**")){
Object result = scheduler.interrupt(jobExecutionContext.getJobDetail().getKey());
System.out.println("stoppeed");
}
}
}
The issue is interrupt method is executed but the job is running continuously.
Helping hand will be appreciated.
This question is an architectural problem that I have not been able to figure out.
I have a TaskScheduler that has operations such as start() and stop(). TaskScheduler is intended to be agnostic, I want to be able to pass into it any "Runnable", a "UID" and the "Interval" that the service should run for. This all gets added to a hashmap so that if you try to pass in an existing runnable with the same UID it will replace the previous runnable with the new information.
Extending the TaskScheduler is MyScheduler, which is specific to the request that I want to make. In this example, I am making multiple Profile requests every 60 seconds. To keep track of which profile request is which, I am using UID as a key.
I want to then bubble up the responses to the app level from MyScheduler. This is where I am having issues. I am only able to bubble up the response from the latest scheduler. So if I create Scheduler A and Scheduler B, I will receive updates only from Scheduler B. Simlarly, if I create Scheduler A-C, then I will receive updates only from Scheduler C.
I know why this is, MyScheduler uses the last request that was passed into it. However, I do not know a good pattern (methodology) to resolve this.
TaskScheduler class
public class TaskScheduler {
private static Map<String, SchedulerModel> schedulerModels = new HashMap<>();
TaskScheduler() {}
private ScheduledFuture<?> start(#NotNull final SchedulerModel schedulerModel) {
return schedulerModel.executorService.scheduleWithFixedDelay(schedulerModel.runnable, 0, schedulerModel.interval, TimeUnit.SECONDS);
}
/**
* Method is used to onSchedulerStop executing tasks
*/
private void shutdown(#NotNull SchedulerModel schedulerModel) {
if (schedulerModel.executorService != null) {
schedulerModel.executorService.shutdownNow();
schedulerModel.executorService = null;
}
}
/**
* Method is used to initialize scheduler task and time delays
*
* #param runnable Represents a command that can be executed
* #param interval The time interval for execution of code
*/
void setTask(Runnable runnable, String uid, int interval) {
SchedulerModel schedulerModel = new SchedulerModel();
schedulerModel.executorService = Executors.newSingleThreadScheduledExecutor();
schedulerModel.runnable = runnable;
schedulerModel.interval = interval;
schedulerModels.put(uid, schedulerModel);
}
public void stop(#NotNull String uid) {
if (schedulerModels.get(uid) != null) {
shutdown(schedulerModels.get(uid));
schedulerModels.remove(uid);
} else {
// scheduler id not found
}
}
public void start(#NotNull String uid) {
if (schedulerModels.get(uid) != null) {
start(schedulerModels.get(uid));
} else {
// scheduler id not found
}
}
}
MyScheduler (this name is temporary)
public class MyScheduler extends TaskScheduler {
private final int DEFAULT_SCHEDULER_INTERVAL = 60; // seconds
private ProfileRequest request;
private ApiInterface apiInterface;
private SchedulerInterface schedulerInterface;
public MyScheduler() {}
public void createScheduler(#NotNull ApiInterface apiInterface,
#NotNull ProfileRequest request,
#NotNull SchedulerInterface schedulerInterface) {
this.apiInterface = apiInterface;
this.request = request;
this.schedulerInterface = schedulerInterface;
super.setTask(new SchedulerRunnable(), request.getUid(), DEFAULT_SCHEDULER_INTERVAL);
}
public void start(#NotNull String uid) {
start(uid); // start scheduler
schedulerInterface.onSchedulerStart(uid); // send feedback to callback
}
public void stop(#NotNull String uid) {
stop(uid); // stop scheduler
schedulerInterface.onSchedulerStop(uid); // send feedback to callback
}
private class SchedulerRunnable implements Runnable {
#Override
public void run() {
ApiClient.createBookmark(request, new Callback<Response>() {
#Override
public void onSuccess(#NotNull Response response) {
schedulerInterface.onSuccess(response);
}
#Override
public void onFailure(#NotNull Exception exception) {
schedulerInterface.onFailure(exception);
}
});
}
}
}
Trying to achieve this on app level
mProfileScheduler.createScheduler(apiInterface, request, new SchedulerInterface {
Override
public void onSuccess(Response response) {
// problem so far is that I only get response from latest scheduler
}
Override
public void onFailure(Exception exception) {}
Override
public void onSchedulerStop(String uid) {
// pass back uid so that I know which profile scheduler was stopped
}
Override
public void onSchedulerStart(String uid) {}
// pass back uid so that I know which profile scheduler was started
}
});
You have this problem because schedulerInterface is a member of MyScheduler.
Thus it's shared across all tasks and is overwritten after each new task is submitted.
Solution here is to make schedulerInterface a member of SchedulerRunnable:
private class SchedulerRunnable implements Runnable {
private SchedulerInterface schedulerInterface;
SchedulerRunnable(SchedulerInterface schedulerInterface) {
this.schedulerInterface = schedulerInterface;
}
}
In order to invoke onSchedulerStop() and onSchedulerStart() you can make start() and stop in TaskScheduler return Runnable. Then in MyTaskScheduler you would cast it to SchedulerRunnable to obtain a reference to the schedulerInterface.
If you don't want Runnable to be returned as part of public interface, you can create protected methods e.g. Runnable doStart() and Runnable doStop that can be overridden and are invoked by void start() and void stop().
Other issues
Concurrency
You are using HashMap for TaskScheduler schedulerModels. It is not thread-safe.
This is OK if you do not intend to access it from more than one thread.
Otherwise, you may encounter issues with race conditions and memory visibility.
You should use ConcurrentHashMap and its atomic operations like computeIfPresent() or computeIfAbsent() instead of put.
Resource management
When you replace an existing task with a new one with same UID you neither stop its executor service nor cancel the currently running task. Thus you are going to leak threads and previous runnable will keep running.
You create a new SingleThreadExecutorService for each task. That makes number of used threads potentially unbounded and makes it's hard to make any guarantees about application resource consumption. Normally you would use a thread pool with fixed number of threads that are reused between tasks.
Again, I suggest reading "Java Concurrency in Practice" book to learn about these problems and patterns to solve them.
Full Solution
After talking in chat this is my suggested solution.
I've mocked all unspecified classes and interfaces.
import org.jetbrains.annotations.NotNull;
import java.util.StringJoiner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class Main {
private static class MySchedulerInterface implements SchedulerInterface {
private final ProfileRequest request;
public MySchedulerInterface(ProfileRequest req1) {
this.request = req1;
}
#Override
public void onSuccess(String response) {
System.out.println("onSuccess:[" + request + "]" + response);
}
#Override
public void onFailure(Exception exception) {
System.out.println("onFailure:[" + request + "]" + exception);
}
#Override
public void onSchedulerStop(String uid) {
System.out.println("onSchedulerStop:[" + request + "] - " + uid);
}
#Override
public void onSchedulerStart(String uid) {
System.out.println("onSchedulerStart:[" + request + "] - " + uid);
}
}
public static void main(String[] args) throws InterruptedException {
ApiInterface api = new ApiInterface();
ProfileRequest req1 = new ProfileRequest("1", "apple");
ProfileRequest req2 = new ProfileRequest("2", "orange");
ProfileRequest req3 = new ProfileRequest("3", "peach");
ProfileRequest req11 = new ProfileRequest("1", "pineapple");
MyScheduler scheduler = new MyScheduler();
scheduler.createScheduler(api, req1, new MySchedulerInterface(req1));
scheduler.createScheduler(api, req2, new MySchedulerInterface(req2));
scheduler.createScheduler(api, req3, new MySchedulerInterface(req3));
System.out.println("Created 3 tasks");
TimeUnit.SECONDS.sleep(2);
System.out.println("Starting 3 tasks");
scheduler.start("1");
scheduler.start("2");
scheduler.start("3");
System.out.println("Started 3 tasks");
TimeUnit.SECONDS.sleep(10);
System.out.println("Replacing task 1...");
scheduler.createScheduler(api, req11, new MySchedulerInterface(req11));
System.out.println("Replaced task 1.");
TimeUnit.SECONDS.sleep(10);
System.out.println("Stopping 3 tasks...");
scheduler.stop("1");
scheduler.stop("2");
scheduler.stop("3");
System.out.println("The end.");
}
}
class ProfileRequest {
private final String uid;
private final String value;
public ProfileRequest(String uid, String value) {
this.uid = uid;
this.value = value;
}
public String getUid() {
return uid;
}
public String getValue() {
return value;
}
#Override
public String toString() {
return new StringJoiner(", ", ProfileRequest.class.getSimpleName() + "[", "]")
.add("uid='" + uid + "'")
.add("value='" + value + "'")
.toString();
}
}
class ApiInterface {
public void createBookmark(ProfileRequest request, Callback<String> stringCallback) {
stringCallback.onSuccess("SUCCESS: I'm done with " + request);
}
}
interface SchedulerInterface {
void onSuccess(String response);
void onFailure(Exception exception);
void onSchedulerStop(String uid);
void onSchedulerStart(String uid);
}
interface Callback<T> {
void onSuccess(#NotNull T response);
void onFailure(#NotNull Exception exception);
}
class MyScheduler extends TaskScheduler {
private final int DEFAULT_SCHEDULER_INTERVAL = 2; // seconds
public MyScheduler() {
}
public void createScheduler(#NotNull ApiInterface apiInterface,
#NotNull ProfileRequest request,
#NotNull SchedulerInterface schedulerInterface) {
super.setTask(new SchedulerRunnable(apiInterface, request, schedulerInterface), request.getUid(), DEFAULT_SCHEDULER_INTERVAL);
}
#Override
public ScheduledTask doStart(#NotNull String uid) {
final ScheduledTask task = super.doStart(uid);
if (task != null) {
final SchedulerRunnable runnable = (SchedulerRunnable) task.runnable;
runnable.schedulerInterface.onSchedulerStart(uid);
}
return task;
}
#Override
protected ScheduledTask doStop(#NotNull String uid) {
final ScheduledTask task = super.doStop(uid);
if (task != null) {
final SchedulerRunnable runnable = (SchedulerRunnable) task.runnable;
runnable.schedulerInterface.onSchedulerStop(uid);
}
return task;
}
private class SchedulerRunnable implements Runnable {
private final ApiInterface apiInterface;
private final ProfileRequest request;
private final SchedulerInterface schedulerInterface;
SchedulerRunnable(ApiInterface apiInterface, ProfileRequest request, SchedulerInterface schedulerInterface) {
this.apiInterface = apiInterface;
this.request = request;
this.schedulerInterface = schedulerInterface;
}
#Override
public void run() {
apiInterface.createBookmark(request, new Callback<String>() {
#Override
public void onSuccess(#NotNull String response) {
schedulerInterface.onSuccess(response);
}
#Override
public void onFailure(#NotNull Exception exception) {
schedulerInterface.onFailure(exception);
}
});
}
}
}
class SchedulerModel {
ScheduledExecutorService executorService;
Runnable runnable;
int interval;
}
class TaskScheduler {
static class ScheduledTask {
String uid;
Runnable runnable;
int interval;
ScheduledFuture<?> future;
ScheduledTask(String uid, Runnable runnable, int interval, ScheduledFuture<?> future) {
this.uid = uid;
this.runnable = runnable;
this.interval = interval;
this.future = future;
}
void dispose() {
if (future != null) {
future.cancel(true);
}
}
boolean isScheduled() {
return future != null;
}
}
private ConcurrentMap<String, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>();
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
TaskScheduler() {
}
/**
* Method is used to initialize scheduler task and time delays
*
* #param runnable Represents a command that can be executed
* #param interval The time interval for execution of code
*/
void setTask(Runnable runnable, String uid, int interval) {
AtomicBoolean requiresRestart = new AtomicBoolean(false);
final ScheduledTask task = scheduledTasks.compute(uid, (id, oldTask) -> {
ScheduledTask newTask = new ScheduledTask(uid, runnable, interval, null);
if (oldTask != null) {
oldTask.dispose();
requiresRestart.set(oldTask.isScheduled());
}
return newTask;
});
if (requiresRestart.get()) {
start(uid);
}
}
public void start(#NotNull String uid) {
doStart(uid);
}
public void stop(#NotNull String uid) {
doStop(uid);
}
protected ScheduledTask doStart(#NotNull String uid) {
final ScheduledTask scheduledTask = scheduledTasks.computeIfPresent(uid, (id, oldTask) -> {
ScheduledFuture<?> future = executor.scheduleWithFixedDelay(
oldTask.runnable, 0, oldTask.interval, TimeUnit.SECONDS);
ScheduledTask newTask = new ScheduledTask(oldTask.uid, oldTask.runnable, oldTask.interval, future);
return newTask;
});
return scheduledTask;
}
protected ScheduledTask doStop(#NotNull String uid) {
final ScheduledTask task = scheduledTasks.remove(uid);
task.dispose();
return task;
}
}
Below is the class I want to test :
SomeClass.java
public void SomeClass {
final CountDownLatch latch = new CountDownLatch(1);
int result;
registerCallbackWithService(new MyCallback());
public int callToExternalService(){
//Do some stuff and make service call
latch.await();
return result;
}
class MyCallback implements ServiceCallback {
#Override
public void onResult(final int res) {
//do something
result = res;
latch.countdown();
}
}
}
The callback MyCallback was registered earlier before invoking callToExternalService().
If I write a simple test to just mock the service call made in callToExternalService(), the test keeps on running infinitely because of latch.await().
How can I test the logic in callToExternalService() as well as in onResult() ?
I modified by code to expose the callback that I am registering using a package-protected function as below :
public void SomeClass {
private final CountDownLatch latch = new CountDownLatch(1);
private int result;
registerCallback(new MyCallback());
public int callToExternalService(){
//Do some stuff and make service call
latch.await();
return result;
}
private class MyCallback implements ServiceCallback {
#Override
public void onResult(final int res) {
//do something
result = res;
latch.countdown();
}
}
protected registerCallback(ServiceCallback callback) {
registerCallbackWithService(callback);
}
}
Now, for testing I do my testing by creating a new class SomeClassTest extends SomeClass and do my testing using an instance of this class. In SomeClassTest all I do is override registerCallback() to access the callback instance that is being registered.
public class ServiceTest {
private ServiceCallback mServiceCallback;
class SomeClassTest extends SomeClass {
#Override
registerCallback(ServiceCallback callback) {
mServiceCallback = callback;
super.registerCallback(callback);
}
}
}
Now all I have to do it using doAnswer, invoke the callback upon service request which results in the execution of latch.countdown() on the same latch reference that is put on await just after making the service request.
SomeClassTest someClassInstance = new SomeClassTest();
doAnswer(new Answer() {
#Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
mServiceVCallback.onResult(int_I_want_to_test_for)
return null;
}
}).when(someClassInstance).service_request_before_latch_await();
int response = someClassInstance.callToExternalService();
assertEquals(response, expected_response);
Quartz is creating new instance of the class through the JobBuilder each time
JobBuilder.newJob(MyJob.class)
However, I only want one MyJob instance, and only trigger testFunction from execute function, how can I make it work?
I find through QuartzGuiceLib I can use some annotations to make it happen, through Spring I can change something in configuration file. But how can I implement it by pure Java without any framwork?
Below is the code snapshot:
public class MyJob implements Job {
public MyJob() {
testFunction();
try {
final Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
if (!scheduler.checkExists(JOB_KEY)) {
triggerScheduler(scheduler);
} else {
log.info("Job with key {{}} already exist", JOB_KEY);
}
} catch (SchedulerException e) {
log.error("Fail to start scheduler", e);
}
}
public void testFunction() {
}
private void triggerScheduler(final Scheduler scheduler) throws SchedulerException {
final JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity(JOB_KEY)
.build();
final Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myjob")
.withSchedule(
simpleSchedule()
.withIntervalInSeconds(60)
.repeatForever())
.build();
scheduler.start();
log.info("Scheduling job with key {{}}", jobDetail.getKey());
scheduler.scheduleJob(jobDetail, trigger);
}
#Override
public void execute(JobExecutionContext context) throws JobExecutionException {
testFunction();
}
}
It might be easier to keep the job and scheduler in two separate classes as below:
public class MyQuartzScheduler {
public static void main( String[] args ) throws Exception {
JobDetail job = JobBuilder.newJob(MyJob.class)
.withIdentity("dummyJobName", "group1").build();
Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("MyJobTrigger", "group1")
.withSchedule(
CronScheduleBuilder.cronSchedule("0 * * * * ?"))
.build();
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.start();
scheduler.scheduleJob(job, trigger);
}
}
And then your Job Class:
public class MyJob implements Job {
public void testFunction() {
System.out.println("Running Test!");
}
#Override
public void execute(JobExecutionContext context) throws JobExecutionException {
testFunction();
}
}
This is an adaptation taken from an mkyong tutorial article found at:
https://www.mkyong.com/java/quartz-2-scheduler-tutorial/
For the answer to your question though, Quartz does create a new instance per run:
https://stackoverflow.com/a/10463309/1410671
You could make another static class or Factory that your Job class would call which would use the same instance every call.
public class MyJob implements Job {
public void testFunction() {
MyClassWithStaticCounter.increaseCounter(1);
System.out.println(MyClassWithStaticCounter.getCounter());
}
#Override
public void execute(JobExecutionContext context) throws JobExecutionException {
testFunction();
}
}
And your class that has the static stuff:
public class MyClassWithStaticCounter {
private static int counter = 0;
public static void increaseCounter(int i){
counter += i;
}
public static int getCounter(){
return counter;
}
}
#Override
#Async
public void asyncExceptionTest() {
int i=1/0;
}
How can I log this using Spring Async framework without having to put try catch around every async method? It doesn't seem to pass to the DefaultUncaughtExceptionHandler like normal.
#Async methods can be configured with a custom Executor to log any thrown exceptions.
The following code implements this pattern. Any method tagged with #Async will use the Executor returned by the method public Executor getAsyncExecutor(). This returns the HandlingExecutor which takes care of all logging (in this case it just prints the word "CAUGHT!" but you can replace with logging.
#Configuration
#EnableAsync
public class ExampleConfig implements AsyncConfigurer {
#Bean
public Runnable testExec() {
return new TestExec();
}
#Override
public Executor getAsyncExecutor() {
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(7);
executor.setMaxPoolSize(42);
executor.setQueueCapacity(11);
executor.setThreadNamePrefix("MyExecutor-");
executor.initialize();
return new HandlingExecutor(executor);
}
}
public class HandlingExecutor implements AsyncTaskExecutor {
private AsyncTaskExecutor executor;
public HandlingExecutor(AsyncTaskExecutor executor) {
this.executor = executor;
}
#Override
public void execute(Runnable task) {
executor.execute(task);
}
#Override
public void execute(Runnable task, long startTimeout) {
executor.execute(createWrappedRunnable(task), startTimeout);
}
#Override
public Future<?> submit(Runnable task) {
return executor.submit(createWrappedRunnable(task));
}
#Override
public <T> Future<T> submit(final Callable<T> task) {
return executor.submit(createCallable(task));
}
private <T> Callable<T> createCallable(final Callable<T> task) {
return new Callable<T>() {
#Override
public T call() throws Exception {
try {
return task.call();
} catch (Exception e) {
handle(e);
throw e;
}
}
};
}
private Runnable createWrappedRunnable(final Runnable task) {
return new Runnable() {
#Override
public void run() {
try {
task.run();
} catch (Exception e) {
handle(e);
}
}
};
}
private void handle(Exception e) {
System.out.println("CAUGHT!");
}
}
Update: Since Spring 4.1
Since Spring 4.1 It is possible to have an AsyncUncaughtExceptionHandler for #Async void methods.
Spring Reference Doc, Chapter 34.4.5 Exception management with #Async
... With a void return type however, the exception is uncaught and cannot be transmitted. For those cases, an AsyncUncaughtExceptionHandler can be provided to handle such exceptions.
By default, the exception is simply logged. A custom AsyncUncaughtExceptionHandler can be defined via AsyncConfigurer or the task:annotation-driven XML element.
(This feature was introduced after DD raised an impovement request: https://jira.spring.io/browse/SPR-8995 , see comments of this answer)
Before Spring 4.1
Looks like an missing feature how to handle exceptions of an void returning #Async Method. (I can not find any hint in the reference or java doc)
What I can imagine of an solution: Try to use AspectJ to write some kind of wrapper arround all #Async methods that log the exceptions.
For the log term, I would recommend to create an freature request in the spring bug tracker.
First off all, you should create a custom exception handler class like following;
#Component
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(AsyncExceptionHandler.class);
#Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
logger.error("Unexpected asynchronous exception at : "
+ method.getDeclaringClass().getName() + "." + method.getName(), ex);
}
}
After that, you should set your customized exception handler class in your configuration like following;
#Configuration
#EnableAsync
public class AsyncConfig extends AsyncConfigurerSupport {
#Autowired
private AsyncExceptionHandler asyncExceptionHandler;
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return asyncExceptionHandler;
}
}
Note : Injectable exception handler is an option. You can create a new instance for every exception. My advice is using Injection for exception handler class, because spring's default scope is singleton so there is no need to create new instance for every exception.
You can use standard Spring AOP approach
#Aspect
#Component
#Slf4j
public class AsyncHandler {
#Around("#annotation(org.springframework.scheduling.annotation.Async)")
private Object handle(ProceedingJoinPoint pjp) throws Throwable {
try {
Object retVal = pjp.proceed();
return retVal;
} catch (Throwable e) {
log.error("in ASYNC, method: " + pjp.getSignature().toLongString() + ", args: " + AppStringUtils.transformToWellFormattedJsonString(pjp.getArgs()) + ", exception: "+ e, e);
throw e;
}
}
}