diff --git a/dubbo-common/src/main/java/com/alibaba/dubbo/config/annotation/Service.java b/dubbo-common/src/main/java/com/alibaba/dubbo/config/annotation/Service.java index 564575b0d1..5de941fa33 100644 --- a/dubbo-common/src/main/java/com/alibaba/dubbo/config/annotation/Service.java +++ b/dubbo-common/src/main/java/com/alibaba/dubbo/config/annotation/Service.java @@ -35,7 +35,7 @@ import java.lang.annotation.Target; @Deprecated @Documented @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) +@Target({ElementType.TYPE, ElementType.METHOD}) @Inherited public @interface Service { diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/AnnotationUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/AnnotationUtils.java index 8da5f1d05d..ba2da5b9fa 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/AnnotationUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/AnnotationUtils.java @@ -25,9 +25,11 @@ import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.Predicate; @@ -479,4 +481,56 @@ public interface AnnotationUtils { Method method = findMethod(annotationType, attributeName); return (T) (method == null ? null : method.getDefaultValue()); } + + /** + * Filter default value of Annotation type + * @param annotationType annotation type from {@link Annotation#annotationType()} + * @param attributes + * @return + */ + static Map filterDefaultValues(Class annotationType, Map attributes) { + Map filteredAttributes = new LinkedHashMap<>(attributes.size()); + attributes.forEach((key,val) -> { + if (!Objects.deepEquals(val, getDefaultValue(annotationType, key))) { + filteredAttributes.put(key, val); + } + }); + return filteredAttributes; + } + + /** + * Filter default value of Annotation type + * @param annotation + * @param attributes + * @return + */ + static Map filterDefaultValues(Annotation annotation, Map attributes) { + return filterDefaultValues(annotation.annotationType(), attributes); + } + + /** + * Get attributes of annotation + * @param annotation + * @return + */ + static Map getAttributes(Annotation annotation, boolean filterDefaultValue) { + Class annotationType = annotation.annotationType(); + Method[] methods = annotationType.getMethods(); + Map attributes = new LinkedHashMap<>(methods.length); + for (Method method : methods) { + try { + if (method.getDeclaringClass() == Annotation.class) { + continue; + } + String name = method.getName(); + Object value = method.invoke(annotation); + if (!filterDefaultValue || !Objects.deepEquals(value, method.getDefaultValue())) { + attributes.put(name, value); + } + } catch (Exception e) { + throw new IllegalStateException("get attribute value of annotation failed: " + method, e); + } + } + return attributes; + } } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java index 7d9007f539..5130e0e06a 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java @@ -86,7 +86,7 @@ public abstract class AbstractConfig implements Serializable { protected final AtomicBoolean refreshed = new AtomicBoolean(false); /** - * Is default or not + * Is default config or not */ protected Boolean isDefault; @@ -606,6 +606,36 @@ public abstract class AbstractConfig implements Serializable { return refreshed.get(); } + /** + * FIXME check @Parameter(required=true) and any conditions that need to match. + */ + @Parameter(excluded = true, attribute = false) + public boolean isValid() { + return true; + } + + @Parameter(excluded = true, attribute = false) + public Boolean isDefault() { + return isDefault; + } + + public void setDefault(Boolean isDefault) { + this.isDefault = isDefault; + } + + /** + * Add {@link AbstractConfig instance} into {@link ConfigManager} + *

+ * Current method will invoked by Spring or Java EE container automatically, or should be triggered manually. + * + * @see ConfigManager#addConfig(AbstractConfig) + * @since 2.7.5 + */ + @PostConstruct + public void addIntoConfigManager() { + ApplicationModel.getConfigManager().addConfig(this); + } + @Override public String toString() { try { @@ -658,15 +688,6 @@ public abstract class AbstractConfig implements Serializable { return fieldNamesCache.computeIfAbsent(configClass, ReflectUtils::getAllFieldNames); } - /** - * FIXME check @Parameter(required=true) and any conditions that need to match. - */ - @Parameter(excluded = true, attribute = false) - public boolean isValid() { - return true; - } - - @Override public boolean equals(Object obj) { if (obj == null || obj.getClass() != this.getClass()) { @@ -708,19 +729,6 @@ public abstract class AbstractConfig implements Serializable { return true; } - /** - * Add {@link AbstractConfig instance} into {@link ConfigManager} - *

- * Current method will invoked by Spring or Java EE container automatically, or should be triggered manually. - * - * @see ConfigManager#addConfig(AbstractConfig) - * @since 2.7.5 - */ - @PostConstruct - public void addIntoConfigManager() { - ApplicationModel.getConfigManager().addConfig(this); - } - @Override public int hashCode() { int hashCode = 1; @@ -748,11 +756,4 @@ public abstract class AbstractConfig implements Serializable { return hashCode; } - public Boolean isDefault() { - return isDefault; - } - - public void setDefault(Boolean isDefault) { - this.isDefault = isDefault; - } } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/ProviderConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/ProviderConfig.java index 62eb377086..9e0a2ddc83 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/ProviderConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/ProviderConfig.java @@ -159,12 +159,6 @@ public class ProviderConfig extends AbstractServiceConfig { this.protocols = new ArrayList<>(Arrays.asList(new ProtocolConfig(protocol))); } - @Override - @Parameter(excluded = true) - public Boolean isDefault() { - return isDefault; - } - @Parameter(excluded = true) public String getHost() { return host; diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/annotation/DubboService.java b/dubbo-common/src/main/java/org/apache/dubbo/config/annotation/DubboService.java index ebeb2ea211..ba9fc3f1ae 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/annotation/DubboService.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/annotation/DubboService.java @@ -27,13 +27,40 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Class-level annotation used for declaring Dubbo service + * Class-level annotation used for declaring Dubbo service. + *

+ * 1. Using with java config bean: + *

+ * This usage is recommended.
+ * It is more flexible on bean methods than on implementation classes, and is more compatible with Spring. + *

+ * @Configuration
+ * class ProviderConfiguration {
+ *
+ *     @Bean
+ *     @DubboService(group="demo")
+ *     public DemoService demoServiceImpl() {
+ *         return new DemoServiceImpl();
+ *     }
+ * }
+ * 
+ * + * 2. Using on implementation class of service: + *
+ * @DubboService(group="demo")
+ * public class DemoServiceImpl implements DemoService {
+ *     ...
+ * }
+ * 
+ * + * This usage causes the implementation class to rely on the Dubbo module. + * * * @since 2.7.7 */ @Documented @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) +@Target({ElementType.TYPE, ElementType.METHOD}) @Inherited public @interface DubboService { diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/annotation/Service.java b/dubbo-common/src/main/java/org/apache/dubbo/config/annotation/Service.java index 1a02bf1b7d..e712e4ab2d 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/annotation/Service.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/annotation/Service.java @@ -33,7 +33,7 @@ import java.lang.annotation.Target; */ @Documented @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) +@Target({ElementType.TYPE, ElementType.METHOD}) @Inherited @Deprecated public @interface Service { diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java b/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java index b9ce3457b1..2ac5969ee8 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java @@ -676,16 +676,11 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt { } static Boolean isDefaultConfig(C config) { - // If no isDefault() method, it is considered that each can be used as default - if (!ReflectUtils.hasMethod(config.getClass(), "isDefault")) { - return true; - } - Boolean isDefault = ReflectUtils.getProperty(config, "isDefault"); - return isDefault; + return config.isDefault(); } static List getDefaultConfigs(Map configsMap) { - // find isDefault() == TRUE + // find isDefault() == true List list = configsMap.values() .stream() .filter(c -> TRUE.equals(ConfigManager.isDefaultConfig(c))) @@ -700,6 +695,8 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt { .filter(c -> ConfigManager.isDefaultConfig(c) == null) .collect(Collectors.toList()); return list; + + // exclude isDefault() == false } } diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/AnnotationUtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/AnnotationUtilsTest.java index 86dff9679c..7c4346557d 100644 --- a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/AnnotationUtilsTest.java +++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/AnnotationUtilsTest.java @@ -31,9 +31,11 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.LinkedList; import java.util.List; +import java.util.Map; import static java.util.Arrays.asList; import static org.apache.dubbo.common.utils.AnnotationUtils.excludedType; +import static org.apache.dubbo.common.utils.AnnotationUtils.filterDefaultValues; import static org.apache.dubbo.common.utils.AnnotationUtils.findAnnotation; import static org.apache.dubbo.common.utils.AnnotationUtils.findMetaAnnotation; import static org.apache.dubbo.common.utils.AnnotationUtils.findMetaAnnotations; @@ -41,7 +43,9 @@ import static org.apache.dubbo.common.utils.AnnotationUtils.getAllDeclaredAnnota import static org.apache.dubbo.common.utils.AnnotationUtils.getAllMetaAnnotations; import static org.apache.dubbo.common.utils.AnnotationUtils.getAnnotation; import static org.apache.dubbo.common.utils.AnnotationUtils.getAttribute; +import static org.apache.dubbo.common.utils.AnnotationUtils.getAttributes; import static org.apache.dubbo.common.utils.AnnotationUtils.getDeclaredAnnotations; +import static org.apache.dubbo.common.utils.AnnotationUtils.getDefaultValue; import static org.apache.dubbo.common.utils.AnnotationUtils.getMetaAnnotations; import static org.apache.dubbo.common.utils.AnnotationUtils.getValue; import static org.apache.dubbo.common.utils.AnnotationUtils.isAnnotationPresent; @@ -100,6 +104,26 @@ public class AnnotationUtilsTest { assertEquals(false, getAttribute(annotation, "deprecated")); } + @Test + public void testGetAttributesMap() { + Annotation annotation = A.class.getAnnotation(Service.class); + Map attributes = getAttributes(annotation, false); + assertEquals("java.lang.CharSequence", attributes.get("interfaceName")); + assertEquals(CharSequence.class, attributes.get("interfaceClass")); + assertEquals("", attributes.get("group")); + assertEquals(getDefaultValue(annotation, "export"), attributes.get("export")); + + Map filteredAttributes = filterDefaultValues(annotation, attributes); + assertEquals(2, filteredAttributes.size()); + assertEquals("java.lang.CharSequence", filteredAttributes.get("interfaceName")); + assertEquals(CharSequence.class, filteredAttributes.get("interfaceClass")); + assertFalse(filteredAttributes.containsKey("group")); + assertFalse(filteredAttributes.containsKey("export")); + + Map nonDefaultAttributes = getAttributes(annotation, true); + assertEquals(nonDefaultAttributes, filteredAttributes); + } + @Test public void testGetValue() { Adaptive adaptive = A.class.getAnnotation(Adaptive.class); diff --git a/dubbo-config/dubbo-config-spring/pom.xml b/dubbo-config/dubbo-config-spring/pom.xml index 08ca2a9c4f..2414579166 100644 --- a/dubbo-config/dubbo-config-spring/pom.xml +++ b/dubbo-config/dubbo-config-spring/pom.xml @@ -28,6 +28,8 @@ false 2.3.1.RELEASE + + diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/Constants.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/Constants.java index d78285e974..eec610059a 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/Constants.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/Constants.java @@ -31,4 +31,15 @@ public interface Constants { */ String REFERENCE_SOURCES = "referenceSources"; + /** + * The name of an attribute that can be + * {@link org.springframework.core.AttributeAccessor#setAttribute set} on a + * {@link org.springframework.beans.factory.config.BeanDefinition} so that + * factory beans can signal their object type when it can't be deduced from + * the factory bean class. + *

+ * From FactoryBean.OBJECT_TYPE_ATTRIBUTE of Spring 5.2. + */ + String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType"; + } diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/ServiceBean.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/ServiceBean.java index 52d45b0d19..b596568c8c 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/ServiceBean.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/ServiceBean.java @@ -100,7 +100,7 @@ public class ServiceBean extends ServiceConfig implements InitializingBean * @return {@link ServiceBean}'s name * @since 2.6.5 */ - @Parameter(excluded = true) + @Parameter(excluded = true, attribute = false) public String getBeanName() { return this.beanName; } diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/AbstractAnnotationBeanPostProcessor.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/AbstractAnnotationBeanPostProcessor.java index 07712440cf..27caae2930 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/AbstractAnnotationBeanPostProcessor.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/AbstractAnnotationBeanPostProcessor.java @@ -241,7 +241,7 @@ public abstract class AbstractAnnotationBeanPostProcessor extends private AbstractAnnotationBeanPostProcessor.AnnotatedInjectionMetadata buildAnnotatedMetadata(final Class beanClass) { Collection fieldElements = findFieldAnnotationMetadata(beanClass); Collection methodElements = findAnnotatedMethodMetadata(beanClass); - return new AbstractAnnotationBeanPostProcessor.AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements); + return new AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements); } protected AnnotatedInjectionMetadata findInjectionMetadata(String beanName, Class clazz, PropertyValues pvs) { @@ -249,11 +249,11 @@ public abstract class AbstractAnnotationBeanPostProcessor extends String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName()); // Quick check on the concurrent map first, with minimal locking. AbstractAnnotationBeanPostProcessor.AnnotatedInjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey); - if (InjectionMetadata.needsRefresh(metadata, clazz)) { + if (needsRefreshInjectionMetadata(metadata, clazz)) { synchronized (this.injectionMetadataCache) { metadata = this.injectionMetadataCache.get(cacheKey); - if (InjectionMetadata.needsRefresh(metadata, clazz)) { + if (needsRefreshInjectionMetadata(metadata, clazz)) { if (metadata != null) { metadata.clear(pvs); } @@ -270,6 +270,11 @@ public abstract class AbstractAnnotationBeanPostProcessor extends return metadata; } + // Use custom check method to compatible with Spring 4.x + private boolean needsRefreshInjectionMetadata(AnnotatedInjectionMetadata metadata, Class clazz) { + return (metadata == null || metadata.needsRefresh(clazz)); + } + @Override public int getOrder() { return order; @@ -402,7 +407,7 @@ public abstract class AbstractAnnotationBeanPostProcessor extends /** * {@link Annotation Annotated} {@link InjectionMetadata} implementation */ - protected class AnnotatedInjectionMetadata extends InjectionMetadata { + protected static class AnnotatedInjectionMetadata extends InjectionMetadata { private Class targetClass; private final Collection fieldElements; @@ -425,7 +430,7 @@ public abstract class AbstractAnnotationBeanPostProcessor extends return methodElements; } - @Override + //@Override // since Spring 5.2.4 protected boolean needsRefresh(Class clazz) { if (this.targetClass == clazz) { return false; diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java index b4231f3ee6..2cb397fef4 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java @@ -34,7 +34,6 @@ import org.springframework.beans.BeansException; import org.springframework.beans.PropertyValue; import org.springframework.beans.PropertyValues; import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.InjectionMetadata; import org.springframework.beans.factory.config.BeanDefinition; @@ -47,7 +46,6 @@ import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.type.MethodMetadata; import java.beans.PropertyDescriptor; @@ -63,6 +61,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import static com.alibaba.spring.util.AnnotationUtils.getAttribute; +import static org.apache.dubbo.common.utils.AnnotationUtils.filterDefaultValues; import static org.springframework.util.StringUtils.hasText; /** @@ -193,8 +192,8 @@ public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBean // Extract beanClass from generic return type of java-config bean method: ReferenceBean // see org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryBeanFromMethod Class beanClass = getBeanFactory().getType(beanName); + MethodMetadata factoryMethodMetadata = beanDefinition.getFactoryMethodMetadata(); if (beanClass == null) { - MethodMetadata factoryMethodMetadata = beanDefinition.getFactoryMethodMetadata(); String beanMethodSignature = factoryMethodMetadata.getDeclaringClassName()+"#"+factoryMethodMetadata.getMethodName()+"()"; throw new BeanCreationException("The ReferenceBean is missing necessary generic type, which returned by the @Bean method of Java-config class. " + "The generic type of the returned ReferenceBean must be specified as the referenced interface type, " + @@ -203,14 +202,14 @@ public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBean // get dubbo reference annotation attributes Map annotationAttributes = null; - MergedAnnotations mergedAnnotations = beanDefinition.getFactoryMethodMetadata().getAnnotations(); - Class referenceAnnotationType = null; // try all dubbo reference annotation types for (Class annotationType : getAnnotationTypes()) { - if (mergedAnnotations.isPresent(annotationType)) { - referenceAnnotationType = annotationType; - annotationAttributes = mergedAnnotations.get(annotationType).filterDefaultValues().asMap(); - break; + if (factoryMethodMetadata.isAnnotated(annotationType.getName())) { + // Since Spring 5.2 + // return factoryMethodMetadata.getAnnotations().get(annotationType).filterDefaultValues().asMap(); + // Compatible with Spring 4.x + annotationAttributes = factoryMethodMetadata.getAnnotationAttributes(annotationType.getName()); + annotationAttributes = filterDefaultValues(annotationType, annotationAttributes); } } @@ -226,7 +225,6 @@ public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBean String interfaceName = (String) attributes.get(ReferenceAttributes.INTERFACE); // check beanClass and reference interface class if (!StringUtils.isEquals(interfaceName, beanClass.getName()) && beanClass != GenericService.class) { - MethodMetadata factoryMethodMetadata = beanDefinition.getFactoryMethodMetadata(); String beanMethodSignature = factoryMethodMetadata.getDeclaringClassName()+"#"+factoryMethodMetadata.getMethodName()+"()"; throw new BeanCreationException("The 'interfaceClass' or 'interfaceName' attribute value of @DubboReference annotation " + "is inconsistent with the generic type of the ReferenceBean returned by the bean method. " + @@ -468,7 +466,7 @@ public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBean beanDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, id+"_decorated")); // signal object type since Spring 5.2 - beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, interfaceClass); + beanDefinition.setAttribute(Constants.OBJECT_TYPE_ATTRIBUTE, interfaceClass); beanDefinitionRegistry.registerBeanDefinition(referenceBeanName, beanDefinition); logger.info("Register dubbo reference bean: "+referenceBeanName+" = "+referenceKey+" at "+member); diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java index 79031cbca7..89ee841705 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java @@ -16,22 +16,25 @@ */ package org.apache.dubbo.config.spring.beans.factory.annotation; +import com.alibaba.spring.util.AnnotationUtils; import org.apache.dubbo.common.logger.Logger; import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.common.utils.ArrayUtils; import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.config.Constants; import org.apache.dubbo.config.MethodConfig; import org.apache.dubbo.config.annotation.DubboService; import org.apache.dubbo.config.annotation.Method; import org.apache.dubbo.config.annotation.Service; import org.apache.dubbo.config.spring.ServiceBean; -import org.apache.dubbo.config.spring.context.DubboBootstrapApplicationListener; import org.apache.dubbo.config.spring.context.annotation.DubboClassPathBeanDefinitionScanner; import org.apache.dubbo.config.spring.schema.AnnotationBeanDefinitionParser; import org.springframework.beans.BeansException; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; @@ -43,49 +46,59 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.AnnotationBeanNameGenerator; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; import org.springframework.context.annotation.ConfigurationClassPostProcessor; -import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.MethodMetadata; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.core.type.filter.TypeFilter; import org.springframework.util.CollectionUtils; +import java.io.IOException; import java.lang.annotation.Annotation; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import static com.alibaba.spring.util.BeanRegistrar.registerInfrastructureBean; import static com.alibaba.spring.util.ObjectUtils.of; import static java.util.Arrays.asList; +import static org.apache.dubbo.common.utils.AnnotationUtils.filterDefaultValues; import static org.apache.dubbo.config.spring.beans.factory.annotation.ServiceBeanNameBuilder.create; import static org.apache.dubbo.config.spring.util.DubboAnnotationUtils.resolveServiceInterfaceClass; import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition; import static org.springframework.context.annotation.AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR; import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation; -import static org.springframework.core.annotation.AnnotationUtils.getAnnotationAttributes; import static org.springframework.util.ClassUtils.resolveClassName; /** - * {@link BeanFactoryPostProcessor} used for processing of {@link Service @Service} annotated classes. it's also the - * infrastructure class of XML {@link BeanDefinitionParser} on <dubbbo:annotation /> + * A {@link BeanFactoryPostProcessor} used for processing of {@link Service @Service} annotated classes and annotated bean in java config classes. + * It's also the infrastructure class of XML {@link BeanDefinitionParser} on <dubbbo:annotation /> + * * * @see AnnotationBeanDefinitionParser * @see BeanDefinitionRegistryPostProcessor * @since 2.7.7 */ public class ServiceAnnotationPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware, - ResourceLoaderAware, BeanClassLoaderAware { + ResourceLoaderAware, BeanClassLoaderAware, ApplicationContextAware { + + public static final String BEAN_NAME = "dubboServiceAnnotationPostProcessor"; private final static List> serviceAnnotationTypes = asList( // @since 2.7.7 Add the @DubboService , the issue : https://github.com/apache/dubbo/issues/6007 @@ -107,6 +120,11 @@ public class ServiceAnnotationPostProcessor implements BeanDefinitionRegistryPos private ClassLoader classLoader; + private BeanDefinitionRegistry registry; + + private ServicePackagesHolder servicePackagesHolder; + + public ServiceAnnotationPostProcessor(String... packagesToScan) { this(asList(packagesToScan)); } @@ -121,14 +139,12 @@ public class ServiceAnnotationPostProcessor implements BeanDefinitionRegistryPos @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { - - // @since 2.7.5 - registerInfrastructureBean(registry, DubboBootstrapApplicationListener.BEAN_NAME, DubboBootstrapApplicationListener.class); + this.registry = registry; Set resolvedPackagesToScan = resolvePackagesToScan(packagesToScan); if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) { - registerServiceBeans(resolvedPackagesToScan, registry); + scanServiceBeans(resolvedPackagesToScan, registry); } else { if (logger.isWarnEnabled()) { logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!"); @@ -138,27 +154,35 @@ public class ServiceAnnotationPostProcessor implements BeanDefinitionRegistryPos } /** - * Registers Beans whose classes was annotated {@link Service} + * Scan and registers service beans whose classes was annotated {@link Service} * * @param packagesToScan The base packages to scan * @param registry {@link BeanDefinitionRegistry} */ - private void registerServiceBeans(Set packagesToScan, BeanDefinitionRegistry registry) { + private void scanServiceBeans(Set packagesToScan, BeanDefinitionRegistry registry) { DubboClassPathBeanDefinitionScanner scanner = new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader); BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry); - scanner.setBeanNameGenerator(beanNameGenerator); - - // refactor @since 2.7.7 - serviceAnnotationTypes.forEach(annotationType -> { + for (Class annotationType : serviceAnnotationTypes) { scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType)); - }); + } + + ScanExcludeFilter scanExcludeFilter = new ScanExcludeFilter(); + scanner.addExcludeFilter(scanExcludeFilter); for (String packageToScan : packagesToScan) { + // avoid duplicated scans + if (servicePackagesHolder.isPackageScanned(packageToScan)) { + if (logger.isInfoEnabled()) { + logger.info("Ignore package who has already bean scanned: " + packageToScan); + } + continue; + } + // Registers @Service Bean first scanner.scan(packageToScan); @@ -167,28 +191,27 @@ public class ServiceAnnotationPostProcessor implements BeanDefinitionRegistryPos findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator); if (!CollectionUtils.isEmpty(beanDefinitionHolders)) { + if (logger.isInfoEnabled()) { + List serviceClasses = new ArrayList<>(beanDefinitionHolders.size()); + for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) { + serviceClasses.add(beanDefinitionHolder.getBeanDefinition().getBeanClassName()); + } + logger.info("Found " + beanDefinitionHolders.size() + " classes annotated by Dubbo @Service under package [" + packageToScan + "]: " + serviceClasses); + } for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) { - registerServiceBean(beanDefinitionHolder, registry, scanner); + processScannedBeanDefinition(beanDefinitionHolder, registry, scanner); + servicePackagesHolder.addScannedClass(beanDefinitionHolder.getBeanDefinition().getBeanClassName()); } - - if (logger.isInfoEnabled()) { - logger.info(beanDefinitionHolders.size() + " annotated Dubbo's @Service Components { " + - beanDefinitionHolders + - " } were scanned under package[" + packageToScan + "]"); - } - } else { - if (logger.isWarnEnabled()) { - logger.warn("No Spring Bean annotating Dubbo's @Service was found under package[" - + packageToScan + "]"); + logger.warn("No class annotated by Dubbo @Service was found under package [" + + packageToScan + "], ignore re-scanned classes: " + scanExcludeFilter.getExcludedCount()); } - } + servicePackagesHolder.addScannedPackage(packageToScan); } - } /** @@ -270,17 +293,15 @@ public class ServiceAnnotationPostProcessor implements BeanDefinitionRegistryPos * @see ServiceBean * @see BeanDefinition */ - private void registerServiceBean(BeanDefinitionHolder beanDefinitionHolder, BeanDefinitionRegistry registry, - DubboClassPathBeanDefinitionScanner scanner) { + private void processScannedBeanDefinition(BeanDefinitionHolder beanDefinitionHolder, BeanDefinitionRegistry registry, + DubboClassPathBeanDefinitionScanner scanner) { Class beanClass = resolveClass(beanDefinitionHolder); Annotation service = findServiceAnnotation(beanClass); - /** - * The {@link AnnotationAttributes} of @Service annotation - */ - AnnotationAttributes serviceAnnotationAttributes = getAnnotationAttributes(service, false, false); + // The attributes of @Service annotation + Map serviceAnnotationAttributes = AnnotationUtils.getAttributes(service, true); Class interfaceClass = resolveServiceInterfaceClass(serviceAnnotationAttributes, beanClass); @@ -290,26 +311,9 @@ public class ServiceAnnotationPostProcessor implements BeanDefinitionRegistryPos String beanName = generateServiceBeanName(serviceAnnotationAttributes, interfaceClass); AbstractBeanDefinition serviceBeanDefinition = - buildServiceBeanDefinition(beanName, service, serviceAnnotationAttributes, interfaceClass, annotatedServiceBeanName); + buildServiceBeanDefinition(serviceAnnotationAttributes, interfaceClass, annotatedServiceBeanName); - if (scanner.checkCandidate(beanName, serviceBeanDefinition)) { // check duplicated candidate bean - registry.registerBeanDefinition(beanName, serviceBeanDefinition); - - if (logger.isInfoEnabled()) { - logger.info("The BeanDefinition[" + serviceBeanDefinition + - "] of ServiceBean has been registered with name : " + beanName); - } - - } else { - - //TODO throw exception - if (logger.isWarnEnabled()) { - logger.warn("The Duplicated BeanDefinition[" + serviceBeanDefinition + - "] of ServiceBean[ bean name : " + beanName + - "] was be found , Did @DubboComponentScan scan to same package in many times?"); - } - - } + registerServiceBeanDefinition(beanName, serviceBeanDefinition, interfaceClass); } @@ -337,10 +341,10 @@ public class ServiceAnnotationPostProcessor implements BeanDefinitionRegistryPos * @return ServiceBean@interfaceClassName#annotatedServiceBeanName * @since 2.7.3 */ - private String generateServiceBeanName(AnnotationAttributes serviceAnnotationAttributes, Class interfaceClass) { + private String generateServiceBeanName(Map serviceAnnotationAttributes, Class interfaceClass) { ServiceBeanNameBuilder builder = create(interfaceClass, environment) - .group(serviceAnnotationAttributes.getString("group")) - .version(serviceAnnotationAttributes.getString("version")); + .group((String) serviceAnnotationAttributes.get("group")) + .version((String) serviceAnnotationAttributes.get("version")); return builder.build(); } @@ -375,18 +379,15 @@ public class ServiceAnnotationPostProcessor implements BeanDefinitionRegistryPos * Build the {@link AbstractBeanDefinition Bean Definition} * * - * @param beanName - * @param serviceAnnotation * @param serviceAnnotationAttributes * @param interfaceClass - * @param annotatedServiceBeanName + * @param refServiceBeanName * @return * @since 2.7.3 */ - private AbstractBeanDefinition buildServiceBeanDefinition(String beanName, Annotation serviceAnnotation, - AnnotationAttributes serviceAnnotationAttributes, + private AbstractBeanDefinition buildServiceBeanDefinition(Map serviceAnnotationAttributes, Class interfaceClass, - String annotatedServiceBeanName) { + String refServiceBeanName) { BeanDefinitionBuilder builder = rootBeanDefinition(ServiceBean.class); @@ -397,16 +398,16 @@ public class ServiceAnnotationPostProcessor implements BeanDefinitionRegistryPos String[] ignoreAttributeNames = of("provider", "monitor", "application", "module", "registry", "protocol", "interface", "interfaceName", "parameters"); - propertyValues.addPropertyValues(new AnnotationPropertyValuesAdapter(serviceAnnotation, environment, ignoreAttributeNames)); + propertyValues.addPropertyValues(new AnnotationPropertyValuesAdapter(serviceAnnotationAttributes, environment, ignoreAttributeNames)); //set config id, for ConfigManager cache key //builder.addPropertyValue("id", beanName); // References "ref" property to annotated-@Service Bean - addPropertyReference(builder, "ref", annotatedServiceBeanName); + addPropertyReference(builder, "ref", refServiceBeanName); // Set interface builder.addPropertyValue("interface", interfaceClass.getName()); // Convert parameters into map - builder.addPropertyValue("parameters", convertParameters(serviceAnnotationAttributes.getStringArray("parameters"))); + builder.addPropertyValue("parameters", convertParameters((String[]) serviceAnnotationAttributes.get("parameters"))); // Add methods parameters List methodConfigs = convertMethodConfigs(serviceAnnotationAttributes.get("methods")); if (!methodConfigs.isEmpty()) { @@ -414,20 +415,20 @@ public class ServiceAnnotationPostProcessor implements BeanDefinitionRegistryPos } // convert provider to providerIds - String providerConfigId = serviceAnnotationAttributes.getString("provider"); + String providerConfigId = (String) serviceAnnotationAttributes.get("provider"); if (StringUtils.hasText(providerConfigId)) { addPropertyValue(builder, "providerIds", providerConfigId); } // Convert registry[] to registryIds - String[] registryConfigIds = serviceAnnotationAttributes.getStringArray("registry"); + String[] registryConfigIds = (String[]) serviceAnnotationAttributes.get("registry"); if (registryConfigIds != null && registryConfigIds.length > 0) { resolveStringArray(registryConfigIds); builder.addPropertyValue("registryIds", StringUtils.join(registryConfigIds, ',')); } // Convert protocol[] to protocolIds - String[] protocolConfigIds = serviceAnnotationAttributes.getStringArray("protocol"); + String[] protocolConfigIds = (String[]) serviceAnnotationAttributes.get("protocol"); if (protocolConfigIds != null && protocolConfigIds.length > 0) { resolveStringArray(protocolConfigIds); builder.addPropertyValue("protocolIds", StringUtils.join(protocolConfigIds, ',')); @@ -435,19 +436,19 @@ public class ServiceAnnotationPostProcessor implements BeanDefinitionRegistryPos // TODO Could we ignore these attributes: applicatin/monitor/module ? Use global config // monitor reference - String monitorConfigId = serviceAnnotationAttributes.getString("monitor"); + String monitorConfigId = (String) serviceAnnotationAttributes.get("monitor"); if (StringUtils.hasText(monitorConfigId)) { addPropertyReference(builder, "monitor", monitorConfigId); } // application reference - String applicationConfigId = serviceAnnotationAttributes.getString("application"); + String applicationConfigId = (String) serviceAnnotationAttributes.get("application"); if (StringUtils.hasText(applicationConfigId)) { addPropertyReference(builder, "application", applicationConfigId); } // module reference - String moduleConfigId = serviceAnnotationAttributes.getString("module"); + String moduleConfigId = (String) serviceAnnotationAttributes.get("module"); if (StringUtils.hasText(moduleConfigId)) { addPropertyReference(builder, "module", moduleConfigId); } @@ -501,7 +502,99 @@ public class ServiceAnnotationPostProcessor implements BeanDefinitionRegistryPos @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + String[] beanNames = beanFactory.getBeanDefinitionNames(); + for (String beanName : beanNames) { + BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); + Map annotationAttributes = getServiceAnnotationAttributes(beanDefinition); + if (annotationAttributes != null) { + // process @DubboService at java-config @bean method + processAnnotatedBeanDefinition(beanName, (AnnotatedBeanDefinition) beanDefinition, annotationAttributes); + } + } + } + /** + * Get dubbo service annotation class at java-config @bean method + * @return return service annotation attributes map if found, or return null if not found. + */ + private Map getServiceAnnotationAttributes(BeanDefinition beanDefinition) { + if (beanDefinition instanceof AnnotatedBeanDefinition) { + AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition; + MethodMetadata factoryMethodMetadata = annotatedBeanDefinition.getFactoryMethodMetadata(); + if (factoryMethodMetadata != null) { + // try all dubbo service annotation types + for (Class annotationType : serviceAnnotationTypes) { + if (factoryMethodMetadata.isAnnotated(annotationType.getName())) { + // Since Spring 5.2 + // return factoryMethodMetadata.getAnnotations().get(annotationType).filterDefaultValues().asMap(); + // Compatible with Spring 4.x + Map annotationAttributes = factoryMethodMetadata.getAnnotationAttributes(annotationType.getName()); + return filterDefaultValues(annotationType, annotationAttributes); + } + } + } + } + return null; + } + + /** + * process @DubboService at java-config @bean method + *

+     * @Configuration
+     * public class ProviderConfig {
+     *
+     *      @Bean
+     *      @DubboService(group="demo", version="1.2.3")
+     *      public DemoService demoService() {
+     *          return new DemoServiceImpl();
+     *      }
+     *
+     * }
+     * 
+ * @param refServiceBeanName + * @param refServiceBeanDefinition + * @param attributes + */ + private void processAnnotatedBeanDefinition(String refServiceBeanName, AnnotatedBeanDefinition refServiceBeanDefinition, Map attributes) { + + Map serviceAnnotationAttributes = new LinkedHashMap<>(attributes); + + // get bean class from return type + String returnTypeName = refServiceBeanDefinition.getFactoryMethodMetadata().getReturnTypeName(); + Class beanClass = resolveClassName(returnTypeName, classLoader); + + Class interfaceClass = resolveServiceInterfaceClass(serviceAnnotationAttributes, beanClass); + + // ServiceBean Bean name + String serviceBeanName = generateServiceBeanName(serviceAnnotationAttributes, interfaceClass); + + AbstractBeanDefinition serviceBeanDefinition = buildServiceBeanDefinition(serviceAnnotationAttributes, interfaceClass, refServiceBeanName); + + // set id + serviceBeanDefinition.getPropertyValues().add(Constants.ID, serviceBeanName); + + registerServiceBeanDefinition(serviceBeanName, serviceBeanDefinition, interfaceClass); + } + + private void registerServiceBeanDefinition(String serviceBeanName, AbstractBeanDefinition serviceBeanDefinition, Class interfaceClass) { + // check service bean + if (registry.containsBeanDefinition(serviceBeanName)) { + BeanDefinition existingDefinition = registry.getBeanDefinition(serviceBeanName); + if (existingDefinition.equals(serviceBeanDefinition)) { + // exist equipment bean definition + return; + } + + String msg = "Found duplicated BeanDefinition of service interface [" + interfaceClass.getName() + "] with bean name [" + serviceBeanName + + "], existing definition [ " + existingDefinition + "], new definition [" + serviceBeanDefinition + "]"; + logger.error(msg); + throw new BeanDefinitionStoreException(serviceBeanDefinition.getResourceDescription(), serviceBeanName, msg); + } + + registry.registerBeanDefinition(serviceBeanName, serviceBeanDefinition); + if (logger.isInfoEnabled()) { + logger.info("Register ServiceBean[" + serviceBeanName + "]: " + serviceBeanDefinition); + } } @Override @@ -518,4 +611,28 @@ public class ServiceAnnotationPostProcessor implements BeanDefinitionRegistryPos public void setBeanClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.servicePackagesHolder = applicationContext.getBean(ServicePackagesHolder.class); + } + + private class ScanExcludeFilter implements TypeFilter { + + private int excludedCount; + + @Override + public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { + String className = metadataReader.getClassMetadata().getClassName(); + boolean excluded = servicePackagesHolder.isClassScanned(className); + if (excluded) { + excludedCount ++; + } + return excluded; + } + + public int getExcludedCount() { + return excludedCount; + } + } } diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServicePackagesHolder.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServicePackagesHolder.java new file mode 100644 index 0000000000..75eb6ca2be --- /dev/null +++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServicePackagesHolder.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.dubbo.config.spring.beans.factory.annotation; + +import java.util.HashSet; +import java.util.Set; + +/** + * A temp holder for scanned packages of service. + */ +public class ServicePackagesHolder { + + public static final String BEAN_NAME = "dubboServicePackagesHolder"; + + private final Set scannedPackages = new HashSet<>(); + + private final Set scannedClasses = new HashSet<>(); + + + public void addScannedPackage(String apackage) { + apackage = normalizePackage(apackage); + synchronized (scannedPackages) { + scannedPackages.add(apackage); + } + } + + public boolean isPackageScanned(String packageName) { + packageName = normalizePackage(packageName); + synchronized (scannedPackages) { + if (scannedPackages.contains(packageName)) { + return true; + } + for (String scannedPackage : scannedPackages) { + if (isSubPackage(packageName, scannedPackage)) { + return true; + } + } + } + return false; + } + + public void addScannedClass(String className) { + synchronized (scannedClasses) { + scannedClasses.add(className); + } + } + + public boolean isClassScanned(String className) { + synchronized (scannedClasses) { + return scannedClasses.contains(className); + } + } + + /** + * Whether test package is sub package of parent package + * @param testPkg + * @param parent + * @return + */ + private boolean isSubPackage(String testPkg, String parent) { + // child pkg startsWith parent pkg + return testPkg.startsWith(parent); + } + + private String normalizePackage(String apackage) { + if (!apackage.endsWith(".")) { + apackage += "."; + } + return apackage; + } + +} diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboInfraBeanRegisterPostProcessor.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboInfraBeanRegisterPostProcessor.java index 7299ab6087..6533fffde9 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboInfraBeanRegisterPostProcessor.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboInfraBeanRegisterPostProcessor.java @@ -54,7 +54,7 @@ public class DubboInfraBeanRegisterPostProcessor implements BeanDefinitionRegist public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { DubboBeanUtils.registerBeansIfNotExists(registry); - // register ConfigManager + // register ConfigManager singleton beanFactory.registerSingleton(ConfigManager.BEAN_NAME, ApplicationModel.getConfigManager()); } diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/annotation/DubboComponentScanRegistrar.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/annotation/DubboComponentScanRegistrar.java index be57482dae..7807efee95 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/annotation/DubboComponentScanRegistrar.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/annotation/DubboComponentScanRegistrar.java @@ -53,12 +53,12 @@ public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistra @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + // @since 2.7.6 Register the common beans + registerCommonBeans(registry); + Set packagesToScan = getPackagesToScan(importingClassMetadata); registerServiceAnnotationPostProcessor(packagesToScan, registry); - - // @since 2.7.6 Register the common beans - registerCommonBeans(registry); } /** diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/schema/DubboBeanDefinitionParser.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/schema/DubboBeanDefinitionParser.java index abcca76940..305cfd45ee 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/schema/DubboBeanDefinitionParser.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/schema/DubboBeanDefinitionParser.java @@ -29,10 +29,10 @@ import org.apache.dubbo.config.ProtocolConfig; import org.apache.dubbo.config.ProviderConfig; import org.apache.dubbo.config.ReferenceConfig; import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.config.spring.Constants; import org.apache.dubbo.config.spring.ReferenceBean; import org.apache.dubbo.config.spring.ServiceBean; import org.springframework.beans.PropertyValue; -import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.RuntimeBeanReference; @@ -278,7 +278,7 @@ public class DubboBeanDefinitionParser implements BeanDefinitionParser { beanDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, beanName+"_decorated")); // signal object type since Spring 5.2 - beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, interfaceClass); + beanDefinition.setAttribute(Constants.OBJECT_TYPE_ATTRIBUTE, interfaceClass); //mark property value as optional List propertyValues = beanDefinition.getPropertyValues().getPropertyValueList(); diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/util/DubboAnnotationUtils.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/util/DubboAnnotationUtils.java index 7cf3527352..7fe662f774 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/util/DubboAnnotationUtils.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/util/DubboAnnotationUtils.java @@ -19,10 +19,11 @@ package org.apache.dubbo.config.spring.util; import org.apache.dubbo.config.annotation.Reference; import org.apache.dubbo.config.annotation.Service; -import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import java.util.Map; + import static com.alibaba.spring.util.AnnotationUtils.getAttribute; import static org.springframework.util.ClassUtils.getAllInterfacesForClass; import static org.springframework.util.ClassUtils.resolveClassName; @@ -59,14 +60,14 @@ public class DubboAnnotationUtils { } /** - * Resolve the interface name from {@link AnnotationAttributes} + * Resolve the interface name from annotation attributes * - * @param attributes {@link AnnotationAttributes} instance, may be {@link Service @Service} or {@link Reference @Reference} - * @param defaultInterfaceClass the default {@link Class class} of interface + * @param attributes annotation attributes instance, may be {@link Service @Service} or {@link Reference @Reference} + * @param defaultInterfaceClass the default class of interface * @return the interface name if found * @throws IllegalStateException if interface name was not found */ - public static String resolveInterfaceName(AnnotationAttributes attributes, Class defaultInterfaceClass) { + public static String resolveInterfaceName(Map attributes, Class defaultInterfaceClass) { Boolean generic = getAttribute(attributes, "generic"); if (generic != null && generic) { // it's a generic reference @@ -79,22 +80,22 @@ public class DubboAnnotationUtils { } /** - * Resolve the {@link Class class} of Dubbo Service interface from the specified - * {@link AnnotationAttributes annotation attributes} and annotated {@link Class class}. + * Resolve the interface class of Dubbo Service annotation from the specified + * annotation attributes and annotated class. * - * @param attributes {@link AnnotationAttributes annotation attributes} - * @param defaultInterfaceClass the annotated {@link Class class}. - * @return the {@link Class class} of Dubbo Service interface + * @param attributes annotation attributes + * @param defaultInterfaceClass the annotated class. + * @return the class of Dubbo Service interface * @throws IllegalArgumentException if can't resolved */ - public static Class resolveServiceInterfaceClass(AnnotationAttributes attributes, Class defaultInterfaceClass) + public static Class resolveServiceInterfaceClass(Map attributes, Class defaultInterfaceClass) throws IllegalArgumentException { ClassLoader classLoader = defaultInterfaceClass != null ? defaultInterfaceClass.getClassLoader() : Thread.currentThread().getContextClassLoader(); Class interfaceClass = getAttribute(attributes, "interfaceClass"); - if (void.class.equals(interfaceClass)) { // default or set void.class for purpose. + if (interfaceClass == null || void.class.equals(interfaceClass)) { // default or set void.class for purpose. interfaceClass = null; diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/util/DubboBeanUtils.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/util/DubboBeanUtils.java index d3ccfdf08b..126fa5e450 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/util/DubboBeanUtils.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/util/DubboBeanUtils.java @@ -18,6 +18,7 @@ package org.apache.dubbo.config.spring.util; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.dubbo.config.spring.beans.factory.annotation.ServicePackagesHolder; import org.apache.dubbo.config.spring.context.DubboConfigInitializationPostProcessor; import org.apache.dubbo.config.spring.reference.ReferenceBeanManager; import org.apache.dubbo.config.spring.beans.factory.annotation.DubboConfigAliasPostProcessor; @@ -58,12 +59,15 @@ public interface DubboBeanUtils { */ static void registerCommonBeans(BeanDefinitionRegistry registry) { + registerInfrastructureBean(registry, ServicePackagesHolder.BEAN_NAME, ServicePackagesHolder.class); + registerInfrastructureBean(registry, ReferenceBeanManager.BEAN_NAME, ReferenceBeanManager.class); // Since 2.5.7 Register @Reference Annotation Bean Processor as an infrastructure Bean registerInfrastructureBean(registry, ReferenceAnnotationBeanPostProcessor.BEAN_NAME, ReferenceAnnotationBeanPostProcessor.class); + // TODO Whether DubboConfigAliasPostProcessor can be removed ? // Since 2.7.4 [Feature] https://github.com/apache/dubbo/issues/5093 registerInfrastructureBean(registry, DubboConfigAliasPostProcessor.BEAN_NAME, DubboConfigAliasPostProcessor.class); diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/JavaConfigBeanTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/JavaConfigBeanTest.java index 97415e7f06..9e6528100b 100644 --- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/JavaConfigBeanTest.java +++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/JavaConfigBeanTest.java @@ -22,10 +22,12 @@ import org.apache.dubbo.config.ProtocolConfig; import org.apache.dubbo.config.ReferenceConfig; import org.apache.dubbo.config.RegistryConfig; import org.apache.dubbo.config.annotation.DubboReference; +import org.apache.dubbo.config.annotation.DubboService; import org.apache.dubbo.config.bootstrap.DubboBootstrap; import org.apache.dubbo.config.context.ConfigManager; import org.apache.dubbo.config.spring.api.DemoService; import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; +import org.apache.dubbo.config.spring.impl.DemoServiceImpl; import org.apache.dubbo.rpc.Constants; import org.apache.dubbo.rpc.model.ApplicationModel; import org.junit.jupiter.api.AfterEach; @@ -38,7 +40,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Collection; -import java.util.HashMap; import java.util.Map; public class JavaConfigBeanTest { @@ -70,15 +71,12 @@ public class JavaConfigBeanTest { SysProps.setProperty("dubbo.protocol.port", "2346"); String registryAddress = "zookeeper://127.0.0.1:2181"; SysProps.setProperty("dubbo.registry.address", registryAddress); + SysProps.setProperty("dubbo.provider.group", "test"); - // test destroy flag -// MockServiceDiscovery mockServiceDiscovery = (MockServiceDiscovery) ExtensionLoader.getExtensionLoader(ServiceDiscovery.class).getExtension("mock"); -// Assertions.assertEquals(false, mockServiceDiscovery.isDestroySucceed()); - - AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext( - TestConfiguration.class, ReferenceConfiguration.class); + AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext( + TestConfiguration.class, ConsumerConfiguration.class, ProviderConfiguration.class); try { - applicationContext.start(); + consumerContext.start(); ConfigManager configManager = ApplicationModel.getConfigManager(); ApplicationConfig application = configManager.getApplication().get(); @@ -101,7 +99,7 @@ public class JavaConfigBeanTest { Assertions.assertEquals(false, consumerConfig.isCheck()); Assertions.assertEquals(2, consumerConfig.getRetries()); - Map referenceBeanMap = applicationContext.getBeansOfType(ReferenceBean.class); + Map referenceBeanMap = consumerContext.getBeansOfType(ReferenceBean.class); Assertions.assertEquals(1, referenceBeanMap.size()); ReferenceBean referenceBean = referenceBeanMap.get("&demoService"); Assertions.assertNotNull(referenceBean); @@ -112,12 +110,14 @@ public class JavaConfigBeanTest { // consumer cannot override reference's attribute Assertions.assertEquals(5, referenceConfig.getRetries()); - } finally { - applicationContext.close(); - } + DemoService referProxy = (DemoService) referenceConfig.get(); + Assertions.assertTrue( referProxy instanceof DemoService); + String result = referProxy.sayName("dubbo"); + Assertions.assertEquals("say:dubbo", result); - // test destroy flag - //Assertions.assertEquals(true, mockServiceDiscovery.isDestroySucceed()); + } finally { + consumerContext.close(); + } } @@ -160,7 +160,7 @@ public class JavaConfigBeanTest { } @Configuration - static class ReferenceConfiguration { + static class ConsumerConfiguration { @Bean @DubboReference(scope = Constants.SCOPE_LOCAL, retries = 5) @@ -169,4 +169,14 @@ public class JavaConfigBeanTest { } } + + @Configuration + static class ProviderConfiguration { + + @Bean + @DubboService(group = "demo") + public DemoService demoServiceImpl() { + return new DemoServiceImpl(); + } + } } diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessorTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessorTest.java index fa4aa80e88..78fa75f226 100644 --- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessorTest.java +++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessorTest.java @@ -19,6 +19,7 @@ package org.apache.dubbo.config.spring.beans.factory.annotation; import org.apache.dubbo.config.bootstrap.DubboBootstrap; import org.apache.dubbo.config.spring.ServiceBean; import org.apache.dubbo.config.spring.api.HelloService; +import org.apache.dubbo.config.spring.context.annotation.DubboComponentScan; import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -45,7 +46,8 @@ import static org.springframework.test.annotation.DirtiesContext.ClassMode.AFTER @ContextConfiguration( classes = { ServiceAnnotationTestConfiguration.class, - ServiceAnnotationPostProcessorTest.class + ServiceAnnotationPostProcessorTest.class, + ServiceAnnotationPostProcessorTest.DuplicatedScanConfig.class }) @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) @TestPropertySource(properties = { @@ -81,7 +83,7 @@ public class ServiceAnnotationPostProcessorTest { Map beanPostProcessorsMap = beanFactory.getBeansOfType(ServiceAnnotationPostProcessor.class); - Assertions.assertEquals(1, beanPostProcessorsMap.size()); + Assertions.assertEquals(2, beanPostProcessorsMap.size()); } @@ -98,4 +100,9 @@ public class ServiceAnnotationPostProcessorTest { } + @DubboComponentScan({"org.apache.dubbo.config.spring.context.annotation", "${provider.package}"}) + static class DuplicatedScanConfig { + + } + } diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/extension/SpringExtensionFactoryTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/extension/SpringExtensionFactoryTest.java index 6715a7bce1..ca70f1f8d0 100644 --- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/extension/SpringExtensionFactoryTest.java +++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/extension/SpringExtensionFactoryTest.java @@ -18,6 +18,7 @@ package org.apache.dubbo.config.spring.extension; import org.apache.dubbo.common.extension.ExtensionFactory; import org.apache.dubbo.common.extension.ExtensionLoader; +import org.apache.dubbo.config.bootstrap.DubboBootstrap; import org.apache.dubbo.config.spring.api.DemoService; import org.apache.dubbo.config.spring.api.HelloService; import org.apache.dubbo.config.spring.impl.DemoServiceImpl; @@ -41,6 +42,8 @@ public class SpringExtensionFactoryTest { @BeforeEach public void init() { + DubboBootstrap.reset(); + // init SpringExtensionFactory ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getExtension("spring"); @@ -56,6 +59,13 @@ public class SpringExtensionFactoryTest { SpringExtensionFactory.addApplicationContext(context2); } + @AfterEach + public void destroy() { + context1.close(); + context2.close(); + SpringExtensionFactory.clearContexts(); + } + @Test public void testGetExtensionBySPI() { Protocol protocol = springExtensionFactory.getExtension(Protocol.class, "protocol"); @@ -70,13 +80,6 @@ public class SpringExtensionFactoryTest { Assertions.assertNotNull(hello); } - @AfterEach - public void destroy() { - SpringExtensionFactory.clearContexts(); - context1.close(); - context2.close(); - } - @Bean("bean1") public DemoService bean1() { return new DemoServiceImpl(); diff --git a/dubbo-dependencies-bom/pom.xml b/dubbo-dependencies-bom/pom.xml index 1831a11bbe..e281b08fb8 100644 --- a/dubbo-dependencies-bom/pom.xml +++ b/dubbo-dependencies-bom/pom.xml @@ -89,6 +89,7 @@ + 5.2.8.RELEASE 3.23.1-GA 10.4.0 diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/springmvc/AbstractRequestAnnotationParameterProcessor.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/springmvc/AbstractRequestAnnotationParameterProcessor.java index 74807ea280..2eac157d67 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/springmvc/AbstractRequestAnnotationParameterProcessor.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/springmvc/AbstractRequestAnnotationParameterProcessor.java @@ -62,7 +62,7 @@ public abstract class AbstractRequestAnnotationParameterProcessor extends Abstra private boolean isDefaultValue(Annotation annotation, String attributeName, Object attributeValue) { String defaultValue = AnnotationUtils.getDefaultValue(annotation, attributeName); - return Objects.equals(attributeValue, defaultValue); + return Objects.deepEquals(attributeValue, defaultValue); } protected boolean isEmpty(String str) { diff --git a/dubbo-spring-boot/dubbo-spring-boot-compatible/autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboAutoConfiguration.java b/dubbo-spring-boot/dubbo-spring-boot-compatible/autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboAutoConfiguration.java index 4fb850c3f0..51be6ac55b 100644 --- a/dubbo-spring-boot/dubbo-spring-boot-compatible/autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboAutoConfiguration.java +++ b/dubbo-spring-boot/dubbo-spring-boot-compatible/autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboAutoConfiguration.java @@ -31,7 +31,6 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; @@ -71,7 +70,6 @@ public class DubboAutoConfiguration implements ApplicationContextAware, BeanDefi */ @ConditionalOnProperty(prefix = DUBBO_SCAN_PREFIX, name = BASE_PACKAGES_PROPERTY_NAME) @ConditionalOnBean(name = BASE_PACKAGES_BEAN_NAME) - @ConditionalOnMissingBean @Bean public ServiceAnnotationPostProcessor serviceAnnotationBeanProcessor(@Qualifier(BASE_PACKAGES_BEAN_NAME) Set packagesToScan) {