简体   繁体   English

Spring Java 配置多个调度程序

[英]Spring Java Config with Multiple Dispatchers

I've some experience Spring now and also have some pure java config web-apps in use.我现在有一些经验 Spring 并且还有一些纯 java 配置网络应用程序正在使用中。 However, these are usually based on a quiet simple setup:但是,这些通常基于安静的简单设置:

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

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.这不是基于 XML 的配置的问题,因为我们有一个独立于调度程序配置的专用 ContextLoaderListener。 But with java config I'm not sure if what I'm doing is fine so far;)但是对于 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).因此,我复制了配置并为两个 getServletName() 添加(否则两者都将被命名为“调度程序”,这将导致错误)。 The second config was looking like that:第二个配置看起来像这样:

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.class return from one of the AbstractAnnotationConfigDispatcherServletInitializer and it works fine.所以我从AbstractAnnotationConfigDispatcherServletInitializer之一删除了第二个MyAppConfig.class返回,它工作正常。 However, that doesn't feel to be the right way;)但是,这感觉不是正确的方法;)

For my understanding: should all DispatcherConfig be handled within one AbstractAnnotationConfigDispatcherServletInitializer or should I separate them as I did?据我了解:应该在一个AbstractAnnotationConfigDispatcherServletInitializer中处理所有 DispatcherConfig 还是应该像我一样将它们分开? I tried to configure them in one class but then my config was totally mixed (so I believe that's not the desired way).我试图在一个 class 中配置它们,但后来我的配置完全混合(所以我相信这不是理想的方式)。

How do you implement such a case?你如何实施这样的案例? Is it possible to set the ContextLoaderListener in java config outside of the AbstractAnnotationConfigDispatcherServletInitializer ?是否可以在AbstractAnnotationConfigDispatcherServletInitializer之外的 java 配置中设置ContextLoaderListener Or should I create a DefaultServlet which has only the root config?或者我应该创建一个只有根配置的DefaultServlet What about implementing the base interface of that configuration WebApplicationInitializer ?如何实现该配置WebApplicationInitializer的基本接口?

Mahesh C. showed the right path, but his implementation is too limited. Mahesh C.展示了正确的道路,但他的实施太有限了。 He is right on one point : you cannot use directly AbstractAnnotationConfigDispatcherServletInitializer for multiple dispatcher servlet. 他是对的:你不能直接使用AbstractAnnotationConfigDispatcherServletInitializer来实现多个调度程序servlet。 But the implementation should : 但实施应该:

  • 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 为它添加一个ContextListener到servlet上下文
  • then for each dispatcher servlet 然后为每个调度程序servlet
    • create a child application context 创建子应用程序上下文
    • gives it the same an initial configuration and packages to scan 给它提供相同的初始配置和扫描包
    • create a DispatcherServlet using the context 使用上下文创建DispatcherServlet
    • add it to the servlet context 将它添加到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配置一样。

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

    }

}

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

}

Some additional notes to @Mahesh C. @Mahesh C 的一些附加说明。 answer:回答:

  • ContextListener is not mandatory, it just initializes the context, this can be either done calling refresh method. ContextListener不是强制性的,它只是初始化上下文,这可以通过调用refresh方法来完成。

  • You can have different WebApplicationInitializer classes for each dispatcher, ordered using @Order annotation.您可以为每个调度程序拥有不同的WebApplicationInitializer类,使用@Order注释进行排序。

  • The different dispatcher web contexts don't need to share a root context.不同的调度程序 web 上下文不需要共享根上下文。 This is usual, but you can create completely independent dispatchers with unrelated contexts.这很常见,但您可以创建具有不相关上下文的完全独立的调度程序。 For example, if you want to serve a REST api along with static context and want to keep configurations separated.例如,如果您想提供 REST api 和 static 上下文并希望保持配置分离。

  • When having several dispatchers, it's recommendable to configure the RequestMappingHandlerMapping to pass the full URL to controllers for those without default mapping ("/"), otherwise by default it trims the dispatcher mapping part.当有多个调度程序时,建议将 RequestMappingHandlerMapping 配置为将完整的 URL 传递给那些没有默认映射(“/”)的控制器,否则默认情况下它会修剪调度程序映射部分。 This will simplify you tests.这将简化您的测试。 Spring-boot does this automatically, or if you don't use it, it can be done with WebMvcConfigurer : Spring-boot 自动执行此操作,或者如果您不使用它,可以使用WebMvcConfigurer完成:

     @Configuration @EnableWebMvc public class WebConfiguration implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { // Configure controller mappings to match with full path UrlPathHelper urlPathHelper = new UrlPathHelper(); urlPathHelper.setAlwaysUseFullPath(true); configurer.setUrlPathHelper(urlPathHelper); } }
  • An important question is Spring Security configuration.一个重要的问题是Spring 安全配置。 This is done registering the DelegatingFilterProxy in the WebApplicationInitializer after the dispatcher configuration with a similar mapping.这是在调度程序配置之后使用类似映射在WebApplicationInitializer中注册DelegatingFilterProxy完成的。 You can have one common configuration for all dispatchers or different configurations for each one registering a filter for each (filters must have different names).您可以为所有调度程序设置一个通用配置,或者为每个调度程序设置不同的配置,为每个调度程序注册一个过滤器(过滤器必须具有不同的名称)。 This filter looks for springSecurityFilterChain bean which is created using @EnableWebSecurity .此过滤器查找使用@EnableWebSecurity创建的springSecurityFilterChain bean。 This annotation can be in a configuration class in root or web app context.此注释可以在根或 web 应用程序上下文中的配置 class 中。 Example:例子:

     final String SERVLET_CONTEXT_PREFIX = "org.springframework.web.servlet.FrameworkServlet.CONTEXT."; FilterRegistration.Dynamic securityFilter = servletContext.addFilter("springSecurityFilterChain1", DelegatingFilterProxy.class); securityFilter.addMappingForUrlPatterns(null, false, "/subcontext1/*"); // Spring security bean is in web app context. securityFilter.setInitParameter("contextAttribute", SERVLET_CONTEXT_PREFIX + "dispatcher1"); // TargetBeanName by default is filter name, so change it to Spring Security standard one securityFilter.setInitParameter("targetBeanName", AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Spring:声明多个调度程序访问正确的WebApplicationContext - Spring: Accessing the correct WebApplicationContext with multiple dispatchers declared Spring MVC:与多个Dispatchers一起发现的模糊映射 - Spring MVC: Ambiguous mapping found with multiple Dispatchers 多个ThreadPoolTask​​执行Spring Java配置 - Multiple ThreadPoolTaskExecuters Spring Java Config Spring Security Java配置-多重身份验证管理器 - spring security java config - multiple authentication manager 具有XML配置的Spring Java Config - Spring Java Config with XML config 如何在 Spring Java Config 的单个 MessageListenerContainer 中添加多个 JMS MessageListner - How to add multiple JMS MessageListners in a single MessageListenerContainer for Spring Java Config 在 Spring Security Java Config 中创建多个 HTTP 部分 - Creating multiple HTTP sections in Spring Security Java Config Spring Security-如何使用Java Config配置多个身份验证提供程序 - Spring security - How to configure multiple authentication providers using java config 在哪里放置由多个Spring Java配置文件共享的私有方法? - Where to place a private method that shared by multiple Spring java config file? 使用java config在Spring中处理多个配置文件的最佳实践是什么? - What is the best practice for handling multiple profiles in Spring with java config?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM