Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring理论基础-控制反转和依赖注入 #6

Open
Yuicon opened this issue Sep 2, 2018 · 0 comments
Open

Spring理论基础-控制反转和依赖注入 #6

Yuicon opened this issue Sep 2, 2018 · 0 comments

Comments

@Yuicon
Copy link
Owner

Yuicon commented Sep 2, 2018

第一次了解到控制反转(Inversion of Control)这个概念,是在学习Spring框架的时候。IOCAOP作为Spring的两大特征,自然是要去好好学学的。而依赖注入(Dependency Injection,简称DI)却使得我困惑了挺久,一直想不明白他们之间的联系。

控制反转

控制反转顾名思义,就是要去反转控制权,那么到底是哪些控制被反转了?在2004年 Martin fowler 大神就提出了

“哪些方面的控制被反转了?”

这个问题,他总结出是依赖对象的获得被反转了。

在单一职责原则的设计下,很少有单独一个对象就能完成的任务。大多数任务都需要复数的对象来协作完成,这样对象与对象之间就有了依赖。一开始对象之间的依赖关系是自己解决的,需要什么对象了就New一个出来用,控制权是在对象本身。但是这样耦合度就非常高,可能某个对象的一点小修改就会引起连锁反应,需要把依赖的对象一路修改过去。

如果依赖对象的获得被反转,具体生成什么依赖对象和什么时候生成都由对象之外的IOC容器来决定。对象只要在用到依赖对象的时候能获取到就可以了,常用的方式有依赖注入和依赖查找(Dependency Lookup)。这样对象与对象之间的耦合就被移除到了对象之外,后续即使有依赖修改也不需要去修改原代码了。

总结一下,控制反转是指把对象的依赖管理从内部转移至外部。

依赖注入

控制反转是把对象之间的依赖关系提到外部去管理,可依赖是提到对象外面了,对象本身还是要用到依赖对象的,这时候就要用到依赖注入了。顾名思义,应用需要把对象所需要的依赖从外部注入进来。可以是通过对象的构造函数传参注入,这种叫做构造器注入(Constructor Injection)。如果是通过JavaBean的属性方法传参注入,就叫做设值方法注入(Setter Injection)

不管是通过什么方式注入的,如果是我们手动注入的话还是显得太麻烦了。这时候就需要一个容器来帮我们实现这个功能,自动的将对象所需的依赖注入进去,这个容器就是前面提到的IOC容器了。

控制反转和依赖注入的关系也已经清晰了,它们本质上可以说是一样的,只是具体的关注点不同。控制反转的关注点是控制权的转移,而依赖注入则内含了控制反转的意义,明确的描述了依赖对象在外部被管理然后注入到对象中。实现了依赖注入,控制也就反转了。

例子

  • 首先是传统的方式,耦合非常严重。
public class Main {

    public static void main(String[] args) {
        OrderService service = new OrderService();
        service.test();
    }

}
public class OrderService {

    private OrderDao dao = new OrderDao();

    public void test() {
        dao.doSomeThing();
    }

}
public class OrderDao {

    public void doSomeThing() {
        System.out.println("test");
    }

}
  • 接下来是没有使用容器的方式,松耦合了,但是手动注入非常的麻烦。
public class Main {

    public static void main(String[] args) {
        Dao dao = new OrderDao();
        OrderService service = new OrderService(dao);
        service.test();
    }

}
public interface Dao {

    void doSomeThing();

}
public class OrderDao implements Dao {

    @Override
    public void doSomeThing() {
        System.out.println("test");
    }

}
public class OrderService {

    private Dao dao;

    public OrderService(Dao dao) {
        this.dao = dao;
    }

    public void test() {
        dao.doSomeThing();
    }

}
  • 接下来使用容器造福人类。
// 引导类要放在项目根目录下,也就是在 src 下面
public class Main {

    public static void main(String[] args) {
        // 生成容器
        Container container = new Container(Main.class);
        // 获取Bean
        OrderService service = container.getBean(OrderService.class);
        // 调用
        service.test();
    }

}
@Component
public class OrderService {

    @Autowired
    private Dao dao;

    public void test() {
        dao.doSomeThing();
    }

    public Dao getDao() {
        return dao;
    }

    public void setDao(Dao dao) {
        this.dao = dao;
    }
}
@Component
public class OrderDao implements Dao {

    @Override
    public void doSomeThing() {
        System.out.println("test");
    }

}
public interface Dao {

    void doSomeThing();

}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Component {
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD})
public @interface Autowired {
}
public class Container {

    private List<String> classPaths = new ArrayList<>();

    private String separator;

    private Map<Class, Object> components = new HashMap<>();

    public Container(Class cls) {
        File file = new File(cls.getResource("").getFile());
        separator = file.getName();
        renderClassPaths(new File(this.getClass().getResource("").getFile()));
        make();
        di();
    }

    private void make() {
        classPaths.forEach(classPath -> {
            try {
                Class c = Class.forName(classPath);
                // 找到有 @ioc.Component 注解的类并实例化
                if (c.isAnnotationPresent(Component.class)) {
                    components.put(c, c.newInstance());
                }
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * 注入依赖
     */
    private void di() {
        components.forEach((aClass, o) -> Arrays.stream(aClass.getDeclaredFields()).forEach(field -> {
            if (field.isAnnotationPresent(Autowired.class)) {
                try {
                    String methodName = "set" + field.getType().getName().substring(field.getType().getName().lastIndexOf(".") + 1);
                    Method method = aClass.getMethod(methodName, field.getType());
                    if (field.getType().isInterface()) {
                        components.keySet().forEach(aClass1 -> {
                            if (Arrays.stream(aClass1.getInterfaces()).anyMatch(aClass2 -> aClass2.equals(field.getType()))) {
                                try {
                                    method.invoke(o, components.get(aClass1));
                                } catch (IllegalAccessException | InvocationTargetException e) {
                                    e.printStackTrace();
                                }
                            }
                        });
                    } else {
                        method.invoke(o, components.get(field.getType()));
                    }
                } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }));
    }

    /**
     * 该方法会得到所有的类,将类的全类名写入到classPaths中
     *
     * @param file 包
     */
    private void renderClassPaths(File file) {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            Arrays.stream(Objects.requireNonNull(files)).forEach(this::renderClassPaths);
        } else {
            if (file.getName().endsWith(".class")) {
                String classPath = file.getPath()
                        .substring(file.getPath().lastIndexOf(separator) + separator.length() + 1)
                        .replace('\\', '.')
                        .replace(".class", "");
                classPaths.add(classPath);
            }
        }
    }

    public <T> T getBean(Class c) {
        return (T) components.get(c);
    }

}

后记

一些概念在脑海里总以为是清晰的,等实际用到或者是写成文字的时候就发现有很多不理解的地方。本文的目的就是梳理下概念,做些记录。这次自己尝试实现了下IOC容器,一开始写就知道自己之前的理解有问题了。好歹是写出了个能用的版本,用来应付文章中的例子。后面可以去参考下Spring的实现,估计能学到不少东西。

参考资料

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant