Skip to content

Commit

Permalink
client's SPI for customizing spring processors' loading (#2313)
Browse files Browse the repository at this point in the history
  • Loading branch information
liucl authored and nobodyiam committed Jun 7, 2019
1 parent 8d82dc1 commit 0446130
Show file tree
Hide file tree
Showing 18 changed files with 282 additions and 66 deletions.
@@ -1,47 +1,20 @@
package com.ctrip.framework.apollo.spring.annotation;

import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor;
import java.util.HashMap;
import java.util.Map;
import com.ctrip.framework.apollo.spring.spi.ApolloConfigRegistrarHelper;
import com.ctrip.framework.foundation.internals.ServiceBootstrap;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
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 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)
*/
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(),
PropertySourcesProcessor.class);
private ApolloConfigRegistrarHelper helper = ServiceBootstrap.loadPrimary(ApolloConfigRegistrarHelper.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 void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
helper.registerBeanDefinitions(importingClassMetadata, registry);
}
}
@@ -1,17 +1,10 @@
package com.ctrip.framework.apollo.spring.config;

import com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor;
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValueProcessor;
import java.util.HashMap;
import java.util.Map;
import com.ctrip.framework.apollo.spring.spi.ConfigPropertySourcesProcessorHelper;
import com.ctrip.framework.foundation.internals.ServiceBootstrap;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
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
Expand All @@ -21,31 +14,10 @@
public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor
implements BeanDefinitionRegistryPostProcessor {

private ConfigPropertySourcesProcessorHelper helper = ServiceBootstrap.loadPrimary(ConfigPropertySourcesProcessorHelper.class);

@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);
helper.postProcessBeanDefinitionRegistry(registry);
}
}
@@ -0,0 +1,10 @@
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);
}
@@ -0,0 +1,10 @@
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;
}
@@ -0,0 +1,51 @@
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;
}
}
@@ -0,0 +1,50 @@
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;
}
}
@@ -0,0 +1 @@
com.ctrip.framework.apollo.spring.spi.DefaultApolloConfigRegistrarHelper
@@ -0,0 +1 @@
com.ctrip.framework.apollo.spring.spi.DefaultConfigPropertySourcesProcessorHelper
@@ -0,0 +1,22 @@
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());
}
}
@@ -0,0 +1,22 @@
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());
}
}
@@ -0,0 +1,18 @@
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;
}
}
@@ -0,0 +1,18 @@
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;
}
}
@@ -0,0 +1 @@
com.ctrip.framework.apollo.spring.spi.TestRegistrarHelper
@@ -0,0 +1 @@
com.ctrip.framework.apollo.spring.spi.TestProcessorHelper
@@ -1,6 +1,11 @@
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.List;
import java.util.ServiceLoader;

public class ServiceBootstrap {
Expand All @@ -19,4 +24,31 @@ public static <S> Iterator<S> loadAll(Class<S> clazz) {

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);
}
}

0 comments on commit 0446130

Please sign in to comment.