2012-11-04 65 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知道這樣的應用程序初始化的存在。

相關問題