4.5.5. Регистрация DispatcherServlet из компонента приложения

В этом разделе мы рассмотрим, как настроить динамическую регистрацию сервлетов и фильтров, настроенных в компоненте приложения, в родительском приложении. Чтобы избежать дублирования кода в файле конфигурации web.xml, необходимо зарегистрировать сервлеты и фильтры с помощью специального бина ServletRegistrationManager.

Простой пример такой регистрации рассмотрен на примере HTTP-сервлета. Здесь мы рассмотрим более сложный случай: частную реализацию сервлета DispatcherServlet в компоненте приложения. Этот сервлет будет загружать параметры своей конфигурации из файла demo-dispatcher-spring.xml, поэтому, чтобы попробовать этот пример на практике, вы должны создать пустой файл с этим именем в корневом каталоге ресурсов проекта (например, web/src).

public class WebDispatcherServlet extends DispatcherServlet {
    private volatile boolean initialized = false;

    @Override
    public String getContextConfigLocation() {
        String configFile = "demo-dispatcher-spring.xml";
        File baseDir = new File(AppContext.getProperty("cuba.confDir"));

        String[] tokenArray = new StrTokenizer(configFile).getTokenArray();
        StringBuilder locations = new StringBuilder();

        for (String token : tokenArray) {
            String location;
            if (ResourceUtils.isUrl(token)) {
                location = token;
            } else {
                if (token.startsWith("/"))
                    token = token.substring(1);
                File file = new File(baseDir, token);
                if (file.exists()) {
                    location = file.toURI().toString();
                } else {
                    location = "classpath:" + token;
                }
            }
            locations.append(location).append(" ");
        }
        return locations.toString();
    }

    @Override
    protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext wac = findWebApplicationContext();
        if (wac == null) {
            ApplicationContext parent = AppContext.getApplicationContext();
            wac = createWebApplicationContext(parent);
        }

        onRefresh(wac);

        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                    "' as ServletContext attribute with name [" + attrName + "]");
        }

        return wac;
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        if (!initialized) {
            super.init(config);
            initialized = true;
        }
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        _service(response);
    }

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        _service(res);
    }

    private void _service(ServletResponse res) throws IOException {
        String testMessage = AppContext.getApplicationContext().getBean(Messages.class).getMainMessage("testMessage");

        res.getWriter()
                .write("WebDispatcherServlet test message: " + testMessage);
    }
}

Чтобы зарегистрировать DispatcherServlet, вам нужно вручную загрузить класс, создать его экземпляр и проинициализировать его, в противном случае использование разных типов ClassLoader может вызвать проблемы при развёртывании в SingleWAR/SingleUberJAR. Кроме того, собственная реализация DispatcherServlet должна выдерживать двойную инициализацию - сначала вручную, а затем с помощью servlet container.

Пример компонента для инициализации WebDispatcherServlet:

@Component
public class WebInitializer {

    private static final String WEB_DISPATCHER_CLASS = "com.demo.comp.web.WebDispatcherServlet";
    private static final String WEB_DISPATCHER_NAME = "web_dispatcher_servlet";
    private final Logger log = LoggerFactory.getLogger(WebInitializer.class);

    @Inject
    private ServletRegistrationManager servletRegistrationManager;

    @EventListener
    public void initialize(ServletContextInitializedEvent e) {
        Servlet webDispatcherServlet = servletRegistrationManager.createServlet(e.getApplicationContext(), WEB_DISPATCHER_CLASS);
        ServletContext servletContext = e.getSource();
        try {
            webDispatcherServlet.init(new AbstractWebAppContextLoader.CubaServletConfig(WEB_DISPATCHER_NAME, servletContext));
        } catch (ServletException ex) {
            throw new RuntimeException("Failed to init WebDispatcherServlet");
        }
        servletContext.addServlet(WEB_DISPATCHER_NAME, webDispatcherServlet)
                .addMapping("/webd/*");
    }
}

Метод createServlet() инжектированного бина ServletRegistrationManager принимает контекст приложения, полученный из события ServletContextInitializedEvent, и полное имя класса WebDispatcherServlet. Для инициализации сервлета мы передаём экземпляр ServletContext, также полученный из события ServletContextInitializedEvent, и имя сервлета.

Сервлет регистрируется с маппингом webd и будет доступен по адресу /app/webd/ или /app-core/webd/ в зависимости от контекста приложения.