J'essaie de créer une application Web simple sans aucune configuration XML à l'aide de Spring 3.1 et d'un serveur Jetty 8 intégré.
Cependant, j'ai du mal à faire reconnaître par Jetty mon implémentation de l'interface Spring WebApplicationInitializer.
Structure du projet:
src
+- main
+- Java
| +- JettyServer.Java
| +- Initializer.Java
|
+- webapp
+- web.xml (objective is to remove this - see below).
La classe Initializer ci-dessus est une implémentation 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");
}
}
De même JettyServer est une implémentation simple d'un serveur Jetty intégré:
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();
}
}
Ma compréhension est qu'au démarrage, Jetty utilisera AnnotationConfiguration pour rechercher les implémentations annotées de ServletContainerInitializer; il devrait trouver Initializer et le câbler ...
Cependant, lorsque je démarre le serveur Jetty (depuis Eclipse), je vois ce qui suit sur la ligne de commande:
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
Le point important est le suivant:
No Spring WebApplicationInitializer types detected on classpath
Notez que src/main/Java est défini comme un dossier source dans Eclipse, donc devrait être sur le chemin de classe. Notez également que la facette du module Web dynamique est définie sur 3.0.
Je suis sûr qu'il y a une explication simple, mais j'ai du mal à voir le bois pour les arbres! Je soupçonne que la clé est avec la ligne suivante:
...
webAppContext.setResourceBase("src/main/webapp");
...
Cela est logique avec une servlet 2.5 utilisant web.xml (voir ci-dessous), mais que devrait-il être lors de l'utilisation de AnnotationConfiguration?
NB: Tout se déclenche correctement si je change les configurations comme suit:
...
webAppContext.setConfigurations(new Configuration[] { new WebXmlConfiguration() });
...
Dans ce cas, il trouve web.xml sous src/main/webapp et l'utilise pour câbler la servlet à l'aide de DispatcherServlet et AnnotationConfigWebApplicationContext de la manière habituelle (en ignorant complètement l'implémentation WebApplicationInitializer ci-dessus).
Cela ressemble beaucoup à un problème de chemin de classe, mais j'ai du mal à comprendre comment Jetty s'associe aux implémentations de WebApplicationInitializer - toutes les suggestions seraient les plus appréciées!
Pour info, j'utilise ce qui suit:
Spring 3.1.1 Jetty 8.1.7 STS 3.1.0
Le problème est que la classe AnnotationConfiguration
de Jetty n'analyse pas les ressources non-jar sur le chemin de classe (sauf sous WEB-INF/classes).
Il trouve mes WebApplicationInitializer
si j'enregistre une sous-classe de AnnotationConfiguration
qui remplace configure(WebAppContext)
pour analyser le chemin de classe Host en plus des emplacements de conteneur et web-inf.
La plupart de la sous-classe est (malheureusement) copiée-collée à partir du parent. Il comprend:
parseHostClassPath
) à la fin de la méthode configure;parseHostClassPath
qui est largement copiée-collée à partir de AnnotationConfiguration
de parseWebInfClasses
;getHostClassPathResource
qui récupère la première URL non-jar du chargeur de classe (qui, pour moi au moins, est l'url du fichier vers mon chemin de classe dans Eclipse).J'utilise des versions légèrement différentes de Jetty (8.1.7.v20120910) et Spring (3.1.2_RELEASE), mais j'imagine que la même solution fonctionnera.
Edit: J'ai créé un exemple de projet de travail dans github avec quelques modifications (le code ci-dessous fonctionne bien depuis Eclipse mais pas lors de l'exécution dans un pot ombré) - https: // github. com/steveliles/jetty-embedded-spring-mvc-noxml
Dans la classe JettyServer de l'OP, la modification nécessaire remplacerait la ligne 15 par:
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;
}
},
});
Mise à jour : Jetty 8.1.8 introduit des modifications internes incompatibles avec le code ci-dessus. Pour 8.1.8, les éléments suivants semblent fonctionner:
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();
}
}
});
J'ai pu résoudre de manière plus simple mais plus limitée en fournissant simplement explicitement à AnnotationConfiguration la classe d'implémentation (MyWebApplicationInitializerImpl dans cet exemple) que je souhaite charger comme ceci:
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);
}
}
});
Jetty 9.0.1 contient une amélioration qui permet d'analyser les annotations des ressources non-jar (c'est-à-dire les classes) sur le chemin d'accès aux classes du conteneur. Voir le commentaire # 5 sur le problème suivant pour savoir comment l'utiliser:
https://bugs.Eclipse.org/bugs/show_bug.cgi?id=404176#c5
Jan
Le code ci-dessous a fait l'affaire dans mon projet 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);
}
}
Basé sur mes tests et ce fil http://forum.springsource.org/showthread.php?127152-WebApplicationInitializer-not-loaded-with-embedded-Jetty Je ne pense pas que cela fonctionne à la moment. Si vous regardez dans AnnotationConfiguration.configure:
parseContainerPath(context, parser);
// snip comment
parseWebInfClasses(context, parser);
parseWebInfLib (context, parser);
il semble couplé à un déploiement guerrier plutôt qu'incorporé.
Voici un exemple utilisant Spring MVC et Jetty intégré qui pourrait être plus utile:
http://www.jamesward.com/2012/08/13/containerless-spring-mvc
Il crée directement la servlet Spring plutôt que de s'appuyer sur des annotations.
Pour ceux qui en sont victimes récemment, il semble que cela contourne le problème:
@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");
}
}
Pour le faire fonctionner sur Jetty 9, définissez l'attribut AnnotationConfiguration.CLASS_INHERITANCE_MAP sur WebAppContext
webAppContext.setAttribute(AnnotationConfiguration.CLASS_INHERITANCE_MAP, createClassMap());
Et voici comment créer cette carte:
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;
}
J'ai placé cette solution sur gitHub
Que diriez-vous de simplement définir l'attribut de contexte qui indique au scanner quelles choses appartiennent au chemin de classe du conteneur qui doivent être analysées?
attribut de contexte: org.Eclipse.jetty.server.webapp.ContainerIncludeJarPattern ./ servlet-api - [^ /]. jar $
Il est conçu pour être utilisé avec des noms de pots, mais vous pouvez tout faire correspondre.
Vous devez utiliser la classe WebInfConfiguration ainsi que les classes AnnotationConfiguration.
cheers Jan
Pour Jetty 9, si vous avez des webjars, la solution fournie ne fonctionne pas tout de suite car ces Jars doivent être sur le chemin de classe et le contenu JAR doit être disponible en tant que ressources pour votre webapp. Donc, pour que cela fonctionne avec les webjars, la configuration devrait être:
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);
}
} });
L'ordre ici est important (WebInfConfiguration doit venir avant MetaInf).
Version Jetty 9 de "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);
}
} });
a fait un simple projet maven pour démontrer comment cela peut être fait proprement.
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;
}
}
et c'est tout - le WebApplicationInitializer de printemps que vous utilisez sera détecté sans informer explicitement le serveur de la jetée de l'existence d'un tel initialiseur d'application.
Solution qui a fonctionné pour moi et n'implique pas l'analyse, mais utilise la classe WebApplicationInitializer que vous fournissez. Version jetée: 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 source (en russe) de cet extrait de code est ici: https://habrahabr.ru/post/255773/
Dans notre cas, ces lignes ont aidé dans le code de démarrage de Jetty:
ClassList cl = Configuration.ClassList.setServerDefault(server);
cl.addBefore("org.Eclipse.jetty.webapp.JettyWebXmlConfiguration", "org.Eclipse.jetty.annotations.AnnotationConfiguration");