具有多个调度程序的 Spring Java 配置

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28877982/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 14:19:54  来源:igfitidea点击:

Spring Java Config with Multiple Dispatchers

javaspringspring-mvcspring-java-config

提问by delimiter

I've some experience Spring now and also have some pure java config web-apps in use. However, these are usually based on a quiet simple setup:

我现在有一些 Spring 的经验,也有一些纯 java 配置的 web 应用程序正在使用中。但是,这些通常基于安静的简单设置:

  • application config for services / repositories
  • dispatcher config for one dispatcher (and some controllers)
  • (optional) spring security to secure the access
  • 服务/存储库的应用程序配置
  • 一个调度员(和一些控制器)的调度员配置
  • (可选)弹簧安全以确保访问安全

For my current project I need to have separate dispatcher contexts with different configuration. That's not a problem with the XML based configuration as we have a dedicated ContextLoaderListener that's independent from Dispatcher Configuration. But with java config I'm not sure if what I'm doing is fine so far ;)

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

Here's a common DispatcherConfig:

这是一个常见的 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";
  }
}

As said, I need a second (third, ...) dispatcher with another mapping (and view resolvers). So, I copied the config and added for both getServletName() (otherwise both will be named as 'dispatcher' which will cause errors). The second config was looking like that:

如前所述,我需要第二个(第三个,...)调度程序和另一个映射(和视图解析器)。因此,我复制了配置并为 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";
  }
}

When I use it like this, starting application results in a problem with ContextLoaderListener:

当我这样使用它时,启动应用程序会导致 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)
...

So I removed the second MyAppConfig.classreturn from one of the AbstractAnnotationConfigDispatcherServletInitializerand it works fine. However, that doesn't feel to be the right way ;)

所以我从AbstractAnnotationConfigDispatcherServletInitializer之一中删除了第二个MyAppConfig.class返回 ,它工作正常。但是,感觉这不是正确的方法;)

For my understanding: should all DispatcherConfig be handled within one AbstractAnnotationConfigDispatcherServletInitializeror should I separate them as I did? I tried to configure them in one class but then my config was totally mixed (so I believe that's not the desired way).

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

How do you implement such a case? Is it possible to set the ContextLoaderListenerin java config outside of the AbstractAnnotationConfigDispatcherServletInitializer? Or should I create a DefaultServletwhich has only the root config? What about implementing the base interface of that configuration WebApplicationInitializer?

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

回答by Serge Ballesta

Mahesh C. showed the right path, but his implementation is too limited. He is right on one point : you cannot use directly AbstractAnnotationConfigDispatcherServletInitializerfor multiple dispatcher servlet. But the implementation should :

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

  • create a root application context
  • gives it an initial configuration and say what packages it should scan
  • add a ContextListener for it to the servlet context
  • then for each dispatcher servlet
    • create a child application context
    • gives it the same an initial configuration and packages to scan
    • create a DispatcherServlet using the context
    • add it to the servlet context
  • 创建根应用程序上下文
  • 给它一个初始配置并说明它应该扫描哪些包
  • 为它添加一个 ContextListener 到 servlet 上下文
  • 然后对于每个调度程序 servlet
    • 创建子应用程序上下文
    • 给它相同的初始配置和要扫描的包
    • 使用上下文创建一个 DispatcherServlet
    • 将它添加到 servlet 上下文

Here is a more complete implementation :

这是一个更完整的实现:

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

That way, you have full control on which beans will end in which context, exactly as you would have with XML configuration.

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

回答by Mahesh C.

I think you can work it out if you use generic WebApplicationInitializer interface rather than using abstract implementation provided by spring - AbstractAnnotationConfigDispatcherServletInitializer.

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

That way, you could create two separate initializers, so you would get different ServletContext on startUp() method and register different AppConfig & dispatcher servlets for each of them.

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

One of such implementing class may look like this:

此类实现类之一可能如下所示:

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

    }

}

回答by Anil Agrawal

I faced the same issue. Actually I had a complex configuration with multiple dispatcher servlets, filters and listeners.

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

I had a web.xml like below

我有一个像下面这样的 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>

I replaced above web.xml with below java file

我用下面的java文件替换了上面的web.xml

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;
    }

}