前言
上篇《照虎画猫写自己的Spring》从无到有讲述并实现了下面几点
- 声明配置文件,用于声明需要加载使用的类
- 加载配置文件,读取配置文件
- 解析配置文件,需要将配置文件中声明的标签转换为Fairy能够识别的类
- 初始化类,提供配置文件中声明的类的实例
一句话概括:不借助Spring容器,实现了Bean的加载和实例化
要想契合Fairy取名时的初衷(东西不大,但是能量无穷
),只有一套加载Bean的机制是远远不够的,所以还是需要照虎画猫,完善这个小精灵。
Spring之所以在Java企业级开发的众多框架中崭露头角光芒万丈,与他的依赖注入(又名控制反转IOC)和面向切面(AOP)两大杀手锏是密不可分的。在Fairy实现了加载实例化Bean的功能后,我们再往前走一步,看看依赖注入是如何实现的。
依赖注入
举个例子,大概介绍下依赖注入。
没有依赖注入之前,我们买白菜的时候,需要挎着篮子去菜市场挑选并购买; 有了依赖注入之后,我们需要白菜的时候,菜装在篮子里,已经放在你家门口。 这就是依赖注入。对于Fairy,如果要实现依赖注入的功能,需要在上一版的代码上做一些小小的改动。
将原来的FairyBean接口和实现类FairyBeanImpl改为FairyDao接口和实现类FairyDaoImpl,除此以外,我们需要新加一个接口FairyService和实现类FairyServiceImpl。 这么声明,相信你一定明白这是为了使用依赖注入功能。配置
我们依旧采用读取配置文件的方式来初始化容器。新建一个配置文件application-context-inject.xml
同时我们需要FairyService和FairyServiceImpl
FairyServicepackage com.jackie.fairy.bean;/** * Created by jackie on 17/11/25. */public interface FairyService { void greet(); void fly(); void lighting();}
FairyServiceImpl
package com.jackie.fairy.bean.impl;import com.jackie.fairy.bean.FairyDao;import com.jackie.fairy.bean.FairyService;/** * Created by jackie on 17/11/25. */public class FairyServiceImpl implements FairyService { private FairyDao fairyDao; private String lightColor; public FairyDao getFairyDao() { System.out.println("===getFairyDao===: " + fairyDao.toString()); return fairyDao; } public void setFairyDao(FairyDao fairyDao) { System.out.println("===setFairyDao===: " + fairyDao.toString()); this.fairyDao = fairyDao; } public String getLightColor() { return lightColor; } public void setLightColor(String lightColor) { this.lightColor = lightColor; } @Override public void greet() { fairyDao.greet(); } @Override public void fly() { fairyDao.fly(); } @Override public void lighting() { System.out.println("----------Hi, I am light fairy. Exactly, " + lightColor + " color light fairy----------"); }}
- 没有使用@Autowired注入FairyDao,这是Spring的那一套
- 将FairyDao作为成员变量,添加setter和getter方法(后续做注入使用)
- 添加FairyService自己的实现方法lighting,这是一个会发光的小精灵的feature,小精灵的发光属性取决于lightColor,这个属性需要注入,所以也有相应的setter和getter方法
升级解析器类
上篇的XmlReaderUtil解析器只能解析这样的配置结构
...
但是我们现在需要支持的配置文件如上面的配置文件所示,所以需要升级解析器类,支持读取子标签的属性标签。
在此之前,需要新建模型PropertyDefinition,用于存储属性值package com.jackie.fairy.model;/** * Created by jackie on 17/11/25. */public class PropertyDefinition { private String name; private String ref; private String value; public PropertyDefinition(String name, String ref, String value) { this.name = name; this.ref = ref; this.value = value; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getRef() { return ref; } public void setRef(String ref) { this.ref = ref; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } @Override public String toString() { return "PropertyDefinition{" + "name='" + name + '\'' + ", ref='" + ref + '\'' + ", value='" + value + '\'' + '}'; }}
同时,需要在BeanDefinition模型中加入List,因为属性值是依附在BeanDefinition下面的。
XmlReaderUtil将核心代码改为
for (Iterator iterator = rootElement.elementIterator(); iterator.hasNext(); ) { Element element = (Element)iterator.next(); String id = element.attributeValue(Constants.BEAN_ID_NAME); String clazz = element.attributeValue(Constants.BEAN_CLASS_NAME); BeanDefinition beanDefinition = new BeanDefinition(id, clazz); // 遍历属性标签 for (Iterator propertyIterator = element.elementIterator(); propertyIterator.hasNext();) { Element propertyElement = (Element) propertyIterator.next(); String name = propertyElement.attributeValue(Constants.PROPERTY_NAME_NAME); String ref = propertyElement.attributeValue(Constants.PROPERTY_REF_NAME); String value = propertyElement.attributeValue(Constants.PROPERTY_VALUE_NAME); propertyDefinitions.add(new PropertyDefinition(name, ref, value)); } beanDefinition.setPropertyDefinitions(propertyDefinitions); beanDefinitions.add(beanDefinition); // 清空propertyDefinitions集合,因为有些bean没有property标签 propertyDefinitions = Lists.newArrayList(); }
即添加了对于属性标签的解析和存储,详细代码可进入GitHub项目查看。
实现依赖注入函数
在FairyApplicationContext中添加实现依赖注入功能的函数,主要思路就是对某个需要依赖注入的主体(这里的FairyService),找到要依赖注入的类(这里的FairyDao),借助反射机制,通过setter方法将FairyDao注入到FairyService中。
injectObject()
private void injectObject() { for (BeanDefinition beanDefinition : beanDefinitions) { Object bean = instanceBeans.get(beanDefinition.getId()); if (bean != null) { try { BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass()); /** * 通过BeanInfo来获取属性的描述器(PropertyDescriptor) * 通过这个属性描述器就可以获取某个属性对应的getter/setter方法 * 然后我们就可以通过反射机制来调用这些方法。 */ PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); for (PropertyDefinition propertyDefinition : beanDefinition.getPropertyDefinitions()) { for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { // 用户定义的bean属性与java内省后的bean属性名称相同时 if (StringUtils.equals(propertyDescriptor.getName(), propertyDefinition.getName())) { // 获取setter方法 Method setter = propertyDescriptor.getWriteMethod(); if (setter != null) { Object value = null; if (StringUtils.isNotEmpty(propertyDefinition.getRef())) { // 根据bean的名称在instanceBeans中获取指定的对象值 value = instanceBeans.get(propertyDefinition.getRef()); } else { value = ConvertUtils.convert(propertyDefinition.getValue(), propertyDescriptor.getPropertyType()); } // //保证setter方法可以访问私有 setter.setAccessible(true); try { // 把引用对象注入到属性 setter.invoke(bean, value); } catch (Exception e) { LOG.error("invoke setter.invoke failed", e); } } break; } } } } catch (Exception e) { LOG.error("invoke getBean failed", e); } } }}
- 用到了Java内省获取Bean各个属性的setter和getter方法
- 使用了反射调用setter方法,将其注入FairyService类中
测试
编写测试代码
/** * bean依赖注入 */FairyApplicationContext autowiredApplicationContext = new FairyApplicationContext("application-context-inject.xml");FairyService fairyService = (FairyService) autowiredApplicationContext.getBean("fairyService");fairyService.greet();fairyService.lighting();
得到结果
===setFairyDao===: com.jackie.fairy.bean.impl.FairyDaoImpl@6615435cHi, I am fairy----------Hi, I am light fairy. Exactly, blue color light fairy----------
其中第一行打印结果是在通过反射执行setter.invoke(bean, value);
时触发打印的。
至此,我们为Fairy实现了依赖注入的功能,项目地址
https://github.com/DMinerJackie/fairy项目结构
Fairy项目改动盘点
- 添加
FairyApplicationContext(String configLocation)
构造函数,默认加载的配置文件是xml格式 - 添加Json配置文件解析器,可以解析Json格式的配置文件并加载bean
- 重构测试Bean,将接口FairyBean改为FairyDao,并新增FairyService接口及实现类,方便本文的用例测试
- 升级XmlReaderUtil,支持Bean的自标签Property的解析
- 添加依赖注入函数,用户实现依赖注入功能
- 添加PropertyDefinition模型,用于存储property属性值
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。