我对控制反转以及依赖注入的认识

IoC诞生的历史

在没有IoC时,关联不同模块是通过类实例实现的,代码可能是这样子的:

// 代码清单1
public interface YourService {
    void func1();
    
    void func2();
}

// 代码清单2
public class MyServiceImpl {
    private YourServiceImpl yourServiceImpl;
    
    public MyServiceImpl() {
        this.yourServiceImpl = new YourServiceImpl();
    }
    
    public void process() {
        // do something
        this.yourServiceImpl.func1(...);
        // do something
        this.yourServiceImpl.func2(...);
    }
}
// 代码清单3
public class MyServiceImpl {
    private YourService yourService;
    
    public MyServiceImpl(YourService yourService) {
        this.yourService = yourService;
    }
    
    public void process() {
        // do something
        this.yourService.func1(...);
        // do something
        this.yourService.func2(...);
    }
}

当YourServiceImpl的接口不变时,只需要根据业务需要更换不同的YourService实现类即可。一旦更换实现类时(如将yourServiceImpl更换为NewServiceImpl),MyServiceImpl中所有引用YourService的代码都要替换为NewServiceImpl,这严重违背了面向对象设计中DIP(Dependence Inversion Principle)原则。

* 上层业务应该依赖抽象,抽象定义了"要做什么",而不应依赖实现细节"怎么做"。
* 模块间通过接口建立依赖关系
* 实现类依赖接口或抽象类

IOC实现的原理

IoC: Inversion of Control(依赖反转), 是针对之前模块间通过实现类建立关联而言,反转指"模块实现依赖实现类"->"模块实现依赖(实现类的)接口"。

执行IoC原则后,模块内原本生成实例的语句被替换成接口调用(代码清单3)。我们知道JVM是在运行时才申请内存并生成类实例对象的,运行时系统怎么知晓具体接口对应的实现是什么呢?

这时候就轮到反射和DI登场了。DI(Dependency injection)依赖注入,在运行时根据spring的xml配置文件(有实现类的全路径名称)选择适当的时机构造依赖对象实例并注入到依赖实现类接口的模块中。而如何完成依赖实例对象的构造以及初始化是Spring DI的核心。

// 代码清单4
    <bean id="userDAO" class="dao.impl.UserDAOImpl">
        <property name="sellerDAO" ref="sellerDAO" />
    </bean>
    
    <bean id="sellerDAO" class="dao.impl.SellerDAOImpl" />

代码清单4中演示了spring的bean配置代码。Spring解析bean的xml文件,拿到bean的类全路径名称后调用Class.forName(String className)方法从对应的类加载器中获取类信息,并调用Class.newInstance()方法生成类实例。

此时类实例并未完成初始化,还需要注入类实例的依赖项。Spring解析bean下的property条目,以property name为优先查找实例类的set方法并匹配入参,如果有则调set方法注入property。此时给模块返回完整的依赖对象。

注入方法

set方法注入

该方式要求被注入的属性在实现类里有set方法。set注入支持简单类型和引用类型。

构造函数注入

// 代码清单5
/**
 * Created by fujianbo on 2018/6/17.
 *
 * @author fujianbo
 * @date 2018/06/17
 */
public class UserDAOImpl implements UserDAO {
    private SellerDAO sellerDAO;
    
    public void setSellerDAO(SellerDAO sellerDAO) {
        this.sellerDAO = sellerDAO;
    }
    
    public UserDAOImpl() {
    }
    
    public UserDAOImpl(SellerDAO sellerDAO) {
        this.sellerDAO = sellerDAO;
    }
}

// 代码清单6
<bean id="sellerDAO" class="dao.impl.SellerDAOImpl" />
<bean id="userDAO" class="dao.impl.UserDAOImpl">
    <constructor-arg ref="sellerDAO"/>
</bean>

当有多个constructor-arg时,Spring根据传入的依赖参数找到对应的构造函数而并不关心书写顺序。但如果构造函数只是入参顺序不同,定义constructor-arg时需要加index参数,否则Spring创建类实例失败。

// 代码清单7
public class User {
    private String userName;
    private Integer age;

    public User(int age, String userName) {
        this.age = age;
        this.userName = userName;
    }

    public User(String userName, int age) {
        this.userName = userName;
        this.age = age;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

...
// yourConfig.xml
<bean id="user" class="..." >
    <constructor-arg index="0" value="Jack"/>
    <constructor-arg index="1" value="26"/>
</bean>

IoC & DI

以下个人对于这两个兄弟的理解,如有谬误还请指正!

IoC

1、IoC(控制反转)是模块间拜托对类实例依赖的桥梁,它统一管理了类实例对象的创建、初始化、注入以及销毁过程。

2、模块间依赖实现类的接口,从调用者角度来看,多数情况无需关注细节以及感知实现细节的变化,只需关注接口入参和返回值,将OOP上升到面向接口、面向切面编程的层次,是工厂模式的升华(参考引用2)。每个模块相当于一个垂直业务,IoC抽象了模块内部创建类实例的代码,依托反射和DI,给出了类实例类型识别解析、初始化、对象管理(spring容器)的解决方案。

3、当模块间不再显示创建类实例总得有人接下这项"苦差事"吧,这人就是Spring。Spring通过bean配置文件或者@Autowired, @Component, @Resource(J2EE提供), @Service, @Repository等注解,结合java反射原理生成类实例对象并给模块间的接口赋值。

DI

Spring提供的传递模块间依赖的接口的实例的方法,是IoC解决方案的一部分。

引用

上一篇:二叉树-查找指定节点


下一篇:手把手教你创建ClassicLinkVPC