Repository: ctripcorp/apollo
Pull request #3602 (2021-03-13 11:14:58)

Allow users to inject customized instance via ApolloInjectorCustomizer
## What's the purpose of this PR

Allow users to inject customized instances via ApolloInjectorCustomizer

## Which issue(s) this PR fixes:
Fixes #3573

## Brief changelog

1. Add ApolloInjectorCustomizer
2. Load customized instances in DefaultInjector

Follow this checklist to help us incorporate your contribution quickly and easily:

- [x] Read the [Contributing Guide](https://github.com/ctripcorp/apollo/blob/master/CONTRIBUTING.md) before making this pull request.
- [x] Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
- [x] Write necessary unit tests to verify the code.
- [x] Run `mvn clean test` to make sure this pull request doesn't break anything.


codecov-io: # [Codecov](https://codecov.io/gh/ctripcorp/apollo/pull/3602?src=pr&el=h1) Report
> Merging [#3602](https://codecov.io/gh/ctripcorp/apollo/pull/3602?src=pr&el=desc) (0718c18) into [master](https://codecov.io/gh/ctripcorp/apollo/commit/d05979590d99ae142445c104003d7c5a05804e32?el=desc) (d059795) will **decrease** coverage by `0.03%`.
> The diff coverage is `78.94%`.

[![Impacted file tree graph](https://codecov.io/gh/ctripcorp/apollo/pull/3602/graphs/tree.svg?width=650&height=150&src=pr&token=2USkCxREbZ)](https://codecov.io/gh/ctripcorp/apollo/pull/3602?src=pr&el=tree)

```diff
@@             Coverage Diff              @@
##             master    #3602      +/-   ##
============================================
- Coverage     50.98%   50.95%   -0.04%     
- Complexity     2330     2331       +1     
============================================
  Files           448      448              
  Lines         14006    14021      +15     
  Branches       1424     1426       +2     
============================================
+ Hits           7141     7144       +3     
- Misses         6376     6389      +13     
+ Partials        489      488       -1     
```


| [Impacted Files](https://codecov.io/gh/ctripcorp/apollo/pull/3602?src=pr&el=tree) | Coverage Δ | Complexity Δ | |
|---|---|---|---|
| [...ip/framework/apollo/internals/DefaultInjector.java](https://codecov.io/gh/ctripcorp/apollo/pull/3602/diff?src=pr&el=tree#diff-YXBvbGxvLWNsaWVudC9zcmMvbWFpbi9qYXZhL2NvbS9jdHJpcC9mcmFtZXdvcmsvYXBvbGxvL2ludGVybmFscy9EZWZhdWx0SW5qZWN0b3IuamF2YQ==) | `70.00% <75.00%> (+2.00%)` | `7.00 <6.00> (+4.00)` | |
| [...amework/foundation/internals/ServiceBootstrap.java](https://codecov.io/gh/ctripcorp/apollo/pull/3602/diff?src=pr&el=tree#diff-YXBvbGxvLWNvcmUvc3JjL21haW4vamF2YS9jb20vY3RyaXAvZnJhbWV3b3JrL2ZvdW5kYXRpb24vaW50ZXJuYWxzL1NlcnZpY2VCb290c3RyYXAuamF2YQ==) | `94.44% <100.00%> (ø)` | `6.00 <0.00> (ø)` | |
| [...work/apollo/biz/message/DatabaseMessageSender.java](https://codecov.io/gh/ctripcorp/apollo/pull/3602/diff?src=pr&el=tree#diff-YXBvbGxvLWJpei9zcmMvbWFpbi9qYXZhL2NvbS9jdHJpcC9mcmFtZXdvcmsvYXBvbGxvL2Jpei9tZXNzYWdlL0RhdGFiYXNlTWVzc2FnZVNlbmRlci5qYXZh) | `50.00% <0.00%> (-14.59%)` | `6.00% <0.00%> (-2.00%)` | |
| [...o/openapi/filter/ConsumerAuthenticationFilter.java](https://codecov.io/gh/ctripcorp/apollo/pull/3602/diff?src=pr&el=tree#diff-YXBvbGxvLXBvcnRhbC9zcmMvbWFpbi9qYXZhL2NvbS9jdHJpcC9mcmFtZXdvcmsvYXBvbGxvL29wZW5hcGkvZmlsdGVyL0NvbnN1bWVyQXV0aGVudGljYXRpb25GaWx0ZXIuamF2YQ==) | `94.11% <0.00%> (-5.89%)` | `4.00% <0.00%> (-1.00%)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/ctripcorp/apollo/pull/3602?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/ctripcorp/apollo/pull/3602?src=pr&el=footer). Last update [d059795...0718c18](https://codecov.io/gh/ctripcorp/apollo/pull/3602?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultInjector.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultInjector.java
index 1f20f21e9..be1f9d94c 100644
--- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultInjector.java
+++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultInjector.java
@@ -1,6 +1,7 @@
 package com.ctrip.framework.apollo.internals;
 
 import com.ctrip.framework.apollo.exceptions.ApolloConfigException;
+import com.ctrip.framework.apollo.spi.ApolloInjectorCustomizer;
 import com.ctrip.framework.apollo.spi.ConfigFactory;
 import com.ctrip.framework.apollo.spi.ConfigFactoryManager;
 import com.ctrip.framework.apollo.spi.ConfigRegistry;
@@ -14,20 +15,24 @@ import com.ctrip.framework.apollo.util.factory.PropertiesFactory;
 import com.ctrip.framework.apollo.util.http.HttpUtil;
 
 import com.ctrip.framework.apollo.util.yaml.YamlParser;
+import com.ctrip.framework.foundation.internals.ServiceBootstrap;
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
 import com.google.inject.Singleton;
+import java.util.List;
 
 /**
  * Guice injector
  * @author Jason Song(song_s@ctrip.com)
  */
 public class DefaultInjector implements Injector {
-  private com.google.inject.Injector m_injector;
+  private final com.google.inject.Injector m_injector;
+  private final List<ApolloInjectorCustomizer> m_customizers;
 
   public DefaultInjector() {
     try {
       m_injector = Guice.createInjector(new ApolloModule());
+      m_customizers = ServiceBootstrap.loadAllOrdered(ApolloInjectorCustomizer.class);
     } catch (Throwable ex) {
       ApolloConfigException exception = new ApolloConfigException("Unable to initialize Guice Injector!", ex);
       Tracer.logError(exception);
@@ -38,6 +43,12 @@ public class DefaultInjector implements Injector {
   @Override
   public <T> T getInstance(Class<T> clazz) {
     try {
+      for (ApolloInjectorCustomizer customizer : m_customizers) {
+        T instance = customizer.getInstance(clazz);
+        if (instance != null) {
+          return instance;
+        }
+      }
       return m_injector.getInstance(clazz);
     } catch (Throwable ex) {
       Tracer.logError(ex);
@@ -48,8 +59,20 @@ public class DefaultInjector implements Injector {
 
   @Override
   public <T> T getInstance(Class<T> clazz, String name) {
-    //Guice does not support get instance by type and name
-    return null;
+    try {
+      for (ApolloInjectorCustomizer customizer : m_customizers) {
+        T instance = customizer.getInstance(clazz, name);
+        if (instance != null) {
+          return instance;
+        }
+      }
+      //Guice does not support get instance by type and name
+      return null;
+    } catch (Throwable ex) {
+      Tracer.logError(ex);
+      throw new ApolloConfigException(
+          String.format("Unable to load instance for %s with name %s!", clazz.getName(), name), ex);
+    }
   }
 
   private static class ApolloModule extends AbstractModule {
diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/ApolloInjectorCustomizer.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/ApolloInjectorCustomizer.java
new file mode 100644
index 000000000..f678ea99f
--- /dev/null
+++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/ApolloInjectorCustomizer.java
@@ -0,0 +1,12 @@
+package com.ctrip.framework.apollo.spi;
+
+import com.ctrip.framework.apollo.core.spi.Ordered;
+import com.ctrip.framework.apollo.internals.DefaultInjector;
+import com.ctrip.framework.apollo.internals.Injector;
+
+/**
+ * Allow users to inject customized instances, see {@link DefaultInjector#getInstance(java.lang.Class)}
+ */
+public interface ApolloInjectorCustomizer extends Injector, Ordered {
+
+}
diff --git a/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/ServiceBootstrap.java b/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/ServiceBootstrap.java
index 4ae8bbba7..bfe272c7a 100644
--- a/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/ServiceBootstrap.java
+++ b/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/ServiceBootstrap.java
@@ -27,13 +27,6 @@ public class ServiceBootstrap {
 
   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
@@ -49,6 +42,13 @@ public class ServiceBootstrap {
   public static <S extends Ordered> S loadPrimary(Class<S> clazz) {
     List<S> candidates = loadAllOrdered(clazz);
 
+    if (candidates.isEmpty()) {
+      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()));
+    }
+
+
     return candidates.get(0);
   }
 }


Test patch:
diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/build/MockInjector.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/build/MockInjector.java
index 0e4591ad5..2c14d095f 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/build/MockInjector.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/build/MockInjector.java
@@ -1,28 +1,24 @@
 package com.ctrip.framework.apollo.build;
 
-import java.util.Map;
-
 import com.ctrip.framework.apollo.internals.DefaultInjector;
 import com.ctrip.framework.apollo.internals.Injector;
+import com.ctrip.framework.apollo.spi.ApolloInjectorCustomizer;
 import com.google.common.collect.HashBasedTable;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Table;
+import java.util.Map;
 
 /**
  * @author Jason Song(song_s@ctrip.com)
  */
 public class MockInjector implements Injector {
+
   private static Map<Class, Object> classMap = Maps.newHashMap();
   private static Table<Class, String, Object> classTable = HashBasedTable.create();
   private static Injector delegate = new DefaultInjector();
 
   @Override
   public <T> T getInstance(Class<T> clazz) {
-    T o = (T) classMap.get(clazz);
-    if (o != null) {
-      return o;
-    }
-
     if (delegate != null) {
       return delegate.getInstance(clazz);
     }
@@ -32,11 +28,6 @@ public class MockInjector implements Injector {
 
   @Override
   public <T> T getInstance(Class<T> clazz, String name) {
-    T o = (T) classTable.get(clazz, name);
-    if (o != null) {
-      return o;
-    }
-
     if (delegate != null) {
       return delegate.getInstance(clazz, name);
     }
@@ -61,4 +52,22 @@ public class MockInjector implements Injector {
     classTable.clear();
     delegate = new DefaultInjector();
   }
+
+  public static class InjectCustomizer implements ApolloInjectorCustomizer {
+
+    @Override
+    public <T> T getInstance(Class<T> clazz) {
+      return (T) classMap.get(clazz);
+    }
+
+    @Override
+    public <T> T getInstance(Class<T> clazz, String name) {
+      return (T) classTable.get(clazz, name);
+    }
+
+    @Override
+    public int getOrder() {
+      return 0;
+    }
+  }
 }
diff --git a/apollo-client/src/test/resources/META-INF/services/com.ctrip.framework.apollo.spi.ApolloInjectorCustomizer b/apollo-client/src/test/resources/META-INF/services/com.ctrip.framework.apollo.spi.ApolloInjectorCustomizer
new file mode 100644
index 000000000..42602228c
--- /dev/null
+++ b/apollo-client/src/test/resources/META-INF/services/com.ctrip.framework.apollo.spi.ApolloInjectorCustomizer
@@ -0,0 +1,1 @@
+com.ctrip.framework.apollo.build.MockInjector$InjectCustomizer
\ No newline at end of file
diff --git a/apollo-core/src/test/java/com/ctrip/framework/foundation/internals/ServiceBootstrapTest.java b/apollo-core/src/test/java/com/ctrip/framework/foundation/internals/ServiceBootstrapTest.java
index 627e87621..d4b0b080a 100644
--- a/apollo-core/src/test/java/com/ctrip/framework/foundation/internals/ServiceBootstrapTest.java
+++ b/apollo-core/src/test/java/com/ctrip/framework/foundation/internals/ServiceBootstrapTest.java
@@ -5,6 +5,7 @@ import org.junit.Test;
 
 import java.util.ServiceConfigurationError;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -37,6 +38,11 @@ public class ServiceBootstrapTest {
     ServiceBootstrap.loadFirst(Interface5.class);
   }
 
+  @Test
+  public void loadAllWithServiceFileButNoServiceImpl() {
+    assertFalse(ServiceBootstrap.loadAll(Interface7.class).hasNext());
+  }
+
   @Test
   public void loadPrimarySuccessfully() {
     Interface6 service = ServiceBootstrap.loadPrimary(Interface6.class);
@@ -48,6 +54,11 @@ public class ServiceBootstrapTest {
     ServiceBootstrap.loadPrimary(Interface7.class);
   }
 
+  @Test
+  public void loadAllOrderedWithServiceFileButNoServiceImpl() {
+    assertTrue(ServiceBootstrap.loadAllOrdered(Interface7.class).isEmpty());
+  }
+
   interface Interface1 {
   }