Servlet3 可编程式注册web组件
为了增强可插拔性,servlet3在servletContext中增加新的方法,支持编程方式申明和注册servlet、filter、listener组件。ServletContext定义一组方法,servlet使用其与servlet容器进行通讯。举例:获取文件MIME类型,分发请求或写日志文件等。
我们可以通过调用ServletContextListener#contextInitialized
或ServletContainerInitializer#onStartup
方法,实现动态注册组件。
ServletContext注册方法汇总
方法描述 | 方法 |
通过addXXX()方法注册组件。右边每个方法有多个重载形式。每个重载方法需要组件name参数(listener方法除外)以及类名或类类型或组件实例。servlet方法返回ServletRegistration.Dynamic,Filter返回FilterRegistration.Dynamic,listener返回void,返回Registration对象可以实现进一步配置。 | `addServlet(…) addFilter(…) addListener(…)` |
通过createXXX()方法创建实例。我们需要提供类类型作为参数给每个方法。这些方法可能在调用上面addXXX方法之前被调用。 | `createServlet(…) createFilter(…) createListener(…)` |
通过 `getXXXRegistration()`获取Registration对象,不使用listener。如果servlet/fliter没有在web.xml或注解中注册,调用将在`ServletContextListener#contextInitialized`中抛出`UnsupportedOperationException`异常。 | `getServletRegistration(…) getFilterRegistration(…)` |
编程式配置组件场景
这个特性让框架设计者在启动阶段可以加载特定的web组件,而无需最终用户手工在应用端提供相同的配置。如spring使用 ServletContainerInitializer
,开发者使用框架仅需要增加框架jar依赖并创建相应的pojo(使用框架特定的注解或实现一些接口)。
我们也可以通过驻留在框架jar中的ServletContextListener
来实现,但缺点是,如果应用的web.xml文件中已经配置metadata-complete="true"
用于提升性能,则不能实现正确加载。设置该标志会引起容器不去扫描jar中的注解。因此ServletContainerInitializer
(spi)是更标准的方式。
我们应该仅使用ServletContextListener#contextInitialized
实现应用级别的可编程配置。基于一些动态参数,选择性决定插入/注册那些web组件。
示例
通过ServletContextListener#contextInitialized
动态注册servlet,假设一个servlet应用需要根据不同的地理位置加载不同的servlet。
1.DefaultAppController servlet代码。
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class DefaultAppController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("DefaultAppController");
}
}
2.OffshoreAppController servlet代码。
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class OffshoreAppController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("OffshoreAppController");
}
}
3.AppContextListener 动态加载servlet。
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRegistration;
import javax.servlet.annotation.WebListener;
import java.util.Locale;
@WebListener
public class AppContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent event) {
Locale locale = Locale.getDefault();
ServletRegistration.Dynamic registration = event.getServletContext()
.addServlet("appController", locale.getISO3Country().equals("USA") ?
DefaultAppController.class : OffshoreAppController.class);
registration.addMapping("/app/");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}