Difference between ExitCodeGenerator and System.exit(0) - spring

I recently learned that the proper way to shut down a Spring Boot application is this:
public class Application {
#Bean
public ExitCodeGenerator exitCodeGenerator() {
return new ExitCodeGenerator() {
#Override
public int getExitCode() {
return 0;
}
};
}
public static void main(String[] args) throws Exception {
System.exit(SpringApplication.exit(SpringApplication.run(Application.class, args)));
}
}
This should return an exit code of 0, or whatever I configure it to return in the getExitCode() method. My question is - what is the difference between doing the approach above vs the one below:
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
System.exit(0);
}
}
The application seems to be shut down in exactly the same way by both approaches, at least in the console. So what's the difference?

The ExitCodeGenerator is used if you wish to return a specific exit code when SpringApplication.exit() is called. This exit code can then be passed to System.exit() to return it as a status code.
For example:
#SpringBootApplication
public class ExitCodeApplication {
#Bean
public ExitCodeGenerator exitCodeGenerator() {
return new ExitCodeGenerator() {
#Override
public int getExitCode() {
return 42;
}
};
}
public static void main(String[] args) {
System.exit(SpringApplication
.exit(SpringApplication.run(ExitCodeApplication.class, args)));
}
}
Also, the ExitCodeGenerator interface may be implemented by exceptions. When such an exception is encountered, Spring Boot will return the exit code provided by the implemented getExitCode() method.

Of course for you examples, this does not matter. But what this allows is to "glue" a certain Exception to a certain exit code. Spring, by default, catches all Exceptions that your code might throw - if such an Exception also implements ExitCodeGenerator - that will be used to output the exit code you have provided.
Of course, you can do that mapping by hand, but it's a lot more verbose and harder to maintain; Spring makes this easier. Once that is understood, you might also be interested in ExitCodeExceptionMapper that can map an underlying Exception (with let's say multiple error messages) to different error codes.

Related

Spring AOP exception handler - execute only first aspect in ordering

UPD I've updated code for Aspects to throw exception further
I have SpringBoot application, service class and I need to implement Exception Handler for my service (not MVC). The task is to log error and throw it further to the client.
I decided to use Aspect with #AfterThrowing advice. I'm gonna catch few exceptions (that extend RuntimeException) at AspectOne aspect. And for other cases I need to catch exceptions (that extend RuntimeException) at AspectTwo aspect.
So I did the following code:
public class MyOwnException extends RuntimeException {
}
#Aspect
#Order(0)
#Component
public class AspectOne {
#Pointcut("execution(* com.test.MyService.*(..))")
public void logException() {}
#AfterThrowing(pointcut="logException()", throwing="ex")
public void logException(MyOwnException ex) {
System.out.println("MyOwnException has been thrown: " + ex.getMessage());
throw ex;
}
}
#Aspect
#Order(1)
#Component
public class AspectTwo {
#Pointcut("execution(* com.test.MyService.*(..))")
public void logException() {}
#AfterThrowing(pointcut="logException()", throwing="ex")
public void logException(RuntimeException ex) {
System.out.println("Some unknown exception has been thrown: " + ex);
throw ex;
}
}
The problem is that AspectTwo is executed in both cases for MyOwnException and other ancestors of RuntimeException. How can I limit AspectTwo to be executed only when AspectOne haven't caught the exception?
Seems like #Order annotation works not as I expected.
How about a little hack to indicate an exception is already handled/adviced ?
Also note that , the order of execution to be AspectOne before AspectTwo here , the Order should be specified as 1 for AspectOne and 0 for AspectTwo.
From the reference documentation section : Advice Ordering
What happens when multiple pieces of advice all want to run at the
same join point? Spring AOP follows the same precedence rules as
AspectJ to determine the order of advice execution. The highest
precedence advice runs first "on the way in" (so, given two pieces of
before advice, the one with highest precedence runs first). "On the
way out" from a join point, the highest precedence advice runs last
(so, given two pieces of after advice, the one with the highest
precedence will run second).
Following code leverages the Throwable.addSuppressed() method to indicate an exception object is already handled/adviced.
--
Add an Exception class to be used as an indicator.
public class AlreadyAdvicedIndicator extends Exception {
private static final long serialVersionUID = 1L;
public AlreadyAdvicedIndicator(String message) {
super(message);
}
}
AspectOne with modified Order and logic to add a suppressed exception.
#Component
#Aspect
#Order(1)
public class AspectOne {
public static final String ALREADY_ADVICED_MSG="Adviced with AspectOne";
private static final AlreadyAdvicedIndicator alreadyAdviced = new AlreadyAdvicedIndicator(ALREADY_ADVICED_MSG);
#Pointcut("execution(* com.test.MyService.*(..))")
public void logException() {}
#AfterThrowing(pointcut="logException()", throwing="ex")
public void logException(MyOwnException ex) {
System.out.println("MyOwnException has been thrown: " + ex.getMessage());
ex.addSuppressed(alreadyAdviced);
}
}
AspectTwo with modified Order and logic to check for already adviced.
#Component
#Aspect
#Order(0)
public class AspectTwo {
#Pointcut("execution(* com.test.MyService.*(..))")
public void logException() {
}
#AfterThrowing(pointcut = "logException()", throwing = "ex")
public void logException(RuntimeException ex) {
if (isAlreadyAdviced(ex)) {
System.out.println("Already Adviced : Skipping");
} else {
System.out.println("RuntimeException has been thrown: " + ex.getMessage());
}
}
private boolean isAlreadyAdviced(RuntimeException ex) {
for(Throwable e : ex.getSuppressed()) {
if(AspectOne.ALREADY_ADVICED_MSG.equals(e.getMessage())){
return true;
}
}
return false;
}
}

spring boot - load app config before calling method run

is there a way to read yaml file of a spring boot app before launching the app, more exactly before calling method run.
here is my code :
#SpringBootApplication
public class CometeRestApi extends SpringBootServletInitializer {
private static void setMongoSecure() {
System.setProperty("javax.net.ssl.keyStore", System.getProperty("user.home") + "/.ssl/client-and-key.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "");
System.setProperty("javax.net.ssl.trustStore", System.getProperty("user.home") + "/.ssl/certificateChain.jks");
System.setProperty("javax.net.ssl.trustPassword", "");
}
#Override
protected SpringApplicationBuilder configure(final SpringApplicationBuilder application) {
setMongoSecure();
return application.sources(CometeRestApi.class);
}
public static void main(final String[] args) {
setMongoSecure();
SpringApplication.run(CometeRestApi.class, args);
}
}
thanks in advance for help
Yes, there are several ways to do it, but the recommended way is to use an EventListener. Annotating a method with #EventListener(ApplicationReadyEvent.class) is ideal when your purpose is to automatically execute code after your application startup.

How to bootstrap SpringBoot application (without mvc)?

I want to use spring-data & IOC container for some test app.
And the problem is: how to bootstrap the app?
In case of spring-mvc we move from controllers, but how to do this without mvc?
I need some main-like method for my code, but the application public static void main is already used for spring initialization:
#SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
So, where should I place my code?
There is a CommandLineRunner interface that just means to do that.
#Service
class FooBar implements CommandLineRunner {
// Inject whatever collaborator you need
#Override
void run(String... args) throws Exception {
// Execute whatever you need.
// command-line arguments are available if you need them
}
}

How to avoid retrieving a Spring managed bean from the context

My application receives messages, extracts data and persists the extracted data to a database. Data is received via a Apache Camel channel, added to a FIFO. The following code takes the next message from the FIFO and processes it. However, in order to do this it needs to get a bean from the Spring application context:
private static void dispatch(Message msg) {
if (msg == null) {
return;
}
// TODO: This really violates IoC.
DomainObjectFactory factory = (DomainObjectFactory) ApplicationContextProvider.getApplicationContext().getBean("domainObjectFactoryBean", DomainObjectFactory.class);
// do something with message
This is the service class:
#Service
public class ApplicationContextProvider implements ApplicationContextAware {
private static final Logger log = LoggerFactory.getLogger(ApplicationContextProvider.class);
private static ApplicationContext ctx = null;
public static ApplicationContext getApplicationContext() {
return getCtx();
}
#Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
log.debug("Loading context");
ApplicationContextProvider.setCtx(ctx);
}
public static ApplicationContext getCtx() {
return ctx;
}
public static void setCtx(ApplicationContext ctx) {
ApplicationContextProvider.ctx = ctx;
}
}
reading a message from the FIFO:
void process(Object obj) {
Message msg = (Message) obj;
try {
Dispatcher.process(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
This is really weak code, but I can't work out how to avoid it? That is how to use Spring IoC to link the removal of a message from the FIFO to the message processing without having to retrieve the bean from the context.
Any advice / guidance appreciated
Naturally, your code would look a whole lot nicer, if you would use annotation or xml-based injection. This would be easier, if your dispatch method was not static. Still, you can inject Spring beans into static fields utilizing a MethodInvokingFactoryBean. Look here for more information:
How can I inject local variables of a static method inside an abstract class using Spring?

Spring Batch - how to fail job when called from SpringApplicationBuilder

I need to be able to have my program exit with an error code so that the scheduler that initiated the program can know that it failed. Currently, I am running my job via SpringApplicationBuilder.
#SpringBootApplication
#EnableBatchProcessing
#Slf4j
public class WeeklyImportApplication extends DefaultBatchConfigurer {
...
public static void main(String[] args) {
handleArguments(args);
new SpringApplicationBuilder(WeeklyImportApplication.class).listeners(new CustomLoggingConfigurationApplicationListener(logConfigurer)).run(args);
finished();
}
#Bean
public Job weeklyImport(JobBuilderFactory jobs, Step determineTableName, Step determineColumnNames, Step readAccessDb) {
return jobs.get("weeklyImport").incrementer(new RunIdIncrementer()).flow(determineTableName).next(determineColumnNames).next(readAccessDb).end().build();
}
#Bean
public Step determineTableName(StepBuilderFactory stepBuilderFactory, ItemReader<String> tableNameReader, TableNameWriter tableNameWriter) {
return stepBuilderFactory.get("determineTableName").<String, String> chunk(100).reader(tableNameReader).writer(tableNameWriter).build();
}
#Bean
public Step determineColumnNames(StepBuilderFactory stepBuilderFactory, ItemReader<String> columnNamesReader, ColumnNamesWriter columnNamesWriter) {
return stepBuilderFactory.get("determineColumnNames").<String, String> chunk(1000).reader(columnNamesReader).writer(columnNamesWriter).build();
}
#Bean
public Step readAccessDb(StepBuilderFactory stepBuilderFactory, ItemReader<WeeklyStoreItem> importReader, ItemWriter<WeeklyStoreItem> weeklyStoreItemWriter, PlatformTransactionManager legacyTransactionManager) {
return stepBuilderFactory.get("readAccessDb").<WeeklyStoreItem, WeeklyStoreItem> chunk(chunkSize).reader(importReader).writer(weeklyStoreItemWriter).transactionManager(legacyTransactionManager).build();
}
...
At any point in the job execution if any step fails, I want to be able to exit out and do two things:
Move the file being processed to specfic folder.
Have the scheduler know via the program exit code that an error occurred.
Right now, the job will exit when an uncaught exception occurs, which is partially what I want, but Spring handles the exception, logs it, and then exits gracefully back to my main method. At that point, I'm not sure how to capture whether the job run was truly successful.
I had the same problem. I managed to solve partially like this (Dave Sayer outlined this idea already in comment).
First I created this created listener:
import org.springframework.batch.core.ExitStatus;
import org.springframework.boot.autoconfigure.batch.JobExecutionEvent;
import org.springframework.context.ApplicationListener;
public class JobResultListener implements
ApplicationListener<JobExecutionEvent> {
private ExitStatus jobExitStatus;
public ExitStatus getJobExitStatus() {
return jobExitStatus;
}
#Override
public void onApplicationEvent(JobExecutionEvent event) {
jobExitStatus = event.getJobExecution().getExitStatus();
}
}
Than I used it to get job execution status this way:
#SpringBootApplication
public class Application{
public static void main(String[] args) {
SpringApplication springApplication =
new SpringApplication(Application.class);
JobResultListener jobResultListener = new JobResultListener();
springApplication.addListeners(jobResultListener);
springApplication.run(args);
if (!ExitStatus.COMPLETED.equals(jobResultListener.getJobExitStatus())) {
throw new IllegalStateException("Job failed");
}
}
}
But for some reason I wasn't able to get instances of exceptions from injected event. But as you mentioned, error is logged into app logs so this mechanism was enough for me to inform called of my process about error with error code != 0.
BTW, I observed that some error penetrated from springApplication.run(args) call, but not sure why not all. Also I didn't find anything mentioned in Spring Boot docs

Resources