|
17 | 17 | package org.springframework.boot.autoconfigure.interfaceclients;
|
18 | 18 |
|
19 | 19 | import java.lang.annotation.Annotation;
|
| 20 | +import java.text.Normalizer; |
20 | 21 | import java.util.HashSet;
|
21 | 22 | import java.util.List;
|
22 | 23 | import java.util.Set;
|
23 | 24 |
|
| 25 | +import org.apache.commons.logging.Log; |
| 26 | +import org.apache.commons.logging.LogFactory; |
| 27 | +import org.apache.commons.text.CaseUtils; |
| 28 | + |
24 | 29 | import org.springframework.beans.factory.ListableBeanFactory;
|
25 | 30 | import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
26 | 31 | import org.springframework.beans.factory.config.BeanDefinition;
|
| 32 | +import org.springframework.beans.factory.config.BeanDefinitionHolder; |
| 33 | +import org.springframework.beans.factory.support.AbstractBeanDefinition; |
| 34 | +import org.springframework.beans.factory.support.BeanDefinitionBuilder; |
| 35 | +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; |
| 36 | +import org.springframework.beans.factory.support.BeanDefinitionRegistry; |
| 37 | +import org.springframework.beans.factory.support.BeanNameGenerator; |
27 | 38 | import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
|
28 | 39 | import org.springframework.context.EnvironmentAware;
|
29 | 40 | import org.springframework.context.ResourceLoaderAware;
|
30 | 41 | import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
31 | 42 | import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
| 43 | +import org.springframework.core.annotation.MergedAnnotation; |
32 | 44 | import org.springframework.core.env.Environment;
|
33 | 45 | import org.springframework.core.io.ResourceLoader;
|
| 46 | +import org.springframework.core.type.AnnotationMetadata; |
34 | 47 | import org.springframework.core.type.filter.AnnotationTypeFilter;
|
| 48 | +import org.springframework.util.Assert; |
| 49 | +import org.springframework.util.ObjectUtils; |
35 | 50 |
|
36 | 51 | /**
|
37 | 52 | * @author Olga Maciaszek-Sharma
|
38 | 53 | */
|
| 54 | +// TODO: Handle AOT |
39 | 55 | // TODO: remove abstract supertype or move to a shared package
|
40 | 56 | public abstract class AbstractInterfaceClientsImportRegistrar
|
41 | 57 | implements ImportBeanDefinitionRegistrar, EnvironmentAware, ResourceLoaderAware {
|
42 | 58 |
|
| 59 | + // TODO: work on IntelliJ plugin /other plugins/ to show that the client beans are |
| 60 | + // autoconfigured |
| 61 | + private static final Log logger = LogFactory.getLog(AbstractInterfaceClientsImportRegistrar.class); |
| 62 | + |
| 63 | + private static final String INTERFACE_CLIENT_SUFFIX = "InterfaceClient"; |
| 64 | + |
| 65 | + private static final String BEAN_NAME_ATTRIBUTE_NAME = "beanName"; |
| 66 | + |
| 67 | + private static final String CLIENT_ID_ATTRIBUTE_NAME = "clientId"; |
| 68 | + |
| 69 | + private static final String BEAN_CLASS_ATTRIBUTE_NAME = "type"; |
| 70 | + |
43 | 71 | private Environment environment;
|
44 | 72 |
|
45 | 73 | private ResourceLoader resourceLoader;
|
46 | 74 |
|
| 75 | + @Override |
| 76 | + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, |
| 77 | + BeanNameGenerator importBeanNameGenerator) { |
| 78 | + Assert.isInstanceOf(ListableBeanFactory.class, registry, |
| 79 | + "Registry must be an instance of " + ListableBeanFactory.class.getSimpleName()); |
| 80 | + ListableBeanFactory beanFactory = (ListableBeanFactory) registry; |
| 81 | + Set<BeanDefinition> candidateComponents = discoverCandidateComponents(beanFactory); |
| 82 | + for (BeanDefinition candidateComponent : candidateComponents) { |
| 83 | + if (candidateComponent instanceof AnnotatedBeanDefinition beanDefinition) { |
| 84 | + registerInterfaceClient(registry, beanDefinition); |
| 85 | + } |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + private void registerInterfaceClient(BeanDefinitionRegistry registry, AnnotatedBeanDefinition beanDefinition) { |
| 90 | + AnnotationMetadata annotatedBeanMetadata = beanDefinition.getMetadata(); |
| 91 | + Assert.isTrue(annotatedBeanMetadata.isInterface(), |
| 92 | + getAnnotation().getSimpleName() + "can only be placed on an interface."); |
| 93 | + MergedAnnotation<? extends Annotation> annotation = annotatedBeanMetadata.getAnnotations().get(getAnnotation()); |
| 94 | + String beanClassName = annotatedBeanMetadata.getClassName(); |
| 95 | + // Class<?> beanClass; |
| 96 | + // try { |
| 97 | + // beanClass = Class.forName(beanClassName); |
| 98 | + // } |
| 99 | + // catch (ClassNotFoundException e) { |
| 100 | + // if (logger.isDebugEnabled()) { |
| 101 | + // logger.debug("Class not found for interface client " + beanClassName + ": " + |
| 102 | + // e.getMessage()); |
| 103 | + // } |
| 104 | + // throw new RuntimeException(e); |
| 105 | + // } |
| 106 | + // TODO: consider naming conventions: value of the annotation is the |
| 107 | + // qualifier to look for related beans |
| 108 | + // TODO: while the actual beanName corresponds to the simple class name |
| 109 | + // suffixed with InterfaceClient |
| 110 | + String clientId = annotation.getString(MergedAnnotation.VALUE); |
| 111 | + String beanName = !ObjectUtils.isEmpty(annotation.getString(BEAN_NAME_ATTRIBUTE_NAME)) |
| 112 | + ? annotation.getString(BEAN_NAME_ATTRIBUTE_NAME) : buildBeanName(clientId); |
| 113 | + BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(getFactoryBeanClass()); |
| 114 | + definitionBuilder.addPropertyValue(BEAN_NAME_ATTRIBUTE_NAME, beanName); |
| 115 | + definitionBuilder.addPropertyValue(CLIENT_ID_ATTRIBUTE_NAME, clientId); |
| 116 | + definitionBuilder.addPropertyValue(BEAN_CLASS_ATTRIBUTE_NAME, beanClassName); |
| 117 | + definitionBuilder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); |
| 118 | + AbstractBeanDefinition definition = definitionBuilder.getBeanDefinition(); |
| 119 | + BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, beanName, new String[] { clientId }); |
| 120 | + BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); |
| 121 | + } |
| 122 | + |
47 | 123 | protected Set<BeanDefinition> discoverCandidateComponents(ListableBeanFactory beanFactory) {
|
48 | 124 | Set<BeanDefinition> candidateComponents = new HashSet<>();
|
49 | 125 | ClassPathScanningCandidateComponentProvider scanner = getScanner();
|
@@ -83,4 +159,13 @@ public void setResourceLoader(ResourceLoader resourceLoader) {
|
83 | 159 |
|
84 | 160 | protected abstract Class<? extends Annotation> getAnnotation();
|
85 | 161 |
|
| 162 | + protected abstract Class<?> getFactoryBeanClass(); |
| 163 | + |
| 164 | + private String buildBeanName(String clientId) { |
| 165 | + // TODO: research Normalizer form types |
| 166 | + String normalised = Normalizer.normalize(clientId, Normalizer.Form.NFD); |
| 167 | + String camelCased = CaseUtils.toCamelCase(normalised, false, '-', '_'); |
| 168 | + return camelCased + INTERFACE_CLIENT_SUFFIX; |
| 169 | + } |
| 170 | + |
86 | 171 | }
|
0 commit comments