WebApplicationInitializer究 Spring 3.1之无web.xml式 基于代码配置的servlet3.0应用
Posted Happy_EveryDay
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WebApplicationInitializer究 Spring 3.1之无web.xml式 基于代码配置的servlet3.0应用相关的知识,希望对你有一定的参考价值。
大家应该都已经知道Spring 3.1对无web.xml式基于代码配置的servlet3.0应用。通过spring的api或是网络上高手们的博文,也一定很快就学会并且加到自己的应用中去了。PS:如果还没,也可以小小参考一下鄙人的上一篇文章<<探 Spring 3.1之无web.xml式 基于代码配置的servlet3.0应用>>。
经过一天的深度research, 我了解,理解以及重现了springframework的那一小段代码。
OK,第一步,入手点,WebApplicationInitializer接口。因为我们只需实现这个接口覆写它的一个方法,就可以做到配置web.xml同样的功效。看它的源码,其实看和不看没什么两样:
- package org.springframework.web;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletException;
- public interface WebApplicationInitializer
- void onStartup(ServletContext servletContext) throws ServletException;
就这么点儿,有效代码5行,弄地我一头雾水,就是一个普通接口,声明了一个方法。连注解都没有,server是怎么找到实现了它的类的?如果这样,何不找我定义的其它接口(的实现类完成配置工作)呢。可见现在java的解耦技术,真令人汗颜。
第二步,这个接口旁边(同包)有个SpringServletContainerInitializer, 看下它是何方神圣吧:
- package org.springframework.web;
- import java.lang.reflect.Modifier;
- import java.util.Collections;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.ServiceLoader;
- import java.util.Set;
- import javax.servlet.ServletContainerInitializer;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletException;
- import javax.servlet.annotation.HandlesTypes;
- import org.springframework.core.annotation.AnnotationAwareOrderComparator;
- @HandlesTypes(WebApplicationInitializer.class)
- public class SpringServletContainerInitializer implements ServletContainerInitializer
- public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
- throws ServletException
- List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
- 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) 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;
- Collections.sort(initializers, new AnnotationAwareOrderComparator());
- servletContext.log("Spring WebApplicationInitializers detected on classpath: " + initializers);
- for (WebApplicationInitializer initializer : initializers)
- initializer.onStartup(servletContext);
以上的有效代码28行。刚看时也很迷茫,其实慢慢就理解了。拟个伪代码吧,方便大家理解:
1,定义一个类SpringServletContainerInitializer,并标明该类要操作的一个类WebApplicationInitializer
2, 该类会行使ServletContainerInitializer接口的一个行为onStartup,从而将一个集合中的初始化设置 全部配置到ServletContext的实例中。
3,具体的onStartup方法中,建立合格配置列表,
4,如果确定集合中有配置,逐一检查配置是否是合格配置,具体判断依据:这个类不是接口,不是抽象类,而且是所要操作的那个接口的一个实现类。满足此依据,合格。将合格的配置类实例化放入合格配置列表。过程中有错要通知控制台。
5,如若执行完步骤4,发现没有合格配置,在ServletContext记录该结果,并结束onStartup行为。
6,将找到配置按一定排列方式(AnnotationAwareOrder)排序。
7,在ServletContext中记录找到结果。
8,逐一执行配置。 即驱动每一个WebApplicationInitializer的实现类行使其onStartup行为。
第三步很明显了,去research 接口ServletContainerInitializer和注解HandleType。在这里:http://docs.oracle.com/javaee/6/api/javax/servlet/ServletContainerInitializer.html
该接口允许一个库或运行时,(运行时应该指server)声明为一个web程序的启动状态,并执行任何所需的程序中注册的servlet,filter,listener来响应它......
其它也就不用看了,可以想象得到支持Servlet3机制的服务器,会找到这样接口的实现类,执行onStartup行为。至于如何找,无非也是这样一系列的反射机制的应用。自己做一个试试吧:
自定义的WebApplicationInitializer:
- package com.gxino.imagecapture.cfg;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletException;
- public interface WebParameter
- public void loadInfo(ServletContext servletContext) throws ServletException;
自定义的ServletContainerInitializer,我做得很简单,直接去执行找到配置类中的loadInfo方法
- package com.gxino.imagecapture.cfg;
- import java.lang.reflect.Modifier;
- import java.util.Set;
- import javax.servlet.ServletContainerInitializer;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletException;
- import javax.servlet.annotation.HandlesTypes;
- @HandlesTypes(WebParameter.class)
- public class WebConfiguration implements ServletContainerInitializer
- @Override
- public void onStartup(Set<Class<?>> webParams, ServletContext servletCtx)
- throws ServletException
- if (webParams != null)
- for (Class<?> paramClass : webParams)
- if (!paramClass.isInterface() && !Modifier.isAbstract(paramClass.getModifiers()) &&
- WebParameter.class.isAssignableFrom(paramClass))
- try
- ((WebParameter) paramClass.newInstance()).loadInfo(servletCtx);
- catch (Throwable ex)
- throw new ServletException("Failed to instantiate WebParam class", ex);
- //loop
- //Web Params
- //onStartup
写个测试Servlet:
- package com.gxino.imagecapture.ctrl;
- import java.io.IOException;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import com.gxino.imagecapture.cfg.WebParameter;
- public class TestServlet extends HttpServlet
- public void doGet(HttpServletRequest req, HttpServletResponse resp)
- System.out.println("Some client access once");
- try
- req.getRequestDispatcher("/index.jsp").forward(req, resp);
- catch (ServletException | IOException e)
- // TODO Auto-generated catch block
- e.printStackTrace();
实现WebParam配置接口来配置刚才的Servlet:
- package com.gxino.imagecapture.cfg;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletException;
- import javax.servlet.ServletRegistration;
- public class ServletParameter implements WebParameter
- @Override
- public void loadInfo(ServletContext servletContext) throws ServletException
- ServletRegistration.Dynamic testServlet=servletContext.addServlet("test","com.gxino.imagecapture.ctrl.TestServlet");
- testServlet.setLoadOnStartup(1);
- testServlet.addMapping("/index.html");
启动服务器,访问http://localhost:xxxx/xxxxx/index.html
失败。Debug. 发现没有走这些代码。应该还差关键环节。看来还得知道Servlet3中是怎么找ServletContainerInitializer的。再回刚才ServletContainerInitializer的api有这样一句:该接口的实现必须声明一个JAR资源放到程序中的META-INF/services下,并且记有该接口那个实现类的全路径,才会被运行时(server)的查找机制或是其它特定机制找到。那篇api需要仔细阅读啊。
到org.springframework.web-3.0.1.RELEASE.jar中能找到META-INF/services下的javax.servlet.ServletContainerInitializer文件,内容为org.springframework.web.SpringServletContainerInitializer同样,我们专门作这样一个包,在mkdir好的META-INF/services下vi 一个文件命名为javax.servlet.ServletContainerInitializer,内容为自定的那个WebConfiguration的全路径类名。 然后在META-INF的parent路径下运行jar cvf test.jar META-INF。一切完毕,将其放到WEB-INF/lib下。启动。
这回大功告成。
访问http://localhost:xxxx/xxxxx/index.html。页面跳到了index.jsp下。
并且控制台打出: Some client access once
再使个劲,将Servlet和Servlet配置合二为一:
- package com.gxino.imagecapture.ctrl;
- import java.io.IOException;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletException;
- import javax.servlet.ServletRegistration;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import com.gxino.imagecapture.cfg.WebParameter;
- public class TestServlet extends HttpServlet implements WebParameter
- @Override
- public void loadInfo(ServletContext servletContext) throws ServletException
- ServletRegistration.Dynamic testServlet=servletContext.addServlet("test", "com.gxino.imagecapture.ctrl.TestServlet");
- testServlet.setLoadOnStartup(1);
- testServlet.addMapping("/index.html");
- public void doGet(HttpServletRequest req, HttpServletResponse resp)
- System.out.println("Some client access once");
- try
- req.getRequestDispatcher("/index.jsp").forward(req, resp);
- catch (ServletException | IOException e)
- // TODO Auto-generated catch block
- e.printStackTrace();
这回我们看到,配置文件与servlet放到了一起。这样将回节省大量时间。
以后直接运用Spring Framework的WebApplicationInitializer也知道是怎么一回事儿了。而且可以将Spring 的applicationContext.xml与web.xml融合在一个类中。即注解为@Configuration,并实现WebApplicationInitializer.回头试试。
转https://blog.csdn.net/xiao__gui/article/details/46803193
https://www.cnblogs.com/zytcomeon/p/15091369.html
https://www.cnblogs.com/aji2014/p/6694361.html
servlet 容器如何找到 WebApplicationInitializer 实现 [重复]
【中文标题】servlet 容器如何找到 WebApplicationInitializer 实现 [重复]【英文标题】:How servlet container finds WebApplicationInitializer implementations [duplicate] 【发布时间】:2015-03-23 17:50:28 【问题描述】:Spring WebApplicationInitializer 提供了一种编程方式来在 Servlet 3.0+ 兼容的 servlet 容器中配置 Spring DispatcherServlet 和 ContextLoaderListener。但它是如何工作的? servlet容器如何找到WebApplicationInitializer的实现,真的是从classpath加载所有类吗?
【问题讨论】:
这不是 servlet 3 中没有 web.xml 的引导问题的重复。这是关于 Spring 的 WebApplicationInitializer。尽管 Spring 建立在 servlet 3 引导之上,但并不相同。 【参考方案1】:来自文档:
此 SPI 的实现将被自动检测到 SpringServletContainerInitializer ,它本身是自举的 由任何 Servlet 3.0 容器自动执行。看 SpringServletContainerInitializer 或有关此引导的详细信息 机制。
和:
运作机制
SpringServletContainerInitializer这个类将被加载并 实例化并使其 onStartup 方法由任何调用 容器启动期间符合 Servlet 3.0 的容器假设 spring-web 模块 JAR 存在于类路径中。 这通过 JAR 服务 API @link ServiceLoader#load(Class) 方法检测 spring-web 模块的 META-INF/services/javax.servlet.ServletContainerInitializer 服务提供者配置文件。
查看 http://download.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#Service%20Provider JAR 服务 API 文档以及 8.2.4 部分 Servlet 3.0 最终草案规范的完整详细信息。
这一切都在文档中,基本上它是检测 SpringServletContainerInitializer
的 Servlet 规范的一部分,它实现了 ServletContainerInitializer
,所以这一切都归结为容器检测这些类的工作。
【讨论】:
我知道 servlet 容器应该找到 ServletContainerInitializer 实现。但是他们是怎么做到的呢?例如tomcat或jetty,它们应该加载所有类吗?我没有看到其他解决方案。如果我有巨大的类路径,那么部署将花费大量时间。 我不认为所有类都被容器急切地加载,因为 servlet 3.0 容器在启动时会扫描捆绑在 WEB-INF\lib 中的 jar 文件,以便找到ServletContainerInitializer
的实现,一旦它找到它,加载实现类,然后通过在实现上调用 onStartup() 来完成所有引导【参考方案2】:
我假设您了解 java SPI 以及 java.util.ServiceLoader 一种用于加载实现的实用程序类的方式。否则please read it。
但是对于这个答案的简要说明:如果有一个 SPI,这里是 javax.servlet.ServletContainerInitializer
,
那么提供者应该实现它,并且必须在库 jar 文件的 META-INF/services/javax.servlet.ServletContainerInitializer 文件中声明实现 - Spring 就是这样一个提供者,并且
在 spring-web*.jar jar 文件中声明它并有一个条目 org.springframework.web.SpringServletContainerInitializer
然后 ServletContainerInitializer 的服务加载器可以加载实现。此加载特定于 ServletContainer 实现
我将针对 tomcat7+ ServletContainer 进行解释。
Tomcat 有 LifecycleListeners,它们将监听生命周期事件,如启动、停止等。
org.apache.catalina.startup.ContextConfig
是这样一个 ServletContext 的启动事件侦听器,它配置该 ServletContext 的属性以及相关的已定义 servlet。
因此,当 tomcat 为 Web 应用程序初始化 ServletContext
时,它会生成这样一个事件,该事件将通知 ContextConfig
的偶数监听方法。然后就会触发
它自己的 processServletContainerInitializers 方法,它扫描 JAR 中的 ServletContainerInitializer 实现。
它通过将这项职责委托给 WebappServiceLoader(Java 的 JAR ServiceLoader 的一种变体)来实现这一点,后者实际上负责从 WEB-INF/ 加载 ServletContainerInitializer 实现。 lib 罐子。
所以作为结论,控制流程应该是这样的。
Tomcat 初始化 ServletContext
ContextConfig
收到此上下文启动事件的通知
服务加载委托给
WebappServiceLoader<ServletContainerInitializer>
WebappServiceLoader 在 WEB-INF/lib jar 中扫描 文件 META-INF/services/javax.servlet.ServletContainerInitializer 以加载实现
一旦加载返回步骤 3,ContextConfig 将调用 实现的(这里SpringServletContainerInitializer
)
onStartup 方法将完成其余的事情。
HTH!
【讨论】:
SpringServletContainerInitializer
的 java 文档也非常重要,值得阅读 docs.spring.io/spring/docs/current/javadoc-api/org/…
我通过调试 tomcat 代码发现了这一点...通过将它与示例应用程序进行比较来找出为什么我的 spring boot 应用程序没有在外部 tomcat 中启动.. 似乎不同之处在于我的应用程序没有 spring-web-xxx.jar,所以没有 meta-inf/services/.... maven 引入了 spring-webmvc- 和 spring-boot-100-other-jars,但没有其中有 SPI - 没有任何文档说我们需要将 spring-web 作为依赖项吗?以上是关于WebApplicationInitializer究 Spring 3.1之无web.xml式 基于代码配置的servlet3.0应用的主要内容,如果未能解决你的问题,请参考以下文章
永远不会调用WebApplicationInitializer的onStartup方法
servlet 容器如何找到 WebApplicationInitializer 实现 [重复]
Spring Security - 有条件的 WebApplicationInitializer
Spring 3.1 WebApplicationInitializer & Embedded Jetty 8 AnnotationConfiguration
在 Wildfly 中部署的 Spring Boot 应用程序“无法实例化 WebApplicationInitializer 类”