diff --git a/Todo.md b/Todo.md index 9b26c74..ee8ec57 100644 --- a/Todo.md +++ b/Todo.md @@ -7,7 +7,7 @@ Parameter-agnostic methods - DI framework -Autowiring +Autowiring (done) Maybe explicit mapping/configuration - error handling diff --git a/src/main/java/com/norwood/core/AnnotationProcessor.java b/src/main/java/com/norwood/core/AnnotationProcessor.java index c9ecbf2..5019b02 100644 --- a/src/main/java/com/norwood/core/AnnotationProcessor.java +++ b/src/main/java/com/norwood/core/AnnotationProcessor.java @@ -40,16 +40,12 @@ public void processAnnotations(List> classDefinitions, Router router) { private void inject(Field field) { try { - Class fieldType = field.getType(); Object owner = container().get(field.getDeclaringClass()); - Object dependency = fieldType.getDeclaredConstructor().newInstance(); + Object dependency = resolveDependency(field.getType()); field.setAccessible(true); field.set(owner, dependency); - - System.out.println(owner); - System.out.println(dependency); - } catch (InstantiationException | + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException e) { @@ -58,6 +54,47 @@ private void inject(Field field) { } } + private Object resolveDependency(Class fieldType) + throws InstantiationException, IllegalAccessException, + IllegalArgumentException, InvocationTargetException, + NoSuchMethodException { + Object existing = container().get(fieldType); + if (existing != null && fieldType.isAnnotationPresent(Singleton.class)) { + return existing; + } + + Object instance = instantiate(fieldType); + + if (fieldType.isAnnotationPresent(Singleton.class)) { + container().set(fieldType, instance); + } + + return instance; + } + + private Object instantiate(Class clazz) + throws InstantiationException, IllegalAccessException, + IllegalArgumentException, InvocationTargetException, + NoSuchMethodException { + // Prefer zero argument constructor + try { + var ctor = clazz.getDeclaredConstructor(); + ctor.setAccessible(true); + return ctor.newInstance(); + } catch (NoSuchMethodException e) { + // Fall back to first constructor and resolve parameters + } + + var ctor = clazz.getDeclaredConstructors()[0]; + ctor.setAccessible(true); + Class[] paramTypes = ctor.getParameterTypes(); + Object[] params = new Object[paramTypes.length]; + for (int i = 0; i < paramTypes.length; i++) { + params[i] = resolveDependency(paramTypes[i]); + } + return ctor.newInstance(params); + } + private void routePost(Post a, Router router, Method method) { String path = a.path(); if (router.hasRouteWithPath(path)) { diff --git a/src/main/java/com/norwood/core/KatanaContainer.java b/src/main/java/com/norwood/core/KatanaContainer.java index 417c22e..026410c 100644 --- a/src/main/java/com/norwood/core/KatanaContainer.java +++ b/src/main/java/com/norwood/core/KatanaContainer.java @@ -17,8 +17,13 @@ public T get(Class beanClass) { @Override public void set(Class beanClass, T bean) { if (beans.get(beanClass) != null) { + // Ignore subsequent registrations for singletons + if (beanClass.isAnnotationPresent(Singleton.class)) { + return; + } throw new BeanAlreadyDefinedException("Bean already defined."); } + beans.put(beanClass, bean); } diff --git a/src/test/java/com/norwood/AutowireTest.java b/src/test/java/com/norwood/AutowireTest.java new file mode 100644 index 0000000..6e3516c --- /dev/null +++ b/src/test/java/com/norwood/AutowireTest.java @@ -0,0 +1,42 @@ +package com.norwood; + +import junit.framework.TestCase; + +import com.norwood.core.AnnotationProcessor; +import com.norwood.core.KatanaContainer; +import com.norwood.core.Singleton; +import com.norwood.core.annotations.Inject; +import com.norwood.routing.Router; + +public class AutowireTest extends TestCase { + @Singleton + public static class SingletonService {} + + public static class NoDefaultConstructor { + final SingletonService service; + public NoDefaultConstructor(SingletonService service) { + this.service = service; + } + } + + public static class ClientBean { + @Inject SingletonService singleton; + @Inject NoDefaultConstructor nodefault; + } + + public void testAutowiring() { + KatanaContainer container = new KatanaContainer(); + container.set(ClientBean.class, new ClientBean()); + container.set(SingletonService.class, new SingletonService()); + + AnnotationProcessor processor = new AnnotationProcessor(); + processor.processAnnotations(container.classDefinitions(), new Router()); + + ClientBean bean = container.get(ClientBean.class); + SingletonService svc = container.get(SingletonService.class); + + assertSame(svc, bean.singleton); + assertNotNull(bean.nodefault); + assertSame(svc, bean.nodefault.service); + } +}