控制反转是一个比较抽象的概念,举例说明。在实际生活中,人们要用到一样东西时,人们的基本想法是找到东西,比如想喝杯橙汁,在没有饮品店的日子里,最直观的做法是,要买果汁机、橙子,准备开水。请注意这是你自己“主动”创造的过程,也就是一杯橙汁需要主动创造。然而到了今时今日,由于饮品店的盛行,已经没有必要自己去榨橙汁了。想喝橙汁的想法一出现,第一个想法是找到饮品店的联系方式,通过电话、微信等渠道描述你的需要、地址、联系方式等,下订单等待,过会就会有人送上橙汁了。请注意你并没有“主动”创造橙汁,也就是橙汁是由饮品店创造的,而不是你,但是也完全达到了你的要求。
控制反转是一种通过描述(在Java中可以是XML或者注解)并通过第三方去产生或获取特定对象的方式。
在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。正如上述的例子,果汁制造器依赖于饮品店和订单去制造果汁的,而饮品店是别人去创造的,我们只需要知道它能生产果汁就可以了,并不需要去理解如何创建果汁。
Spring IoC容器的设计主要是基于BeanFactory和ApplicationContext两个接口,其中ApplicationContext是BeanFactory的子接口之一,换句话说BeanFactory是Spring IoC容器所定义的最底层接口,而ApplicationContext是其高级接口之一,并且对BeanFactory功能做了许多有用的扩展,所以在绝大部分的工作场景下,都会使用ApplicationContext作为Spring IoC容器
思路:
注意: 在模拟过程中, 只考虑标准情况; 在Spring实例化对象维护依赖时, 如果依赖的对象还没有注册到容器中, 那么Spring会先去注册依赖的对象, 并加入缓存, 在按顺序注册对象时也会首先检查缓存中是否已经有该对象, 在spring中对循环依赖注入的解决方法比较复杂, 在实例化对象时, 在未完成实例化时就会提前将对象暴露在缓存中; 这里稍微提一下, 感兴趣的可以去阅读源码
package com.lic.dao;
public interface UserDao {
public void query();
}
package com.lic.dao;
public class UserDaoImpl implements UserDao{
@Override
public void query() {
System.out.println("模拟查询");
}
}
package com.lic.service;
public interface UserService {
public void query();
}
package com.lic.service;
import com.lic.dao.UserDao;
public class UserServiceImpl implements UserService {
private UserDao userDao;
private UserDao userDao1;
//构造方法
/*public UserServiceImpl(UserDao userDao,UserDao userDao1) {
this.userDao = userDao;
this.userDao1 = userDao1;
}*/
public void query() {
userDao.query();
userDao1.query();
}
}
package com.lic.spring;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*;
public class BeanFactory {
// ioc容器, 用来存储注册的bean对象
Map map =new HashMap<>();
// 构造方法
public BeanFactory(String path){
parseXml(path);
}
//解析配置文件并注册相关对象
private void parseXml(String path){
File file = new File(this.getClass().getResource("/").getPath()+"//"+path);
//需要导入dom4j jar包
SAXReader reader = new SAXReader();
try{
//使用dom4j解析配置文件
Document document = reader.read(file);
//获取根节点
Element elementRoot = document.getRootElement();
//自动注入标签,默认为false
boolean autowireFlag = false;
String autowire = elementRoot.attribute("default-autowire").getValue();
//如果设置了自动注入,则在后面将会视情况进行注入(属性注入, 构造方法注入优先级高于自动注入)
if (autowire.trim().length() > 1) {
autowireFlag = true;
}
//获取根节点的子节点
for (Iterator beanFir = elementRoot.elementIterator(); beanFir.hasNext();) {
/**
* setup1、实例化对象, 获取实例化对象的类信息
*/
Element elementChild = beanFir.next();
//获取当前bean对象name属性值
String attrName = elementChild.attribute("name").getValue();
//获取当前bean对象的class属性值
String attrClass = elementChild.attribute("class").getValue();
Class clazz = Class.forName(attrClass);
/**
* setup2、维护依赖关系
* 看这个对象有没有依赖(判断是否有property子标签。或者判断类是否有属性)
* 如果有则注入
*/
Object object = null;
//存储构造方法的参数,实例化对象时需要参数
List conObj = new ArrayList
package com.lic.spring;
public class CannotParseExecption extends RuntimeException{
public CannotParseExecption(String msg) {
super(msg);
}
}
package com.lic.spring;
public class TooManyExpectClassrExecption extends RuntimeException{
public TooManyExpectClassrExecption(String msg) {
super(msg);
}
}
package com.lic.test;
import com.lic.service.UserService;
import com.lic.spring.BeanFactory;
public class SpringTest {
public static void main(String[] args) {
BeanFactory beanFactory = new BeanFactory("spring.xml");
UserService userService = (UserService) beanFactory.getBean("userService");
userService.query();
}
}
分析:
通过dom4j对配置文件进行解析, 将解析后的对象信息, 利用反射生成对象注册到map容器中, 并维护依赖, 对依赖的维护主要有三种:属性注入, 构造方法注入, 自动注入; 在生命自动注入后, 如果在自动注入之前使用了其他两种类型注入, 则自动注入不再使用;
下一篇:
Spring源码_模拟基于注解方式的SpringIOC(模拟ByType自动注入,ByName自动注入)