Exiting a SpringBoot app - how do I get a reference to SpringApplication? - spring-boot

I realize that to programmatically exit a SpringBoot 4 application I want to call the exit() method of SpringApplication, but how can I get a reference to it?
Of course I have access to it in my main() method, but I ask because if I'm in some class that is loading a resource and fails, I want to terminate the app, but from that class I can't figure out how to access SpringApplication.
Thanks...

A cleaner approach for this use case is to use events & listeners wherein you have to add your listener to SpringApplication class which will listens to an event like in your case a resource load failure and then subsequently act accordingly i.e. exit the application. You can get application context handle by implementing the ApplicationContextAware interface. Details on event & listener can be found here.
MyEvent class :-
public class MyEvent extends ContextRefreshedEvent {
private static final long serialVersionUID = 1L;
#Autowired
public MyEvent(ApplicationContext source) {
super(source);
}
}
MyEvent listener class :-
#Component
public class MyListener implements ApplicationListener<ContextRefreshedEvent> {
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if(event instanceof MyEvent){
SpringApplication.exit(event.getApplicationContext(), new ExitCodeGenerator() {
#Override
public int getExitCode() {
return 2;
}
});
}
}
}
Resource loader class:-
#Component
public class MyResourceLoader implements ApplicationContextAware, CommandLineRunner {
private ApplicationContext ctx ;
#Autowired
private ApplicationEventPublisher publisher;
#Override
public void run(String... args) throws Exception {
//inside RUN for resource load failure
publisher.publishEvent(new MyEvent(ctx));
}
#Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
ctx = applicationContext;
}
}

Related

Log ApplicationEventPublisher.publishEvent() calls

I've got an Spring Boot 2.2 Application which publishes and consumes spring application events in different packages. Now I want to log every time an event has been published by ApplicationEventPublisher.publishEvent().
One solution could be to write my own event publisher like:
public class LoggableApplicationEventPublisher implements ApplicationEventPublisher {
private final ApplicationEventPublisher eventPublisher;
private final Logger logger;
public ApplicationEventLogger(ApplicationEventPublisher eventPublisher, Logger logger) {
this.eventPublisher = eventPublisher;
this.logger = logger;
}
#Override
public void publishEvent(ApplicationEvent event) {
eventPublisher.publishEvent(event);
logger.info("--> Emitting {}", event);
}
}
Another solution could be to use aspect oriented programming and write an Aspect which is triggered everytime publishEvent() has been triggered:
#Aspect
#Component
public class EventPublishAspect {
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
#Pointcut("execution(* org.springframework.context.ApplicationEventPublisher.*(..))")
public void logPublishEvent() {
}
#After("logPublishEvent()")
public void log(JoinPoint point) {
Object[] lArgs = point.getArgs();
LOG.info("Triggered", lArgs[0]);
}
}
I've set up all correctly (dependencies aswell) and this example is working for other pointcuts (like for a call of specific method of my services).
However, this aspect is not working with the declared pointcut for the ApplicationEventPublisher-Interface. Do you know why not? It seems like spring boot injects AbstractApplicationContext on runtime, which is actually implementing this interface.
Solution that does not require aspects (and has faster startup time?)
#Primary
#Bean
DelegatingApplicationEventPublisher applicationEventPublisher(ApplicationContext applicationContext) {
new DelegatingApplicationEventPublisher(applicationContext)
}
#Slf4j
#RequiredArgsConstructor
public class DelegatingApplicationEventPublisher implements ApplicationEventPublisher {
private final ApplicationContext context;
#Override
public void publishEvent(ApplicationEvent event) {
logEvent(event);
context.publishEvent(event);
}
#Override
public void publishEvent(Object event) {
logEvent(event);
context.publishEvent(event);
}
private void logEvent(Object event) {
if (event instanceof PayloadApplicationEvent payloadApplicationEvent) {
log.debug(markers("eventName", payloadApplicationEvent.getPayload().getClass(), "event", payloadApplicationEvent.getPayload()), "publishing...");
} else {
log.debug(markers("eventName", event.getClass(), "event", event), "publishing ...");
}
}
}

Different ways to run custom code before the application starts

Could you describe different ways to run custom code before the application starts for data initialization or something else?
(like ApplicationListener, CommandLineRunner etc.)
What is the difference between all of them? Which cases is better to use each of them in?
I want to know not only one way to do that but an understanding when and what I need to use.
Here is enough old question with too many options to do that: Running code after Spring Boot starts
If it is a wrong place to ask this question, please, point me to the right one.
What options I know:
CommandLineRunner - receive command-line arguments as String
#Slf4j
#Component
public class DemoCommandLineRunner implements CommandLineRunner {
#Override
public void run(String... args) {
log.info("[CommandLineRunner] Args: " + Arrays.toString(args));
}
}
ApplicationRunner - receive command-line arguments with names
#Slf4j
#Component
public class DemoApplicationRunner implements ApplicationRunner {
#Override
public void run(ApplicationArguments args) {
log.info("[ApplicationRunner] Args: ");
nonOptionArgs(args);
optionArgs(args);
}
private void nonOptionArgs(ApplicationArguments args) {
args.getNonOptionArgs().forEach(log::info);
}
private void optionArgs(ApplicationArguments args) {
args.getOptionNames().stream()
.map(args::getOptionValues)
.map(Objects::toString)
.forEach(log::info);
}
}
ApplicationListener - listener for different events (for each event own class)
#Slf4j
#Component
public class DemoApplicationListener implements ApplicationListener<ApplicationEvent> {
#Override
public void onApplicationEvent(ApplicationEvent event) {
logEvent(event);
}
private void logEvent(ApplicationEvent event) {
log.info("[DemoApplicationListener] Event: " + event);
}
}
#EventListener - listener for different events (several events in one bean)
#Slf4j
#Component
public class DemoEventApplicationListener {
#EventListener
public void handleContextRefreshedEvent(ContextRefreshedEvent event) {
logEvent(event);
}
#EventListener
public void handleApplicationReadyEvent(ApplicationReadyEvent event) {
logEvent(event);
}
private void logEvent(ApplicationEvent event) {
log.info("[DemoEventApplicationListener] Event: " + event);
}
}
SmartLifecycle - configure bean lifecycle
#Slf4j
#Component
public class DemoSmartLifecycle implements SmartLifecycle {
private boolean isRunning;
#Override
public void start() {
isRunning = true;
log.info("[DemoSmartLifecycle]: Start");
}
#Override
public void stop() {
isRunning = false;
log.info("[DemoSmartLifecycle]: Stop");
}
#Override
public boolean isRunning() {
return isRunning;
}
}
SmartInitializingSingleton - triggered at the end of the singleton pre-instantiation phase
#Slf4j
#Component
public class DemoSmartInitializingSingleton implements SmartInitializingSingleton {
#Override
public void afterSingletonsInstantiated() {
log.info("[SmartInitializingSingleton] afterSingletonsInstantiated");
}
}
Github repo: https://github.com/venkaDaria/demo-bootstrap-spring
If you need to run some code "once the SpringApplication has started" you should use ApplicationRunner or CommandLineRunner - they work the same way.
ApplicationListener, or #EventListener with ApplicationReadyEvent do the same as well.
See my example.
The option you choose is up to you.

Autowiring not working in springboot application

I am trying to create a Spring boot application with JFrame. I can see my beans in applicationContext but they are not getting autowired. I am unable to find the reason for this issue. Can someone help me with this?
Here is the code:
JavauiApplication - it is showing both userManager and userNameRepository is beans
#SpringBootApplication
public class JavauiApplication implements CommandLineRunner {
#Autowired
private ApplicationContext appContext;
public static void main(String[] args) {
new SpringApplicationBuilder(JavauiApplication.class).headless(false).run(args);
java.awt.EventQueue.invokeLater(() -> new InputNameForm().setVisible(true));
}
#Override
public void run(String... args) throws Exception {
String[] beans = appContext.getBeanDefinitionNames();
Arrays.sort(beans);
for (String bean : beans) {
System.out.println(bean);
}
}
}
InputNameForm.java -> userManager coming null
#Component
public class InputNameForm extends javax.swing.JFrame {
/**
* Creates new form InputNameForm
*/
public InputNameForm() {
initComponents();
}
#Autowired
UserManager userManager;
private void submitButtonActionPerformed(java.awt.event.ActionEvent evt) {
userManager.setName(firstName.getText(), lastName.getText());
}
/**
* #param args the command line arguments
*/
public static void main(String args[]) {
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(InputNameForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new InputNameForm().setVisible(true);
}
});
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JTextField firstName;
private javax.swing.JLabel firstNameLabel;
private javax.swing.JTextField lastName;
private javax.swing.JLabel lastNameLabel;
private javax.swing.JButton submitButton;
// End of variables declaration//GEN-END:variables
}
UserManager.java -> userNameRepository is coming null
#Component
public class UserManager {
#Autowired
UserNameRepository userNameRepository;
public void setName(String firstName, String lastName) {
userNameRepository.save(new UserName(firstName, lastName));
System.out.println(userNameRepository.findAllByFirstName(firstName));
}
}
It's a very common problem and it occurs because newcomers don't understand how the IoC container works.
Firstly, BeanDefinitionReader reads metadata about your beans from XML, Annotations(#Component, #Service etc), JavaConfig or Groovy script.
There are several BeanPostProcessor's which is responsible for reading all of these Spring annotation you're writing(#Autowired etc).
BeanFactory creates all BeanPostProcessor's then it creates all of your beans.
What happen if you create your bean with #Autowired dependencies via new operator? Nothing, because it isn't actually a bean. The object you created isn't related to IoC container. You may have the bean already in your ApplicationContext if you marked it with #Component(for example) but the object which was created via new operator wont be processed by Spring(annotations won't work).
Hope this helps.
PS: The lifecycle is simplified.
I had the same problem few days ago. What I undertood was that GUI builders like the one that comes with netbeans will automatically create components using new keyword. This means that those components won't be manage by spring. The code usually loks like this:
private void initComponents() {
jPanel1 = new javax.swing.JPanel(); //This component will not be managed by spring.
//...
}
You could use the following class provided here, to make it work.
#Component
public class BeanProvider {
private static ApplicationContext applicationContext;
// Autowires the specified object in the spring context
public static void autowire(Object object) {
applicationContext.getAutowireCapableBeanFactory().autowireBean(object);
}
#Autowired
private void setApplicationContext(ApplicationContext applicationContext) {
BeanProvider.applicationContext = applicationContext;
}
}
The top level SwingApp class:
#SpringBootApplication
public class SwingApp implements CommandLineRunner {
public static void main(String[] args) {
new SpringApplicationBuilder(SwingApp.class)
.headless(false).bannerMode(Banner.Mode.OFF).run(args);
}
#Override
public void run(String... args) throws Exception {
SwingUtilities.invokeLater(() -> {
MainFrame frame = new MainFrame();
frame.setVisible(true);
});
}
}
The MainFrame class:
public class MainFrame extends javax.swing.JFrame {
public MainFrame() {
initComponents();
}
private void initComponents() {
//Gui Builder generated code. Bean not managed by spring.
//Thus, autowired inside CustomPanel won't work if you rely on ComponentScan.
jPanel1 = new CustomJPanel();
//...
}
private CustomJPanel jPanel1;
}
The panel class where you want to autowire things:
//#Component //not needed since it wont work with gui generated code.
public class CustomJPanel extends javax.swing.JPanel{
#Autowired
private SomeRepository someRepository
public CustomJPanel(){
BeanProvider.autowire(this); //use someRepository somewhere after this line.
}
}
I have the same problem in a JavaFx project. Service and Component annotated classes were null in UI controllers even if it was shown in context that it was created. Below code worked for me
#Component
public class FxmlLoaderWithContext {
private final ApplicationContext context;
#Autowired
public FxmlLoaderWithContext(ApplicationContext context) {
this.context = context;
FXMLLoader fxmlloader = new FXMLLoader();
fxmlloader.setControllerFactory(context::getBean); //this row ensure services and components to be autowired
}
}
I think it returns null because you using command new to create object, such as new InputNameForm(). When creating object like that, the object isn't managed by Spring. That's why autowired not working.
The solution is registering your class as a bean.
You can use a class like in here.
#Component
public class BeanProvider {
private static ApplicationContext applicationContext;
public static void autowire(Object object) {
applicationContext.getAutowireCapableBeanFactory().autowireBean(object);
}
#Autowired
private void setApplicationContext(ApplicationContext applicationContext) {
BeanProvider.applicationContext = applicationContext;
}
}
And then, in your class InputNameForm constructor, call this:
class InputNameForm() {
BeanProvider.autowire(this);
...
}
And that's it. Spring will take care the rest.

spring boot how to unregister guava eventbus listener?

there's a spring boot application. and I create an event like UserCreateEvent and have a listener UserCreateListener
event:
public class UserCreateEvent {
private Long userId;
}
listener:
#Component
public class UserCreateListener {
#Autowired
private Eventbus eventbus;
#PostConstruct
public void init() {
this.eventbus.register(this)
}
#Subscribe
public void onUserCreate(UserCreateEvent event) {
Long userId = event.getUserId();
// todo something necessary
}
}
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application.class);
application.run(args);
}
}
now, I want to unregister the UserCreateListener after spring boot application startup. how can I make Eventbus unregister this event and listener??
Introduce an unregister() method in the UserCreateListener
#Component
public class UserCreateListener {
#Autowired
private Eventbus eventbus;
#PostConstruct
public void init() {
this.eventbus.register(this)
}
public void unregister() {
this.eventbus.unregister(this)
}
}
Then if you want to unregister get autowired UserCreateListener (or retrieve the bean from application context) and call listenerInstance.unregister()
UPDATE
Create your own component and call unregister there. Guess the listener and eventbus are singletons.
#Component
public class MyUnregisterService {
#Autowired
private Eventbus eventbus;
#Autowired
private UserCreateListener listener;
public void unregister() {
eventbus.unregister(listener)
}
}

why invoking a service in my servlet gives me an NPE (spring)

I have defined a service in Spring
#Service("StockageService")
public class StockageServiceImpl implements StockageService{
}
with
public interface StockageService extends Serializable {
}
And I need in a servlet to invoke this service
So I wrote
public class SpringApplicationContextListener implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent sce) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
sce.getServletContext().setAttribute("applicationContext", ac);
}
public void contextDestroyed(ServletContextEvent sce) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}
And
public class handler extends HttpServlet {
private String message;
private StockageService stockageService;
#Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
ApplicationContext ac = (ApplicationContext) config.getServletContext().getAttribute("applicationContext");
this.stockageService = (StockageService)ac.getBean("StockageService");
}
The problem is that I get a NPE at the last mentionned line
(StockageService)ac.getBean("StockageService");
Where could I have made a mistake ?
First, thanks ankur-singhal for having taken the time to answer my question
I understand the reasonnement of your answer but it does not work when I invoke
ApplicationContextUtils. getApplicationContext().getBean("StockageService");
So I used a trick which works but I do not understand very well
I override the init in my servlet as it follows
#Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this,
config.getServletContext());
}
and I have to put a
#Autowired
private StockageService stockageService;
in the servlet and it works
It seems that ApplicationContext itself is comming as null.
Look at below code and try make use of this.
ApplicationContextUtils.java
We will create the following utility class, it implements ApplicationContextAware and provides the setApplicationContext which will be invoked by spring container and the applicationContext will be passed by it. We store it in a static variable and expose it through a get method so that it can be accessed all through the application.
You can set the same while creating ApplicationContext
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext ctx;
#Override
public void setApplicationContext(ApplicationContext appContext)
throws BeansException {
ctx = appContext;
}
public static ApplicationContext getApplicationContext() {
return ctx;
}
}

Resources