IOC的简单实现

IoC的基本知识

IoC的概念是控制反转

谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

                传统应用程序示意图                                            有IoC容器后程序结构示意图

该部分来源于 https://www.cnblogs.com/NancyStartOnce/p/6813162.html

此处就不再介绍更多知识,可以自己检索相关知识点。

IoC的简单实现

这里有两种方式实现IoC,基于XML配置文件和注解方式。

基本步骤:

    a.定义xml或者注解描述对象的依赖关系。

    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。此处先贴一下工程结构,结尾处也有下载地址。

102713_g3Sb_2528990.png

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

转载于:https://my.oschina.net/u/2528990/blog/1806017

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值