Commit 04461302 authored by liuchunlei's avatar liuchunlei Committed by Jason Song

client's SPI for customizing spring processors' loading (#2313)

parent 8d82dc19
package com.ctrip.framework.apollo.spring.annotation; package com.ctrip.framework.apollo.spring.annotation;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor; import com.ctrip.framework.apollo.spring.spi.ApolloConfigRegistrarHelper;
import java.util.HashMap; import com.ctrip.framework.foundation.internals.ServiceBootstrap;
import java.util.Map;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
import com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor;
import com.ctrip.framework.apollo.spring.util.BeanRegistrationUtil;
import com.google.common.collect.Lists;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar { public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata
.getAnnotationAttributes(EnableApolloConfig.class.getName()));
String[] namespaces = attributes.getStringArray("value");
int order = attributes.getNumber("order");
PropertySourcesProcessor.addNamespaces(Lists.newArrayList(namespaces), order);
Map<String, Object> propertySourcesPlaceholderPropertyValues = new HashMap<>();
// to make sure the default PropertySourcesPlaceholderConfigurer's priority is higher than PropertyPlaceholderConfigurer
propertySourcesPlaceholderPropertyValues.put("order", 0);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class.getName(), private ApolloConfigRegistrarHelper helper = ServiceBootstrap.loadPrimary(ApolloConfigRegistrarHelper.class);
PropertySourcesProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(), @Override
ApolloAnnotationProcessor.class); public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
helper.registerBeanDefinitions(importingClassMetadata, registry);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class.getName(), SpringValueDefinitionProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(),
ApolloJsonValueProcessor.class);
} }
} }
package com.ctrip.framework.apollo.spring.config; package com.ctrip.framework.apollo.spring.config;
import com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor; import com.ctrip.framework.apollo.spring.spi.ConfigPropertySourcesProcessorHelper;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor; import com.ctrip.framework.foundation.internals.ServiceBootstrap;
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValueProcessor;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import com.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor;
import com.ctrip.framework.apollo.spring.util.BeanRegistrationUtil;
/** /**
* Apollo Property Sources processor for Spring XML Based Application * Apollo Property Sources processor for Spring XML Based Application
...@@ -21,31 +14,10 @@ import com.ctrip.framework.apollo.spring.util.BeanRegistrationUtil; ...@@ -21,31 +14,10 @@ import com.ctrip.framework.apollo.spring.util.BeanRegistrationUtil;
public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor
implements BeanDefinitionRegistryPostProcessor { implements BeanDefinitionRegistryPostProcessor {
private ConfigPropertySourcesProcessorHelper helper = ServiceBootstrap.loadPrimary(ConfigPropertySourcesProcessorHelper.class);
@Override @Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
Map<String, Object> propertySourcesPlaceholderPropertyValues = new HashMap<>(); helper.postProcessBeanDefinitionRegistry(registry);
// to make sure the default PropertySourcesPlaceholderConfigurer's priority is higher than PropertyPlaceholderConfigurer
propertySourcesPlaceholderPropertyValues.put("order", 0);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
ApolloAnnotationProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(),
ApolloJsonValueProcessor.class);
processSpringValueDefinition(registry);
}
/**
* For Spring 3.x versions, the BeanDefinitionRegistryPostProcessor would not be
* instantiated if it is added in postProcessBeanDefinitionRegistry phase, so we have to manually
* call the postProcessBeanDefinitionRegistry method of SpringValueDefinitionProcessor here...
*/
private void processSpringValueDefinition(BeanDefinitionRegistry registry) {
SpringValueDefinitionProcessor springValueDefinitionProcessor = new SpringValueDefinitionProcessor();
springValueDefinitionProcessor.postProcessBeanDefinitionRegistry(registry);
} }
} }
package com.ctrip.framework.apollo.spring.spi;
import com.ctrip.framework.apollo.core.spi.Ordered;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.type.AnnotationMetadata;
public interface ApolloConfigRegistrarHelper extends Ordered {
void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
package com.ctrip.framework.apollo.spring.spi;
import com.ctrip.framework.apollo.core.spi.Ordered;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
public interface ConfigPropertySourcesProcessorHelper extends Ordered {
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
package com.ctrip.framework.apollo.spring.spi;
import com.ctrip.framework.apollo.core.spi.Ordered;
import com.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor;
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValueProcessor;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor;
import com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor;
import com.ctrip.framework.apollo.spring.util.BeanRegistrationUtil;
import com.google.common.collect.Lists;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
public class DefaultApolloConfigRegistrarHelper implements ApolloConfigRegistrarHelper {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(EnableApolloConfig.class.getName()));
String[] namespaces = attributes.getStringArray("value");
int order = attributes.getNumber("order");
PropertySourcesProcessor.addNamespaces(Lists.newArrayList(namespaces), order);
Map<String, Object> propertySourcesPlaceholderPropertyValues = new HashMap<>();
// to make sure the default PropertySourcesPlaceholderConfigurer's priority is higher than PropertyPlaceholderConfigurer
propertySourcesPlaceholderPropertyValues.put("order", 0);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class.getName(),
PropertySourcesProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
ApolloAnnotationProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(),
SpringValueProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class.getName(),
SpringValueDefinitionProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(),
ApolloJsonValueProcessor.class);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
package com.ctrip.framework.apollo.spring.spi;
import com.ctrip.framework.apollo.core.spi.Ordered;
import com.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor;
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValueProcessor;
import com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor;
import com.ctrip.framework.apollo.spring.util.BeanRegistrationUtil;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
public class DefaultConfigPropertySourcesProcessorHelper implements ConfigPropertySourcesProcessorHelper {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
Map<String, Object> propertySourcesPlaceholderPropertyValues = new HashMap<>();
// to make sure the default PropertySourcesPlaceholderConfigurer's priority is higher than PropertyPlaceholderConfigurer
propertySourcesPlaceholderPropertyValues.put("order", 0);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
ApolloAnnotationProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(),
SpringValueProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(),
ApolloJsonValueProcessor.class);
processSpringValueDefinition(registry);
}
/**
* For Spring 3.x versions, the BeanDefinitionRegistryPostProcessor would not be instantiated if
* it is added in postProcessBeanDefinitionRegistry phase, so we have to manually call the
* postProcessBeanDefinitionRegistry method of SpringValueDefinitionProcessor here...
*/
private void processSpringValueDefinition(BeanDefinitionRegistry registry) {
SpringValueDefinitionProcessor springValueDefinitionProcessor = new SpringValueDefinitionProcessor();
springValueDefinitionProcessor.postProcessBeanDefinitionRegistry(registry);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
package com.ctrip.framework.apollo.spring.spi;
import static org.springframework.test.util.AssertionErrors.assertEquals;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigRegistrar;
import java.lang.reflect.Field;
import org.junit.Test;
import org.springframework.util.ReflectionUtils;
public class ApolloConfigRegistrarHelperTest {
@Test
public void testHelperLoadingOrder() {
ApolloConfigRegistrar apolloConfigRegistrar = new ApolloConfigRegistrar();
Field field = ReflectionUtils.findField(ApolloConfigRegistrar.class, "helper");
ReflectionUtils.makeAccessible(field);
Object helper = ReflectionUtils.getField(field, apolloConfigRegistrar);
assertEquals("helper is not TestRegistrarHelper instance", TestRegistrarHelper.class, helper.getClass());
}
}
package com.ctrip.framework.apollo.spring.spi;
import static org.springframework.test.util.AssertionErrors.assertEquals;
import com.ctrip.framework.apollo.spring.config.ConfigPropertySourcesProcessor;
import java.lang.reflect.Field;
import org.junit.Test;
import org.springframework.util.ReflectionUtils;
public class ConfigPropertySourcesProcessorHelperTest {
@Test
public void testHelperLoadingOrder() {
ConfigPropertySourcesProcessor processor = new ConfigPropertySourcesProcessor();
Field field = ReflectionUtils.findField(ConfigPropertySourcesProcessor.class, "helper");
ReflectionUtils.makeAccessible(field);
Object helper = ReflectionUtils.getField(field, processor);
assertEquals("helper is not TestProcessorHelper instance", TestProcessorHelper.class, helper.getClass());
}
}
package com.ctrip.framework.apollo.spring.spi;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
public class TestProcessorHelper extends DefaultConfigPropertySourcesProcessorHelper {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
super.postProcessBeanDefinitionRegistry(registry);
}
@Override
public int getOrder() {
return 0;
}
}
package com.ctrip.framework.apollo.spring.spi;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.type.AnnotationMetadata;
public class TestRegistrarHelper extends DefaultApolloConfigRegistrarHelper {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
super.registerBeanDefinitions(importingClassMetadata, registry);
}
@Override
public int getOrder() {
return 0;
}
}
package com.ctrip.framework.foundation.internals; package com.ctrip.framework.foundation.internals;
import com.ctrip.framework.apollo.core.spi.Ordered;
import com.google.common.collect.Lists;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader; import java.util.ServiceLoader;
public class ServiceBootstrap { public class ServiceBootstrap {
...@@ -19,4 +24,31 @@ public class ServiceBootstrap { ...@@ -19,4 +24,31 @@ public class ServiceBootstrap {
return loader.iterator(); return loader.iterator();
} }
public static <S extends Ordered> List<S> loadAllOrdered(Class<S> clazz) {
Iterator<S> iterator = loadAll(clazz);
if (!iterator.hasNext()) {
throw new IllegalStateException(String.format(
"No implementation defined in /META-INF/services/%s, please check whether the file exists and has the right implementation class!",
clazz.getName()));
}
List<S> candidates = Lists.newArrayList(iterator);
Collections.sort(candidates, new Comparator<S>() {
@Override
public int compare(S o1, S o2) {
// the smaller order has higher priority
return Integer.compare(o1.getOrder(), o2.getOrder());
}
});
return candidates;
}
public static <S extends Ordered> S loadPrimary(Class<S> clazz) {
List<S> candidates = loadAllOrdered(clazz);
return candidates.get(0);
}
} }
package com.ctrip.framework.foundation.internals; package com.ctrip.framework.foundation.internals;
import com.ctrip.framework.apollo.core.spi.Ordered;
import org.junit.Test; import org.junit.Test;
import java.util.ServiceConfigurationError; import java.util.ServiceConfigurationError;
...@@ -36,6 +37,17 @@ public class ServiceBootstrapTest { ...@@ -36,6 +37,17 @@ public class ServiceBootstrapTest {
ServiceBootstrap.loadFirst(Interface5.class); ServiceBootstrap.loadFirst(Interface5.class);
} }
@Test
public void loadPrimarySuccessfully() {
Interface6 service = ServiceBootstrap.loadPrimary(Interface6.class);
assertTrue(service instanceof Interface6Impl1);
}
@Test(expected = IllegalStateException.class)
public void loadPrimaryWithServiceFileButNoServiceImpl() {
ServiceBootstrap.loadPrimary(Interface7.class);
}
private interface Interface1 { private interface Interface1 {
} }
...@@ -53,4 +65,24 @@ public class ServiceBootstrapTest { ...@@ -53,4 +65,24 @@ public class ServiceBootstrapTest {
private interface Interface5 { private interface Interface5 {
} }
private interface Interface6 extends Ordered {
}
public static class Interface6Impl1 implements Interface6 {
@Override
public int getOrder() {
return 1;
}
}
public static class Interface6Impl2 implements Interface6 {
@Override
public int getOrder() {
return 2;
}
}
private interface Interface7 extends Ordered {
}
} }
com.ctrip.framework.foundation.internals.ServiceBootstrapTest$Interface6Impl1
com.ctrip.framework.foundation.internals.ServiceBootstrapTest$Interface6Impl2
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment