Browse Source
Add ConfigurationWarningsApplicationContextInitializer to report warnings for common configuration mistakes. Currently the initializer will log a warning if @ComponentScan is used on a @Configuration class in the "default" package. Fixes gh-2050pull/2071/head
13 changed files with 544 additions and 3 deletions
@ -0,0 +1,179 @@
@@ -0,0 +1,179 @@
|
||||
/* |
||||
* Copyright 2012-2014 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.context; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.springframework.beans.BeansException; |
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; |
||||
import org.springframework.beans.factory.config.BeanDefinition; |
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; |
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry; |
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; |
||||
import org.springframework.context.ApplicationContextInitializer; |
||||
import org.springframework.context.ConfigurableApplicationContext; |
||||
import org.springframework.context.annotation.ComponentScan; |
||||
import org.springframework.core.Ordered; |
||||
import org.springframework.core.PriorityOrdered; |
||||
import org.springframework.core.annotation.AnnotationAttributes; |
||||
import org.springframework.core.type.AnnotationMetadata; |
||||
import org.springframework.util.ClassUtils; |
||||
import org.springframework.util.ObjectUtils; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* {@link ApplicationContextInitializer} to report warnings for common misconfiguration |
||||
* mistakes. |
||||
* |
||||
* @author Phillip Webb |
||||
* @since 1.2.0 |
||||
*/ |
||||
public class ConfigurationWarningsApplicationContextInitializer implements |
||||
ApplicationContextInitializer<ConfigurableApplicationContext> { |
||||
|
||||
private static Log logger = LogFactory |
||||
.getLog(ConfigurationWarningsApplicationContextInitializer.class); |
||||
|
||||
@Override |
||||
public void initialize(ConfigurableApplicationContext context) { |
||||
context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor( |
||||
getChecks())); |
||||
} |
||||
|
||||
/** |
||||
* Returns the checks that should be applied. |
||||
* @return the checks to apply |
||||
*/ |
||||
protected Check[] getChecks() { |
||||
return new Check[] { new ComponentScanDefaultPackageCheck() }; |
||||
} |
||||
|
||||
/** |
||||
* {@link BeanDefinitionRegistryPostProcessor} to report warnings. |
||||
*/ |
||||
protected final static class ConfigurationWarningsPostProcessor implements |
||||
PriorityOrdered, BeanDefinitionRegistryPostProcessor { |
||||
|
||||
private Check[] checks; |
||||
|
||||
public ConfigurationWarningsPostProcessor(Check[] checks) { |
||||
this.checks = checks; |
||||
} |
||||
|
||||
@Override |
||||
public int getOrder() { |
||||
return Ordered.LOWEST_PRECEDENCE - 1; |
||||
} |
||||
|
||||
@Override |
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) |
||||
throws BeansException { |
||||
} |
||||
|
||||
@Override |
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) |
||||
throws BeansException { |
||||
for (Check check : this.checks) { |
||||
String message = check.getWarning(registry); |
||||
if (StringUtils.hasLength(message)) { |
||||
warn(message); |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
private void warn(String message) { |
||||
if (logger.isWarnEnabled()) { |
||||
logger.warn("\n\n** WARNING ** : " + message + "\n\n"); |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* A single check that can be applied. |
||||
*/ |
||||
protected static interface Check { |
||||
|
||||
/** |
||||
* Returns a warning if the check fails or {@code null} if there are no problems. |
||||
* @param registry the {@link BeanDefinitionRegistry} |
||||
* @return a warning message or {@code null} |
||||
*/ |
||||
String getWarning(BeanDefinitionRegistry registry); |
||||
|
||||
} |
||||
|
||||
/** |
||||
* {@link Check} for {@code @ComponentScan} on the default package. |
||||
*/ |
||||
protected static class ComponentScanDefaultPackageCheck implements Check { |
||||
|
||||
@Override |
||||
public String getWarning(BeanDefinitionRegistry registry) { |
||||
if (isComponentScanningDefaultPackage(registry)) { |
||||
return "Your ApplicationContext is unlikely to start due to a " |
||||
+ "@ComponentScan of the default package."; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
private boolean isComponentScanningDefaultPackage(BeanDefinitionRegistry registry) { |
||||
String[] names = registry.getBeanDefinitionNames(); |
||||
for (String name : names) { |
||||
BeanDefinition definition = registry.getBeanDefinition(name); |
||||
if (definition instanceof AnnotatedBeanDefinition) { |
||||
AnnotatedBeanDefinition annotatedDefinition = (AnnotatedBeanDefinition) definition; |
||||
if (isScanningDefaultPackage(annotatedDefinition.getMetadata())) { |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private boolean isScanningDefaultPackage(AnnotationMetadata metadata) { |
||||
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata |
||||
.getAnnotationAttributes(ComponentScan.class.getName(), true)); |
||||
if (attributes != null && hasNoScanPackageSpecified(attributes)) { |
||||
if (isInDefaultPackage(metadata.getClassName())) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private boolean hasNoScanPackageSpecified(AnnotationAttributes attributes) { |
||||
return isAllEmpty(attributes, "value", "basePackages", "basePackageClasses"); |
||||
} |
||||
|
||||
private boolean isAllEmpty(AnnotationAttributes attributes, String... names) { |
||||
for (String name : names) { |
||||
if (!ObjectUtils.isEmpty(attributes.getStringArray(name))) { |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
protected boolean isInDefaultPackage(String className) { |
||||
String packageName = ClassUtils.getPackageName(className); |
||||
return StringUtils.isEmpty(packageName); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,133 @@
@@ -0,0 +1,133 @@
|
||||
/* |
||||
* Copyright 2012-2014 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.context; |
||||
|
||||
import org.junit.Rule; |
||||
import org.junit.Test; |
||||
import org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer.ComponentScanDefaultPackageCheck; |
||||
import org.springframework.boot.context.configwarnings.InDefaultPackageConfiguration; |
||||
import org.springframework.boot.context.configwarnings.InDefaultPackageWithBasePackageClassesConfiguration; |
||||
import org.springframework.boot.context.configwarnings.InDefaultPackageWithBasePackagesConfiguration; |
||||
import org.springframework.boot.context.configwarnings.InDefaultPackageWithMetaAnnotationConfiguration; |
||||
import org.springframework.boot.context.configwarnings.InDefaultPackageWithValueConfiguration; |
||||
import org.springframework.boot.context.configwarnings.InDefaultPackageWithoutScanConfiguration; |
||||
import org.springframework.boot.context.configwarnings.InRealPackageConfiguration; |
||||
import org.springframework.boot.test.OutputCapture; |
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext; |
||||
|
||||
import static org.hamcrest.Matchers.containsString; |
||||
import static org.hamcrest.Matchers.not; |
||||
import static org.junit.Assert.assertThat; |
||||
|
||||
/** |
||||
* Tests for {@link ConfigurationWarningsApplicationContextInitializer}. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
public class ConfigurationWarningsApplicationContextInitializerTests { |
||||
|
||||
private static final String SCAN_WARNING = "Your ApplicationContext is unlikely to " |
||||
+ "start due to a @ComponentScan of the default package"; |
||||
|
||||
@Rule |
||||
public OutputCapture output = new OutputCapture(); |
||||
|
||||
@Test |
||||
public void logWarningInDefaultPackage() { |
||||
load(InDefaultPackageConfiguration.class); |
||||
assertThat(this.output.toString(), containsString(SCAN_WARNING)); |
||||
} |
||||
|
||||
@Test |
||||
public void logWarningInDefaultPackageAndMetaAnnotation() { |
||||
load(InDefaultPackageWithMetaAnnotationConfiguration.class); |
||||
assertThat(this.output.toString(), containsString(SCAN_WARNING)); |
||||
} |
||||
|
||||
@Test |
||||
public void noLogIfInRealPackage() throws Exception { |
||||
load(InRealPackageConfiguration.class); |
||||
assertThat(this.output.toString(), not(containsString(SCAN_WARNING))); |
||||
} |
||||
|
||||
@Test |
||||
public void noLogWithoutComponetScanAnnotation() throws Exception { |
||||
load(InDefaultPackageWithoutScanConfiguration.class); |
||||
assertThat(this.output.toString(), not(containsString(SCAN_WARNING))); |
||||
} |
||||
|
||||
@Test |
||||
public void noLogIfHasValue() throws Exception { |
||||
load(InDefaultPackageWithValueConfiguration.class); |
||||
assertThat(this.output.toString(), not(containsString(SCAN_WARNING))); |
||||
} |
||||
|
||||
@Test |
||||
public void noLogIfHasBasePackages() throws Exception { |
||||
load(InDefaultPackageWithBasePackagesConfiguration.class); |
||||
assertThat(this.output.toString(), not(containsString(SCAN_WARNING))); |
||||
} |
||||
|
||||
@Test |
||||
public void noLogIfHasBasePackageClasses() throws Exception { |
||||
load(InDefaultPackageWithBasePackageClassesConfiguration.class); |
||||
assertThat(this.output.toString(), not(containsString(SCAN_WARNING))); |
||||
} |
||||
|
||||
private void load(Class<?> configClass) { |
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); |
||||
new TestConfigurationWarningsApplicationContextInitializer().initialize(context); |
||||
context.register(configClass); |
||||
try { |
||||
context.refresh(); |
||||
} |
||||
catch (Exception ex) { |
||||
ex.printStackTrace(); |
||||
} |
||||
finally { |
||||
context.close(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Testable version of {@link ConfigurationWarningsApplicationContextInitializer}. |
||||
*/ |
||||
public static class TestConfigurationWarningsApplicationContextInitializer extends |
||||
ConfigurationWarningsApplicationContextInitializer { |
||||
|
||||
@Override |
||||
protected Check[] getChecks() { |
||||
return new Check[] { new TestComponentScanDefaultPackageCheck() }; |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Testable ComponentScanDefaultPackageCheck that doesn't need to use the default |
||||
* package. |
||||
*/ |
||||
static class TestComponentScanDefaultPackageCheck extends |
||||
ComponentScanDefaultPackageCheck { |
||||
|
||||
@Override |
||||
protected boolean isInDefaultPackage(String className) { |
||||
return className.contains("InDefault"); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
/* |
||||
* Copyright 2012-2014 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.context.configwarnings; |
||||
|
||||
import org.springframework.context.annotation.ComponentScan; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
@Configuration |
||||
@ComponentScan |
||||
public class InDefaultPackageConfiguration { |
||||
} |
||||
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
/* |
||||
* Copyright 2012-2014 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.context.configwarnings; |
||||
|
||||
import org.springframework.boot.context.configwarnings.nested.ExampleBean; |
||||
import org.springframework.context.annotation.ComponentScan; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
@Configuration |
||||
@ComponentScan(basePackageClasses = ExampleBean.class) |
||||
public class InDefaultPackageWithBasePackageClassesConfiguration { |
||||
} |
||||
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
/* |
||||
* Copyright 2012-2014 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.context.configwarnings; |
||||
|
||||
import org.springframework.context.annotation.ComponentScan; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
@Configuration |
||||
@ComponentScan(basePackages = "org.springframework.boot.context.configwarnings.nested") |
||||
public class InDefaultPackageWithBasePackagesConfiguration { |
||||
} |
||||
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
/* |
||||
* Copyright 2012-2014 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.context.configwarnings; |
||||
|
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
@Configuration |
||||
@MetaComponentScan |
||||
public class InDefaultPackageWithMetaAnnotationConfiguration { |
||||
} |
||||
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
/* |
||||
* Copyright 2012-2014 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.context.configwarnings; |
||||
|
||||
import org.springframework.context.annotation.ComponentScan; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
@Configuration |
||||
@ComponentScan("org.springframework.boot.context.configwarnings.nested") |
||||
public class InDefaultPackageWithValueConfiguration { |
||||
} |
||||
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
/* |
||||
* Copyright 2012-2014 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.context.configwarnings; |
||||
|
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
@Configuration |
||||
public class InDefaultPackageWithoutScanConfiguration { |
||||
} |
||||
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
/* |
||||
* Copyright 2012-2014 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.context.configwarnings; |
||||
|
||||
import org.springframework.context.annotation.ComponentScan; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
@Configuration |
||||
@ComponentScan |
||||
public class InRealPackageConfiguration { |
||||
} |
||||
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
/* |
||||
* Copyright 2012-2014 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.context.configwarnings; |
||||
|
||||
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.ComponentScan; |
||||
|
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Target(ElementType.TYPE) |
||||
@ComponentScan |
||||
public @interface MetaComponentScan { |
||||
|
||||
} |
||||
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
/* |
||||
* Copyright 2012-2014 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.context.configwarnings.nested; |
||||
|
||||
import org.springframework.stereotype.Component; |
||||
|
||||
@Component |
||||
public class ExampleBean { |
||||
|
||||
} |
||||
Loading…
Reference in new issue