可能现在大部分人写项目都接触不到xml这种方式了。毕竟有点out了。。但是作为学习 还是有值得进一步挖掘的意义。
本文只模拟 属性注入的两种方式 1.set注入 2.构造器注入 如何被spring管理到容器中。再通过getBean()的方式去获取对象。而不是耦合度太高的 new xxx()。后续的一些比如包扫描 @Autowired等DI注入。以后在慢慢弥补。或许也会开新的篇章。单独模拟内部源码。让大家更好的理解 spring底层。
先大致讲一下 整个思路吧。因为后面 我不知道该怎么介绍代码细节。好像除了贴图也只能贴图。或者让你们看代码自行理解细节。因为我代码里 注释写的还算详细。就不重复啰嗦。
github 下载源码路径 https://github.com/hzprivate/frame-topic 点到 mySpring项目即可。或者也可看贴图代码。下面都会展示。
代码思路:
1.困难点:
①如何读取 spring.xml文件 筛选有效数据?
通过 dom4j 去读取xml,什么?dom4j没用过不会? 好吧 其实我也不会。看dom4j官方文档 https://dom4j.github.io/ 一目了然 简单一批。没必要百度别人的例子。要学会看官方文档。然后后面猜一猜也知道怎么写了。
//获取xml中的<bean>标签 id属性 value值
String id = firstElement.attribute("id").getValue();
//获取xml中<bean>标签 class属性 全限定路径名称
String className = firstElement.attribute("class").getValue();
通过这两种就可以获取 你们xml中定义的bean标签 中的 id属性 和 class属性的值。
②如何反射生成对象然后放入容器?
spring容器就是个大map
<bean>标签中定义的class属性值通过 Element对象 的attribute("class")方法 获取, 值为该类的全限定路径名称。然后利用全限定路径名称 生成 class对象。然后class.newInstance() 实例化即可.通过键值对map.put("属性名","反射生成的对象")例如 map.put("userdao",new UserDao()) 放入spring map容器中。 通过 代码里 getBean("userDao")从 map中 获取 对象即可。到此已经实现了 如何将对象放入容器去管理。
//通过全限定路径名称反射生成class对象
Class<?> clazz = Class.forName(className);
③如何实现对象之间依赖的注入 。交给spring管理时 例如UserService中依赖了UserDao.如何对象关联调用时不报UserDao空指针?
首先要双重判断 1.类中是否含有属性。2.xml是否含有 property或 constructor-arg标签 。有的话才能证明 有对象依赖注入(构造器注入或set注入)
1.set注入时 通过 class对象获取所有类属性名称 即clazz.getDeclaredFields()。判断Field的属性名是否和xml中 <property>定义的 name值是否相同。相同则意味着 确实可以通过 <property> 关联到set去注入 因为类中也有定义属性。然后在通过<property> 中定义的ref属性 获取spring容器 map对象中已经反射生成的依赖对象。 最后通过 下面两行关键代码。就可实现 属性的set注入
declaredField.setAccessible(true);
declaredField.set(o,ref);
for (Field declaredField : declaredFields) {
String name = secondElementProperty.attribute("name").getValue();
if(name.equals(declaredField.getName())){
//属性名和xml中name定义名称相同
String refClazzName = secondElementProperty.attribute("ref").getValue();
Object ref = reslutMap.get(refClazzName);
if(ref != null){
//map中含有 该ref依赖对象名称
//set方法注入
declaredField.setAccessible(true);
declaredField.set(o,ref);
}
}
}
2.构造器注入时 同样获取<constructor-arg>标签定义的 name属性值和Field的属性名是否相同。相同则意味着 确实可以通过 <constructor-arg>关联到构造器去注入。然后在通过<constructor-arg>中定义的ref属性值 获取spring容器 map对象中已经存放的依赖对象。 最后通过class对象获取指定构造器,利用构造器 newInstance() 反射生成对象。然后放入map即可。
此处要区别 set注入 和 构造器注入时 反射生成对象 。 set可以直接通过 class.newInstance()反射生成对象 。此处利用默认构造器。而一旦利用 构造器注入就不用此种方式去反射生成对象。要先获取有参构造器、利用有参构造器反射生成对象。不然会报错。
//constructor 方法注入
//因为代码里只有一个构造器 所以[0] 获取第一个
Constructor<?> declaredConstructor = clazz.getDeclaredConstructors()[0];
o = declaredConstructor.newInstance(ref);
代码思路到此处讲解完。具体的可直接看代码。不想看代码可看下面贴图。
1.构建好maven项目 如下结构
2.pom文件中添加maven依赖 (为了通过idea生成spring.xml文件 )
<!-- 毫无作用 只是为了 通过idea可以生成 spring.xml文件-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.1.RELEASE</version>
</dependency>
<!--解析xml 工具-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
3.在resources文件夹下生成 spring.xml文件
4.新建如下四个接口以及实现类(后续测试使用)
5.spring.xml 配置对象
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="com.hz.dao.UserDaoImpl"></bean>
<bean id="userService" class="com.hz.service.UserServiceImpl">
<!--property setter注入 name 是传入的userDao 变量名称 ref是 属性方法setUserDao名称-->
<!--<property name="userDao" ref="userDao"></property>-->
<!--constructor-arg 构造器注入-->
<constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
</beans>
6.核心代码 来了 BeanFactory文件
package com.hz;
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.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* @author hz
* @create 2020-09-21
*/
public class BeanFactory {
//解析xml
public Map<String,Object> parseXml(String xml){
SAXReader reader = new SAXReader();
try {
//定义spring map容器(包含所有bean对象)
Map<String,Object> reslutMap = new HashMap(16);
//获取项目工程中 spring.xml文件路径
String springXmlUrl = this.getClass().getResource(xml).getFile();
File file = new File(springXmlUrl);
//通过file 形式读取xml文件 生成Document对象
Document document = reader.read(file);
//获取<beans>标签对象
Element rootElement = document.getRootElement();
for(Iterator<Element> iterator = rootElement.elementIterator();iterator.hasNext();){
Object o = null;
//获取 <bean> 一级标签对象
Element firstElement = iterator.next();
//获取<bean> 标签中的 id属性值 和 class属性值
String id = firstElement.attribute("id").getValue();
//获取全限定路径名称
String className = firstElement.attribute("class").getValue();
//通过全限定路径名称反射生成class对象
Class<?> clazz = Class.forName(className);
//对象依赖注入 思路判断
//1.类中是否含有属性。2.xml是否含有 property或 constructor-arg标签
Field[] declaredFields = clazz.getDeclaredFields();
if(declaredFields != null && declaredFields.length>0){
//获取二级标签 <property>对象
Element secondElementProperty = firstElement.element("property");
// setter注入 判断
if(secondElementProperty != null){
o = clazz.newInstance();
for (Field declaredField : declaredFields) {
String name = secondElementProperty.attribute("name").getValue();
if(name.equals(declaredField.getName())){
//属性名和xml中name定义名称相同
String refClazzName = secondElementProperty.attribute("ref").getValue();
Object ref = reslutMap.get(refClazzName);
if(ref != null){
//map中含有 该ref依赖对象名称
//set方法注入
declaredField.setAccessible(true);
declaredField.set(o,ref);
}
}
}
}
//获取二级标签 <constructor-arg>对象
Element secondElementConst = firstElement.element("constructor-arg");
//构造器注入判断
if(secondElementConst != null){
for (Field declaredField : declaredFields) {
String name = secondElementConst.attribute("name").getValue();
if(name.equals(declaredField.getName())){
//属性名和xml中name定义名称相同
String refClazzName = secondElementConst.attribute("ref").getValue();
Object ref = reslutMap.get(refClazzName);
if(ref != null){
//map中含有 该ref依赖对象
//constructor 方法注入
//因为代码里只有一个构造器 所以[0] 获取第一个
Constructor<?> declaredConstructor = clazz.getDeclaredConstructors()[0];
o = declaredConstructor.newInstance(ref);
}
}
}
}
}else{
//无依赖注入 直接反射生成对象
o = clazz.newInstance();
}
reslutMap.put(id,o);
}
return reslutMap;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//获取bean对象
public Object getBean(String name){
Map<String, Object> map = parseXml("/spring.xml");
Object o = map.get(name);
return o;
}
}
7.test测试
package com.hz.test;
import com.hz.BeanFactory;
import com.hz.dao.UserDao;
import com.hz.service.UserService;
import com.hz.service.UserServiceImpl;
/**
* @author hz
* @create 2020-09-21
*/
public class test {
public static void main(String[] args) {
BeanFactory beanFactory = new BeanFactory();
//垃圾版 试验 人为set方法注入 (只实现了无依赖的bean对象生成)
// UserService userService = new UserServiceImpl();
// UserDao userDao =(UserDao) beanFactory.getBean("userDao");
// ((UserServiceImpl) userService).setUserDao(userDao);
// userService.query();
//进阶版 试验 xml set注入 或 构造器注入
UserService userService =(UserService) beanFactory.getBean("userService");
userService.query();
}
}
8.测试结果