Skip to content

Refactor SpringBootServletInitializer.onStartup to initialize WebApplicationContext inside ServletContextListener #2070

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
apogrebnyak opened this issue Dec 5, 2014 · 10 comments
Labels
status: declined A suggestion or change that we don't feel we should currently apply

Comments

@apogrebnyak
Copy link

Currently, onStartup creates a web application, and only then registers a ServletContextListener (to react to contextDestroyed event).

If an application wants to register some other ServletContextListener before the SpringApplication is created it cannot reuse current onStartup method. I propose that onStartup be refactored to register a fully functioning ServletContextListener, that will be called by the Servlet container.

Here is a proposed refactoring:

@Override
public void onStartup (
        final ServletContext servletContext
    )
{
    servletContext.addListener(
        new ServletContextListener( ) {
            private ContextLoaderListener delegate = null;

            @Override
            public void contextInitialized ( final ServletContextEvent sce ) {
                final WebApplicationContext rootAppContext =
                    createRootApplicationContext(servletContext);

                if (rootAppContext != null) {
                    delegate = new ContextLoaderListener(rootAppContext);
                    // Do not call delegate.contextInitialized as we've 
                    // already initilized it
                }
                else {
                    this.logger.debug(
                        "No ContextLoaderListener registered, as "
                            + "createRootApplicationContext() did not "
                            + "return an application context");
                }
            }

            @Override
            public void contextDestroyed ( final ServletContextEvent sce ) {
                if ( delegate != null ) {
                    // Delegate call
                    delegate.contextDestroyed( sce );
                }
            }
        }
    );
}

With this change client application would be able to do the following:

public class ClientApp extends SpringBootServletInitializer {
  @Override
  public void onStartup (
        final ServletContext servletContext
    )
  {
    servletContext.addListener( new MyPreListener( ) );
    super.onStartup( servletContext );
    servletContext.addListener( new MyPostListener( ) );
  }
}

and ensure that listeners are called in order. Currently all listeners will be called only after SpringApplication has already been initialized (For some legacy applications this may not be desirable).

@philwebb
Copy link
Member

philwebb commented Dec 5, 2014

@dsyer any thoughts?

@dsyer
Copy link
Member

dsyer commented Dec 5, 2014

Looks harmless. (Famous last words.)

@apogrebnyak
Copy link
Author

I have to be a devil's advocate on this. The only drawback that I can think of, is that if anybody had implemented ClientApp in a way I described originally, the MyPreListener will be called after the ApplicationContext is created. On the other hand, it would be destroyed after the ApplicationContext is destroyed (basically the ordering is not honored).

I don't know if anybody in the wild has any dependency on this behavior.

On the pro side, I think the proposed change is more in-line with the Servlet framework, as it does extra initialization through a well defined hook, and allows other cooperative players to rely on a defined call order.

-Alex

@philwebb philwebb added this to the 1.2.0 milestone Dec 8, 2014
@philwebb philwebb added the type: enhancement A general enhancement label Dec 8, 2014
@philwebb
Copy link
Member

philwebb commented Dec 9, 2014

I think we'll risk the change.

@philwebb philwebb reopened this Dec 9, 2014
@philwebb
Copy link
Member

philwebb commented Dec 9, 2014

[INFO] [talledLocalContainer] Caused by: java.lang.UnsupportedOperationException: Section 4.4 of the Servlet 3.0 specification does not permit this method to be called from a ServletContextListener that was not defined in web.xml, a web-fragment.xml file nor annotated with @WebListener
[INFO] [talledLocalContainer]   at org.apache.catalina.core.StandardContext$NoPluggabilityServletContext.addServlet(StandardContext.java:6942)
[INFO] [talledLocalContainer]   at org.springframework.boot.context.embedded.ServletRegistrationBean.onStartup(ServletRegistrationBean.java:166)
[INFO] [talledLocalContainer]   at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext$1.onStartup(EmbeddedWebApplicationContext.java:203)
[INFO] [talledLocalContainer]   at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.createEmbeddedServletContainer(EmbeddedWebApplicationContext.java:153)
[INFO] [talledLocalContainer]   at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.onRefresh(EmbeddedWebApplicationContext.java:121)
[INFO] [talledLocalContainer]   ... 20 more
[INFO] [talledLocalContainer] 

@philwebb
Copy link
Member

philwebb commented Dec 9, 2014

OK, I've needed to revert that because we can't add Servlets from the ServletContextListener.

@philwebb
Copy link
Member

philwebb commented Dec 9, 2014

@apogrebnyak Can you provide a bit more detail about the underlying reason that you wanted this change. Perhaps there is some other way that we can fix it?

@philwebb philwebb removed this from the 1.2.0 milestone Dec 9, 2014
@philwebb philwebb added the status: waiting-for-feedback We need additional information before we can continue label Dec 9, 2014
@apogrebnyak
Copy link
Author

I've reviewed your checking and now I see why I did not hit this exception earlier.

In the original proposal, the listener is created through anonymous class, and the ServletContext it uses is bound to the onStartup method's parameter.

In the 1763148 a stand-alone listener class is created and the context passed in the event is used.

After debugging Tomcat initialization code I see that the two ServletContexts are not created equals, and one in the event is significantly neutered facade (I guess Tomcat needs it to be this way in order to initialize application runtime listeners properly).

Now back to my reason for requesting this bug. I had a legacy ServletContextListener, that I wanted to initialize before the SpringBoot application.

I think, in view of how fragile ServletContextListener is, this proposition of rewriting onStartup method is out, however, looks like the original intent (of registering another ServletContextListener before hitting SpringBootServletInitializer.onStartup) can be achived by implementing Ordered interface in SpringBootServletInitializer, and giving it a neutral precedence of 0.

So, please revert to original onStartup implementation and I am going to create a new issue and paste a reference here

@philwebb
Copy link
Member

philwebb commented Dec 9, 2014

Great thanks. If you can provide a sample project with the new issue that you open that will be very helpful.

@philwebb philwebb closed this as completed Dec 9, 2014
@philwebb philwebb added status: declined A suggestion or change that we don't feel we should currently apply and removed status: waiting-for-feedback We need additional information before we can continue type: enhancement A general enhancement labels Dec 9, 2014
@apogrebnyak
Copy link
Author

Created request for SpringBootServletInitializer.Ordered implementation ->
#2098

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: declined A suggestion or change that we don't feel we should currently apply
Projects
None yet
Development

No branches or pull requests

3 participants