IoC的概念是控制反轉html
誰控制誰,控制什麼:傳統Java SE程序設計,咱們直接在對象內部經過new進行建立對象,是程序主動去建立依賴對象;而IoC是有專門一個容器來建立這些對象,即由Ioc容器來控制對象的建立;誰控制誰?固然是IoC 容器控制了對象;控制什麼?那就是主要控制了外部資源獲取(不僅是對象包括好比文件等)。java
爲什麼是反轉,哪些方面反轉了:有反轉就有正轉,傳統應用程序是由咱們本身在對象中主動控制去直接獲取依賴對象,也就是正轉;而反轉則是由容器來幫忙建立及注入依賴對象;爲什麼是反轉?由於由容器幫咱們查找及注入依賴對象,對象只是被動的接受依賴對象,因此是反轉;哪些方面反轉了?依賴對象的獲取被反轉了。git
傳統應用程序示意圖 有IoC容器後程序結構示意圖spring
該部分來源於 https://www.cnblogs.com/NancyStartOnce/p/6813162.htmlapache
此處就再也不介紹更多知識,能夠本身檢索相關知識點。app
這裏有兩種方式實現IoC,基於XML配置文件和註解方式。dom
基本步驟:maven
a.定義xml或者註解描述對象的依賴關係。ide
b.經過程序解析xml或者註解。工具
c.經過反射建立須要實例化的對象,並放到容器中存儲。
1.建立maven工程並加入解析xml的依賴
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.tgb.myspring</groupId> <artifactId>myspring</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <!-- 解析xml的工具 --> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> </dependencies> <build> <sourceDirectory>src</sourceDirectory> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.3</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
1.建立須要注入的測試類BeanA、BeanB。此處先貼一下工程結構,結尾處也有下載地址。
BeanA代碼
public class BeanA { private BeanB beanB; public BeanB getBeanB() { return beanB; } public void setBeanB(BeanB beanB) { this.beanB = beanB; } }
BeanB代碼
package com.lzy.ioc.test; import com.lzy.ioc.annotation.Bean; public class BeanB { private String name ; public String getName() { return name; } public void setName(String name) { this.name = name; } }
2.建立applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean name="beanA" class="com.lzy.ioc.test.BeanA"> <property name="beanB" ref="beanB"></property> </bean> <bean name="beanB" class="com.lzy.ioc.test.BeanB"> <property name ="name" value="lzy"></property> </bean> </beans>
這個xml文件很簡單,只是描述出了BeanA和BeanB的關係。
3.建立Bean和Property兩個類用於描述xml文件
package com.lzy.ioc.config; import java.util.ArrayList; import java.util.List; /** * 描述bean的基本信息 * <bean name="B" class="com.lzy.ioc"> <property name ="a" ref="A"></property> * </bean> 主要有name 、 class、 property * * @author admin * */ public class Bean { /** bean名稱 */ private String name; /** 類的全路徑包名 */ private String className; /** 類的全路徑包名 */ private List<Property> properties = new ArrayList<>(); //get/set本身生產 }
package com.lzy.ioc.config; /** * bean 的屬性值 <property name ="name" value="zhangsan" ref="bean2"></property> * * @author admin * */ public class Property { /**名稱*/ private String name; /**值*/ private String value; /**引用 */ private String ref; //get/set本身生產 }
4.建立ConfigManager類解析這個xml文件
package com.lzy.ioc.config.parse; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import com.lzy.ioc.config.Bean; import com.lzy.ioc.config.Property; /** * 讀取配置文件,返回Bean * * @author admin * */ public class ConfigManager { /** * 將配置文件轉換成Bean返回 * * @param path * @return */ public static Map<String, Bean> getConfig(String path){ //存儲解析出來的bean Map<String, Bean> beanMap = new HashMap<>(); // 1.建立解析器 SAXReader saxReader = new SAXReader(); // 2.讀取配置 文件 InputStream is = ConfigManager.class.getResourceAsStream(path); Document document = null; try { document = saxReader.read(is); } catch (DocumentException e) { e.printStackTrace(); throw new RuntimeException("請檢查xml配置文件"); } // 3.定義xpath表達式,用於匹配全部bean String xpath = "//bean"; // 4.從document中找出全部的bean元素 List<Element> beanNodes = document.selectNodes(xpath); if(beanNodes != null){ for (Element beanNode : beanNodes) { Bean bean = new Bean(); // 將xml中的bean描述信息封裝到自定義的Bean對象中 String name = beanNode.attributeValue("name"); String className = beanNode.attributeValue("class"); bean.setName(name); bean.setClassName(className); // 將xml中的property封裝到bean中 List<Element> children = beanNode.elements("property"); if(children != null){ for (Element child : children) { // 獲取xml中的Property屬性的信息,並封裝到Property對象中 Property property = new Property(); String pName = child.attributeValue("name"); String pValue = child.attributeValue("value"); String pRef = child.attributeValue("ref"); property.setName(pName); property.setRef(pRef); property.setValue(pValue); // 將property封裝到bean對象中 bean.getProperties().add(property); } // 將bean存儲到beanMap中 beanMap.put(name, bean); } } } return beanMap; } }
5.通過以上步驟咱們已經將xml轉化成咱們能處理的java對象,如今就能夠建立Bean工廠了,這裏 咱們須要先定義一個接口和抽象類,並實現xml模式的實現類。
接口AbstraBeanFactory.java
package com.lzy.ioc.main; /** * bean 工廠類 * * 只負責bean的基本管理,不負責bean 的建立 * * @author admin * */ public interface BeanFactory { /** * 根據bean的名稱獲取bean * * @param beanName * @return */ Object getBean(String beanName); }
抽象類AbstraBeanFactory
package com.lzy.ioc.main; import java.util.HashMap; import java.util.Map; public abstract class AbstraBeanFactory implements BeanFactory{ /** bean 容器,用於存儲建立的bean */ protected Map<String,Object> context = new HashMap<>(); @Override public Object getBean(String beanName) { return context.get(beanName); } }
咱們的xml Bean工廠實現類
package com.lzy.ioc.main; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import com.lzy.ioc.config.Bean; import com.lzy.ioc.config.Property; import com.lzy.ioc.config.parse.ConfigManager; import com.lzy.ioc.utils.BeanUtils; /** * * 使用xml 方式注入bean * * @author admin * */ public class ClassPathXmlApplicationContext extends AbstraBeanFactory { //配置信息 private Map<String, Bean> config; public ClassPathXmlApplicationContext(String path) { // 1.讀取配置信息,將xml轉化成com.lzy.ioc.config.Bean對象 config = ConfigManager.getConfig(path); // 2.遍歷出全部的com.lzy.ioc.config.Bean,根據信息建立實例 if(config != null){ for(Entry<String, Bean> en :config.entrySet()){ // 獲取bean描述信息 String beanName = en.getKey(); Bean bean = en.getValue(); // 判斷該bean是否已經存在,存在就再也不建立,不存在就繼續建立 Object object = context.get(beanName); if(Objects.isNull(object)){ // 建立須要注入的bean Object creatBean = creatBean(bean); // 放入到容器中 context.put(beanName, creatBean); } } } } /** * 根據Bean對象的描述信息建立 注入到ioc容器的對象實例 * * 實現:獲取bean中的className,根據反射實例化該對象 * * @param bean * @return */ private Object creatBean(Bean bean) { // 1.獲取class的路徑 String className = bean.getClassName(); Class clazz = null; try { clazz = Class.forName(className); } catch (ClassNotFoundException e) { e.printStackTrace(); throw new RuntimeException("請檢查bean的Class配置" + className); } // 2.根據clazz建立實例對象 Object beanObj = null; try { beanObj = clazz.newInstance(); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); throw new RuntimeException("bean沒有空參構造"+className); } // 3.爲建立的對象填充數據 if(Objects.nonNull(bean.getProperties())){ for (Property property : bean.getProperties()) { // 1.基本類型value注入 // 獲取注入的屬性名稱 String name = property.getName(); // 根據屬性名稱獲取對應的set方法 Method setMethod = BeanUtils.getWriteMethod(beanObj, name); Object parm = null; if(Objects.nonNull(property.getValue())){ // 獲取注入的屬性值 String value = property.getValue(); parm = value; } // 2.若是是複雜對象的注入 if(Objects.nonNull(property.getRef())){ // 先從當前容器中獲取,看是否已經被建立 Object exsiBean = context.get(property.getRef()); if(Objects.isNull(exsiBean)){ // 建立bean並放到容器中 exsiBean = creatBean(config.get(property.getRef())); context.put(property.getRef(), exsiBean); } parm = exsiBean; } // 3.調用set方法將值設置到對象中 try { setMethod.invoke(beanObj, parm); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); throw new RuntimeException("bean的屬性"+parm+"沒有對應的set方法,或者參數不正確"+className); } } } return beanObj; } }
這裏用到了BeanUtils工具類,我也先貼一下
package com.lzy.ioc.utils; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * 根據對象和屬性獲取set方法 * * @author admin * */ public class BeanUtils { /** * 獲取set方法 * * @param beanObj set方法所屬的類 * @param name set方法字段 * @return */ public static Method getWriteMethod(Object beanObj, String name) { Method method = null; try { // 1.分析bean對象 -->BeanInfo BeanInfo beanInfo = Introspector.getBeanInfo(beanObj.getClass()); // 2.根據beaninfo獲取全部屬性的描述器 PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); // 3.遍歷屬性器 if (pds != null) { for (PropertyDescriptor pd : pds) { // 獲取當前屬性 String pName = pd.getName(); if (pName.equals(name)) { // 獲取set方法 method = pd.getWriteMethod(); } } } } catch (IntrospectionException e) { e.printStackTrace(); } if (method == null) { throw new RuntimeException("請檢查" + name + "屬性的set方法是否建立"); } return method; } /** * 經過對象直接給屬性賦值,不經過set方法 * * @param bean set方法所屬的類 * @param fieldName 屬性字段名 * @param value 注入的值 * @throws IllegalArgumentException * @throws IllegalAccessException * @throws SecurityException * @throws NoSuchFieldException */ public static void setValue(Object bean, String fieldName, Object value) throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException { Field privateVar = bean.getClass().getDeclaredField(fieldName); privateVar.setAccessible(true); privateVar.set(bean, value); } /** * 不經過get方法獲取屬性值 * * * @param bean get方法所屬的類 * @param fieldName 屬性字段名 * @return * @throws IllegalArgumentException * @throws IllegalAccessException * @throws SecurityException * @throws NoSuchFieldException */ public static Object getValue(Object bean, String fieldName) throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException { Field privateVar = bean.getClass().getDeclaredField(fieldName); privateVar.setAccessible(true); return privateVar.get(bean); } }
通過以上步驟咱們就完成了一個很是簡單的基於xml配置文件的ioc容器也就是Bean工廠,如今來測試一下
package com.lzy.ioc.test; import com.lzy.ioc.main.AnnotationApplicationContext; import com.lzy.ioc.main.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 測試註解模式 //AnnotationApplicationContextTest(); // 測試配置文件模式 ClassPathXmlApplicationContextTest(); } /** * Annotation 方式注入 * */ /*public static void AnnotationApplicationContextTest(){ AnnotationApplicationContext ioc = new AnnotationApplicationContext("com.lzy.ioc"); BeanB b = (BeanB) ioc.getBean("beanB"); System.out.println(b.toString()); }*/ /** * XML 方式注入 * */ public static void ClassPathXmlApplicationContextTest(){ String path = "applicationContext.xml"; ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext(path); BeanA a = (BeanA) ioc.getBean("beanA"); System.out.println(a.getBeanB().getName()); } }
這裏能夠輸出name的值就說明注入成功了,更詳細的測試可使用debug模式查看ClassPathXmlApplicationContext ioc的Map<String,Object> context容器,這裏面存儲了全部的bean。
註解模式相比xml模式更加簡潔,開發過程當中就能完成依賴的配置,不用切換到xml配置文件。
1.定義兩個註解Bean、Autowired,被Bean註解的類表示要添加到IoC容器中,被Autowired註解的表示須要自動注入值。
package com.lzy.ioc.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 標記爲須要掃描實例化的類 * * @author admin * */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Bean { }
package com.lzy.ioc.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 標記 爲須要自動注入 * * @author admin * */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { }
2.將註解添加到測試的Bean上,既是BeanA、BeanB上。
package com.lzy.ioc.test; import com.lzy.ioc.annotation.Bean; @Bean public class BeanB { private String name ; public String getName() { return name; } public void setName(String name) { this.name = name; } }
package com.lzy.ioc.test; import com.lzy.ioc.annotation.Autowired; import com.lzy.ioc.annotation.Bean; @Bean public class BeanA { @Autowired private BeanB beanB; public BeanB getBeanB() { return beanB; } public void setBeanB(BeanB beanB) { this.beanB = beanB; } }
這樣添加註解的意思是容器須要把BeanA和BeanB都實例化到容器,而且把BeanB注入到BeanA的屬性上。
3.建立註解模式的IOC容器 AnnotationApplicationContext
package com.lzy.ioc.main; import java.io.File; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import java.util.Set; import com.lzy.ioc.annotation.Autowired; import com.lzy.ioc.annotation.Bean; import com.lzy.ioc.utils.BeanUtils; /** * bean 工廠,使用註解注入bean的bean工廠 * * * @author admin * */ public class AnnotationApplicationContext extends AbstraBeanFactory { /** * 初始化bean工廠 * * @param packageName */ public AnnotationApplicationContext(String packageName) { // 1.獲取掃描根包的實際路徑 scanPackage(packageName); // 爲容器內的bean注入屬性 injectionField(); } /** * 掃描給的包下的類,並實例化到容器中,此處只實例化,不作屬性注入 * * @param packageName */ public void scanPackage(String packageName){ String currentpath = ClassLoader.getSystemResource("").getPath(); String filePath = currentpath + (packageName.replace(".", "\\")); List<String> annotationClasses = new ArrayList<>(); getAnnotationClassName(filePath, annotationClasses); for (String path : annotationClasses) { Class<?> clazz = null; try { clazz = Class.forName(path); } catch (ClassNotFoundException e) { e.printStackTrace(); } // 獲取類上面的全部註解 Annotation[] annotations = clazz.getAnnotations(); for (Annotation annotation : annotations) { if (annotation instanceof Bean) { Object newInstance = null; try { // 實例化Bean,並將類的首字母小寫的名稱做爲實例化後的bean名稱 newInstance = clazz.newInstance(); String beanName = path.substring(path.lastIndexOf(".") + 1); char firstChar = Character.toLowerCase(beanName.charAt(0)); String replaceFirst = beanName.replaceFirst(beanName.substring(0, 1), Character.toString(firstChar)); context.put(replaceFirst, newInstance); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } } } } } /** * 向容器中注入屬性 * 使用的set方法 注入的,全部必須包含set方法 * */ public void injectionField(){ Set<Entry<String, Object>> beans = context.entrySet(); for (Entry<String, Object> beanEntry : beans) { Object bean = beanEntry.getValue(); String beanName = beanEntry.getKey(); // 獲取須要注入的屬性 Field[] declaredFields = bean.getClass().getDeclaredFields(); for (Field field : declaredFields) { Annotation[] fieldAnnotions = field.getAnnotations(); // 處理字段上的注入註解 for (Annotation fieldAnnotation : fieldAnnotions) { String fieldName = field.getName(); if(fieldAnnotation instanceof Autowired){ // 判斷容器中是否有該bean if(context.containsKey(fieldName)){ // 將容器中的值經過set方法注入到bean中 /* //這裏使用的是 set方法注入 Method getMethod = BeanUtils.getWriteMethod(bean, fieldName); try { getMethod.invoke(bean, context.get(fieldName)); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); }*/ // 不在依賴屬性的set方法爲屬性注入值 try { BeanUtils.setValue(bean, fieldName, context.get(fieldName)); } catch (IllegalArgumentException | IllegalAccessException | SecurityException | NoSuchFieldException e) { e.printStackTrace(); } } }else{ // 容器中沒有該值,須要建立該bean // TODO throw new RuntimeException("請檢查"+ beanName + "類中的" + fieldName + "字段是否已經注入到容器中"); } } break; } } } /** * 獲取路徑下的全部全類名 * * @param filePat * @param annotationClasses */ public void getAnnotationClassName(String filePath, List<String> annotationClasses) { String currentpath = ClassLoader.getSystemResource("").getPath(); File file = new File(filePath); File[] childFiles = file.listFiles(); for (File childFile : childFiles) { if (childFile.isDirectory()) { // 目錄 getAnnotationClassName(childFile.getPath(), annotationClasses); } else { // 文件 String childPath = childFile.getPath(); if (childPath.endsWith(".class")) { // 是class文件 String packageNameOfClass = childPath.substring(currentpath.length() - 1, childPath.length() - 6) .replace("\\", "."); annotationClasses.add(packageNameOfClass); } } } } }
4.建立測試代碼,咱們能夠經過debug模式查看容器內部是否已經建立了被註解加持的對象。
package com.lzy.ioc.test; import com.lzy.ioc.main.AnnotationApplicationContext; import com.lzy.ioc.main.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 測試註解模式 AnnotationApplicationContextTest(); // 測試配置文件模式 //ClassPathXmlApplicationContextTest(); } /** * Annotation 方式注入 * */ public static void AnnotationApplicationContextTest(){ AnnotationApplicationContext ioc = new AnnotationApplicationContext("com.lzy.ioc"); BeanB b = (BeanB) ioc.getBean("beanB"); System.out.println(b.toString()); } /** * XML 方式注入 * */ public static void ClassPathXmlApplicationContextTest(){ String path = "applicationContext.xml"; ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext(path); BeanA a = (BeanA) ioc.getBean("beanA"); //System.out.println(a.getBeanB().getName()); } }
至此咱們的兩種簡單IOC就實現完成了,最後貼一下碼雲的代碼地址:
https://gitee.com/liubluesnow/MyIOC