轉載來源:【https://www.cnblogs.com/fingerboy/p/5425813.html】
前言:
在逛博客園的時候突然發現一篇關於事務的好文章,說起spring事物就離不開AOP和動態代理,在研究的過程中突然想起spring的兩大特性IOC和AOP,之前對這方面也是理解很淺,於是在網上查了一下關於springIOC的文章,發現了一篇好文章,自己也跟着手動創建了一個項目,打斷點隨着流程走了一遍,這篇文章對IOC的理解可以起到很大的作用,在這里轉載記錄一下:
主要思想:
提到IOC,第一反應就是控制反轉,我以前以為SpringIOC就是控制反轉,控制反轉就是SpringIOC,當然這種理解是錯誤的,控制反轉是一種思想,一種模式,而Spring的IOC容器是實現了這種思想這種模式的一個載體.
使用過Spring的人都熟知,SpringIOC容器可以在對象生成或初始化時就直接將數據注入到對象中,如果對象A的屬性是另一個對象B,還可以將這個對象B的引用注入到注入到A的數據域中.
如果在初始化對象A的時候,對象B還沒有進行初始化,而A又需要對象B作為自己的屬性,那么就會用一種遞歸的方式進行注入,這樣就可以把對象的依賴關系清晰有序的建立起來.
IOC容器解決問題的核心就是把創建和管理對象的控制權從具體的業務對象手中搶過來.由IOC容器來管理對象之間的依賴關系,並由IOC容器完成對象的注入.這樣就把應用從復雜的對象依賴關系的管理中解放出來,簡化了程序的開發過程.
下圖是這個簡單IOC容器的類圖(原諒我真沒學過UML,湊合看吧):
程序中所有的Bean之間的依賴關系我們是放在一個xml文件中進行維護的,就是applicationContext.xml,ConfigManager類完成的功能是讀取xml,並將所有讀取到有用的信息封裝到我們創建的一個Map<String,Bean>集合中,用來在初始化容器時創建bean對象.
定義一個BeanFactory的接口,接口中有一個getBean(String name)方法,用來返回你想要創建的那個對象.
然后定義一個該接口的實現類ClassPathXmlApplicationContext.就是在這個類的構造方法中,初始化容器,通過調用ConfigManager的方法返回的Map集合,通過反射和內省一一創建bean對象.這里需要注意,對象的創建有兩個時間點,這取決與bean標簽中scope屬性的值:
如果scope="singleton",那么對象在容器初始化時就已創建好,用的時候只需要去容器中取即可.
如果scope="prototype",那么容器中不保存這個bean的實例對象,每次開發者需要使用這個對象時再進行創建.
使用的主要知識點:
dom4j解析xml文件
xpath表達式(用於解析xml中的標簽
java反射機制
內省(獲取Bean屬性的set方法進行賦值)
項目結構圖及介紹如下:
項目需要的jar包與項目結構已經在上圖中介紹了,這個項目所能實現的功能如下:
1、IOC容器能管理對象的創建以及對象之間的依賴關系.
2、能夠實現數據的自動類型轉換(借助BeanUtils).
3、能夠實現scope="singleton"和scope="prototype"的功能,即能夠控制對象是否為單例.
下面介紹代碼部分:
application.xml:
<?xml version="1.0" encoding="utf-8"?> <beans> <bean name="student" class="com.entiy.Student" > <property name="name" value="逝清雪"></property> </bean> <bean name="teacher" class="com.entiy.Teacher"> <property name="student" ref="student"></property> </bean> <bean name="person" class="com.entiy.Person" scope="prototype"> <property name="teacher" ref="teacher"></property> <property name="student" ref="student"></property> </bean> </beans>
實體類Student,Teacher,Person:
public class Person { private Student student; private Teacher teacher; public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } public Teacher getTeacher() { return teacher; } public void setTeacher(Teacher teacher) { this.teacher = teacher; } } public class Student { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } public class Teacher { private Student student; public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } }
用於封裝Bean標簽信息的Bean類:
public class Bean { private String name; private String className; private String scope="singleton"; private List<Property> properties = new ArrayList<Property>(); public String getScope() { return scope; } public void setScope(String scope) { this.scope = scope; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public List<Property> getProperties() { return properties; } public void setProperties(List<Property> properties) { this.properties = properties; } }
用與封裝Bean子標簽property內容的Property類:
public class Property { private String name; private String value; private String ref; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public String getRef() { return ref; } public void setRef(String ref) { this.ref = ref; } }
ConfigManager類:
public class ConfigManager { private static Map<String, Bean> map = new HashMap<String, Bean>(); // 讀取配置文件並返回讀取結果 // 返回Map集合便於注入,key是每個Bean的name屬性,value是對應的那個Bean對象 @SuppressWarnings("unchecked") public static Map<String, Bean> getConfig(String path) { /* * dom4j實現 1.創建解析器 2.加載配置文件,得到document對象 3.定義xpath表達式,取出所有Bean元素 * 4.對Bean元素繼續遍歷 4.1將Bean元素的name/class屬性封裝到bean類屬性中 * 4.2獲得bean下的所有property子元素 4.3將屬性name/value/ref分裝到類Property類中 * 5.將property對象封裝到bean對象中 6.將bean對象封裝到Map集合中,返回map */ // 1.創建解析器 SAXReader reader = new SAXReader(); // 2.加載配置文件,得到document對象 InputStream is = ConfigManager.class.getResourceAsStream(path); Document doc = null; try { doc = reader.read(is); } catch (DocumentException e) { e.printStackTrace(); throw new RuntimeException("請檢查您的xml配置是否正確"); } // 3.定義xpath表達式,取出所有Bean元素 String xpath = "//bean"; // 4.對Bean元素繼續遍歷 List<Element> list = doc.selectNodes(xpath); if (list != null) { // 4.1將Bean元素的name/class屬性封裝到bean類屬性中 // 4.3將屬性name/value/ref分裝到類Property類中 for (Element bean : list) { Bean b = new Bean(); String name = bean.attributeValue("name"); String clazz = bean.attributeValue("class"); String scope = bean.attributeValue("scope"); b.setName(name); b.setClassName(clazz); if (scope != null) { b.setScope(scope); } // 4.2獲得bean下的所有property子元素 List<Element> children = bean.elements("property"); // 4.3將屬性name/value/ref分裝到類Property類中 if (children != null) { for (Element child : children) { Property prop = new Property(); String pName = child.attributeValue("name"); String pValue = child.attributeValue("value"); String pRef = child.attributeValue("ref"); prop.setName(pName); prop.setRef(pRef); prop.setValue(pValue); // 5.將property對象封裝到bean對象中 b.getProperties().add(prop); } } // 6.將bean對象封裝到Map集合中,返回map map.put(name, b); } } return map; } }
BeanFactory接口:
public interface BeanFactory { //核心方法getBean Object getBean(String name); }
ClassPathXmlApplicationContext類:
public class ClassPathXmlApplicationContext implements BeanFactory { // 獲得讀取的配置文件中的Map信息 private Map<String, Bean> map; // 作為IOC容器使用,放置sring放置的對象 private Map<String, Object> context = new HashMap<String, Object>(); public ClassPathXmlApplicationContext(){ super(); } public ClassPathXmlApplicationContext(String path) { // 1.讀取配置文件得到需要初始化的Bean信息 map = ConfigManager.getConfig(path); // 2.遍歷配置,初始化Bean for (Entry<String, Bean> en : map.entrySet()) { String beanName = en.getKey(); Bean bean = en.getValue(); Object existBean = context.get(beanName); // 當容器中為空並且bean的scope屬性為singleton時 if (existBean == null && bean.getScope().equals("singleton")) { // 根據字符串創建Bean對象 Object beanObj = createBean(bean); // 把創建好的bean對象放置到map中去 context.put(beanName, beanObj); } } } // 通過反射創建對象 @SuppressWarnings("rawtypes") private Object createBean(Bean bean) { // 創建該類對象 Class clazz = null; try { clazz = Class.forName(bean.getClassName()); } catch (ClassNotFoundException e) { e.printStackTrace(); throw new RuntimeException("沒有找到該類" + bean.getClassName()); } Object beanObj = null; try { beanObj = clazz.newInstance(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("沒有提供無參構造器"); } // 獲得bean的屬性,將其注入 if (bean.getProperties() != null) { for (Property prop : bean.getProperties()) { // 注入分兩種情況 // 獲得要注入的屬性名稱 String name = prop.getName(); String value = prop.getValue(); String ref = prop.getRef(); // 使用BeanUtils工具類完成屬性注入,可以自動完成類型轉換 // 如果value不為null,說明有 if (value != null) { Map<String, String[]> parmMap = new HashMap<String, String[]>(); parmMap.put(name, new String[] { value }); try { BeanUtils.populate(beanObj, parmMap); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("請檢查你的" + name + "屬性"); } } if (ref != null) { // 根據屬性名獲得一個注入屬性對應的set方法 // Method setMethod = BeanUtil.getWriteMethod(beanObj, // name); // 看一看當前IOC容器中是否已存在該bean,有的話直接設置沒有的話使用遞歸,創建該bean對象 Object existBean = context.get(prop.getRef()); if (existBean == null) { // 遞歸的創建一個bean existBean = createBean(map.get(prop.getRef())); // 放置到context容器中 // 只有當scope="singleton"時才往容器中放 if (map.get(prop.getRef()).getScope() .equals("singleton")) { context.put(prop.getRef(), existBean); } } try { // setMethod.invoke(beanObj, existBean)通過BeanUtils為beanObj設置屬性 BeanUtils.setProperty(beanObj, name, existBean); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("您的bean的屬性" + name + "沒有對應的set方法"); } } } } return beanObj; } @Override public Object getBean(String name) { Object bean = context.get(name); // 如果為空說明scope不是singleton,那么容器中是沒有的,這里現場創建 if (bean == null) { bean = createBean(map.get(name)); } return bean; }
最后就是一個測試類TestBean:
public class TestBean { @Test public void test(){ BeanFactory bf= new ClassPathXmlApplicationContext("/applicationContent.xml"); Person s=(Person)bf.getBean("person"); Person s1=(Person)bf.getBean("person"); System.out.println(s==s1); System.out.println(s1); Student stu1=(Student) bf.getBean("student"); Student stu2=(Student) bf.getBean("student"); String name=stu1.getName(); System.out.println(name); System.out.println(stu1==stu2); } }
測試結果:
false com.entiy.Person@79b4d0f 逝清雪 true