-
Notifications
You must be signed in to change notification settings - Fork 41.3k
Description
Spring Boot: 3.5.4
The @FilterRegistrationBean
annotation is ignored in the auto-configured MockMvc
. Filters are registered, but with their default configuration.
This seems to be caused by SpringBootMockMvcBuilderCustomizer
using its own RegistrationBeanAdapter
implementation that is not aware of the @FilterRegistrationBean
annotation:
Lines 327 to 336 in 925f9bc
private static final class FilterRegistrationBeanAdapter implements RegistrationBeanAdapter<Filter> { | |
@Override | |
public RegistrationBean createRegistrationBean(String name, Filter source, int totalNumberOfSourceBeans) { | |
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(source); | |
bean.setName(name); | |
return bean; | |
} | |
} |
Compare this with the "real" FilterRegistrationBeanAdapter
used during proper application start, which queries the BeanFactory
for the annotation and uses it to configure the filter:
Lines 344 to 355 in 925f9bc
public RegistrationBean createRegistrationBean(String beanName, Filter source, int totalNumberOfSourceBeans) { | |
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(source); | |
bean.setName(beanName); | |
FilterRegistration registrationAnnotation = this.beanFactory.findAnnotationOnBean(beanName, | |
FilterRegistration.class); | |
if (registrationAnnotation != null) { | |
Order orderAnnotation = this.beanFactory.findAnnotationOnBean(beanName, Order.class); | |
Assert.notNull(orderAnnotation, "'orderAnnotation' must not be null"); | |
configureFromAnnotation(bean, registrationAnnotation, orderAnnotation); | |
} | |
return bean; | |
} |
Reproducer (extract and run mvnw test
): demo2.zip
The reproducer registers two filter beans.
The first one uses FilterRegistrationBean
directly and maps the filter to /classic
:
@Bean
FilterRegistrationBean<MyFilter> classicRegistrationBean() {
var f = new FilterRegistrationBean<>(new MyFilter("Classic"));
f.setUrlPatterns(List.of("/classic"));
return f;
}
The second one uses the annotation variant and maps the filter to /annotation
:
@Bean
@FilterRegistration(urlPatterns = "/annotation")
MyFilter annotationRegistrationBean() {
return new MyFilter("Annotation");
}
The filter itself adds the string given in the constructor to the matched-filter
response header.
A test than issues requests to /annotation
and /classic
and verifies that exactly one of the filters matched by asserting that the response header contains only Classic
or only Annotation
:
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@WebMvcTest
class Demo2ApplicationTests {
@Autowired
private MockMvcTester mvc;
@Test
void annotation() {
assertThat(mvc.get().uri("/annotation"))
.headers()
.containsEntry("matched-filter", List.of("Annotation")); // SUCCESS, because the non-annotation-based filter was configured correctly and, as expected, didn't apply here.
}
@Test
void classic() {
assertThat(mvc.get().uri("/classic"))
.headers()
.containsEntry("matched-filter", List.of("Classic")); // FAILURE, because the header _also_ contains "Annotation", even though we didn't expect the annotation-based filter to apply here.
}
}