Description
Hi,
I'm not sure if this is a Spring Boot problem (I'm using spring-boot-3.4.2
with spring-webmvc-6.2.2
), a problem with @EnableWebMvc
Javadoc, or an issue with my implementation.
Similar to 34843, it seems that I must redeclare @EnableWebMvc
in each ServletRegistrationBean configuration classes, contrary to what the annotation Javadoc says that should only one configuration class should have that annotation.
* <p><strong>Note:</strong> only one {@code @configuration} class may have the
* {@code @EnableWebMvc} annotation to import the Spring Web MVC
* configuration. There can however be multiple {@code @configuration} classes
* implementing {@code WebMvcConfigurer} in order to customize the provided
* configuration.
In my implementation, I had disabled the Spring Boot DispatcherServletAutoConfiguration (MainApplication.java
), and created a configuration class (MvcConfig.java
) annotated with @EnableWebMvc
and extending WebMvcConfigurer
where I create two ServletRegistrationBean
(api
and webapi
) using their corresponding configuration classes (ApiConfig
and WebAppConfig
).
If I redeclare @EnableWebMvc
in the ServletRegistrationBean specific configuration class (e.g. ApiConfig
), I can call their scanned controllers with no problem. But if I don't do that (e.g. WebAppConfig
), when I call any scanned controller of that servlet I get a HTTP 404 error with a message "No endpoint GET /my-app-context/webapi/my-request-mapping-name."
MainApplication.java:
@Configuration
@SpringBootApplication(
exclude = {
DispatcherServletAutoConfiguration.class,
ErrorMvcAutoConfiguration.class,
SecurityAutoConfiguration.class,
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class
}
)
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class MainApplication extends SpringBootServletInitializer {
}
MvcConfig.java:
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
private static final String DISPATCHER_SERVLET_MAPPINGS_FORMAT = "/%s/*";
@Bean
DispatcherServletPath dispatcherServletPath() {
return () -> "/";
}
/**
* Servlet for Web Application.
* @return
*/
@Bean
ServletRegistrationBean<DispatcherServlet> app() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(WebAppConfig.class);
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean<DispatcherServlet> servletRegistrationBean =
new ServletRegistrationBean<>(
dispatcherServlet,
String.format(DISPATCHER_SERVLET_MAPPINGS_FORMAT, WebAppConfig.WEB_SERVLET_NAME
)
);
servletRegistrationBean.setName(WebAppConfig.WEB_SERVLET_NAME);
return servletRegistrationBean;
}
/**
* Servlet for API consumed by external systems.
* @return
*/
@Bean
ServletRegistrationBean<DispatcherServlet> api() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
AnnotationConfigWebApplicationContext applicationContext =
new AnnotationConfigWebApplicationContext();
applicationContext.register(ApiConfig.class);
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean<DispatcherServlet> servletRegistrationBean =
new ServletRegistrationBean<>(
dispatcherServlet,
String.format(DISPATCHER_SERVLET_MAPPINGS_FORMAT, ApiConfig.API_SERVLET_NAME
)
);
servletRegistrationBean.setName(ApiConfig.API_SERVLET_NAME);
return servletRegistrationBean;
}
}
ApiConfig.java (WITH @EnableWebMvc
):
@Configuration
@EnableWebMvc
@EnableAsync
@EnableAspectJAutoProxy(proxyTargetClass = true)
@EnableScheduling
@ComponentScan(basePackages = {
"my.packages.api"
}
, includeFilters = @Filter(Controller.class))
public class ApiConfig {
/**
* Servlet name.
*/
public static final String API_SERVLET_NAME = "api";
public ApiConfig() { }
}
WebAppConfig.java (WITHOUT @EnableWebMvc
):
@Configuration
@EnableAsync
@EnableAspectJAutoProxy(proxyTargetClass = true)
@EnableScheduling
@ComponentScan(basePackages = {
"my.packages.web"
}
, includeFilters = @Filter(Controller.class))
public class WebAppConfig {
/**
* servlet name.
*/
public static final String WEB_SERVLET_NAME = "webapi";
public WebAppConfig() { }
}