是時候拋棄web.xml了?


你是否再為配置文件web.xml容易出錯而煩惱?是否為web.xml文件存放位置而不知所措?是否為web.xml為什么要這樣配?怎么才能更好的配置web.xml而煩惱?那么一種新的方式出現了:

spring提供了支持servlet 3+以上的編程方式,它可以替換或者和web.xml共存的方式工作。其相關類如下:

WebApplicationInitializer

  傳統上,我們基於web.xml這種方式來配置web應用,而WebApplicationInitializer的實現支持在servlet 3.0以上的環境里通過編程的方式來配置ServletContext,這種方式既可以替換web.xml這種方式,也可以和web.xml這種方式共存。

  這種SPI的實現通常由SpringServletContainerInitializer來自動發現,SpringServletContainerInitializer可以被servlet 3.0以上的容器自動啟動。詳細請參考下面的章節。示例如下:

   傳統基於xml的方式

    絕大多數spring開發者構建web應用時需要注冊spring DispatcherServlet到WEB/web.xml如下方式:

  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/spring/dispatcher-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
 
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

       基於編程方式的WebApplicationInitializer

下面基於WebApplicationInitializer樣式的代碼等同於DispatcherServlet的注冊邏輯:

  public class MyWebAppInitializer implements WebApplicationInitializer {
 
     @Override
     public void onStartup(ServletContext container) {
       XmlWebApplicationContext appContext = new XmlWebApplicationContext();
       appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
 
       ServletRegistration.Dynamic dispatcher =
         container.addServlet("dispatcher", new DispatcherServlet(appContext));
       dispatcher.setLoadOnStartup(1);
       dispatcher.addMapping("/");
     }
 
  }

上面的實現,還可以通過擴展org.springframework.web.servlet.support.AbstractDispatcherServletInitializer實現。

  如上所示,歸功於servlet3.0的ServletContext#addServlet方法,我們注冊一個DispatcherServlet的實例,這意味着DispatcherServlet可以如其他Object一樣,接受在應用上下文中通過構造器進行注入。

  這種方式不但簡單還很明了。不需要關注init-param的處理等等,僅有通常的javaBean樣式的屬性和構造參數。可以在DispatcherServlet注入之前,盡可能的自由的創建spring context、使用spring context。

  絕大部分spring web組件已經更新來支持這種注冊方式,你會發現DispatcherServlet、FrameworkServlet、ContextLoaderListener和DelegatingFilterProxy現在都支持構造參數。

  盡管個別組件(非spring的,其他第三方的)沒有更新到支持WebApplicationInitializer的使用,它們仍然可以使用。servlet 3.0的ServletContext api支持編碼式設置init-params,context-param等的屬性。

  基於編碼式的配置

  上例中,WEB/web.xml可以通過WebApplicationInitializer樣式的代碼完全替換掉,但真正的dispatcher-config.xml spring配置文件仍然是基於xml方式的。WebApplicationInitializer也是一個很棒的方式來進行spring的基於編程方式的配置類,詳情參考org.springframework.context.annotation.Configuration。

     下面的例子中,將使用spring的

org.springframework.web.context.support.AnnotationConfigWebApplicationContext
類來替代XmlWebApplicationContext,用戶自定義的@Configuration配置類AppConfig和DispatcherConfig來替換spring的xml文件來重構上面的例子。這個示例也有一些超出部分,用來展示root application context的的通用配置和ContextLoaderListener的注冊:

 public class MyWebAppInitializer implements WebApplicationInitializer {
 
     @Override
     public void onStartup(ServletContext container) {
       // Create the 'root' Spring application context
       AnnotationConfigWebApplicationContext rootContext =
         new AnnotationConfigWebApplicationContext();
       rootContext.register(AppConfig.class);
 
       // Manage the lifecycle of the root application context
       container.addListener(new ContextLoaderListener(rootContext));
 
       // Create the dispatcher servlet's Spring application context
       AnnotationConfigWebApplicationContext dispatcherContext =
         new AnnotationConfigWebApplicationContext();
       dispatcherContext.register(DispatcherConfig.class);
 
       // Register and map the dispatcher servlet
       ServletRegistration.Dynamic dispatcher =
         container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
       dispatcher.setLoadOnStartup(1);
       dispatcher.addMapping("/");
     }
 
  }

上面的示例的另一種實現方式,可以通過擴展org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer來實現。

注意,WebApplicationInitializer的實現是自動發現的,因此無需在你的應用中打包。

  WebApplicationInitializer執行順序

WebApplicationInitializer的實現可以通過@order來注解也可以通過實現spring的org.springframework.core.Ordered接口,如果這樣,就根據優先級去觸發。spring為用戶提供了一種在servlet container初始化時保證執行順序的機制。使用這種特性的應用場景比較罕見,因為典型的應用通常在一個單獨的WebApplicationInitializer中集中所有的容器初始化。

  附加說明

  web.xml版本信息

  WEB/web.xml和WebApplicationInitializer不是相斥的。例如web.xml可以注冊一個servlet,WebApplicationInitializer可以注冊另外一個servlet。一個WebApplicationInitializer甚至可以通過方法如ServletContext#getServletRegistration(String)來修改web.xml中的注冊信息。然而,若應用中出現web.xml,它的version屬性必須設置成3.0或者以上,否則ServletContainerInitializer將會在servlet容器啟動時被忽略啟動。

  tomcat下映射到"/"

   apache tomcat映射它內部的DefaultServlet到"/",並且當tomcat版本小於7.0.14時,這個映射屬性不能通過編碼重寫。7.0.15解決了這個問題。重寫"/"映射已經在glassFish3.1中進行了驗證確認。

public interface WebApplicationInitializer {

    /**
     * Configure the given {@link ServletContext} with any servlets, filters, listeners
     * context-params and attributes necessary for initializing this web application. See
     * examples {@linkplain WebApplicationInitializer above}.
     * @param servletContext the {@code ServletContext} to initialize
     * @throws ServletException if any call against the given {@code ServletContext}
     * throws a {@code ServletException}
     */
    void onStartup(ServletContext servletContext) throws ServletException;

}

1 SpringServletContainerInitializer

  和傳統基於web.xml的方式不同,servlet 3.0 ServletContainerInitializer 使用spring的WebApplicationInitializer來支持對servlet container的基於編程的配置支持。

 工作機制

  假定spring-web模塊的jar都出現在classpath上,在容器啟動時,servlet 3.0兼容的容器將會加載類,並初始化,然后觸發它的onStartup方法。Jar服務Api 方法ServiceLoader#load(class)發現spring-web模塊的META-INF/services/javax.servlet.ServletContainerInitializer服務提供配置文件,詳情參考http://docs.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#Service%20Provider

  和web.xml共用

  一個web應用在啟動階段選擇限制classpath 掃描的servlet container的數量的方式有兩種,一種通過web.xml的屬性metadata-complete,它控制server注解的掃描。另一種是通過web.xml中的absolute-ordering屬性,它控制哪些web片段(例如jar文件)允許servletContainerInitializer掃描。當使用這些特色,springServletContainerInitializer通過增加spring_web到web.xml的命名片段中來啟用這些,如下所示:

  <absolute-ordering>
    <name>some_web_fragment</name>
    <name>spring_web</name>
  </absolute-ordering>

 與spring的WebApplicationInitializer的關系

spring的WebApplicationInitializer spi僅僅包含了一個方法WebApplicationInitializer#onStartup(ServletContext),類似於ServletContainerInitializer#onStartup(Set, ServletContext)。SpringServletContainerInitializer負責初始化並對用戶定義的WebApplicationInitializer代理servletContext。然后負責讓每個WebApplicationInitializer去做servletContext初始化的具體工作。代理的精確過程描述在下文的onStartup方法。

 

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

    /**
     * Delegate the {@code ServletContext} to any {@link WebApplicationInitializer}
     * implementations present on the application classpath.
     * <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)},
     * Servlet 3.0+ containers will automatically scan the classpath for implementations
     * of Spring's {@code WebApplicationInitializer} interface and provide the set of all
     * such types to the {@code webAppInitializerClasses} parameter of this method.
     * <p>If no {@code WebApplicationInitializer} implementations are found on the classpath,
     * this method is effectively a no-op. An INFO-level log message will be issued notifying
     * the user that the {@code ServletContainerInitializer} has indeed been invoked but that
     * no {@code WebApplicationInitializer} implementations were found.
     * <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,
     * they will be instantiated (and <em>sorted</em> if the @{@link
     * org.springframework.core.annotation.Order @Order} annotation is present or
     * the {@link org.springframework.core.Ordered Ordered} interface has been
     * implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}
     * method will be invoked on each instance, delegating the {@code ServletContext} such
     * that each instance may register and configure servlets such as Spring's
     * {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},
     * or any other Servlet API componentry such as filters.
     * @param webAppInitializerClasses all implementations of
     * {@link WebApplicationInitializer} found on the application classpath
     * @param servletContext the servlet context to be initialized
     * @see WebApplicationInitializer#onStartup(ServletContext)
     * @see AnnotationAwareOrderComparator
     */
    @Override
    public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {

        List<WebApplicationInitializer> initializers = new LinkedList<>();

        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                // Be defensive: Some servlet containers provide us with invalid classes,
                // no matter what @HandlesTypes says...
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                        WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer)
                                ReflectionUtils.accessibleConstructor(waiClass).newInstance());
                    }
                    catch (Throwable ex) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
            return;
        }

        servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
        AnnotationAwareOrderComparator.sort(initializers);
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);
        }
    }

}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM