第一次了解到控制反转(Inversion of Control)这个概念,是在学习 Spring
框架的时候。 IOC
和 AOP
作为 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
的实现,估计能学到不少东西。