Log warning if scanning org or org.springframework

Update ConfigurationWarningsApplicationContextInitializer to also log
warnings if the user is scanning `org` or `org.springframework`.

Fixes gh-4777
pull/4690/merge
Phillip Webb 9 years ago
parent 9be4b57182
commit 19056a1104

@ -16,6 +16,13 @@
package org.springframework.boot.context;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -33,7 +40,6 @@ 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;
/**
@ -60,7 +66,7 @@ public class ConfigurationWarningsApplicationContextInitializer
* @return the checks to apply
*/
protected Check[] getChecks() {
return new Check[] { new ComponentScanDefaultPackageCheck() };
return new Check[] { new ComponentScanPackageCheck() };
}
/**
@ -120,62 +126,101 @@ public class ConfigurationWarningsApplicationContextInitializer
}
/**
* {@link Check} for {@code @ComponentScan} on the default package.
* {@link Check} for {@code @ComponentScan} on problematic package.
*/
protected static class ComponentScanDefaultPackageCheck implements Check {
protected static class ComponentScanPackageCheck implements Check {
private static final Set<String> PROBLEM_PACKAGES;
static {
Set<String> pacakges = new HashSet<String>();
pacakges.add("org.springframework");
pacakges.add("org");
PROBLEM_PACKAGES = Collections.unmodifiableSet(pacakges);
}
@Override
public String getWarning(BeanDefinitionRegistry registry) {
if (isComponentScanningDefaultPackage(registry)) {
return "Your ApplicationContext is unlikely to start due to a "
+ "@ComponentScan of the default package.";
Set<String> scannedPackages = getComponentScanningPackages(registry);
List<String> problematicPackages = getProblematicPackages(scannedPackages);
if (problematicPackages.isEmpty()) {
return null;
}
return null;
return "Your ApplicationContext is unlikely to "
+ "start due to a @ComponentScan of "
+ StringUtils.collectionToDelimitedString(problematicPackages, ", ")
+ ".";
}
private boolean isComponentScanningDefaultPackage(
protected Set<String> getComponentScanningPackages(
BeanDefinitionRegistry registry) {
Set<String> packages = new LinkedHashSet<String>();
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;
}
addComponentScanningPackages(packages,
annotatedDefinition.getMetadata());
}
}
return false;
return packages;
}
private boolean isScanningDefaultPackage(AnnotationMetadata metadata) {
private void addComponentScanningPackages(Set<String> packages,
AnnotationMetadata metadata) {
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata
.getAnnotationAttributes(ComponentScan.class.getName(), true));
if (attributes != null && hasNoScanPackageSpecified(attributes)) {
if (isInDefaultPackage(metadata.getClassName())) {
return true;
if (attributes != null) {
addPackages(packages, attributes.getStringArray("value"));
addPackages(packages, attributes.getStringArray("basePackages"));
addClasses(packages, attributes.getStringArray("basePackageClasses"));
if (packages.isEmpty()) {
packages.add(ClassUtils.getPackageName(metadata.getClassName()));
}
}
return false;
}
private boolean hasNoScanPackageSpecified(AnnotationAttributes attributes) {
return isAllEmpty(attributes, "value", "basePackages", "basePackageClasses");
private void addPackages(Set<String> packages, String[] values) {
if (values != null) {
for (String value : values) {
packages.add(value);
}
}
}
private boolean isAllEmpty(AnnotationAttributes attributes, String... names) {
for (String name : names) {
if (!ObjectUtils.isEmpty(attributes.getStringArray(name))) {
return false;
private void addClasses(Set<String> packages, String[] values) {
if (values != null) {
for (String value : values) {
packages.add(ClassUtils.getPackageName(value));
}
}
}
private List<String> getProblematicPackages(Set<String> scannedPackages) {
List<String> problematicPackages = new ArrayList<String>();
for (String scannedPackage : scannedPackages) {
if (isProblematicPackage(scannedPackage)) {
problematicPackages.add(getDisplayName(scannedPackage));
}
}
return true;
return problematicPackages;
}
private boolean isProblematicPackage(String scannedPackage) {
if (scannedPackage == null || scannedPackage.length() == 0) {
return true;
}
return PROBLEM_PACKAGES.contains(scannedPackage);
}
protected boolean isInDefaultPackage(String className) {
String packageName = ClassUtils.getPackageName(className);
return StringUtils.isEmpty(packageName);
private String getDisplayName(String scannedPackage) {
if (scannedPackage == null || scannedPackage.length() == 0) {
return "the default package";
}
return "'" + scannedPackage + "'";
}
}
}

@ -16,17 +16,23 @@
package org.springframework.boot.context;
import java.util.LinkedHashSet;
import java.util.Set;
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.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer.ComponentScanPackageCheck;
import org.springframework.boot.context.configwarnings.dflt.InDefaultPackageConfiguration;
import org.springframework.boot.context.configwarnings.dflt.InDefaultPackageWithBasePackageClassesConfiguration;
import org.springframework.boot.context.configwarnings.dflt.InDefaultPackageWithBasePackagesConfiguration;
import org.springframework.boot.context.configwarnings.dflt.InDefaultPackageWithMetaAnnotationConfiguration;
import org.springframework.boot.context.configwarnings.dflt.InDefaultPackageWithValueConfiguration;
import org.springframework.boot.context.configwarnings.dflt.InDefaultPackageWithoutScanConfiguration;
import org.springframework.boot.context.configwarnings.orgspring.InOrgSpringPackageConfiguration;
import org.springframework.boot.context.configwarnings.real.InRealButScanningProblemPackages;
import org.springframework.boot.context.configwarnings.real.InRealPackageConfiguration;
import org.springframework.boot.test.OutputCapture;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@ -41,8 +47,11 @@ import static org.junit.Assert.assertThat;
*/
public class ConfigurationWarningsApplicationContextInitializerTests {
private static final String SCAN_WARNING = "Your ApplicationContext is unlikely to "
+ "start due to a @ComponentScan of the default package";
private static final String DEFAULT_SCAN_WARNING = "Your ApplicationContext is unlikely to "
+ "start due to a @ComponentScan of the default package.";
private static final String ORGSPRING_SCAN_WARNING = "Your ApplicationContext is unlikely to "
+ "start due to a @ComponentScan of 'org.springframework'.";
@Rule
public OutputCapture output = new OutputCapture();
@ -50,43 +59,58 @@ public class ConfigurationWarningsApplicationContextInitializerTests {
@Test
public void logWarningInDefaultPackage() {
load(InDefaultPackageConfiguration.class);
assertThat(this.output.toString(), containsString(SCAN_WARNING));
assertThat(this.output.toString(), containsString(DEFAULT_SCAN_WARNING));
}
@Test
public void logWarningInDefaultPackageAndMetaAnnotation() {
load(InDefaultPackageWithMetaAnnotationConfiguration.class);
assertThat(this.output.toString(), containsString(SCAN_WARNING));
assertThat(this.output.toString(), containsString(DEFAULT_SCAN_WARNING));
}
@Test
public void noLogIfInRealPackage() throws Exception {
load(InRealPackageConfiguration.class);
assertThat(this.output.toString(), not(containsString(SCAN_WARNING)));
assertThat(this.output.toString(), not(containsString(DEFAULT_SCAN_WARNING)));
}
@Test
public void noLogWithoutComponentScanAnnotation() throws Exception {
load(InDefaultPackageWithoutScanConfiguration.class);
assertThat(this.output.toString(), not(containsString(SCAN_WARNING)));
assertThat(this.output.toString(), not(containsString(DEFAULT_SCAN_WARNING)));
}
@Test
public void noLogIfHasValue() throws Exception {
load(InDefaultPackageWithValueConfiguration.class);
assertThat(this.output.toString(), not(containsString(SCAN_WARNING)));
assertThat(this.output.toString(), not(containsString(DEFAULT_SCAN_WARNING)));
}
@Test
public void noLogIfHasBasePackages() throws Exception {
load(InDefaultPackageWithBasePackagesConfiguration.class);
assertThat(this.output.toString(), not(containsString(SCAN_WARNING)));
assertThat(this.output.toString(), not(containsString(DEFAULT_SCAN_WARNING)));
}
@Test
public void noLogIfHasBasePackageClasses() throws Exception {
load(InDefaultPackageWithBasePackageClassesConfiguration.class);
assertThat(this.output.toString(), not(containsString(SCAN_WARNING)));
assertThat(this.output.toString(), not(containsString(DEFAULT_SCAN_WARNING)));
}
@Test
public void logWarningInOrgSpringPackage() {
load(InOrgSpringPackageConfiguration.class);
assertThat(this.output.toString(), containsString(ORGSPRING_SCAN_WARNING));
}
@Test
public void logWarningIfScanningProblemPackages() throws Exception {
load(InRealButScanningProblemPackages.class);
assertThat(this.output.toString(),
containsString("Your ApplicationContext is unlikely to start due to a "
+ "@ComponentScan of the default package, 'org.springframework'."));
}
private void load(Class<?> configClass) {
@ -121,12 +145,25 @@ public class ConfigurationWarningsApplicationContextInitializerTests {
* Testable ComponentScanDefaultPackageCheck that doesn't need to use the default
* package.
*/
static class TestComponentScanDefaultPackageCheck
extends ComponentScanDefaultPackageCheck {
static class TestComponentScanDefaultPackageCheck extends ComponentScanPackageCheck {
@Override
protected boolean isInDefaultPackage(String className) {
return className.contains("InDefault");
protected Set<String> getComponentScanningPackages(
BeanDefinitionRegistry registry) {
Set<String> scannedPackages = super.getComponentScanningPackages(registry);
Set<String> result = new LinkedHashSet<String>();
for (String scannedPackage : scannedPackages) {
if (scannedPackage.endsWith("dflt")) {
result.add("");
}
if (scannedPackage.endsWith("orgspring")) {
result.add("org.springframework");
}
else {
result.add(scannedPackage);
}
}
return result;
}
}

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.context.configwarnings;
package org.springframework.boot.context.configwarnings.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.context.configwarnings;
package org.springframework.boot.context.configwarnings.dflt;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@ -14,9 +14,9 @@
* limitations under the License.
*/
package org.springframework.boot.context.configwarnings;
package org.springframework.boot.context.configwarnings.dflt;
import org.springframework.boot.context.configwarnings.nested.ExampleBean;
import org.springframework.boot.context.configwarnings.real.nested.ExampleBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.context.configwarnings;
package org.springframework.boot.context.configwarnings.dflt;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@ -14,8 +14,9 @@
* limitations under the License.
*/
package org.springframework.boot.context.configwarnings;
package org.springframework.boot.context.configwarnings.dflt;
import org.springframework.boot.context.configwarnings.annotation.MetaComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.context.configwarnings;
package org.springframework.boot.context.configwarnings.dflt;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.context.configwarnings;
package org.springframework.boot.context.configwarnings.dflt;
import org.springframework.context.annotation.Configuration;

@ -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.orgspring;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class InOrgSpringPackageConfiguration {
}

@ -0,0 +1,29 @@
/*
* Copyright 2012-2015 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.real;
import org.springframework.boot.context.configwarnings.dflt.InDefaultPackageConfiguration;
import org.springframework.boot.context.configwarnings.orgspring.InOrgSpringPackageConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackageClasses = { InDefaultPackageConfiguration.class,
InOrgSpringPackageConfiguration.class })
public class InRealButScanningProblemPackages {
}

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.context.configwarnings;
package org.springframework.boot.context.configwarnings.real;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.context.configwarnings.nested;
package org.springframework.boot.context.configwarnings.real.nested;
import org.springframework.stereotype.Component;
Loading…
Cancel
Save