Если нельзя, но очень хочется, то нужно обязательно и ничего в мире не стоит того, чтобы делать из этого проблему!


Интересна Java? Кликай по ссылке и изучай!
Если тебе полезно что-то из того, чем я делюсь в своем блоге - можешь поделиться своими деньгами со мной.
с пожеланием
столько времени читатели провели на блоге - 
сейчас онлайн - 

понедельник, 19 марта 2012 г.

Java for fun: Что такое Dependency injection, Inversion of Control и почему это возникло. Часть #6

В прошлый раз обещал, что выложу самописный IoC контейнер. Видимо пора бы это сделать уже - столько времени прошло.

Итак исходники качаем тут.

Начнем как всегда с тестов.

package container;
import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;

import container.ioc.*;
import container.flashlight.*;
import container.battery.*;

public class TestBaterry {

    private Container container;
    
    @Before 
    public void initContainer() {
        // обрати внимание перед каждым тестом проинитится контейнер, которому мы сообщили, что 
        // заместь батарейки используй ChinaBattery а вместо фонарика SomeFlashlight
        container = new ContainerImpl(
                 Binder.use(ChinaBattery.class).as(Battery.class), 
                 Binder.use(SomeFlashlight.class).as(Flashlight.class));
    }
    
    @Test
    public void testDischargeNewBattery() {    
        // таким нехитрым способом по интерфейсу мы получаем реализацию
        // контейнер пройдется по всем полям новосозданного объекта и 
        // если там найдет знакомые типы то вставит в них реализации, 
        // в соответствии с настройками, которые мы указали
        Flashlight flashlight = container.get(Flashlight.class);        
        assertFalse(flashlight.isShines());
        
        flashlight.swithOn();        
        assertTrue(flashlight.isShines());
        
        for (int count = 0; count < 1000; count ++) {
            flashlight.swithOff();                            
            flashlight.swithOn();                    
        }
        
        flashlight.swithOn();
        assertFalse(flashlight.isShines());        
    }
    
    @Test
    public void testBadBattery() {                                
        Battery battery = new Battery(){
            @Override
            public boolean getVoltage() {            
                return false;
            }
        };
        
        // а вот так мы вдруг можем передумать и переопределить настройки контейнера
        container.update(Binder.use(battery).as(Battery.class));
        
        Flashlight flashlight = container.get(Flashlight.class);        
        assertFalse(flashlight.isShines());
        
        flashlight.swithOn();        
        assertFalse(flashlight.isShines());            
    }
        
    @Test
    public void testNoGetPowerIfDoubleSwithOn() {                        
        Flashlight flashlight = container.get(Flashlight.class);    
        assertFalse(flashlight.isShines());
        
        for (int count = 0; count < 1000; count ++) {                    
            flashlight.swithOn();                    
        }
        
        assertTrue(flashlight.isShines());            
    }    
    
    @Test 
    public void testNoBatteryNoLight() {        
        container.remove(Battery.class);
        
        Flashlight flashlight = container.get(Flashlight.class);
        
        assertFalse(flashlight.isShines());
        
        flashlight.swithOn();
        
        assertFalse(flashlight.isShines());    
    }

    @Test
    public void integrationTestGetPowerFormNewChinaBattery() {                
        Flashlight flashlight = container.get(Flashlight.class);    
        
        assertFalse(flashlight.isShines());
        
        flashlight.swithOn();
        
        assertTrue(flashlight.isShines());    
    }  
}

Именно так я и написал код, когда я начал разработку этого примера. Никаких классов контейнера естественно не было, и тест не компилился. Но я создал пустые классы с помощью IDE (Ctrl1-1 в Eclipse или Alt-Enter в Idea).

Вот он код пустой
package container.ioc;

public class Binder {    

    public static Binder use(Class<?> classToCreate) {
        return null;
    }
    
    public static Binder use(Object object) {
        return null;
    }

    public Binder as(Class<?> interfaceClass) {
        return null;
    }    
}

package container.ioc;

public interface Container {

    <T> T get(Class<T> interfaceClass);

    void remove(Class<?> clazz);

    void update(Binder binder);
}

package container.ioc;

public class ContainerImpl implements Container {

    public ContainerImpl(Binder...binders) {    
    }
    
    @Override
    public <T> T get(Class<T> interfaceClass) {
        return null;
    }

    @Override
    public void remove(Class<?> clazz) {        
    }

    @Override
    public void update(Binder binder) {
    }
}

Так теперь код хоть компилится! :) После того как была создана интерфейсная часть, я взялся за реализацию с помощью TDD.

Вот те тестовые сценарии, которые я один за другим написал и реализовал. Кода очень много :) потому что должен был проверить всевозможные случаи инъекции с разными полями, с одинаковыми полями, с наследниками, с реализациями, с примитивами и так далее. Если хочешь скипнуть - жми сюда.

Текста на самом деле много. А еще я все классы сделал Inner классами хотя в реальной версии они должны быть отдельными public классами иначе ничего не получится. Работа с иннераклассами у меня у туду.

package container2.ioc.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

import container.ioc.Binder;
import container.ioc.ContainerImpl;

public class IoCTest {
            
    interface Marker1 {
    }
    
    class Marker1Impl implements Marker1{
    }
    
    interface Marker2 {
    }
    
    class Marker2Impl implements Marker2 {
    }
    
    interface Marker3 {
    }
    
    class Marker3Impl implements Marker3 {
    }
    
    interface MainMarker {
    }
    
    private <T> T getImpl(Class<T> mainClass) {
        return (T)new ContainerImpl(
                Binder.use(Marker1Impl.class).as(Marker1.class), 
                Binder.use(Marker2Impl.class).as(Marker2.class),
                Binder.use(Marker3Impl.class).as(Marker3.class),                
                Binder.use(mainClass).as(MainMarker.class)).get(MainMarker.class);
    }
    
    class ClassWithDefaultConstructor implements MainMarker {
        Marker1 marker1; 
        
        public ClassWithDefaultConstructor() {
            
        }
    }
    
    @Test
    public void testWithDefaultConstructor() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithDefaultConstructor.class).marker1.getClass());            
    }
    
    class ClassWithoutAnyConstructors implements MainMarker {
        Marker1 marker1; 
        
        private ClassWithoutAnyConstructors() {        
        }
    }
    
    @Test
    public void testWithoutAnyConstructors() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithoutAnyConstructors.class).marker1.getClass());            
    }
    
    class ClassWithPrivateField implements MainMarker {
        private Marker1 marker1; 
        
        public ClassWithPrivateField() {}

        public Object getMarker1() {
            return marker1;
        }
    }
    
    @Test
    public void testWithPrivateField() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithPrivateField.class).getMarker1().getClass());            
    }
    
    class ClassWithInjectorConstructor implements MainMarker {
        private Marker1 marker1;
        
        public ClassWithInjectorConstructor(Marker1 marker1) {
            this.marker1 = marker1;
        }

        public Marker1 getMarker1() {
            return marker1;
        }            
    }
                
    @Test
    public void testWithInjectorConstructor() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithInjectorConstructor.class).getMarker1().getClass());            
    }
    
    class СlassWithInjectorConstructorAndTwoFields implements MainMarker {
        
        private Marker1 marker1;
        private Marker2 marker2;
        
        public СlassWithInjectorConstructorAndTwoFields(Marker2 marker2, Marker1 marker1) {
            this.marker1 = marker1;
            this.marker2 = marker2;
        }

        public Marker1 getMarker1() {
            return marker1;
        }
        
        public Marker2 getMarker2() {
            return marker2;
        }    
    
    }
    
    @Test
    public void testWithInjectorConstructorAndTwoFieldsCheckField1() {
        assertEquals(Marker1Impl.class, 
                getImpl(СlassWithInjectorConstructorAndTwoFields.class).getMarker1().getClass());            
    }
    
    @Test
    public void testWithInjectorConstructorAndTwoFieldsCheckField2() {
        assertEquals(Marker2Impl.class, 
                getImpl(СlassWithInjectorConstructorAndTwoFields.class).getMarker2().getClass());            
    }    
    
    class СlassWithPrivateInjectorConstructorAndTwoFields implements MainMarker {
        private Marker1 marker1;
        private Marker2 marker2;
        
        private СlassWithPrivateInjectorConstructorAndTwoFields(Marker2 marker2, Marker1 marker1) {
            this.marker1 = marker1;
            this.marker2 = marker2;
        }

        public Marker1 getMarker1() {
            return marker1;
        }
        
        public Marker2 getMarker2() {
            return marker2;
        }        
    }
    
    @Test
    public void testWithPrivateInjectorConstructorAndTwoFieldsCheckField1() {
        assertEquals(Marker1Impl.class, 
                getImpl(СlassWithPrivateInjectorConstructorAndTwoFields.class).getMarker1().getClass());            
    }
    
    @Test
    public void testWithPrivateInjectorConstructorAndTwoFieldsCheckField2() {
        assertEquals(Marker2Impl.class, 
                getImpl(СlassWithPrivateInjectorConstructorAndTwoFields.class).getMarker2().getClass());            
    }
    
    class ClassWithTwoConstructors implements MainMarker {
        private Marker1 marker1; 
        
        public ClassWithTwoConstructors() {
            
        }
        
        private ClassWithTwoConstructors(Marker1 marker) {
            this.marker1 = marker;
        }
        
        public Object getMarker1() {
            return marker1;
        }
    }
    
    @Test
    public void testWithTwoConstructors() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithTwoConstructors.class).getMarker1().getClass());            
    }
    
    class ClassWithTwoConstructorsWitnSameParametersCount implements MainMarker {
        private Marker1 marker1; 
            
        private ClassWithTwoConstructorsWitnSameParametersCount(Marker1 marker) {
            this.marker1 = marker;
        }
        
        private ClassWithTwoConstructorsWitnSameParametersCount(String string) {
        }
        
        public Object getMarker1() {
            return marker1;
        }
    }
    
    @Test
    public void testWithTwoConstructorsWithSameParametersCount() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithTwoConstructorsWitnSameParametersCount.class).getMarker1().getClass());            
    }
    
    class ClassWithTwoFieldsAndOnlyOneInjectedViaConstructor implements MainMarker {
        private Marker1 marker1; 
        private Marker2 marker2;
        private boolean costructorCall = false;
            
        private ClassWithTwoFieldsAndOnlyOneInjectedViaConstructor(Marker1 marker) {
            this.costructorCall = true;
            this.marker1 = marker;
        }
            
        public Object getMarker1() {
            return marker1;
        }
        
        public Object getMarker2() {
            return marker2;
        }

        public boolean isCostructorCall() {
            return costructorCall;
        }
    }
    
    @Test
    public void testWithTwoFieldsAndOnlyOneInjectedViaConstructor1() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithTwoFieldsAndOnlyOneInjectedViaConstructor.class).getMarker1().getClass());            
    }
    
    @Test
    public void testWithTwoFieldsAndOnlyOneInjectedViaConstructor2() {
        assertEquals(Marker2Impl.class, 
                getImpl(ClassWithTwoFieldsAndOnlyOneInjectedViaConstructor.class).getMarker2().getClass());            
    }
    
    @Test
    public void testWithTwoFieldsAndOnlyOneInjectedViaConstructor_constructorUsed() {
        assertTrue(getImpl(ClassWithTwoFieldsAndOnlyOneInjectedViaConstructor.class).isCostructorCall());            
    }
    
    class ClassWithTwoFieldsAndOtherOneInjectedViaConstructor implements MainMarker {
        private Marker1 marker1; 
        private Marker2 marker2;
        private boolean costructorCall = false;
            
        private ClassWithTwoFieldsAndOtherOneInjectedViaConstructor(Marker2 marker) {
            this.costructorCall = true;
            this.marker2 = marker;
        }
            
        public Object getMarker1() {
            return marker1;
        }
        
        public Object getMarker2() {
            return marker2;
        }

        public boolean isCostructorCall() {
            return costructorCall;
        }
    }
    
    @Test
    public void testWithTwoFieldsAndOtherOneInjectedViaConstructor_CheckField1() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithTwoFieldsAndOtherOneInjectedViaConstructor.class).getMarker1().getClass());            
    }
    
    @Test
    public void testWithTwoFieldsAndOtherOneInjectedViaConstructor_CheckField2() {
        assertEquals(Marker2Impl.class, 
                getImpl(ClassWithTwoFieldsAndOtherOneInjectedViaConstructor.class).getMarker2().getClass());            
    }
    
    @Test
    public void testWithTwoFieldsAndOtherOneInjectedViaConstructor_constructorUsed() {
        assertTrue(getImpl(ClassWithTwoFieldsAndOtherOneInjectedViaConstructor.class).isCostructorCall());            
    }
    
    class ClassWithThreeFieldsAndOnlyOneInjectedViaConstructor implements MainMarker {
        private Marker1 marker1; 
        private Marker2 marker2;
        private boolean costructorCall = false;
        private Marker3 marker3;
            
        private ClassWithThreeFieldsAndOnlyOneInjectedViaConstructor(Marker3 marker) {
            this.costructorCall = true;
            this.marker3 = marker;
        }
            
        public Object getMarker1() {
            return marker1;
        }
        
        public Object getMarker2() {
            return marker2;
        }

        public boolean isCostructorCall() {
            return costructorCall;
        }

        public Object getMarker3() {
            return marker3;
        }
    }
    
    @Test
    public void testWithThreeFieldsAndOtherOneInjectedViaConstructor_CheckField1() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithThreeFieldsAndOnlyOneInjectedViaConstructor.class).getMarker1().getClass());            
    }
    
    @Test
    public void testWithThreeFieldsAndOtherOneInjectedViaConstructor_CheckField2() {
        assertEquals(Marker2Impl.class, 
                getImpl(ClassWithThreeFieldsAndOnlyOneInjectedViaConstructor.class).getMarker2().getClass());            
    }
    
    
    @Test
    public void testWithThreeFieldsAndOtherOneInjectedViaConstructor_CheckField3() {
        assertEquals(Marker3Impl.class, 
                getImpl(ClassWithThreeFieldsAndOnlyOneInjectedViaConstructor.class).getMarker3().getClass());            
    }
    
    @Test
    public void testWithThreeFieldsAndOtherOneInjectedViaConstructor_constructorUsed() {
        assertTrue(getImpl(ClassWithThreeFieldsAndOnlyOneInjectedViaConstructor.class).isCostructorCall());            
    }
    
    class Marker1Fasade implements Marker1 {

        private Marker1 marker;

        public Marker1Fasade(Marker1 marker) {
            this.marker = marker; 
        }
        
        public Object getMarker() {
            return marker;
        }
        
    }
    
    class ClassWithTwoFieldsAndOnlyOneInjectedViaConstructorWithFasade implements MainMarker {
        private Marker1 marker1; 
        private Marker2 marker2;
            
        private ClassWithTwoFieldsAndOnlyOneInjectedViaConstructorWithFasade(Marker1 marker) {
            this.marker1 = new Marker1Fasade(marker);
        }
            
        public Object getMarker1() {
            return marker1;
        }
        
        public Object getMarker2() {
            return marker2;
        }
    }
    
    @Test
    public void testWithTwoFieldsAndOnlyOneInjectedViaConstructorWithFasade_checkThatNoReflectionInjectionIfConstructorInjected() {
        ClassWithTwoFieldsAndOnlyOneInjectedViaConstructorWithFasade impl = 
            getImpl(ClassWithTwoFieldsAndOnlyOneInjectedViaConstructorWithFasade.class);
        
        assertEquals(Marker1Fasade.class, impl.getMarker1().getClass());            
        assertEquals(Marker1Impl.class, ((Marker1Fasade)impl.getMarker1()).getMarker().getClass());
    }
    
    class ClassWithThreeFieldsAndTwoInjectedViaConstructor implements MainMarker {
        private Marker3 marker3;
        private boolean costructorCall = false;
        private Marker1 marker1; 
        private Marker2 marker2;
            
        private ClassWithThreeFieldsAndTwoInjectedViaConstructor(Marker3 marker3, Marker2 marker2) {
            this.costructorCall = true;
            this.marker3 = marker3;
            this.marker2 = marker2;
        }
            
        public Object getMarker1() {
            return marker1;
        }
        
        public Object getMarker2() {
            return marker2;
        }

        public boolean isCostructorCall() {
            return costructorCall;
        }

        public Object getMarker3() {
            return marker3;
        }
    }
    
    @Test
    public void testWithThreeFieldsAndTwoInjectedViaConstructor_CheckField1() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithThreeFieldsAndTwoInjectedViaConstructor.class).getMarker1().getClass());            
    }
    
    @Test
    public void testWithThreeFieldsAndTwoInjectedViaConstructor_CheckField2() {
        assertEquals(Marker2Impl.class, 
                getImpl(ClassWithThreeFieldsAndTwoInjectedViaConstructor.class).getMarker2().getClass());            
    }
    
    @Test
    public void testWithThreeFieldsAndTwoInjectedViaConstructor_CheckField3() {
        assertEquals(Marker3Impl.class, 
                getImpl(ClassWithThreeFieldsAndTwoInjectedViaConstructor.class).getMarker3().getClass());            
    }
    
    @Test
    public void testWithThreeFieldsAndTwoInjectedViaConstructor_constructorUsed() {
        assertTrue(getImpl(ClassWithThreeFieldsAndTwoInjectedViaConstructor.class).isCostructorCall());            
    }    
    
    class Marker3Fasade implements Marker3 {

        private Marker3 marker;

        public Marker3Fasade(Marker3 marker) {
            this.marker = marker; 
        }
        
        public Object getMarker() {
            return marker;
        }    
    }
    
    class ClassWithThreeFieldsAndTwoConstructors implements MainMarker {
        private Marker3 marker3;
        private boolean isCommonCostructorCall = false;
        private Marker1 marker1; 
        private Marker2 marker2;
        
        private ClassWithThreeFieldsAndTwoConstructors(Marker1 marker1) {
            this.marker1 = marker1;
        }
        
        private ClassWithThreeFieldsAndTwoConstructors(Marker3 marker3, Marker2 marker2) {
            this.isCommonCostructorCall = true;
            this.marker3 = new Marker3Fasade(marker3);
            this.marker2 = marker2;
        }
            
        public Object getMarker1() {
            return marker1;
        }
        
        public Object getMarker2() {
            return marker2;
        }

        public boolean isCommonCostructorCall() {
            return isCommonCostructorCall;
        }
        
        public Object getMarker3() {
            return marker3;
        }
    }
    
    @Test
    public void testWithThreeFieldsAndTwoConstructors_CheckField1() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithThreeFieldsAndTwoConstructors.class).getMarker1().getClass());            
    }
    
    @Test
    public void testWithThreeFieldsAndTwoConstructors_CheckField2() {
        assertEquals(Marker2Impl.class, 
                getImpl(ClassWithThreeFieldsAndTwoConstructors.class).getMarker2().getClass());            
    }
    
    @Test
    public void testWithThreeFieldsAndTwoConstructors_CheckField3() {        
        ClassWithThreeFieldsAndTwoConstructors impl = 
            getImpl(ClassWithThreeFieldsAndTwoConstructors.class);
        
        assertEquals(Marker3Fasade.class, impl.getMarker3().getClass());            
        assertEquals(Marker3Impl.class, ((Marker3Fasade)impl.getMarker3()).getMarker().getClass());        
    }
    
    @Test
    public void testWithThreeFieldsAndTwoConstructors_constructorUsed() {
        assertTrue(getImpl(ClassWithThreeFieldsAndTwoConstructors.class).isCommonCostructorCall());            
    }    
    
    class ClassWithThreeFieldsAndThreeConstructorsOneIsNotUsed implements MainMarker {
        private Marker3 marker3;
        private boolean isCommonCostructorCall = false;
        private Marker1 marker1; 
        private Marker2 marker2;
        
        private ClassWithThreeFieldsAndThreeConstructorsOneIsNotUsed(Marker1 marker1) {
            this.marker1 = marker1;
        }
        
        private ClassWithThreeFieldsAndThreeConstructorsOneIsNotUsed(Marker3 marker3, Marker2 marker2) {
            this.isCommonCostructorCall = true;
            this.marker3 = new Marker3Fasade(marker3);
            this.marker2 = marker2;
        }
        
        private ClassWithThreeFieldsAndThreeConstructorsOneIsNotUsed(Marker3 marker3, Marker2 marker2, String bla) {
            this.marker3 = marker3;
            this.marker2 = marker2;
        }
            
        public Object getMarker1() {
            return marker1;
        }
        
        public Object getMarker2() {
            return marker2;
        }

        public boolean isCommonCostructorCall() {
            return isCommonCostructorCall;
        }
        
        public Object getMarker3() {
            return marker3;
        }
    }
    
    @Test
    public void testWithThreeFieldsAndThreeConstructorsOneIsNotUsed_CheckField1() {
        assertEquals(Marker1Impl.class, 
                getImpl(ClassWithThreeFieldsAndThreeConstructorsOneIsNotUsed.class).getMarker1().getClass());            
    }
    
    @Test
    public void testWithThreeFieldsAndThreeConstructorsOneIsNotUsed_CheckField2() {
        assertEquals(Marker2Impl.class, 
                getImpl(ClassWithThreeFieldsAndThreeConstructorsOneIsNotUsed.class).getMarker2().getClass());            
    }
    
    @Test
    public void testWithThreeFieldsAndThreeConstructorsOneIsNotUsed_CheckField3() {        
        ClassWithThreeFieldsAndThreeConstructorsOneIsNotUsed impl = 
            getImpl(ClassWithThreeFieldsAndThreeConstructorsOneIsNotUsed.class);
        
        assertEquals(Marker3Fasade.class, impl.getMarker3().getClass());            
        assertEquals(Marker3Impl.class, ((Marker3Fasade)impl.getMarker3()).getMarker().getClass());        
    }
    
    @Test
    public void testWithThreeFieldsAndThreeConstructorsOneIsNotUsed_constructorUsed() {
        assertTrue(getImpl(ClassWithThreeFieldsAndThreeConstructorsOneIsNotUsed.class).isCommonCostructorCall());            
    }
}

Постепенно появлялась реализация. После несокльких подходов к коду я родил вот эти реализации

package container.ioc;

public class Binder {

    Class<?> interfaceClass;
    Class<?> classToCreate;
    Object object;

    public Binder(Class<?> classToCreate) {
        this.classToCreate = classToCreate;
    }

    public Binder(Object object) {
        this.object = object;
    }

    public static Binder use(Class<?> classToCreate) {
        return new Binder(classToCreate);
    }
    
    public static Binder use(Object object) {
        return new Binder(object);
    }

    public Binder as(Class<?> interfaceClass) {
        this.interfaceClass = interfaceClass;
        return this;
    }
    
}

package container.ioc;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

public class ContainerImpl implements Container {

    private Collection<Binder> binders;

    public ContainerImpl(Binder...binders) {
        this.binders = new LinkedList<Binder>(Arrays.asList(binders));
    }
    
    @Override
    public <T> T get(Class<T> interfaceClass) {
        Binder binder = find(interfaceClass);
        
        if (binder.object != null) {
            return (T) binder.object;            
        }
        
        Class<?> classToCreate = binder.classToCreate;
        
        List<Constructor<?>> constructors = getCostructors(classToCreate);
        
        List<Object> dependencies = getObjectsFor(onlyInterfaceTypes(getTypes(getFields(classToCreate))));                                                    
                
        return (T) foundCommonConstructor(constructors, dependencies).newInstanceFor(dependencies);                                     
    }

    private InstanceMaker foundCommonConstructor(
            List<Constructor<?>> constructors, List<Object> dependencies) 
    {
        sortByParameterCount(constructors);
        
        for (Constructor<?> constructor : constructors) {
            if (sufficiently(constructor.getParameterTypes(), dependencies)) {
                return new InstanceMaker(constructor); 
            }
        }        
        
        throw new RuntimeException("Constructor not found");
    }

    private void sortByParameterCount(List<Constructor<?>> constructors) {
        Collections.sort(constructors, new Comparator<Constructor<?>>() {
            @Override
            public int compare(Constructor<?> constructor1, Constructor<?> constructor2) {
                return constructor2.getParameterTypes().length - 
                    constructor1.getParameterTypes().length;
            }                
        });
    }    

    private boolean sufficiently(Class<?>[] parameterTypes, List<Object> dependencies) {        
        for (Class<?> clazz : parameterTypes) {
            if (notIn(clazz, dependencies)) {
                return false;
            }
        }
        return true;        
    }

    private boolean notIn(Class<?> clazz, List<Object> dependencies) {
        for (Object dependency : dependencies) {
            if (clazz.isAssignableFrom(dependency.getClass())) {
                return false;
            }
        }
        return true;
    }

    private List<Class<?>> getTypes(Collection<Field> fields) {
        List<Class<?>> result = new LinkedList<Class<?>>();
        for (Field field : fields) {            
            result.add(field.getType());
        }
        return result;
    }
    
    private List<Class<?>> onlyInterfaceTypes(List<Class<?>> classes) {
        List<Class<?>> result = new LinkedList<Class<?>>();
        for (Class<?> clazz : classes) {
            
            try {
                find(clazz);
                result.add(clazz);
            } catch (RuntimeException e) {
                continue;
            }
        }
        return result;                
    }
    
    private Binder find(Class<?> interfaceClass) {
        for (Binder binder : binders) {
            if (binder.interfaceClass.equals(interfaceClass)) {
                return binder;
            }
        }
        throw new RuntimeException("Dependency class not found for interface" + interfaceClass.getName());
    }

    private List<Object> getObjectsFor(List<Class<?>> parameterTypes) {
        List<Object> result = new LinkedList<Object>();
        for (Class<?> clazz : parameterTypes) {
            result.add(get(clazz));
        }
        return result;        
    }
    
    private List<Constructor<?>> getCostructors(Class<?> classToCreate) {
        return Arrays.asList(classToCreate.getDeclaredConstructors());        
    }
    
    private Collection<Field> getFields(Class<?> classToCreate) {
        return Arrays.asList(classToCreate.getDeclaredFields());
    }

    @Override
    public void remove(Class<?> clazz) {
        for (Binder binder : binders) {
            if (binder.interfaceClass.equals(clazz)) {
                binders.remove(binder);
                return;
            }
        }        
    }

    @Override
    public void update(Binder binder) {
        remove(binder.interfaceClass);
        binders.add(binder);
    }
}

package container.ioc;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedList;
import java.util.List;

public class InstanceMaker {
    private Constructor<?> constructor;
    
    public InstanceMaker(Constructor<?> constructor) {
        this.constructor = constructor;
    }
    
    public Object newInstanceFor(List<Object> dependencies) {
        List<Object> foundDependencies = getDependenciesFor(constructor, dependencies);
        Object object = getObject(constructor, foundDependencies);            
        dependencies.removeAll(foundDependencies);
            
        return injectToField(object, dependencies);
    }
    
    private List<Object> getDependenciesFor(Constructor<?> constructor, List<Object> dependencies) {
        List<Object> result = new LinkedList<Object> ();
        for (Class<?> parameterType : constructor.getParameterTypes()) {
            for (Object dependency : dependencies) {
                if (parameterType.isAssignableFrom(dependency.getClass())) { 
                    result.add(dependency);
                    break; 
                }
            }
        }
        return result;
    }
        
    private Object getObject(Constructor<?> constructor, List<Object> dependencies) {
        try {
            constructor.setAccessible(true);
            Object newInstance = constructor.newInstance(dependencies.toArray(new Object[0]));
            constructor.setAccessible(false);
            
            return newInstance;
        } catch (IllegalArgumentException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
    
    private <T> T injectToField(T object, List<Object> dependencies) {
        for (Object dependency : dependencies) {
            Injector.injectAll(object, dependency);
        }
        return object;
    }
}

Классы достаточно сложновастые, но рефлексия все же. Библиотеку Fest Reflection я не хотел подключать из за лишней jar-dependency. В следующий раз попробую сделаю с ней - за одно попиарю этот классный инструмент для работы с рефлексией.

В будущих планах есть сделать то же но со Spring IoC. Ждите, продолжение следует..

Комментариев нет:

Отправить комментарий