控制反轉(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 + "]"; } }

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(); }
此時,我們有一個老師,兩個班級,那么在實際使用中是不是就要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' }
instance方法,第一步“創建對象”,class.forname和class.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/>"); } } } }
啟動tomcat運行servlet,瀏覽器效果如下:
很明顯,已經實現了對文本json文件的動態對象生成和賦值,不需要在java文件中重復new對象並且賦值
對了,項目中有個異常處理文件,IocException.java如下:
public class IocException extends Exception{ public IocException(Throwable cause){ super(cause); } }