DI
什么是DI
DI: Dependency Injection, 依赖注入
DI和IOC的关系
IOC是一种设计思想,用于降低程序见的耦合度。DI是IOC的一种实现,IOC的另一常见的实现方式是DL(Dependency Lookup,依赖查找)。
Spring使用的是DI。依赖
主要类之间的依赖, 例如下面代码,我们可以认为UserDao是UserServiceImpl的一个依赖:public class UserServiceImpl implements UserService { private UserDao userDao;
注入
不需要从容器获取,Spring已经帮我们处理好所有依赖对象了。
例如,订购报纸或者牛奶,我们只需要告诉订购商需要什么,然后每天早上家门口就会放好报纸或者牛奶。
与“注入”相对应的是“查找”,每天早上需要自己去附近的订购点取报纸或者牛奶。
手写DI
Spring的核心是IOC容器,请使用DI技术自己实现一套IOC容器框架,以加深对Java和Spring的理解。
技术点:
- Java反射
- XML配置文件解析
实现方式:
- 解析XML文件中所有bean,以及bean属性;
- 利用反射技术,给解析的bean创建对象,并设置相关属性;
- 将创建的对象存放到map中,这个map就是一个“IOC容器”;
- 容器对象通过
getBean(beanId)
的方式获取,该方法实际从map中获取对象;
构造函数注入
通过构造函数来进行依赖注入
修改XML文件
xml配置示例:
注意:
配置文件中
表示构造函数的参数名称和取值。
IOC容器编码
示例代码:
public class BeanFactory {
private static Map beanMap = new HashMap<>();
public static void refresh(String xmlPath) throws Exception {
// 创建SAXReader
SAXReader saxReader = new SAXReader();
// 获取xml配置文件
URL xmlUrl = BeanFactory.class.getClassLoader().getResource(xmlPath);
// 将SAXReader和xml配置文件绑定
Document doc = saxReader.read(xmlUrl);
// 获取根元素“beans”
Element root = doc.getRootElement();
// 遍历bean节点
List beanElementList = root.elements();
for (Element beanElement: beanElementList) {
parseBeanElement(beanElement);
}
}
private static void parseBeanElement(Element beanElement) throws Exception {
String id = beanElement.attributeValue("id");
String clsName = beanElement.attributeValue("class");
// 获取Class对象
Class> cls = Class.forName(clsName);
// 获取构造参数节点
List subElemList = beanElement.elements();
// 如果没有构造参数,则直接调用无参构造函数
if (subElemList.size() == 0) {
// 获取无参构造函数
Constructor> constructor = cls.getDeclaredConstructor();
// 通过无参构造函数,实例化一个对象
Object beanObj = constructor.newInstance();
// 将bean对象存放到容器中
beanMap.put(id, beanObj);
return;
}
Class>[] parameterTypes = new Class[subElemList.size()];
Object[] argsObject = new Object[subElemList.size()];
int i = 0;
for (Element subElem: subElemList) {
String ref = subElem.attributeValue("ref");
argsObject[i] = beanMap.get(ref);
// 获取参数对象的接口,而不是类,因为构造函数中参数类型是接口
parameterTypes[i] = argsObject[i].getClass().getInterfaces()[0];
i++;
}
// 根据构造参数获取构造函数
Constructor> constructor = cls.getDeclaredConstructor(parameterTypes);
// 实例化对象,传入构造参数
Object beanObj = constructor.newInstance(argsObject);
beanMap.put(id, beanObj);
}
public static Object getBean(String beanId) {
return beanMap.get(beanId);
}
}
测试:
public static void main(String[] args) throws Exception {
// 通过xml文件,创建IOC容器
BeanFactory.refresh("com/bailiban/day1/mydi/constructordemo/DI-constructor.xml");
// 使用getBean方法,从自建的IOC容器中获取对象
Client client = (Client) BeanFactory.getBean("client");
// 我们并没有给Client对象设置userService属性,userService的创建是由IOC容器帮我们完成的
client.userService.add(new User(1001, "Jim"));
}
用户添加成功:User{id=1001, name='Jim'}
修改业务类
添加含参数的构造函数
public class UserServiceImpl implements UserService {
private UserDao userDao;
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
// 省略其他代码
}
public class Client {
private UserService userService;
public Client(UserService userService) {
this.userService = userService;
}
// 省略其他代码
}
setter方法注入
修改XML文件
xml配置示例:
注意:
配置文件中
表示属性名称和取值。
IOC容器编码
示例代码:
public class BeanFactory {
// 省略其他代码
private static void parseBeanElement(Element beanElement) throws Exception {
String id = beanElement.attributeValue("id");
String clsName = beanElement.attributeValue("class");
// 获取Class对象
Class> cls = Class.forName(clsName);
// 直接调用无参构造函数,实例化一个对象
Object beanObj = cls.getDeclaredConstructor().newInstance();
beanMap.put(id, beanObj);
// 获取属性节点,并调用setter方法设置属性
List subElemList = beanElement.elements();
for (Element subElem: subElemList) {
// 获取属性名称
String name = subElem.attributeValue("name");
// 获取属性值
String ref = subElem.attributeValue("ref");
Object refObj = beanMap.get(ref);
// 根据属性名称构造setter方法名: set + 属性首字母大写 + 属性其他字符,例:setUserDao
String methodName = "set" + (char)(name.charAt(0) - 32) + name.substring(1);
// 获取Method对象
Method method = cls.getDeclaredMethod(methodName, refObj.getClass().getInterfaces()[0]);
// 调用setter方法,设置对象属性
method.invoke(beanObj, refObj);
}
}
public static Object getBean(String beanId) {
return beanMap.get(beanId);
}
}
测试:
public static void main(String[] args) throws Exception {
// 通过xml文件,创建IOC容器
BeanFactory.refresh("com/bailiban/day1/mydi/setterdemo/DI-setter.xml");
// 使用getBean方法,从自建的IOC容器中获取对象
Client client = (Client) BeanFactory.getBean("client");
// 我们并没有给Client对象设置userService属性,userService的创建是由IOC容器帮我们完成的
client.userService.add(new User(1001, "Jim"));
}
用户添加成功:User{id=1001, name='Jim'}
field注入
修改XML文件
xml配置示例:
注意:
配置文件中
表示属性名称和取值。
IOC容器编码
示例代码:
public class BeanFactory {
// 省略其他代码
private static void parseBeanElement(Element beanElement) throws Exception {
String id = beanElement.attributeValue("id");
String clsName = beanElement.attributeValue("class");
// 获取Class对象
Class> cls = Class.forName(clsName);
// 直接调用无参构造函数,实例化一个对象
Object beanObj = cls.getDeclaredConstructor().newInstance();
beanMap.put(id, beanObj);
// 获取属性节点,并调用setter方法设置属性
List subElemList = beanElement.elements();
for (Element subElem: subElemList) {
// 获取属性名称
String name = subElem.attributeValue("name");
// 获取属性值
String ref = subElem.attributeValue("ref");
Object refObj = beanMap.get(ref);
// 根据属性名称获取Field对象
Field field = cls.getDeclaredField(name);
// 调用field的set方法,设置对象属性
field.setAccessible(true);
field.set(beanObj, refObj);
}
}
}
测试:
public static void main(String[] args) throws Exception {
// 通过xml文件,创建IOC容器
BeanFactory.refresh("com/bailiban/day1/mydi/fielddemo/DI-field.xml");
// 使用getBean方法,从自建的IOC容器中获取对象
Client client = (Client) BeanFactory.getBean("client");
// 我们并没有给Client对象设置userService属性,userService的创建是由IOC容器帮我们完成的
client.userService.add(new User(1001, "Jim"));
}
用户添加成功:User{id=1001, name='Jim'}