Add @ConditionalOnMissingFilterBean

Add a new condition that can be used to check for servlet `Filter`
beans that are either registered directly, or via a
`FilterRegistrationBean`.

Closes gh-14940
This commit is contained in:
Phillip Webb 2018-10-23 17:49:14 -07:00
parent 9f858e759c
commit 44a46f1514
2 changed files with 281 additions and 0 deletions

View File

@ -0,0 +1,58 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web.servlet;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.servlet.Filter;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.annotation.AliasFor;
/**
* {@link Conditional} that only matches when no {@link Filter} beans of the specified
* type are contained in the {@link BeanFactory}. This condition will detect both directly
* register {@link Filter} beans as well as those registered via a
* {@link FilterRegistrationBean}.
* <p>
* When placed on a {@code @Bean} method, the bean class defaults to the return type of
* the factory method:
*
* @author Phillip Webb
* @since 2.1.0
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ConditionalOnMissingBean(parameterizedContainer = FilterRegistrationBean.class)
public @interface ConditionalOnMissingFilterBean {
/**
* The filter bean type that must not be present.
* @return the bean type
*/
@AliasFor(annotation = ConditionalOnMissingBean.class)
Class<? extends Filter>[] value() default {};
}

View File

@ -0,0 +1,223 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web.servlet;
import java.io.IOException;
import java.util.function.Consumer;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.junit.Test;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConditionalOnMissingFilterBean}.
*
* @author Phillip Webb
*/
public class ConditionalOnMissingFilterBeanTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
@Test
public void outcomeWhenValueIsOfMissingBeanReturnsMatch() {
this.contextRunner
.withUserConfiguration(WithoutTestFilterConfig.class,
OnMissingWithValueConfig.class)
.run((context) -> assertThat(context)
.satisfies(filterBeanRequirement("myOtherFilter", "testFilter")));
}
@Test
public void outcomeWhenValueIsOfExistingBeanReturnsNoMatch() {
this.contextRunner
.withUserConfiguration(WithTestFilterConfig.class,
OnMissingWithValueConfig.class)
.run((context) -> assertThat(context)
.satisfies(filterBeanRequirement("myTestFilter")));
}
@Test
public void outcomeWhenValueIsOfMissingBeanRegistrationReturnsMatch() {
this.contextRunner
.withUserConfiguration(WithoutTestFilterRegistrationConfig.class,
OnMissingWithValueConfig.class)
.run((context) -> assertThat(context)
.satisfies(filterBeanRequirement("myOtherFilter", "testFilter")));
}
@Test
public void outcomeWhenValueIsOfExistingBeanRegistrationReturnsNoMatch() {
this.contextRunner
.withUserConfiguration(WithTestFilterRegistrationConfig.class,
OnMissingWithValueConfig.class)
.run((context) -> assertThat(context)
.satisfies(filterBeanRequirement("myTestFilter")));
}
@Test
public void outcomeWhenReturnTypeIsOfExistingBeanReturnsNoMatch() {
this.contextRunner
.withUserConfiguration(WithTestFilterConfig.class,
OnMissingWithReturnTypeConfig.class)
.run((context) -> assertThat(context)
.satisfies(filterBeanRequirement("myTestFilter")));
}
@Test
public void outcomeWhenReturnTypeIsOfExistingBeanRegistrationReturnsNoMatch() {
this.contextRunner
.withUserConfiguration(WithTestFilterRegistrationConfig.class,
OnMissingWithReturnTypeConfig.class)
.run((context) -> assertThat(context)
.satisfies(filterBeanRequirement("myTestFilter")));
}
@Test
public void outcomeWhenReturnRegistrationTypeIsOfExistingBeanReturnsNoMatch() {
this.contextRunner
.withUserConfiguration(WithTestFilterConfig.class,
OnMissingWithReturnRegistrationTypeConfig.class)
.run((context) -> assertThat(context)
.satisfies(filterBeanRequirement("myTestFilter")));
}
@Test
public void outcomeWhenReturnRegistrationTypeIsOfExistingBeanRegistrationReturnsNoMatch() {
this.contextRunner
.withUserConfiguration(WithTestFilterRegistrationConfig.class,
OnMissingWithReturnRegistrationTypeConfig.class)
.run((context) -> assertThat(context)
.satisfies(filterBeanRequirement("myTestFilter")));
}
private Consumer<ConfigurableApplicationContext> filterBeanRequirement(
String... names) {
return (context) -> {
String[] filters = context.getBeanNamesForType(Filter.class);
String[] registrations = context
.getBeanNamesForType(FilterRegistrationBean.class);
assertThat(StringUtils.concatenateStringArrays(filters, registrations))
.containsOnly(names);
};
}
@Configuration
static class WithTestFilterConfig {
@Bean
public TestFilter myTestFilter() {
return new TestFilter();
}
}
@Configuration
static class WithoutTestFilterConfig {
@Bean
public OtherFilter myOtherFilter() {
return new OtherFilter();
}
}
@Configuration
static class WithoutTestFilterRegistrationConfig {
@Bean
public FilterRegistrationBean<OtherFilter> myOtherFilter() {
return new FilterRegistrationBean<OtherFilter>(new OtherFilter());
}
}
@Configuration
static class WithTestFilterRegistrationConfig {
@Bean
public FilterRegistrationBean<TestFilter> myTestFilter() {
return new FilterRegistrationBean<TestFilter>(new TestFilter());
}
}
@Configuration
static class OnMissingWithValueConfig {
@Bean
@ConditionalOnMissingFilterBean(TestFilter.class)
public TestFilter testFilter() {
return new TestFilter();
}
}
@Configuration
static class OnMissingWithReturnTypeConfig {
@Bean
@ConditionalOnMissingFilterBean
public TestFilter testFilter() {
return new TestFilter();
}
}
@Configuration
static class OnMissingWithReturnRegistrationTypeConfig {
@Bean
@ConditionalOnMissingFilterBean
public FilterRegistrationBean<TestFilter> testFilter() {
return new FilterRegistrationBean<TestFilter>(new TestFilter());
}
}
static class TestFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
}
}
static class OtherFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
}
}
}