1. 程式人生 > >Spring理論基礎-控制反轉和依賴註入

Spring理論基礎-控制反轉和依賴註入

acl some req 名企 lis 直播 implement 分析 開發


第一次了解到控制反轉(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
br/>@Component

@Autowired
private Dao dao;

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

public Dao getDao() {
    return dao;
}

public void setDao(Dao dao) {
    this.dao = dao;
}

}@Component
br/>@Component

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

}
public interface Dao {

void doSomeThing();

}@Retention(RetentionPolicy.RUNTIME)
br/>@Retention(RetentionPolicy.RUNTIME)
public @interface Component {}
@Retention(RetentionPolicy.RUNTIME)
br/>}
@Retention(RetentionPolicy.RUNTIME)
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的實現,估計能學到不少東西。
如果你現在在JAVA這條路上掙紮,也想在IT行業拿高薪,可以參加我們的訓練營課程,選擇最適合自己的課程學習,技術大牛親授,7個月後,進入名企拿高薪。我們的課程內容有:Java工程化、高性能及分布式、高性能、深入淺出。高架構。性能調優、Spring,MyBatis,Netty源碼分析和大數據等多個知識點。如果你想拿高薪的,想學習的,想就業前景好的,想跟別人競爭能取得優勢的,想進阿裏面試但擔心面試不過的,你都可以來,q群號為:230419550

註:加群要求

1、具有1-5工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加。

2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加。

3、如果沒有工作經驗,但基礎非常紮實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的,可以加。

4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加。

5.阿裏Java高級大牛直播講解知識點,分享知識,多年工作經驗的梳理和總結,帶著大家全面、科學地建立自己的技術體系和技術認知!

Spring理論基礎-控制反轉和依賴註入