Servlet规范之注解与可插拔性

Annotations and pluggability

文章是对 JSR-000340 JavaTM Servlet 3.1 Final Release的Java™ Servlet规范的翻译,尚未校准


本章描述了注解和其他增强功能的使用,以实现框架和库的可插拔性,以便在Web应用程序中使用。

注释和可插拔性

在Web应用程序中,使用注解的类只有在位于WEB-INF/classes目录下,或者在应用程序中被打包在位于WEB-INF/lib的jar文件中时,其注解才会得到处理。网络应用程序部署描述符在web-app元素上包含一个新的"metadata-complete"属性。metadata-complete"属性定义了web描述符是否完整,或者jar文件的类文件是否应该在部署时检查注释和web片段。如果 "metadata-complete "被设置为 “true”,部署工具必须忽略任何Servlet注解,这些注解指定了应用和Web片段的类文件中存在的部署信息。如果没有指定元数据完整属性或将其设置为 “false”,部署工具必须检查应用程序的类文件以获得注释,并扫描Web片段。以下是必须由符合Servlet 3.0的Web容器支持的注释。

@WebServlet

该注解用于在Web应用程序中定义一个 "Servlet "组件。这个注解被指定在一个类上,并包含关于被声明的Servlet的元数据。注解中的urlPatternsvalue属性必须存在。所有其他属性都是可选的,有默认设置(更多细节见javadocs)。当注解上唯一的属性是url模式时,建议使用value,当其他属性也被使用时,使用urlPatterns属性。在同一个注解上同时使用valueurlPatterns属性是非法的。如果没有指定 “Servlet”,其默认名称是完全合格的类名称。被注解的Servlet必须至少指定一个要部署的url模式。如果在部署描述符中以不同的名称声明相同的servlet类,则必须实例化该servlet的新实例。如果通过第4.4.1节 “Programmatically adding and configuring Servlets”(第4-35页)中定义的编程API,以不同的名称将相同的Servlet类添加到 “ServletContext"中,通过”@WebServlet"注解声明的属性值必须被忽略,并且必须创建一个具有指定名称的Servlet的新实例。

@WebServlet类注释的类必须扩展javax.servlet.http.HttpServlet类。

下面是一个如何使用这种注释的例子。

CODE EXAMPLE 8-1 @WebServlet Annotation Example

@WebServlet(/foo”)
public class CalculatorServlet extends HttpServlet{
	//...
}

下面是一个例子,说明如何使用这个注释,并指定一些更多的属性。

CODE EXAMPLE 8-2 @WebServlet annotation example using other annotation attributes specified

@WebServlet(name=MyServlet, urlPatterns={"/foo", "/bar"})
public class SampleUsingAnnotationAttributes extends HttpServlet{
	public void doGet(HttpServletRequest req, HttpServletResponse res) {
	}
}

@WebFilter

该注解用于在Web应用程序中定义一个 “Filter”。这个注解被指定在一个类上,并包含关于被声明的过滤器的元数据。如果没有指定,"过滤器 "的默认名称是完全合格的类名称。urlPatterns属性、servletNames属性或注释的value属性必须被指定。所有其他属性都是可选的,有默认设置(更多细节见javadocs)。当注解的唯一属性是url模式时,建议使用value,当其他属性也被使用时,建议使用urlPatterns属性。在同一个注解上同时使用value和urlPatterns属性是非法的。

@WebFilter注释的类必须实现javax.servlet.Filter

下面是一个如何使用这种注释的例子。

CODE EXAMPLE 8-3 @WebFilter annotation example

@WebFilter(/foo”)
public class MyFilter implements Filter {
public void doFilter(HttpServletRequest req, HttpServletResponse res)
    {
    ...
    }
}

@WebInitParam

这个注解用于指定任何必须传递给ServletFilter的初始参数。它是 WebServletWebFilter注解的一个属性。

@WebListener

WebListener注解用于注解一个监听器,以获取特定Web应用程序上下文的各种操作的事件。用@WebListener注解的类必须实现以下接口之一:

  • javax.servlet.ServletContextListener
  • javax.servlet.ServletContextAttributeListener
  • javax.servlet.ServletRequestListener
  • javax.servlet.ServletRequestAttributeListener
  • javax.servlet.http.HttpSessionListener
  • javax.servlet.http.HttpSessionAttributeListener
  • javax.servlet.http.HttpSessionIdListener

An example:

@WebListener
public class MyListener implements ServletContextListener{
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext sc = sce.getServletContext();
        sc.addServlet("myServlet", "Sample servlet", "foo.bar.MyServlet", null, -1);
        sc.addServletMapping("myServlet", new String[] {"/urlpattern/*"});
    }
}

@MultipartConfig

当在一个Servlet上指定这个注解时,表明它所期望的请求是mime/multipart类型。相应Servlet的HttpServletRequest对象必须通过getPartsgetPart方法来提供mime附件,以遍历各种mime附件。javax.servlet.annotation.MultipartConfig<multipart-config><location>元素的location属性被解释为绝对路径,默认为javax.servlet.context.tempdir的值。如果指定了一个相对路径,它将是相对于tempdir的位置。绝对路径与相对路径的测试必须通过java.io.File.isAbsolute来完成。

其他注释/约定

除了这些注解之外,第15-189页的第15.5节 "Annotations and Resource Injection"中定义的所有注解将继续在这些新注解的背景下工作。

默认情况下,所有应用程序都会在 welcome-file-list中包含index.htm(l)index.jsp。该描述符可以用来覆盖这些默认设置。

当使用注解时,从WEB-INF/classesWEB-INF/lib中的各种框架jars/class中加载监听器、Servlets的顺序是没有规定的。如果排序很重要,那么请看下面的web.xml的模块化以及web.xmlweb-fragment.xml的排序部分。顺序可以只在部署描述符中指定。

Pluggability

web.xml web.xml的模块化程度

使用上面定义的注解使web.xml的使用成为可选项。然而,对于覆盖默认值或通过注释设置的值,部署描述符被使用。和以前一样,如果metadata-complete元素在web.xml描述符中被设置为true,那么指定存在于类文件和捆绑在jars中的web-fragments的部署信息的注释将不会被处理。这意味着应用程序的所有元数据都是通过web.xml描述符指定的。

为了提高可插拔性和减少开发人员的配置,我们引入了web模块部署描述符片段(web fragment)的概念。web片段是web.xml的一部分或全部,可以指定并包含在一个库或框架jar的META-INF目录中。在WEB-INF/ lib目录下的一个普通的jar文件,没有web-fragment.xml也被认为是一个片段。其中指定的任何注释将按照 8.2.3 中定义的规则进行处理。容器将按照下面定义的规则拾取和使用配置。

Web 片段是对 Web 应用程序的逻辑划分,其方式是在 Web 应用程序中使用的框架可以定义所有的工件,而不要求开发者在 web.xml 中编辑或添加信息。它几乎可以包括web.xml描述符使用的所有相同元素。然而,描述符的顶层元素必须是web-fragment,相应的描述符文件必须称为web-fragment.xml。web-fragment.xml和web.xml的相关元素的排序也不同,请看第14章部署描述符部分中的web-fragments的相应模式。

如果一个框架被打包成jar文件,并且有部署描述符形式的元数据信息,那么web-fragment.xml描述符必须在jar文件的META-INF/目录下。

如果一个框架希望它的META-INF/web-fragment.xml以这样一种方式被尊重,即它增强了Web应用程序的web.xml,那么该框架必须被捆绑在Web应用程序的WEB-INF/lib目录中。为了使框架的任何其他类型的资源(例如,类文件)对Web应用程序可用,只要框架出现在Web应用程序的类加载器委托链的任何地方就可以了。换句话说,只有捆绑在Web应用的WEB-INF/lib目录下的JAR文件,而不是类加载授权链中更高的文件,需要扫描web-fragment.xml

在部署过程中,容器负责扫描上面指定的位置,发现web-fragment.xml并处理它们。目前对单个web.xml存在的关于名称唯一性的要求也适用于web.xml和所有适用的web-fragment.xml文件的联合。

一个库或框架可以包括的内容的例子如下所示

<web-fragment>
    <servlet>
        <servlet-name>welcome</servlet-name>
        <servlet-class>WelcomeServlet</servlet-class>
    </servlet>
    <listener>
        <listener-class>
            RequestListener
        </listener-class>
    </listener>
</web-fragment>

上述web-fragment.xml将被包含在框架的jar文件的META-INF/目录中。web-fragment.xml中的配置和注解的应用顺序是未定义的。如果顺序对一个特定的应用来说是一个重要的方面,请看下面定义的规则,如何实现所需的顺序。

web.xml和web-fragment.xml的排序

由于规范允许应用程序配置资源由多个配置文件(web.xmlweb-fragment.xml)组成,从应用程序的几个不同地方发现和加载,所以必须解决排序的问题。本节规定了配置资源作者如何声明其工件的排序要求。

一个web-fragment.xml可以有一个顶层的<name>元素,类型为javaee:java-identifierType。一个web-fragment.xml中只能有一个<name>元素。如果有一个<name>元素存在,在对工件排序时必须考虑它(除非重复的名字例外,如下所述)。

必须考虑两种情况,以允许应用配置资源表达其排序偏好。

  1. 绝对排序:在web.xml中的一个<absolute-ordering>元素。一个web.xml中只能有一个<absolute-ordering>元素。

    1. 在这种情况下,必须忽略本来由下面情况2处理的排序偏好。
    2. web.xml和 WEB-INF/classes 必须在absolute-ordering元素中列出的任何web-fragments之前被处理。
    3. 任何<name>元素的直接子元素<absolute-ordering>必须被解释为表示那些被命名的web-fragments(可能存在也可能不存在)必须被处理的绝对顺序。
    4. <absolute-ordering>元素可以包含零或一个<others/>元素。这个元素所需的行动在下面描述。如果<absolute-ordering>元素不包含<others/>元素,任何在<name/>元素中没有特别提到的web-fragment都必须被忽略。被排除的罐子不会被扫描出有注释的Servlet、过滤器或监听器。然而,如果被排除的jar中的servlet、过滤器或监听器被列在web.xml或非排除的web-fragment.xml中,那么它的注释将被应用,除非被metadata-complete排除。在被排除的jars的TLD文件中发现的ServletContextListeners不能使用编程API来配置过滤器和servlets。任何试图这样做的行为都会导致 “非法状态异常”(IllegalStateException)。如果一个被发现的ServletContainerInitializer是从被排除的jar加载的,它将被忽略。不管metadata-complete的设置如何,被<absolute-ordering>元素排除的jar不会被扫描,以寻找任何ServletContainerInitializer要处理的类。
    5. 重复名称例外:如果在遍历<absoluteordering>的子节点时,遇到具有相同<name>元素的多个子节点,必须只考虑第一次出现的这种情况。
  2. 相对排序:在web-fragment.xml中的一个<ordering>元素。一个web-fragment.xml中只能有一个<ordering>元素。

    1. 一个 web-fragment.xml可能有一个<ordering>元素。如果是这样,这个元素必须包含零或一个<before>元素和零或一个after>元素。这些元素的含义将在下面解释。
    2. web.xml和WEB-INF/classes必须在ordering元素中列出的任何web-fragments之前被处理。
    3. 重复名称例外:如果在遍历web-fragments时,遇到具有相同<name>元素的多个成员,应用程序必须记录一个信息性的错误信息,包括帮助解决问题的信息,并且必须不能部署。例如,解决这个问题的一个方法是用户使用绝对排序,在这种情况下,相对排序被忽略。
    4. 考虑一下这个简略但能说明问题的例子。3个web-fragments - MyFragment1, MyFragment2MyFragment3是应用程序的一部分,也包括web.xml
web-fragment.xml
<web-fragment>
<name>MyFragment1</name>
<ordering><after><name>MyFragment2</name></after></ordering>
...
</web-fragment>

web-fragment.xml
<web-fragment>
<name>MyFragment2</name>
..
</web-fragment>

web-fragment.xml
<web-fragment>
<name>MyFragment3</name>
<ordering><before><others/></before></ordering>
..
</web-fragment>

web.xml
<web-app>
...
</web-app>

在这个例子中,处理顺序将为

web.xml
MyFragment3
MyFragment2
MyFragment1

前面的例子说明了以下一些原则,但不是全部。

  • <before>表示该文件必须排序在名称与嵌套的<name>元素相符的文件之前。

  • <after>表示文件必须排序在名字与嵌套的<name>元素相匹配的文件之后。

  • 有一个特殊的元素<others/>,它可以在<before><after>元素中包含零次或一次,或者在<absolute-ordering>元素中直接包含零次或一次。<others/>元素必须按以下方式处理。

    • 如果<before>元素包含一个嵌套的<others/>,该文件将被移到排序文件列表的开头。如果有多个文件说明<before><others/>,它们都将在排序文件列表的开头,但在这类文件组中的排序是没有规定的。
    • 如果<after>元素包含一个嵌套的<others/>,该文件将被移到排序文件列表的最后。如果有多个文件需要<after><others/>,它们都将在排序文件列表的末尾,但在这类文件组中的排序是未指定的。
    • 在一个<before><after>元素内,如果有一个<others/>元素存在,但不是其父元素内唯一的<name>元素,在排序过程中必须考虑该父元素内的其他元素。
    • 如果<others/>元素直接出现在<absoluteordering>元素内,运行时必须确保任何未在<absolute-ordering>部分明确命名的网络碎片在该点被包括在处理顺序中。
  • 如果web-fragment.xml文件没有<ordering>,或者web.xml没有<absolute-ordering>元素,则认为工件没有任何排序依赖。

  • 如果运行时发现了循环引用,必须记录一条信息,并且应用程序必须不能部署。同样,用户可以采取的一个行动方案是在web.xml中使用绝对排序。

  • 前面的例子可以扩展到说明web.xml包含排序部分时的情况。

    web.xml
    <web-app>
    <absolute-ordering>
    <name>MyFragment3</name>
    <name>MyFragment2</name>
    </absolute-ordering>
    ...
    </web-app>
    

在这个例子中,各种元素的排序将是

web.xml
MyFragment3
MyFragment2

下面包括一些额外的例子情况。所有这些都适用于相对排序,而不是绝对排序

Document A:
<after>
<others/>
<name>
C
</name>
</after>

Document B
<before>
<others/>
</before>

Document C:
<after>
<others/>
</after>

Document D: no ordering

Document E: no ordering

Document F:
<before>
<others/>
<name>
B
</name>
</before>

Resulting parse order: web.xml, F, B, D, E, C, A.

Document <no id>:
<after>
<others/>
</after>
<before>
<name>
C
</name>
</before>
Document B:
<before>
<others/>
</before>
Document C: no ordering
Document D:
<after>
<others/>
</after>
Document E:
<before>
<others/>
</before>
Document F: no ordering

Resulting parse order can be one of the following:

  • B, E, F, <no id>, C, D

  • B, E, F,<no id>, D, C

  • E, B, F, <no id>, C, D

  • E, B, F, <no id>, D, C

  • E, B, F, D, <no id>, C

  • E, B, F, D, <no id>, D

    Document A:
    <after>
    <name>
    B
    </name>
    </after>
    Document B: no ordering
    Document C:
    <before>
    <others/>
    </before>
    Document D: no ordering
    

Resulting parse order: C, B, D, A. The parse order could also be: C, D, B, A or C, B, A, D

从web.xml、web-fragment.xml和注解中组装描述符

如果listeners、servlets、filters的调用顺序对一个应用程序很重要,那么必须使用部署描述符。另外,如果有必要,可以使用上面定义的排序元素。如上所述,当使用注解来定义listeners、servlets、filters时,它们被调用的顺序是未指定的。下面是一组适用于组装应用程序的最终部署描述符的规则。

  1. listeners, servlets, filters的顺序必须在web-fragment.xmlweb.xml中指定。

  2. 排序将基于它们在描述符中定义的顺序和web.xml中的absolute-ordering元素或web-fragment.xml中的排序元素(如果存在)。

    1. 匹配请求的过滤器是按照它们在web.xml中声明的顺序链式的。
    2. Servlet可以在请求处理时被懒惰地初始化,或在部署时被急切地初始化。在后一种情况下,它们是按照它们的 "load-on-startup"元素的顺序来初始化的。
    3. listeners是按照它们在web.xml中声明的顺序来调用的,具体规定如下。
      1. javax.servlet.ServletContextListener的实现在其contextInitialized方法中按其声明的顺序被调用,在其contextDestroyed方法中按相反顺序被调用。
      2. javax.servlet.ServletRequestListener的实现会按照它们被声明的顺序在它们的requestInitialized方法处被调用,并按照相反的顺序在它们的requestDestroyed方法处被调用。
      3. javax.servlet.http.HttpSessionListener的实现会按照它们被声明的顺序在它们的sessionCreated方法中被调用,并按照相反的顺序在它们的sessionDestroyed方法中被调用。
      4. javax.servlet.ServletContextAttributeListenerjavax.servlet.ServletRequestAttributeListenerjavax.servlet.HttpSessionAttributeListener的实现方法在相应的事件被触发时按照声明的顺序被调用。
  3. 如果使用web.xml中引入的enabled元素禁用一个Servlet,那么该Servlet将不能在为该Servlet指定的url-pattern中使用。

  4. 在解决web.xmlweb-fragment.xml和注释之间的冲突时,web应用程序的web.xml具有最高优先权。

  5. 如果描述符中没有指定metadata-complete,或者在部署描述符中设置为false,那么应用程序的有效元数据将通过合并注释和描述符中存在的元数据而得到。合并的规则规定如下:

    1. 在web fragments中的配置设置被用来增加那些在主web.xml中指定的配置,就像它们被指定在同一个web.xml中一样。

    2. 网络片段的配置设置被添加到主web.xml中的配置设置的顺序,如第8.2.2节 “Ordering of web.xml and web-fragment.xm”(第8-72页)中的规定。

    3. metadata-complete属性在主web.xml中设置为true时,被认为是完整的,在部署时不会发生注释和片段的扫描。如果存在absolute-ordering和排序元素,将被忽略。当在一个片段上设置为true'时,metadata-complete’属性只适用于扫描该特定jar中的注释。

    4. 除非metadata-complete被设置为true,否则网络片段将被合并到主web.xml中。合并是在相应片段的注释处理之后进行的。

    5. The following are considered configuration conflicts when augmenting a web.xml with web fragments:

    6. 当用web fragments丰富web.xml配置时,以下被认为是配置冲突:

      1. 多个 init-param 元素具有相同的<param-name>但不同的<param-value>
      2. 多个<mime-mapping>元素具有相同的<extension>但不同的<mime-type>
    7. 上述配置冲突的解决方法如下:

      1. 主web.xml和web片段之间的配置冲突被解决,从而使web.xml中的配置具有优先权。
      2. 两个web片段之间的配置冲突,如果冲突中心的元素不在主web.xml中,将导致一个错误。必须记录一个信息性的消息,并且应用程序必须无法部署。
    8. 在解决了上述冲突之后,还要应用这些额外的规则:

      1. 可以声明任何次数的元素在所产生的web.xml'中的web-fragments’之间是相加的。例如,具有不同的 "参数名称 "的<context-param>元素是相加的。
      2. 可以声明任何次数的元素,如果在web.xml中指定,将覆盖web-fragments中指定的同名值。
      3. 如果一个最小出现次数为0,最大出现次数为1的元素出现在web片段中,而在主web.xml中缺失,主web.xml将继承web片段中的设置。如果该元素同时出现在主web.xml和网络片段中,则以主web.xml中的配置设置为准。例如,如果主web.xml和一个web片段都声明了同一个Servlet,而web片段中的Servlet声明指定了一个<load-on-startup>元素,而主web.xml中没有,那么web片段中的<load-on-startup>元素将在合并的web.xml中使用。
      4. 如果一个最小出现次数为0,最大出现次数为1的元素,在两个网络片段中被指定为不同的元素,而在主web.xml中却没有,这将被视为一个错误。例如,如果两个web-fragments声明了相同的servlet,但有不同的<load-on-startup>元素,而在主web.xml中也声明了相同的servlet,但没有任何<load-on-startup>,那么必须报告一个错误。
      5. <welcome-file>的声明是附加的。
      6. <servlet-mapping>元素具有相同的<servlet-name>,在不同的web-fragments中是相加的。在 "web.xml "中指定的<servlet-mapping>将覆盖在具有相同<servlet-name>的web-fragments中的值。
      7. 具有相同<filter-name><filter-mapping>元素在网络片段中是相加的。在 "web.xml “中指定的<filter-mapping>将覆盖在具有相同”<filter-name>"的web-fragments中指定的值。
      8. 具有相同<listener-class>的多个<listener>元素被视为一个<listener>声明
      9. 合并产生的web.xml只有在其所有网络片段也被标记为<distributable>的情况下才被视为<distributable>
      10. 顶层的<icon>和它的子元素,<display-name><description>元素在一个web片段中被忽略。
      11. jsp-property-group是加法的。当在jar文件的META-INF/resources目录下捆绑静态资源时,建议jsp-config元素使用url-pattern,而不是扩展映射。更多的片段的JSP资源应该在一个与片段名称相同的子目录下,如果存在的话。这有助于防止web-fragment的jsp-property-group影响应用程序主文件根中的JSP,以及jsp-property-group影响fragment的META-INF/resources目录中的JSP。
    9. 对于所有的资源引用元素(env-entryejb-refejblocal-refservice-refresource-refresource-env-refmessage-destination-refpersistence-context-refpersistence-unit-ref)适用以下规则:

      1. 如果任何资源参考元素出现在网络片段中,而在主web.xml中没有,主web.xml继承网络片段的值。如果该元素同时出现在主web.xml和网络片段中,且名称相同,web.xml优先。除了下面指定的injection-target外,片段中的子元素都不会被合并到主web.xml中。例如,如果主web.xml和web片段都声明了一个<resource-ref>,具有相同的<resource-ref-name>web.xml中的<resource-ref>将被使用,除了下面描述的<injection-target>,没有任何子元素被从片段中合并。
      2. 如果在两个片段中指定了一个资源参考元素,而在主web.xml中没有,并且资源参考元素的所有属性和子元素是相同的,资源参考将被合并到主web.xml中。如果一个资源引用元素在两个片段中指定了相同的名称,而不在主web.xml中,并且两个片段中的属性和子元素不相同,这将被视为一个错误。必须报告一个错误,并且应用程序的部署必须失败。例如,如果两个web片段声明一个<resource-ref>,具有相同的<resource-ref-name>元素,但其中一个的类型被指定为javax.sql.DataSource,而另一个的类型是JavaMail资源,这是一个错误,应用程序将无法部署。
      3. 对于具有相同名称的资源参考元素<injection-target>元素将被合并到主web.xml中。
    10. 除了上面定义的web-fragment.xml的合并规则外,在使用资源引用注解(@Resource@Resources@EJB@EJBs@WebServiceRef@WebServiceRefs@PersistenceContext@PersistenceContexts@PersistenceUnit@PersistenceUnits)时适用以下规则

      如果在一个类上应用了资源引用注解,它就相当于定义了一个资源,然而它并不等同于定义了一个 “注射目标”。在这种情况下,上面的规则适用于injection-target元素。

      如果在一个字段上使用了资源引用注解,就相当于在web.xml中定义了注入目标元素。然而,如果描述符中没有injection-target元素,那么片段中的injection-target仍将被合并到web.xml中,如上所述。

      另一方面,如果在主web.xml中有一个injection-target,并且有一个具有相同资源名称的资源引用annotation,那么它被认为是对资源引用注释的覆盖。在这种情况下,由于描述符中指定了injection-target,除了覆盖资源引用注释的值之外,上面定义的规则也将适用。

    11. 如果在两个片段中指定了data-source元素,而在主web.xml中没有,并且data-source元素的所有属性和子元素是相同的,data-source将被合并到主web.xml中。如果一个数据源元素在两个片段中指定了相同的名称,而在主web.xml中没有,并且在两个片段中的属性和子元素不相同,这将被视为一个错误。在这种情况下,必须报告一个错误,并且应用程序必须不能部署。

      下面是一些例子,显示不同情况下的结果。

      CODE EXAMPLE 8-4

      web.xml - no resource-ref definition
      
      
      
      Fragment 1
      web-fragment.xml
      <resource-ref>
      <resource-ref-name="foo">
      ...
      <injection-target>
      <injection-target-class>
      com.foo.Bar.class
      </injection-target-class>
      <injection-target-name>
      baz
      </injection-target-name>
      </injection-target>
      </resource-ref>
      

      有效的元数据将是

      <resource-ref>
      <resource-ref-name="foo">
      ....
      <injection-target>
      <injection-target-class>
      com.foo.Bar.class
      </injection-target-class>
      <injection-target-name>
      baz
      </injection-target-name>
      </injection-target>
      </resource-ref>
      

      CODE EXAMPLE 8-5

      web.xml
      <resource-ref>
      <resource-ref-name="foo">
      ...
      </resource-ref>
      
          
      Fragment 1
      web-fragment.xml
      <resource-ref>
      <resource-ref-name="foo">
      ...
      <injection-target>
      <injection-target-class>
      com.foo.Bar.class
      </injection-target-class>
      <injection-target-name>
      baz
      </injection-target-name>
      </injection-target>
      </resource-ref>
      
          
      Fragment 2
      web-fragment.xml
      <resource-ref>
      <resource-ref-name="foo">
      ...
      <injection-target>
      <injection-target-class>
      com.foo.Bar2.class
      </injection-target-class>
      <injection-target-name>
      baz2
      </injection-target-name>
      </injection-target>
      </resource-ref>
      

      有效的元数据将是

      <resource-ref>
      <resource-ref-name="foo">
      ....
      <injection-target>
      <injection-target-class>
      com.foo.Bar.class
      </injection-target-class>
      <injection-target-name>
      baz
      </injection-target-name>
      </injection-target>
      <injection-target>
      <injection-target-class>
      com.foo.Bar2.class
      </injection-target-class>
      <injection-target-name>
      baz2
      </injection-target-name>
      </injection-target>
      </resource-ref>
      

      CODE EXAMPLE 8-6

      web.xml
      <resource-ref>
      <resource-ref-name="foo">
      <injection-target>
      <injection-target-class>
      com.foo.Bar3.class
      </injection-target-class>
      <injection-target-name>
      baz3
      </injection-target-name>
      ...
      </resource-ref>
      
      Fragment 1
      web-fragment.xml<resource-ref>
      <resource-ref-name="foo">
      ...
      <injection-target>
      <injection-target-class>
      com.foo.Bar.class
      </injection-target-class>
      <injection-target-name>
      baz
      </injection-target-name>
      </injection-target>
      </resource-ref>
      
      Fragment 2
      web-fragment.xml<resource-ref>
      <resource-ref-name="foo">
      ...
      <injection-target>
      <injection-target-class>
      com.foo.Bar2.class
      </injection-target-class>
      <injection-target-name>
      baz2
      </injection-target-name>
      </injection-target>
      </resource-ref>
      

      有效的元数据将是

      <resource-ref>
      <resource-ref-name="foo">
      <injection-target>
      <injection-target-class>
      com.foo.Bar3.class
      </injection-target-class>
      <injection-target-name>
      baz3
      </injection-target-name>
      <injection-target-class>
      com.foo.Bar.class
      </injection-target-class>
      <injection-target-name>
      baz
      </injection-target-name>
      <injection-target-class>
      com.foo.Bar2.class
      </injection-target-class>
      <injection-target-name>
      baz2
      </injection-target-name>
      </injection-target>
      ...
      </resource-ref>
      

      Fragment 1和Fragment 2的<injection-target>将被合并到主web.xml中。

    12. 如果主web.xml没有指定任何<post-construct>元素,而web-fragments指定了<post-construct>,那么fragments的<post-construct>元素将被合并到主web.xml。然而,如果在主web.xml中至少有一个post-construct>元素被指定,那么片段中的<post-construct>元素将不会被合并。web.xml的作者有责任确保<post-construct>的列表是完整的。

    13. 如果主web.xml没有指定任何<pre-destroy>元素,而web-fragments指定了<pre-destroy>,那么片段中的<pre-destroy>元素将被合并到主web.xml。然而,如果在主web.xml中至少有一个<pre-destroy>元素被指定,那么片段中的<pre-destroy>元素将不会被合并。web.xml的作者有责任确保<pre-destroy>列表是完整的。

    14. 在处理完web-fragment.xml后,来自相应片段的注释被处理,以便在处理下一个片段前完成该片段的有效元数据。以下规则用于处理注释。

    15. 任何通过注释指定的元数据,如果还没有出现在描述符中,将被用来增加有效描述符。

      1. 在主web.xml或web fragment 中指定的配置优先于通过注释指定的配置。
      2. 对于通过@WebServlet注解定义的Servlet,要覆盖通过描述符的值,描述符中的Servlet名称必须与通过注解指定的Servlet名称相匹配(明确指定或默认名称,如果没有通过注解指定)。
      3. 通过注解定义的servlet和过滤器的初始参数,如果初始参数的名称与通过注解指定的名称完全一致,则将在描述符中被覆盖。初始参数在注释和描述符之间是相加的。
      4. url-patterns,当在描述符中为一个给定的Servlet名称指定时,将覆盖通过注释指定的url模式。
      5. 对于通过`@WebFilter’注解定义的过滤器,要想通过描述符覆盖数值,描述符中的过滤器名称必须与通过注解指定的过滤器名称(明确指定或默认名称,如果没有通过注解指定)相匹配。
      6. 当在描述符中为一个给定的过滤器名称指定 "url-patterns"时,该过滤器将覆盖通过注释指定的url模式。
      7. 过滤器适用的DispatcherTypes,当在描述符中为一个给定的过滤器名称指定时,会覆盖通过注释指定的DispatcherTypes。
      8. 下面的例子展示了上述的一些规则

    通过注解声明的Servlet,并与描述符中相应的web.xml打包。

    @WebServlet(urlPatterns=/MyPattern, initParams={@WebInitParam(name="ccc", value="333")})
    public class com.acme.Foo extends HttpServlet
    {
    ...
    }
    

    web.xml

    <servlet>
    <servlet-class>com.acme.Foo</servlet-class>
    <servlet-name>Foo</servlet-name>
    <init-param>
    <param-name>aaa</param-name>
    <param-value>111</param-value>
    </init-param>
    </servlet>
    
    <servlet>
    <servlet-class>com.acme.Foo</servlet-class>
    <servlet-name>Fum</servlet-name>
    <init-param>
    <param-name>bbb</param-name>
    <param-value>222</param-value>
    </init-param>
    </servlet>
    
    <servlet-mapping>
    <servlet-name>Foo</servlet-name>
    <url-pattern>/foo/*</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
    <servlet-name>Fum</servlet-name>
    <url-pattern>/fum/*</url-pattern>
    </servlet-mapping>
    

    由于通过注解声明的servlet名称与web.xml中声明的servlet名称不一致,注解在web.xml中的其他声明之外指定了一个新的servlet声明,相当于:

    <servlet>
    <servlet-class>com.acme.Foo</servlet-class>
    <servlet-name>com.acme.Foo</servlet-name>
    <init-param>
    <param-name>ccc</param-name>
    <param-value>333</param-name>
    </servlet>
    

    如果上述web.xml被替换成以下内容

    <servlet>
    <servlet-class>com.acme.Foo</servlet-class>
    <servlet-name>com.acme.Foo</servlet-name>
    <init-param>
    <param-name>aaa</param-name>
    <param-value>111</param-value>
    </init-param>
    </servlet>
    <servlet-mapping>
    <servlet-name>com.acme.Foo</servlet-name>
    <url-pattern>/foo/*</url-pattern>
    </servlet-mapping>
    

    那么有效的描述符就相当于

    <servlet>
    <servlet-class>com.acme.Foo</servlet-class>
    <servlet-name>com.acme.Foo</servlet-name>
    <init-param>
    <param-name>aaa</param-name>
    <param-value>111</param-value>
    </init-param>
    <init-param>
    <param-name>ccc</param-name>
    <param-value>333</param-value>
    </init-param>
    </servlet>
    
    <servlet-mapping>
    <servlet-name>com.acme.Foo</servlet-name>
    <url-pattern>/foo/*</url-pattern>
    </servlet-mapping>
    
    

共享库和runtimes的可插拔性

除了支持fragments 和使用注解之外,其中一个要求是,我们不仅要能插入捆绑在WEB-INF/lib中的东西,还要能插入框架的共享副本–包括能将JAX-WS、JAX-RS和JSF等建立在web容器之上的东西插入到web容器中。ServletContainerInitializer允许处理这种用例,如下所述。

ServletContainerInitializer类是通过jar服务API查询的。对于每个应用程序,在应用程序启动时,容器会创建一个ServletContainerInitializer的实例。提供ServletContainerInitializer实现的框架必须在jar文件的META-INF/services目录下捆绑一个名为 javax.servlet.ServletContainerInitializer的文件,根据jar服务API,该文件指向 ServletContainerInitializer的实现类别。

除了 ServletContainerInitializer之外,我们还有一个注解–HandlesTypes。在ServletContainerInitializer的实现上的HandlesTypes注解是用来表达对那些在HandlesTypes的值中可能有注解(类型、方法或字段级注解)的类的兴趣,或者如果它扩展/实现了这些类中的任何一个超级类型。HandlesTypes注释的应用与metadata-complete的设置无关。

当检查应用程序的类,看它们是否符合 ServletContainerInitializerHandlesTypes注解所指定的任何标准时,如果应用程序的一个或多个可选的JAR文件丢失,容器可能会遇到类加载问题。由于容器无法决定这些类型的类加载失败是否会妨碍应用程序的正常工作,它必须忽略它们,同时提供一个配置选项来记录它们。

如果 ServletContainerInitializer 的实现没有@HandlesTypes 注解,或者没有与任何指定的 HandlesType相匹配,那么它将为每个应用程序调用一次,Set的值为null。这将允许初始化器根据应用程序中的可用资源来确定是否需要初始化一个Servlet/filter 。

ServletContainerInitializeronStartup方法将在任何Servlet监听器事件被触发之前被调用。

ServletContainerInitializer的onStartup方法将被调用,该方法将扩展/实现初始化器所感兴趣的类,或者通过@HandlesTypes`注解指定任何类。

下面的一个具体例子展示了这将如何工作。

让我们来看看 JAX-WS 网络服务运行时。

JAX-WS 运行时的实现通常不会捆绑在每一个 war 文件中。该实现将捆绑一个ServletContainerInitializer的实现(如下所示),容器将使用服务 API 来查找(jar 文件将在其 "META-INF/services "目录下捆绑一个名为 javax.servlet.ServletContainerInitializer 的文件,它将指向如下所示的 JAXWSServletContainerInitializer)。

@HandlesTypes(WebService.class)
JAXWSServletContainerInitializer implements ServletContainerInitializer {
	public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
		// JAX-WS specific code here to initialize the runtime
		// and setup the mapping etc.
	ServletRegistration reg = ctx.addServlet("JAXWSServlet","com.sun.webservice.JAXWSServlet");
	reg.addServletMapping("/foo");
	}
}

框架jar文件也可以被捆绑在war文件的WEB-INF/lib目录下。如果ServletContainerInitializer被捆绑在应用程序的WEB-INF/lib目录下的JAR文件中,它的onStartup方法将在捆绑的应用程序启动时只被调用一次。另一方面,如果ServletContainerInitializer被捆绑在WEB-INF/lib目录外的JAR文件中,但仍可被运行时的服务提供者查询机制发现,那么每次启动应用程序时,它的onStartup方法都会被调用。

ServletContainerInitializer接口的实现将被运行时的服务查找机制或与之语义相当的容器特定机制发现。在这两种情况下,来自Web片段JAR文件的ServletContainerInitializer服务如果被排除在绝对排序之外,就必须被忽略,而发现这些服务的顺序必须遵循应用程序的类加载委托模型。

JSP容器的可插拔性

ServletContainerInitializer和编程式注册功能使Servlet和JSP容器之间的责任明确分离成为可能,它使Servlet容器只负责解析web.xmlweb-fragment.xml资源,而将标签库描述符(TLD)资源的解析工作委托给JSP容器。

以前,Web容器必须扫描TLD资源以获取任何监听器声明。在Servlet 3.0及以后的版本中,这个责任可以委托给JSP容器。嵌入Servlet容器的JSP容器可以提供它自己的ServletContainerInitializer实现,在传递给它的onStartup方法的ServletContext中搜索任何TLD资源,扫描这些资源中的监听器声明,并向ServletContext注册相应的监听器。

此外,在Servlet 3.0之前,JSP容器必须扫描应用程序的部署描述符以获取任何与jsp-config相关的配置。在Servlet 3.0及以后的版本中,Servlet容器必须通过ServletContext.getJspConfigDescriptor方法,从应用程序的web.xmlweb-fragment.xml部署描述符中提供任何jsp-config相关配置。

任何在TLD中发现并以编程方式注册的ServletContextListeners的功能都是有限的。任何试图调用Servlet3.0之后添加的ServletContextAPI方法的行为都将导致UnsupportedOperationException

此外,符合Servlet 3.0或更高版本的Servlet容器必须提供一个名为javax.servlet.context.orderedLibsServletContext属性,其值(类型为java.util.List<java.lang. String>)包含了ServletContext所代表的应用程序的WEB-INF/lib目录中的JAR文件名称的列表,按其网络片段名称排序(如果片段JAR文件被排除在absolute-ordering之外,则可能被排除),如果应用程序没有指定任何绝对或相对排序,则为空。

处理注释和fragments

Web应用程序可以包括注释和web.xml/web-fragment.xml部署描述符。如果没有部署描述符,或者有部署描述符但没有将 metadata-complete设置为 true,则必须处理web.xmlweb-fragment.xml和注释(如果在应用程序中使用)。下表描述了是否要处理注释和web.xml片段。

TABLE 8-1 注释和web fragment处理要求

Deployment descriptormetadata-completeprocess annotations and web fragments
web.xml 2.5yesno
web.xml 2.5noyes
web.xml 3.0 or lateryesno
web.xml 3.0 or laternoyes
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

顧棟

若对你有帮助,望对作者鼓励一下

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值