2012-11-04 63 views
32

我试图创建一个简单的webapp,没有使用Spring 3.1和嵌入式Jetty 8服务器的任何XML配置。Spring 3.1 WebApplicationInitializer&Embedded Jetty 8 AnnotationConfiguration

但是,我正在努力让Jetty认识到我的接口实现了Spring的应用程序。

项目结构:

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

初始化器上述类是一个简单的实施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"); 
    } 
} 

同样JettyServer是一个简单的实现嵌入式码头服务器的:

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(); 
    } 
} 

我的理解是,在启动时将码头使用AnnotationConfiguration扫描的ServletContainerInitializer 标注的实现;它应该找到初始化器和电线它...

然而,当我启动Jetty服务器(从Eclipse)我看到在命令行:

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 

最重要的一点是这样的:

No Spring WebApplicationInitializer types detected on classpath 

注意的src /主/ JAVA被定义为在Eclipse源文件夹,所以应该是类路径上。另请注意,Dynamic Web Module Facet设置为3.0。

我确定有一个简单的解释,但我很努力地看到树木!我怀疑,关键是与以下行:

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

使用web.xml文件(见下文),这是有道理的有2.5的servlet,但使用AnnotationConfiguration当什么应该是什么?

注:一切都正确触发,如果我改变配置为以下内容:

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

在这种情况下,它发现在的src/main/webapp的的web.xml并使用导线该servlet使用DispatcherServletAnnotationConfigWebApplicationContext按照常规方式(完全绕过上述WebApplicationInitializer执行)。

这感觉非常像类路径问题,但我很努力地理解Jetty如何将它自己与WebApplicationInitializer的实现联系起来 - 任何建议都将非常感谢!

有关信息,我使用的是以下几点:

春3.1.1 码头8.1.7 STS 3.1.0

回答

25

问题是,Jetty的AnnotationConfiguration类不会扫描类路径上的非jar资源(除了在WEB-INF/classes下)。

它找到我的WebApplicationInitializer的是否我注册了AnnotationConfiguration的子类,它覆盖configure(WebAppContext)以扫描除容器和web-inf位置之外的主机类路径。

大部分的子类是(可惜)从父母复制粘贴。它包括:

  • 一个额外的解析调用(parseHostClassPath)在配置方法的末尾;
  • parseHostClassPath这种方法主要是从 AnnotationConfigurationparseWebInfClasses复制粘贴;
  • getHostClassPathResource这个方法从类加载器中获取第一个非jar URL (至少对我来说是eclipse中的 类路径的文件url)。

我使用稍微不同的版本的Jetty(8.1.7.v20120910)和Spring(3.1.2_RELEASE),但我想象同样的解决方案将工作。

编辑:我在github上创建一个工作示例项目的一些修改(在阴影罐子运行时,下面的代码工作正常,从食,但没有) - https://github.com/steveliles/jetty-embedded-spring-mvc-noxml

在OP的JettyServer类进行必要的更改

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;      
      } 
     }, 
    }); 

更新:将与更换线15码头8.1.8引入了不兼容与上面的代码的内部变化。对于8.1。8以下似乎工作:

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();     
      } 
     } 
    }); 
+1

这似乎工作。你有没有想过通知Jetty或发送补丁? – Jason

+0

非常感谢您的详细解答 - 正是我所期待的。从码头和春天的角度看,我自己做了一场戏,现在对事物的工作方式有了更清晰的了解。肯定会考虑在Jetty中修复并提交补丁。 – Duncan

+0

@Duncan:这是否是Jetty提交的补丁/问题? –

2

根据我的测试,这个线程http://forum.springsource.org/showthread.php?127152-WebApplicationInitializer-not-loaded-with-embedded-Jetty我不认为这目前工作。如果你看看AnnotationConfiguration.configure:

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

它似乎耦合到战争般的部署,而不是嵌入式。

下面是使用Spring MVC的一个例子,嵌入式码头,可能是更为有用:

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

它创建春天的servlet直接而不是依靠注解。

+0

感谢您的詹姆斯沃德链接 - 很好知道如何直接/手动构建的东西;不知道这是我的一部分混乱。 – Duncan

1

什么只需设置上下文属性,告诉其事情上需要被扫描的容器类路径所属的扫描仪?

上下文属性: org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern 。 /servlet-api - [^ /] .jar $

它被设计成与jar名称一起使用,但您可以匹配所有内容。

您需要使用WebInfConfiguration以及AnnotationConfiguration类。

欢呼 月

+0

不幸的是,我不能得到那个工作。我将模式设置为。*,并且我看到我的类dir被添加到要扫描的URI列表中。不幸的是,AnnotationConfiguration在parseContainerPath中使用JarScanner,因为URI不会以“.jar”结尾,所以parseContainerPath会拒绝我的类dir。 – Stevie

+0

顺便说一句,你的个人资料中有一个错字 - 我想你的意思是webtide.com,而不是wetide.com? – Stevie

13

我能够只是明确地提供给AnnotationConfiguration,我想加载这样的实现类(在这个例子中MyWebApplicationInitializerImpl)以一种轻松,更有限的方式来解决:

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); 
     } 
    } 
}); 
+0

它的工作原理,谢谢! –

+0

为了让Jersey类扫描能够正常工作,我也将'WebAppContext.getWebInf()'覆盖为''return newResource(“target”);'而不是'{resourceBase}/WEB-INF/classes /'。 –

+0

我花了一段时间才弄清楚WebXmlConfiguration是允许Jetty服务资源(如src/main/webapp中的HTML和CSS)所必需的。另见'WebAppContext#setResourceBase()'。 –

6

Jetty 9.0.1包含一个增强功能,允许扫描容器类路径上非jar资源(即类)的注释。见注释#5以下问题如何使用它:

https://bugs.eclipse.org/bugs/show_bug.cgi?id=404176#c5

+2

有没有使用war文件的例子?使用。*和。*/classes /.*不起作用:|而且我没有真正的jetty_home,因为这是嵌入码头。 – BeepDog

4

下面的代码确实在我的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); 
    } 

} 
0

在我们的情况下,这些线帮助码头启动代码:

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

对于那些经验最近ncing此,看来这得到解决此问题:

@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"); 
    } 
} 
+0

这非常适合使用Spring的人。它看起来很干净,解决了所有问题。 –

0

码头9版“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); 
       } 
      } }); 
0

对于码头9,如果你有webjars,提供的解决方案不起作用因为那些Jars需要放在类路径中,并且JAR内容需要作为Web应用程序的资源提供。所以,对于接来一起webjars工作,配置必须是:

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); 
      } 
     } }); 

这里的顺序很重要(WebInfConfiguration有来MetaInf之前)。

2

为了使它在Jetty 9上设置AnnotationConfiguration属性。在WebAppContext

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

这里是如何创建此地图:

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

0

解决该解决方案,但使用WebApplicationInitializer你提供的课程。码头版本: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(); 
} 
} 

源(俄语)该代码片断是在这里:https://habrahabr.ru/post/255773/

0

做了一个简单的Maven项目来说明如何可以做到干净。

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; 
    } 
} 

,这一切 - 你使用不明确让码头服务器将获得被检测的春天WebApplicationInitializer知道这样的应用程序初始化的存在。

相关问题