Simple implementation of IOC

Basic knowledge of IoC

The concept of IoC is Inversion of Control

Who controls who controls what: In traditional Java SE programming, we create objects directly inside the object through new, and the program actively creates dependent objects; while IoC has a special container to create these objects, that is, the Ioc container controls Object Creation; Who Controls Who? Of course the IoC container controls the object; what? That is, it mainly controls the acquisition of external resources (not just objects including files, etc.).

Why is it reversed, and which aspects are reversed: if there is a reverse, there will be a forward rotation. Traditional applications are actively controlled by ourselves in the object to directly obtain the dependent object, that is, forward rotation; while the reverse rotation is performed by the container. Help create and inject dependent objects; why reverse? Because the container helps us find and inject dependent objects, the object only passively accepts dependent objects, so it is reversed; which aspects are reversed? The acquisition of dependent objects is reversed.

                Diagram of traditional application program Diagram of program structure with IoC container

This part comes from https://www.cnblogs.com/NancyStartOnce/p/6813162.html

No more knowledge will be introduced here, you can search for relevant knowledge points yourself.

Simple Implementation of IoC

There are two ways to implement IoC, based on XML configuration files and annotations.

The basic steps:

    a. Define xml or annotations to describe the dependencies of objects.

    b. Parse xml or annotations through the program.

    c. Create the object that needs to be instantiated through reflection and store it in the container.

xml way to achieve IoC

1. Create a maven project and add dependencies for parsing 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. Create the test classes BeanA and BeanB that need to be injected. The project structure is posted here first, and there is also a download address at the end.

BeanA code


public class BeanA {
	private BeanB beanB;

	public BeanB getBeanB() {
		return beanB;
	}

	public void setBeanB(BeanB beanB) {
		this.beanB = beanB;
	}
	
}

BeanB code

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. Create 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>

This xml file is very simple, it just describes the relationship between BeanA and BeanB.

3. Create two classes, Bean and Property, to describe the xml file

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. Create a ConfigManager class to parse this xml file

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. After the above steps, we have transformed xml into java objects that we can handle, and now we can create a bean factory. Here we need to define an interface and abstract class, and implement the implementation class of the xml mode.

Interface AbstraBeanFactory.java

package com.lzy.ioc.main;
/**
 * bean 工厂类
 * 
 * 只负责bean的基本管理,不负责bean 的创建
 * 
 * @author admin
 *
 */
public interface BeanFactory {

	/**
	 * 根据bean的名称获取bean
	 * 
	 * @param beanName
	 * @return
	 */
	Object getBean(String beanName);
	
	
}

Abstract class 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);
	}
}

Our xml bean factory implementation class

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;  
	}

}

The BeanUtils tool class is used here, I will post it first

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);
	}
}

After the above steps, we have completed a very simple ioc container based on xml configuration file, that is, Bean factory. Now let's test it

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());
	}
	
}

The value of name can be output here, indicating that the injection is successful. For more detailed tests, you can use the debug mode to view the Map<String, Object> context container of the ClassPathXmlApplicationContext ioc, which stores all the beans.

Annotation to achieve IoC

The annotation mode is more concise than the xml mode, and the configuration of dependencies can be completed during the development process without switching to the xml configuration file.

1. Define two annotation beans, Autowired. The class annotated by Bean indicates that it is to be added to the IoC container, and the expression annotated by Autowired needs to automatically inject values.

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. Add the annotation to the tested Bean, both BeanA and 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;
	}
	
}

This annotation means that the container needs to instantiate both BeanA and BeanB into the container, and inject BeanB into the properties of BeanA.

3. Create an IOC container for annotation mode 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. Create test code, we can use the debug mode to check whether the object supported by the annotation has been created inside the container.

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());
	}
	
}

So far, our two simple IOCs have been implemented, and finally paste the code address of the code cloud:

https://gitee.com/liubluesnow/MyIOC

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325251330&siteId=291194637