Skip to content

Commit 7d3fcaa

Browse files
committed
Consider abstract classes with @lookup methods as candidate components
Issue: SPR-14550
1 parent ed40b1c commit 7d3fcaa

File tree

8 files changed

+164
-80
lines changed

8 files changed

+164
-80
lines changed

spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java

Lines changed: 66 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@
3030
import org.springframework.beans.factory.BeanDefinitionStoreException;
3131
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
3232
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
33+
import org.springframework.beans.factory.annotation.Lookup;
3334
import org.springframework.beans.factory.config.BeanDefinition;
3435
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
3536
import org.springframework.context.ResourceLoaderAware;
@@ -43,6 +44,7 @@
4344
import org.springframework.core.io.ResourceLoader;
4445
import org.springframework.core.io.support.ResourcePatternResolver;
4546
import org.springframework.core.io.support.ResourcePatternUtils;
47+
import org.springframework.core.type.AnnotationMetadata;
4648
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
4749
import org.springframework.core.type.classreading.MetadataReader;
4850
import org.springframework.core.type.classreading.MetadataReaderFactory;
@@ -294,7 +296,62 @@ public Set<BeanDefinition> findCandidateComponents(String basePackage) {
294296
}
295297
}
296298

297-
protected Set<BeanDefinition> addCandidateComponentsFromIndex(String basePackage) {
299+
/**
300+
* Determine if the index can be used by this instance.
301+
* @return {@code true} if the index is available and the configuration of this
302+
* instance is supported by it, {@code false} otherwise
303+
* @since 5.0
304+
*/
305+
protected boolean isIndexSupported() {
306+
if (this.componentsIndex == null) {
307+
return false;
308+
}
309+
for (TypeFilter includeFilter : this.includeFilters) {
310+
if (!isIndexSupportsIncludeFilter(includeFilter)) {
311+
return false;
312+
}
313+
}
314+
return true;
315+
}
316+
317+
/**
318+
* Determine if the specified include {@link TypeFilter} is supported by the index.
319+
* @param filter the filter to check
320+
* @return whether the index supports this include filter
321+
* @since 5.0
322+
* @see #extractStereotype(TypeFilter)
323+
*/
324+
protected boolean isIndexSupportsIncludeFilter(TypeFilter filter) {
325+
if (filter instanceof AnnotationTypeFilter) {
326+
Class<? extends Annotation> annotation = ((AnnotationTypeFilter) filter).getAnnotationType();
327+
return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotation) ||
328+
annotation.getName().startsWith("javax."));
329+
}
330+
if (filter instanceof AssignableTypeFilter) {
331+
Class<?> target = ((AssignableTypeFilter) filter).getTargetType();
332+
return AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, target);
333+
}
334+
return false;
335+
}
336+
337+
/**
338+
* Extract the stereotype to use for the specified compatible filter.
339+
* @param filter the filter to handle
340+
* @return the stereotype in the index matching this filter
341+
* @since 5.0
342+
* @see #isIndexSupportsIncludeFilter(TypeFilter)
343+
*/
344+
protected String extractStereotype(TypeFilter filter) {
345+
if (filter instanceof AnnotationTypeFilter) {
346+
return ((AnnotationTypeFilter) filter).getAnnotationType().getName();
347+
}
348+
if (filter instanceof AssignableTypeFilter) {
349+
return ((AssignableTypeFilter) filter).getTargetType().getName();
350+
}
351+
return null;
352+
}
353+
354+
private Set<BeanDefinition> addCandidateComponentsFromIndex(String basePackage) {
298355
Set<BeanDefinition> candidates = new LinkedHashSet<>();
299356
try {
300357
Set<String> types = new HashSet<>();
@@ -337,7 +394,7 @@ protected Set<BeanDefinition> addCandidateComponentsFromIndex(String basePackage
337394
return candidates;
338395
}
339396

340-
protected Set<BeanDefinition> scanCandidateComponents(String basePackage) {
397+
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
341398
Set<BeanDefinition> candidates = new LinkedHashSet<>();
342399
try {
343400
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
@@ -440,66 +497,18 @@ private boolean isConditionMatch(MetadataReader metadataReader) {
440497

441498
/**
442499
* Determine whether the given bean definition qualifies as candidate.
443-
* <p>The default implementation checks whether the class is concrete
444-
* (i.e. not abstract and not an interface). Can be overridden in subclasses.
500+
* <p>The default implementation checks whether the class is not an interface
501+
* and not dependent on an enclosing class.
502+
* <p>Can be overridden in subclasses.
445503
* @param beanDefinition the bean definition to check
446504
* @return whether the bean definition qualifies as a candidate component
447505
*/
448506
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
449-
return (beanDefinition.getMetadata().isConcrete() && beanDefinition.getMetadata().isIndependent());
507+
AnnotationMetadata metadata = beanDefinition.getMetadata();
508+
return (metadata.isIndependent() && (metadata.isConcrete() ||
509+
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
450510
}
451511

452-
/**
453-
* Determine if the index can be used by this instance.
454-
* @return {@code true} if the index is available and the configuration of this
455-
* instance is supported by it, {@code false otherwise}.
456-
*/
457-
protected boolean isIndexSupported() {
458-
if (this.componentsIndex == null) {
459-
return false;
460-
}
461-
for (TypeFilter includeFilter : this.includeFilters) {
462-
if (!isIndexSupportsIncludeFilter(includeFilter)) {
463-
return false;
464-
}
465-
}
466-
return true;
467-
}
468-
469-
/**
470-
* Determine if the specified include {@link TypeFilter} is supported by the index.
471-
* @param filter the filter to check
472-
* @return whether the index supports this include filter
473-
* @see #extractStereotype(TypeFilter)
474-
*/
475-
protected boolean isIndexSupportsIncludeFilter(TypeFilter filter) {
476-
if (filter instanceof AnnotationTypeFilter) {
477-
Class<? extends Annotation> annotation = ((AnnotationTypeFilter) filter).getAnnotationType();
478-
return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotation)
479-
|| annotation.getName().startsWith("javax."));
480-
}
481-
if (filter instanceof AssignableTypeFilter) {
482-
Class<?> target = ((AssignableTypeFilter) filter).getTargetType();
483-
return AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, target);
484-
}
485-
return false;
486-
}
487-
488-
/**
489-
* Extract the stereotype to use for the specified compatible filter.
490-
* @param filter the filter to handle
491-
* @return the stereotype in the index matching this filter
492-
* @see #isIndexSupportsIncludeFilter(TypeFilter)
493-
*/
494-
protected String extractStereotype(TypeFilter filter) {
495-
if (filter instanceof AnnotationTypeFilter) {
496-
return ((AnnotationTypeFilter) filter).getAnnotationType().getName();
497-
}
498-
if (filter instanceof AssignableTypeFilter) {
499-
return ((AssignableTypeFilter) filter).getTargetType().getName();
500-
}
501-
return null;
502-
}
503512

504513
/**
505514
* Clear the local metadata cache, if any, removing all cached class metadata.

spring-context/src/test/java/example/profilescan/DevComponent.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2010 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@
3030
@Component
3131
public @interface DevComponent {
3232

33-
public static final String PROFILE_NAME = "dev";
33+
String PROFILE_NAME = "dev";
3434

3535
String value() default "";
3636

spring-context/src/test/java/example/profilescan/ProfileAnnotatedComponent.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
/*
2+
* Copyright 2002-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package example.profilescan;
218

319
import org.springframework.context.annotation.Profile;
@@ -8,6 +24,7 @@
824
public class ProfileAnnotatedComponent {
925

1026
public static final String BEAN_NAME = "profileAnnotatedComponent";
27+
1128
public static final String PROFILE_NAME = "test";
1229

1330
}

spring-context/src/test/java/example/profilescan/ProfileMetaAnnotatedComponent.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2010 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,10 +16,9 @@
1616

1717
package example.profilescan;
1818

19-
2019
@DevComponent(ProfileMetaAnnotatedComponent.BEAN_NAME)
2120
public class ProfileMetaAnnotatedComponent {
2221

2322
public static final String BEAN_NAME = "profileMetaAnnotatedComponent";
2423

25-
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2002-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package example.profilescan;
18+
19+
import org.springframework.stereotype.Component;
20+
21+
@Component
22+
public abstract class SomeAbstractClass {
23+
24+
}

spring-context/src/test/java/example/scannable/FooServiceImpl.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
2424
import org.springframework.beans.factory.BeanFactory;
2525
import org.springframework.beans.factory.ListableBeanFactory;
2626
import org.springframework.beans.factory.annotation.Autowired;
27+
import org.springframework.beans.factory.annotation.Lookup;
2728
import org.springframework.context.ApplicationContext;
2829
import org.springframework.context.ApplicationEventPublisher;
2930
import org.springframework.context.ConfigurableApplicationContext;
@@ -42,7 +43,7 @@
4243
* @author Juergen Hoeller
4344
*/
4445
@Service @Lazy @DependsOn("myNamedComponent")
45-
public class FooServiceImpl implements FooService {
46+
public abstract class FooServiceImpl implements FooService {
4647

4748
// Just to test ASM5's bytecode parsing of INVOKESPECIAL/STATIC on interfaces
4849
private static final Comparator<MessageBean> COMPARATOR_BY_MESSAGE = Comparator.comparing(MessageBean::getMessage);
@@ -84,16 +85,24 @@ public String foo(int id) {
8485
return this.fooDao.findFoo(id);
8586
}
8687

88+
public String lookupFoo(int id) {
89+
return fooDao().findFoo(id);
90+
}
91+
8792
@Override
8893
public Future<String> asyncFoo(int id) {
8994
System.out.println(Thread.currentThread().getName());
9095
Assert.state(ServiceInvocationCounter.getThreadLocalCount() != null, "Thread-local counter not exposed");
91-
return new AsyncResult<>(this.fooDao.findFoo(id));
96+
return new AsyncResult<>(fooDao().findFoo(id));
9297
}
9398

9499
@Override
95100
public boolean isInitCalled() {
96101
return this.initCalled;
97102
}
98103

104+
105+
@Lookup
106+
protected abstract FooDao fooDao();
107+
99108
}

0 commit comments

Comments
 (0)