一、前言

    最近一段时间撸了Spring IOC、AOP、Transactional源码,这篇文章聊聊我写了哪些小玩意,这可能就是阅读源码以后最大收获。希望大家在里面能学习一些什么东西吧;

二、Spring IOC简单实现

第一步首先看一下配置文件,配置文件模拟Spring Bean注入时候的样子,少了XML验证头的一些东西;

<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="student" class="org.handwriting.spring.entity.Student">
<property name="name" value="wtz" />
<property name="age" value="20" />
</bean>
</beans>

第二步是实体类;

public class Student {

    private String name;

    private String age;

    public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String getAge() {
return age;
} public void setAge(String age) {
this.age = age;
}
}

第三步也是最重要的一步,实现从配置中解析文件,然后通过反射创建Student对象,解析配置中Student的属性,赋值给Student对象完成初始化,最后加载到Map容器中;

public class SpringIoc {

    private HashMap<String, Object> beanMap = new HashMap<String, Object>();

    public SpringIoc(String location) throws SAXException, IllegalAccessException, IOException, InstantiationException, ParserConfigurationException, NoSuchFieldException {
loadBeans(location);
} /**
* 获取 bean
*
* @param name bean 名称
* @return bean
*/
public Object getBean(String name) {
Object bean = beanMap.get(name);
if (bean == null) {
throw new IllegalArgumentException(" bean is null"+name);
}
return bean;
} /**
* 加载配置和初始化 bean 属性
*
* @param location
* @throws IOException
* @throws ParserConfigurationException
* @throws SAXException
* @throws IllegalAccessException
* @throws InstantiationException
* @throws NoSuchFieldException
*/
private void loadBeans(String location) throws IOException, ParserConfigurationException, SAXException, IllegalAccessException, InstantiationException, NoSuchFieldException {
//加载 xml 配置文件
InputStream inputStream = new FileInputStream(location);
DocumentBuilderFactory documentBuilderFactory =
DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder =
documentBuilderFactory.newDocumentBuilder();
Document document = documentBuilder.parse(inputStream);
Element root = document.getDocumentElement();
NodeList nodeList = root.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node instanceof Element) {
Element element = (Element) node; String id = element.getAttribute("id");
String className = element.getAttribute("class");
//通过反射获取当前类
Class beanClass = null;
try {
beanClass = Class.forName(className);
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
Object bean = beanClass.newInstance();
//将属性值赋值给 bean 对象
NodeList propertyList = element.getElementsByTagName("property");
for (int j = 0; j < propertyList.getLength(); j++) {
Node propertyNode = propertyList.item(j);
Element propertyElement = (Element) propertyNode;
String name = propertyElement.getAttribute("name");
String value = propertyElement.getAttribute("value"); Field declaredField = bean.getClass().getDeclaredField(name);
declaredField.setAccessible(true); if (value != null && value.length() > 0) {
declaredField.set(bean, value);
}
}
beanMap.put(id, bean);
} } } }

第四部测试调用;

public class SpringIocTest {

    public static void main(String[] args) throws IllegalAccessException, ParserConfigurationException, IOException, InstantiationException, SAXException, NoSuchFieldException {
//通过 ClassLoader 加载文件信息
String location=
SpringIoc.class.getClassLoader().getResource("spring.xml").getFile();
//完成 bean 对象的初始化
SpringIoc ioc=new SpringIoc(location);
//获取 student 对象
Student student=(Student) ioc.getBean("student");
System.out.println(student);
}
}

这简简单单的几步,其实已经反应出Bean的创建流程,首先来看一下我们通过BeanFactory怎么来获取一个对象,这里先不看ApplicationContext这个家伙不是一个纯粹的人;

如上图的标注,第一步主要是初始化配置文件信息,生成Resource,该过程也就是我写的读取文件的流程;

第二步主要是初始化BeanFactory,DefaultListableBeanFactory该类BeaFactory默认实现,这个也是Spring常用的手段,凡是复杂实现总要抽象一个多功能抽象类和一个默认实现,这样做的好处就在于我们只需要重写抽象类的方法就能实现新的一些扩展功能;

  第三部分完成了Resource转化为Document,Document解析为BeanDefinition,最终加入到Map容器中过程,第二、三部就相当于我实现Spring IOC的loadBeans的方法前半部分,这个时候还不是真正可用的对象;

  第四部分是最重要的部分,该部分完成BeanDefinition到实体Bean的创建,针对这部分我把Bean整个生命周期用到的接口做了具体的实现;

总结一下,Spring IOC就是将配置加入容器中,然后从容器取出配置信息,利用反射或者CGLIB方式创建对应实例,然后填充属性信息,最终完成初始化;

三、Spring Bean生命周期

首先来看下继承了哪些接口和实现;

public class LifeCycleBean implements BeanNameAware, BeanClassLoaderAware,
BeanFactoryAware, InitializingBean, DisposableBean, BeanPostProcessor { private String test; public String getTest() {
return test;
} public void setTest(String test) {
System.out.println("属性注入");
this.test = test;
} public void method(){
System.out.println("方法被调用");
} public void setBeanClassLoader(ClassLoader classLoader) {
System.out.println("BeanClassLoaderAware 被调用");
} public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("BeanFactoryAware 被调用");
} public void setBeanName(String name) {
System.out.println("BeanNameAware 被调用");
} public void destroy() throws Exception {
System.out.println("DisposableBean 被调用");
} public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean 被调用");
} public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor 开始被调用");
return bean;
} public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor 结束被调用");
return bean;
} public void initMethod(){
System.out.println("init-method 被调用");
} public void destroyMethod(){
System.out.println("destroy-method 被调用");
}
}

接下来看下运行结果和怎么调用;

public class SpringIocTest {

    public static void main(String[] args) {
//加载配置文件
ClassPathResource classPathResource = new ClassPathResource("spring.xml");
//初始化 beanFactory
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
//读取配置文件信息
XmlBeanDefinitionReader definitionReader =
new XmlBeanDefinitionReader(defaultListableBeanFactory);
definitionReader.loadBeanDefinitions(classPathResource);
// beanFactory 必须手动调用 BeanPostProcessor 注入
defaultListableBeanFactory.addBeanPostProcessor(new LifeCycleBean());
//获取 bean
LifeCycleBean lifeCycleBean=
(LifeCycleBean)defaultListableBeanFactory.getBean(LifeCycleBean.class);
//执行方法
lifeCycleBean.method();
defaultListableBeanFactory.destroySingletons();
}
}

这就是整个Spring Bean的生命周期,了解整个生命周期再去看Spring IOC部分的源码相信大家会更好理解,接下来介绍下这些接口的作用;

BeanNameAware、BeanClassLoaderAware、BeanFactoryAware这个三个接口是在Bean创建完成以后执行,实现Aware类型的接口其实就是将Spring提供的能力按照Bean的形式暴露出去;

BeanPostProcessor是Bean在实例化前后提供的重要的扩展点,允许在Bean的实例化阶段对Bean进行修改,Spring经典的实现AOP、事务都是借助其扩展方法postProcessAfterInitialization实现;

InitializingBean、init-method在进行 Aware 接口和 BeanPostProcessor 前置处理之后,会接着检测当前 Bean 对象是否实现了 InitializingBean 接口,如果是,则会调用其 afterPropertiesSet方法,进一步调整 Bean 实例对象的状态,init-method指定的方法在afterPropertiesSet执行,其执行效果一样,但是在实现上init-method指定的方法采用反射实现,消除了继承接口的依赖;

DisposableBean、destory-method是在容器被注销的时候触发的,两者的不同请参考InitializingBean和init-method;

接下来我们就可以聊聊mybatis-spring插件机制,这样就能体现生命周期的价值,也可以体会Spring之美;

四、mybatis-spring插件

我们可以思考下如何实现这个功能,我们需要完成那些步骤:

1.解析Mapper文件;

2.将Mapper解析以后的对象转化为Spring的Bean;

3.将转化以后的Bean注入到Spring容器当中;

完成以上3步,我们就可以将Mapper委托Spring去管理,我们来看一下mybatis-spring是如何实现的,整个过程中我们又用到了哪些Spring Bean生命周期,首先先来把这个插件整合起来;

第一步先在Maven中添加依赖;

 

第二步在Spring配置中添加Mybatis的依赖;

 

接下来看下原理,从配置流程可以看到mybatis把SqlSessionFactory交给了Spring容器进行管理,看下SqlSessionFactoryBean的继承结构,

圈出了重点两个接口,FactoryBean是Spring Bean注入的一种方式,关键的重点在于InitializingBean,看到这个就想到生命周期,这个动作发生于Bean创建完成以后,我们看一下他做那些事;

在Spring实例化Bean的时候会调用FactoryBeangetObject()方法。所以Mybatis与Spring的集成的入口就是org.mybatis.spring.SqlSessionFactoryBean#getObject()方法,是通过XMLConfigBuilderXMLMapperBuilderXMLStatementBuilder三个建造者来完成了对Mybatis XML文件的解析。

现在已经将Spring与Mybatis整合到一起了,接下来可以看下如何将Mapper动态加载到Spring容器中,配置中MapperScannerConfigurer实现这一过程,接下来我们一起看一下该类的继承结构;

重点是BeanDefinitionRegistryPostProcessor,会执行BeanDefinitionRegistryPostProcessorpostProcessBeanDefinitionRegistry方法来完成Bean的装载;

我们可以看到通过包扫描,将会扫描出所有的Mapper类,然后注册Bean定义到Spring容器。Mapper是一个接口,不能直接实例化,在ClassPathMapperScanner中,它将所有Mapper对象的BeanDefinition给改了,将所有Mapper的接口对象指向MapperFactoryBean工厂Bean,所以在Spring中Mybatis所有的Mapper接口对应的类是MapperFactoryBean,源码如下:

至此我们完成Mapper注入到Spring容器中,至于Mybatis后面的东西暂不去讨论了。整个过程中出现的一个从未出现过的接口BeanDefinitionRegistryPostProcessor,接下来我们一起来探究下这个类;

五、BeanDefinitionRegistryPostProcessor

  这里通过一个例子看下这个接口作用;

public class BeanDefinitionRegistryPostProcessorDemo implements BeanDefinitionRegistryPostProcessor {

    private static final String beanName = "student";

    /**
* 加载 teacher 类
*
* @param registry
* @throws BeansException
*/
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { //设置 bean 相关属性
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(Student.class);
definition.setScope("singleton");
// bean 属性赋值
MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
mutablePropertyValues.add("name", "wtz");
mutablePropertyValues.add("age", 20);
definition.setPropertyValues(mutablePropertyValues);
//注入 bean
registry.registerBeanDefinition("student", definition); } /**
* 获取 Bean 对象并修改其属性
*
* @param beanFactory
* @throws BeansException
*/
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { BeanDefinition definition = beanFactory.getBeanDefinition("teacher");
MutablePropertyValues mutablePropertyValues = null;
mutablePropertyValues = definition.getPropertyValues();
if (definition != null && mutablePropertyValues != null) {
PropertyValue propertyValue=
mutablePropertyValues.getPropertyValue("name");
System.out.println("获取到的值"+propertyValue.getValue());
propertyValue.setConvertedValue("修改属性为go");
}
}
}

接下来看下实体和配置;

/**
* student
*
* @author wtz
*/
public class Student { private String name; private Integer age; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
} @Override
public String toString() {
return "student name " + name + " age " + age;
}
} /**
* teacher
*
* @author wtz
*/
public class Teacher { private String name; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public String toString() {
return "teacher name " + name;
}
} <bean id="teacher" class="org.handwriting.springdemo.Teacher">
<property name="name" value="www" />
</bean> <bean id="beanDefinitionRegistryPostProcessorDemo"
class="org.handwriting.springdemo.BeanDefinitionRegistryPostProcessorDemo" />

我们来看一下运行结果;

BeanDefinitionRegistryPostProcessor接口的方法postProcessBeanFactory是由BeanFactoryPostProcessor继承得来的,所以我们就对这两个接口进行总结一下;

BeanFactoryPostProcessor该接口可以获取BeanFactory对象,通过操作BeanFactory修改BeanDefinition中的属性,但不支持实例化该Bean;

BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的子类除了继承父类功能以外,我们可以获取到BeanDefinitionRegistry,通过该对象向容器中注入Bean;

这两个接口是实现自动扫描的关键,比如ConfigurationClassPostProcessor这里我就不展开讨论了,这里我们还需要知道是什么时候加载的BeanDefinitionRegistryPostProcessor,上面给大家提到一个不纯粹的人;

这个家伙有个refresh方法,该方法中有个invokeBeanFactoryPostProcessors方法,该方法就是自动扫描的关键,也就是这个时候执行的该接口的实现,源码展开说明,送大家一张图结束本文;

六、总结

  文章有些散乱,总体还是讲明白了三件事情:

1.Spring IOC到底是在做一件什么事情;

2.Spring Bean的生命周期都经历什么;

3.插件是怎么加入到Spring容器中的;

希望大家看过以后,能做到有需求写一个插件注入到Spring容器中的时候能不虚,很快将这个功能实现,另外还小小提了一下自动扫描,有兴趣自己去了解下;Spring加载过程中其实还有很多细节问题,这里都没有提及,希望大家努力去看源码,真的能学习到很多东西!

七、结束

  欢迎大家加群438836709!欢迎大家关注我!

  

聊聊最近撸Spring源码感悟的更多相关文章

  1. Spring源码分析——资源访问利器Resource之实现类分析

    今天来分析Spring的资源接口Resource的各个实现类.关于它的接口和抽象类,参见上一篇博文——Spring源码分析——资源访问利器Resource之接口和抽象类分析 一.文件系统资源 File ...

  2. spring源码学习之路---AOP初探(六)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 最近工作很忙,但当初打算学习 ...

  3. Spring源码解读之BeanFactoryPostProcessor的处理

    前言 前段时间旁听了某课堂两节Spring源码解析课,刚好最近自己又在重新学习中,便在这里记录一下学习所得.我之前写过一篇博文,是介绍BeanFactoryPostProcessor跟BeanPost ...

  4. Spring源码解析——循环依赖的解决方案

    一.前言 承接<Spring源码解析--创建bean>.<Spring源码解析--创建bean的实例>,我们今天接着聊聊,循环依赖的解决方案,即创建bean的ObjectFac ...

  5. java架构之路-(spring源码篇)springIOC容器源码解析(上)

    我们这次来叭叭一下Spring的源码,这次博客主要来说说Spring源码,先粗略的撸一遍,下篇博客选几个重点去说,由于过于复杂,我也是看了一点点,我们先来过一遍源码,然后上流程图,最后我们再回头总结一 ...

  6. Spring源码解析系列汇总

    相信我,你会收藏这篇文章的 本篇文章是这段时间撸出来的Spring源码解析系列文章的汇总,总共包含以下专题.喜欢的同学可以收藏起来以备不时之需 SpringIOC源码解析(上) 本篇文章搭建了IOC源 ...

  7. Ubuntu搭建Spring源码环境常见问题

    在一心想要学习Spring框架源码时,我们会遇到很多麻烦的问题.开始本文前,你只需要拥有一个装好IDEA的Ubuntu系统就可以愉快启程了.如果还没有IDEA,可以参考在Ubuntu上安装Intell ...

  8. Spring源码解析——核心类介绍

    前言: Spring用了这么久,虽然Spring的两大核心:IOC和AOP一直在用,但是始终没有搞懂Spring内部是怎么去实现的,于是决定撸一把Spring源码,前前后后也看了有两边,很多东西看了就 ...

  9. 从零开始学spring源码之xml解析(二):默认标签和自定义标签解析

    默认标签: 上一篇说到spring的默认标签和自定义标签,发现这里面东西还蛮多的.决定还是拆开来写.今天就来好好聊聊这两块是怎么玩的,首先我们先看看默认标签: private void parseDe ...

随机推荐

  1. windows7蓝屏0x000000c4

    故障还原: 360更新弹出更新提示,于是选择了关机自动更新,第二天开机发现电脑蓝屏报0x000000c4错误! 故障排查: 1.无法从最后一次正确配置启动windows7 2.无法进入安全模式 该错误 ...

  2. Flask_Migrate数据库迁移

    migrate数据库迁移 有models,没有迁移仓库.本地新建数据库:首次创建迁移仓库.迁移脚本:执行迁移脚本生成数据库表: python manage.py db init python mana ...

  3. SGU 107 987654321 problem【找规律】

    题目链接: http://acm.sgu.ru/problem.php?contest=0&problem=107 题意: 平方后几位为987654321的n位数有多少个 分析: 虽然说是水题 ...

  4. Streamy障碍一:大批量条目

  5. @atcoder - ARC066F@ Contest with Drinks Hard

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定序列 T1, T2, ... TN,你可以从中选择一些 Ti ...

  6. uni-app获取当前位置

    uniapp获取当前城市: 官方api:uni.getLocation()获取当前的地理位置.速度. 在微信小程序中,当用户离开应用后,此接口无法调用,除非申请后台持续定位权限:当用户点击“显示在聊天 ...

  7. phpstudy2016安装redis扩展

    第一步:查看phpstudy版本 第二步:查看phpinfo信息,看Architecture所属类型. 第三步:下载redis扩展 去http://windows.php.Net/downloads/ ...

  8. js后端返回一个时间戳,用原生怎么对时间进行格式化?

    function fn(time) { var date = new Date(time); var len = time.toString().length; // 时间戳不足13位则在后面加零 i ...

  9. Mac MAMP 使用终端shell操作mysql数据库

    在MAMP中已经集成了phpMyAdmin,可以很方便的管理mysql数据库,但是有的情况是phpMyAdmin不能做到的.比如,导入sql文件,当sql文件非常大(大于20MB)的时候,apache ...

  10. uni-app学习记录07-生命周期

    <template> <view class="content"> 我是首页 </view> </template> <scr ...