第一章:Spring引言
一:当前Spring的一个地位
Spring准确的来讲是一套解决方案,是一个技术栈,是一个全家桶
从起初的Spring,到因为要进行web开发引入了SpringMVC,到为了后来的快捷开发,引入了Springboot,在到后来的微服务,也就是SpringCloud。
Spring能有以上这么优秀的离不开Spring核心当中的两大特性,IOC和AOP,Spring能完成这么复杂的生态的构建离不开这两大伟大的特性。
SpringMVC本质上就是由Spring+MVC这两大部分组成,任何一个MVC框架都离不开MVC的内容和影子。
二:SpringBoot的真正的价值
SpringBoot不要把他妖魔化,SpringBoot真正的价值是:简化了Spring和SpringMVC的开发。
他本质上没有什么新的东西,他本质上就是对Spring和SpringMVC的简化:
1:方便导入Jar包
我们现在都是基于maven导入依赖的,maven我们都是自己引入依赖,但是boot觉得这样太麻烦了,他引入了starter,方便引入导入jar包
2:简化冗余配置
Spring从原始的xml到后续的注解,已经简化了,但是还是太麻烦,Spring
boot再次基础上再次进行了封装,简化冗余配置。
3:内嵌了Tomcat
内嵌Tomcat对我们来讲是一个非常重要的特性,两大好处
1:内嵌了Tomcat好处1
好处的背景:
解决了SpringMVC父子容器的问题。这是原有springMVC开发需要面对和解决的问题
在SpringMVC开发中,是如何解决父子容器的呢?
首先知道,SpringMVC开发过程中,是有两个Spring工厂的。回忆一下SpringMVC的配置:
DispatcherServlet:SpringMVC当中的前端控制器
init-param:我们还需要执行Spring的配置文件,指定spring的配置文件
DispatcherServlet本质上就是一个Spring的工厂,他是servlet但是内部封装了一个Spring的工厂
ContextLoaderListerner这个spring的一个监听器。这个listener本质上就是一个创建了spring工厂的listener,有这两个工厂并存,ContextLoaderListerner是父工厂,dispatcherServlet是子工厂
带来的问题:
Controller当中获取Service的时候,在Service当中配置了事务,但是事务不生效。
父工厂当中创建了Service,添加了事务,但是在子工厂当中,也同样创建了Service,但是没有事务,Controller是子工厂帮我们创建的,这个时候,就会获取子工厂的service,这样就是没有事务了。
问题的解决方案:
子工厂不要创建Service,去调用父工厂的Service。这是MVC开发过程中注明的父子容器的问题。
SpringBoot没有父子容器的问题,SpringBoot是内嵌Tomcat,他自己不需要解决了这个问题。不需要你程序员外部激活。
2:内嵌了Tomcat好处2
Web开发的启动进程,是通过Tomcat为载体启动进程的,换句话说一个Tomcat就是一个进程,但是SpringBoot内嵌了Tomcat之后,就可以通过运行jar包的方式去启动web服务,也就是启动一个服务进程,这样就可以多个Tomcat进程。轻松将一个大的web服务,轻松启动提供服务。对外发送请求,接收相应。也就提供了将一个web服务轻松进行拆分的条件,这也就解释了,一个SpringCloud必须嫁接于SpringBoot的原因。
除了这玩意,之后的好处跟SpringBoot毛关系都没有。
3:但是,如果没有SpringBoot,我们可以做微服务开发么?
当然是可以的。
没有SpringBoot,我们依然可以有类似于SpringCloud的微服务方案,我们完全可以使用Spring+SpringMVC去搭建微服务方案。
微服务是一个架构思想,他解决了分布式的很多问题,SpringCloud只是其中的一个解决方案
甚至我们可以脱离Java的体系,使用go来进行微服务编程,当前的go,Java都能干这事。
SpringBoot和SpringCloud都是停留在应用层,真正的核心体系,还是在Spring和SpringMVC当中。
4:Spring源码特点
1.枯燥,mybatis顶多算是spring的一个模块。 2.方方面面都要掌握, 3.课程源码学习的内容: Spring容器技术:IOC xml读取,存储,对象创建,加工,缓存,分析 AOP代理设计:Spring真正把代理设计融入到Spring体系当中。 事务:Spring是如何进行事务处理的,Spring与mybatis源码整合,现有持久化方案,在来讨论事务处理
5:Spring源码学习方案
在源码中,编写自己的测试代码,研究Spring源码。
1搭建不但见源码环境是不重要的,我们当前搭建环境只是为了加注释。
源码环境:从运行的角度来讲,我们在Spring源码当中写点自己的测试代码进行运行,会把自己的自己和Spring的代码都编译掉,这是要给很漫长而又蛋疼的过程。
什么叫基于源码运行?
我们从idea当中启动的时候,就是基于源码来跑,这样就会把所有的源码重新编译一遍。
为什么我们选择这个版本
我们的Spring学习是基于Spring5.1.x,Spring核心部分在Spring4.0之后,改动量很小很小,基本是一致的,Spring的5.2版本中引入了Groovy脚本和Kotinj脚本的支持,反而不利于我们源码的学习。
6:Spring源码环境搭建
1.获取Spring源码Github获取即可 2.Spring从5.x之后构建工具就由Maven转到了Gradle Spring下载下来的源码中,有一个gradle的包,其中有一个gradle-wrapper.properties的配置文件, 其中的url指定了这个Spring的版本对应的gradle的版本。 我们开发的时候gradle在中国用的很少,没有必要单独去搭建gradle环境,我们可以让Spring源码自动帮我们搭建环境时下载Gradle 80mb。 gradle这个东西,引入aliyun的镜像。在源码根路径下有build.gradle配置文件,这个就相当于maven的配置文件。 3.idea当中导入Spring项目,当我们导入在之后,就会进入这个buildoutput窗口,idea会自动帮我们下载gradle环境。 4.当我们构建完成之后,我们还需要做最后一件事,我们去点击那个build小斧头,跟他构建成class文件。
repositories { maven { url "https://repo.spring.io/libs-spring-framework-build" } maven { url 'https://maven.aliyun.com/repository/public/' } maven { url 'https://maven.aliyun.com/repository/spring/'} mavenCentral() }
三:Spring容器
1:Spring容器概述
1):工厂or容器?
Spring工厂,也就是Spring的容器。作为Spring的工厂来讲,Spring工厂是为了创建对象,创建完对象之后,不仅仅把这个对象创建出来,同时还会把这个对象存储在工厂当中宫其他调用者进行调用。创建和存储是他的作用,能存储就像是一个容器。当前这得有得有一个前提,对象必须得是单实例对象,Spring的scope必须得是sigleton才会进行存储,否则之给创建,每次使用每次建。
2):Spring容器是怎样存储单实例
思路很简单,Java的世界中,存储多种对象有多种数据结构,还得方便取用,最方便的数据结构就是Map,但凡是容器的概念,一定是有存储对象的作用。所以,使用容器或者工厂或者Ioc容器来称呼spring工厂都是没毛病的
为什么有的人把Tomcat也称为容器
因为tomcat不仅仅创建servlet还存储servlet供其他人进行使用。
2:Spring基本工厂
ApplicationContext是Spring最常用的工厂,又叫应用上下文。实际上是Spring提供的较为高级的一个工厂,这个工厂是复合了很多个其他工厂的作用,他的角色被很多的其他Spring工厂所分担了,所以,这个工厂可以视为Spring工厂的一个门面。就像是Mybatis的门面是sqlSession,用sqlSession进行访问和操作,它为我们提供了很多的工具和方法,使用方便,接口单一。但是实际上,他的是线上是很复杂的。
对应ApplicationContext来讲,他是一个高级的工厂,但是他集齐了其他高级工厂的能力,应用的时候,使用这个高级工厂很方便,但是我们真正分析研究的时候,就不能用这个东西了,因为功能太多。
Spring工厂的最为核心和底层的工厂是BeanFactory,BeanFactory是一个接口。这个容器的最基本的接口,Spring工厂的最底层,就是这个接口了。SpringBean容器的根接口,Spring的真个设计过程中,严格遵守类型单一职责的概念,因此他的工厂为了实现这么复杂的功能,演变成了一个非常复杂的体系。
底层是接口,在Java认知体系当中我们知道,子接口一定是在父接口的基础上提供了一些个性化的东西。经过层层继承,我们就可以看到一个更为高级的ApplicationContext接口。
1):BeanFactory
首先Spring工厂最底层的类就是这个BeanFactory接口,他定义了工厂的基本操作,也就是存取,也就是getBean这些操作都是这个接口而生。
2):HierarchicalBeanFactory
这个接口让Spring容器具有了继承的能力,也就是让Spring中引入了父子容器的概念。
3):AutowireCapableBeanFactory
这个接口提供了自动注入的能力。自动注入的能力是怎么体现的呢?我们在Spring的Bean标签当中,配置的Autowired标签,提供了这个标签之后就可以实现注入,这个注入的能力是这个容器提供的。除此之外,它还帮我们做了,initialBean和DisposBean,这个是什么意思呢?就是对Bean来进行初始化和销毁。工厂为啥会具有初始化和销毁Bean的操作呢,就是这个工厂提供了这样的能力,而当我们在我们创建的Bean实现上述的两个接口时候,工厂的这两样能力就可以在这个Bean上体现。
4):SimpleJndiBeanFactory
这个基本没啥屌用
5):ListableBeanFactory
这个工厂可以去获取一些配置信息。比如说:constrainsBeanDefinition或着getBeanNameForType,我们可以通过这个方法来获取Spring创建的所有的Bean的信息(名字等)。
我们讲解这么多内容干什么,就是因为我们的Spring高级工厂为什么具有这么丰富的功能,就是以为你Spring工厂是由这一系列的工厂能力堆砌而来的。每一个的工厂都是功能单一的,但是他们把共鞥都堆砌到ApplicationContext上的时候,这个ApplicationContext就很牛逼了。
6):ConfigurableBeanFactory
这个工厂被称为可配置工厂,我们创建一个对象是单实例多实例,这个事就是由这个工厂来实现的,当然话还有类型转换器,类型转换器和后置处理Bean都是由这个工厂提供的能力。
3:XmlBeanFactory
以上这么多工厂,有两个工厂是将上述工厂的能力集大成的。(以上都是基础工厂)
ApplicationContext和DefaultListableBeanFactory
DefaultListableBeanFactory是集成了上述工厂类型的最集大成者,这两个是成熟的工厂。当前我们使用的是Xml进行配置的,这样的话我们使用的是DefaultListableBeanFactory这个工厂的一个子类XmlBeanFactory他能基于Xml配置来完成对象的创建和管理。这里边多亏了他一个强大的工具类:
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); 这个玩意是用来读取xml的配置信息的。
基于此:我们就得到了一个分析的核心就是 XmlBeanFactory
@Deprecated @SuppressWarnings({"serial", "all"}) public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); /** * Create a new XmlBeanFactory with the given resource, * which must be parsable using DOM. * @param resource the XML resource to load bean definitions from * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } /** * Create a new XmlBeanFactory with the given input stream, * which must be parsable using DOM. * @param resource the XML resource to load bean definitions from * @param parentBeanFactory parent bean factory * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); } }
初看,这里边的功能也太少了,他自己的功能大部分都是从父类继承来的。这个类已经过期了:
Deprecated as of Spring 3.1 in favor of DefaultListableBeanFactory and XmlBeanDefinitionReader 自Spring3.1起已弃用,取而代之的是{link DefaultListableBeanFactory}和{XmlBeanDefinitionReader}
当前我们先按他学习,后期在进行替换。
1.读取配置文件,读取到JVM内存中,封装成Java对象。 2.创建工厂生产的对象。 BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource('applicationContext.xml')); 3.工厂容器中获取对象。 User user = ("User")beanFactory.getBean("u");
1):Spring读取Xml的配置文件
new ClassPathResource(‘applicationContext.xml’)指定了Spring配置文件的类路径。
ClassPathResource是Spring定义的一个类型,实现了Resource接口通过类加载器获取数据流。
Resource
Spring当中一个非常核心的接口,读取相关资源文件的内容,获取数据流,不仅仅是xml文件,可以读取网络中的资源。实现不同的功能就体现在不同的实现类里边了。Resource继承了一个接口,InputStreamSource这里边有一个方法InputStream getInputStream() throws IOException;那么我们就知道,读取方式一定是引入一个输入流的这种形式,当我们有了输入流,一切的问题迎刃而解。
Alt+7调出类的全部方法内容,点击对应方法:
ctrl+h查看了类的继承关系。
@Override public InputStream getInputStream() throws IOException { InputStream is; if (this.clazz != null) { is = this.clazz.getResourceAsStream(this.path); } else if (this.classLoader != null) { is = this.classLoader.getResourceAsStream(this.path); } else { is = ClassLoader.getSystemResourceAsStream(this.path); } if (is == null) { throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist"); } return is; }
Resource实现了继承了InputStreamSource接口,所有的内容读取都会以输入流的形式进行。
任何一个框架在开发和实现的过程中都会对常用的功能进行封装,Mybatis对反射进行了封装,Spring对输入流进行了封装。
FileSystemResource:从文件系统中获取输入流
UrlResource:从网络中获取输入流
ClassPathResource:从类路径中获取数据流,Resource的实现类,通过类加载器的方式获取输入流。
ByteArrayResource从字节数组中获取输入流
我们的xml都会基于这些类读取到我们的Jvm内存中,他是通过什么形式体现的呀?Spring配置文件的内容是以对象的形式存在,最终被封装成了一个BeanDefination,这些一个一个的Bean标签,
XML的解析,Mybatis的Xml解析是通过xpath,Spring的Xml解析方式是用的SAX
Mybatis当中对反射封装的很到位,Spring对流的使用,封装的也很到位。
Spring配置文件的内容,时以对象的形式,存储在JVM当中,Mybatis当中,Mybatis-Config.xml被解析封装成一个Configuration对象,Mapper.xml会被封装成一个MapperStatement这样的对象。在我们的Spring当中最终被封装成了一个BeanDefination这样的一个对象,这里的Bean对应着配置文件中的一个个Bean标签。
我们如何把Spring核心配置文件经过XML解析(SAX)之后,封装成BeanDefination对象。Mybatis用的是xpath这个解析工具比较新。
Spring当中的Resource接口继承了InputStreamSource接口(接口与接口之间的关系也叫做继承)
复习:
整体的SPRING工厂,BeanFactory是Spring当中最为底层的工厂。几个核心工厂:
XmlBeanFactory最大的特点:可以帮我们读取Xml配置文件来帮我们创建对应的对象,如何使用:
1.怎么读取配置文件? 2.读取了配置文件之后,我们如何在Spring当中以对象的形式进行封装? 3。如何获取导配置信息之后,根据配置信息创建对象? 4.所创建对象的生命周期如何整理? 以上几句话以及涵盖了SpringIOC的全部的核心内容。包括我们所说的Spring中对象的注入是从哪个 位置完成注入的?这个是从对象的生命周期这个阶段当中进行注入的。包括我们的回环应用,或者叫循 环依赖,也是在创建对象和生命周期当中进行体现的。 BeanFactory beanFactory = new XmlBeanFactory("Resource..."); BeanFactory.getBean 我们在研究一个框架的源码研究的时候,不可能研究明白他的放方面面,抓大放小,抓主去次。
1.怎么读取配置文件?
spring当中做了很好的封装,基于各种的Resouce的实现类,最终以InputStream的形式读取进来。
2.读取了配置文件之后,我们如何在Spring当中以对象的形式进行封装?
Resource接口实现类通过这个接口的某一个实现类,就是打开输入流,可读取资源导虚拟机当中。我们都进来之后,我们必须以对象的形式来存储。所有的框架都是这个尿性,Spring当中式搞成了一个BeanDefination对象,他是一个对象,他的是实现类,后续的我们使用最多的实现类是GenericBeanDefination继承关系如下:
BeanDefination实现了Cloneable接口,实现类为GenericBeanDefination,从输入流到GenericBeanDefination对象的过程当中是由XmlBeanDefinitionReader作的。XmlBeanDefinitionReader这个类型是在我们的XMLBeanFactory这个对象当中来进行应用的。所以我们为啥从XMLBeanFactory开始学习,XMLBeanFactory具有Spring工厂完成的功能,而且是通过解析xml来进行对象的创建。这样就得通过XmlBeanDefinitionReader这个组件来操作。如果哪天XMLBeanFactory这个东西过期了,我们可能需要自己去手动调用解析。
2):XmlBeanFactory替换
尝试替换的思路:我们用DefaultlistableBeanFactory替换XMLBeanFactory
@Test public void test() { BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext_tx.xml")); Product product = (Product) beanFactory.getBean("p"); //DefaultlistableBeanFactory 替换 XmlBeanFactory //DefaultListableBeanFactory + XmlBeanDefinitionReader //过期之后的代码我们只能通过这个写法来进行替换了。 DefaultListableBeanFactory beanFactory1 = new DefaultListableBeanFactory(); Resource resource = new ClassPathResource("applicationContext_tx.xml"); XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(beanFactory1); xmlBeanDefinitionReader.loadBeanDefinitions(resource); Object product1 = beanFactory1.getBean("product"); System.out.println("product1 = " + product1); ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); User user = (User) applicationContext.getBean("u"); // System.out.println("user.getName() = " + user.getName()); // System.out.println("user.getPassword() = " + user.getPassword()); }
如果或者Bean的时候没有:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'u' available at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:773) at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1221) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:294) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1105) at com.baizhiedu.SpringTest.testshit(SpringTest.java:161)
我们为什么不直接在Spring源码环境当中去写测试类,因为编译一坨坨Spring源码太特么耗时间了。新建一个Maven工程,使用Spring的依赖,然后只编译自己那几行代码他不香么。
Spring底层是如何通过BeanDefinitionReader组件将XMl封装成GenericBeanDefination对象的呢?
在Spring当中允许在一个工程当中有多个Spring工厂同时存在,
这种情况非常少见SpringMVC中的父子容器。
第二个视频看了半个小时