mirror of
https://github.com/lightbend/config.git
synced 2025-03-14 11:20:25 +08:00
Search for polymorphic types using ServiceLoader
This allows to have dynamic implementations of types, that cannot be defined as subtypes at compile time, but will be discovered at runtime.
This commit is contained in:
parent
c39ae8c7ca
commit
252fda11a7
@ -382,6 +382,21 @@ public class ConfigBeanImpl {
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Class<?>> subtypes2 = discoverServices(clazz);
|
||||
for (Map.Entry<String, Class<?>> entry : subtypes2.entrySet()) {
|
||||
// handle abstract
|
||||
if (Modifier.isAbstract(entry.getValue().getModifiers())) {
|
||||
result.putAll(findAllSubtypes(entry.getValue()));
|
||||
continue;
|
||||
}
|
||||
// add concrete type
|
||||
String typeName = findTypeName(entry.getValue());
|
||||
if (typeName == null) {
|
||||
typeName = entry.getKey();
|
||||
}
|
||||
result.putIfAbsent(typeName, entry.getValue());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -406,4 +421,45 @@ public class ConfigBeanImpl {
|
||||
return (tn == null) ? null : tn.value();
|
||||
}
|
||||
|
||||
private static Map<String, Class<?>> discoverServices(Class<?> clazz) {
|
||||
// using TCCL
|
||||
java.util.ServiceLoader<?> services = java.util.ServiceLoader.load(clazz);
|
||||
if (services.iterator().hasNext()) {
|
||||
return extractDiscoveredServices(services.iterator());
|
||||
} else {
|
||||
// By default ServiceLoader.load uses the TCCL, this may not be
|
||||
// enough in environment dealing with classloaders differently
|
||||
// such as OSGi. So we should try to use the classloader having
|
||||
// loaded this class.
|
||||
services = java.util.ServiceLoader.load(clazz, ConfigBeanImpl.class.getClassLoader());
|
||||
if (services.iterator().hasNext()) {
|
||||
return extractDiscoveredServices(services.iterator());
|
||||
} else {
|
||||
return java.util.Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Class<?>> extractDiscoveredServices(java.util.Iterator<?> servicesIterator) {
|
||||
Map<String, Class<?>> serviceClasses = new HashMap<String, Class<?>>(4);
|
||||
// use while(true) loop so that we can isolate all service loader
|
||||
// errors from .next and .hasNext to a single service
|
||||
while (true) {
|
||||
try {
|
||||
if (!servicesIterator.hasNext()) {
|
||||
break;
|
||||
}
|
||||
Class<?> serviceClass = servicesIterator.next().getClass();
|
||||
String typeName = findTypeName(serviceClass);
|
||||
if (typeName == null) {
|
||||
typeName = serviceClass.getSimpleName();
|
||||
}
|
||||
serviceClasses.put(typeName, serviceClass);
|
||||
} catch (java.util.ServiceConfigurationError error) {
|
||||
throw new IllegalArgumentException("Found error loading a service", error);
|
||||
}
|
||||
}
|
||||
return serviceClasses;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,43 @@
|
||||
package beanconfig.polymorphic;
|
||||
|
||||
import com.typesafe.config.ConfigTypeInfo;
|
||||
import com.typesafe.config.ConfigTypeName;
|
||||
|
||||
public class WithServiceLoader {
|
||||
|
||||
public interface ExampleTag {
|
||||
}
|
||||
|
||||
@ConfigTypeInfo
|
||||
public interface ExampleSPI extends ExampleTag {
|
||||
}
|
||||
|
||||
@ConfigTypeName("a")
|
||||
public static class ImplA implements ExampleSPI {
|
||||
|
||||
private String message;
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
@ConfigTypeName("b")
|
||||
public static class ImplB implements ExampleSPI {
|
||||
|
||||
private int value;
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package beanconfig.polymorphic;
|
||||
|
||||
import beanconfig.polymorphic.WithServiceLoader.ExampleSPI;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class WithServiceLoaderConfigs {
|
||||
|
||||
public static class SimpleServiceLoader {
|
||||
|
||||
private ExampleSPI singleTag;
|
||||
private List<ExampleSPI> otherTags;
|
||||
|
||||
public ExampleSPI getSingleTag() {
|
||||
return singleTag;
|
||||
}
|
||||
|
||||
public void setSingleTag(ExampleSPI singleTag) {
|
||||
this.singleTag = singleTag;
|
||||
}
|
||||
|
||||
public List<ExampleSPI> getOtherTags() {
|
||||
return otherTags;
|
||||
}
|
||||
|
||||
public void setOtherTags(List<ExampleSPI> otherTags) {
|
||||
this.otherTags = otherTags;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
beanconfig.polymorphic.WithServiceLoader$ImplA
|
||||
beanconfig.polymorphic.WithServiceLoader$ImplB
|
@ -0,0 +1 @@
|
||||
beanconfig.polymorphic.WithServiceLoader$ExampleSPI
|
@ -105,4 +105,21 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"withServiceLoader": {
|
||||
"singleTag": {
|
||||
"type": "a"
|
||||
"message": "bar"
|
||||
}
|
||||
"otherTags": [
|
||||
{
|
||||
"type": "a"
|
||||
"message": "baz"
|
||||
}
|
||||
{
|
||||
"type": "b"
|
||||
"value": 42
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ import beanconfig.polymorphic.TypeNamesConfigs._
|
||||
import beanconfig.polymorphic.VisibleTypeId._
|
||||
import beanconfig.polymorphic.VisibleTypeIdConfigs._
|
||||
import beanconfig.polymorphic.WithGenericsConfigs._
|
||||
import beanconfig.polymorphic.WithServiceLoader._
|
||||
import beanconfig.polymorphic.WithServiceLoaderConfigs._
|
||||
import beanconfig.polymorphic.{TypeNames, WithGenerics}
|
||||
import com.typesafe.config._
|
||||
import org.junit.Assert._
|
||||
@ -175,6 +177,23 @@ class ConfigPolymorphicBeanTest extends TestUtils {
|
||||
assertEquals(3, beanConfig.getWrapperWithDog.getAnimal.asInstanceOf[WithGenerics.Dog].getBoneCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testSimpleServiceLoader() {
|
||||
val beanConfig: SimpleServiceLoader =
|
||||
ConfigBeanFactory.create(loadConfig().getConfig("withServiceLoader"), classOf[SimpleServiceLoader])
|
||||
assertNotNull(beanConfig)
|
||||
|
||||
assertTrue(beanConfig.getSingleTag.isInstanceOf[ImplA])
|
||||
assertEquals("bar", beanConfig.getSingleTag.asInstanceOf[ImplA].getMessage)
|
||||
|
||||
assertEquals(2, beanConfig.getOtherTags.size)
|
||||
|
||||
assertTrue(beanConfig.getOtherTags.get(0).isInstanceOf[ImplA])
|
||||
assertEquals("baz", beanConfig.getOtherTags.get(0).asInstanceOf[ImplA].getMessage)
|
||||
assertTrue(beanConfig.getOtherTags.get(1).isInstanceOf[ImplB])
|
||||
assertEquals(42, beanConfig.getOtherTags.get(1).asInstanceOf[ImplB].getValue)
|
||||
}
|
||||
|
||||
private def loadConfig(): Config = {
|
||||
val configIs: InputStream = this.getClass().getClassLoader().getResourceAsStream("beanconfig/polymorphic01.conf")
|
||||
try {
|
||||
|
Loading…
Reference in New Issue
Block a user