Servlet3.0之四:动态注册和Servlet容器初始化
相关文章
《Servlet3.0之四:动态注册和Servlet容器初始化》
《SpringBoot中通过SpringBootServletInitializer如何实现组件加载》
《SpringMVC之五:自定义DispatcherServlet配置及配置额外的 servlets 和 filters》
一、servlet3.0中动态注册Servlet、filter、Listener
为了使动态注册成为可能,ServletContext接口中还添加了以下两类方法:
1、用来动态地创建Web对象:
<T extends Filter>createFilter(Java.lang.Class<T> clazz) <T extends java.util.EventListener> createListener(java.lang.Class<T> clazz) <T extends Servlet> createServlet(java.lang.Class<T> clazz)
2、用来动态地将Web对象添加到ServletContext中:
每类组件注册都提供三个方法,如下:
//servlet相关 public ServletRegistration.Dynamic addServlet( String servletName, String className); public ServletRegistration.Dynamic addServlet( String servletName, Servlet servlet); public ServletRegistration.Dynamic addServlet(String servletName, Class <? extends Servlet> servletClass); //filter相关 public FilterRegistration.Dynamic addFilter( String filterName, Filter filter); public FilterRegistration.Dynamic addFilter(String filterName, Class <? extends Filter> filterClass); public FilterRegistration.Dynamic addFilter(String filterName, Class <? extends Filter> filterClass); //listener相关 public void addListener(String className); public <T extends EventListener> void addListener(T t); public void addListener(Class <? extends EventListener> listenerClass);
二、动态注册时机及办法
所谓的动态注册,也只能在初始化时进行注册。在运行时为了安全原因,无法完成注册。在初始化情况下的注册Servlet组件(servlet、filter、listener)有两种方法:
- 实现ServletContextListener接口,在contextInitialized方法中完成注册.
- 在jar文件中放入实现ServletContainerInitializer接口的初始化器
- ServletContainerInitializer接口的方法实现在容器启动阶段为容器动态注册Servlet、Filter和listeners。容器会在应用的启动阶段,调用所有实现ServletContainerInitializer接口类中的onStartup()方法。而Spring 3.2中,则进一步简化了这点,只需要实现WebApplicationInitializer接口就可以了,其中提供了一个相关的实现类--AbstractContextLoaderInitializer,它可以动态注册DispatcherServlet。
- 在SpringBoot中通过实现SpringBootServletInitializer接口来初始化容器,示例见《SpringBoot中通过SpringBootServletInitializer如何实现容器初始化》
简单说明如下
1、先说在ServletContextListener监听器中完成注册。
public void contextInitialized(ServletContextEvent sce) { ServletContext sc = sce.getServletContext(); // Register Servlet ServletRegistration sr = sc.addServlet("DynamicServlet", "web.servlet.dynamicregistration_war.TestServlet"); sr.setInitParameter("servletInitName", "servletInitValue"); sr.addMapping("/*"); // Register Filter FilterRegistration fr = sc.addFilter("DynamicFilter", "web.servlet.dynamicregistration_war.TestFilter"); fr.setInitParameter("filterInitName", "filterInitValue"); fr.addMappingForServletNames(EnumSet.of(DispatcherType.REQUEST), true, "DynamicServlet"); // Register ServletRequestListener sc.addListener("web.servlet.dynamicregistration_war.TestServletRequestListener"); }
很简单,难度不大。
2、再说说在jar文件中的servlet组件注册,需要在jar包含META-INF/services/javax.servlet.ServletContainerInitializer文件,文件内容为已经实现ServletContainerInitializer接口的类:
com.learn.servlet3.jardync.CustomServletContainerInitializer
该实现部分代码:
@HandlesTypes({ JarWelcomeServlet.class }) public class CustomServletContainerInitializer implements ServletContainerInitializer { private static final Log log = LogFactory .getLog(CustomServletContainerInitializer.class); private static final String JAR_HELLO_URL = "/jarhello"; public void onStartup(Set<Class<?>> c, ServletContext servletContext) throws ServletException { log.info("CustomServletContainerInitializer is loaded here..."); log.info("now ready to add servlet : " + JarWelcomeServlet.class.getName()); ServletRegistration.Dynamic servlet = servletContext.addServlet( JarWelcomeServlet.class.getSimpleName(), JarWelcomeServlet.class); servlet.addMapping(JAR_HELLO_URL); log.info("now ready to add filter : " + JarWelcomeFilter.class.getName()); FilterRegistration.Dynamic filter = servletContext.addFilter( JarWelcomeFilter.class.getSimpleName(), JarWelcomeFilter.class); EnumSet<DispatcherType> dispatcherTypes = EnumSet .allOf(DispatcherType.class); dispatcherTypes.add(DispatcherType.REQUEST); dispatcherTypes.add(DispatcherType.FORWARD); filter.addMappingForUrlPatterns(dispatcherTypes, true, JAR_HELLO_URL); log.info("now ready to add listener : " + JarWelcomeListener.class.getName()); servletContext.addListener(JarWelcomeListener.class); } }
其中@HandlesTypes注解表示CustomServletContainerInitializer
可以处理的类,在onStartup
方法中,可以通过Set<Class<?>> c
获取得到。
jar文件中不但可以包含需要自定义注册的servlet,也可以包含应用注解的servlet,具体怎么做,视具体环境而定。
把处理某类事物的servlet组件打包成jar文件,有利于部署和传输,功能不要了,直接去除掉jar即可,方便至极!
3、spring中
ServletContainerInitializer接口的方法实现在容器启动阶段为容器动态注册Servlet、Filter和listeners。容器会在应用的启动阶段,调用所有实现ServletContainerInitializer接口类中的onStartup()方法。而Spring 3.2中,则进一步简化了这点,只需要实现WebApplicationInitializer接口就可以了,其中提供了一个相关的实现类--AbstractContextLoaderInitializer,它可以动态注册DispatcherServlet。这意味着,只要spring-webmvc.jar放置在web应用的web-inf/lib中,就可以调用Dispatcher servlet了。可以参考如下的例子(来自Spring文档):
public class MyWebApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext container) { ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet()); registration.setLoadOnStartup(1); registration.addMapping("/example/*"); } }
三、示例
3.1、ServletContextListener监听器中动态注册servlet、filter、listener示例:
3.1.1、动态注册Servlet
FirstServlet.java
package com.dxz.demo.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class FirstServlet extends HttpServlet { private static final long serialVersionUID = 1L; private String name; @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter writer = response.getWriter(); writer.println("<html><head><title>First servlet" + "</title></head><body>" + name); writer.println("</body></html>"); } public void setName(String name) { this.name = name; } } package com.dxz.demo.servlet; import javax.servlet.Servlet; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; import javax.servlet.annotation.WebListener; @WebListener public class DynRegListener implements ServletContextListener { @Override public void contextDestroyed(ServletContextEvent arg0) { } @Override public void contextInitialized(ServletContextEvent sce) { ServletContext servletContext = sce.getServletContext() ; Servlet firstServlet = null ; try { firstServlet = servletContext.createServlet(FirstServlet.class) ; } catch (ServletException e) { e.printStackTrace(); } if(firstServlet != null && firstServlet instanceof FirstServlet){ ((FirstServlet) firstServlet).setName("Dynamically registered servlet"); } ServletRegistration.Dynamic dynamic = servletContext.addServlet("firstServlet", firstServlet) ; dynamic.addMapping("/dynamicservlet") ; } }
当应用程序启动时,容器会调用监听器的contextInitialized方法,结果是创建了一个FirstServlet实例,并注册和映射到/dynamicservlet。可以利用下面这个路径访问FirstServlet
http://127.0.0.1:8080/servlet3/dynamicservlet
3.1.2、动态注册Filter
package com.dxz.demo.servlet; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class FirstFilter implements Filter { @Override public void destroy() { System.out.println("destroy() in FirstFilter"); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("doFilter() in FirstFilter"); chain.doFilter(request, response); } @Override public void init(FilterConfig arg0) throws ServletException { System.out.println("init() in FirstFilter"); } } package com.dxz.demo.servlet; import java.util.EnumSet; import javax.servlet.DispatcherType; import javax.servlet.Filter; import javax.servlet.FilterRegistration; import javax.servlet.Servlet; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; import javax.servlet.annotation.WebListener; @WebListener public class DynRegListener implements ServletContextListener { @Override public void contextDestroyed(ServletContextEvent arg0) { } @Override public void contextInitialized(ServletContextEvent sce) { ServletContext servletContext = sce.getServletContext() ; Servlet firstServlet = null ; try { firstServlet = servletContext.createServlet(FirstServlet.class) ; } catch (ServletException e) { e.printStackTrace(); } if(firstServlet != null && firstServlet instanceof FirstServlet){ ((FirstServlet) firstServlet).setName("Dynamically registered servlet"); } ServletRegistration.Dynamic dynamic = servletContext.addServlet("firstServlet", firstServlet) ; dynamic.addMapping("/dynamicservlet") ; Filter firstFilter = null ; try { firstFilter = servletContext.createFilter(FirstFilter.class) ; } catch (ServletException e) { e.printStackTrace(); } FilterRegistration.Dynamic dynamic2 = servletContext.addFilter("firstFilter", firstFilter); EnumSet<DispatcherType> dispatcherTypes = EnumSet .allOf(DispatcherType.class); dispatcherTypes.add(DispatcherType.REQUEST); dispatcherTypes.add(DispatcherType.FORWARD); dynamic2.addMappingForUrlPatterns(dispatcherTypes, true, "/dynamicfilter"); } }
结果:
访问:http://127.0.0.1:8080/servlet3/dynamicfilter
3.1.3、动态注册Listener
package com.dxz.demo.servlet; import java.util.EventListener; public class FirstListener implements EventListener { public void init() { System.out.println("init() in FirstListener"); } } //注册 servletContext.addListener(FirstListener.class);
运行没有成功,报错了。(待跟进...)
3.2、Servlet容器初始化
如果你使用过像Struts或Struts2这类Java Web框架,就应该知道,在使用框架之前必须先配置应用程序。一般来说,需要通过修改部署描述符,告诉Servlet容器你正在使用一个框架。例如,要想在应用程序中使用Struts2,可以将以下标签添加到部署描述符中:
但在Servlet3中,就不再需要这些了。框架可以进行打包,自动完成Web对象的首次注册。
Servlet容器初始化的核心是javax.servlet.ServletContainerInitializer接口。这是一个简单的接口,它只有一个方法:onStartup。在执行任何ServletContext监听器之前,由Servlet容器调用这个方法。
实现ServletContainerInitializer的类必须用@HandleTypes注解进行标注,以便声明初始化程序可以处理这些类型的类。
下面是个例子。要把下面的类和文件打成jar包。
FirstServlet.java
package servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class FirstServlet extends HttpServlet { private static final long serialVersionUID = 1L; private String name = "wuhaixu" ; @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter writer = response.getWriter() ; writer.println("<html><head><title>First servlet" + "</title></head><body>" + name); writer.println("</body></html>") ; } }
javax.servlet.ServletContainerInitializer
MyServletContainerInitializer.java
package initializer; import java.util.Set; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; import javax.servlet.annotation.HandlesTypes; import servlet.FirstServlet; @HandlesTypes({FirstServlet.class}) public class MyServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException { System.out.println("onStartup"); ServletRegistration registration = servletContext.addServlet("firstServlet", "servlet.FirstServlet"); registration.addMapping("/f"); System.out.println("leaving onStartup"); } }
把这些文件和类放在下面的包中,在cmd进行jar包压缩
结果为: