1. 程式人生 > >100行程式碼擼完SpringIOC容器

100行程式碼擼完SpringIOC容器

用過Spring框架的人一定都知道Spring的依賴注入控制反轉;通俗的講就是負責例項化物件 和 管理物件間的依賴 實現解耦。

我們來對比兩段程式碼:

UserController{
    UserService userService = new UserController();
    userService.insert(user);
}

UserController{
    @Autowwired
    UserService userService;
    userService.insert(user);
}

乍一看好像沒什麼區別,好像都是一樣的。在controller裡面建立了一個service物件然後呼叫它裡面的方法。但是換個角度想想, 如果還有2個,3個,甚至n個類需要用到這個service呢,那它豈不是要被建立n次,這樣就會極大的浪費資源,分分鐘就記憶體溢位了。

企業開發案例:

我們只需要在xml配置檔案裡面指定配置引數,然後在類上加上spring註解它就能幫我們管理物件了,那它又是怎麼實現的呢? 答案是: xml+反射+工廠模式。

手寫IOC容器,演示它的載入過程:

1. 模仿spring,我們也定義一個註解

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface MyService {

}


@Target({ TYPE, FIELD, METHOD })
@Retention(RUNTIME)
public
@interface MyAutowired { }

2. 像使用spring註解一樣,使用我們剛才的註解

@MyService
public class OrderService {

    public void add() {
        System.out.println("Order....add()");
    }
}


@MyService
public class UserService {

    @MyAutowired
    private OrderService orderService;
    
    public void add() {
        orderService.add();
        System.out.println(
"User....add()"); } }

 3. 演示容器載入核心程式碼

/**
 * SpringIOC容器實現過程
 */
public class MyClassPathXmlApplicationContext {
    // 1. 指定掃描包的範圍
    private String packageName ;
    
    // Spring的bean容器 (容器啟動時初始化所有bean,預設單例)  key:beanName(預設類名小寫)  value:bean物件
    private ConcurrentHashMap<String, Object> beans;
    
    public MyClassPathXmlApplicationContext(String packageName) throws Exception {
        // 類載入時初始化這些引數
        this.packageName = packageName;
        this.beans = new ConcurrentHashMap<String, Object>();
        initBeans();// 初始化所有類
        initEntryField();// 初始化所有類的屬性
    }
    
    /**
     * 初始化bean(IOC控制反轉)
     */
    public void initBeans() throws Exception {
        // 2. 通過java反射掃描指定包下面所有類的class地址
        List<Class<?>> classes = ClassUtil.getClasses(packageName);
        ConcurrentHashMap<String, Object> classExistAnnotation = this.findClassExistAnnotation(classes);
        if(classExistAnnotation == null || classExistAnnotation.isEmpty()) throw new Exception("cannot find bean");
    }
    
    /**
     * 判斷類上面是否存在bean的註解
     */
    private ConcurrentHashMap<String, Object> findClassExistAnnotation(List<Class<?>> classes) throws InstantiationException, IllegalAccessException {
        for(Class<?> classInfo : classes) {
            // 3. 獲取指定路徑中有ioc註解的類
            MyService annotation = classInfo.getAnnotation(MyService.class);
            if(annotation!=null) {
                String className = classInfo.getSimpleName();// 獲取類名稱
                // 4. 將類名首字母小寫,獲取bean名稱。
                String beanaName = StringUtil.toLowerCaseFirstOne(className);
                // 5. 反射初始化物件
                Object object = classInfo.newInstance();
                // 6. 將bean物件存入spring容器中
                beans.put(beanaName, object);
            }
        }
        return beans;// 物件初始化完成
    }    
    /**
     * 初始化屬性
     */
    private void initEntryField() throws Exception {
        // 7. 遍歷spring容器中所有的bean物件
        for (Entry<String, Object> entry : beans.entrySet()) {
            Object bean = entry.getValue();
            this.attriAssign(bean);
        }

    }
    /**
     * 依賴注入實現原理(DI依賴注入)
     */
    public void attriAssign(Object object) throws Exception {
        Class<? extends Object> classInfo = object.getClass();
        // 8. 使用反射機制,獲取當前類的所有屬性值
        Field[] fields = classInfo.getDeclaredFields();
        for(Field field : fields) {
            // 9. 獲取有@MyAutowired註解的屬性
            MyAutowired myAutowired = field.getAnnotation(MyAutowired.class);
            if(myAutowired != null) {
                // 10. 獲取屬性名稱
                String beanName = field.getName();
                // 11. 預設使用屬性名稱,查詢bean容器物件
                Object bean = this.getBean(beanName);
                if(bean != null) {
                    field.setAccessible(true);// 允許訪問私有屬性
                    // 12. 將得到的bean物件賦值給當前物件的屬性上。(bean名稱=屬性名稱)
                    field.set(object, bean);
                }
            }
        }
    }
    /**
     * 通過bean名稱去spring容器裡面獲取bean物件
     */
    public Object getBean(String beanName) throws Exception {
        if(StringUtils.isEmpty(beanName)) throw new Exception("beans.factory.BeanCreationException");
        Object object = beans.get(beanName);
        return object;
    }
    /**
     * 測試
     */
    public static void main(String[] args) throws Exception {
        MyClassPathXmlApplicationContext applicationContext = new MyClassPathXmlApplicationContext("com.wulei.service");
        UserService userService = (UserService)applicationContext.getBean("userService");
        userService.add();
    }
}

依賴注入:比如userController裡面的userService屬性加上bean註解,在類被載入時通過反射獲取service的物件,並且賦值給該屬性,這就叫做依賴注入。
控制反轉:controller引用service的例項,不需要通過new來建立,將建立物件的責任轉移給spring容器,這就叫反轉; ioc容器實現物件的建立,以及外部資源的獲取(其他類的屬性和方法),這就叫控制