Java Web實現IOC控制反轉之依賴注入


控制反轉(Inversion of Control,英文縮寫為IoC)是一個重要的面向對象編程的法則來削減計算機程序的耦合問題,也是輕量級的Spring框架的核心。

控制反轉一般分為兩種類型,依賴注入(Dependency Injection,簡稱DI)和依賴查找(Dependency Lookup)。

依賴注入應用比較廣泛。本文介紹java實現一個簡單的依賴注入

 

簡單而言,當你在某一個類中需要調用其他的類並且生成對象,大部分情況是new一個對象,此時如果你不確定要new哪一個對象,你就需要為所有的類作if或者switch判斷,在不同情況下new不同的對象,然后給他們屬性賦值

使用最多的地方就是JavaBean類和他們的對象了,假設項目的Model里面有Teacher,StudentClass,分別代表老師和班級兩個javaBean類

public class Teacher {

    private String name;
    
    private String id;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Teacher [name=" + name + ", id=" + id + "]";
    }
    
}
Teacher.java
public class SchoolClass {

    private String schoolClassName;
    
    private String schoolClassId;
    
    private Teacher manager;

    public String getSchoolClassName() {
        return schoolClassName;
    }

    public void setSchoolClassName(String schoolClassName) {
        this.schoolClassName = schoolClassName;
    }

    public String getSchoolClassId() {
        return schoolClassId;
    }

    public void setSchoolClassId(String schoolClassId) {
        this.schoolClassId = schoolClassId;
    }

    public Teacher getManager() {
        return manager;
    }

    public void setManager(Teacher manager) {
        this.manager = manager;
    }

    @Override
    public String toString() {
        return "The manager of "+schoolClassName+"("+schoolClassId+")"+"is Teacher"+manager.toString();
    }
    
SchoolClass

 

此時,我們有一個老師,兩個班級,那么在實際使用中是不是就要new一個老師,然后使用getter,setter為他賦值屬性,然后new兩個班級,並且按照老師的做法賦值,這樣如果有很多的老師和班級,那么就要不停的在java代碼中new,new,new......

而且每次多一個老師,班級,我們就要修改java代碼,重新編譯,這樣耦合度就很高,能不能使用一個工具類自動新建對象,而對象的信息保存在一個文本中

IoC的設計模式解決了這個問題,使用依賴注入,我們把類以及類相依賴的類放到動態修改的文本當中,每次從文本中讀取,然后根據文本信息,動態的new出這些對象,做到靈活,多樣化,易擴展。這樣不在需要去修改或者添加java代碼,代碼重復性也減少。

看看設計圖:

開始,新建一個web dynamic項目

目錄結構如下:

其中兩個javaBean放在app包中,確切說這是pojo類

ioc包里面是ioc核心控制器

test包是一個servlet,主要用於Ioc是否成功的測試

由於個人覺得xml文件的書寫和讀取,傳輸都不是很好,尤其在spring,struts的配置均采用xml,實在厭惡

之前在做php開發的時候,配置文件一般是json的格式或者php對象的格式(好吧,兩者只是本質有區別,事實上形式相似)

所以,我這次異想天開的在自己的IoC中使用Json作為依賴注入的動態配置文件取代大部分框架使用的xml文件

如下:

[
    {
        "bean": "cn.cslg.app.Teacher",
        "id": "class_manager",
        "properties": {
            "name": "黃有為",
            "id": "200500027"
        }
    },
    {
        "bean": "cn.cslg.app.SchoolClass",
        "id": "class1",
        "properties": {
            "schoolClassId": "Z094141",
            "schoolClassName": "軟件工程"
        },
        "ref": {
            "manager": "class_manager"
        }
    },
    {
        "bean": "cn.cslg.app.SchoolClass",
        "id": "class2",
        "properties": {
            "schoolClassId": "Z094142",
            "schoolClassName": "軟件工程"
        },
        "ref": {
            "manager": "class_manager"
        }
    }
]

IocListener代碼如下:

package cn.cslg.ioc;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class IocListener implements ServletContextListener{

    private final String filename = "/WEB-INF/ioc.json";
    
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // TODO Auto-generated method stub
        ServletContext context =sce.getServletContext();
        String configFile = context.getInitParameter("ioc-config");
        String path = context.getRealPath(filename);
        try {
            if (configFile != null) {
                path = context.getRealPath(configFile);
            }
            ConfigParser parser = new ConfigParser(path);
            context.setAttribute("APPLICATION_CONTEXT_BEANS", parser.parse());
        } catch (IocException e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // TODO Auto-generated method stub
        
    }

}

實現對servlet上下文的監聽,主要是調用了ConfigParser創建對象並在servlet中增加了一個值

APPLICATION_CONTEXT_BEANS

將剛剛創建的beans對象保存在其中,以便servlet使用過程中使用

ConfigParser實現了控制反轉的依賴注入,類構成如下:

 

類私有屬性:

private HashMap<String, Object> beansMap = new HashMap<>();
    private String jsonString;
    private String filename;

 

類構造方法:

public ConfigParser(String filename) {
        super();
        this.filename = filename;
        this.jsonString = ReadJsonFile(filename);
    }

使用ReadJsonFile對json進行解析,返回json的字符串,交給jsonString

ReadJsonFile代碼如下:

private String ReadJsonFile(String path) {
        File file = new File(path);
        BufferedReader reader = null;
        String laststr = "";
        try {
            reader = new BufferedReader(new FileReader(file));
            String tempString = null;
            int line = 1;
            while ((tempString = reader.readLine()) != null) {
                System.out.println("line " + line + ": " + tempString);
                laststr = laststr + tempString;
                line++;
            }
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e1) {
                }
            }
        }
        return laststr;
    }

其中解析json(JsonArray,JsonObject)需要用到json-lib,javaBean是解析需要用到common-beanutils

請使用gradle或者手動導入這兩個包,如使用gradle如下:

repositories {
    mavenCentral()
    jcenter()
    maven { url "http://repo.spring.io/release" } 

}
dependencies {
    compile group: 'net.sf.json-lib', name: 'json-lib', version: '2.4'
    compile group: 'commons-beanutils', name: 'commons-beanutils', version: '1.9.3'
    compile group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0'
    testImplementation 'junit:junit:4.12'
}
View Code

 

 

 instance方法,第一步“創建對象”,class.fornameclass.newInstance是核心,有了他們實現了類->對象的創建

public void instantiate() throws IocException {
        JSONArray beans = (JSONArray) JSONArray.fromObject(jsonString);
        try {
            for (Iterator it = beans.iterator(); it.hasNext();) {
                JSONObject bean = (JSONObject) it.next();
                String className = (String) bean.get("bean");
                String idName = (String) bean.get("id");
                Object obj;
                Class<?> class1 = Class.forName(className);
                obj = class1.newInstance();
                beansMap.put(idName, obj);
            }

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

 

injection方法,為instance創建的對象賦值屬性,主要包括了普通值,引用值

public void injection() throws IocException {
        JSONArray beans = (JSONArray) JSONArray.fromObject(jsonString);
        try {
            for (Iterator it = beans.iterator(); it.hasNext();) {
                JSONObject bean = (JSONObject) it.next();
                String beanId = bean.get("id").toString();
                Object beanObj = beansMap.get(beanId);
                if (beanObj != null) {
                    PropertyDescriptor pds[] = PropertyUtils.getPropertyDescriptors(beanObj);
                    JSONObject properties = (JSONObject) bean.get("properties");
                    JSONObject ref = (JSONObject) bean.get("ref");
                    Iterator keyIter;
                    String key;
                    String value;

                    if (properties != null) {

                        keyIter = properties.keys();
                        while (keyIter.hasNext()) {
                            key = (String) keyIter.next();
                            for (PropertyDescriptor pd : pds) {
                                if (pd.getName().equals(key)) {
                                    value = properties.get(key).toString();
                                    if (value != null)
                                        pd.getWriteMethod().invoke(beanObj, (Object) value);

                                }
                            }
                        }
                    }

                    if (ref != null) {
                        keyIter = ref.keys();
                        while (keyIter.hasNext()) {
                            key = keyIter.next().toString();
                            for (PropertyDescriptor pd : pds) {
                                if (pd.getName().equals(key)) {
                                    value = ref.get(key).toString();
                                    if (value != null)
                                        pd.getWriteMethod().invoke(beanObj, beansMap.get(value));
                                }
                            }
                        }
                    }

                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

注意在json中,配置了對象的賦值,properties是普通的屬性值,可以int,String值。而ref則是引用值,引用了其他的對象,這里是Teacher對象,表示班級由某一個老師管理。注意處理時要區別對待

其中使用了兩重循環迭代,第一重遍歷bean對象是數組JsonArray,第二重遍歷bean下的properties或者是ref是對象JsonObject,從json文件中不難看出其迭代結構

核心方法是invoke,替代了getter,setter,直接對對象的屬性進行賦值(初始化),值從json文件的properties和ref中獲取,並且一一對應的賦值

注意java中,json對象是不能直接轉化為JavaBean對象的,需要對其一層層遍歷手動賦值,php則可以直接將json轉化為對象,因為php本身是動態語言!

 

最后,parse方法返回一個HashMap,存儲了所有生成的JavaBean對象:

public HashMap<String, Object> parse() throws IocException {
        instantiate();
        injection();
        return beansMap;
    }

使用,servlet進行測試:

TestIocServlet.java的代碼:

@WebServlet("/TestIocServlet")
public class TestIocServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setCharacterEncoding("utf-8");
        resp.setContentType("text/html");
        try (PrintWriter out = resp.getWriter()) {
            @SuppressWarnings("unchecked")
            HashMap<String, Object> beans = (HashMap<String, Object>) this.getServletContext()
                    .getAttribute("APPLICATION_CONTEXT_BEANS");
            Set<String> keys = beans.keySet();
            for (String key : keys) {
                out.println(key + ": " + beans.get(key) + "<br/>");
            }
        }
    }
}
View Code

 

啟動tomcat運行servlet,瀏覽器效果如下:

很明顯,已經實現了對文本json文件的動態對象生成和賦值,不需要在java文件中重復new對象並且賦值

 

對了,項目中有個異常處理文件,IocException.java如下:

public class IocException extends Exception{

    public IocException(Throwable cause){
        super(cause);
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM