IOC的簡單實現

IoC的基本知識

IoC的概念是控制反轉html

誰控制誰,控制什麼:傳統Java SE程序設計,咱們直接在對象內部經過new進行建立對象,是程序主動去建立依賴對象;而IoC是有專門一個容器來建立這些對象,即由Ioc容器來控制對象的建立;誰控制誰?固然是IoC 容器控制了對象;控制什麼?那就是主要控制了外部資源獲取(不僅是對象包括好比文件等)。java

爲什麼是反轉,哪些方面反轉了:有反轉就有正轉,傳統應用程序是由咱們本身在對象中主動控制去直接獲取依賴對象,也就是正轉;而反轉則是由容器來幫忙建立及注入依賴對象;爲什麼是反轉?由於由容器幫咱們查找及注入依賴對象,對象只是被動的接受依賴對象,因此是反轉;哪些方面反轉了?依賴對象的獲取被反轉了。git

                傳統應用程序示意圖                                            有IoC容器後程序結構示意圖spring

該部分來源於 https://www.cnblogs.com/NancyStartOnce/p/6813162.htmlapache

此處就再也不介紹更多知識,能夠本身檢索相關知識點。app

IoC的簡單實現

這裏有兩種方式實現IoC,基於XML配置文件和註解方式。dom

基本步驟:maven

    a.定義xml或者註解描述對象的依賴關係。ide

    b.經過程序解析xml或者註解。工具

    c.經過反射建立須要實例化的對象,並放到容器中存儲。

xml方式實現IoC

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。

註解方式實現IoC

註解模式相比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

相關文章
相關標籤/搜索