深入理解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>