A similar question has been answered...
Here: Listener for server starup and all spring bean loaded completely
Here: How to add a hook to the application context initialization event?
and several other places.
In my primary application, when the Root Spring context initializes it will trigger three child contexts to initialize. When listening for an event to fire (based on the ContextRefreshedEvent), this results in four total events. This does not give the consumer of these events accurate information regarding the overall state of the application's Spring context.
Unfortunately I don't have the ability to change the primary application. The code that I'm wrestling with now is packaged as a jar and loaded into the primary application using a plugin architecture. I am able to hook into the application's Spring context without issue, and I can successfully receive ContextRefreshEvent triggers.
What I'm looking for is a way to understand if all Spring application contexts in the primary application have completed. One thing I tried was to manually keep track of the starting and completing of the application context (using a map) so that I could tell when all known application contexts were finished initializing. This didn't work for me, as I found that the ContextStartedEvent didn't trigger during Spring's refresh operation.
Additional things that could work for me would be knowing how many application contexts exist or how many beans there are that need to be loaded. That way I could keep track of them as they finished and ultimately know that all application contexts are complete.
Any ideas would be appreciated.
EDIT
In an attempt to ask as pointed a question as possible, I made this question too prescriptive in terms of the implementation. Here is the actual problem that I was trying to solve (in the form of a question):
How might a plugin, packaged as a jar inside of a webapp, tell when the context of the webapp has been deployed successfully in Tomcat?
Through testing, I thought it was fairly safe to assert that the initialization of the Spring context was synonymous with the deploy phase of the application through Tomcat. This may be false, but it lined up well enough.
Broadening the scope of the fix from exclusively using Spring, I was able to come up with a solution to the problem. I will post that as a separate answer.
As of Spring 2.5.X < 4.X
One thing you could do is listen for ContextStartedEvent-s, add them to a map and then once ContextRefreshedEvent-s are triggered, wait while all contexts are refreshed.
This still might be not safe if the context is refreshed multiple times though...but at least a place to start.
As of Spring 4+
Reading ContextRefreshedEvent javadocs:
Event raised when an {#code ApplicationContext} gets initialized or refreshed.
What this means is that you can have your own logic to track whether that context was initialized or refreshed.
#EventListener( { ContextRefreshedEvent.class } )
void contextRefreshedEvent( ContextRefreshedEvent e ) {
ApplicationContext context = ( ApplicationContext ) e.getSource();
System.out.println( "a context refreshed event happened for context: " + context.getDisplayName() );
}
Meaning - the first time you'll get an event with that context - you will know that it was initialized. The following times - you will know that it was refreshed.
Being brutally simple ( no optimizations ) you can do the following:
private final Map<String, String> contextStatuses = new HashMap<>();
#EventListener( { ContextRefreshedEvent.class } )
public void contextRefreshedEvent( ContextRefreshedEvent e ) {
ApplicationContext context = ( ApplicationContext ) e.getSource();
if ( !contextStatuses.containsKey( context.getDisplayName() ) ) { // initialized
contextStatuses.put( context.getDisplayName(), "initialized" );
} else { // refreshed
contextStatuses.put( context.getDisplayName(), "refreshed" );
}
checkAllContextsRefreshed();
}
private void checkAllContextsRefreshed() {
for ( String status : contextStatuses.values() ) {
if ( !"refreshed".equals( status ) ) {
return;
}
}
doWhenAllContextsRefreshed();
}
Per the edit on my question, here is the solution that I went with:
In the plugin that is packaged inside the primary application, I added a check using JMX against the stateName attribute of the WebModule type under Catalina. This isn't the exact code I used, but for simplicity the logic looks like this:
ObjectName name = new ObjectName("Catalina:j2eeType=WebModule,name=//localhost/" + contextPath + ",J2EEApplication=none,J2EEServer=none");
MBeanServer mx = ManagementFactory.getPlatformMBeanServer();
String state = String.valueOf(mx.getAttribute(name, "stateName"));
if (state.equals("STARTED")) {
LOGGER.warn("SUCCESS!");
}
As I mentioned in the edit, equating Tomcat deploy to Spring context initialization was close, but ultimately it wasn't a fair comparison. Using this JMX endpoint gives me a very accurate representation of the state of the Tomcat deploy.
Related
I have built a web application that uses
SpringBoot v1.3.6.RELEASE
Tomcat 8.0.36
Java 1.8u101
on CentOS 7.2
The web application is also a SOAP client that calls out to another web application.(JAX-WS RI 2.2.9) If the applications remains idle for 15 seconds the first webservice call stalls for nearly 2 seconds. It appears that the stall happens in o.a.c.loader.WebappClassLoaderBase.
After idle 15 seconds
16:02:36.165 : Delegating to parent classloader org.springframework.boot.loader.LaunchedURLClassLoader#45283ce2
16:02:36.170 : Searching local repositories
16:02:36.170 : findResource(META-INF/services/javax.xml.soap.MetaFactory)
16:02:38.533 : --> Resource not found, returning null
16:02:38.533 : --> Resource not found, returning null
Next request no idle time
16:07:09.981 : Delegating to parent classloader org.springframework.boot.loader.LaunchedURLClassLoader#45283ce2
16:07:09.984 : Searching local repositories
16:07:09.985 : findResource(META-INF/services/javax.xml.soap.MetaFactory)
16:07:09.986 : --> Resource not found, returning null
16:07:09.986 : --> Resource not found, returning null
16:07:09.988 : findResources(META-INF/services
All above messages produced by o.a.c.loader.WebappClassLoaderBase and they are apparently being caused by ClientSOAPHandlerTube.processRequest which is from JAX-WS RI.
You'll notice the first call takes over 2 seconds but subsequent calls take only milliseconds.
I'm wondering if anyone has experienced this behavior?
Possible solutions:
Is it possible to change out the classloader used by tomcat in springboot to use ParallelWebappClassLoader
Or maybe this is a product of the reloadable flag on the classloader but I don't see how to change that flag in springboot.
When run using Jetty as the container this does not occur.
Final Solution: (thanks to Gergely Bacso)
#Bean
public EmbeddedServletContainerCustomizer servletContainerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (container instanceof TomcatEmbeddedServletContainerFactory) {
customizeTomcat((TomcatEmbeddedServletContainerFactory) container);
}
}
private void customizeTomcat(TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory) {
tomcatEmbeddedServletContainerFactory.addContextCustomizers(new TomcatContextCustomizer() {
#Override
public void customize(Context cntxt) {
cntxt.setReloadable(false);
}
});
}
};
}
Actually your findings are quite good and you have 90% answered your question already. These two facts:
"it appears that the stall happens in o.a.c.loader.WebappClassLoaderBase"
"when run using Jetty as the container this does not occur."
show that it is going be a Tomcat-related problem because:
o.a.c. stands for org.apache.catalina
Your code works well on another container. (Jetty)
You also observed, that the issue is happening after 15 seconds of idle time. This perfectly corresponds to Tomcat's default checkInterval setting, which is:
The number of seconds between checks for modified classes and
resources, if reloadable has been set to true. The default is 15
seconds.
So in short: currently your reloadable flag is ON, and Tomcat tries to reload your classes which is handy during development, but unacceptable in any other case. The way to switch it off is not via Spring-boot though.
SOLUTION:
You need to locate your context.xml / server.xml where you will find your Context defined like this:
<Context ... reloadable="true">
Remove the reloadable flag, and you have solved the problem. The file itself can be either in $CATALINA_BASE/conf of $CATALINE_HOME/conf, but in reality these locations can be a bit tricky to find if you are using some IDE to manage Tomcat for you.
In case of embedded Tomcat with Spring-boot:
The class you can use to manipulate Tomcat settings is: EmbeddedServletContainerCustomizer.
Through this you can add a TomcatContextCustomizer (addContextCustomizers) so that you can call setReloadable on the context itself.
I do not see any reason for Spring-boot needing this flag on true.
Our configuration is: 1...n Message receivers with a shared database.
Messages should only be processed once.
#RabbitListener(bindings = #QueueBinding(
value = #Queue(value = "message-queue", durable = "true"),
exchange = #Exchange(value = TOPIC_EXCHANGE, type = "topic", durable = "true"),
key = MESSAGE_QUEUE1_RK)
)
public void receiveMessage(CustomMessage message) throws InterruptedException {
System.out.println("I have been received = " + message);
}
We want to to guarantee messages will be processed once, we have a message store with id's of messages already processed.
Is it possible to hook in this check before receiveMessage?
We tried to look at a MessagePostProcessor with a rabbitTemplate but didn't seem to work.
any advice on how to do this?
We tried with a MethodInterceptor and this works, but is pretty ugly.
Thanks
Solution found - thanks to Gary
I created a MessagePostProcessorInjector which implements SmartLifecycle
and on startup, I inspect each container and if it is a AbstractMessageListenerContainer add a customer MessagePostProccesser
and a custom ErrorHandler which looks for certain type of Exceptions and drops them (other forward to defaultErrorHandler)
Since we are using DLQ I found throwing exceptions or setting to null wouldn't really work.
I'll make a pull request to ignore null Messages after a MPP.
Interesting; the SimpleMessageListenerContainer does have a property afterReceivePostProcessors (not currently available via the listener container factory used by the annotation, but it could be injected later).
However, those postprocessors won't help because we still invoke the listener.
Please feel free to open a JIRA Improvement Issue for two things:
expose the afterReceivePostProcessors in the listener container factories
if a post processor returns null, skip calling the listener method.
(correction, the property is indeed exposed by the factory).
EDIT
How it works...
During context initialization...
For each annotation detected by the bean post processor the container is created and registered in the RabbitListenerEndpointRegistry
Near the end of context initialization, the registry is start()ed and it starts all containers that are configured for autoStartup (default).
To do further configuration of the container before it's started (e.g. for properties not currently exposed by the container factories), set autoStartup to false.
You can then get the container(s) from the registry (either as a collection or by id). Simply #Autowire the registry in your app.
Cast the container to a SimpleMessageListenerContainer (or alternatively a DirectMessageListenerContainer if using Spring AMQP 2.0 or later and you are using its factory instead).
Set the additional properties (such as the afterReceiveMessagePostProcessors); then start() the container.
Note: until we enhance the container to allow MPPs that return null, a possible alternative is to throw an AmqpRejectAndDontRequeueException from the MPP. However, this is probably not what you want if you have DLQs configured.
Throwing an exception extending from ImmediateAcknowledgeAmqpException from postProcessMessage() of DuplicateChecking MPP when message is duplicate will also not pass the message to the rabbit Listener.
I've spent some time experimenting with and studying the OSGi enRoute site. The Quick Start, and Base tutorials were really good. Now as a learning exercise, I'm creating my own example following the principles in those tutorials.
I've decided to reproduce the StageService from the blog post "Making JavaFX better with OSGi". Rather than using the org.apache.felix.dm and org.apache.felix.dm.annotation.api packages I want to use the OSGi standard SCR packages (org.osgi.service.component.*) along with the enRoute provider template.
So far everything has worked out nicely. But I'm stuck on one point. In the "Making JavaFX better with OSGi" tutorial the service is programmatically registered into the service registry using the org.apache.felix.dm.DependencyManager like this:
#Override
public void start(Stage primaryStage) throws Exception {
BundleContext bc = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
DependencyManager dm = new DependencyManager(bc);
dm.add(dm.createComponent()
.setInterface(StageService.class.getName(), null)
.setImplementation(new StageServiceImpl(primaryStage)));
}
My assumption is that in this example the DependencyManager is an Apache Felix specific feature rather than an OSGi standard. I would like to have my enRoute provider depend only on OSGi standard features.
So my question is simply:
How would one register a service in the service registry programmatically using only OSGi standard features? (I know from following the enRoute tutorials that if my component implements the exported service that SCR will automatically register my component in the service registry when my component is activated. The problem with this solution though is that when my component is activated it has to launch the JavaFX application in a different thread so as to not block the thread in use by the SCR until the JavaFX application terminates. Because of this, my component must programmatically register the service in the service registry. Otherwise it won't be guaranteed to be available upon registration.)
For reference, here is what I currently have:
private void registerService(Stage stage) {
DependencyManager dm = new DependencyManager(bundle().getBundleContext());
dm.add(
dm.createComponent()
.setInterface(StageService.class.getName(), null)
.setImplementation(new StageServiceImpl(primaryStage))
);
}
But instead I want to replace it with this:
private void registerService(Stage stage) {
// How to register service in service registry using only OSGi standard features? (not the apache felix dependency manager)
}
UPDATE 1
Following BJ Hargrave's recommendation I tried to register the service directly from the bundle context as follows:
FrameworkUtil
.getBundle(getClass())
.getBundleContext()
.registerService(StageService.class, new StageServiceImpl(primaryStage), null);
After doing this and trying to resolve the enRoute application project the following error occurs:
org.osgi.service.resolver.ResolutionException: Unable to resolve
<> version=null: missing requirement
com.github.axiopisty.osgi.javafx.launcher.application
-> Unable to resolve com.github.axiopisty.osgi.javafx.launcher.application
version=1.0.0.201608172037: missing requirement
objectClass=com.github.axiopisty.osgi.javafx.launcher.api.StageService]
I have uploaded the project to github so you can reproduce the error.
Update 2
The build tab in the bnd.bnd file in the provider module shows the following warning:
The servicefactory:=true directive is set but no service is provided, ignoring it
Might this have something to do with the application module not being able to be resolved?
In rare cases it is necessary to register a 'service by hand' using the standard OSGi API. Try very hard to avoid this case because if you start to register (and maybe depend) on services that you manually register you get a lot of responsibility that is normally hidden from view. For example, you have to ensure that the services you register are also unregistered.
One of the rare cases where this is necessary is when you have to wait for a condition before you can register your service. For example, you need to poll a piece of hardware before you register a service for the device. You will need to control the CPU but at that moment you cannot yet register a service. In that case you create an immediate component and register the service manually.
To register a service manually you require a BundleContext object. You can get that objectvia the activate method, just declare a Bundle Context in its arguments and it is automatically injected:
#Activate
void activate( BundleContext context) {
this.context = context;
}
You can now register a service with the bundle context:
void register(MyService service) {
Hashtable<String,Object> properties = new Hashtable<>();
properties.put("foo", "bar");
this.registration = context.registerService( MyService.class, service, properties );
}
However, you now have the responsibility to unregister this service in your deactivate. If you do not clean up this service then your component might be deactivated while your service still floats around. Your service is unmanaged. (Although when the bundle is stopped it will be cleaned up.)
#Deactivate
void deactivate() {
if ( this.registration != null)
this.registration.unregister();
}
If you create the service is a call back or background thread then you obviously have to handle the concurrency issues. You must ensure that there is no race condition that you register a service while the deactivate method has finished.
This text has also been added to the DS Page of OSGi enRoute
Reading the OSGi spec would help you understand the service API.
But this should do it:
ServiceRegistration<StageService> reg = bc.registerService(StageService.class, new StageServiceImpl(primaryStage), null);
How do I know when my Vaadin 7 web app first starting/launching, so I can do some initial set-up work?
Likewise, how do I know when my web app is ending, getting shutdown/exiting?
ServletContextListener
Vaadin is built on top of Java Servlet technology. A “context” is the technical term for your web app in Servlet terms. So the answer here is not Vaadin-specific, it applies to any Servlet -- and at the end of the day, Vaadin is just one big Servlet.
Since Servlet spec version 2.3, a Servlet container such as Tomcat, Jetty, etc. must promise to be on the lookout for any Java class you define as implementing the ServletContextListener interface. That interface has two simple methods:
One that gets called when your web first launches (contextInitialized)
One that gets called when your web app is ending (contextDestroyed).
The ending could be caused by the Servlet container (ex: Tomcat) is being shutdown so all the web apps (“contexts”) are ending, or because just your Vaadin app’s context is ending (if your Servlet container supports per-context shutdown).
The contract every Servlet container must fulfill is that each of your ServletContextListener classes (you can have more than one) must have its contextInitialized invoked before any servlet or filter executes. So this is the perfect time to do initialization work that might benefit more than a single Servlet request-response cycle. If you need to startup a database such as [H2 Database), this is a good time. If you load some data into memory as a cache to be used by the servlet(s) repeatedly, this is a good time. Also a good time to test your apps resources, to be certain logging works or certain expected files are in place, for example.
Likewise, every compliant Servlet container invokes contextDestroyed only after the servlet(s) and filters have finished their last invocation. So this is a good place to shutdown your database, make backups, or do any other clean-up chore appropriate to your web app.
We are discussing the life cycle of your web app’s “context”. That context may involve one, or more than one, servlet. This life cycle of the context goes beyond the life cycle of any one of the servlets participating in this context. The context is kinda-sorta like the queen bee who gives birth to all her drones in a new hive, where she was living before them and she will outlive them all as they die off in dutiful service to her (if that is how a hive works?).
Defining your ServletContextListener
Making a ServletContextListener is quite easy: Make a class with a pair of methods plus an annotation.
Add a new Java class as part of your Vaadin app. You can name the class anything you want.
I add my context listeners in the same package as my main Vaadin app UI class (MyUI.java may have been generated by your Vaadin plugin or by Maven archetype). Seems like a natural place as the context listener is the beginning of my Vaadin app launching before any user is handled while the designated UI class will then be the first piece of my Vaadin app being run for each user.
Declare your class as implementing ServleContextListener. Add the two required methods discussed above; your IDE may assist with this chore.
One more trick: You must inform the Servlet container about this context listener. There is more than one way to do this, but I use the simplest, an annotation #WebListener on the class.
Here is an entire example class.
package com.example.amazingapp;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/**
*
* #author Basil Bourque
*/
#WebListener
public class WebAppListener implements ServletContextListener {
#Override
public void contextInitialized ( ServletContextEvent sce ) {
System.out.println ( "My Vaadin web app is starting. " );
}
#Override
public void contextDestroyed ( ServletContextEvent sce ) {
System.out.println ( "My Vaadin web app is shutting down." );
}
}
Specifically, I want Tomcat to abort start-up when my web app can't initialize (due to a configuration error).
I'm tried to use the Tomcat shutdown port. That's not ready when my web app is starting up.
-Dorg.apache.catalina.startup.EXIT_ON_INIT_FAILURE=true, doesn't work. Tomcat starts up just fine.
Throw an uncaught Exception. I've tried throwing an exception. Tomcat still initializes correctly but my web app is in a non-functional state.
Use the Tomcat JMX API. None of the shutdown methods will cause Tomcat to shut down.
So far, the only other option I've got is System.exit(-1). That does work, but I'm concerned that doing that to Tomcat will cause problems.
Tomcat is designed to be resilient against badly written applications that don't start up.
org.apache.catalina.startup.EXIT_ON_INIT_FAILURE is for stopping Tomcat if one of Tomcat's components, such as one of the connectors, doesn't start (e.g. because the port is in use).
System.exit() is ugly but it will work and it is safe as far as Tomcat is concerned (as for that matter is kill -9). It is applications that may not like this rather brutal approach to shutdown.
There is a Tomcat specific way to do this more cleanly. Add a custom LifecycleListener to the Server component and have it respond to AFTER_START_EVENT. Have the listener navigate the Container hierarchy from the Server to the Contexts [Server->Service(s)->Engine-Host(s)->Context(s)] and check that each context is in the state STARTED. If not, call stop() and then destroy() on the Server.
Here's an implementation of the listener which aborts the Tomcat startup, as mentioned by Mark Thomas. StrictStateCheckListener is an after-start server listener. It checks the state of all Container, including engine, host, and context. The server start-up will be aborted if there's any component failed to start (state different from STARTED).
package com.example.lifecycle;
import java.util.logging.*;
import org.apache.catalina.*;
public class StrictStateCheckListener implements LifecycleListener {
private static final Logger LOGGER = Logger.getLogger(StrictStateCheckListener.class.getName());
private boolean hasFailures;
private String containerState;
#Override
public void lifecycleEvent(LifecycleEvent event) {
String type = event.getType();
Lifecycle lifecycle = event.getLifecycle();
if (lifecycle instanceof Server && type.equals(Lifecycle.AFTER_START_EVENT)) {
Server server = (Server) lifecycle;
hasFailures = false;
// Check status of each container
StringBuilder sb = new StringBuilder("Status:").append(System.lineSeparator());
for (Service service : server.findServices()) {
checkState(service.getContainer(), sb, "");
}
if (LOGGER.isLoggable(Level.INFO)) {
LOGGER.info(sb.toString());
}
if (hasFailures) {
/*
* The server will not be stopped by this listener. Indeed,
* an exception is raised to trigger shutdown hook. See:
* `org.apache.catalina.startup.Catalina`.
*/
throw new IllegalStateException(containerState);
}
}
}
private void checkState(Container container, StringBuilder report, String indent) {
if (!hasFailures && container.getState() != LifecycleState.STARTED) {
hasFailures = true;
containerState = stateOf(container);
}
report.append(indent).append(stateOf(container));
for (Container child : container.findChildren()) {
checkState(child, report, indent + " ");
}
}
private String stateOf(Container container) {
String className = container.getClass().getSimpleName();
String stateName = container.getStateName();
return String.format("%s[%s]: %s%n", className, container.getName(), stateName);
}
}
Register this listener to your server component via server.xml:
<Server>
...
<Listener className="com.example.lifecycle.StrictStateCheckListener" />
...
</Server>
Your point No 1
Do you mean when uninitialized state exception is thrown from application you are calling tomcat shutdown.bat file.Have you tried to call shutdown.bat file from your program.
I know this question is a bit old, but I'm going a bit further here thinking out of the box to solution you are seeking. It looks like your requirement is to stop tomcat when your webapp fails to initialize. This is a bit different than "abort Tomcat start-up". Tomcat itself is already started and will start to deploy your webapp right after it started up. Based on your requirement, I assume that you don't have any other webapp. If that's the case, why don't you just use Spring Boot? Spring boot provides a way to deploy self contained web application. In this case, if initialization fails, application will not start.