聊聊最近撸Spring源码感悟
Posted "大魔王先生
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了聊聊最近撸Spring源码感悟相关的知识,希望对你有一定的参考价值。
一、前言
最近一段时间撸了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的时候会调用FactoryBean
的getObject()
方法。所以Mybatis与Spring的集成的入口就是org.mybatis.spring.SqlSessionFactoryBean#getObject()
方法,是通过XMLConfigBuilder
、XMLMapperBuilder
和XMLStatementBuilder
三个建造者来完成了对Mybatis XML文件的解析。
现在已经将Spring与Mybatis整合到一起了,接下来可以看下如何将Mapper动态加载到Spring容器中,配置中MapperScannerConfigurer实现这一过程,接下来我们一起看一下该类的继承结构;
重点是BeanDefinitionRegistryPostProcessor,会执行BeanDefinitionRegistryPostProcessor
的postProcessBeanDefinitionRegistry
方法来完成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源码感悟的主要内容,如果未能解决你的问题,请参考以下文章
跟大家聊聊我们为什么要学习源码?学习源码对我们有用吗?(源码感悟)
《Spring 手撸专栏》| 开篇介绍,我要带新人撸 Spring 啦!
《Spring 手撸专栏》| 开篇介绍,我要带新人撸 Spring 啦!
初识Spring源码 -- doResolveDependency | findAutowireCandidates | @Order@Priority调用排序 | @Autowired注入(代码片段
初识Spring源码 -- doResolveDependency | findAutowireCandidates | @Order@Priority调用排序 | @Autowired注入(代码片段