diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ConditionalOnMissingFilterBean.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ConditionalOnMissingFilterBean.java new file mode 100644 index 0000000000..8e6fb1c112 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ConditionalOnMissingFilterBean.java @@ -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}. + *

+ * 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[] value() default {}; + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ConditionalOnMissingFilterBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ConditionalOnMissingFilterBeanTests.java new file mode 100644 index 0000000000..9a1719f1c8 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ConditionalOnMissingFilterBeanTests.java @@ -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 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 myOtherFilter() { + return new FilterRegistrationBean(new OtherFilter()); + } + + } + + @Configuration + static class WithTestFilterRegistrationConfig { + + @Bean + public FilterRegistrationBean myTestFilter() { + return new FilterRegistrationBean(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() { + return new FilterRegistrationBean(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 { + } + + } + +}