forked from spring-projects/spring-boot
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce @ConditionalOnMissingServletFilter
This commit introduces a new Conditional annotation `@ConditionalOnMissingServletFilter` which checks for the absence of Servlet filters as beans or `FilterRegistrationBean`s. This Conditional annotation can be used to guard Servlet Filter bean definitions like: ``` @bean @ConditionalOnMissingServletFilter(MyServletFilter.class) public MyServletFilter myServletFilter() { //... } ``` Fixes spring-projectsgh-7475
- Loading branch information
Showing
3 changed files
with
357 additions
and
0 deletions.
There are no files selected for viewing
54 changes: 54 additions & 0 deletions
54
...a/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingServletFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/* | ||
* Copyright 2012-2016 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.condition; | ||
|
||
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 org.springframework.context.annotation.Conditional; | ||
|
||
/** | ||
* {@link Conditional} that only matches when the specified servlet filter types | ||
* are not contained in the {@link org.springframework.beans.factory.BeanFactory}, | ||
* either registered directly as beans or registered with | ||
* {@link org.springframework.boot.web.servlet.FilterRegistrationBean}. | ||
* <p> | ||
* The condition can only match the bean definitions that have been processed by the | ||
* application context so far and, as such, it is strongly recommended to use this | ||
* condition on auto-configuration classes only. If a candidate bean may be created by | ||
* another auto-configuration, make sure that the one using this condition runs after. | ||
* | ||
* @author Brian Clozel | ||
* @since 1.5.0 | ||
*/ | ||
@Target({ ElementType.TYPE, ElementType.METHOD }) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Documented | ||
@Conditional(OnMissingServletFilterCondition.class) | ||
public @interface ConditionalOnMissingServletFilter { | ||
|
||
/** | ||
* The class type of servlet filter bean that should be checked. | ||
* The condition matches when each class specified is missing in the | ||
* {@link org.springframework.context.ApplicationContext}. | ||
* @return the class types of beans to check | ||
*/ | ||
Class<?>[] value(); | ||
} |
129 changes: 129 additions & 0 deletions
129
...ava/org/springframework/boot/autoconfigure/condition/OnMissingServletFilterCondition.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
/* | ||
* Copyright 2012-2016 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.condition; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.LinkedHashSet; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
|
||
import javax.servlet.Filter; | ||
|
||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; | ||
import org.springframework.boot.web.servlet.FilterRegistrationBean; | ||
import org.springframework.context.annotation.Condition; | ||
import org.springframework.context.annotation.ConditionContext; | ||
import org.springframework.context.annotation.ConfigurationCondition; | ||
import org.springframework.core.Ordered; | ||
import org.springframework.core.annotation.Order; | ||
import org.springframework.core.type.AnnotatedTypeMetadata; | ||
import org.springframework.util.Assert; | ||
import org.springframework.util.ClassUtils; | ||
|
||
/** | ||
* {@link Condition} that checks for the absence of specific Servlet Filters. | ||
* @author Brian Clozel | ||
*/ | ||
@Order(Ordered.LOWEST_PRECEDENCE) | ||
class OnMissingServletFilterCondition extends SpringBootCondition implements ConfigurationCondition { | ||
|
||
@Override | ||
public ConfigurationPhase getConfigurationPhase() { | ||
return ConfigurationPhase.REGISTER_BEAN; | ||
} | ||
|
||
@Override | ||
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { | ||
ConditionMessage matchMessage = ConditionMessage.empty(); | ||
if (metadata.isAnnotated(ConditionalOnMissingServletFilter.class.getName())) { | ||
List<String> servletFilters = parseAnnotationForServletFilters(context, metadata); | ||
Set<String> matching = getMatchingBeans(context, servletFilters); | ||
if (matching.isEmpty()) { | ||
return ConditionOutcome.match(ConditionMessage | ||
.forCondition(ConditionalOnMissingServletFilter.class) | ||
.didNotFind("any Servlet filter").atAll()); | ||
} | ||
else { | ||
return ConditionOutcome.noMatch(ConditionMessage | ||
.forCondition(ConditionalOnMissingServletFilter.class) | ||
.found("Servlet filter", "Servlet filters") | ||
.items(ConditionMessage.Style.QUOTE, matching)); | ||
} | ||
} | ||
return ConditionOutcome.match(matchMessage); | ||
} | ||
|
||
private List<String> parseAnnotationForServletFilters(ConditionContext context, AnnotatedTypeMetadata metadata) { | ||
List<String> servletFilters = new ArrayList<String>(); | ||
String annotationType = ConditionalOnMissingServletFilter.class.getName(); | ||
List<Object> values = metadata | ||
.getAllAnnotationAttributes(annotationType, true).get("value"); | ||
for (Object value : values) { | ||
if (value instanceof String[]) { | ||
Collections.addAll(servletFilters, (String[]) value); | ||
} | ||
else { | ||
servletFilters.add((String) value); | ||
} | ||
} | ||
return servletFilters; | ||
} | ||
|
||
private Set<String> getMatchingBeans(ConditionContext context, List<String> servletFilters) { | ||
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); | ||
if (beanFactory == null) { | ||
return Collections.emptySet(); | ||
} | ||
Set<String> result = new LinkedHashSet<String>(); | ||
BeanTypeRegistry beanTypeRegistry = BeanTypeRegistry.get(context.getBeanFactory()); | ||
try { | ||
Map<String, FilterRegistrationBean> filterRegistrations = findFilterRegistrationBeans(context); | ||
for (String servletFilter : servletFilters) { | ||
// checking for servlet filter beans | ||
Class<?> filterType = ClassUtils.forName(servletFilter, context.getClassLoader()); | ||
Assert.state(ClassUtils.isAssignable(Filter.class, filterType), | ||
"Type " + filterType.getName() + " is not a Servlet Filter"); | ||
result.addAll(beanTypeRegistry.getNamesForType(filterType)); | ||
// checking for servlet filter registrations | ||
for (Map.Entry<String, FilterRegistrationBean> entry : filterRegistrations.entrySet()) { | ||
FilterRegistrationBean registrationBean = entry.getValue(); | ||
if (ClassUtils.isAssignableValue(filterType, registrationBean.getFilter())) { | ||
result.add(entry.getKey()); | ||
} | ||
} | ||
} | ||
} | ||
catch (Throwable e) { | ||
return Collections.emptySet(); | ||
} | ||
return result; | ||
} | ||
|
||
private Map<String, FilterRegistrationBean> findFilterRegistrationBeans(ConditionContext context) { | ||
BeanTypeRegistry beanTypeRegistry = BeanTypeRegistry.get(context.getBeanFactory()); | ||
Map<String, FilterRegistrationBean> filterRegistrations = new HashMap<String, FilterRegistrationBean>(); | ||
for (String registrationName : beanTypeRegistry.getNamesForType(FilterRegistrationBean.class)) { | ||
filterRegistrations.put(registrationName, | ||
context.getBeanFactory().getBean(registrationName, FilterRegistrationBean.class)); | ||
} | ||
return filterRegistrations; | ||
} | ||
|
||
} |
174 changes: 174 additions & 0 deletions
174
.../springframework/boot/autoconfigure/condition/ConditionalOnMissingServletFilterTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
/* | ||
* Copyright 2012-2016 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.condition; | ||
|
||
import java.io.IOException; | ||
|
||
import javax.servlet.Filter; | ||
import javax.servlet.FilterChain; | ||
import javax.servlet.FilterConfig; | ||
import javax.servlet.ServletException; | ||
import javax.servlet.ServletRequest; | ||
import javax.servlet.ServletResponse; | ||
|
||
import org.junit.Rule; | ||
import org.junit.Test; | ||
import org.junit.rules.ExpectedException; | ||
|
||
import org.springframework.boot.web.servlet.FilterRegistrationBean; | ||
import org.springframework.context.annotation.AnnotationConfigApplicationContext; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
/** | ||
* Tests for {@link ConditionalOnMissingServletFilter}. | ||
* | ||
* @author Brian Clozel | ||
*/ | ||
public class ConditionalOnMissingServletFilterTests { | ||
|
||
@Rule | ||
public ExpectedException thrown = ExpectedException.none(); | ||
|
||
private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); | ||
|
||
@Test | ||
public void testMissingFilter() { | ||
this.context.register(TestSingleFilterConfiguration.class); | ||
this.context.refresh(); | ||
assertThat(this.context.getBeansOfType(CustomServletFilter.class).isEmpty()).isTrue(); | ||
assertThat(this.context.getBean("foo")).isEqualTo("foo"); | ||
} | ||
|
||
@Test | ||
public void testExistingFilter() { | ||
this.context.register(CustomServletFilterConfiguration.class, TestSingleFilterConfiguration.class); | ||
this.context.refresh(); | ||
assertThat(this.context.getBeansOfType(CustomServletFilter.class)) | ||
.containsKey("customServletFilter").hasSize(1); | ||
assertThat(this.context.containsBean("foo")).isFalse(); | ||
} | ||
|
||
@Test | ||
public void testExistingFilterRegistration() { | ||
this.context.register(CustomFilterRegistrationConfiguration.class, TestSingleFilterConfiguration.class); | ||
this.context.refresh(); | ||
assertThat(this.context.getBeansOfType(FilterRegistrationBean.class)) | ||
.containsKey("customServletFilterRegistration").hasSize(1); | ||
assertThat(this.context.containsBean("foo")).isFalse(); | ||
} | ||
|
||
@Test | ||
public void testMultipleFiltersCondition() { | ||
this.context.register(OtherServletFilterConfiguration.class, CustomFilterRegistrationConfiguration.class, | ||
TestMultipleFiltersConfiguration.class); | ||
this.context.refresh(); | ||
assertThat(this.context.getBeansOfType(OtherServletFilter.class)) | ||
.containsKey("otherServletFilter").hasSize(1); | ||
assertThat(this.context.getBeansOfType(FilterRegistrationBean.class)) | ||
.containsKey("customServletFilterRegistration").hasSize(1); | ||
assertThat(this.context.containsBean("foo")).isFalse(); | ||
} | ||
|
||
@Configuration | ||
protected static class CustomServletFilterConfiguration { | ||
|
||
@Bean | ||
public CustomServletFilter customServletFilter() { | ||
return new CustomServletFilter(); | ||
} | ||
|
||
} | ||
|
||
@Configuration | ||
protected static class OtherServletFilterConfiguration { | ||
|
||
@Bean | ||
public OtherServletFilter otherServletFilter() { | ||
return new OtherServletFilter(); | ||
} | ||
|
||
} | ||
|
||
@Configuration | ||
protected static class CustomFilterRegistrationConfiguration { | ||
|
||
@Bean | ||
public FilterRegistrationBean customServletFilterRegistration() { | ||
FilterRegistrationBean filter = new FilterRegistrationBean(); | ||
filter.setFilter(new CustomServletFilter()); | ||
return filter; | ||
} | ||
|
||
} | ||
|
||
@Configuration | ||
protected static class TestSingleFilterConfiguration { | ||
|
||
@Bean | ||
@ConditionalOnMissingServletFilter(CustomServletFilter.class) | ||
public String foo() { | ||
return "foo"; | ||
} | ||
|
||
} | ||
|
||
@Configuration | ||
protected static class TestMultipleFiltersConfiguration { | ||
|
||
@Bean | ||
@ConditionalOnMissingServletFilter({CustomServletFilter.class, OtherServletFilter.class}) | ||
public String foo() { | ||
return "foo"; | ||
} | ||
|
||
} | ||
|
||
static class CustomServletFilter implements Filter { | ||
|
||
@Override | ||
public void init(FilterConfig filterConfig) throws ServletException { | ||
} | ||
|
||
@Override | ||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) | ||
throws IOException, ServletException { | ||
} | ||
|
||
@Override | ||
public void destroy() { | ||
} | ||
} | ||
|
||
static class OtherServletFilter implements Filter { | ||
|
||
@Override | ||
public void init(FilterConfig filterConfig) throws ServletException { | ||
} | ||
|
||
@Override | ||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) | ||
throws IOException, ServletException { | ||
} | ||
|
||
@Override | ||
public void destroy() { | ||
} | ||
} | ||
} |