Skip to content

Auto-configured MockMvc ignores @FilterRegistrationBean annotation #46541

@kzander91

Description

@kzander91

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:

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:

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

}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions