Skip to content

Commit

Permalink
Support @componentscan as a repeatable composed annotation
Browse files Browse the repository at this point in the history
This is a "proof of concept".

See spring-projectsgh-30941
  • Loading branch information
sbrannen committed Aug 12, 2023
1 parent a33b143 commit 6acf78e
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
* @author Chris Beams
* @author Phillip Webb
* @author Stephane Nicoll
* @author Sam Brannen
* @since 2.5
* @see ContextAnnotationAutowireCandidateResolver
* @see ConfigurationClassPostProcessor
Expand Down Expand Up @@ -288,7 +289,7 @@ static Set<AnnotationAttributes> attributesForRepeatable(AnnotationMetadata meta
Set<AnnotationAttributes> result = new LinkedHashSet<>();

// Direct annotation present or meta-present?
addAttributesIfNotNull(result, metadata.getAnnotationAttributes(annotationType.getName()));
addAttributes(result, metadata.getAllMergedAnnotationAttributes(annotationType.getName()));

// Container annotation present or meta-present?
Map<String, Object> container = metadata.getAnnotationAttributes(containerType.getName());
Expand All @@ -302,6 +303,14 @@ static Set<AnnotationAttributes> attributesForRepeatable(AnnotationMetadata meta
return Collections.unmodifiableSet(result);
}

private static void addAttributes(
Set<AnnotationAttributes> result, Set<? extends Map<String, Object>> attributesList) {

for (Map<String, Object> attributes : attributesList) {
result.add(AnnotationAttributes.fromMap(attributes));
}
}

private static void addAttributesIfNotNull(
Set<AnnotationAttributes> result, @Nullable Map<String, Object> attributes) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
package org.springframework.core.type;

import java.lang.annotation.Annotation;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotation.Adapt;
Expand Down Expand Up @@ -151,4 +154,47 @@ default MultiValueMap<String, Object> getAllAnnotationAttributes(
map -> (map.isEmpty() ? null : map), adaptations));
}

/**
* Retrieve all attributes of all annotations of the given type, if any (i.e. if
* defined on the underlying element, as direct annotation or meta-annotation).
* <p>Note: in contrast to {@link #getAllAnnotationAttributes(String)},
* this method <em>does</em> take attribute overrides on composed annotations
* into account.
* @param annotationName the fully qualified class name of the annotation
* type to look for
* @return a list of maps of attributes, with the attribute name as map key
* (e.g. "key") and the attribute value as map value; never {@code null} but
* potentially empty if no such annotations are found
* @since 6.1
* @see #getAllMergedAnnotationAttributes(String, boolean)
*/
default Set<? extends Map<String, Object>> getAllMergedAnnotationAttributes(String annotationName) {
return getAllMergedAnnotationAttributes(annotationName, false);
}

/**
* Retrieve all attributes of all annotations of the given type, if any (i.e. if
* defined on the underlying element, as direct annotation or meta-annotation).
* <p>Note: in contrast to {@link #getAllAnnotationAttributes(String, boolean)},
* this method <em>does</em> take attribute overrides on composed annotations
* into account.
* @param annotationName the fully qualified class name of the annotation
* type to look for
* @param classValuesAsString whether to convert class references to String
* @return a list of maps of attributes, with the attribute name as map key
* (e.g. "key") and the attribute value as map value; never {@code null} but
* potentially empty if no such annotations are found
* @since 6.1
* @see #getAllMergedAnnotationAttributes(String)
*/
default Set<? extends Map<String, Object>> getAllMergedAnnotationAttributes(
String annotationName, boolean classValuesAsString) {

Adapt[] adaptations = Adapt.values(classValuesAsString, true);
return getAnnotations().stream(annotationName)
.filter(MergedAnnotationPredicates.unique(MergedAnnotation::getMetaTypes))
.map(annotation -> annotation.asAnnotationAttributes(adaptations))
.collect(Collectors.toCollection(LinkedHashSet::new));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2002-2023 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
*
* https://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.test.gh30941;

import org.springframework.context.annotation.Configuration;

@Configuration
@ConnectedToModuleA
@ConnectedToModuleB
class AppConfig {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2002-2023 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
*
* https://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.test.gh30941;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.gh30941.a.ModuleAClient;
import org.springframework.test.gh30941.b.ModuleBClient;

import static org.assertj.core.api.Assertions.assertThat;

@SpringJUnitConfig(AppConfig.class)
class ComponentScanTests {

@Autowired
ModuleAClient clientA;

@Autowired
ModuleBClient clientB;

@Test
void test() {
assertThat(clientA).isNotNull();
assertThat(clientB).isNotNull();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2002-2023 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
*
* https://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.test.gh30941;

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;
import org.springframework.test.gh30941.a.ModuleAClient;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ComponentScan(basePackageClasses = ModuleAClient.class)
@interface ConnectedToModuleA {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2002-2023 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
*
* https://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.test.gh30941;

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;
import org.springframework.test.gh30941.b.ModuleBClient;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ComponentScan(basePackageClasses = ModuleBClient.class)
@interface ConnectedToModuleB {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2002-2023 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
*
* https://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.test.gh30941.a;

import org.springframework.stereotype.Component;

@Component
public class ModuleAClient {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2002-2023 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
*
* https://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.test.gh30941.b;

import org.springframework.stereotype.Component;

@Component
public class ModuleBClient {
}

0 comments on commit 6acf78e

Please sign in to comment.