java spring-mvc jetty embedded-jetty jetty-8

java - Spring 3.1 WebApplicationInitializer & Embedded Jetty 8 AnnotationConfiguration



spring-mvc embedded-jetty (13)

¿Qué hay de simplemente establecer el atributo de contexto que le dice al escáner qué cosas pertenecen a la classpath del contenedor que necesita ser escaneada?

atributo de contexto: org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern. / servlet-api - [^ /] .jar $

Está diseñado para ser usado con nombres de jar, pero podrías simplemente combinar todo.

Debería usar WebInfConfiguration, así como las clases AnnotationConfiguration.

aplausos Jan

Intento crear una aplicación web simple sin configuración XML utilizando Spring 3.1 y un servidor Jetty 8 integrado.

Sin embargo, estoy luchando para que Jetty reconozca mi implementación de la interfaz Spring WebApplicationInitializer .

Estructura del proyecto:

src +- main +- java | +- JettyServer.java | +- Initializer.java | +- webapp +- web.xml (objective is to remove this - see below).

La clase de inicializador anterior es una implementación simple de WebApplicationInitializer :

import javax.servlet.ServletContext; import javax.servlet.ServletException; import org.springframework.web.WebApplicationInitializer; public class Initializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { System.out.println("onStartup"); } }

Del mismo modo, JettyServer es una implementación simple de un servidor Jetty integrado:

import org.eclipse.jetty.annotations.AnnotationConfiguration; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.Configuration; import org.eclipse.jetty.webapp.WebAppContext; public class JettyServer { public static void main(String[] args) throws Exception { Server server = new Server(8080); WebAppContext webAppContext = new WebAppContext(); webAppContext.setResourceBase("src/main/webapp"); webAppContext.setContextPath("/"); webAppContext.setConfigurations(new Configuration[] { new AnnotationConfiguration() }); webAppContext.setParentLoaderPriority(true); server.setHandler(webAppContext); server.start(); server.join(); } }

Tengo entendido que al inicio Jetty usará AnnotationConfiguration para buscar implementaciones anotadas de ServletContainerInitializer ; debería encontrar Initializer y conectarlo en ...

Sin embargo, cuando inicio el servidor Jetty (desde dentro de Eclipse) veo lo siguiente en la línea de comandos:

2012-11-04 16:59:04.552:INFO:oejs.Server:jetty-8.1.7.v20120910 2012-11-04 16:59:05.046:INFO:/:No Spring WebApplicationInitializer types detected on classpath 2012-11-04 16:59:05.046:INFO:oejsh.ContextHandler:started o.e.j.w.WebAppContext{/,file:/Users/duncan/Coding/spring-mvc-embedded-jetty-test/src/main/webapp/} 2012-11-04 16:59:05.117:INFO:oejs.AbstractConnector:Started [email protected]:8080

Lo importante es esto:

No Spring WebApplicationInitializer types detected on classpath

Tenga en cuenta que src / main / java se define como una carpeta de origen en Eclipse, por lo que debe estar en el classpath. También tenga en cuenta que Dynamic Web Module Facet está configurado en 3.0.

Estoy seguro de que hay una explicación simple, ¡pero estoy luchando por ver la madera de los árboles! Sospecho que la clave está con la siguiente línea:

... webAppContext.setResourceBase("src/main/webapp"); ...

Esto tiene sentido con un servlet de 2.5 usando web.xml (ver más abajo), pero ¿qué debería ser al usar AnnotationConfiguration ?

NB: Todo se activa correctamente si cambio las configuraciones a lo siguiente:

... webAppContext.setConfigurations(new Configuration[] { new WebXmlConfiguration() }); ...

En este caso, encuentra el archivo web.xml en src / main / webapp y lo utiliza para conectar el servlet utilizando DispatcherServlet y AnnotationConfigWebApplicationContext de la forma habitual (omitiendo por completo la implementación de WebApplicationInitializer anterior).

Esto se parece mucho a un problema de classpath, pero estoy luchando por entender cómo Jetty se asocia con las implementaciones de WebApplicationInitializer . ¡Cualquier sugerencia sería muy apreciada!

Para información, estoy usando lo siguiente:

Spring 3.1.1 Jetty 8.1.7 STS 3.1.0


Basado en mis pruebas y este hilo http://forum.springsource.org/showthread.php?127152-WebApplicationInitializer-not-loaded-with-embedded-Jetty No creo que funcione en este momento. Si miras en AnnotationConfiguration.configure:

parseContainerPath(context, parser); // snip comment parseWebInfClasses(context, parser); parseWebInfLib (context, parser);

parece estar acoplado a un despliegue tipo guerra en lugar de incrustado.

Aquí hay un ejemplo usando Spring MVC y Embedded Jetty que podría ser más útil:

http://www.jamesward.com/2012/08/13/containerless-spring-mvc

Crea el servlet Spring directamente en lugar de confiar en las anotaciones.


El problema es que la clase Jetty''s AnnotationConfiguration no explora los recursos que no son jar en classpath (excepto en WEB-INF / classes).

Encuentra mi WebApplicationInitializer si registro una subclase de AnnotationConfiguration que anula la configure(WebAppContext) para analizar el classpath del host además de las ubicaciones del contenedor y web-inf.

La mayor parte de la subclase es (tristemente) copiar y pegar desde el padre. Incluye:

  • una llamada parse adicional ( parseHostClassPath ) al final del método de configuración;
  • el método parseHostClassPath que es en gran medida de copiar y pegar de parseWebInfClasses de AnnotationConfiguration ;
  • el método getHostClassPathResource que toma la primera URL no-jar del cargador de clases (que, al menos para mí, es la url del archivo para mi classpath en eclipse).

Estoy usando versiones ligeramente diferentes de Jetty (8.1.7.v20120910) y Spring (3.1.2_RELEASE), pero imagino que la misma solución funcionará.

Editar: Creé un proyecto de ejemplo en github con algunas modificaciones (el código siguiente funciona bien desde Eclipse pero no cuando se ejecuta en un contenedor sombreado) - https://github.com/steveliles/jetty-embedded-spring-mvc-noxml

En la clase JettyServer de OP, el cambio necesario reemplazaría a la línea 15 con:

webAppContext.setConfigurations (new Configuration [] { new AnnotationConfiguration() { @Override public void configure(WebAppContext context) throws Exception { boolean metadataComplete = context.getMetaData().isMetaDataComplete(); context.addDecorator(new AnnotationDecorator(context)); AnnotationParser parser = null; if (!metadataComplete) { if (context.getServletContext().getEffectiveMajorVersion() >= 3 || context.isConfigurationDiscovered()) { parser = createAnnotationParser(); parser.registerAnnotationHandler("javax.servlet.annotation.WebServlet", new WebServletAnnotationHandler(context)); parser.registerAnnotationHandler("javax.servlet.annotation.WebFilter", new WebFilterAnnotationHandler(context)); parser.registerAnnotationHandler("javax.servlet.annotation.WebListener", new WebListenerAnnotationHandler(context)); } } List<ServletContainerInitializer> nonExcludedInitializers = getNonExcludedInitializers(context); parser = registerServletContainerInitializerAnnotationHandlers(context, parser, nonExcludedInitializers); if (parser != null) { parseContainerPath(context, parser); parseWebInfClasses(context, parser); parseWebInfLib (context, parser); parseHostClassPath(context, parser); } } private void parseHostClassPath(final WebAppContext context, AnnotationParser parser) throws Exception { clearAnnotationList(parser.getAnnotationHandlers()); Resource resource = getHostClassPathResource(getClass().getClassLoader()); if (resource == null) return; parser.parse(resource, new ClassNameResolver() { public boolean isExcluded (String name) { if (context.isSystemClass(name)) return true; if (context.isServerClass(name)) return false; return false; } public boolean shouldOverride (String name) { //looking at webapp classpath, found already-parsed class of same name - did it come from system or duplicate in webapp? if (context.isParentLoaderPriority()) return false; return true; } }); //TODO - where to set the annotations discovered from WEB-INF/classes? List<DiscoveredAnnotation> annotations = new ArrayList<DiscoveredAnnotation>(); gatherAnnotations(annotations, parser.getAnnotationHandlers()); context.getMetaData().addDiscoveredAnnotations (annotations); } private Resource getHostClassPathResource(ClassLoader loader) throws IOException { if (loader instanceof URLClassLoader) { URL[] urls = ((URLClassLoader)loader).getURLs(); for (URL url : urls) if (url.getProtocol().startsWith("file")) return Resource.newResource(url); } return null; } }, });

Actualización : Jetty 8.1.8 introduce cambios internos que son incompatibles con el código anterior. Para 8.1.8, lo siguiente parece funcionar:

webAppContext.setConfigurations (new Configuration [] { // This is necessary because Jetty out-of-the-box does not scan // the classpath of your project in Eclipse, so it doesn''t find // your WebAppInitializer. new AnnotationConfiguration() { @Override public void configure(WebAppContext context) throws Exception { boolean metadataComplete = context.getMetaData().isMetaDataComplete(); context.addDecorator(new AnnotationDecorator(context)); //Even if metadata is complete, we still need to scan for ServletContainerInitializers - if there are any AnnotationParser parser = null; if (!metadataComplete) { //If metadata isn''t complete, if this is a servlet 3 webapp or isConfigDiscovered is true, we need to search for annotations if (context.getServletContext().getEffectiveMajorVersion() >= 3 || context.isConfigurationDiscovered()) { _discoverableAnnotationHandlers.add(new WebServletAnnotationHandler(context)); _discoverableAnnotationHandlers.add(new WebFilterAnnotationHandler(context)); _discoverableAnnotationHandlers.add(new WebListenerAnnotationHandler(context)); } } //Regardless of metadata, if there are any ServletContainerInitializers with @HandlesTypes, then we need to scan all the //classes so we can call their onStartup() methods correctly createServletContainerInitializerAnnotationHandlers(context, getNonExcludedInitializers(context)); if (!_discoverableAnnotationHandlers.isEmpty() || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty()) { parser = createAnnotationParser(); parse(context, parser); for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers) context.getMetaData().addDiscoveredAnnotations(((AbstractDiscoverableAnnotationHandler)h).getAnnotationList()); } } private void parse(final WebAppContext context, AnnotationParser parser) throws Exception { List<Resource> _resources = getResources(getClass().getClassLoader()); for (Resource _resource : _resources) { if (_resource == null) return; parser.clearHandlers(); for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers) { if (h instanceof AbstractDiscoverableAnnotationHandler) ((AbstractDiscoverableAnnotationHandler)h).setResource(null); // } parser.registerHandlers(_discoverableAnnotationHandlers); parser.registerHandler(_classInheritanceHandler); parser.registerHandlers(_containerInitializerAnnotationHandlers); parser.parse(_resource, new ClassNameResolver() { public boolean isExcluded (String name) { if (context.isSystemClass(name)) return true; if (context.isServerClass(name)) return false; return false; } public boolean shouldOverride (String name) { //looking at webapp classpath, found already-parsed class of same name - did it come from system or duplicate in webapp? if (context.isParentLoaderPriority()) return false; return true; } }); } } private List<Resource> getResources(ClassLoader aLoader) throws IOException { if (aLoader instanceof URLClassLoader) { List<Resource> _result = new ArrayList<Resource>(); URL[] _urls = ((URLClassLoader)aLoader).getURLs(); for (URL _url : _urls) _result.add(Resource.newResource(_url)); return _result; } return Collections.emptyList(); } } });


El siguiente código hizo el truco en mi proyecto de maven:

public static void main(String[] args) throws Exception { Server server = new Server(); ServerConnector scc = new ServerConnector(server); scc.setPort(Integer.parseInt(System.getProperty("jetty.port", "8080"))); server.setConnectors(new Connector[] { scc }); WebAppContext context = new WebAppContext(); context.setServer(server); context.setContextPath("/"); context.setWar("src/main/webapp"); context.getMetaData().addContainerResource(new FileResource(new File("./target/classes").toURI())); context.setConfigurations(new Configuration[]{ new WebXmlConfiguration(), new AnnotationConfiguration() }); server.setHandler(context); try { System.out.println(">>> STARTING EMBEDDED JETTY SERVER, PRESS ANY KEY TO STOP"); System.out.println(String.format(">>> open http://localhost:%s/", scc.getPort())); server.start(); while (System.in.available() == 0) { Thread.sleep(5000); } server.stop(); server.join(); } catch (Throwable t) { t.printStackTrace(); System.exit(100); } }


En nuestro caso, estas líneas ayudaron en el código de inicio de Jetty:

ClassList cl = Configuration.ClassList.setServerDefault(server); cl.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", "org.eclipse.jetty.annotations.AnnotationConfiguration");


Hizo un proyecto simple de maven para demostrar cómo se puede hacer limpiamente.

public class Console { public static void main(String[] args) { try { Server server = new Server(8080); //Set a handler to handle requests. server.setHandler(getWebAppContext()); //starts to listen at 0.0.0.0:8080 server.start(); server.join(); } catch (Exception e) { log.error("server exited with exception", e); } } private static WebAppContext getWebAppContext() { final WebAppContext webAppContext = new WebAppContext(); //route all requests via this web-app. webAppContext.setContextPath("/"); /* * point to location where the jar into which this class gets packaged into resides. * this could very well be the target directory in a maven development build. */ webAppContext.setResourceBase("directory_where_the_application_jar_exists"); //no web inf for us - so let the scanning know about location of our libraries / classes. webAppContext.getMetaData().setWebInfClassesDirs(Arrays.asList(webAppContext.getBaseResource())); //Scan for annotations (servlet 3+) final AnnotationConfiguration configuration = new AnnotationConfiguration(); webAppContext.setConfigurations(new Configuration[]{configuration}); return webAppContext; } }

y eso es todo: el Spring WebApplicationInitializer que utiliza se detectará sin que el servidor Jetty sepa explícitamente sobre la existencia de dicho inicializador de aplicaciones.



Para Jetty 9, si tiene webjars, la solución provista no funciona de inmediato ya que esos Jars deben estar en el classpath y los contenidos de JAR deben estar disponibles como recursos para su aplicación web. Entonces, para que eso funcione junto con webjars, la configuración debería ser:

context.setExtraClasspath(pathsToWebJarsCommaSeparated); context.setAttribute(WebInfConfiguration.WEBINF_JAR_PATTERN, ".*//.jar$"); context.setAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN, ".*//.jar$"); context.setConfigurations( new org.eclipse.jetty.webapp.Configuration[] { new WebInfConfiguration(), new MetaInfConfiguration(), new AnnotationConfiguration() { @Override public void preConfigure(WebAppContext context) throws Exception { final ClassInheritanceMap map = new ClassInheritanceMap(); final ConcurrentHashSet<String> set = new ConcurrentHashSet<>(); set.add(MyWebAppInitializer.class.getName()); map.put(WebApplicationInitializer.class.getName(), set); context.setAttribute(CLASS_INHERITANCE_MAP, map); _classInheritanceHandler = new ClassInheritanceHandler(map); } } });

El orden aquí es importante ( WebInfConfiguration tiene que venir antes que MetaInf ).


Para aquellos que están experimentando esto últimamente, parece que esto evita el problema:

@Component public class Initializer implements WebApplicationInitializer { private ServletContext servletContext; @Autowired public WebInitializer(ServletContext servletContext) { this.servletContext = servletContext; } @PostConstruct public void onStartup() throws ServletException { onStartup(servletContext); } public void onStartup(ServletContext servletContext) throws ServletException { System.out.println("onStartup"); } }


Para que funcione en Jetty 9 establece el atributo AnnotationConfiguration.CLASS_INHERITANCE_MAP en WebAppContext

webAppContext.setAttribute(AnnotationConfiguration.CLASS_INHERITANCE_MAP, createClassMap());

Y aquí está cómo crear este mapa:

private ClassInheritanceMap createClassMap() { ClassInheritanceMap classMap = new ClassInheritanceMap(); ConcurrentHashSet<String> impl = new ConcurrentHashSet<>(); impl.add(MyWebAppInitializer.class.getName()); classMap.put(WebApplicationInitializer.class.getName(), impl); return classMap; }

gitHub esa solución en gitHub


Pude resolverlo de una manera más fácil pero más limitada simplemente proporcionando explícitamente a AnnotationConfiguration la clase de implementación (MyWebApplicationInitializerImpl en este ejemplo) que quiero que se cargue así:

webAppContext.setConfigurations(new Configuration[] { new WebXmlConfiguration(), new AnnotationConfiguration() { @Override public void preConfigure(WebAppContext context) throws Exception { MultiMap<String> map = new MultiMap<String>(); map.add(WebApplicationInitializer.class.getName(), MyWebApplicationInitializerImpl.class.getName()); context.setAttribute(CLASS_INHERITANCE_MAP, map); _classInheritanceHandler = new ClassInheritanceHandler(map); } } });


Solución que funcionó para mí y que no requiere escaneo, pero usa la clase WebApplicationInitializer que usted proporciona. Versión del embarcadero: 9.2.20

public class Main { public static void main(String... args) throws Exception { Properties properties = new Properties(); InputStream stream = Main.class.getResourceAsStream("/WEB-INF/application.properties"); properties.load(stream); stream.close(); PropertyConfigurator.configure(properties); WebAppContext webAppContext = new WebAppContext(); webAppContext.setResourceBase("resource"); webAppContext.setContextPath(properties.getProperty("base.url")); webAppContext.setConfigurations(new Configuration[] { new WebXmlConfiguration(), new AnnotationConfiguration() { @Override public void preConfigure(WebAppContext context) { ClassInheritanceMap map = new ClassInheritanceMap(); map.put(WebApplicationInitializer.class.getName(), new ConcurrentHashSet<String>() {{ add(WebInitializer.class.getName()); add(SecurityWebInitializer.class.getName()); }}); context.setAttribute(CLASS_INHERITANCE_MAP, map); _classInheritanceHandler = new ClassInheritanceHandler(map); } } }); Server server = new Server(Integer.parseInt(properties.getProperty("base.port"))); server.setHandler(webAppContext); server.start(); server.join(); } }

La fuente (en ruso) de este fragmento de código está aquí: https://habrahabr.ru/post/255773/


Versión Jetty 9 de la respuesta "magomarcelo":

context.setConfigurations( new org.eclipse.jetty.webapp.Configuration[] { new WebXmlConfiguration(), new AnnotationConfiguration() { @Override public void preConfigure(WebAppContext context) throws Exception { final ClassInheritanceMap map = new ClassInheritanceMap(); final ConcurrentHashSet<String> set = new ConcurrentHashSet<>(); set.add(MyWebAppInitializer.class.getName()); map.put(WebApplicationInitializer.class.getName(), set); context.setAttribute(CLASS_INHERITANCE_MAP, map); _classInheritanceHandler = new ClassInheritanceHandler(map); } } });