4.5.4. 注册组件中的 DispatcherServlet

本节将介绍如何将一个应用程序组件中的 servlet 和 filter 配置传递到使用该组件的应用程序。为了避免web.xml文件中的代码重复,需要在组件中使用特殊的 ServletRegistrationManager bean 注册 servlet 和 filter。

关于 Servlet 注册的最常见情况在示例HTTP servlet 注册中介绍。我们考虑一个更复杂的例子:一个应用程序组件带有一个用于处理 Web 请求的自定义 DispatcherServlet 的实现。

这个 servlet 从 demo-dispatcher-spring.xml 文件加载配置,如果要该 servlet 正常工作,应该在源码根目录(例如 web/src)先创建一个同名的空文件。

  1. public class WebDispatcherServlet extends DispatcherServlet {
  2. private volatile boolean initialized = false;
  3. @Override
  4. public String getContextConfigLocation() {
  5. String configFile = "demo-dispatcher-spring.xml";
  6. File baseDir = new File(AppContext.getProperty("cuba.confDir"));
  7. String[] tokenArray = new StrTokenizer(configFile).getTokenArray();
  8. StringBuilder locations = new StringBuilder();
  9. for (String token : tokenArray) {
  10. String location;
  11. if (ResourceUtils.isUrl(token)) {
  12. location = token;
  13. } else {
  14. if (token.startsWith("/"))
  15. token = token.substring(1);
  16. File file = new File(baseDir, token);
  17. if (file.exists()) {
  18. location = file.toURI().toString();
  19. } else {
  20. location = "classpath:" + token;
  21. }
  22. }
  23. locations.append(location).append(" ");
  24. }
  25. return locations.toString();
  26. }
  27. @Override
  28. protected WebApplicationContext initWebApplicationContext() {
  29. WebApplicationContext wac = findWebApplicationContext();
  30. if (wac == null) {
  31. ApplicationContext parent = AppContext.getApplicationContext();
  32. wac = createWebApplicationContext(parent);
  33. }
  34. onRefresh(wac);
  35. String attrName = getServletContextAttributeName();
  36. getServletContext().setAttribute(attrName, wac);
  37. if (this.logger.isDebugEnabled()) {
  38. this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
  39. "' as ServletContext attribute with name [" + attrName + "]");
  40. }
  41. return wac;
  42. }
  43. @Override
  44. public void init(ServletConfig config) throws ServletException {
  45. if (!initialized) {
  46. super.init(config);
  47. initialized = true;
  48. }
  49. }
  50. @Override
  51. protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  52. _service(response);
  53. }
  54. @Override
  55. public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
  56. _service(res);
  57. }
  58. private void _service(ServletResponse res) throws IOException {
  59. String testMessage = AppContext.getApplicationContext().getBean(Messages.class).getMainMessage("testMessage");
  60. res.getWriter()
  61. .write("WebDispatcherServlet test message: " + testMessage);
  62. }
  63. }

要注册 DispatcherServlet,必须手动对此类进行加载、实例化、初始化,否则不同的类加载器可能会在 SingleWAR/SingleUberJAR 部署的情况下引发问题。而且,自定义 DispatcherServlet 应该需要进行双重初始化 - 第一次手动初始化,第二次由 servlet 容器初始化。

下面是一个初始化 WebDispatcherServlet 的组件示例:

  1. @Component
  2. public class WebInitializer {
  3. private static final String WEB_DISPATCHER_CLASS = "com.demo.comp.web.WebDispatcherServlet";
  4. private static final String WEB_DISPATCHER_NAME = "web_dispatcher_servlet";
  5. private final Logger log = LoggerFactory.getLogger(WebInitializer.class);
  6. @Inject
  7. private ServletRegistrationManager servletRegistrationManager;
  8. @EventListener
  9. public void initialize(ServletContextInitializedEvent e) {
  10. Servlet webDispatcherServlet = servletRegistrationManager.createServlet(e.getApplicationContext(), WEB_DISPATCHER_CLASS);
  11. ServletContext servletContext = e.getSource();
  12. try {
  13. webDispatcherServlet.init(new AbstractWebAppContextLoader.CubaServletConfig(WEB_DISPATCHER_NAME, servletContext));
  14. } catch (ServletException ex) {
  15. throw new RuntimeException("Failed to init WebDispatcherServlet");
  16. }
  17. servletContext.addServlet(WEB_DISPATCHER_NAME, webDispatcherServlet)
  18. .addMapping("/webd/*");
  19. }
  20. }

注入的 ServletRegistrationManager bean 的 createServlet() 方法从 ServletContextInitializedEvent 获取应用程序上下文,并获取 WebDispatcherServlet 类的完全限定名。要初始化 servlet,需要传递从 ServletContextInitializedEvent 获得的 ServletContext 实例和 servlet 名称。addMapping() 方法用于定义通过 URL:/webd/ 访问 servlet 的 HTTP 映射。