1. 程式人生 > >深入理解Spring IOC和集合注入

深入理解Spring IOC和集合注入

一、什麼是Ioc/DI?

IoC 容器:最主要是完成了完成物件的建立和依賴的管理注入等等。

先從我們自己設計這樣一個視角來考慮:

  所謂控制反轉,就是把原先我們程式碼裡面需要實現的物件建立、依賴的程式碼,反轉給容器來幫忙實現。那麼必然的我們需要建立一個容器,同時需要一種描述來讓容器知道需要建立的物件與物件的關係。這個描述最具體表現就是我們可配置的檔案。

物件和物件關係怎麼表示?

  可以用 xml , properties 檔案等語義化配置檔案表示。

描述物件關係的檔案存放在哪裡?

  可能是 classpath , filesystem ,或者是 URL 網路資源, servletContext 等。

回到正題,有了配置檔案,還需要對配置檔案解析。下面,我們藉助SAXBuilder來對applicationContext.xml進行解析,並生成對應的Bean物件和物件集合,從而簡單地還原IOC的機制。

(1)定義beanFactory介面

public interface beanFactory {
    Object getBean(String beanId);

}

(2)  ClasspathXmlApplicationContext.java

package com.oracle.rebuildSpring;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;

import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;

public class ClasspathXmlApplicationContext implements beanFactory{
    private static HashMap<String,Object> map = new HashMap<String,Object>();
    private Object obj;
    public ClasspathXmlApplicationContext(String path) throws Exception {
        //讀取applicationContext配置檔案的資訊
        SAXBuilder builder = new SAXBuilder();
        //得到document檔案物件
        Document doc = builder.build(this.getClass().getResourceAsStream(path));
        //得到root根元素
        Element root = doc.getRootElement();
        //System.out.println(root.getName());
        //先得到<context:property-placeholder location="db.properties"/>的標籤資訊
        Element placeholder = root.getChild("context");
        String location = (String)placeholder.getAttributeValue("location");
        //System.out.println(location);
        //通過Connection生成Properties物件
            
        //得到bean標籤的集合
        List<Element> beans = root.getChildren("bean");
        for(Element bean:beans) {
            String id = (String)bean.getAttributeValue("id");
            //System.out.println(id);
            String classname = (String)bean.getAttributeValue("class");
            //System.out.println(classname);
            //例項化物件,並set注入引數
            /**
             * 用另一種方法,通過Method的invoke從而實現set方法的實現
             * 用法:
             * Method method = obj.getClass().getDeclaredMethods(String methodName,String.class);
             * method.invoke(obj,String params);
             *
             */
            
            obj= Class.forName(classname).newInstance();
            //System.out.println(obj.getClass().getName());
            List<Element> properties = (List<Element>) bean.getChildren("property");    
            
            for(Element property:properties) {
            //通過java反射中的Method來執行obj.setName(value);
                String name = property.getAttributeValue("name");
                String value= property.getAttributeValue("value");
                String ref = property.getAttributeValue("ref");
                
            //如果是<property name="" value=""/>這種形式,即value不為null
                if(value!=null && ref==null) {
            //對value的值進行判斷,如果帶$符號,需要getPropety()
                if("$".equals(value.substring(0, 1))) {
                    int first = value.indexOf("{");
                    int last = value.lastIndexOf("}");
                    String key =value.substring(first+1, last);
                    //System.out.println("key is "+key);
                    String information =Connection.getPropety(key);
                    
                    //set注入給物件
                    //System.out.println("Information is "+information);
                    Field field = obj.getClass().getDeclaredField(name);
                    field.setAccessible(true);
                    field.set(obj, information);    
                    //System.out.println("注入成功!");
                }else {
                    //實現首字母大寫的效果
                    String methodName = "set"+name.substring(0, 1).toUpperCase() + name.substring(1);                
                    Field field = obj.getClass().getDeclaredField(name);
                    field.setAccessible(true);
                    String type = field.getGenericType().toString();
                    
                    if(type.equals("class java.lang.Integer") || type.equals("int")) {
                        field.set(obj, Integer.parseInt(value));
                    }
                    else if(type.equals("class java.lang.Double")) {                
                        field.set(obj, Double.parseDouble(value));
                    }
                    else if(type.equals("class java.lang.Float")) {                            
                        field.set(obj, Float.parseFloat(value));
                    }
                    else if(type.equals("class java.lang.Character") || type.equals("char")) {                            
                        if(value.length()>1) {
                            throw new Exception("Could not create "+name+" because the value is not correct.");
                        }
                        field.set(obj, value.charAt(0));
                    }
                    else if(type.equals("class java.lang.Boolean")) {
                        field.set(obj, Boolean.parseBoolean(value));
                    }else {
                        field.set(obj, value);
                    }                
                  }
              
                }else if(value==null && ref==null){
                    String methodName = "set"+name.substring(0, 1).toUpperCase() + name.substring(1);        
                    //此時,說明value為null,即為<property name=""></property>這種型別
                    //那麼<property>下面的標籤分為五種:<value>、<list>、<set>、<map>和<props>,<props>暫時不寫                
                    Element valueElement = property.getChild("value");
                    Element listElement = property.getChild("list");
                    Element setElement = property.getChild("set");
                    Element mapElement = property.getChild("map");
                    if(valueElement!=null) {
                        String valueText = valueElement.getText();
                        Field field = obj.getClass().getDeclaredField(name);
                        field.setAccessible(true);
                        field.set(obj, valueText);
                    }else if(listElement!=null) {
                        //判斷list下,是value標籤還是ref標籤
                        List<Element> list_values = listElement.getChildren("value");
                        List<Element> list_refs = listElement.getChildren("ref");                    
                        if(list_values!=null) {
                            String[] strs = new String[list_values.size()];
                            for(int i=0;i<list_values.size();i++) {
                                strs[i]=list_values.get(i).getText().trim();
                            }
                            Field field = obj.getClass().getDeclaredField(name);
                            field.setAccessible(true);
                            field.set(obj, strs);
                        }else {
                            //如果不是,說明是屬於<list><ref></ref></list>的形式
                            //但是,<ref>標籤中的值可能代表“物件”,也可能代表list集合的子元素,所以需要再區分
                            Field field = obj.getClass().getDeclaredField(name);
                            String paramType = field.getGenericType().toString();
                            if("class".equals(paramType.substring(0, 5).trim())) {
                             //如果擷取前五個字串為“class”,說明為物件,由於暫時還不清楚如何通過Java反射機制生成新建陣列物件
                             //即 Object[] objs = new Student[]{};像這樣,所以物件陣列的spring注入配置暫時擱置
                                
                            }
                        }
                    }
                }else {
                    //說明是屬於<property name="" ref=""/>的形式            
                    Field field = obj.getClass().getDeclaredField(name);
                    field.setAccessible(true);
                    field.set(obj, getBean(ref));
                    //第二種寫法:
                    //String methodName = "set"+name.substring(0, 1).toUpperCase() + name.substring(1);                
                    //Object target = getBean(ref);
                    //System.out.println(target.getClass().getName());
                    //Method method = obj.getClass().getDeclaredMethod(methodName, target.getClass());
                    //method.invoke(obj, target);
                    
                }
            }                    
            //將物件新增到map物件中去
            map.put(id, obj);            
        }                
    }
    
    @Override
    public Object getBean(String beanId) {
        return map.get(beanId);
    }
    
}
/**
 * 考慮到程式碼的效能,利用單例模型,使得讀取db.properties檔案一次,通過靜態程式碼塊實現IO流對檔案的讀
 */
class Connection{    
    public static  Properties properties = new Properties();
    
    static{
    try {            
        properties.load(ClasspathXmlApplicationContext.class.getResourceAsStream("db.properties"));
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
     }
    }
    
    public static String getPropety(String key) {       
        return properties.getProperty(key);
    }

}

(3) 例項物件1:Teacher

public class Teacher {
    private String name;
    private String course;
    private int age;
    private char cha;
    public char getCha() {
        return cha;
    }

    public void setCha(char cha) {
        this.cha = cha;
    }

    public String getCourse() {
        return course;
    }

    public void setCourse(String course) {
        this.course = course;
    }

    public String getName() {
        return name;
    }

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

    }

    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }   

}

(4) 例項物件2: Students

public class Students {

    private String className;
    private String grade;
    private String[] students;
    private Teacher teacher;
    
    public Teacher getTeacher() {
        return teacher;
    }
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
    public String[] getStudents() {
        return students;
    }
    public void setStudents(String[] students) {
        this.students = students;
    }
    public String getClassName() {
        return className;
    }
    public void setClassName(String className) {
        this.className = className;
    }
    public String getGrade() {
        return grade;
    }
    public void setGrade(String grade) {
        this.grade = grade;
    }
    @Override
    public String toString() {
        return "Students [className=" + className + ", grade=" + grade + ", students=" + Arrays.toString(students)
                + ", teacher=" + teacher.getName() + "]";
    }     

}

(5) 例項物件3:DBUtil

package com.oracle.rebuildSpring;

public class DBUtil {
    private String driverName;
    private String url;
    private String username;
    private String password;
    
    public String getDriverName() {
        return driverName;
    }
    public void setDriverName(String driverName) {
        this.driverName = driverName;
    }
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }

}

(6) Test測試類
public class Test {

    public static void main(String[] args) throws Exception {       

        beanFactory bean = new ClasspathXmlApplicationContext("applicationContext.xml ");
        //測試Teacher
        Teacher teacher = (Teacher) bean.getBean("teacher");
        System.out.println(teacher.getName()+" 是 "+teacher.getCourse()+" 已經 "+teacher.getAge()+" 歲了。"+teacher.getCha());
        
        //測試DBUtil
        DBUtil util = (DBUtil) bean.getBean("dbUtil");
        System.out.println(util.getUsername()+","+util.getPassword()+","+util.getDriverName()+","+util.getUrl());
        
        //測試Students集合
        Students students = (Students) bean.getBean("students");
        System.out.println(students);
        for(String stu:students.getStudents()) {
            System.out.println(stu);
        }       
    }

}

結果:

小溪 是 一班 已經 23 歲了。a
rocky,oracle,com.mysql.jdbc.Driver,jdbc:mysql://localhost:3306/students
Students [className=一班, grade=六年級, students=[李明, 韓美美], teacher=小溪]
李明
韓美美

(7) applicationContext.xml配置檔案

<?xml version="1.0" encoding="UTF-8"?>
<beans>
 <context location="db.properties">
 </context>
    <bean id="teacher"
        class="com.oracle.rebuildSpring.Teacher">
        <property name="name" value="小溪"/>        
        <property name="course" value="一班"/>    
        <property name="age" value="23"/>    
        <property name="cha" value="a"/>
    </bean>
    <!-- 配置db.properties的bean -->
    <bean id="dbUtil"  class="com.oracle.rebuildSpring.DBUtil" >
        <property name="driverName" value="${driverName}"></property>        
        <property name="url" value="${url}"></property>        
        <property name="username" value="${username}"></property>        
        <property name="password" value="${password}"></property>        
    </bean>
    
    <!-- 完善一下,讀取Property標籤的幾種形式:List、Set、Map以及陣列-->
    <!-- 第一、裝配陣列 -->
    <bean id="students" class="com.oracle.rebuildSpring.Students">
        <property name="className">
            <value>一班</value>
        </property>
        <property name="grade" value="六年級"/>
        <property name="students">
            <list>
                <value>李明</value>
                <value>韓美美</value>        
            </list>
        </property>
        <property name="teacher" ref="teacher"></property>            
    </bean>              
</beans>