Best way to initialize beans in Spring context after application started? - java

I need to initialize beans in the Spring context after my application has started; currently, I initialize beans in a class with annotation #Configuration like this:
#Configuration
public class AppConfig {
#Inject
#Bean
public BeanA init(param1, param2, etc...) {
--- Code to construct bean A ---
}
#Inject
#Bean
public BeanB init(param1, param2, etc...) {
--- Code to construct bean B ---
}
}
But some beans I need to initialize after application startup so my approach is create a class listen to ApplicationReadyEvent event in Spring and put the code to initialize beans in that class.
#Configuration
class ApplicationStartingListener implements ApplicationListener<ApplicationReadyEvent>{
---- Code to init bean here ----
#Override
public void onApplicationEvent(ApplicationReadyEvent event) {
--- If I put init bean code in here, is it correct? ----
}
}
Is this the best way? Or there are some other better solutions?

I will enumerate other approaches in order to init beans, I grouped the approach in Standard Approach and Spring Boot Approach.
Standard Approach
#PostConstruct: it is just an annotation that triggers a method after bean is create, it doesn't allow input parameters.
#Bean(init-method="somInitMehotd"): this approach is totally related to Spring bean lifecycle and it is called after bean creation, if you are using another method with #PostConstruct annotation, then the #PostConstruct will be called first. This approach doesn't allow input parameters.
ApplicationListener: this interface allows to listen the standard events related to the Context Lifecycle, also it can listen customized events. For example: create a class MyAppListener and implements ApplicationListener<ContextRefreshedEvent> in this case the MyAppListener will implement an onApplicationEvent method that receives a ContextRefreshedEvent
Spring Boot Approach
The runners: There are two very useful interfaces CommandLineRunner and ApplicationRunner both of them will run after ApplicationContext is created both of them allows to inject beans as input parameters.
Spring boot listeners: Spring Application gives some additional events than the standards events that comes from the Application Context. One of the event is ApplicationReadyEvent and it is fire when the application is ready to receive request. In order to listen this events just implements the ApplicationListener using ApplicationReadyEvent as generic.
Here is the example:
MyBean class has different methods that will be called for each approach listed above, every method will call a print method and that method has a Thread.sleep in order to validate the order that every listener is called.
import javax.annotation.PostConstruct;
public class MyBean {
private String myVar="";
public MyBean(){
}
#PostConstruct
public void postConstructInit(){
this.myVar="Post init called";
print();
}
public void beanInit(){
this.myVar="Bean init called";
print();
}
public void contextInit(){
this.myVar="Context init called";
print();
}
public void runnerInit(){
this.myVar="Runner init called";
print();
}
public void bootListenerInit(){
this.myVar="Boot init called";
print();
}
public void print(){
System.out.println(this.myVar);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Here is the ContextRefreshListener class that will listen the ContextRefreshedEvent and handle it.
public class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
#Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
contextRefreshedEvent.getApplicationContext().getBean(MyBean.class).contextInit();
}
}
And it is the BootListener that will receive the ApplicationReadyEvent that comes from Spring Application.
public class MyBootListener implements ApplicationListener<ApplicationReadyEvent> {
#Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
applicationReadyEvent.getApplicationContext().getBean(MyBean.class).bootListenerInit();
}
}
And finally the Spring Boot Application
#SpringBootApplication
public class StackoverflowBootApplication {
public static void main(String[] args) {
SpringApplication.run(StackoverflowBootApplication.class, args);
}
#Bean(name = "myBean", initMethod = "beanInit")
public MyBean getMyBean(){
return new MyBean();
}
#Bean
public ContextRefreshListener getContextRefreshedListener(){return new ContextRefreshListener();}
#Bean
public MyBootListener getBootListener(){return new MyBootListener();}
#Bean
public CommandLineRunner getRunner(ApplicationContext ctx){
return (args) -> {
ctx.getBean(MyBean.class).runnerInit();
};
}
}
The output is:
Post init called
Bean init called
Context init called
Runner init called
Boot init called
Post init called output comes from
#PostConstruct
public void init(){
this.initByPostconstruct="Post init called";
Bean init called comes from the initMethod value
#Bean(name = "myBean", initMethod = "beanInit")
public MyBean getMyBean(){
return new MyBean();
}
}
Context init called comes from ContextRefreshedEvent
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
contextRefreshedEvent.getApplicationContext().getBean(MyBean.class).contextInit();
}
Runner init called comes from CommandLineRunner
#Bean
public CommandLineRunner getRunner(ApplicationContext ctx){
return (args) -> {
ctx.getBean(MyBean.class).runnerInit();
};
}
Boot init called comes from ApplicationReadyEvent
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
applicationReadyEvent.getApplicationContext().getBean(MyBean.class).bootListenerInit();
}
All the listed scenarios were triggered by Spring, I didi'nt call any of the events directly, all of them were called by Spring Framework.

Related

How to enforce 1 Bean instance and avoid NoUniqueBeanDefinitionException?

I'm implementing a websocket service where incoming messages are passed to controllers and the controllers can then broadcast response messages to another websocket session(s).
When they broadcast the message back, there is either 1 of 2 issues. Either MySocketHandler is a different instance than the one that handled afterConnectionEstablished (using Autowired annotation on MySocketHandler in MessageRouter seems to create a new instance) or I get NoUniqueBeanDefinitionException (if I use ApplicationContext to specifically get the bean by class type).
An instance of my application should only have 1 MySocketHandler, so I annotated MySocketHandler with #Scope(ConfigurableBeanFactory.SCOPE_SINGLETON).
I suspect this has something to do with asynchronous event publishing and listening. I've refactored this code a few times to try to implement this the "Spring" way but there's some fundamental error each time.
I want to know how I can enforce the Spring container to create and reuse only 1 instance of MySocketHandler.
Here is my a minimalized version of MySocketHandler.java to exemplify the problem:
#Component
#Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class MySocketHandler extends BinaryWebSocketHandler {
#Autowired private ApplicationContext applicationContext
#Autowired private MessageRouter messageRouter;
private final HashMap<String, WebSocketSession> sessions = new HashMap<>();
#EventListener
public void onOutgoingBinaryMessageEvent(OutgoingBinaryMessageEvent event) {
// ERROR: NoUniqueBeanDefinitionException
applicationContext.getBean(MySocketHandler.class).broadcast(event.getBytes(), event.getConnectionIds());
}
#Override
public void afterConnectionEstablished(WebSocketSession session) {
sessions.put(session.getId(), session);
}
#Override
public void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
eventPublisher.publishEvent(new IncomingBinaryMessageEvent(
this,
message.getPayload().array(),
session.getId()));
}
private void broadcast(byte[] bytes, Set<String> playerIds) {
BinaryMessage binaryMessage = new BinaryMessage(bytes);
// this.sessions is null because its a different instance of MySocketHandler than the one that actually managing the connections
for (WebSocketSession session : sessions.values()) {
try {
webSocketSession.sendMessage(binaryMessage);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
And an example of the MessageRouter.java:
#Component
public class MessageRouter {
#Autowired private ApplicationEventPublisher eventPublisher;
public void send(Message message) {
eventPublisher.publishEvent(message);
}
#EventListener
private void routeMessageToController(SomeMessageEvent any, String connectionId) {
.....
// Parse message and route it to a controller class.
.....
}
}
}
Application entry point:
public class MyApplication implements WebSocketConfigurer {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(getSocketHandler(), "/").setAllowedOriginPatterns("*");
}
#Bean
public MySocketHandler getSocketHandler() {
return new MySocketHandler();
}
}
An instance of my application should only have 1 MySocketHandler, so I
annotated MySocketHandler with
#Scope(ConfigurableBeanFactory.SCOPE_SINGLETON).
First of all , singleton is applied in the bean level but not the type level. It can't ensure your application will only has the single bean of a particular type. You can still define multiple singleton bean for the same type.
In most general cases , a bean can be defined by the following ways:
Annotating the class with #Component (or its specialisation version such as #Repository , #Service , #Controller , #Configuration etc.)
Using #Bean method in the #Configuration class
Now you are doing :
#Component
#Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class MySocketHandler extends BinaryWebSocketHandler
}
#SpringBootApplication
public class MyApplication implements WebSocketConfigurer {
#Bean
public MySocketHandler getSocketHandler() {
return new MySocketHandler();
}
}
Note: #SpringBootApplication is a composed annotation which contain #Configuration
which means you are now defining two MySocketHandler beans . One with the name mySocketHandler (defined via #Component) and the other has the name getSocketHandler (defined via #Bean)
So to ensure there is only one MySocketHandler bean , either remove #Component from MySocketHandler or remove this #Bean method.

spring dependency injection is not working in a class annotated with #WebListener

I tried constructor based Dependency injection in TestAppListener class which implements ServletContextListener.
I got this error
Exception sending context initialized event to listener instance of class [com.example.listener.TestAppListener].
I searched stack overflow but I couldn't find any solution for this scenario. Please any one help me in sort out this. I placed Implementation classes in META-INF.services folder also. My understanding is there is some problem in constructor dependency injection but this way of DI is need for my situation, because in real time I want to create datasource connection inside startup method.
Find all my classes below which i'm using,
#WebListener
public class TestAppListener implements ServletContextListener {
private static TestDao dao;
public TestAppListener(TestDao dao){
this.dao = dao;
}
public TestAppListener(){}
#Override
public void contextInitialized(ServletContextEvent sce) {
dao = ServiceLoader.load(TestDao.class).iterator().next();
dao.startUp();
System.out.println("Context initialized method called");
}
#Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("Context destroyed method called");
dao.shutDown();
}
}
public interface TestDao {
void startUp();
void shutDown();
}
public class TestDaoImpl implements TestDao {
#Override
public void startUp() {
System.out.println("Database is initialized");
}
#Override
public void shutDown() {
System.out.println("Database is initialized");
}
}
#Configuration
public class SpringConfig {
public SpringConfig() {
}
#Bean
public ServletListenerRegistrationBean<ServletContextListener> listenerRegistrationBean() {
ServletListenerRegistrationBean<ServletContextListener> bean = new ServletListenerRegistrationBean<>();
bean.setListener(new TestAppListener());
return bean;
}
}
The Servlet #WebListeners are handled by Servlet containers(tomcat/Jetty) when the container is starting. So they know nothing about Spring.
For more details, see discussion in this issue.
A workaround solution is replace the #WebListener with Spring's #Component, thus you can inject Spring beans(declare your Dao as spring beans) into the listeners directly.
BTW, you have to add #ServletComponentScan on your Spring Boot application class to activate it.
I created an example project to demo #WebServlet, #WebFilter and #WebListener.
Change private static TestDao dao to private final TestDao dao. Spring doesn't allow statics to be used as targets for injection. Also, your TestDaoImpl class needs to be a component or a bean defined in a Spring configuration file.

Sharing an instance of a class across a spring boot application

I have a particular class used to interface with a service that requires initialization. In the application lifecycle, the only place this makes sense is in the start of the application because the rest of the spring application cannot run without it. I had the idea to do this:
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
try {
MyRequiredService mrs = new MyRequiredService();
mrs.connect(); // This will throw if it fails
run(MyApplication.class, args);
} catch(MyException e) {
System.out.println("Failed to connect to MyRequiredService!");
}
}
}
This will launch the service and attempt to connect but I have one big problem. How do I pass this class around the application? I need it's functions in the service endpoints I am writing.
I didn't see anything obvious and searching "passing class instance in spring boot application" turns up a bunch of unrelated topics.
Is there a smart, clean way to do this in spring boot? I apologize for a contrived example. The names of the service are unique enough I didn't want to violate any agreements.
You can make Spring do this for you. First, you need to annotate your class with #Service, so Spring will pick it up when scanning for classes.
Then, define an init() method and annotate it with #PostConstruct. Spring will instantiate your MyRequiredService class and call init()
#Service
public class MyRequiredService {
#PostConstruct
public void init() {
connect();
}
public void connect() {
// ...
}
}
You could call connect() from the constructor, but I don't like to define objects that may throw exceptions out of the constructor.
And then, you can use MyRequiredService in some other class by injecting it via the #Autowired annotation:
#Component
public class MyOtherClass {
private final MyRequiredService service;
public MyOtherClass(final MyRequiredService service) {
this.service = service;
}
// Other methods here.
}
This has the same overall effect as what you're trying to do above. If MyRequiredService fails, the application will not start up.
Make it a bean. Then it will be in the ApplicationContext which then you can pass to your desired other classes through the constructor
#Configuration
public class ApplicationConfiguration
{
#Bean
public MyRequiredService myRequiredService()
{
MyRequiredService mrs = new MyRequiredService();
try {
mrs.connect(); // This will throw if it fails
return mrs;
} catch(MyException e) {
log.error("Failed to connect to MyRequiredService!");
throw new IllegalStateException("MyRequiredService failed connection. Stopping startup");
}
}
#Bean
public SomeOtherService someOtherService(MyRequiredService mrs) {
return new SomeOtherService(mrs);
}
}
IMHO Instead of catching the error and logging it. I would throw it and stop the application from starting, but to keep with your example I added the throw IllegalStateException after the log.
Doing it this way Spring will create your MyRequiredService bean in the ApplicationContext then you can see I added as a parameter needed by the bean below that. Spring will grab that bean out of the ApplicationContext and supply it to the bean. If Spring doesn't find the bean in the ApplicationContext it will throw an error and stop the application from startup.
a class implements BeanFactoryPostProcessor which is init before normal bean
#Configuration
public class MyRequiredService implements BeanFactoryPostProcessor,
PriorityOrdered, InitializingBean {
#Override
public int getOrder() {
return Integer.MIN_VALUE;
}
public void connect() {
// ...
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
#Override
public void afterPropertiesSet() throws Exception {
connect();
}
}

Method annotated with #Async not called asynchronously and ContextRefreshedEvent is received before application context starts

I present two scenario, one with a bean registered using the bean factory and the other with the bean created from auto-scanning the packages for annotated definitions (e.g #Component). The bean class listens to the ContextRefreshedEvent using a method annotated with #EventListener and also with #Async so that it is called asynchronously.
Scenario 1
A singleton of class BlockingListener is created and registered with the bean factory. This is done at the initialisation of another Bean as described below in the method afterPropertiesSet. The ContextRefreshedEvent is received but doesn't exit, and thus the Application doesn't start. It remained blocked.
#EnableAsync
#EnableScheduling
#EnableAutoConfiguration
#SpringBootApplication
public class SampleApp implements InitializingBean {
private final DefaultListableBeanFactory beanFactory;
#Autowired
public SampleApp(DefaultListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
#Override
public void afterPropertiesSet() {
String beanName = "blocking-listener";
Object bean = new BlockingListener();
beanFactory.registerBeanDefinition(beanName, rootBeanDefinition(bean.getClass()).getBeanDefinition());
beanFactory.registerSingleton(beanName, bean);
}
public static void main(final String... args) {
SpringApplication.run(SampleApp.class, args);
}
public static class BlockingListener {
#Async
#EventListener(ContextRefreshedEvent.class)
void block() throws InterruptedException {
Thread.sleep(Long.MAX_VALUE);
}
}
}
Scenario 2
The class BlockingListener is annotated with #Component and the bean is auto-detected and created. The ContextRefreshedEvent is received but doesn't exit, and yet the Application starts.
#EnableAsync
#EnableScheduling
#EnableAutoConfiguration
#SpringBootApplication
public class SampleApp {
private final DefaultListableBeanFactory beanFactory;
#Autowired
public SampleApp(DefaultListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public static void main(final String... args) {
SpringApplication.run(SampleApp.class, args);
}
#Component
public static class BlockingListener {
#Async
#EventListener(ContextRefreshedEvent.class)
void block() throws InterruptedException {
Thread.sleep(Long.MAX_VALUE);
}
}
}
The expected behaviour is with the second scenario since the ContextRefreshedEvent is expected to be published after the context is successfully started. However I can't figure out why the bean registered dynamically with the bean factory received the event before the context started and why it blocked the context from starting up.
In scenario 1 the call of block() does not happen asynchronously because the annotation #Async doesn't become effective.
#Async is working through a BeanPostProcessor, namely AsyncAnnotationBeanPostProcessor that is wrapping the instance with a proxy. But when you manually add a bean to the bean factory, the post processors are not applied.
What you can do in the given setting, is applying the post processors manually as:
bean = beanFactory.initializeBean(bean, beanName);
beanFactory.registerSingleton(beanName, bean);

How to properly destroy a Spring configuration class

Case 1
Let's consider the following Spring configuration:
#Configuration
public class MyConf1 {
#Bean
public Foo getFoo() {
// Foo class is defined as part of an external lib.
return new Foo();
}
#Bean
public Bar getBar() {
return new Bar(getFoo());
}
}
For some reasons, I need to invoke a Foo's method (i.e. myFoo.shutdown();) when MyConf1 is destroyed.
Is there any way to perform this operation without retrieving the bean instance directly from the application context (via ApplicationContext.getBean())?
Case 2
Again, let's consider a second Spring configuration class:
#Configuration
public class MyConf2 {
#Bean
public ScheduledJob scheduledJob() {
Timer jobTimer = new Timer(true);
return new ScheduledJob(jobTimer);
}
}
This time, I need to invoke jobTimer.cancel() before destroying MyConf2. Indeed, I can instantiate jobTimer outside scheduledJob(), or making it a method's parameter, as scheduledJob(Timer jobTimer).
It will then be possible to define a proper destroyer method for MyConf2. However, I would like to know if there are other ways to proceed.
Any good suggestion?
Note: Foo, Bar, Timer, ScheduledJob classes are defined externally. Thus, there is no possibility to explicitly define an inner destroy method. As assumption, I can modify only MyConf1 and MyConf2.
I would suggest defining a destroy() method (annotated with #PreDestroy) in Foo class
Similarly, modify ScheduledJob class like
public class ScheduledJob {
private Timer timer;
public ScheduledJob(Timer timer){
this.timer = timer;
}
#PreDestroy
public void destroy(){
timer.cancel();
}
}
And add destroyMethod param in #Bean
#Configuration
public class MyConf2 {
#Bean(destroyMethod = "destroy")
public ScheduledJob scheduledJob() {
Timer jobTimer = new Timer(true);
return new ScheduledJob(jobTimer);
}
}
Please see the following page http://forum.spring.io/forum/spring-projects/container/48426-postconstruct-and-predestroy-in-javaconfig
DisposableBean should help you with case #1.
You can implement the DestructionAwareBeanPostProcessor interface that can adds a before-destruction callback when the bean is destroy.In that interface,the method postProcessBeforeDestruction is do that,see the following:
#Override
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
System.out.println("before destory:"+bean);
}
#Override
public boolean requiresDestruction(Object bean) {
return true;
}
Pay attention to that the method requiresDestruction should return true,otherwise the method postProcessBeforeDestruction will not call when bean should destroy.
And i have a test:
public static void main(String[] args){
ClassPathXmlApplicationContext applicationContext=new ClassPathXmlApplicationContext("classpath:application-main.xml");
applicationContext.registerShutdownHook();
}
The postProcessBeforeDestruction really call when the bean is destroy.The output is :
before destory:com.zhuyiren.spring.learn.util.Handler#55141def
before destory:com.zhuyiren.spring.learn.controller.TestControleler#47eaca72
before destory:com.zhuyiren.spring.learn.service.impl.TestServiceImpl#7b2bbc3
before destory:com.zhuyiren.spring.learn.service.impl.TwoServiceImpl#48f2bd5b
before destory:com.zhuyiren.spring.learn.controller.ConverConroller#72967906
before destory:org.springframework.context.event.DefaultEventListenerFactory#1a482e36
before destory:org.springframework.context.event.EventListenerMethodProcessor#77fbd92c

Categories