手写简易IOC容器
手写简易IOC容器
1. IOC介绍
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:
- 谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
- 为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
IOC实现流程图:
从示意图可以看出,当web容器启动的时候,spring的全局bean的管理器会去xml配置文件中扫描的包下面获取到所有的类,并根据你使用的注解,进行对应的封装,封装到全局的bean容器中进行管理,一旦容器初始化完毕,beanID以及bean实例化的类对象信息就全部存在了,现在我们需要在某个service里面调用另一个bean的某个方法的时候,我们只需要依赖注入进来另一个bean的Id即可,调用的时候,spring会去初始化完成的bean容器中获取即可,如果存在就把依赖的bean的类的实例化对象返回给你,你就可以调用依赖的bean的相关方法或属性等;
IOC的实现原理:https://www.jiansh·u.com/p/ad05cfe7868e
2.简单代码实现
实现简单的IOC可以分为两步
1.读取xml文件
2.通过反射动态的操作对象
package com.example.demo.ioctest;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class MySingleIOC {
/**
* 模拟spring容器 用于保存bean的键值对的hashMap
*/
private Map<String, Object> beanMap = new HashMap<>();
public MySingleIOC() { }
/**
* 有参构造函数 初始化xml
*/
public MySingleIOC(String xmlName) throws Exception {
loadbean(xmlName);
}
/**
* 私有方法加载bean
*/
private void loadbean(String xmlName) throws Exception {
//第一步 读取 xml文件
InputStream resourceAsStream = MySingleIOC.class.getClassLoader().getResourceAsStream(xmlName);
if (resourceAsStream == null) {
//当找不到xml抛出异常
throw new FileNotFoundException("该XML文件无法找到:" + xmlName);
}
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
Document parse = documentBuilder.parse(resourceAsStream);
resourceAsStream.close();
NodeList beanList = parse.getElementsByTagName("bean");
//遍历bean标签
for (int i = 0; i < beanList.getLength(); i++) {
Node node = beanList.item(i);
if (node instanceof Element) {
Element el = (Element) node;
String beanId = el.getAttribute("id");
String beanClass = el.getAttribute("class");
//第二步 反射动态获取对象
Class beanClazz = null;
try {
//加载beanClass
beanClazz = Class.forName(beanClass);
} catch (ClassNotFoundException e) {
e.getMessage();
return;
}
//创建Bean对象
Object beanObj = beanClazz.newInstance();
NodeList propertyList = el.getElementsByTagName("property");
//遍历子标签property
for (int j = 0; j < propertyList.getLength(); j++) {
Node item = propertyList.item(j);
if (item instanceof Element) {
Element ele = (Element) item;
//拿到变量名称
String name = ele.getAttribute("name");
//拿到变量值(不一定都是String类型下面要做转换)
String value = ele.getAttribute("value");
//通过反射将bean对象指定属性
Field declaredField = beanObj.getClass().getDeclaredField(name);
//将私有属性设置为可访问
declaredField.setAccessible(true);
//获取成员属性的类型名称,若非字符串类型,则需要做相应转换
String fieldTypeName = declaredField.getType().getName();
Object o = ParamType(fieldTypeName, value);
//为该成员属性赋值
declaredField.set(beanObj, o);
//将该字段属性设置值
beanMap.put(beanId, beanObj);
}
}
}
}
}
/**
* 转换运行时参数类型(做简单演示,目前只有String Integer和int可扩展)
*/
private Object ParamType(String fieldTypeName, String value) {
Object obj = null;
//判断该成员属性是否为int或Integer类型
if ("int".equals(fieldTypeName) || "java.lang.Integer".equals(fieldTypeName)) {
//转换为int类型并为该成员属性赋值
int intFieldValue = Integer.parseInt(value);
obj = intFieldValue;
}//判断该成员属性是否为String类型
if ("java.lang.String".equals(fieldTypeName)) {
//为该成员属性赋值
obj = value;
}//判断另外类型同理
//if(){ }
return obj;
}
/**
* 获取bean
*/
public Object getBean(String beanName) {
Object bean = beanMap.get(beanName);
if (bean == null) {
throw new IllegalArgumentException("无法实例化该名称的bean,请确定名称是否正确 " + beanName);
}
return bean;
}
}
测试使用bean代码:
public class User {
private String name;
private Integer age;
private String sex;
//省略get、set方法
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
public class Home {
private String country;
private String province;
private String homeCity;
private String workCity;
//省略get、set方法
@Override
public String toString() {
return "Home{" +
"country='" + country + '\'' +
", province='" + province + '\'' +
", homeCity='" + homeCity + '\'' +
", workCity='" + workCity + '\'' +
'}';
}
}
bean配置文件MySpring.xml:
<beans>
<bean id="user" class="com.xiaoli.model.User">
<property name="name" value="小立"></property> <!--姓名-->
<property name="age" value="22"></property> <!--年龄-->
<property name="sex" value="男"></property> <!--性别-->
</bean>
<bean id="home" class="com.xiaoli.model.Home">
<property name="country" value="中国"></property> <!--国家-->
<property name="province" value="湖北"></property> <!--省份-->
<property name="homeCity" value="宜昌"></property> <!--家乡城市-->
<property name="workCity" value="杭州"></property> <!--工作城市-->
</bean>
</beans>
演示类:
package com.example.demo.ioctest;
/**
* @author y001
*/
public class DemoTest {
public static void main(String[] args) throws Exception{
MySingleIOC ioc=new MySingleIOC("MySpring.xml");
User user = (User)ioc.getBean("user");
System.out.println(user);
Home home = (Home)ioc.getBean("home");
System.out.println(home);
}
}
IOC使用注解区别简介
注解 | 含义 |
---|---|
@Component | 最普通的组件,可以被注入到spring容器进行管理 |
@Repository | 作用于持久层 |
@Service | 作用于业务逻辑层 |
@Controller | 作用于表现层 |
@Component, @Service, @Controller, @Repository是spring注解,注解后可以被spring框架所扫描并注入到spring容器来进行管理
@Component是通用注解,其他三个注解是这个注解的拓展,并且具有了特定的功能
@Repository注解在持久层中,具有将数据库操作抛出的原生异常翻译转化为spring的持久层异常的功能。
@Controller层是spring-mvc的注解,具有将请求进行转发,重定向的功能。
@Service层是业务逻辑层注解,这个注解只是标注该类处于业务逻辑层。
用这些注解对应用进行分层之后,就能将请求处理,义务逻辑处理,数据库操作处理分离出来,为代码解耦,也方便了以后项目的维护和开发。
注解对比 | @Resource | @Autowire |
---|---|---|
注解来源 | JDK | Spring |
装配方式 | 优先按名称 | 优先按类型 |
属性 | name,type | required |
- @Resource和@Autowired都可以用来装配bean,都可以用于字段或setter方法。
- @Autowired默认按类型装配,默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false。
- @Resource默认按名称装配,当找不到与名称匹配的bean时才按照类型进行装配。名称可以通过name属性指定,如果没有指定name属性,当注解写在字段上时,默认取字段名,当注解写在setter方法上时,默认取属性名进行装配。