Skip to content
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

Allow @WebListener ServletContextListeners to programmatically add servlets and filters #18303

Closed
janbartel opened this issue Sep 23, 2019 · 6 comments
Assignees
Labels
type: enhancement A general enhancement
Milestone

Comments

@janbartel
Copy link

SpringBoot version 2.1.8.RELEASE

Please refer to jetty issue jetty/jetty.project#4103.
See also StackOverflow reports of same problem for Tomcat: https://stackoverflow.com/questions/44389716/spring-boot-embedded-tomcat-weblistener-scanned-by-servletcomponentscan

The issue is that a ServletContextListener that is annotated with @WebListener is not able to programmatically add Servlets (and I suppose Filters, but I didn't test that), but throws an UnsupportedOperationException. Here's an example of such an exception for jetty:

org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Jetty web server
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:156) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543) ~[spring-context-5.1.9.RELEASE.jar!/:5.1.9.RELEASE]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:744) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:391) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:312) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1204) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
	at com.example.demo.DemoApplication.main(DemoApplication.java:13) ~[classes!/:0.0.1-SNAPSHOT]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
	at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48) ~[demo-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:87) ~[demo-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:51) ~[demo-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:52) ~[demo-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
Caused by: org.springframework.boot.web.server.WebServerException: Unable to start embedded Jetty web server
	at org.springframework.boot.web.embedded.jetty.JettyWebServer.initialize(JettyWebServer.java:114) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
	at org.springframework.boot.web.embedded.jetty.JettyWebServer.<init>(JettyWebServer.java:86) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
	at org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory.getJettyWebServer(JettyServletWebServerFactory.java:398) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
	at org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory.getWebServer(JettyServletWebServerFactory.java:153) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:180) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:153) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
	... 16 common frames omitted
Caused by: java.lang.UnsupportedOperationException: null
	at org.eclipse.jetty.servlet.ServletContextHandler$Context.checkDynamic(ServletContextHandler.java:1088) ~[jetty-servlet-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
	at org.eclipse.jetty.servlet.ServletContextHandler$Context.addServlet(ServletContextHandler.java:1245) ~[jetty-servlet-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
	at com.example.demo.MyServletContextListener.contextInitialized(MyServletContextListener.java:16) ~[classes!/:0.0.1-SNAPSHOT]
	at org.eclipse.jetty.server.handler.ContextHandler.callContextInitialized(ContextHandler.java:930) ~[jetty-server-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
	at org.eclipse.jetty.servlet.ServletContextHandler.callContextInitialized(ServletContextHandler.java:555) ~[jetty-servlet-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
	at org.eclipse.jetty.server.handler.ContextHandler.startContext(ContextHandler.java:889) ~[jetty-server-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
	at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:357) ~[jetty-servlet-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
	at org.eclipse.jetty.webapp.WebAppContext.startWebapp(WebAppContext.java:1443) ~[jetty-webapp-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
	at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1407) ~[jetty-webapp-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
	at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:822) ~[jetty-server-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
	at org.eclipse.jetty.servlet.ServletContextHandler.doStart(ServletContextHandler.java:276) ~[jetty-servlet-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
	at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:524) ~[jetty-webapp-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
	at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:72) ~[jetty-util-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
	at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:169) ~[jetty-util-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
	at org.eclipse.jetty.server.Server.start(Server.java:407) ~[jetty-server-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
	at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:110) ~[jetty-util-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
	at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:106) ~[jetty-server-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
	at org.eclipse.jetty.server.Server.doStart(Server.java:371) ~[jetty-server-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
	at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:72) ~[jetty-util-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
	at org.springframework.boot.web.embedded.jetty.JettyWebServer.initialize(JettyWebServer.java:108) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]

This problem occurs because springboot is processing the @WebListener annotation and then using the ServletContext programmatic interfaces to add the listener: this classifies the listener as being programmatically added, and thus not permitted to add servlets/filters/listeners in its contextInitialized() method. See the ServletSpecification section 4.4:

If the ServletContext passed to the ServletContextListener’s
contextInitialized method where the ServletContextListener was neither
declared in web.xml or web-fragment.xml nor annotated with @WebListener
then an UnsupportedOperationException MUST be thrown for all the methods
defined in ServletContext for programmatic configuration of servlets, filters and
listeners.

Here is the springboot stacktrace showing where the @WebListener is being added as a programmatic listener:

	at org.eclipse.jetty.server.handler.ContextHandler.addProgrammaticListener(ContextHandler.java:718)
	at org.eclipse.jetty.servlet.ServletContextHandler.access$000(ServletContextHandler.java:89)
	at org.eclipse.jetty.servlet.ServletContextHandler$Context.addListener(ServletContextHandler.java:1439)
	at org.springframework.boot.web.servlet.ServletListenerRegistrationBean.register(ServletListenerRegistrationBean.java:116)
	at org.springframework.boot.web.servlet.RegistrationBean.onStartup(RegistrationBean.java:53)
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.selfInitialize(ServletWebServerApplicationContext.java:228)
	at org.springframework.boot.web.embedded.jetty.ServletContextInitializerConfiguration.callInitializers(ServletContextInitializerConfiguration.java:65)
	at org.springframework.boot.web.embedded.jetty.ServletContextInitializerConfiguration.configure(ServletContextInitializerConfiguration.java:54)
	at org.eclipse.jetty.webapp.WebAppContext.configure(WebAppContext.java:498)
	at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1402)
	at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:822)
	at org.eclipse.jetty.servlet.ServletContextHandler.doStart(ServletContextHandler.java:276)
	at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:524)
	at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:72)
	at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:169)
	at org.eclipse.jetty.server.Server.start(Server.java:407)
	at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:110)
	at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:106)
	at org.eclipse.jetty.server.Server.doStart(Server.java:371)
	at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:72)
	at org.springframework.boot.web.embedded.jetty.JettyWebServer.initialize(JettyWebServer.java:108)
	at org.springframework.boot.web.embedded.jetty.JettyWebServer.<init>(JettyWebServer.java:86)
	at org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory.getJettyWebServer(JettyServletWebServerFactory.java:398)
	at org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory.getWebServer(JettyServletWebServerFactory.java:153)
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:180)
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:153)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:744)
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:391)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:312)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1204)
	at com.example.demo.DemoApplication.main(DemoApplication.java:13)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48)
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:87)
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:51)
	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:52)
@wilkinsona
Copy link
Member

Thanks, @janbartel. This is a known limitation at the moment due to the way that Spring Boot scans for and registers @WebListener-annotated components. Is there an API in Jetty to register @WebListener-annotated components programmatically that would still allow them to perform their own programmatic registration of further components?

@wilkinsona wilkinsona added the status: waiting-for-feedback We need additional information before we can continue label Sep 23, 2019
@janbartel
Copy link
Author

@wilkinsona the way our own handling of @WebListener annotations adds a listener, using jetty-specific apis, is as follows:

ListenerHolder h = _context.getServletHandler().newListenerHolder(new Source(Source.Origin.ANNOTATION, clazz.getName()));
 h.setHeldClass(clazz);
_context.getServletHandler().addListener(h);

Where _context is a WebAppContext.

@larsgrefer
Copy link
Contributor

For JoinFaces we've implemented a WebServerFactoryCustomizer which is able to add listeners to the embedded Jetty, Tomcat or Undertow in such a way that they aren't affected by the restrictions of Section 4.4 of the ServletSpec: https://github.com/joinfaces/joinfaces/blob/master/joinfaces-autoconfigure/src/main/java/org/joinfaces/autoconfigure/servlet/WebFragmentRegistrationBean.java

@wilkinsona Could the implementation behind @ServletComponentScan use this in order to remove this "known limitation"?

@wilkinsona wilkinsona changed the title @WebListener ServletContextListeners should be able to programmatically add servlets and filters @WebListener ServletContextListeners cannot programmatically add servlets and filters Jan 14, 2020
@wilkinsona wilkinsona added type: bug A general bug and removed status: feedback-provided Feedback has been provided status: waiting-for-triage An issue we've not yet triaged labels Jan 14, 2020
@wilkinsona wilkinsona added this to the 2.2.x milestone Jan 14, 2020
@wilkinsona
Copy link
Member

I have a proposal for this but there is a small chance that it will be a breaking change. As such, I'm wonder if it may be a little too risky to do it in 2.2.x.

@wilkinsona wilkinsona added the for: team-attention An issue we'd like other members of the team to review label Jul 31, 2020
@wilkinsona wilkinsona self-assigned this Jul 31, 2020
@philwebb philwebb modified the milestones: 2.2.x, 2.4.x Jul 31, 2020
@philwebb philwebb added type: enhancement A general enhancement and removed for: team-attention An issue we'd like other members of the team to review type: bug A general bug labels Jul 31, 2020
@wilkinsona wilkinsona changed the title @WebListener ServletContextListeners cannot programmatically add servlets and filters Allow @WebListener ServletContextListeners to programmatically add servlets and filters Jul 31, 2020
@wilkinsona
Copy link
Member

The proposal requires some further thought as there are some package tangles at the moment.

@wilkinsona wilkinsona added the for: team-attention An issue we'd like other members of the team to review label Aug 3, 2020
@philwebb philwebb removed the for: team-attention An issue we'd like other members of the team to review label Aug 5, 2020
@wilkinsona
Copy link
Member

I've reworked the changes to remove the package tangles: https://github.com/wilkinsona/spring-boot/tree/gh-18303.

@wilkinsona wilkinsona added the for: team-attention An issue we'd like other members of the team to review label Sep 9, 2020
@philwebb philwebb self-assigned this Sep 12, 2020
@philwebb philwebb removed the for: team-attention An issue we'd like other members of the team to review label Sep 18, 2020
@philwebb philwebb modified the milestones: 2.4.x, 2.4.0-RC1 Oct 27, 2020
bencalegari added a commit to codeforamerica/shiba that referenced this issue Jan 30, 2021
bencalegari added a commit to codeforamerica/shiba that referenced this issue Jan 30, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

5 participants