1. 程式人生 > >SpringIOC簡單模擬,菜鳥篇

SpringIOC簡單模擬,菜鳥篇

IOC是Spring兩大特徵之一,今天我們就來用最最最土的方式模擬下它。本文全是基礎,不涉及設計模式,適合初級程式設計師閱讀。

到底什麼是IOC、DI

IOC(控制反轉),不是什麼技術,而是一種設計思想。Ioc意味著,將你設計好的物件交給容器控制,而不是傳統的,在你的物件內部直接控制。

所以控制反轉就是說把建立物件的控制權進行轉移,以前建立物件的主動權和建立時機都是有自己控制的,而現在把這種權利交給第三方,比如IOC容器,它是一個專門用來建立物件的工廠,有了IOC容器後,把建立和查詢依賴物件的控制權交給了容器,由容器進行注入組合物件,所以物件與物件之間是鬆散耦合。

DI(依賴注入)

一種IoC的實現方式。即將依賴物件的建立和繫結工作轉移到第三者(呼叫方)

想象下Spring載入過程

假設有一個URL請求,http://demo/get/uid/1

當這個請求到達伺服器之後,首先獲取url,接著找到url所對應的處理方法,最後把url引數傳給這個方法,然後在呼叫下這個方法。

問題是:如何通過通過url找到這個方法的對映,攏共分幾步?

分六個步驟

第一步:自定義幾個註解

我們需要自定義幾個註解來標識我們bean物件:

1. @Controller 用來標識controller層。

2. @Service用來標識Sevice層。

3. @Qualifier 用來標識類中屬性。

4. @RequestMapping 用來標識方法。

這些註解中都有一個預設的value屬性。

第二步:收集bean

把整個工程檔案遍歷一遍,通過IO讀取所有java檔案,java的name放在一個list容器裡,就稱他為beanName集合吧。

第三步:註冊bean

遍歷beanName集合,獲取java檔案的類名,用反射的方法通過名字,獲取該類的Class,判斷Class是否包含Controller或Service註解,若包含,則通過反射的方式把當前Class生成bean物件,用Controller或Service註解中的value值做key來標識。放到一個map容器裡,就叫他instanceMap容器吧。

第四步:注入bean

遍歷instanceMap容器,instanceMap存的都是例項物件,我們遍歷每個物件,獲取當前物件內的所有變數fields,然後在遍歷這個fields,看當前field是否包含Qualifier註解,包含的話,就用Qualifier註解設定的value當做key去instanceMap容器裡找到bean例項物件,然後把這個例項物件設定到當前的field中。

第五步:收集RequestMapping方法

遍歷instanceMap容器,找到帶有Controller註解的類,獲取該類的所有方法methods,再遍methods方法,找到帶有RequestMapping註解的方法物件,用RequestMapping中的value加上Controller註解中的value拼接成url,當做key,把當前方法物件放到一個map容器,就把這個容器叫做methodMap容器吧。

第六步:實現url對映

當一個請求到達Servlet時,獲取這個請求的url,通過字串分割獲取url中的引數,把引數設定到request.Attribute中,刪除url中的引數,做key去methodMap獲取方法物件,再用反射的方法,將request,response做引數,呼叫這個方法,獲取返回值,這個返回值就是我們要返回給客戶端的頁面,在通過request轉發的方式,請頁面返回。

記住這六步,下面看程式碼。

Coding1:自定義幾個註解


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
    String value() ;
}

 

 

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Service {
    String value();
}

 

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Qualifier {
    String value();
}

 

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequestMapping {
    String value();
}

Coding2:收集bean

basePackages為配置掃描的包。

就像spring配置檔案中的<context:component-scan base-package="" />

public void scanClassName(String basePackages){

//獲取掃描註解所在的路徑
    URL url =this.getClass().getResource("/"+replacePath(basePackages));
    File files = new File(url.getFile());
    String[] fileList = files.list();
    for(String file: fileList){
        File eachFile = new File(url.getFile()+file);
        if(eachFile.isDirectory()){
            scanClassName(basePackages+"."+file);
        }else{

//將java類的名稱放在classList中
            classlist.add(basePackages+"."+eachFile.getName());
        }
    }

}

Coding3:註冊bean

public void filterAndInstance() throws Exception {
    for(String current:classlist){
        //將.class字尾去掉,獲取檔名,用反射獲取class
        Class clazz = Class.forName(current.replace(".class",""));
        //判斷是否包含Controller註解
        if(clazz.isAnnotationPresent(Controller.class)){
            //建立物件
            Object object = clazz.newInstance();
            String key = ((Controller)clazz.getAnnotation(Controller.class)).value();
            instanceMap.put(key,object);
        }else if(clazz.isAnnotationPresent(Service.class)){
            Object object = clazz.newInstance();
            String key = ((Service)clazz.getAnnotation(Service.class)).value();
            instanceMap.put(key,object);
        }else{
            continue;
        }
    }
}

Coding4:注入bean

public void springDi() throws IllegalAccessException {
    for(Map.Entry entry: instanceMap.entrySet()){
        Field[] fields = entry.getValue().getClass().getDeclaredFields();
        for(Field field :fields){
            if(field.isAnnotationPresent(Qualifier.class)){
                field.setAccessible(true);
                String key = field.getAnnotation(Qualifier.class).value();
                field.set(entry.getValue(),instanceMap.get(key));
            }
        }
    }
}

Coding5:收集RequestMapping方法

private void mvc() throws ServletException {
    if(instanceMap == null){
        throw new ServletException("instanceMap is null");
    }
    for(Map.Entry entry : instanceMap.entrySet()){
        if(entry.getValue().getClass().isAnnotationPresent(Controller.class)){
            String ctlUrl = entry.getValue().getClass().getAnnotation(Controller.class).value();
            Method[] methods = entry.getValue().getClass().getMethods();
            for(Method current: methods){
                if (current.isAnnotationPresent(RequestMapping.class)){
                    String methodUrl = current.getAnnotation(RequestMapping.class).value();
                    methodMap.put("/"+ctlUrl+"/"+methodUrl,current);
                }
            }
        }
    }
}

Coding6:實現url對映

private void methodInvoke(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String url = req.getServletPath();
    url = url.replace(".html","");
    String className = url;
    Method method = methodMap.get(className);
    if(method==null){
        req.getRequestDispatcher("/404.jsp").forward(req, resp);
    }else {
        Object o = instanceMap.get(className.split("/")[1]);
        String forwod = null;

        try {
            forwod = (String) method.invoke(o, new Object[]{req, resp});
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        req.getRequestDispatcher("/" + forwod + ".jsp").forward(req, resp);
    }
}