从设计模式理解Spring的原理之模板模式

博客主页:JavaProfessional
一个专注于Java的博主,致力于使用通俗易懂的语句讲解内容,期待您的关注!

前言

读完本篇你将获得:

  1. 理解Spring IOC的架构图
  2. 学习模板模式,并学习大神如何在源码中应用
  3. 从设计模式出发理解Spring的精妙之处,学习大神写代码的精髓,多学习多实践相信我们都可以写出如此精妙的代码。

第0步:学习模板模式

菜鸟教程

第1步:从IOC中获取第一个JavaBean

点击这里

第2步:从Main出发

本次讲解都是基于第一步实现的程序,不是很复杂,却是Spring的核心,学习之前请实践一下哦~

以下是主程序,也是我们的入口:

public class Main {
    public static void main(String[] args) {
        ApplicationContext springContext = new ClassPathXmlApplicationContext("spring-context.xml");
        User user = springContext.getBean("user", User.class);
        System.out.println(String.format("userName:%s", user.getUserName()));
    }
}

我们发现我们最核心的功能getBean方法是来自一个叫ClassPathXmlApplicationContext的类。现在我们不深入源码,如果让你实现这个getBean你会怎样实现?

public class ClassPathXmlApplicationContext{
    private HashMap<String, Object> beanMap;

    ClassPathXmlApplicationContext(String configFileName) {
        // 读取配置文件,略
        // 解析配置文件,略
        // 循环读取配置文件的bean配置,反射生成对象,放入map中,等待getBean调用
        beanMap.put("beanName"BeanClass.newInstance());
    }

    public Object getBean(String beanName) {
        return beanMap.get(beanName);
    }
}

这样做非常好,我们的第一个版本满足了IOC的最基本要求。能做到这里其实已经实现了Spring的核心,对吗?其余的都是添砖加瓦让这个框架更好。
那我们打开ClassPathXmlApplicationContext看一下他是如何实现的吧!(使用idea的diagram功能)
在这里插入图片描述

好家伙,怎么这么复杂,想必大家都有恐惧之心。但其实我们抓住核心,ClassPathXmlApplicationContext类里面没有getBean方法,那么getBean到底是从哪里来的?我们一路往上追溯,发现ClassPathXmlApplicationContext继承了太多的类,但是有一个类有getBean方法,于是ClassPathXmlApplicationContext也就有了getBean方法,这个类就是BeanFactory
在这里插入图片描述

我们精简一下,看一下大神是怎样写代码的:

public interface BeanFactory {
    Object getBean(String beanName);
}

public abstract class AbstractApplicationContext implements BeanFactory{
    public abstract BeanFactory getBeanFactory();
    
    public getBean(String beanName) {
        return this.getBeanFactory().getBean(beanName);
    }
    public void refresh(){
    	// 略,之后详细解释
    }
}
public ClassPathXmlApplicationContext extends AbstractApplicationContext {
    Resource[] resources;
    ClassPathXmlApplicationContext() {
			refresh();
	}
}

我们在重新温习一下我们的代码:

ClassPathXmlApplicationContext(String configFileName) {
    // 读取配置文件,略
    // 解析配置文件,略
    // 循环读取配置文件的bean配置,反射生成对象,放入map中,等待getBean调用
    beanMap.put("beanName"BeanClass.newInstance());
}

我们发现大神没有将读取配置文件解析配置文件等过程像我们一样写在ClassPathXmlApplicationContext类中。而是调用了其父类AbstractApplicationContextrefresh方法。
大神为什么要这么写呢?是故意让其显得复杂吗?其实并不是,这其中使用了一种设计模式——模板模式,为了解决重复代码问题。其中这个AbstractApplicationContext就是模板。

试想我们不仅要从ClassPath路径下读取XML文件的形式读取配置文件,我们还需要从文件系统中,从注解中读取等等方式,有一部分的代码其实是重复的,我们就都实现一遍吗?这时候就可以使用模板模式,用于减少重复代码。

第3步:查看模板方法

接下我我们一起查看一下核心的模板方法,我们将每个步骤都进行翻译,要注意看注释哦~:

@Override
public void refresh() throws BeansException, IllegalStateException {
  // 加锁,创建和销毁close方法互斥
	synchronized (this.startupShutdownMonitor) {
		// 为容器刷新做准备
		prepareRefresh();

		// 解析Spring的配置文件,加载bean到新的BeanFactory中
		// 从而获得一个Map<String, BeanDefinition>)
		//(其实IOC容器中有四个Map,这部分内容我们在专题中详谈,点击上方专题查看)
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		// 为能够使用beanFactory容器做准备。主要的工作有:
		// 1. 添加BeanPostProcessor
		// 2. registerResolvableDependency注册依赖关系
		// 3. registerSingleton注册其他的单例bean(environment, systemProperties, systemEnvironment)
		prepareBeanFactory(beanFactory);

		try {
			// 允许子类对BeanFacotry进行处理,当前为空,子类可以根据需要重写。
			postProcessBeanFactory(beanFactory);

			// 注册并调用所有的BeanFatory的后置处理器(注意不是BeanPostProcessor,而是BeanFactoryPostProcessor)
			invokeBeanFactoryPostProcessors(beanFactory);

			// 注册所有的后置处理器
			// 在所有的bean实例化时,前会调用所有BeanPostProcessor的postProcessBeforeInitialization()
			// 后会调用调用所有BeanPostProcessor的postProcessAfterInitialization()
			registerBeanPostProcessors(beanFactory);

			// 初始化消息资源
			initMessageSource();

			// 初始化应用的事件广播器
			initApplicationEventMulticaster();

			// 模板方法,提供给子类扩展实现,用于添加特定于上下文的刷新工作
			onRefresh();

			// 注册监听器
			registerListeners();

			// 核心源码:实例化所有非懒加载的单例bean,包括内部的bean、实现了BeanFactoryPostProcessor接口的bean,实现了BeanPostProcessor接口的bean
			// 触发前置处理器和后置处理器
			finishBeanFactoryInitialization(beanFactory);

			// 收尾工作,推送上下文刷新完毕事件到监听器
			finishRefresh();
		}

		catch (BeansException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception encountered during context initialization - " +
						"cancelling refresh attempt: " + ex);
			}

			// Destroy already created singletons to avoid dangling resources.
			destroyBeans();

			// Reset 'active' flag.
			cancelRefresh(ex);

			// Propagate exception to caller.
			throw ex;
		}

		finally {
			// Reset common introspection caches in Spring's core, since we
			// might not ever need metadata for singleton beans anymore...
			resetCommonCaches();
		}
	}

我们看到模板方法有许多可供子类扩展的点,可大大减少子类的重复代码。

总结

我们通过自己实现IOC容器,对比官方大神实现的IOC容器,发现大神引入了模板方法,巧妙地实现的易扩展,又能减少重复代码,同时保证了程序按照既定的顺序执行。所有实现了AbstractApplicationContext抽象类的类,都可以自定义自己的refresh方法了。最后,我们查看一下哪些类实现了模板方法吧
在这里插入图片描述这些都是不同实现的ApplicationContext,有从XML中读取配置的,有从注解中读取配置的,有从文件系统中读取配置的,这些是ApplicationContext的具体实现。

好文推荐

  1. 通俗易懂JVM
  2. 为什么Spring中的IOC(控制反转)能够降低耦合性(解耦)?
  3. 效率翻倍,这些idea的逆天功能你知道吗?
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tinpo_123

感谢给小张填杯java~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值