![](/img/trans.png)
[英]Spring: Accessing the correct WebApplicationContext with multiple dispatchers declared
[英]Spring Java Config with Multiple Dispatchers
我现在有一些经验 Spring 并且还有一些纯 java 配置网络应用程序正在使用中。 但是,这些通常基于安静的简单设置:
对于我当前的项目,我需要具有不同配置的单独调度程序上下文。 这不是基于 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() 添加(否则两者都将被命名为“调度程序”,这将导致错误)。 第二个配置看起来像这样:
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)
...
所以我从AbstractAnnotationConfigDispatcherServletInitializer之一删除了第二个MyAppConfig.class返回,它工作正常。 但是,这感觉不是正确的方法;)
据我了解:应该在一个AbstractAnnotationConfigDispatcherServletInitializer中处理所有 DispatcherConfig 还是应该像我一样将它们分开? 我试图在一个 class 中配置它们,但后来我的配置完全混合(所以我相信这不是理想的方式)。
你如何实施这样的案例? 是否可以在AbstractAnnotationConfigDispatcherServletInitializer之外的 java 配置中设置ContextLoaderListener ? 或者我应该创建一个只有根配置的DefaultServlet ? 如何实现该配置WebApplicationInitializer的基本接口?
Mahesh C.展示了正确的道路,但他的实施太有限了。 他是对的:你不能直接使用AbstractAnnotationConfigDispatcherServletInitializer
来实现多个调度程序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配置一样。
如果您使用通用WebApplicationInitializer接口而不是使用spring提供的抽象实现 - AbstractAnnotationConfigDispatcherServletInitializer,我认为您可以解决这个问题。
这样,您可以创建两个单独的初始化程序,因此您将在startUp()方法上获得不同的ServletContext,并为每个初始化程序注册不同的AppConfig和dispatcher 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");
}
}
我遇到了同样的问题。 实际上我有一个复杂的配置,有多个调度程序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>
我用以下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;
}
}
@Mahesh C 的一些附加说明。 回答:
ContextListener
不是强制性的,它只是初始化上下文,这可以通过调用refresh
方法来完成。
您可以为每个调度程序拥有不同的WebApplicationInitializer
类,使用@Order
注释进行排序。
不同的调度程序 web 上下文不需要共享根上下文。 这很常见,但您可以创建具有不相关上下文的完全独立的调度程序。 例如,如果您想提供 REST api 和 static 上下文并希望保持配置分离。
当有多个调度程序时,建议将 RequestMappingHandlerMapping 配置为将完整的 URL 传递给那些没有默认映射(“/”)的控制器,否则默认情况下它会修剪调度程序映射部分。 这将简化您的测试。 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); } }
一个重要的问题是Spring 安全配置。 这是在调度程序配置之后使用类似映射在WebApplicationInitializer
中注册DelegatingFilterProxy
完成的。 您可以为所有调度程序设置一个通用配置,或者为每个调度程序设置不同的配置,为每个调度程序注册一个过滤器(过滤器必须具有不同的名称)。 此过滤器查找使用@EnableWebSecurity
创建的springSecurityFilterChain
bean。 此注释可以在根或 web 应用程序上下文中的配置 class 中。 例子:
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.