【问题标题】:Spring Java Config with Multiple Dispatchers具有多个调度程序的 Spring Java Config
【发布时间】:2015-03-05 12:33:38
【问题描述】:

我现在对 Spring 有了一些经验,并且还使用了一些纯 java config web-apps。但是,这些通常基于安静的简单设置:

  • 服务/存储库的应用程序配置
  • 一个调度程序(和一些控制器)的调度程序配置
  • (可选)用于保护访问的弹簧安全装置

对于我当前的项目,我需要具有不同配置的单独调度程序上下文。这不是基于 XML 的配置的问题,因为我们有一个独立于 Dispatcher 配置的专用 ContextLoaderListener。但是使用 java config 我不确定到目前为止我所做的是否还好;)

这是一个常见的 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() 添加(否则两者都将被命名为“调度程序”,这将导致错误)。第二个配置是这样的:

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.class 返回 AbstractAnnotationConfigDispatcherServletInitializer 并且工作正常。但是,这感觉不是正确的方法;)

据我了解:应该在一个 AbstractAnnotationConfigDispatcherServletInitializer 中处理所有 DispatcherConfig,还是应该像我一样将它们分开?我尝试在一个类中配置它们,但后来我的配置完全混合(所以我相信这不是理想的方式)。

您如何实施这种情况?是否可以在 AbstractAnnotationConfigDispatcherServletInitializer 之外的 java 配置中设置 ContextLoaderListener?或者我应该创建一个只有根配置的 DefaultServlet 吗?如何实现该配置的基本接口WebApplicationInitializer

【问题讨论】:

  • 您能解释一下在一个应用程序中需要多个调度程序的原因吗?前端控制器的全部意义在于您将请求多路复用到一个上。
  • @chrylis:当然。该项目更像是一个基于模块的共享服务构建工具包。它们彼此没有链接,但共享相同的基本设置和实体。在该项目中部署两个应用程序是不行的,并且尝试将调度程序配置为处理所有类型的视图技术(一些基于磁贴,其他基于 jsp,较新的在 Thymeleaf 上)也是一个坏主意。跨度>
  • 为什么这是个坏主意? Spring Boot 让它变得简单。
  • Spring Boot 是另一个主题。我真的很想拥有不同的 DispatcherServlets(具有不同的 Web 上下文)。使用 web.xml 配置很容易(因为 ContextLoaderListener 没有绑​​定到 Dispatcher)。我确信有一个解决方案或至少是最佳实践。
  • 你找到灵魂了吗?

标签: java spring spring-mvc spring-java-config


【解决方案1】:

Mahesh C. 展示了正确的道路,但他的实施太有限了。他在一点上是对的:您不能直接将AbstractAnnotationConfigDispatcherServletInitializer 用于多个调度程序servlet。但实施应该:

  • 创建根应用上下文
  • 给它一个初始配置并说明它应该扫描哪些包
  • 为它添加一个 ContextListener 到 servlet 上下文中
  • 然后对于每个调度程序 servlet
    • 创建子应用上下文
    • 为其提供相同的初始配置和要扫描的软件包
    • 使用上下文创建 DispatcherServlet
    • 将其添加到 servlet 上下文中

这是一个更完整的实现:

@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
    ...
}

这样,您可以完全控制哪些 bean 将在哪个上下文中结束,就像使用 XML 配置一样。

【讨论】:

  • 这与我的实现方式非常相似。与此同时,我改变了主意,转而使用 Spring Boot 转向微服务架构。
【解决方案2】:

如果你使用通用的 WebApplicationInitializer 接口而不是使用 spring 提供的抽象实现 - AbstractAnnotationConfigDispatcherServletInitializer,我认为你可以解决这个问题。

这样,您可以创建两个单独的初始化程序,这样您就可以在 startUp() 方法上获得不同的 ServletContext 并为每个方法注册不同的 AppConfig 和调度程序 servlet。

其中一个实现类可能如下所示:

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");

    }

}

【讨论】:

    【解决方案3】:

    我遇到了同样的问题。实际上,我有一个包含多个调度程序 servlet、过滤器和侦听器的复杂配置。

    我有一个如下所示的 web.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;
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 2017-03-11
      • 2015-06-15
      • 2019-01-15
      • 2021-10-09
      • 2015-02-03
      • 2019-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多