From dca95745c4668a819e832a0fca3e56fe386ad040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E9=A9=AC=E5=93=A5?= Date: Fri, 19 Oct 2018 14:45:10 +0800 Subject: [PATCH] @Service and @Reference Optimization (#2657) * Polish apache/incubator-dubbo#2235 apache/incubator-dubbo#2251 apache/incubator-dubbo-spring-boot-project#243 * Fixed bugs and optimized imports --- dependencies-bom/pom.xml | 11 + dubbo-config/dubbo-config-spring/pom.xml | 7 +- .../dubbo/config/spring/ServiceBean.java | 47 +- .../AbstractAnnotationConfigBeanBuilder.java | 4 +- .../ReferenceAnnotationBeanPostProcessor.java | 508 +++++------------- .../annotation/ReferenceBeanBuilder.java | 46 +- .../ServiceAnnotationBeanPostProcessor.java | 22 +- .../annotation/ServiceBeanNameBuilder.java | 111 ++++ .../event/ServiceBeanExportedEvent.java | 50 ++ .../converter/StringArrayToMapConverter.java | 38 -- .../StringArrayToStringConverter.java | 37 -- .../config/spring/util/AnnotationUtils.java | 46 +- ...erenceAnnotationBeanPostProcessorTest.java | 128 ++--- ...erviceAnnotationBeanPostProcessorTest.java | 45 +- .../ServiceAnnotationTestConfiguration.java | 114 ++++ .../ServiceBeanNameBuilderTest.java | 75 +++ .../StringArrayToMapConverterTest.java | 52 -- .../StringArrayToStringConverterTest.java | 46 -- 18 files changed, 691 insertions(+), 696 deletions(-) create mode 100644 dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceBeanNameBuilder.java create mode 100644 dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/context/event/ServiceBeanExportedEvent.java delete mode 100644 dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/convert/converter/StringArrayToMapConverter.java delete mode 100644 dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/convert/converter/StringArrayToStringConverter.java create mode 100644 dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationTestConfiguration.java create mode 100644 dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceBeanNameBuilderTest.java delete mode 100644 dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/convert/converter/StringArrayToMapConverterTest.java delete mode 100644 dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/convert/converter/StringArrayToStringConverterTest.java diff --git a/dependencies-bom/pom.xml b/dependencies-bom/pom.xml index 752816b0cbe..e6bda8f0c45 100644 --- a/dependencies-bom/pom.xml +++ b/dependencies-bom/pom.xml @@ -112,6 +112,7 @@ 2.2.7 1.2.0 3.2.4 + 1.0.1 @@ -358,6 +359,16 @@ hessian-lite ${hessian_lite_version} + + + + + + com.alibaba.spring + spring-context-support + ${alibaba_spring_context_support_version} + + org.apache.curator diff --git a/dubbo-config/dubbo-config-spring/pom.xml b/dubbo-config/dubbo-config-spring/pom.xml index ad3ea15a13b..8978c488c93 100644 --- a/dubbo-config/dubbo-config-spring/pom.xml +++ b/dubbo-config/dubbo-config-spring/pom.xml @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. --> - + 4.0.0 com.alibaba @@ -51,6 +52,10 @@ org.springframework spring-context + + com.alibaba.spring + spring-context-support + javax.servlet javax.servlet-api diff --git a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/ServiceBean.java b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/ServiceBean.java index b38684b2c75..6174d1b752d 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/ServiceBean.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/ServiceBean.java @@ -24,7 +24,9 @@ import com.alibaba.dubbo.config.RegistryConfig; import com.alibaba.dubbo.config.ServiceConfig; import com.alibaba.dubbo.config.annotation.Service; +import com.alibaba.dubbo.config.spring.context.event.ServiceBeanExportedEvent; import com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory; + import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.BeanNameAware; @@ -32,6 +34,8 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.support.AbstractApplicationContext; @@ -46,7 +50,9 @@ * * @export */ -public class ServiceBean extends ServiceConfig implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware { +public class ServiceBean extends ServiceConfig implements InitializingBean, DisposableBean, + ApplicationContextAware, ApplicationListener, BeanNameAware, + ApplicationEventPublisherAware { private static final long serialVersionUID = 213195494150089726L; @@ -60,6 +66,8 @@ public class ServiceBean extends ServiceConfig implements InitializingBean private transient boolean supportedApplicationListener; + private ApplicationEventPublisher applicationEventPublisher; + public ServiceBean() { super(); this.service = null; @@ -265,6 +273,34 @@ && getInterface() != null && getInterface().length() > 0 } } + /** + * Get the name of {@link ServiceBean} + * + * @return {@link ServiceBean}'s name + * @since 2.6.5 + */ + public String getBeanName() { + return this.beanName; + } + + /** + * @since 2.6.5 + */ + @Override + public void export() { + super.export(); + // Publish ServiceBeanExportedEvent + publishExportEvent(); + } + + /** + * @since 2.6.5 + */ + private void publishExportEvent() { + ServiceBeanExportedEvent exportEvent = new ServiceBeanExportedEvent(this); + applicationEventPublisher.publishEvent(exportEvent); + } + @Override public void destroy() throws Exception { // This will only be called for singleton scope bean, and expected to be called by spring shutdown hook when BeanFactory/ApplicationContext destroys. @@ -280,4 +316,13 @@ protected Class getServiceClass(T ref) { } return super.getServiceClass(ref); } + + /** + * @param applicationEventPublisher + * @since 2.6.5 + */ + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.applicationEventPublisher = applicationEventPublisher; + } } diff --git a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/AbstractAnnotationConfigBeanBuilder.java b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/AbstractAnnotationConfigBeanBuilder.java index fc8d3f46105..0357ff0bc3e 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/AbstractAnnotationConfigBeanBuilder.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/AbstractAnnotationConfigBeanBuilder.java @@ -21,6 +21,7 @@ import com.alibaba.dubbo.config.ModuleConfig; import com.alibaba.dubbo.config.MonitorConfig; import com.alibaba.dubbo.config.RegistryConfig; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; @@ -34,6 +35,7 @@ /** * Abstract Configurable {@link Annotation} Bean Builder + * * @since 2.5.7 */ abstract class AbstractAnnotationConfigBeanBuilder { @@ -76,7 +78,7 @@ public final B build() throws Exception { configureBean(bean); if (logger.isInfoEnabled()) { - logger.info(bean + " has been built."); + logger.info("The bean[type:" + bean.getClass().getSimpleName() + "] has been built."); } return bean; diff --git a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java index cfd719668ae..2e45c692c0f 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java @@ -18,488 +18,236 @@ import com.alibaba.dubbo.config.annotation.Reference; import com.alibaba.dubbo.config.spring.ReferenceBean; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeanUtils; +import com.alibaba.dubbo.config.spring.ServiceBean; +import com.alibaba.dubbo.config.spring.context.event.ServiceBeanExportedEvent; +import com.alibaba.dubbo.config.spring.util.AnnotationUtils; +import com.alibaba.spring.beans.factory.annotation.AnnotationInjectedBeanPostProcessor; + import org.springframework.beans.BeansException; -import org.springframework.beans.PropertyValues; -import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.InjectionMetadata; -import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; -import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; -import org.springframework.core.PriorityOrdered; -import org.springframework.core.env.Environment; -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; -import org.springframework.util.StringUtils; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; -import java.beans.PropertyDescriptor; import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; +import java.lang.reflect.Proxy; import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; +import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import static org.springframework.core.BridgeMethodResolver.findBridgedMethod; -import static org.springframework.core.BridgeMethodResolver.isVisibilityBridgeMethodPair; -import static org.springframework.core.annotation.AnnotationUtils.findAnnotation; -import static org.springframework.core.annotation.AnnotationUtils.getAnnotation; - /** * {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation * that Consumer service {@link Reference} annotated fields * * @since 2.5.7 */ -public class ReferenceAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter - implements MergedBeanDefinitionPostProcessor, PriorityOrdered, ApplicationContextAware, BeanClassLoaderAware, - DisposableBean { +public class ReferenceAnnotationBeanPostProcessor extends AnnotationInjectedBeanPostProcessor + implements ApplicationContextAware, ApplicationListener { /** * The bean name of {@link ReferenceAnnotationBeanPostProcessor} */ public static final String BEAN_NAME = "referenceAnnotationBeanPostProcessor"; - private final Log logger = LogFactory.getLog(getClass()); + /** + * Cache size + */ + private static final int CACHE_SIZE = Integer.getInteger(BEAN_NAME + ".cache.size", 32); - private ApplicationContext applicationContext; + private final ConcurrentMap> referenceBeanCache = + new ConcurrentHashMap>(CACHE_SIZE); - private ClassLoader classLoader; + private final ConcurrentHashMap localReferenceBeanInvocationHandlerCache = + new ConcurrentHashMap(CACHE_SIZE); - private final ConcurrentMap injectionMetadataCache = - new ConcurrentHashMap(256); + private final ConcurrentMap> injectedFieldReferenceBeanCache = + new ConcurrentHashMap>(CACHE_SIZE); - private final ConcurrentMap> referenceBeansCache = - new ConcurrentHashMap>(); - - @Override - public PropertyValues postProcessPropertyValues( - PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException { - - InjectionMetadata metadata = findReferenceMetadata(beanName, bean.getClass(), pvs); - try { - metadata.inject(bean, beanName, pvs); - } catch (BeanCreationException ex) { - throw ex; - } catch (Throwable ex) { - throw new BeanCreationException(beanName, "Injection of @Reference dependencies failed", ex); - } - return pvs; - } + private final ConcurrentMap> injectedMethodReferenceBeanCache = + new ConcurrentHashMap>(CACHE_SIZE); + private ApplicationContext applicationContext; /** - * Finds {@link InjectionMetadata.InjectedElement} Metadata from annotated {@link Reference @Reference} fields + * Gets all beans of {@link ReferenceBean} * - * @param beanClass The {@link Class} of Bean - * @return non-null {@link List} + * @return non-null read-only {@link Collection} + * @since 2.5.9 */ - private List findFieldReferenceMetadata(final Class beanClass) { - - final List elements = new LinkedList(); - - ReflectionUtils.doWithFields(beanClass, new ReflectionUtils.FieldCallback() { - @Override - public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { - - Reference reference = getAnnotation(field, Reference.class); - - if (reference != null) { - - if (Modifier.isStatic(field.getModifiers())) { - if (logger.isWarnEnabled()) { - logger.warn("@Reference annotation is not supported on static fields: " + field); - } - return; - } - - elements.add(new ReferenceFieldElement(field, reference)); - } - - } - }); - - return elements; - + public Collection> getReferenceBeans() { + return referenceBeanCache.values(); } /** - * Finds {@link InjectionMetadata.InjectedElement} Metadata from annotated {@link Reference @Reference} methods + * Get {@link ReferenceBean} {@link Map} in injected field. * - * @param beanClass The {@link Class} of Bean - * @return non-null {@link List} + * @return non-null {@link Map} + * @since 2.5.11 */ - private List findMethodReferenceMetadata(final Class beanClass) { - - final List elements = new LinkedList(); - - ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() { - @Override - public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { - - Method bridgedMethod = findBridgedMethod(method); - - if (!isVisibilityBridgeMethodPair(method, bridgedMethod)) { - return; - } - - Reference reference = findAnnotation(bridgedMethod, Reference.class); - - if (reference != null && method.equals(ClassUtils.getMostSpecificMethod(method, beanClass))) { - if (Modifier.isStatic(method.getModifiers())) { - if (logger.isWarnEnabled()) { - logger.warn("@Reference annotation is not supported on static methods: " + method); - } - return; - } - if (method.getParameterTypes().length == 0) { - if (logger.isWarnEnabled()) { - logger.warn("@Reference annotation should only be used on methods with parameters: " + - method); - } - } - PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, beanClass); - elements.add(new ReferenceMethodElement(method, pd, reference)); - } - } - }); - - return elements; - + public Map> getInjectedFieldReferenceBeanMap() { + return Collections.unmodifiableMap(injectedFieldReferenceBeanCache); } - /** - * @param beanClass - * @return + * Get {@link ReferenceBean} {@link Map} in injected method. + * + * @return non-null {@link Map} + * @since 2.5.11 */ - private ReferenceInjectionMetadata buildReferenceMetadata(final Class beanClass) { - Collection fieldElements = findFieldReferenceMetadata(beanClass); - Collection methodElements = findMethodReferenceMetadata(beanClass); - return new ReferenceInjectionMetadata(beanClass, fieldElements, methodElements); - - } - - private InjectionMetadata findReferenceMetadata(String beanName, Class clazz, PropertyValues pvs) { - // Fall back to class name as cache key, for backwards compatibility with custom callers. - String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName()); - // Quick check on the concurrent map first, with minimal locking. - ReferenceInjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey); - if (InjectionMetadata.needsRefresh(metadata, clazz)) { - synchronized (this.injectionMetadataCache) { - metadata = this.injectionMetadataCache.get(cacheKey); - if (InjectionMetadata.needsRefresh(metadata, clazz)) { - if (metadata != null) { - metadata.clear(pvs); - } - try { - metadata = buildReferenceMetadata(clazz); - this.injectionMetadataCache.put(cacheKey, metadata); - } catch (NoClassDefFoundError err) { - throw new IllegalStateException("Failed to introspect bean class [" + clazz.getName() + - "] for reference metadata: could not find class that it depends on", err); - } - } - } - } - return metadata; - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - @Override - public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { - if (beanType != null) { - InjectionMetadata metadata = findReferenceMetadata(beanName, beanType, null); - metadata.checkConfigMembers(beanDefinition); - } - } - - @Override - public int getOrder() { - return LOWEST_PRECEDENCE; + public Map> getInjectedMethodReferenceBeanMap() { + return Collections.unmodifiableMap(injectedMethodReferenceBeanCache); } @Override - public void destroy() throws Exception { + protected Object doGetInjectedBean(Reference reference, Object bean, String beanName, Class injectedType, + InjectionMetadata.InjectedElement injectedElement) throws Exception { - for (ReferenceBean referenceBean : referenceBeansCache.values()) { - if (logger.isInfoEnabled()) { - logger.info(referenceBean + " was destroying!"); - } - referenceBean.destroy(); - } + String referencedBeanName = buildReferencedBeanName(reference, injectedType); - injectionMetadataCache.clear(); - referenceBeansCache.clear(); + ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referencedBeanName, reference, injectedType, getClassLoader()); - if (logger.isInfoEnabled()) { - logger.info(getClass() + " was destroying!"); - } + cacheInjectedReferenceBean(referenceBean, injectedElement); - } + Object proxy = buildProxy(referencedBeanName, referenceBean, injectedType); - @Override - public void setBeanClassLoader(ClassLoader classLoader) { - this.classLoader = classLoader; + return proxy; } - - /** - * Gets all beans of {@link ReferenceBean} - * - * @return non-null {@link Collection} - * @since 2.5.9 - */ - public Collection> getReferenceBeans() { - return this.referenceBeansCache.values(); + private Object buildProxy(String referencedBeanName, ReferenceBean referenceBean, Class injectedType) { + InvocationHandler handler = buildInvocationHandler(referencedBeanName, referenceBean); + Object proxy = Proxy.newProxyInstance(getClassLoader(), new Class[]{injectedType}, handler); + return proxy; } + private InvocationHandler buildInvocationHandler(String referencedBeanName, ReferenceBean referenceBean) { - /** - * {@link Reference} {@link InjectionMetadata} implementation - * - * @since 2.5.11 - */ - private static class ReferenceInjectionMetadata extends InjectionMetadata { - - private final Collection fieldElements; + ReferenceBeanInvocationHandler handler = localReferenceBeanInvocationHandlerCache.get(referencedBeanName); - private final Collection methodElements; - - - public ReferenceInjectionMetadata(Class targetClass, Collection fieldElements, - Collection methodElements) { - super(targetClass, combine(fieldElements, methodElements)); - this.fieldElements = fieldElements; - this.methodElements = methodElements; + if (handler == null) { + handler = new ReferenceBeanInvocationHandler(referenceBean); } - private static Collection combine(Collection... elements) { - List allElements = new ArrayList(); - for (Collection e : elements) { - allElements.addAll(e); - } - return allElements; - } - - public Collection getFieldElements() { - return fieldElements; + if (applicationContext.containsBean(referencedBeanName)) { // Is local @Service Bean or not ? + // ReferenceBeanInvocationHandler's initialization has to wait for current local @Service Bean has been exported. + localReferenceBeanInvocationHandlerCache.put(referencedBeanName, handler); + } else { + // Remote Reference Bean should initialize immediately + handler.init(); } - public Collection getMethodElements() { - return methodElements; - } + return handler; } - /** - * {@link Reference} {@link Method} {@link InjectionMetadata.InjectedElement} - */ - private class ReferenceMethodElement extends InjectionMetadata.InjectedElement { + private static class ReferenceBeanInvocationHandler implements InvocationHandler { - private final Method method; + private final ReferenceBean referenceBean; - private final Reference reference; + private Object bean; - private volatile ReferenceBean referenceBean; - - protected ReferenceMethodElement(Method method, PropertyDescriptor pd, Reference reference) { - super(method, pd); - this.method = method; - this.reference = reference; + private ReferenceBeanInvocationHandler(ReferenceBean referenceBean) { + this.referenceBean = referenceBean; } @Override - protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { - - Class referenceClass = pd.getPropertyType(); - - referenceBean = buildReferenceBean(reference, referenceClass); - - ReflectionUtils.makeAccessible(method); - - method.invoke(bean, referenceBean.getObject()); - + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return method.invoke(bean, args); } - } - - /** - * {@link Reference} {@link Field} {@link InjectionMetadata.InjectedElement} - */ - private class ReferenceFieldElement extends InjectionMetadata.InjectedElement { - - private final Field field; - - private final Reference reference; - - private volatile ReferenceBean referenceBean; - - protected ReferenceFieldElement(Field field, Reference reference) { - super(field, null); - this.field = field; - this.reference = reference; + private void init() { + this.bean = referenceBean.get(); } + } - @Override - protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { - - Class referenceClass = field.getType(); + @Override + protected String buildInjectedObjectCacheKey(Reference reference, Object bean, String beanName, + Class injectedType, InjectionMetadata.InjectedElement injectedElement) { - referenceBean = buildReferenceBean(reference, referenceClass); + String key = buildReferencedBeanName(reference, injectedType) + + "#source=" + (injectedElement.getMember()) + + "#attributes=" + AnnotationUtils.getAttributes(reference,getEnvironment(),true); - ReflectionUtils.makeAccessible(field); + return key; + } - field.set(bean, referenceBean.getObject()); + private String buildReferencedBeanName(Reference reference, Class injectedType) { - } + ServiceBeanNameBuilder builder = ServiceBeanNameBuilder.create(reference, injectedType, getEnvironment()); + return getEnvironment().resolvePlaceholders(builder.build()); } - private ReferenceBean buildReferenceBean(Reference reference, Class referenceClass) throws Exception { - - String referenceBeanCacheKey = generateReferenceBeanCacheKey(reference, referenceClass); + private ReferenceBean buildReferenceBeanIfAbsent(String referencedBeanName, Reference reference, + Class referencedType, ClassLoader classLoader) + throws Exception { - ReferenceBean referenceBean = referenceBeansCache.get(referenceBeanCacheKey); + ReferenceBean referenceBean = referenceBeanCache.get(referencedBeanName); if (referenceBean == null) { - ReferenceBeanBuilder beanBuilder = ReferenceBeanBuilder .create(reference, classLoader, applicationContext) - .interfaceClass(referenceClass); - + .interfaceClass(referencedType); referenceBean = beanBuilder.build(); - - referenceBeansCache.putIfAbsent(referenceBeanCacheKey, referenceBean); - + referenceBeanCache.put(referencedBeanName, referenceBean); } return referenceBean; - } - - /** - * Generate a cache key of {@link ReferenceBean} - * - * @param reference {@link Reference} - * @param beanClass {@link Class} - * @return - */ - private String generateReferenceBeanCacheKey(Reference reference, Class beanClass) { - - String interfaceName = resolveInterfaceName(reference, beanClass); - - String key = reference.url() + "/" + interfaceName + - "/" + reference.version() + - "/" + reference.group(); - - Environment environment = applicationContext.getEnvironment(); - - key = environment.resolvePlaceholders(key); - - return key; - - } - - private static String resolveInterfaceName(Reference reference, Class beanClass) - throws IllegalStateException { - - String interfaceName; - if (!"".equals(reference.interfaceName())) { - interfaceName = reference.interfaceName(); - } else if (!void.class.equals(reference.interfaceClass())) { - interfaceName = reference.interfaceClass().getName(); - } else if (beanClass.isInterface()) { - interfaceName = beanClass.getName(); - } else { - throw new IllegalStateException( - "The @Reference undefined interfaceClass or interfaceName, and the property type " - + beanClass.getName() + " is not a interface."); + private void cacheInjectedReferenceBean(ReferenceBean referenceBean, + InjectionMetadata.InjectedElement injectedElement) { + if (injectedElement.getMember() instanceof Field) { + injectedFieldReferenceBeanCache.put(injectedElement, referenceBean); + } else if (injectedElement.getMember() instanceof Method) { + injectedMethodReferenceBeanCache.put(injectedElement, referenceBean); } - - return interfaceName; - } + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } - /** - * Get {@link ReferenceBean} {@link Map} in injected field. - * - * @return non-null {@link Map} - * @since 2.5.11 - */ - public Map> getInjectedFieldReferenceBeanMap() { - - Map> injectedElementReferenceBeanMap = - new LinkedHashMap>(); - - for (ReferenceInjectionMetadata metadata : injectionMetadataCache.values()) { - - Collection fieldElements = metadata.getFieldElements(); - - for (ReferenceFieldElement fieldElement : fieldElements) { - - injectedElementReferenceBeanMap.put(fieldElement, fieldElement.referenceBean); - - } - + @Override + public void onApplicationEvent(ApplicationEvent event) { + if (event instanceof ServiceBeanExportedEvent) { + onServiceBeanExportEvent((ServiceBeanExportedEvent) event); + } else if (event instanceof ContextRefreshedEvent) { + onContextRefreshedEvent((ContextRefreshedEvent) event); } - - return injectedElementReferenceBeanMap; - } - /** - * Get {@link ReferenceBean} {@link Map} in injected method. - * - * @return non-null {@link Map} - * @since 2.5.11 - */ - public Map> getInjectedMethodReferenceBeanMap() { - - Map> injectedElementReferenceBeanMap = - new LinkedHashMap>(); - - for (ReferenceInjectionMetadata metadata : injectionMetadataCache.values()) { - - Collection methodElements = metadata.getMethodElements(); - - for (ReferenceMethodElement methodElement : methodElements) { - - injectedElementReferenceBeanMap.put(methodElement, methodElement.referenceBean); - - } + private void onServiceBeanExportEvent(ServiceBeanExportedEvent event) { + ServiceBean serviceBean = event.getServiceBean(); + initReferenceBeanInvocationHandler(serviceBean); + } + private void initReferenceBeanInvocationHandler(ServiceBean serviceBean) { + String serviceBeanName = serviceBean.getBeanName(); + // Remove ServiceBean when it's exported + ReferenceBeanInvocationHandler handler = localReferenceBeanInvocationHandlerCache.remove(serviceBeanName); + // Initialize + if (handler != null) { + handler.init(); } - - return injectedElementReferenceBeanMap; - } - private T getFieldValue(Object object, String fieldName, Class fieldType) { - - Field field = ReflectionUtils.findField(object.getClass(), fieldName, fieldType); - - ReflectionUtils.makeAccessible(field); - - return (T) ReflectionUtils.getField(field, object); + private void onContextRefreshedEvent(ContextRefreshedEvent event) { } -} + @Override + public void destroy() throws Exception { + super.destroy(); + this.referenceBeanCache.clear(); + this.localReferenceBeanInvocationHandlerCache.clear(); + this.injectedFieldReferenceBeanCache.clear(); + this.injectedMethodReferenceBeanCache.clear(); + } +} \ No newline at end of file diff --git a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ReferenceBeanBuilder.java b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ReferenceBeanBuilder.java index 5d9418fbc8a..883a56a1879 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ReferenceBeanBuilder.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ReferenceBeanBuilder.java @@ -16,21 +16,23 @@ */ package com.alibaba.dubbo.config.spring.beans.factory.annotation; +import com.alibaba.dubbo.common.utils.CollectionUtils; import com.alibaba.dubbo.config.ConsumerConfig; import com.alibaba.dubbo.config.annotation.Reference; import com.alibaba.dubbo.config.spring.ReferenceBean; -import com.alibaba.dubbo.config.spring.convert.converter.StringArrayToMapConverter; -import com.alibaba.dubbo.config.spring.convert.converter.StringArrayToStringConverter; +import org.springframework.beans.propertyeditors.StringTrimmerEditor; import org.springframework.context.ApplicationContext; -import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import org.springframework.validation.DataBinder; +import java.beans.PropertyEditorSupport; +import java.util.Map; + import static com.alibaba.dubbo.config.spring.util.BeanFactoryUtils.getOptionalBean; import static com.alibaba.dubbo.config.spring.util.ObjectUtils.of; +import static org.springframework.util.StringUtils.commaDelimitedListToStringArray; /** * {@link ReferenceBean} Builder @@ -39,6 +41,8 @@ */ class ReferenceBeanBuilder extends AbstractAnnotationConfigBeanBuilder { + // Ignore those fields + static final String[] IGNORE_FIELD_NAMES = of("application", "module", "consumer", "monitor", "registry"); private ReferenceBeanBuilder(Reference annotation, ClassLoader classLoader, ApplicationContext applicationContext) { super(annotation, classLoader, applicationContext); @@ -93,20 +97,30 @@ protected ReferenceBean doBuild() { protected void preConfigureBean(Reference reference, ReferenceBean referenceBean) { Assert.notNull(interfaceClass, "The interface class must set first!"); DataBinder dataBinder = new DataBinder(referenceBean); - // Set ConversionService - dataBinder.setConversionService(getConversionService()); - // Ignore those fields - String[] ignoreAttributeNames = of("application", "module", "consumer", "monitor", "registry"); -// dataBinder.setDisallowedFields(ignoreAttributeNames); + // Register CustomEditors for special fields + dataBinder.registerCustomEditor(String.class, "filter", new StringTrimmerEditor(true)); + dataBinder.registerCustomEditor(String.class, "listener", new StringTrimmerEditor(true)); + dataBinder.registerCustomEditor(Map.class, "parameters", new PropertyEditorSupport() { + + public void setAsText(String text) throws java.lang.IllegalArgumentException { + // Trim all whitespace + String content = StringUtils.trimAllWhitespace(text); + if (!StringUtils.hasText(content)) { // No content , ignore directly + return; + } + // replace "=" to "," + content = StringUtils.replace(content, "=", ","); + // replace ":" to "," + content = StringUtils.replace(content, ":", ","); + // String[] to Map + Map parameters = CollectionUtils.toStringMap(commaDelimitedListToStringArray(content)); + setValue(parameters); + } + }); + // Bind annotation attributes - dataBinder.bind(new AnnotationPropertyValuesAdapter(reference, applicationContext.getEnvironment(), ignoreAttributeNames)); - } + dataBinder.bind(new AnnotationPropertyValuesAdapter(reference, applicationContext.getEnvironment(), IGNORE_FIELD_NAMES)); - private ConversionService getConversionService() { - DefaultConversionService conversionService = new DefaultConversionService(); - conversionService.addConverter(new StringArrayToStringConverter()); - conversionService.addConverter(new StringArrayToMapConverter()); - return conversionService; } diff --git a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationBeanPostProcessor.java b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationBeanPostProcessor.java index 35604650906..df46850b304 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationBeanPostProcessor.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationBeanPostProcessor.java @@ -71,7 +71,6 @@ public class ServiceAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware, ResourceLoaderAware, BeanClassLoaderAware { - private static final String SEPARATOR = ":"; private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -289,27 +288,10 @@ private void registerServiceBean(BeanDefinitionHolder beanDefinitionHolder, Bean */ private String generateServiceBeanName(Service service, Class interfaceClass, String annotatedServiceBeanName) { - StringBuilder beanNameBuilder = new StringBuilder(ServiceBean.class.getSimpleName()); + ServiceBeanNameBuilder builder = ServiceBeanNameBuilder.create(service, interfaceClass, environment); - beanNameBuilder.append(SEPARATOR).append(annotatedServiceBeanName); - String interfaceClassName = interfaceClass.getName(); - - beanNameBuilder.append(SEPARATOR).append(interfaceClassName); - - String version = service.version(); - - if (StringUtils.hasText(version)) { - beanNameBuilder.append(SEPARATOR).append(version); - } - - String group = service.group(); - - if (StringUtils.hasText(group)) { - beanNameBuilder.append(SEPARATOR).append(group); - } - - return beanNameBuilder.toString(); + return builder.build(); } diff --git a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceBeanNameBuilder.java b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceBeanNameBuilder.java new file mode 100644 index 00000000000..b80840cf667 --- /dev/null +++ b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceBeanNameBuilder.java @@ -0,0 +1,111 @@ +/* + * 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 com.alibaba.dubbo.config.spring.beans.factory.annotation; + +import com.alibaba.dubbo.config.annotation.Reference; +import com.alibaba.dubbo.config.annotation.Service; +import com.alibaba.dubbo.config.spring.ReferenceBean; +import com.alibaba.dubbo.config.spring.ServiceBean; +import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; + +import static com.alibaba.dubbo.config.spring.util.AnnotationUtils.resolveInterfaceName; + +/** + * Dubbo {@link Service @Service} Bean Builder + * + * @see Service + * @see Reference + * @see ServiceBean + * @see ReferenceBean + * @since 2.6.5 + */ +class ServiceBeanNameBuilder { + + private static final String SEPARATOR = ":"; + + private final String interfaceClassName; + + private final Environment environment; + + // Optional + private String version; + + private String group; + + private ServiceBeanNameBuilder(String interfaceClassName, Environment environment) { + this.interfaceClassName = interfaceClassName; + this.environment = environment; + } + + private ServiceBeanNameBuilder(Class interfaceClass, Environment environment) { + this(interfaceClass.getName(), environment); + } + + private ServiceBeanNameBuilder(Service service, Class interfaceClass, Environment environment) { + this(resolveInterfaceName(service, interfaceClass), environment); + this.group(service.group()); + this.version(service.version()); + } + + private ServiceBeanNameBuilder(Reference reference, Class interfaceClass, Environment environment) { + this(resolveInterfaceName(reference, interfaceClass), environment); + this.group(reference.group()); + this.version(reference.version()); + } + + public static ServiceBeanNameBuilder create(Class interfaceClass, Environment environment) { + return new ServiceBeanNameBuilder(interfaceClass, environment); + } + + public static ServiceBeanNameBuilder create(Service service, Class interfaceClass, Environment environment) { + return new ServiceBeanNameBuilder(service, interfaceClass, environment); + } + + public static ServiceBeanNameBuilder create(Reference reference, Class interfaceClass, Environment environment) { + return new ServiceBeanNameBuilder(reference, interfaceClass, environment); + } + + private static void append(StringBuilder builder, String value) { + if (StringUtils.hasText(value)) { + builder.append(value).append(SEPARATOR); + } + } + + public ServiceBeanNameBuilder group(String group) { + this.group = group; + return this; + } + + public ServiceBeanNameBuilder version(String version) { + this.version = version; + return this; + } + + public String build() { + StringBuilder beanNameBuilder = new StringBuilder("ServiceBean").append(SEPARATOR); + // Required + append(beanNameBuilder, interfaceClassName); + // Optional + append(beanNameBuilder, version); + append(beanNameBuilder, group); + // Build and remove last ":" + String rawBeanName = beanNameBuilder.substring(0, beanNameBuilder.length() - 1); + // Resolve placeholders + return environment.resolvePlaceholders(rawBeanName); + } +} \ No newline at end of file diff --git a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/context/event/ServiceBeanExportedEvent.java b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/context/event/ServiceBeanExportedEvent.java new file mode 100644 index 00000000000..3b14d012040 --- /dev/null +++ b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/context/event/ServiceBeanExportedEvent.java @@ -0,0 +1,50 @@ +/* + * 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 com.alibaba.dubbo.config.spring.context.event; + +import com.alibaba.dubbo.config.spring.ServiceBean; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; + +/** + * A {@link ApplicationEvent} after {@link ServiceBean} {@link ServiceBean#export() export} invocation + * + * @see ApplicationEvent + * @see ApplicationListener + * @see ServiceBean + * @since 2.6.5 + */ +public class ServiceBeanExportedEvent extends ApplicationEvent { + + /** + * Create a new ApplicationEvent. + * + * @param serviceBean {@link ServiceBean} bean + */ + public ServiceBeanExportedEvent(ServiceBean serviceBean) { + super(serviceBean); + } + + /** + * Get {@link ServiceBean} instance + * + * @return non-null + */ + public ServiceBean getServiceBean() { + return (ServiceBean) super.getSource(); + } +} diff --git a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/convert/converter/StringArrayToMapConverter.java b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/convert/converter/StringArrayToMapConverter.java deleted file mode 100644 index 56c6d4c4ccd..00000000000 --- a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/convert/converter/StringArrayToMapConverter.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 com.alibaba.dubbo.config.spring.convert.converter; - -import com.alibaba.dubbo.common.utils.CollectionUtils; -import org.springframework.core.convert.converter.Converter; -import org.springframework.util.ObjectUtils; - -import java.util.Map; - -/** - * {@link String}[] to {@link Map} {@link Converter} - * - * @see CollectionUtils#toStringMap(String[]) - * @since 2.5.11 - */ -public class StringArrayToMapConverter implements Converter> { - - @Override - public Map convert(String[] source) { - return ObjectUtils.isEmpty(source) ? null : CollectionUtils.toStringMap(source); - } - -} diff --git a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/convert/converter/StringArrayToStringConverter.java b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/convert/converter/StringArrayToStringConverter.java deleted file mode 100644 index 23e948b0644..00000000000 --- a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/convert/converter/StringArrayToStringConverter.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 com.alibaba.dubbo.config.spring.convert.converter; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; - - -/** - * String[] to String {@ConditionalGenericConverter} - * - * @see StringUtils#arrayToCommaDelimitedString(Object[]) - * @since 2.5.11 - */ -public class StringArrayToStringConverter implements Converter { - - @Override - public String convert(String[] source) { - return ObjectUtils.isEmpty(source) ? null : StringUtils.arrayToCommaDelimitedString(source); - } - -} diff --git a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/util/AnnotationUtils.java b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/util/AnnotationUtils.java index aa15e567eb0..f9b3c61ebd8 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/util/AnnotationUtils.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/util/AnnotationUtils.java @@ -16,7 +16,11 @@ */ package com.alibaba.dubbo.config.spring.util; +import com.alibaba.dubbo.config.annotation.Reference; +import com.alibaba.dubbo.config.annotation.Service; + import org.springframework.core.env.PropertyResolver; +import org.springframework.util.StringUtils; import java.lang.annotation.Annotation; import java.util.HashSet; @@ -86,4 +90,44 @@ public static Map getAttributes(Annotation annotation, PropertyR } -} + public static String resolveInterfaceName(Service service, Class defaultInterfaceClass) + throws IllegalStateException { + + String interfaceName; + if (StringUtils.hasText(service.interfaceName())) { + interfaceName = service.interfaceName(); + } else if (!void.class.equals(service.interfaceClass())) { + interfaceName = service.interfaceClass().getName(); + } else if (defaultInterfaceClass.isInterface()) { + interfaceName = defaultInterfaceClass.getName(); + } else { + throw new IllegalStateException( + "The @Service undefined interfaceClass or interfaceName, and the type " + + defaultInterfaceClass.getName() + " is not a interface."); + } + + return interfaceName; + + } + + public static String resolveInterfaceName(Reference reference, Class defaultInterfaceClass) + throws IllegalStateException { + + String interfaceName; + if (!"".equals(reference.interfaceName())) { + interfaceName = reference.interfaceName(); + } else if (!void.class.equals(reference.interfaceClass())) { + interfaceName = reference.interfaceClass().getName(); + } else if (defaultInterfaceClass.isInterface()) { + interfaceName = defaultInterfaceClass.getName(); + } else { + throw new IllegalStateException( + "The @Reference undefined interfaceClass or interfaceName, and the type " + + defaultInterfaceClass.getName() + " is not a interface."); + } + + return interfaceName; + + } + +} \ No newline at end of file diff --git a/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessorTest.java b/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessorTest.java index a1b5807cbf9..eba43b2a599 100644 --- a/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessorTest.java +++ b/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessorTest.java @@ -19,26 +19,23 @@ import com.alibaba.dubbo.config.annotation.Reference; import com.alibaba.dubbo.config.spring.ReferenceBean; import com.alibaba.dubbo.config.spring.api.DemoService; -import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan; -import org.junit.After; import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; +import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.InjectionMetadata; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.ImportResource; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; import java.util.Collection; import java.util.Map; import static com.alibaba.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor.BEAN_NAME; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; /** @@ -46,50 +43,48 @@ * * @since 2.5.7 */ +@RunWith(SpringRunner.class) +@ContextConfiguration( + classes = { + ServiceAnnotationTestConfiguration.class, + ReferenceAnnotationBeanPostProcessorTest.class + }) +@TestPropertySource(properties = { + "packagesToScan = com.alibaba.dubbo.config.spring.context.annotation.provider", + "consumer.version = ${demo.service.version}", + "consumer.url = dubbo://127.0.0.1:12345", +}) public class ReferenceAnnotationBeanPostProcessorTest { - private ConfigurableApplicationContext providerApplicationContext; - - @BeforeClass - public static void prepare() { - System.setProperty("provider.version", "1.2"); - System.setProperty("package1", "com.alibaba.dubbo.config.spring.annotation.provider"); - System.setProperty("packagesToScan", "${package1}"); - System.setProperty("consumer.version", "1.2"); - System.setProperty("consumer.url", "dubbo://127.0.0.1:12345"); + @Bean + public TestBean testBean() { + return new TestBean(); } - @Before - public void init() { - // Starts Provider - providerApplicationContext = new AnnotationConfigApplicationContext(ServiceAnnotationBeanPostProcessorTest.TestConfiguration.class); + @Bean(BEAN_NAME) + public ReferenceAnnotationBeanPostProcessor referenceAnnotationBeanPostProcessor() { + return new ReferenceAnnotationBeanPostProcessor(); } - @After - public void destroy() { - // Shutdowns Provider - providerApplicationContext.close(); - } + @Autowired + private ConfigurableApplicationContext context; @Test public void test() throws Exception { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestBean.class); - TestBean testBean = context.getBean(TestBean.class); + DemoService demoService = testBean.getDemoService(); + + Assert.assertEquals("Hello,Mercy", demoService.sayName("Mercy")); + Assert.assertNotNull(testBean.getDemoServiceFromAncestor()); Assert.assertNotNull(testBean.getDemoServiceFromParent()); Assert.assertNotNull(testBean.getDemoService()); - Assert.assertEquals(testBean.getDemoServiceFromAncestor(), testBean.getDemoServiceFromParent()); - Assert.assertEquals(testBean.getDemoService(), testBean.getDemoServiceFromParent()); - - DemoService demoService = testBean.getDemoService(); - - Assert.assertEquals("annotation:Mercy", demoService.sayName("Mercy")); - - context.close(); + Assert.assertEquals("Hello,Mercy", testBean.getDemoServiceFromAncestor().sayName("Mercy")); + Assert.assertEquals("Hello,Mercy", testBean.getDemoServiceFromParent().sayName("Mercy")); + Assert.assertEquals("Hello,Mercy", testBean.getDemoService().sayName("Mercy")); } @@ -99,8 +94,6 @@ public void test() throws Exception { @Test public void testGetReferenceBeans() { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestBean.class); - ReferenceAnnotationBeanPostProcessor beanPostProcessor = context.getBean(BEAN_NAME, ReferenceAnnotationBeanPostProcessor.class); @@ -112,17 +105,13 @@ public void testGetReferenceBeans() { TestBean testBean = context.getBean(TestBean.class); - Assert.assertEquals(referenceBean.get(), testBean.getDemoServiceFromAncestor()); - Assert.assertEquals(referenceBean.get(), testBean.getDemoServiceFromParent()); - Assert.assertEquals(referenceBean.get(), testBean.getDemoService()); + Assert.assertNotNull(referenceBean.get()); } @Test public void testGetInjectedFieldReferenceBeanMap() { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestBean.class); - ReferenceAnnotationBeanPostProcessor beanPostProcessor = context.getBean(BEAN_NAME, ReferenceAnnotationBeanPostProcessor.class); @@ -136,12 +125,12 @@ public void testGetInjectedFieldReferenceBeanMap() { InjectionMetadata.InjectedElement injectedElement = entry.getKey(); - Assert.assertEquals("com.alibaba.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor$ReferenceFieldElement", + Assert.assertEquals("com.alibaba.spring.beans.factory.annotation.AnnotationInjectedBeanPostProcessor$AnnotatedFieldElement", injectedElement.getClass().getName()); ReferenceBean referenceBean = entry.getValue(); - Assert.assertEquals("1.2", referenceBean.getVersion()); + Assert.assertEquals("2.5.7", referenceBean.getVersion()); Assert.assertEquals("dubbo://127.0.0.1:12345", referenceBean.getUrl()); } @@ -151,8 +140,6 @@ public void testGetInjectedFieldReferenceBeanMap() { @Test public void testGetInjectedMethodReferenceBeanMap() { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestBean.class); - ReferenceAnnotationBeanPostProcessor beanPostProcessor = context.getBean(BEAN_NAME, ReferenceAnnotationBeanPostProcessor.class); @@ -166,36 +153,35 @@ public void testGetInjectedMethodReferenceBeanMap() { InjectionMetadata.InjectedElement injectedElement = entry.getKey(); - Assert.assertEquals("com.alibaba.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor$ReferenceMethodElement", + Assert.assertEquals("com.alibaba.spring.beans.factory.annotation.AnnotationInjectedBeanPostProcessor$AnnotatedMethodElement", injectedElement.getClass().getName()); ReferenceBean referenceBean = entry.getValue(); - Assert.assertEquals("1.2", referenceBean.getVersion()); + Assert.assertEquals("2.5.7", referenceBean.getVersion()); Assert.assertEquals("dubbo://127.0.0.1:12345", referenceBean.getUrl()); } } - @Test - public void testModuleInfo() { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestBean.class); - - ReferenceAnnotationBeanPostProcessor beanPostProcessor = context.getBean(BEAN_NAME, - ReferenceAnnotationBeanPostProcessor.class); - - - Map> referenceBeanMap = - beanPostProcessor.getInjectedMethodReferenceBeanMap(); - - for (Map.Entry> entry : referenceBeanMap.entrySet()) { - ReferenceBean referenceBean = entry.getValue(); - - assertThat(referenceBean.getModule().getName(),is("defaultModule")); - assertThat(referenceBean.getMonitor(), not(nullValue())); - } - } +// @Test +// public void testModuleInfo() { +// +// ReferenceAnnotationBeanPostProcessor beanPostProcessor = context.getBean(BEAN_NAME, +// ReferenceAnnotationBeanPostProcessor.class); +// +// +// Map> referenceBeanMap = +// beanPostProcessor.getInjectedMethodReferenceBeanMap(); +// +// for (Map.Entry> entry : referenceBeanMap.entrySet()) { +// ReferenceBean referenceBean = entry.getValue(); +// +// assertThat(referenceBean.getModule().getName(), is("defaultModule")); +// assertThat(referenceBean.getMonitor(), not(nullValue())); +// } +// } private static class AncestorBean { @@ -209,7 +195,7 @@ public DemoService getDemoServiceFromAncestor() { return demoServiceFromAncestor; } - @Reference(version = "1.2", url = "dubbo://127.0.0.1:12345") + @Reference(version = "2.5.7", url = "dubbo://127.0.0.1:12345") public void setDemoServiceFromAncestor(DemoService demoServiceFromAncestor) { this.demoServiceFromAncestor = demoServiceFromAncestor; } @@ -233,8 +219,6 @@ public DemoService getDemoServiceFromParent() { } - @ImportResource("META-INF/spring/dubbo-annotation-consumer.xml") - @DubboComponentScan(basePackageClasses = ReferenceAnnotationBeanPostProcessorTest.class) static class TestBean extends ParentBean { private DemoService demoService; @@ -246,7 +230,7 @@ public DemoService getDemoService() { return demoService; } - @Reference(version = "1.2", url = "dubbo://127.0.0.1:12345") + @Reference(version = "2.5.7", url = "dubbo://127.0.0.1:12345") public void setDemoService(DemoService demoService) { this.demoService = demoService; } diff --git a/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationBeanPostProcessorTest.java b/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationBeanPostProcessorTest.java index c0964418144..6133c48aaba 100644 --- a/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationBeanPostProcessorTest.java +++ b/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationBeanPostProcessorTest.java @@ -25,9 +25,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.ImportResource; -import org.springframework.context.annotation.PropertySource; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; @@ -41,17 +38,25 @@ */ @RunWith(SpringRunner.class) @ContextConfiguration( - classes = {ServiceAnnotationBeanPostProcessorTest.TestConfiguration.class}) + classes = { + ServiceAnnotationTestConfiguration.class, + ServiceAnnotationBeanPostProcessorTest.class + }) @TestPropertySource(properties = { - "package1 = com.alibaba.dubbo.config.spring.context.annotation", - "packagesToScan = ${package1}", - "provider.version = 1.2" + "provider.package = com.alibaba.dubbo.config.spring.context.annotation.provider", + "packagesToScan = ${provider.package}", }) public class ServiceAnnotationBeanPostProcessorTest { @Autowired private ConfigurableListableBeanFactory beanFactory; + @Bean + public ServiceAnnotationBeanPostProcessor serviceAnnotationBeanPostProcessor2 + (@Value("${packagesToScan}") String... packagesToScan) { + return new ServiceAnnotationBeanPostProcessor(packagesToScan); + } + @Test public void test() { @@ -61,38 +66,16 @@ public void test() { Map serviceBeansMap = beanFactory.getBeansOfType(ServiceBean.class); - Assert.assertEquals(3, serviceBeansMap.size()); + Assert.assertEquals(2, serviceBeansMap.size()); Map beanPostProcessorsMap = beanFactory.getBeansOfType(ServiceAnnotationBeanPostProcessor.class); - Assert.assertEquals(4, beanPostProcessorsMap.size()); + Assert.assertEquals(2, beanPostProcessorsMap.size()); - Assert.assertTrue(beanPostProcessorsMap.containsKey("doubleServiceAnnotationBeanPostProcessor")); - Assert.assertTrue(beanPostProcessorsMap.containsKey("emptyServiceAnnotationBeanPostProcessor")); Assert.assertTrue(beanPostProcessorsMap.containsKey("serviceAnnotationBeanPostProcessor")); Assert.assertTrue(beanPostProcessorsMap.containsKey("serviceAnnotationBeanPostProcessor2")); } - @ImportResource("META-INF/spring/dubbo-annotation-provider.xml") - @PropertySource("META-INF/default.properties") - @ComponentScan("com.alibaba.dubbo.config.spring.context.annotation.provider") - public static class TestConfiguration { - - @Bean - public ServiceAnnotationBeanPostProcessor serviceAnnotationBeanPostProcessor - (@Value("${packagesToScan}") String... packagesToScan) { - return new ServiceAnnotationBeanPostProcessor(packagesToScan); - } - - @Bean - public ServiceAnnotationBeanPostProcessor serviceAnnotationBeanPostProcessor2 - (@Value("${packagesToScan}") String... packagesToScan) { - return new ServiceAnnotationBeanPostProcessor(packagesToScan); - } - - - } - } diff --git a/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationTestConfiguration.java b/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationTestConfiguration.java new file mode 100644 index 00000000000..2e20797fa15 --- /dev/null +++ b/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationTestConfiguration.java @@ -0,0 +1,114 @@ +/* + * 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 com.alibaba.dubbo.config.spring.beans.factory.annotation; + +import com.alibaba.dubbo.config.ApplicationConfig; +import com.alibaba.dubbo.config.ProtocolConfig; +import com.alibaba.dubbo.config.RegistryConfig; +import com.alibaba.dubbo.config.annotation.Service; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.PropertySource; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.TransactionStatus; + +/** + * {@link Service} Bean + * + * @since 2.6.5 + */ +@PropertySource("META-INF/default.properties") +public class ServiceAnnotationTestConfiguration { + + /** + * Current application configuration, to replace XML config: + * + * <dubbo:application name="dubbo-annotation-provider"/> + * + * + * @return {@link ApplicationConfig} Bean + */ + @Bean("dubbo-annotation-provider") + public ApplicationConfig applicationConfig() { + ApplicationConfig applicationConfig = new ApplicationConfig(); + applicationConfig.setName("dubbo-annotation-provider"); + return applicationConfig; + } + + /** + * Current registry center configuration, to replace XML config: + * + * <dubbo:registry id="my-registry" address="N/A"/> + * + * + * @return {@link RegistryConfig} Bean + */ + @Bean("my-registry") + public RegistryConfig registryConfig() { + RegistryConfig registryConfig = new RegistryConfig(); + registryConfig.setAddress("N/A"); + return registryConfig; + } + + /** + * Current protocol configuration, to replace XML config: + * + * <dubbo:protocol name="dubbo" port="12345"/> + * + * + * @return {@link ProtocolConfig} Bean + */ + @Bean("dubbo") + public ProtocolConfig protocolConfig() { + ProtocolConfig protocolConfig = new ProtocolConfig(); + protocolConfig.setName("dubbo"); + protocolConfig.setPort(12345); + return protocolConfig; + } + + @Primary + @Bean + public PlatformTransactionManager platformTransactionManager() { + return new PlatformTransactionManager() { + + @Override + public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException { + return null; + } + + @Override + public void commit(TransactionStatus status) throws TransactionException { + + } + + @Override + public void rollback(TransactionStatus status) throws TransactionException { + + } + }; + } + + @Bean + public ServiceAnnotationBeanPostProcessor serviceAnnotationBeanPostProcessor + (@Value("${packagesToScan}") String... packagesToScan) { + return new ServiceAnnotationBeanPostProcessor(packagesToScan); + } + +} \ No newline at end of file diff --git a/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceBeanNameBuilderTest.java b/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceBeanNameBuilderTest.java new file mode 100644 index 00000000000..8ee0c4b2d48 --- /dev/null +++ b/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/beans/factory/annotation/ServiceBeanNameBuilderTest.java @@ -0,0 +1,75 @@ +/* + * 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 com.alibaba.dubbo.config.spring.beans.factory.annotation; + +import com.alibaba.dubbo.config.annotation.Reference; +import com.alibaba.dubbo.config.annotation.Service; +import com.alibaba.dubbo.config.spring.api.DemoService; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.mock.env.MockEnvironment; +import org.springframework.util.ReflectionUtils; + +import static com.alibaba.dubbo.config.spring.beans.factory.annotation.ServiceBeanNameBuilderTest.GROUP; +import static com.alibaba.dubbo.config.spring.beans.factory.annotation.ServiceBeanNameBuilderTest.VERSION; + +/** + * {@link ServiceBeanNameBuilder} Test + * + * @see ServiceBeanNameBuilder + * @since 2.6.5 + */ +@Service(interfaceClass = DemoService.class, group = GROUP, version = VERSION, + application = "application", module = "module", registry = {"1", "2", "3"}) +public class ServiceBeanNameBuilderTest { + + @Reference(interfaceClass = DemoService.class, group = "DUBBO", version = "1.0.0", + application = "application", module = "module", registry = {"1", "2", "3"}) + static final Class INTERFACE_CLASS = DemoService.class; + + static final String GROUP = "DUBBO"; + + static final String VERSION = "1.0.0"; + + static final String BEAN_NAME = "ServiceBean:com.alibaba.dubbo.config.spring.api.DemoService:1.0.0:DUBBO"; + + private MockEnvironment environment = new MockEnvironment(); + + @Test + public void testRequiredAttributes() { + ServiceBeanNameBuilder builder = ServiceBeanNameBuilder.create(INTERFACE_CLASS, environment); + Assert.assertEquals("ServiceBean:com.alibaba.dubbo.config.spring.api.DemoService", builder.build()); + } + + @Test + public void testServiceAnnotation() { + Service service = AnnotationUtils.getAnnotation(ServiceBeanNameBuilderTest.class, Service.class); + ServiceBeanNameBuilder builder = ServiceBeanNameBuilder.create(service, INTERFACE_CLASS, environment); + Assert.assertEquals(BEAN_NAME, + builder.build()); + } + + @Test + public void testReferenceAnnotation() { + Reference reference = AnnotationUtils.getAnnotation(ReflectionUtils.findField(ServiceBeanNameBuilderTest.class, "INTERFACE_CLASS"), Reference.class); + ServiceBeanNameBuilder builder = ServiceBeanNameBuilder.create(reference, INTERFACE_CLASS, environment); + Assert.assertEquals(BEAN_NAME, + builder.build()); + } + +} diff --git a/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/convert/converter/StringArrayToMapConverterTest.java b/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/convert/converter/StringArrayToMapConverterTest.java deleted file mode 100644 index 51be7c3f2f2..00000000000 --- a/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/convert/converter/StringArrayToMapConverterTest.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 com.alibaba.dubbo.config.spring.convert.converter; - -import org.junit.Assert; -import org.junit.Test; - -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * {@link StringArrayToMapConverter} Test - */ -public class StringArrayToMapConverterTest { - - @Test - public void testConvert() { - - StringArrayToMapConverter converter = new StringArrayToMapConverter(); - - Map value = converter.convert(new String[]{"Hello", "World"}); - - Map expected = new LinkedHashMap(); - - expected.put("Hello", "World"); - - Assert.assertEquals(expected, value); - - value = converter.convert(new String[]{}); - - Assert.assertNull(value); - - value = converter.convert(null); - - Assert.assertNull(value); - - } -} diff --git a/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/convert/converter/StringArrayToStringConverterTest.java b/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/convert/converter/StringArrayToStringConverterTest.java deleted file mode 100644 index 67e82479235..00000000000 --- a/dubbo-config/dubbo-config-spring/src/test/java/com/alibaba/dubbo/config/spring/convert/converter/StringArrayToStringConverterTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 com.alibaba.dubbo.config.spring.convert.converter; - -import org.junit.Assert; -import org.junit.Test; - -/** - * {@link StringArrayToStringConverter} Test - */ -public class StringArrayToStringConverterTest { - - @Test - public void testConvert() { - - StringArrayToStringConverter converter = new StringArrayToStringConverter(); - - String value = converter.convert(new String[]{"Hello", "World"}); - - Assert.assertEquals("Hello,World", value); - - value = converter.convert(new String[]{}); - - Assert.assertNull(value); - - value = converter.convert(null); - - Assert.assertNull(value); - - } - -}