JNDI lookup in ServletContextListener.contextDestroyed - java

I'm using JBoss 7.1.1 and servlet-api 2.5.
I have to shutdown some objects which located in JNDI on application shutdown.
I'm using ServletContextListener for this purposes:
public class MyServletContextListener implements ServletContextListener {
...
#Override
public void contextDestroyed(ServletContextEvent sce) {
((TaskClient) new InitialContext().lookup("myName")).disconnect();
}
}
But I get exception:
Error looking up myName, service service
jboss.naming.context.java.myName is not started
If I try to lookup the object when application is running everything is fine.
Thank you for any help.
update
How I bind data to jndi:
public class MyJbpmServletContextListener implements ServletContextListener {
public static final String TASK_CLIENT_JNDI_NAME = "myJbpmTaskClient";
private Log logger = SLF4JLogFactory.getLog(getClass());
#Override
public void contextInitialized(ServletContextEvent sce) {
try {
TaskClient tc = ...
// long initialization of TaskClient
InitialContext context = new InitialContext();
context.bind(TASK_CLIENT_JNDI_NAME, client);
} catch (NamingException exception) {
logger.error("Cannot bind task client", exception);
}
}
#Override
public void contextDestroyed(ServletContextEvent sce) {
try {
((TaskClient) new InitialContext().lookup(TASK_CLIENT_JNDI_NAME)).disconnect();
} catch (NamingException exception) {
logger.error("Cannot obtain task client", exception);
}
}
}
The object is accessible on following path TASK_CLIENT_JNDI_NAME when application runs. But when contextDestroyed called I have NamingException.
I don't know how and when it's destroyed. I only sure that I'm not rebind or unbind it.
update2
I also tried to use jndi paths java:comp/myName, java:comp/env/myName, java:/myName, java:jboss/myName. Behavior is the same: it possible to lookup object when application runs and unable to do it when ServletContextListener.contextDestroyed method called.

Looks like a JBoss bug to me, I created AS7-5746

Related

Spring - how to get ServletContext into a #Service (+ how to get WebApps Manifest)

In a Web App's #Controllers you can autowire your Servlet Context so you can (in my case) get the Manifest from the web-app (see https://stackoverflow.com/a/615545/1019307).
#Autowired
ServletContext servletContext;
How do you get this into the service?
I implemented this simple pattern and thought I'd share.
Update: This is a poor solution as it makes the service depend on the client. See below for updated solution.
Simply with a #PostConstruct so that the Service has the ServletContext set before it isRunning.
#Controller
#RequestMapping("/manifests")
public class ManifestEndpoint {
#Autowired
private ManifestService manifestService;
#Autowired
ServletContext servletContext;
#PostConstruct
public void initService() {
manifestService.setServletContext(servletContext);
}
Then in the service be sure to check it is used, since that can't be guaranteed.
#Component
public class ManifestService {
....
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
private void buildManifestCurrentWebApp() {
if (servletContext == null) {
throw new RuntimeException("ServletContext not set");
}
# Here's how to complete my example on how to get WebApp Manifest
try {
URL thisAppsManifestURL = servletContext.getResource("/META-INF/MANIFEST.MF");
System.out.println("buildManifestCurrentWebApp - url: "+thisAppsManifestURL);
buildManifest(thisAppsManifestURL);
} catch (MalformedURLException e) {
e.printStackTrace();
}
Updated solution that doesn't make the service depend on the client.
#Controller
#RequestMapping("/manifests")
public class ManifestEndpoint {
private static final Logger logger = LoggerFactory.logger(ManifestEndpoint.class);
#Autowired
private ManifestService manifestService;
#Autowired
private ServletContext servletContext;
#PostConstruct
public void initService() {
// We need to use the Manifest from this web app.
URL thisAppsManifestURL;
try {
thisAppsManifestURL = servletContext.getResource("/META-INF/MANIFEST.MF");
} catch (MalformedURLException e) {
throw new GeodesyRuntimeException("Error retrieving META-INF/MANIFEST.MF resource from webapp", e);
}
manifestService.buildManifest(thisAppsManifestURL);
}
The ManifestService doesn't change (that is, there is no need for buildManifestCurrentWebApp() now).

CDI #ApplicationScoped

I have an on Object with an annotation
#ApplicationScoped
public class DbGraphConnectionLocator implements ServerStopTask {
...
}
This object create an embedded database.
I have tried to create an hook to shutdown database when i redeploy the application.
So i construct the class
#WebListener
public class UndeployHook implements ServletContextListener{
#Inject
DbGraphConnectionLocator dbGraphConnectionLocator;
#Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
}
#Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
try {
dbGraphConnectionLocator.executeStopTask();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Actually the method contextDestoyed is called but the object used in is not the original one DbGraphConnectionLocator created at start up, and this is causing me some trouble.
In my opinion with the annotation
#ApplicationScoped
public class DbGraphConnectionLocator implements ServerStopTask { ....}
The instance of DbGraphConnectionLocator have to be unique at application level so when i use it with a
#Inject
DbGraphConnectionLocator dbGraphConnectionLocator;
I have to found the same instance created on start up by my application, but this is not true another instance was created.
Anyway i resolved adding the
#PreDestroy
public void shutdownDB(){
graphDb.shutdown();
}
to DbGraphConnectionLocator.
For info i see that this method is called after the contextDestroyed method , and this means that when the contextDestroyed is called the original instance still exist, so i miss something....
any hint?
If the embedded db shall be created at application start and be shut down when the application is stopped, just do the following:
#ApplicationScoped
#Startup
public class AnyNameForYourApplicationClass {
#PostConstruct
public void connectDB() {
// creates the db connection at startup
}
#PreDestoy
public void disconnectDB() {
// disconnects from the db
}
}
This should do it.
Alternatively you could also use a CDI producer to create an application scoped db connection. Then you can disconnect from the db using a disposer method.
In your case the behavior is correct since when the web listener is called the application must still be alive. So the shutdownDB method with #PreDestroy is called afterwards when the application scoped bean is destroyed. And since you are referencing it in your listener, it must stay alive until the listener is destroyed.

how to catch the event of shutting down of tomcat?

What I want is to start a thread every time the tomcat server starts.For this I need to catch the event of shutting down of tomcat.How can I do this?I tried to do it using sessions but sometimes the session even persists after shutting down and restating tomcat?what are my options?
You can try to catch an JVM shutdown event in this way:
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
System.out.println("BYE BYE");
}
});
The other option is to implement ServletContextListener by using #WebListener Annotation. No xml configuration is required in this case.
#WebListener
public class MyLifeCycleListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
//TODO ON START
}
public void contextDestroyed(ServletContextEvent event) {
//TODO ON DESTROY
}
}

#Autowired in ServletContextListener

i hava aclass InitApp
#Component
public class InitApp implements ServletContextListener {
#Autowired
ConfigrationService weatherConfService;
/** Creates a new instance of InitApp */
public InitApp() {
}
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println(weatherConfService);
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
and listener in web.xml:
<listener>
<listener-class>com.web.Utils.InitApp</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
the confService print --> null
what the problem?
A couple of ideas came to me as I was having the same issue.
First one is to use Spring utils to retrieve the bean from the Spring context within the listener:
Ex:
#WebListener
public class CacheInitializationListener implements ServletContextListener {
/**
* Initialize the Cache Manager once the application has started
*/
#Override
public void contextInitialized(ServletContextEvent sce) {
CacheManager cacheManager = WebApplicationContextUtils.getRequiredWebApplicationContext(
sce.getServletContext()).getBean(CacheManager.class);
try {
cacheManager.init();
} catch (Exception e) {
// rethrow as a runtime exception
throw new IllegalStateException(e);
}
}
#Override
public void contextDestroyed(ServletContextEvent sce) {
// TODO Auto-generated method stub
}
}
This works fine if you only have one or two beans. Otherwise it can get tedious. The other option is to explicitly call upon Spring's Autowire utilities:
#WebListener
public class CacheInitializationListener implements ServletContextListener {
#Autowired
private CacheManager cacheManager;
/**
* Initialize the Cache once the application has started
*/
#Override
public void contextInitialized(ServletContextEvent sce) {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
try {
cacheManager.init();
} catch (Exception e) {
// rethrow as a runtime exception
throw new IllegalStateException(e);
}
}
#Override
public void contextDestroyed(ServletContextEvent sce) {
// TODO Auto-generated method stub
}
}
The caveat in both these solutions, is that the Spring context must by loaded first before either of these can work. Given that there is no way to define the Listener order using #WebListener, ensure that the Spring ContextLoaderListener is defined in web.xml to force it to be loaded first (listeners defined in the web descriptor are loaded prior to those defined by annotation).
By declaring
<listener>
<listener-class>com.web.Utils.InitApp</listener-class>
</listener>
in your web.xml, you're telling your container to initialize and register an instance of InitApp. As such, that object is not managed by Spring and you cannot #Autowired anything into it.
If your application context is set up to component-scan the com.web.Utils package, then you will have a InitApp bean that isn't registered as a Listener with the container. So even though your other bean will be #Autowired, the servlet container won't ever hit it.
That is the trade-off.
There are workarounds to this if you use programmatic configuration with a ServletContainerInitializer or a WebApplicationInitializer for servlet 3.0. You wouldn't use #Autowired, you would just have setter/getter that you would use to set the instance.
Here's an example
class SpringExample implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
WebApplicationContext context = ...;
SomeBean someBean = context.getBean(SomeBean.class);
CustomListener listener = new CustomListener();
listener.setSomeBean(someBean);
// register the listener instance
servletContext.addListener(listener);
// then register DispatcherServlet and ContextLoaderListener, as appropriate
...
}
}
class CustomListener implements ServletContextListener {
private SomeBean someBean;
public SomeBean getSomeBean() {
return someBean;
}
public void setSomeBean(SomeBean someBean) {
this.someBean = someBean;
}
#Override
public void contextInitialized(ServletContextEvent sce) {
// do something with someBean
}
#Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
Note that Spring has some custom implementation of WebApplicationInitializer that are quite sophisticated. You really shouldn't need to implement it directly yourself. The idea remains the same, just deeper in the inheritance hierarchy.
#WebListener
public class StartupListener implements ServletContextListener {
#Autowired
private MyRepository repository;
#Override
public void contextDestroyed(ServletContextEvent event) {
}
#Override
public void contextInitialized(ServletContextEvent event) {
AutowireCapableBeanFactory autowireCapableBeanFactory = WebApplicationContextUtils.getRequiredWebApplicationContext(event.getServletContext()).getAutowireCapableBeanFactory();
autowireCapableBeanFactory.autowireBean(this);
repository.doSomething();
}
}
As others have said this listener observes by the web servlet(tomcat) context (Not the Spring Container) and is notified of servlet startup/shutdown.
Since it is created by the servlet outside of the Spring container it is not managed by Spring hence #Autowire members is not possible.
If you setup your bean as a managed #Component then Spring will create the instance and the listener wont register with the external servlet.
You cannot have it both ways..
One solution is the remove the Spring annotations and manually retrieve your member from the Spring Application context and set your members that way.
ie
public class InitApp implements ServletContextListener {
private ConfigrationService weatherConfService;
private static ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:web-context.xml");
#Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
weatherConfService = applicationContext.getBean(ConfigrationService.class);
System.out.println(weatherConfService);
}
#Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
With current versions of Spring Boot 2, you can also register Servlets, filters, and listeners as Spring beans and use autowired components normally:
Registering Servlets, Filters, and Listeners as Spring Beans
Any Servlet, Filter, or servlet *Listener instance that is a Spring bean is registered with the embedded container. This can be particularly convenient if you want to refer to a value from your application.properties during configuration.
More info here Register #WebListeners in a way that allows them to register servlets and filters.
This means that you simply have to annotate your ServletContextListener as #Comonent.
Since Spring Boot 2.4 using #WebListener does not work anymore, mentioned in the release notes.
A side-effect of this change is that the Servlet container now creates the instance of the WebListener and, therefore, dependency injection such as with #Autowired can no longer be used. In such cases, #Component should be used instead.

How does Lifecycle interface work in Spring? What are "top-level singleton beans"?

It is said in Spring javadoc, that "Note that the Lifecycle interface is only supported on top-level singleton beans." Here URL
My LifecycleBeanTest.xml describes bean as follows:
<beans ...>
<bean id="lifecycle" class="tests.LifecycleBean"/>
</beans>
so it looks "topish" and "singletonish" enough.
What does it mean? How to make Spring know about my bean implementing Lifecycle and do something with it?
Suppose my main method looks following in Spring
public static void main(String[] args) {
new ClassPathXmlApplicationContext("/tests/LifecycleBeanTest.xml").close();
}
so, it instantiates context and then closes it immediately.
May I create some bean in my configuration, which delays close() execution until application do all it's works? So that main method thread wait for application termination?
For example, the following bean does not work in way I thought. Neither start() not stop() is called.
package tests;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.Lifecycle;
public class LifecycleBean implements Lifecycle {
private static final Logger log = LoggerFactory.getLogger(LifecycleBean.class);
private final Thread thread = new Thread("Lifecycle") {
{
setDaemon(false);
setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
#Override
public void uncaughtException(Thread t, Throwable e) {
log.error("Abnormal thread termination", e);
}
});
}
public void run() {
for(int i=0; i<10 && !isInterrupted(); ++i) {
log.info("Hearbeat {}", i);
try {
sleep(1000);
} catch (InterruptedException e) {
return;
}
}
};
};
#Override
public void start() {
log.info("Starting bean");
thread.start();
}
#Override
public void stop() {
log.info("Stopping bean");
thread.interrupt();
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
#Override
public boolean isRunning() {
return thread.isAlive();
}
}
UPDATE 1
I know I can wait for bean in code. It is interesting to hook into Spring itself.
You should use SmartLifecycle instead of Lifecycle. Only the former is working as you expected Lifecycle to work. Make sure you return true in your isRunning() implementation.
I have used SmartLifecycle for asynchronous jobs for which it sounds like designed for. I suppose it will work for you but at the same time you may have a look at ApplicationListener and events like ContextStoppedEvent.
You can examine AbstractApplicationContext.doClose() method and see that no interruption of application context closing has been provided by the Spring developers
protected void doClose() {
boolean actuallyClose;
synchronized (this.activeMonitor) {
actuallyClose = this.active && !this.closed;
this.closed = true;
}
if (actuallyClose) {
if (logger.isInfoEnabled()) {
logger.info("Closing " + this);
}
try {
// Publish shutdown event.
publishEvent(new ContextClosedEvent(this));
}
catch (Throwable ex) {
logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
}
// Stop all Lifecycle beans, to avoid delays during individual destruction.
try {
getLifecycleProcessor().onClose();
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
}
// Destroy all cached singletons in the context's BeanFactory.
destroyBeans();
// Close the state of this context itself.
closeBeanFactory();
// Let subclasses do some final clean-up if they wish...
onClose();
synchronized (this.activeMonitor) {
this.active = false;
}
}
}
So you can't prevent the application context from closing.
Testing the service with TestContext framework
If you are using Spring test context framework with JUnit, I think you can use it to test services that implement Lifecycle, I used the technique from one of the internal Spring tests
Slightly modified LifecycleBean(I've added waitForTermination() method):
public class LifecycleBean implements Lifecycle {
private static final Logger log = LoggerFactory
.getLogger(LifecycleBean.class);
private final Thread thread = new Thread("Lifecycle") {
{
setDaemon(false);
setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
#Override
public void uncaughtException(Thread t, Throwable e) {
log.error("Abnormal thread termination", e);
}
});
}
public void run() {
for (int i = 0; i < 10 && !isInterrupted(); ++i) {
log.info("Hearbeat {}", i);
try {
sleep(1000);
} catch (InterruptedException e) {
return;
}
}
};
};
#Override
public void start() {
log.info("Starting bean");
thread.start();
}
#Override
public void stop() {
log.info("Stopping bean");
thread.interrupt();
waitForTermination();
}
#Override
public boolean isRunning() {
return thread.isAlive();
}
public void waitForTermination() {
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
Test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:Test-context.xml")
public class LifecycleBeanTest {
#Autowired
LifecycleBean bean;
Lifecycle appContextLifeCycle;
#Autowired
public void setLifeCycle(ApplicationContext context){
this.appContextLifeCycle = (Lifecycle)context;
}
#Test
public void testLifeCycle(){
//"start" application context
appContextLifeCycle.start();
bean.waitForTermination();
}
}
Test-context.xml content:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="LifecycleBean"/>
</beans>
P.S. starting and stopping the context is not a thing you may want to do many times on the same application context, so you may need to put #DirtiesContextannotation on your test methods for the best results.
Answer to the new version of the question
DefaultLifecycleProcessor uses beanFactory.getBeanNamesForType(Lifecycle.class, false, false); to retrieve the list of the beans implementing Lifecycle
From getBeanNamesForType javadoc:
NOTE: This method introspects top-level beans only. It does
not check nested beans which might match the specified type
as well.
So this method does not list the inner beans (they were called nested when only xml configuration was available - they are declared as nested bean xml elements).
Consider the following example from the documentation
<bean id="outer" class="...">
<!-- Instead of using a reference to target, just use an inner bean -->
<property name="target">
<bean class="com.mycompany.PersonImpl">
<property name="name"><value>Tony</value></property>
<property name="age"><value>51</value></property>
</bean>
</property>
</bean>
Start() and Stop() are merely events that are propagated by the application context they are not connected with lifetime of the application context, for example you can implement a download manager with some service beans - when the user hits "pause" button, you will broadcast the "stop" event, then when the user hits "start" button, you can resume the processing by broadcasting the "start" event. Spring is usable here, because it dispatches events in the proper order.
I never used Lifecycle interface and I am not sure how it is suppose to work. But it looks like simply calling start() on context calls these callbacks:
AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("...");
ctx.start();
However typically I use #PostConstruct/#PreDestroy annotations or implement InitializingBean or DisposableBean:
public class LifecycleBean implements InitializingBean, DisposableBean {
#Override
public void afterPropertiesSet() {
//...
}
#Override
public void destroy() {
//...
}
}
Notice I don't call close() on application context. Since you are creating non-daemon thread in LifecycleBean the JVM remains running even when main exits.
When you stop that thread JVM exists but does not close application context properly. Basically last non-daemon thread stops, causing the whole JVM to terminate. Here is a bit hacky workaround - when your background non-daemon thread is about to finish, close the application context explicitly:
public class LifecycleBean implements ApplicationContextAware /* ... */ {
private AbstractApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = (AbstractApplicationContext)applicationContext;
}
public void run() {
for(int i=0; i<10 && !isInterrupted(); ++i) {
log.info("Hearbeat {}", i);
try {
sleep(1000);
} catch (InterruptedException e) {
}
}
applicationContext.close();
}
}
So, finally I foundm that if I:
1) Define my bean as implements Lifecycle
2) Introduce a delay in stop() method like this
#Override
public void stop() {
log.info("Stopping bean");
//thread.interrupt();
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
3) And code context creation as follows:
new ClassPathXmlApplicationContext("/tests/LifecycleBeanTest.xml").stop();
Then I get what I want:
context creation code does not exit until all stops of all Lifecycle beans executed. So, this code works in JUnit tests
What about using SmartLifecycle? Seems like it provides all necessary functionality.
There is method public void stop(Runnable contextStopping) {}.
And you can continue app context closing by executing passed in contextStopping in time you want.
In my environment all works fine even on J-UNIT, of course by running them with SpringJUnit4ClassRunner.

Categories