浏览代码

fix: Meta annotation target
new: HostContext Class<?>
new: HostContext constructor injection
resolve #119

Ranides Atterwim 1 年之前
父节点
当前提交
4097f3b932

+ 4 - 0
assira.commons/src/main/java/net/ranides/assira/system/HostContext.java

@@ -9,12 +9,16 @@ public interface HostContext {
 
     Object getBean(String name);
 
+    <T> T getBean(Class<T> type);
+
     <T> T getBean(IClass<T> type);
 
     boolean isPrototype(String name);
 
     boolean isSingleton(String name);
 
+    void registerPrototype(String name, Class<?> type);
+
     void registerPrototype(String name, IClass<?> type);
 
     void registerSingleton(String name, Object instance);

+ 25 - 0
assira.commons/src/main/java/net/ranides/assira/system/impl/HostCompositeContext.java

@@ -37,6 +37,18 @@ public class HostCompositeContext implements HostContext {
         throw new NoSuchElementException("No bean with type: " + type);
     }
 
+    @Override
+    public <T> T getBean(Class<T> type) {
+        for (HostContext c : contexts) {
+            try {
+                return c.getBean(type);
+            } catch (Exception e) {
+                // skip
+            }
+        }
+        throw new NoSuchElementException("No bean with type: " + type);
+    }
+
     @Override
     public boolean isPrototype(String name) {
         for (HostContext c : contexts) {
@@ -74,6 +86,19 @@ public class HostCompositeContext implements HostContext {
         throw new UnsupportedOperationException();
     }
 
+    @Override
+    public void registerPrototype(String name, Class<?> type) {
+        for (HostContext c : contexts) {
+            try {
+                c.registerPrototype(name, type);
+                break;
+            } catch (Exception e) {
+                // skip
+            }
+        }
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public void registerSingleton(String name, Object instance) {
         for (HostContext c : contexts) {

+ 56 - 2
assira.commons/src/main/java/net/ranides/assira/system/impl/HostSimpleContext.java

@@ -2,11 +2,16 @@ package net.ranides.assira.system.impl;
 
 import net.ranides.assira.collection.maps.OpenMap;
 import net.ranides.assira.collection.maps.TypeMap;
+import net.ranides.assira.collection.query.CQuery;
+import net.ranides.assira.generic.Ref;
+import net.ranides.assira.reflection.IAttribute;
 import net.ranides.assira.reflection.IClass;
+import net.ranides.assira.reflection.IConstructor;
 import net.ranides.assira.system.HostContext;
 import net.ranides.assira.trace.LoggerUtils;
 import org.slf4j.Logger;
 
+import java.util.Comparator;
 import java.util.Map;
 import java.util.NoSuchElementException;
 
@@ -28,7 +33,7 @@ public class HostSimpleContext implements HostContext {
             return name2instance.get(name);
         }
         if(name2prototype.containsKey(name)) {
-            return name2prototype.get(name).construct();
+            return construct(name2prototype.get(name));
         }
         throw new NoSuchElementException("No bean with name: " + name);
     }
@@ -39,11 +44,17 @@ public class HostSimpleContext implements HostContext {
             return type.cast(type2instance.match(type));
         }
         if(type2prototype.matches(type)) {
-            return type.cast(type2prototype.match(type).construct());
+            Object o = construct(type2prototype.match(type));
+            return type.cast(o);
         }
         throw new NoSuchElementException("No bean with type: " + type);
     }
 
+    @Override
+    public <T> T getBean(Class<T> type) {
+        return getBean(IClass.typeinfo(type));
+    }
+
     @Override
     public boolean isPrototype(String name) {
         return name2prototype.containsKey(name);
@@ -64,6 +75,11 @@ public class HostSimpleContext implements HostContext {
         }
     }
 
+    @Override
+    public void registerPrototype(String name, Class<?> type) {
+        registerPrototype(name, IClass.typeinfo(type));
+    }
+
     @Override
     public void registerSingleton(String name, Object instance) {
         type2instance.add(IClass.typefor(instance), instance);
@@ -73,4 +89,42 @@ public class HostSimpleContext implements HostContext {
             }
         }
     }
+
+    private Object construct(IClass<?> type) {
+        return type.constructors()
+            .require(IAttribute.PUBLIC)
+            .stream()
+            .sort(Comparator.comparingInt(v -> v.arguments().count()))
+            .reverse()
+            .map(c -> construct(c))
+            .filter(o -> o.isPresent())
+            .first()
+            .orElseThrow(() -> new NoSuchElementException("There is no constructor for bean prototype " + type))
+            .orElseThrow(() -> new NoSuchElementException("There is no constructor for bean prototype " + type));
+    }
+
+    private Ref<Object> construct(IConstructor<?> ctor) {
+        final Object[] args;
+        try {
+            args = ctor.arguments().stream().map(a -> {
+                if(a.type().isPrimitive() || a.type().isArray()) {
+                    // skip if argument is not bean
+                    throw new NoSuchElementException("primitive");
+                }
+                if(a.type().isSuper(ctor.parent())) {
+                    // skip if argument is super class of requested bean
+                    // (it's heuristic: we ignore "copying constructors")
+                    throw new NoSuchElementException("copy");
+                }
+                return getBean(a.type());
+            }).array();
+        } catch (Exception e) {
+            return Ref.empty();
+        }
+        try {
+            return Ref.of(ctor.apply(args));
+        } catch (Exception e) {
+            return Ref.empty();
+        }
+    }
 }

+ 10 - 0
assira.commons/src/main/java/net/ranides/assira/system/impl/HostSpringContext.java

@@ -26,6 +26,11 @@ public class HostSpringContext implements HostContext {
         return delegate.getBean(type.raw());
     }
 
+    @Override
+    public <T> T getBean(Class<T> type) {
+        return delegate.getBean(type);
+    }
+
     @Override
     public boolean isPrototype(String name) {
         return delegate.isPrototype(name);
@@ -46,6 +51,11 @@ public class HostSpringContext implements HostContext {
         throw new UnsupportedOperationException();
     }
 
+    @Override
+    public void registerPrototype(String name, Class<?> type) {
+        throw new UnsupportedOperationException();
+    }
+
     /**
      *
      * @noinspection JavadocReference

+ 47 - 0
assira.commons/src/test/java/net/ranides/assira/system/impl/HostSimpleContextTest.java

@@ -1,5 +1,7 @@
 package net.ranides.assira.system.impl;
 
+import lombok.Data;
+import net.ranides.assira.annotations.Meta;
 import net.ranides.assira.collection.maps.OpenMap;
 import net.ranides.assira.generic.TypeToken;
 import net.ranides.assira.io.IOEvent;
@@ -102,4 +104,49 @@ public class HostSimpleContextTest {
         assertEquals(Arrays.asList(15, 9), list1);
         assertEquals(Arrays.asList(12, 8), list2);
     }
+
+    @Test
+    public void testArguments() {
+        HostSimpleContext ctx = new HostSimpleContext();
+
+        ctx.registerSingleton("one", new MyBean1());
+        ctx.registerPrototype("two", MyBean2.class);
+        ctx.registerPrototype("tre", MyBean3.class);
+
+        assertEquals("HostSimpleContextTest.MyBean3(count=3)", ctx.getBean("tre").toString());
+        assertEquals(3, ctx.getBean(MyBean3.class).getCount());
+        assertEquals(1, ctx.getBean(MyBean1.class).getA());
+        assertEquals(2, ctx.getBean(MyBean2.class).getB());
+
+        ctx.getBean(MyBean1.class).setA(8);
+        ctx.getBean(MyBean2.class).setB(9);
+
+        assertEquals(10, ctx.getBean(MyBean3.class).getCount());
+    }
+
+    @Data
+    public static class MyBean1 {
+        private int a = 1;
+    }
+
+    @Data
+    public static class MyBean2 {
+        private int b = 2;
+    }
+
+    @Data
+    public static class MyBean3 {
+
+        private final int count;
+
+        @Meta.Dynamic
+        public MyBean3(MyBean1 first, MyBean2 second) {
+            count = first.a + second.b;
+        }
+
+        @Meta.Dynamic
+        public MyBean3(MyBean2 second) {
+            count = second.b;
+        }
+    }
 }

+ 4 - 4
assira.core/src/main/java/net/ranides/assira/annotations/Meta.java

@@ -148,7 +148,7 @@ public final class Meta {
     /**
      * Annotated method is invoked at runtime by {@link net.ranides.assira.generic.DynamicInvoker}.
      */
-    @Target(METHOD)
+    @Target({METHOD, CONSTRUCTOR})
     @Retention(RUNTIME)
     @Documented
     public @interface DynamicDispatch {
@@ -157,7 +157,7 @@ public final class Meta {
     /**
      * Annotated type or method can be drastically changed or removed at all in future versions
      */
-    @Target({METHOD, TYPE})
+    @Target({METHOD, CONSTRUCTOR, TYPE})
     @Retention(SOURCE)
     public @interface Experimental {
     }
@@ -166,7 +166,7 @@ public final class Meta {
      * Annotation used by classes or methods to mark code as thread-safe.
      * It is used for documentation purposes or static analysis.
      */
-    @Target({METHOD, TYPE})
+    @Target({METHOD, CONSTRUCTOR, TYPE})
     @Retention(SOURCE)
     @Documented
     public @interface ThreadSafe {
@@ -178,7 +178,7 @@ public final class Meta {
      * Static analysis should not warn about unused method.
      */
     @Retention(RUNTIME)
-    @Target({METHOD, FIELD})
+    @Target({METHOD, FIELD, CONSTRUCTOR})
     @Documented
     public @interface Dynamic {
         /**