Spring Java Config с несколькими диспетчерами

У меня есть некоторый опыт весной сейчас, а также есть некоторые чистые веб-приложения Java config в использовании. Тем не менее, они обычно основаны на тихой простой настройке:

  • конфигурация приложения для служб / репозиториев
  • конфигурация диспетчера для одного диспетчера (и некоторых контроллеров)
  • (необязательно) spring security для обеспечения доступа

для моего текущего проекта мне нужно иметь отдельные контексты диспетчера с различной конфигурацией. Это не проблема с конфигурацией на основе XML, поскольку у нас есть выделенный ContextLoaderListener, независимый от конфигурации диспетчера. Но с конфигурацией java я не уверен, что то, что я делаю, хорошо до сих пор;)

вот общий DispatcherConfig:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new class[]{MyAppConfig.class};
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class[]{MyDispatcherConfig.class};
  }

  @Override
  protected String[] getServletMappings() {
    return new String[]{"/mymapping/*"};
  }

  @Override
  protected String getServletName() {
    return "myservlet";
  }
}

как сказано, мне нужен второй (третий,...) диспетчер с другим отображением (и вид преобразователей). Итак, я скопировал конфигурацию и добавил для обоих getServletName () (в противном случае оба будут названы как "dispatcher", который будет вызывать ошибки.) Вторая конфигурация выглядела так:

public class AnotherWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new class[]{MyAppConfig.class};
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class[]{AnotherDispatcherConfig.class};
  }

  @Override
  protected String[] getServletMappings() {
    return new String[]{"/another_mapping/*"};
  }

  @Override
  protected String getServletName() {
    return "anotherservlet";
  }
}

когда я использую его так, запуск приложения приводит к проблеме с ContextLoaderListener:

java.lang.IllegalStateException: Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:277)
...

поэтому я удалил второй MyAppConfig.класс!--23--> возвращение от одного из AbstractAnnotationConfigDispatcherservletinitializer и он отлично работает. Однако это не кажется правильным способом;)

для моего понимания: должны ли все DispatcherConfig быть обрабатывается в пределах одного AbstractAnnotationConfigDispatcherservletinitializer или я должен разделить их, как я сделал? Я попытался настроить их в одном классе, но тогда моя конфигурация была полностью смешана (поэтому я считаю, что это не желаемый способ).

как вы реализуете такой случай? Можно ли установить ContextLoaderListener в конфигурации java вне AbstractAnnotationConfigDispatcherservletinitializer? Или я должен создать DefaultServlet, который имеет только корневой конфиг? Как насчет реализации базового интерфейса этой конфигурации WebApplicationInitializer?

3 ответов


Махеш С. показал правильный путь, но его реализация слишком ограничена. Он прав в одном: вы не можете использовать напрямую AbstractAnnotationConfigDispatcherServletInitializer для нескольких сервлетов диспетчера. Но реализация должна:

  • создать корневой контекст приложения
  • дает ему начальную конфигурацию и говорит, какие пакеты он должен сканировать
  • добавьте ContextListener для него в контекст сервлета
  • затем для каждого сервлета диспетчера
    • создать контекст дочернего приложения
    • дает ему такую же начальную конфигурацию и пакеты для сканирования
    • создайте DispatcherServlet, используя контекст
    • добавить его в контекст сервлета

вот более полная реализация :

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
    // root context
    AnnotationConfigWebApplicationContext rootContext =
            new AnnotationConfigWebApplicationContext();
    rootContext.register(RootConfig.class); // configuration class for root context
    rootContext.scan("...service", "...dao"); // scan only some packages
    servletContext.addListener(new ContextLoaderListener(rootContext));

    // dispatcher servlet 1
    AnnotationConfigWebApplicationContext webContext1 = 
            new AnnotationConfigWebApplicationContext();
    webContext1.setParent(rootContext);
    webContext1.register(WebConfig1.class); // configuration class for servlet 1
    webContext1.scan("...web1");            // scan some other packages
    ServletRegistration.Dynamic dispatcher1 =
    servletContext.addServlet("dispatcher1", new DispatcherServlet(webContext1));
    dispatcher1.setLoadOnStartup(1);
    dispatcher1.addMapping("/subcontext1");

    // dispatcher servlet 2
    ...
}

таким образом, у вас есть полный контроль над тем, какие бобы закончатся в каком контексте, точно так же, как вы бы с конфигурацией XML.


Я думаю, вы можете решить это, если используете общий интерфейс WebApplicationInitializer, а не абстрактную реализацию, предоставляемую spring - AbstractAnnotationConfigDispatcherservletinitializer.

таким образом, вы можете создать два отдельных инициализатора, чтобы получить другой ServletContext при методе startUp() и зарегистрировать разные сервлеты AppConfig & dispatcher для каждого из них.

один из таких реализующих классов может выглядеть так это:

public class FirstAppInitializer implements WebApplicationInitializer {

    public void onStartup(ServletContext container) throws ServletException {

        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(AppConfig.class);
        ctx.setServletContext(container);

        ServletRegistration.Dynamic servlet = container.addServlet(
                "dispatcher", new DispatcherServlet(ctx));

        servlet.setLoadOnStartup(1);
        servlet.addMapping("/control");

    }

}

я столкнулся с той же проблемой. На самом деле у меня была сложная конфигурация с несколькими сервлетами диспетчера, фильтрами и слушателями.

у меня была паутина.xml, как показано ниже

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1">
    <listener>
        <listener-class>MyAppContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>spring.profiles.active</param-name>
        <param-value>${config.environment}</param-value>
    </context-param>
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </context-param>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>MyAppConfig</param-value>
    </context-param>
    <servlet>
        <servlet-name>restEntryPoint</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
        </init-param>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>MyRestConfig</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>restEntryPoint</servlet-name>
        <url-pattern>/api/*</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>webSocketEntryPoint</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
        </init-param>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>MyWebSocketWebConfig</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>webSocketEntryPoint</servlet-name>
        <url-pattern>/ws/*</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>webEntryPoint</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
        </init-param>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>MyWebConfig</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>webEntryPoint</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <filter>
        <filter-name>exceptionHandlerFilter</filter-name>
        <filter-class>com.san.common.filter.ExceptionHandlerFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>exceptionHandlerFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter>
        <filter-name>validationFilter</filter-name>
        <filter-class>MyValidationFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>validationFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter>
        <filter-name>lastFilter</filter-name>
        <filter-class>MyLastFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>lastFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

Я заменил выше web.XML с ниже Java файл

import java.util.EnumSet;

import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;


public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {

        servletContext.addListener(MyAppContextLoaderListener.class);

        servletContext.setInitParameter("spring.profiles.active", "dev");
        servletContext.setInitParameter("contextClass", "org.springframework.web.context.support.AnnotationConfigWebApplicationContext");
        servletContext.setInitParameter("contextConfigLocation", "MyAppConfig");

        // dispatcher servlet for restEntryPoint
        AnnotationConfigWebApplicationContext restContext = new AnnotationConfigWebApplicationContext();
        restContext.register(MyRestConfig.class);
        ServletRegistration.Dynamic restEntryPoint = servletContext.addServlet("restEntryPoint", new DispatcherServlet(restContext));
        restEntryPoint.setLoadOnStartup(1);
        restEntryPoint.addMapping("/api/*");

        // dispatcher servlet for webSocketEntryPoint
        AnnotationConfigWebApplicationContext webSocketContext = new AnnotationConfigWebApplicationContext();
        webSocketContext.register(MyWebSocketWebConfig.class);
        ServletRegistration.Dynamic webSocketEntryPoint = servletContext.addServlet("webSocketEntryPoint", new DispatcherServlet(webSocketContext));
        webSocketEntryPoint.setLoadOnStartup(1);
        webSocketEntryPoint.addMapping("/ws/*");

        // dispatcher servlet for webEntryPoint
        AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext();
        webContext.register(MyWebConfig.class);
        ServletRegistration.Dynamic webEntryPoint = servletContext.addServlet("webEntryPoint", new DispatcherServlet(webContext));
        webEntryPoint.setLoadOnStartup(1);
        webEntryPoint.addMapping("/");

        FilterRegistration.Dynamic validationFilter = servletContext.addFilter("validationFilter", new MyValidationFilter());
        validationFilter.addMappingForUrlPatterns(null, false, "/*");

        FilterRegistration.Dynamic lastFilter = servletContext.addFilter("lastFilter", new MyLastFilter());
        lastFilter.addMappingForUrlPatterns(null, false, "/*");

    }

    @Override
    protected Class<?>[] getRootConfigClasses() {
        // return new Class<?>[] { AppConfig.class };
        return null;
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    protected String[] getServletMappings() {
        // TODO Auto-generated method stub
        return null;
    }

}