From a339d6ce9beca584a6bcfbc7e2981ef8a4548979 Mon Sep 17 00:00:00 2001 From: Jesper Skov Date: Tue, 29 Mar 2016 15:52:59 +0200 Subject: [PATCH] Add factory to allow automatic field injection @PostConstruct methods are called after fields are injected. --- .../java/org/codejargon/feather/Feather.java | 59 +++++++++++++++++-- .../feather/FieldInjectionTest.java | 10 ++++ .../TransitiveFieldsDependencyTest.java | 37 ++++++++++++ 3 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 feather/src/test/java/org/codejargon/feather/TransitiveFieldsDependencyTest.java diff --git a/feather/src/main/java/org/codejargon/feather/Feather.java b/feather/src/main/java/org/codejargon/feather/Feather.java index d024f2d..0471525 100644 --- a/feather/src/main/java/org/codejargon/feather/Feather.java +++ b/feather/src/main/java/org/codejargon/feather/Feather.java @@ -1,5 +1,6 @@ package org.codejargon.feather; +import javax.annotation.PostConstruct; import javax.inject.*; import java.lang.annotation.Annotation; import java.lang.reflect.*; @@ -10,22 +11,40 @@ public class Feather { private final Map> providers = new ConcurrentHashMap<>(); private final Map singletons = new ConcurrentHashMap<>(); private final Map injectFields = new ConcurrentHashMap<>(0); + private final boolean autoInjectFields; /** * Constructs Feather with configuration modules */ public static Feather with(Object... modules) { - return new Feather(Arrays.asList(modules)); + return new Feather(false, Arrays.asList(modules)); + } + + /** + * Constructs Feather with configuration modules. + * Will auto-inject fields and call @PostConstruct on target. + */ + public static Feather withAutoInjectFields(Object... modules) { + return new Feather(true, Arrays.asList(modules)); } /** * Constructs Feather with configuration modules */ public static Feather with(Iterable modules) { - return new Feather(modules); + return new Feather(false, modules); } - private Feather(Iterable modules) { + /** + * Constructs Feather with configuration modules + * Will auto-inject fields and call @PostConstruct on target. + */ + public static Feather withAutoInjectFields(Iterable modules) { + return new Feather(true, modules); + } + + private Feather(boolean autoInjectFields, Iterable modules) { + this.autoInjectFields = autoInjectFields; providers.put(Key.of(Feather.class), new Provider() { @Override public Object get() { @@ -84,10 +103,33 @@ public void injectFields(Object target) { try { field.set(target, (boolean) f[1] ? provider(key) : instance(key)); } catch (Exception e) { - throw new FeatherException(String.format("Can't inject field %s in %s", field.getName(), target.getClass().getName())); + throw new FeatherException(String.format("Can't inject field %s in %s", field.getName(), target.getClass().getName()), e); } } + callPostConstructMethods(target); } + + private void callPostConstructMethods(Object target) { + List postConstructMethods = new ArrayList<>(); + for (Class clazz = target.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) { + for (Method method : Arrays.asList(clazz.getDeclaredMethods())) { + if (method.isAnnotationPresent(PostConstruct.class)) { + postConstructMethods.add(method); + } + } + } + + Collections.reverse(postConstructMethods); + for (Method m : postConstructMethods) { + try { + m.setAccessible(true); + m.invoke(target); + } catch (Exception e) { + throw new FeatherException(String.format("Can't call @PostConstruct method %s:%s", m.getClass(), m.getName()), e); + } + } + } + @SuppressWarnings("unchecked") private Provider provider(final Key key, Set chain) { @@ -98,7 +140,11 @@ private Provider provider(final Key key, Set chain) { @Override public Object get() { try { - return constructor.newInstance(params(paramProviders)); + Object o = constructor.newInstance(params(paramProviders)); + if (autoInjectFields) { + injectFields(o); + } + return o; } catch (Exception e) { throw new FeatherException(String.format("Can't instantiate %s", key.toString()), e); } @@ -215,13 +261,14 @@ private static Object[][] injectFields(Class target) { Object[][] fs = new Object[fields.size()][]; int i = 0; for (Field f : fields) { + Class providerType = f.getType().equals(Provider.class) ? (Class) ((ParameterizedType) f.getGenericType()).getActualTypeArguments()[0] : null; fs[i++] = new Object[]{ f, providerType != null, - Key.of(providerType != null ? providerType : f.getType(), qualifier(f.getAnnotations())) + Key.of((Class)(providerType != null ? providerType : f.getType()), qualifier(f.getAnnotations())) }; } return fs; diff --git a/feather/src/test/java/org/codejargon/feather/FieldInjectionTest.java b/feather/src/test/java/org/codejargon/feather/FieldInjectionTest.java index dd98fe5..d68f9c8 100644 --- a/feather/src/test/java/org/codejargon/feather/FieldInjectionTest.java +++ b/feather/src/test/java/org/codejargon/feather/FieldInjectionTest.java @@ -2,9 +2,11 @@ import org.junit.Test; +import javax.annotation.PostConstruct; import javax.inject.Inject; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; public class FieldInjectionTest { @Test @@ -13,12 +15,20 @@ public void fieldsInjected() { Target target = new Target(); feather.injectFields(target); assertNotNull(target.a); + assertTrue(target.postConstructCalled); } public static class Target { @Inject private A a; + + private boolean postConstructCalled; + + @PostConstruct + void postConstruct() { + postConstructCalled = true; + } } public static class A { diff --git a/feather/src/test/java/org/codejargon/feather/TransitiveFieldsDependencyTest.java b/feather/src/test/java/org/codejargon/feather/TransitiveFieldsDependencyTest.java new file mode 100644 index 0000000..c6b3ae1 --- /dev/null +++ b/feather/src/test/java/org/codejargon/feather/TransitiveFieldsDependencyTest.java @@ -0,0 +1,37 @@ +package org.codejargon.feather; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; + +import org.junit.Test; + +public class TransitiveFieldsDependencyTest { + @Test + public void transitiveFields() { + Feather feather = Feather.withAutoInjectFields(); + A a = feather.instance(A.class); + assertNotNull(a.b.c); + assertTrue(a.b.c.postConstructRan); + } + + public static class A { + @Inject private B b; + + } + + public static class B { + @Inject private C c; + } + + public static class C { + boolean postConstructRan; + + @PostConstruct + void postConstruct() { + postConstructRan = true; + } + } +}