diff --git "a/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/01.IoC/01.350円257円276円347円250円213円350円265円204円346円226円231円/ioc346円211円200円351円234円200円jar.zip" "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/01.IoC/01.350円257円276円347円250円213円350円265円204円346円226円231円/ioc346円211円200円351円234円200円jar.zip" new file mode 100644 index 0000000..fc2a694 Binary files /dev/null and "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/01.IoC/01.350円257円276円347円250円213円350円265円204円346円226円231円/ioc346円211円200円351円234円200円jar.zip" differ diff --git "a/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/01.IoC/02.350円257円276円347円250円213円347円254円224円350円256円260円/Spring IoC346円246円202円345円277円265円345円217円212円345円256円236円346円210円230円.md" "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/01.IoC/02.350円257円276円347円250円213円347円254円224円350円256円260円/Spring IoC346円246円202円345円277円265円345円217円212円345円256円236円346円210円230円.md" new file mode 100644 index 0000000..56c47da --- /dev/null +++ "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/01.IoC/02.350円257円276円347円250円213円347円254円224円350円256円260円/Spring IoC346円246円202円345円277円265円345円217円212円345円256円236円346円210円230円.md" @@ -0,0 +1,587 @@ +# Spring IoC 概念及实战 + +# 说在前面 + +- **本章相关代码及笔记地址**:飞机票🚀 +- **🌍**Github:**[**🚀Java超神之路:【🍔Java全生态技术学习笔记,一起超神吧🍔】](https://github.com/EayonLee/JavaGod) +- **🪐**CSDN:**[**🚀Java超神之路:【🍔Java全生态技术学习笔记,一起超神吧🍔】](https://blog.csdn.net/qq_20492277/article/details/114269863) + +# 目录 +- [Spring IoC](#spring-ioc) +- [说在前面](#说在前面) +- [目录](#目录) +- [一. 什么是Spring?](#一-什么是spring) + - [1.1 什么是Spring?(面试题)](#11-什么是spring面试题) + - [1.2 Spring优点(面试题)](#12-spring优点面试题) +- [二. 入门案例](#二-入门案例) + - [2.1 下载Spring 5](#21-下载spring-5) + - [2.2 创建Java工程](#22-创建java工程) + - [2.3 导入Spring核jar包](#23-导入spring核jar包) + - [2.4 创建User类](#24-创建user类) + - [2.5 创建XML配置文件去实例化User Bean](#25-创建xml配置文件去实例化user-bean) + - [2.6 测试](#26-测试) +- [三. Spring-IoC控制反转 相关概念](#三-spring-ioc控制反转-相关概念) + - [3.1 什么是IoC(面试)](#31-什么是ioc面试) + - [3.2 IoC底层原理(面试)](#32-ioc底层原理面试) + - [3.3 Spring提供IoC容器实现的两种方式(Spring的两个核心接口)(面试)](#33-spring提供ioc容器实现的两种方式spring的两个核心接口面试) + - [3.4 ApplicationContext接口主要的实现类(面试)](#34-applicationcontext接口主要的实现类面试) + - [3.5 Spring中Bean的作用域(面试)](#35-spring中bean的作用域面试) +- [四. IoC Bean管理 - 基于XML方式](#四-ioc-bean管理---基于xml方式) + - [4.1 什么是Bean管理?](#41-什么是bean管理) + - [4.2 创建Bean](#42-创建bean) + - [4.3 属性注入](#43-属性注入) + - [4.3.1 setter方式](#431-setter方式) + - [4.3.2 构造函数方式](#432-构造函数方式) + - [4.3.3 属性注入 - 注入外部Bean](#433-属性注入---注入外部bean) + - [4.3.4 属性注入 - 注入内部Bean](#434-属性注入---注入内部bean) + - [4.3.5 属性注入 - 集合属性](#435-属性注入---集合属性) + - [4.4 FactoryBean(工厂Bean)](#44-factorybean工厂bean) + - [4.4.1 第一步:创建工厂Bean](#441-第一步创建工厂bean) + - [4.4.2 第二步:在XML中定义Bean](#442-第二步在xml中定义bean) + - [4.4.3 第三步:测试](#443-第三步测试) + - [4.5 Bean的作用域(单例/多例)](#45-bean的作用域单例多例) + - [4.5.1 验证Spring默认情况下的Bean是单例的](#451-验证spring默认情况下的bean是单例的) + - [4.5.1 修改Spring Bean默认单例为多例](#451-修改spring-bean默认单例为多例) + - [4.6 自动装配(自动注入)](#46-自动装配自动注入) + - [4.6.1 手动装配](#461-手动装配) + - [4.6.2 自动装配](#462-自动装配) +- [五. IoC Bean管理 - 基于注解方式](#五-ioc-bean管理---基于注解方式) + - [5.1 Spring中创建Bean的注解](#51-spring中创建bean的注解) + - [5.2 基于注解实现Bean创建](#52-基于注解实现bean创建) + - [5.2.1 引入依赖](#521-引入依赖) + - [5.2.2 开启组件扫描](#522-开启组件扫描) + - [5.2.3 创建类 添加注解](#523-创建类-添加注解) + - [5.2.4 测试](#524-测试) + - [5.3 基于注解实现属性自动注入](#53-基于注解实现属性自动注入) + - [5.3.1 @AutoWired](#531-autowired) + - [5.3.2 @Qualifie](#532-qualifie) + - [5.3.3 @Resource](#533-resource) + - [5.3.4 @Value](#534-value) +- [六. 通过配置类完全替代XML文件](#六-通过配置类完全替代xml文件) + - [6.1 创建配置类](#61-创建配置类) + - [6.2 测试](#62-测试) + + +# 一. 什么是Spring? + +## 1.1 什么是Spring?(面试题) + +Spring是一个设计层框架,有两个核心: + +**IOC控制反转**,目的是降低耦合度,它是基于工厂设计模式,所谓控制反转就是将自己手动new对象的操作交给Spring容器来完成。和控制反转配套使用的还有一个DI依赖注入。我们可以进行构造函数注入、属性注入(getter/setter)等。最常用的还是属性注入。可以注入各种类型:Map、List、Properties。注入可以通过ByType和ByName分别按照类型和名字进行自动注入。Spring中的Bean支持单例和原型两种方式,默认是单例的。可以通过Scope="seingleton",Scope="prototype"来配置。所谓单例:即至始至终在JVM中都只有一个该类的实例。所谓原型:也叫多例,就是每次都会创建一个新的对象。 + +**AOP面向切面编程**,不修改源代码进行方法增强,AOP是OOP(面向对象编程)的延续,主要用于日志记录、性能统计、安全控制、事务处理等方面。它是基于代理设计模式,而代理设计模式又分为静态代理和动态代理,静态代理比较简单就是一个接口,分别由一个真实实现和一个代理实现,而动态代理分为基于接口的JDK动态代理和基于类的cglib的动态代理,咱们正常都是面向接口开发,所以AOP使用的是基于接口的JDK动态代理。 + +**注:POJO和JavaBean的区别** + +- POJO 和JavaBean是我们常见的两个关键字,一般容易混淆,POJO全称是Plain Ordinary Java Object / Pure Old Java Object,中文可以翻译成:普通Java类,具有一部分getter/setter方法的那种类就可以称作POJO,但是JavaBean则比 POJO复杂很多, Java Bean 是可复用的组件,对 Java Bean 并没有严格的规范,理论上讲,任何一个 Java 类都可以是一个 Bean 。但通常情况下,由于 Java Bean 是被容器所创建(如 Tomcat) 的,所以 Java Bean 应具有一个无参的构造器。 + +- 通常 Java Bean 还要实现 Serializable 接口用于实现 Bean 的持久性。 Java Bean 是不能被跨进程访问的。JavaBean是一种组件技术,就好像你做了一个扳子,而这个扳子会在很多地方被拿去用,这个扳子也提供多种功能(你可以拿这个扳子扳、锤、撬等等),而这个扳子就是一个组件。一般在web应用程序中建立一个数据库的映射对象时,我们只能称它为POJO。POJO(Plain Old Java Object)这个名字用来强调它是一个普通java对象,而不是一个特殊的对象,其主要用来指代那些没有遵从特定的Java对象模型、约定或框架(如EJB)的Java对象。理想地讲,一个POJO是一个不受任何限制的Java对象(除了Java语言规范)。 + +## 1.2 Spring优点(面试题) + +- **IOC可以方便解耦,简化开发**:原来我们需要手动new对象,现在我们只需要将创建对象交给IOC并且管理对象之间的关系从而降低耦合。 +- **Aop编程支持**:不改变源代码进行方法(功能)增强 +- **方便程序测试**:Spring对Junit的支持可以通过注解方便的测试Spring程序 +- **方便和其他框架进行整合**:方便整合MyBatis、SpringMVC、Hibernate、Quartz等等。 +- **方便进行事务操作**:通过Spring声明式事务灵活的进行事务控制,提高开发效率 +- **降低 API开发难度**:Spring对一些难用的Java API进行了简易封装,如JDBC、JavaMail + + + +# 二. 入门案例 + +## 2.1 下载Spring 5 + +下载地址:https://repo.spring.io/release/org/springframework/spring/ + +![image-20210819105019708](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105019708.png) + +下载完成后,解压如下: + +![image-20210819104957397](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819104957397.png) + +## 2.2 创建Java工程 + +![image-20210819105100148](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105100148.png) + +## 2.3 导入Spring核jar包 + +由于现在只做入门案例,所以只导入Spring的基础核心jar包即可。 + +那我们通过上面Spring架构图中可得知 Spring的核心为4个 + +![image-20210819105148588](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105148588.png) + +那么我们就可以从libs文件夹中取出如上几个Jar包等待导入进工程 + +![image-20210819105213141](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105213141.png) + +commons-logging-1.1.1.jar(日志)需要单独下载:http://commons.apache.org/proper/commons-logging/download_logging.cgi + +**创建存放jar包的lib文件夹,并将4个jar包添加** + +![image-20210819105235247](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105235247.png) + +**将jar包导入项目** + +![image-20210819105253684](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105253684.png) + +![image-20210819105310891](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105310891.png) + +![image-20210819105322813](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105322813.png) + +## 2.4 创建User类 + +![image-20210819105709786](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105709786.png) + +## 2.5 创建XML配置文件去实例化User Bean + +![image-20210819105536125](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105536125.png) + +## 2.6 测试 + +![image-20210819105558880](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105558880.png) + + + +# 三. Spring-IoC控制反转 相关概念 + +## 3.1 什么是IoC(面试) + +**IOC控制反转**,目的是降低耦合度,它是基于工厂设计模式,所谓控制反转就是将自己手动new对象的操作交给Spring容器来完成。和控制反转配套使用的还有一个DI依赖注入。我们可以进行构造函数注入、属性注入等。最常用的还是属性注入。可以注入各种类型:Map、List、Properties。注入可以通过ByType和ByName分别按照类型和名字进行自动注入。Spring中的Bean支持单例和原型两种方式,默认是单例的。可以通过Scope="seingleton",Scope="prototype"来配置。所谓单例:即至始至终在JVM中都只有一个该类的实例。所谓原型:也叫多例,就是每次都会创建一个新的对象。 + +## 3.2 IoC底层原理(面试) + +**IOC底层主要实用技术**:xml解析、工厂设计模式、反射 + +![image-20210819105846881](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105846881.png) + +**上图解释**: + +比如我们想要在UserServlet中使用UserService中的方法,我们需要在UserServlet中去new UserService,但是这样耦合度太高了。我们如果使用IoC来托管Bean的话就是如下情况 + +我们在xml文件中去通过`` ``标签配置需要实例化的对象,IoC的底层其实就是通过工厂类(工厂设计模式),去解析xml文件中配置需要实例化对些的bean标签(如使用dom4j解析xml),从而获取到bean标签中的class属性值,即是需要实例化对象的Class全路径,然后就可以通过Class.forName()获取到他的字节码文件,然后通过newInstance()方法通过反射创建UserService对象并返回。 + +这样UserServlet类中的每个方法就不需要一个个去new对象了,直接通过工厂去获取就可以。就算UserService对象的路径变了,我们也只需要修改xml配置文件中该类的class属性即可。 + +## 3.3 Spring提供IoC容器实现的两种方式(Spring的两个核心接口)(面试) + +**IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂** + +(1)**BeanFactory接口**:IOC容器基本实现,是Spring内部用接口,不提供给开发人员进行使用 + +​ **特点**:加载配置文件时候不会创建对象,在使用getBean()获取对象时才会创建对象。 + +![image-20210819105958728](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105958728.png) + +(2)**ApplicationContext接口**:BeanFactory接口的子接口,提供更多更强大的功能,提供给开发人员使用 + +​ **特点**:加载配置文件时候就会把在配置文件对象进行创建 + +![image-20210819110020048](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110020048.png) + +## 3.4 ApplicationContext接口主要的实现类(面试) + +- **FileSystemXmlApplicationContext**:从XML文件中去加载bean,没有盘符的是项目工作路径,即项目的根目录;有盘符表示的是文件绝对路径. +- **ClassPathXmlApplicationContext**:从XML文件中去加载bean,默认就是指项目的classpath路径下面。如果要使用绝对路径,需要加上file:前缀表示这是绝对路径; + +**备注:通过Ctrl + H 可以查看ApplicationContext的结构** + +![image-20210819110056043](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110056043.png) + +## 3.5 Spring中Bean的作用域(面试) + +- **singleton**:单例,容器中只有一个实例**(默认)** +- **prototype**:多例,每个请求都会创建一个实例 +- **request**:它是为每个请求创建一个实例,请求完后bean会失效进行垃圾回收 +- **session**:它是确保每个session中都有一个bean实例,当session过期后bean会失效 + +**singletion和prototype的区别**: + +除了单例和多例的区别外还有实例化bean时机的区别,singleton则在加载xml文件时就去实例化,而prototype则是在getBean()的时候实例化 + + + +# 四. IoC Bean管理 - 基于XML方式 + +## 4.1 什么是Bean管理? + +**Bean管理分为**: + +- 创建对象 +- 属性注入(创建对象的时候,为类中属性设置属性值) + +**Bean管理的操作有两种方式** + +- 基于xml配置文件方式实现创建对象和属性注入 +- 基于注解方式实现创建对象和属性注入 + +## 4.2 创建Bean + +![image-20210819110300141](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110300141.png) + +在xml文件中使用Bean标签,在标签中添加对应的属性,就可以实现对象的创建 + +**Bean标签中的属性**: + +- **id**:该实例化对象的标识 +- **class**:实例化对象的类全路径 +- **name**:和id属性作用相同,区别在于name可以加特殊符号,id不可以(不常用) + +**特点**: + +- 使用xml的bean标签创建对象默认使用该对象的无参构造去创建,所以如果该对象没有生成无参构造会实例化失败 + +## 4.3 属性注入 + +### 4.3.1 setter方式 + +**Book实体** + +![image-20210819110542025](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110542025.png) + +**bean.xml配置文件** + +**注意**:想要使用setter注入的话该实体一定要有set方法 + +![image-20210819110355959](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110355959.png) + +**测试** + +![image-20210819110442380](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110442380.png) + +### 4.3.2 构造函数方式 + +**order订单实体** + +![image-20210819110631420](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110631420.png) + +**bean.xml配置文件** + +**注意**:想要使用狗咱函数注入的话该实体一定要有有参构造 + +![image-20210819110645122](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110645122.png) + +**测试** + +![image-20210819110659431](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110659431.png) + +### 4.3.3 属性注入 - 注入外部Bean + +比如现在有个实体叫 School,这个实体中有个属性是Book,那么我们该如何去使用xml方式去实例化并属性注入School呢? + +**School实体** + +![image-20210819110725131](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110725131.png) + +**bean.xml配置文件** + +**注意**:这里我是用setter方法注入,使用构造函数的话也是一样的 + +![image-20210819110739068](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110739068.png) + +**测试** + +![image-20210819110757867](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110757867.png) + +### 4.3.4 属性注入 - 注入内部Bean + +**Book实体(和上面相同)** + +![image-20210819110837664](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110837664.png) + +**School实体(和上面相同)** + +![image-20210819110850572](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110850572.png) + +**bean.xml配置文件** + +**注意**:这里我是用setter方法注入,使用构造函数的话也是一样的 + +![image-20210819110903901](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110903901.png) + +**测试** + +![image-20210819110919779](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110919779.png) + +### 4.3.5 属性注入 - 集合属性 + +**部门实体(包含了List、数据、Map)** + +![image-20210819110954059](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110954059.png) + +**bean.xml配置文件** + +**注意**:这里我是用setter方法注入,使用构造函数的话也是一样的 + +![image-20210819111009972](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111009972.png) + +**测试** + +![image-20210819111026438](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111026438.png) + +## 4.4 FactoryBean(工厂Bean) + +**区别**: + +​ **普通bean**:上面的所有我们在xml中配置的bean返回值类型就是当前bean的类型 + +​ **工厂bean**:还有一种bean就是工厂bean,他在xml中配置的bean的返回值类型就可以不是当前bean类型 + +### 4.4.1 第一步:创建工厂Bean + +该类一定要实现FactoryBean接口 + +```java +//需要在FactoryBean的泛型中定义需要返回的bean对象 +public class MyBean implements FactoryBean { + + /** + * 定义返回bean + */ + @Override + public User getObject() throws Exception { + User user = new User(); + user.setName("张三"); + return user; + } + + @Override + public Class getObjectType() { + return null; + } +} +``` + +### 4.4.2 第二步:在XML中定义Bean + +```java + + +``` + +### 4.4.3 第三步:测试 + +![image-20210819111220706](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111220706.png) + +## 4.5 Bean的作用域(单例/多例) + +在Spring中的bean是分为单例和多例的,那么我们同样可以在创建Bean的时候去进行控制 + +**在不做任何配置的情况下Spring创建出来的Bean是单例的。** + +### 4.5.1 验证Spring默认情况下的Bean是单例的 + +如果输出的User对象地址是同一个则说明是单例 + +**xml文件中定义user bean** + +![image-20210819111324681](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111324681.png) + +**测试** + +![image-20210819111341594](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111341594.png) + +### 4.5.1 修改Spring Bean默认单例为多例 + +**xml文件中定义user bean** + +我们可以通过``Scope = "singleton"`` 或者`` Scope = "prototype"`` 修改bean为单例(singleton)或者多例(prototype) + +并且如果设置成singleton则在加载xml文件时就去实例化,而prototype则是在getBean()的时候实例化 + +![image-20210819111436998](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111436998.png) + +**测试** + +![image-20210819111451671](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111451671.png) + +## 4.6 自动装配(自动注入) + +### 4.6.1 手动装配 + +我们上面所有的操作都是手动装配,这里再演示一次 让大家好理解 + +**实体** + +![image-20210819111526118](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111526118.png) + +![image-20210819111534163](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111534163.png) + +**xml文件** + +其实就是和之前的操作是一样的,手动装配外部bean + +![image-20210819111552636](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111552636.png) + +**测试** + +![image-20210819111605807](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111605807.png) + +### 4.6.2 自动装配 + +**实体相同 不做演示** + +**XML文件** + +![image-20210819111629232](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111629232.png) + +**测试** + +![image-20210819111642752](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111642752.png) + + + +# 五. IoC Bean管理 - 基于注解方式 + +## 5.1 Spring中创建Bean的注解 + +- @Controller:常用于Web层 +- @Service:常用于Service层 +- @Repository:常用于持久层 +- @Component:通用 + +其实四个注解功能都是一样的,都可以创建Bean实例,只不过是一种规范而已。 + +## 5.2 基于注解实现Bean创建 + +### 5.2.1 引入依赖 + +![image-20210819111745348](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111745348.png) + +### 5.2.2 开启组件扫描 + +在xml文件中添加开启组件扫描,去扫描我们加了注解的类 + +**备注:xml文件头必须为如下格式** + +```xml + + + + + + + +``` + +### 5.2.3 创建类 添加注解 + +![image-20210819111830901](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111830901.png) + +### 5.2.4 测试 + +![image-20210819111844856](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111844856.png) + +## 5.3 基于注解实现属性自动注入 + +- @AutoWired:默认是按照ByType进行注入,如果当前Type类型个数大于1的话则按照ByName进行注入。如果声明按照ByName进行注入需要和@Qualifier注解一起进行使用。 +- @Qualifier:根据ByName属性名称进行自动注入,必须要和@AutoWired结和使用 +- @Resource:默认按照ByName进行注入,如果注入失败,则按照ByType进行注入 +- @Value:注入普通类型属性,如String... + +### 5.3.1 @AutoWired + +默认是按照ByType进行注入,如果当前Type类型个数大于1的话则按照ByName进行注入。如果声明按照ByName进行注入需要和@Qualifier注解一起进行使用。 + +**UserService** + +![image-20210819111932337](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111932337.png) + +**UserDao** + +![image-20210819111944850](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111944850.png) + +**测试UserService是否通过注解自动注入了UserDao** + +![image-20210819111958089](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111958089.png) + +### 5.3.2 @Qualifie + +根据ByName属性名称进行自动注入,必须要和@AutoWired结和使用 + +**UserService** + +![image-20210819112021658](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112021658.png) + +**UserDao** + +![image-20210819112045944](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112045944.png) + +**测试UserService是否通过注解自动注入了UserDao** + +![image-20210819112058729](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112058729.png) + +### 5.3.3 @Resource + +默认按照ByName进行注入,如果注入失败,则按照ByType进行注入 + +**UserService** + +![image-20210819112122037](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112122037.png) + +**UserDao** + +![image-20210819112135830](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112135830.png) + +**测试UserService是否通过注解自动注入了UserDao** + +![image-20210819112150153](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112150153.png) + +### 5.3.4 @Value + +**UserService** + +![image-20210819112237369](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112237369.png) + +**测试** + +![image-20210819112254263](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112254263.png) + + + +# 六. 通过配置类完全替代XML文件 + +如上我们通过注解可以完全替代在XML中编写bean标签去实例化bean的操作和属性注入的操作, + +但是我们还是需要在XML中编写标签去扫描我们增加注解的类才能去实例化,其实我们可以通过配置类去解决。 + +## 6.1 创建配置类 + +![image-20210819112335230](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112335230.png) + +这个配置类就等同于如下XML配置 + +![image-20210819112348630](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112348630.png) + +## 6.2 测试 + +测试和之前有所不同,因为我们不需要再去加载XML文件了,而是去加载我们的配置类 + +![image-20210819112407552](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112407552.png) + + + + + + + + + + + + + + + + + + + + + diff --git "a/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/01.IoC/README.md" "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/01.IoC/README.md" new file mode 100644 index 0000000..56c47da --- /dev/null +++ "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/01.IoC/README.md" @@ -0,0 +1,587 @@ +# Spring IoC 概念及实战 + +# 说在前面 + +- **本章相关代码及笔记地址**:飞机票🚀 +- **🌍**Github:**[**🚀Java超神之路:【🍔Java全生态技术学习笔记,一起超神吧🍔】](https://github.com/EayonLee/JavaGod) +- **🪐**CSDN:**[**🚀Java超神之路:【🍔Java全生态技术学习笔记,一起超神吧🍔】](https://blog.csdn.net/qq_20492277/article/details/114269863) + +# 目录 +- [Spring IoC](#spring-ioc) +- [说在前面](#说在前面) +- [目录](#目录) +- [一. 什么是Spring?](#一-什么是spring) + - [1.1 什么是Spring?(面试题)](#11-什么是spring面试题) + - [1.2 Spring优点(面试题)](#12-spring优点面试题) +- [二. 入门案例](#二-入门案例) + - [2.1 下载Spring 5](#21-下载spring-5) + - [2.2 创建Java工程](#22-创建java工程) + - [2.3 导入Spring核jar包](#23-导入spring核jar包) + - [2.4 创建User类](#24-创建user类) + - [2.5 创建XML配置文件去实例化User Bean](#25-创建xml配置文件去实例化user-bean) + - [2.6 测试](#26-测试) +- [三. Spring-IoC控制反转 相关概念](#三-spring-ioc控制反转-相关概念) + - [3.1 什么是IoC(面试)](#31-什么是ioc面试) + - [3.2 IoC底层原理(面试)](#32-ioc底层原理面试) + - [3.3 Spring提供IoC容器实现的两种方式(Spring的两个核心接口)(面试)](#33-spring提供ioc容器实现的两种方式spring的两个核心接口面试) + - [3.4 ApplicationContext接口主要的实现类(面试)](#34-applicationcontext接口主要的实现类面试) + - [3.5 Spring中Bean的作用域(面试)](#35-spring中bean的作用域面试) +- [四. IoC Bean管理 - 基于XML方式](#四-ioc-bean管理---基于xml方式) + - [4.1 什么是Bean管理?](#41-什么是bean管理) + - [4.2 创建Bean](#42-创建bean) + - [4.3 属性注入](#43-属性注入) + - [4.3.1 setter方式](#431-setter方式) + - [4.3.2 构造函数方式](#432-构造函数方式) + - [4.3.3 属性注入 - 注入外部Bean](#433-属性注入---注入外部bean) + - [4.3.4 属性注入 - 注入内部Bean](#434-属性注入---注入内部bean) + - [4.3.5 属性注入 - 集合属性](#435-属性注入---集合属性) + - [4.4 FactoryBean(工厂Bean)](#44-factorybean工厂bean) + - [4.4.1 第一步:创建工厂Bean](#441-第一步创建工厂bean) + - [4.4.2 第二步:在XML中定义Bean](#442-第二步在xml中定义bean) + - [4.4.3 第三步:测试](#443-第三步测试) + - [4.5 Bean的作用域(单例/多例)](#45-bean的作用域单例多例) + - [4.5.1 验证Spring默认情况下的Bean是单例的](#451-验证spring默认情况下的bean是单例的) + - [4.5.1 修改Spring Bean默认单例为多例](#451-修改spring-bean默认单例为多例) + - [4.6 自动装配(自动注入)](#46-自动装配自动注入) + - [4.6.1 手动装配](#461-手动装配) + - [4.6.2 自动装配](#462-自动装配) +- [五. IoC Bean管理 - 基于注解方式](#五-ioc-bean管理---基于注解方式) + - [5.1 Spring中创建Bean的注解](#51-spring中创建bean的注解) + - [5.2 基于注解实现Bean创建](#52-基于注解实现bean创建) + - [5.2.1 引入依赖](#521-引入依赖) + - [5.2.2 开启组件扫描](#522-开启组件扫描) + - [5.2.3 创建类 添加注解](#523-创建类-添加注解) + - [5.2.4 测试](#524-测试) + - [5.3 基于注解实现属性自动注入](#53-基于注解实现属性自动注入) + - [5.3.1 @AutoWired](#531-autowired) + - [5.3.2 @Qualifie](#532-qualifie) + - [5.3.3 @Resource](#533-resource) + - [5.3.4 @Value](#534-value) +- [六. 通过配置类完全替代XML文件](#六-通过配置类完全替代xml文件) + - [6.1 创建配置类](#61-创建配置类) + - [6.2 测试](#62-测试) + + +# 一. 什么是Spring? + +## 1.1 什么是Spring?(面试题) + +Spring是一个设计层框架,有两个核心: + +**IOC控制反转**,目的是降低耦合度,它是基于工厂设计模式,所谓控制反转就是将自己手动new对象的操作交给Spring容器来完成。和控制反转配套使用的还有一个DI依赖注入。我们可以进行构造函数注入、属性注入(getter/setter)等。最常用的还是属性注入。可以注入各种类型:Map、List、Properties。注入可以通过ByType和ByName分别按照类型和名字进行自动注入。Spring中的Bean支持单例和原型两种方式,默认是单例的。可以通过Scope="seingleton",Scope="prototype"来配置。所谓单例:即至始至终在JVM中都只有一个该类的实例。所谓原型:也叫多例,就是每次都会创建一个新的对象。 + +**AOP面向切面编程**,不修改源代码进行方法增强,AOP是OOP(面向对象编程)的延续,主要用于日志记录、性能统计、安全控制、事务处理等方面。它是基于代理设计模式,而代理设计模式又分为静态代理和动态代理,静态代理比较简单就是一个接口,分别由一个真实实现和一个代理实现,而动态代理分为基于接口的JDK动态代理和基于类的cglib的动态代理,咱们正常都是面向接口开发,所以AOP使用的是基于接口的JDK动态代理。 + +**注:POJO和JavaBean的区别** + +- POJO 和JavaBean是我们常见的两个关键字,一般容易混淆,POJO全称是Plain Ordinary Java Object / Pure Old Java Object,中文可以翻译成:普通Java类,具有一部分getter/setter方法的那种类就可以称作POJO,但是JavaBean则比 POJO复杂很多, Java Bean 是可复用的组件,对 Java Bean 并没有严格的规范,理论上讲,任何一个 Java 类都可以是一个 Bean 。但通常情况下,由于 Java Bean 是被容器所创建(如 Tomcat) 的,所以 Java Bean 应具有一个无参的构造器。 + +- 通常 Java Bean 还要实现 Serializable 接口用于实现 Bean 的持久性。 Java Bean 是不能被跨进程访问的。JavaBean是一种组件技术,就好像你做了一个扳子,而这个扳子会在很多地方被拿去用,这个扳子也提供多种功能(你可以拿这个扳子扳、锤、撬等等),而这个扳子就是一个组件。一般在web应用程序中建立一个数据库的映射对象时,我们只能称它为POJO。POJO(Plain Old Java Object)这个名字用来强调它是一个普通java对象,而不是一个特殊的对象,其主要用来指代那些没有遵从特定的Java对象模型、约定或框架(如EJB)的Java对象。理想地讲,一个POJO是一个不受任何限制的Java对象(除了Java语言规范)。 + +## 1.2 Spring优点(面试题) + +- **IOC可以方便解耦,简化开发**:原来我们需要手动new对象,现在我们只需要将创建对象交给IOC并且管理对象之间的关系从而降低耦合。 +- **Aop编程支持**:不改变源代码进行方法(功能)增强 +- **方便程序测试**:Spring对Junit的支持可以通过注解方便的测试Spring程序 +- **方便和其他框架进行整合**:方便整合MyBatis、SpringMVC、Hibernate、Quartz等等。 +- **方便进行事务操作**:通过Spring声明式事务灵活的进行事务控制,提高开发效率 +- **降低 API开发难度**:Spring对一些难用的Java API进行了简易封装,如JDBC、JavaMail + + + +# 二. 入门案例 + +## 2.1 下载Spring 5 + +下载地址:https://repo.spring.io/release/org/springframework/spring/ + +![image-20210819105019708](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105019708.png) + +下载完成后,解压如下: + +![image-20210819104957397](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819104957397.png) + +## 2.2 创建Java工程 + +![image-20210819105100148](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105100148.png) + +## 2.3 导入Spring核jar包 + +由于现在只做入门案例,所以只导入Spring的基础核心jar包即可。 + +那我们通过上面Spring架构图中可得知 Spring的核心为4个 + +![image-20210819105148588](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105148588.png) + +那么我们就可以从libs文件夹中取出如上几个Jar包等待导入进工程 + +![image-20210819105213141](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105213141.png) + +commons-logging-1.1.1.jar(日志)需要单独下载:http://commons.apache.org/proper/commons-logging/download_logging.cgi + +**创建存放jar包的lib文件夹,并将4个jar包添加** + +![image-20210819105235247](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105235247.png) + +**将jar包导入项目** + +![image-20210819105253684](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105253684.png) + +![image-20210819105310891](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105310891.png) + +![image-20210819105322813](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105322813.png) + +## 2.4 创建User类 + +![image-20210819105709786](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105709786.png) + +## 2.5 创建XML配置文件去实例化User Bean + +![image-20210819105536125](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105536125.png) + +## 2.6 测试 + +![image-20210819105558880](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105558880.png) + + + +# 三. Spring-IoC控制反转 相关概念 + +## 3.1 什么是IoC(面试) + +**IOC控制反转**,目的是降低耦合度,它是基于工厂设计模式,所谓控制反转就是将自己手动new对象的操作交给Spring容器来完成。和控制反转配套使用的还有一个DI依赖注入。我们可以进行构造函数注入、属性注入等。最常用的还是属性注入。可以注入各种类型:Map、List、Properties。注入可以通过ByType和ByName分别按照类型和名字进行自动注入。Spring中的Bean支持单例和原型两种方式,默认是单例的。可以通过Scope="seingleton",Scope="prototype"来配置。所谓单例:即至始至终在JVM中都只有一个该类的实例。所谓原型:也叫多例,就是每次都会创建一个新的对象。 + +## 3.2 IoC底层原理(面试) + +**IOC底层主要实用技术**:xml解析、工厂设计模式、反射 + +![image-20210819105846881](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105846881.png) + +**上图解释**: + +比如我们想要在UserServlet中使用UserService中的方法,我们需要在UserServlet中去new UserService,但是这样耦合度太高了。我们如果使用IoC来托管Bean的话就是如下情况 + +我们在xml文件中去通过`` ``标签配置需要实例化的对象,IoC的底层其实就是通过工厂类(工厂设计模式),去解析xml文件中配置需要实例化对些的bean标签(如使用dom4j解析xml),从而获取到bean标签中的class属性值,即是需要实例化对象的Class全路径,然后就可以通过Class.forName()获取到他的字节码文件,然后通过newInstance()方法通过反射创建UserService对象并返回。 + +这样UserServlet类中的每个方法就不需要一个个去new对象了,直接通过工厂去获取就可以。就算UserService对象的路径变了,我们也只需要修改xml配置文件中该类的class属性即可。 + +## 3.3 Spring提供IoC容器实现的两种方式(Spring的两个核心接口)(面试) + +**IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂** + +(1)**BeanFactory接口**:IOC容器基本实现,是Spring内部用接口,不提供给开发人员进行使用 + +​ **特点**:加载配置文件时候不会创建对象,在使用getBean()获取对象时才会创建对象。 + +![image-20210819105958728](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819105958728.png) + +(2)**ApplicationContext接口**:BeanFactory接口的子接口,提供更多更强大的功能,提供给开发人员使用 + +​ **特点**:加载配置文件时候就会把在配置文件对象进行创建 + +![image-20210819110020048](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110020048.png) + +## 3.4 ApplicationContext接口主要的实现类(面试) + +- **FileSystemXmlApplicationContext**:从XML文件中去加载bean,没有盘符的是项目工作路径,即项目的根目录;有盘符表示的是文件绝对路径. +- **ClassPathXmlApplicationContext**:从XML文件中去加载bean,默认就是指项目的classpath路径下面。如果要使用绝对路径,需要加上file:前缀表示这是绝对路径; + +**备注:通过Ctrl + H 可以查看ApplicationContext的结构** + +![image-20210819110056043](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110056043.png) + +## 3.5 Spring中Bean的作用域(面试) + +- **singleton**:单例,容器中只有一个实例**(默认)** +- **prototype**:多例,每个请求都会创建一个实例 +- **request**:它是为每个请求创建一个实例,请求完后bean会失效进行垃圾回收 +- **session**:它是确保每个session中都有一个bean实例,当session过期后bean会失效 + +**singletion和prototype的区别**: + +除了单例和多例的区别外还有实例化bean时机的区别,singleton则在加载xml文件时就去实例化,而prototype则是在getBean()的时候实例化 + + + +# 四. IoC Bean管理 - 基于XML方式 + +## 4.1 什么是Bean管理? + +**Bean管理分为**: + +- 创建对象 +- 属性注入(创建对象的时候,为类中属性设置属性值) + +**Bean管理的操作有两种方式** + +- 基于xml配置文件方式实现创建对象和属性注入 +- 基于注解方式实现创建对象和属性注入 + +## 4.2 创建Bean + +![image-20210819110300141](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110300141.png) + +在xml文件中使用Bean标签,在标签中添加对应的属性,就可以实现对象的创建 + +**Bean标签中的属性**: + +- **id**:该实例化对象的标识 +- **class**:实例化对象的类全路径 +- **name**:和id属性作用相同,区别在于name可以加特殊符号,id不可以(不常用) + +**特点**: + +- 使用xml的bean标签创建对象默认使用该对象的无参构造去创建,所以如果该对象没有生成无参构造会实例化失败 + +## 4.3 属性注入 + +### 4.3.1 setter方式 + +**Book实体** + +![image-20210819110542025](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110542025.png) + +**bean.xml配置文件** + +**注意**:想要使用setter注入的话该实体一定要有set方法 + +![image-20210819110355959](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110355959.png) + +**测试** + +![image-20210819110442380](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110442380.png) + +### 4.3.2 构造函数方式 + +**order订单实体** + +![image-20210819110631420](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110631420.png) + +**bean.xml配置文件** + +**注意**:想要使用狗咱函数注入的话该实体一定要有有参构造 + +![image-20210819110645122](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110645122.png) + +**测试** + +![image-20210819110659431](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110659431.png) + +### 4.3.3 属性注入 - 注入外部Bean + +比如现在有个实体叫 School,这个实体中有个属性是Book,那么我们该如何去使用xml方式去实例化并属性注入School呢? + +**School实体** + +![image-20210819110725131](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110725131.png) + +**bean.xml配置文件** + +**注意**:这里我是用setter方法注入,使用构造函数的话也是一样的 + +![image-20210819110739068](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110739068.png) + +**测试** + +![image-20210819110757867](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110757867.png) + +### 4.3.4 属性注入 - 注入内部Bean + +**Book实体(和上面相同)** + +![image-20210819110837664](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110837664.png) + +**School实体(和上面相同)** + +![image-20210819110850572](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110850572.png) + +**bean.xml配置文件** + +**注意**:这里我是用setter方法注入,使用构造函数的话也是一样的 + +![image-20210819110903901](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110903901.png) + +**测试** + +![image-20210819110919779](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110919779.png) + +### 4.3.5 属性注入 - 集合属性 + +**部门实体(包含了List、数据、Map)** + +![image-20210819110954059](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819110954059.png) + +**bean.xml配置文件** + +**注意**:这里我是用setter方法注入,使用构造函数的话也是一样的 + +![image-20210819111009972](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111009972.png) + +**测试** + +![image-20210819111026438](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111026438.png) + +## 4.4 FactoryBean(工厂Bean) + +**区别**: + +​ **普通bean**:上面的所有我们在xml中配置的bean返回值类型就是当前bean的类型 + +​ **工厂bean**:还有一种bean就是工厂bean,他在xml中配置的bean的返回值类型就可以不是当前bean类型 + +### 4.4.1 第一步:创建工厂Bean + +该类一定要实现FactoryBean接口 + +```java +//需要在FactoryBean的泛型中定义需要返回的bean对象 +public class MyBean implements FactoryBean { + + /** + * 定义返回bean + */ + @Override + public User getObject() throws Exception { + User user = new User(); + user.setName("张三"); + return user; + } + + @Override + public Class getObjectType() { + return null; + } +} +``` + +### 4.4.2 第二步:在XML中定义Bean + +```java + + +``` + +### 4.4.3 第三步:测试 + +![image-20210819111220706](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111220706.png) + +## 4.5 Bean的作用域(单例/多例) + +在Spring中的bean是分为单例和多例的,那么我们同样可以在创建Bean的时候去进行控制 + +**在不做任何配置的情况下Spring创建出来的Bean是单例的。** + +### 4.5.1 验证Spring默认情况下的Bean是单例的 + +如果输出的User对象地址是同一个则说明是单例 + +**xml文件中定义user bean** + +![image-20210819111324681](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111324681.png) + +**测试** + +![image-20210819111341594](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111341594.png) + +### 4.5.1 修改Spring Bean默认单例为多例 + +**xml文件中定义user bean** + +我们可以通过``Scope = "singleton"`` 或者`` Scope = "prototype"`` 修改bean为单例(singleton)或者多例(prototype) + +并且如果设置成singleton则在加载xml文件时就去实例化,而prototype则是在getBean()的时候实例化 + +![image-20210819111436998](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111436998.png) + +**测试** + +![image-20210819111451671](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111451671.png) + +## 4.6 自动装配(自动注入) + +### 4.6.1 手动装配 + +我们上面所有的操作都是手动装配,这里再演示一次 让大家好理解 + +**实体** + +![image-20210819111526118](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111526118.png) + +![image-20210819111534163](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111534163.png) + +**xml文件** + +其实就是和之前的操作是一样的,手动装配外部bean + +![image-20210819111552636](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111552636.png) + +**测试** + +![image-20210819111605807](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111605807.png) + +### 4.6.2 自动装配 + +**实体相同 不做演示** + +**XML文件** + +![image-20210819111629232](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111629232.png) + +**测试** + +![image-20210819111642752](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111642752.png) + + + +# 五. IoC Bean管理 - 基于注解方式 + +## 5.1 Spring中创建Bean的注解 + +- @Controller:常用于Web层 +- @Service:常用于Service层 +- @Repository:常用于持久层 +- @Component:通用 + +其实四个注解功能都是一样的,都可以创建Bean实例,只不过是一种规范而已。 + +## 5.2 基于注解实现Bean创建 + +### 5.2.1 引入依赖 + +![image-20210819111745348](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111745348.png) + +### 5.2.2 开启组件扫描 + +在xml文件中添加开启组件扫描,去扫描我们加了注解的类 + +**备注:xml文件头必须为如下格式** + +```xml + + + + + + + +``` + +### 5.2.3 创建类 添加注解 + +![image-20210819111830901](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111830901.png) + +### 5.2.4 测试 + +![image-20210819111844856](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111844856.png) + +## 5.3 基于注解实现属性自动注入 + +- @AutoWired:默认是按照ByType进行注入,如果当前Type类型个数大于1的话则按照ByName进行注入。如果声明按照ByName进行注入需要和@Qualifier注解一起进行使用。 +- @Qualifier:根据ByName属性名称进行自动注入,必须要和@AutoWired结和使用 +- @Resource:默认按照ByName进行注入,如果注入失败,则按照ByType进行注入 +- @Value:注入普通类型属性,如String... + +### 5.3.1 @AutoWired + +默认是按照ByType进行注入,如果当前Type类型个数大于1的话则按照ByName进行注入。如果声明按照ByName进行注入需要和@Qualifier注解一起进行使用。 + +**UserService** + +![image-20210819111932337](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111932337.png) + +**UserDao** + +![image-20210819111944850](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111944850.png) + +**测试UserService是否通过注解自动注入了UserDao** + +![image-20210819111958089](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819111958089.png) + +### 5.3.2 @Qualifie + +根据ByName属性名称进行自动注入,必须要和@AutoWired结和使用 + +**UserService** + +![image-20210819112021658](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112021658.png) + +**UserDao** + +![image-20210819112045944](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112045944.png) + +**测试UserService是否通过注解自动注入了UserDao** + +![image-20210819112058729](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112058729.png) + +### 5.3.3 @Resource + +默认按照ByName进行注入,如果注入失败,则按照ByType进行注入 + +**UserService** + +![image-20210819112122037](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112122037.png) + +**UserDao** + +![image-20210819112135830](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112135830.png) + +**测试UserService是否通过注解自动注入了UserDao** + +![image-20210819112150153](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112150153.png) + +### 5.3.4 @Value + +**UserService** + +![image-20210819112237369](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112237369.png) + +**测试** + +![image-20210819112254263](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112254263.png) + + + +# 六. 通过配置类完全替代XML文件 + +如上我们通过注解可以完全替代在XML中编写bean标签去实例化bean的操作和属性注入的操作, + +但是我们还是需要在XML中编写标签去扫描我们增加注解的类才能去实例化,其实我们可以通过配置类去解决。 + +## 6.1 创建配置类 + +![image-20210819112335230](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112335230.png) + +这个配置类就等同于如下XML配置 + +![image-20210819112348630](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112348630.png) + +## 6.2 测试 + +测试和之前有所不同,因为我们不需要再去加载XML文件了,而是去加载我们的配置类 + +![image-20210819112407552](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819112407552.png) + + + + + + + + + + + + + + + + + + + + + diff --git "a/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/02.AOP/01.350円257円276円347円250円213円350円265円204円346円226円231円/aop346円211円200円351円234円200円jar.zip" "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/02.AOP/01.350円257円276円347円250円213円350円265円204円346円226円231円/aop346円211円200円351円234円200円jar.zip" new file mode 100644 index 0000000..27a431a Binary files /dev/null and "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/02.AOP/01.350円257円276円347円250円213円350円265円204円346円226円231円/aop346円211円200円351円234円200円jar.zip" differ diff --git "a/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/02.AOP/02.350円257円276円347円250円213円347円254円224円350円256円260円/Spring AOP346円246円202円345円277円265円345円217円212円345円256円236円346円210円230円.md" "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/02.AOP/02.350円257円276円347円250円213円347円254円224円350円256円260円/Spring AOP346円246円202円345円277円265円345円217円212円345円256円236円346円210円230円.md" new file mode 100644 index 0000000..cac038d --- /dev/null +++ "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/02.AOP/02.350円257円276円347円250円213円347円254円224円350円256円260円/Spring AOP346円246円202円345円277円265円345円217円212円345円256円236円346円210円230円.md" @@ -0,0 +1,549 @@ +# Spring AOP 概念及实战 + +# 说在前面 + +- **本章相关代码及笔记地址**:飞机票🚀 +- **🌍**Github:**[**🚀Java超神之路:【🍔Java全生态技术学习笔记,一起超神吧🍔】](https://github.com/EayonLee/JavaGod) +- **🪐**CSDN:**[**🚀Java超神之路:【🍔Java全生态技术学习笔记,一起超神吧🍔】](https://blog.csdn.net/qq_20492277/article/details/114269863) + +# 目录 +- [Spring AOP 概念及实战](#spring-aop-概念及实战) +- [说在前面](#说在前面) +- [目录](#目录) +- [一. AOP基础概念](#一-aop基础概念) + - [1.1 AOP?](#11-aop) + - [1.2 AOP中的一些常用概念](#12-aop中的一些常用概念) + - [1.3 通知类型](#13-通知类型) +- [二. AOP底层实现](#二-aop底层实现) + - [2.1 AOP底层原理](#21-aop底层原理) + - [2.2 代理概述](#22-代理概述) + - [2.3 静态代理实现](#23-静态代理实现) + - [2.3 JDK动态代理实现](#23-jdk动态代理实现) +- [三. Spring AOP的操作](#三-spring-aop的操作) + - [3.1 什么是AspectJ?](#31-什么是aspectj) + - [3.1.1 在Spring框架中,一般都是基于AspectJ来实现AOP的相关操作](#311-在spring框架中一般都是基于aspectj来实现aop的相关操作) + - [3.1.2 基于AspectJ实现AOP操作](#312-基于aspectj实现aop操作) + - [3.2 准备工作](#32-准备工作) + - [3.2.1 引入Spring AOP相关依赖](#321-引入spring-aop相关依赖) + - [3.2.2 切入点表达式了解](#322-切入点表达式了解) + - [3.3 AOP操作 - 基于AspectJ注解](#33-aop操作---基于aspectj注解) + - [3.3.1 创建被增强类及方法](#331-创建被增强类及方法) + - [3.3.2 创建切面类(写方法增强逻辑的地方)](#332-创建切面类写方法增强逻辑的地方) + - [3.3.3 加载bean到Spring容器并开启Aspect](#333-加载bean到spring容器并开启aspect) + - [3.3.4 测试机结果](#334-测试机结果) + - [3.3.5 通知的执行顺序](#335-通知的执行顺序) + - [3.3.6 设置增强类的加载优先级](#336-设置增强类的加载优先级) + +# 一. AOP基础概念 + +## 1.1 AOP? + +**AOP面向切面编程**,可以``不修改源代码进行方法增强``,AOP是OOP(面向对象编程)的延续,主要用于日志记录、性能统计、安全控制、事务处理等方面。它是基于``代理设计模式``,而代理设计模式又分为静态代理和``动态代理``,静态代理比较简单就是一个接口,分别由一个真实实现和一个代理实现,而动态代理分为基于接口的``JDK动态代理``和基于类的``cglib的动态代理``,咱们正常都是面向接口开发,所以AOP使用的是基于接口的JDK动态代理。 + +## 1.2 AOP中的一些常用概念 + +- **切面(Aspect)**:AOP核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在Spring AOP中,切面通过带有@Aspect注解的类实现。 +- **连接点(Join Point)**:哪些方法需要被AOP增强,这些方法就叫做连接点。 +- **通知(Advice)**:AOP在特定的切入点上执行的增强处理,有``before``, ``after``,`` afterReturning``, ``afterThrowing``, ``around`` +- **切入点(Pointcut)**:实际真正被增强的方法,称为切入点。 + +## 1.3 通知类型 + +通知(advice)是你在你的程序中想要应用在其他模块中的横切关注点的实现。Advice主要有以下5种类型: + +- **前置通知(Before Advice)**:在连接点之前执行的Advice,不过除非它抛出异常,否则没有能力中断执行流。使用``@Before`` 注解使用这个Advice。 + +- **返回之后通知(After Retuning Advice)**:在连接点正常结束之后执行的Advice。例如,如果一个方法没有抛出异常正常返回。通过 ``@AfterReturning`` 关注使用它。 + +- **抛出(异常)后执行通知(After Throwing Advice)**:如果一个方法通过抛出异常来退出的话,这个Advice就会被执行。通过 ``@AfterThrowing`` 注解来使用。 + +- **后置通知(After Advice)**:无论连接点是通过什么方式退出的(正常返回或者抛出异常)都会执行在结束后执行这些Advice。通过 ``@After`` 注解使用。 + +- **围绕通知(Around Advice)**:围绕连接点执行的Advice,就你一个方法调用。这是最强大的Advice。通过``@Around`` 注解使用。 + + + +# 二. AOP底层实现 + +## 2.1 AOP底层原理 + +它是基于代理设计模式,而代理设计模式又分为静态代理和动态代理,静态代理比较简单就是一个接口,分别由一个真实实现和一个代理实现,而动态代理分为基于接口的JDK动态代理和基于类的CGLIB的动态代理。 + +**第一种 有接口情况,使用 JDK 动态代理** + +创建接口实现类代理对象,增强类的方法 + +![image-20210819142955341](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819142955341.png) + +JDK动态代理是去创建一个UserDao接口的实现类的代理对象,该接口实现类的代理对象会调用该接口的真实实现,并且在代理对象中调用真实实现类的前后做方法增强 + +**第二种 没有接口情况,使用 CGLIB 动态代理** + +创建子类的代理对象,增强类的方法 + +![image-20210819143018532](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819143018532.png) + +CGLIB动态代理是去创建一个User类子类的代理对象,该子类的代理对象会去调用父类User中的方法,并且在子类代理对象调用其父类方法签后去做增强 + +## 2.2 代理概述 + +代理就是在不修改源代码的情况下使得原本不具备某种行为能力的类、对象具有该种行为能力,实现对目标对象的功能扩展 + +**代理的应用场景** + +- 事务处理 +- 权限管理 +- 日志收集 +- AOP切面 +- ......... + +**Java的代理分为静态代理和动态代理** + +静态代理的局限性:只能代理某一类型接口的实例,不能代理任意接口任意方法的操作。 + +静态代理只能代理固定或单一接口的方法,也就是说不能做到任何类任何方法的代理。 + +## 2.3 静态代理实现 + +**Movie 接口的实现** + +```java +/** + * 委托类的父接口 + */ +public interface Movie { + void player(); +} +``` + +**实现了Movie 接口的 真实实现类(委托类)**: + +```java +public class RealMovie implements Movie { + @Override + public void player() { + System.out.println(">>>>>>>> 您正在观看《士兵突击》"); + } +} +``` + +**实现了Movie 接口的 代理实现类**: + +```java +public class Cinema implements Movie { + + RealMovie realMovie; + + public Cinema(RealMovie realmovie) { + this.realMovie = realmovie; + } + + public void player() { + //对目标方法进行方法增强 + System.out.println("|||||||||||||||||||||||电影开始前,卖爆米花"); + + //执行真实实现的目标方法 + realMovie.player(); + + //对目标方法进行方法增强 + System.out.println("----------------------电影结束了,打扫卫生"); + } + + +} +``` + +**具体的调用如下**: + +```java +public class ProxyTest { + public static void main(String[] args) { + //创建电影院(静态代理) + Cinema cinema = new Cinema(new RealMovie()); + cinema.player(); + } +} +``` + +**使用静态代理的好处:** + +使得真实角色处理的业务更加纯粹,不再去关注一些公共的事情。 + +公共的业务由代理来完成---实现业务的分工。 + +公共业务发生扩展时变得更加集中和方便。 + +**缺点**:每一个代理类都必须实现一遍真实 实现类(也就是realMovie)的接口,如果接口增加方法,则代理类也必须跟着修改。其次,每一个代理类对应一个真实实现类(委托类),如果真实实现(委托类)非常多,则静态代理类就非常臃肿,难以胜任。 + +## 2.3 JDK动态代理实现 + +动态代理有别于静态代理,是根据代理的对象,动态创建代理类。这样,就可以避免静态代理中代理类接口过多的问题。动态代理是通过反射来实现的,借助Java自带的java.lang.reflect.Proxy,通过固定的规则生成。 + +其步骤如下: + +1. 创建一个需要动态代理的接口,即Movie接口 +2. 创建一个需要动态代理接口的真实实现,即RealMovie类 +3. 创建一个动态代理处理器,实现``InvocationHandler接口``,并重写invoke方法去增强真实实现中的目标方法 +4. 在测试类中,生成动态代理的对象。 + +**第一二步骤,和静态代理一样,不过说了。第三步,代码如下**: + +```java +/** + * 动态代理处理类 + */ +public class MyInvocationHandler implements InvocationHandler { + + //需要动态代理接口的真实实现类 + private Object object; + + //通过构造方法去给需要动态代理接口的真实实现类赋值 + public MyInvocationHandler(Object object) { + this.object = object; + } + + + /** + * 对真实实现(被代理对象)的目标方法进行增强 + * 当代理对象调用真实实现类的方法时,就会执行动态代理处理器中的该invoke方法 + * + * @param proxy 生成的代理对象 + * @param method 代理对象调用的方法 + * @param args 调用的方法中的参数 + * @return + * @throws Throwable + */ + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + //方法增强 + System.out.println("卖爆米花"); + + //object是真实实现,args是调用方法的参数 + //当代理对象调用真实实现的方法,那么这里就会将真实实现和方法参数传递过去,去调用真实实现的方法 + method.invoke(object,args); + + //方法增强 + System.out.println("扫地"); + return null; + } +} +``` + +**第四步,创建动态代理的对象** + +```java +public class DynamicProxyTest { + + public static void main(String[] args) { + // 保存生成的代理类的字节码文件 + //由于设置sun.misc.ProxyGenerator.saveGeneratedFiles 的值为true,所以代理类的字节码内容保存在了项目根目录下,文件名为$Proxy0.class + System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); + + //需要动态代理接口的真实实现 + RealMovie realMovie = new RealMovie(); + //动态代理处理类 + MyInvocationHandler handler = new MyInvocationHandler(realMovie); + //获取动态代理对象 + //第一个参数:真实实现(被代理对象)的类加载器 + //第二个参数:真实实现类(被代理对象)它所实现的所有接口的数组 + //第三个参数:动态代理处理器 + Movie movie = (Movie) Proxy.newProxyInstance(realMovie.getClass().getClassLoader(), + realMovie.getClass().getInterfaces(), + handler); + movie.player(); + } + +} +``` + +**结果** + +![image-20210819143321623](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819143321623.png) + +由于设置 sun.misc.ProxyGenerator.saveGeneratedFiles 的值为true,所以代理类的字节码内容保存在了项目根目录下,文件名为$Proxy0.class + +![image-20210819143343829](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819143343829.png) + + + +# 三. Spring AOP的操作 + +## 3.1 什么是AspectJ? + +### 3.1.1 在Spring框架中,一般都是基于AspectJ来实现AOP的相关操作 + +AspectJ并不是Spring的组成部分,它是独立的AOP框架(不需要Spring也能独立使用), + +所以我们一般把AspectJ和Spring框架一起使用,去进行一些AOP操作。 + +### 3.1.2 基于AspectJ实现AOP操作 + +- 基于XML配置文件实现 +- 基于注解方式实现(常用、方便) + +## 3.2 准备工作 + +### 3.2.1 引入Spring AOP相关依赖 + +在原有的依赖的基础上添加jar包(包括AspectJ) + +![image-20210819143538837](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819143538837.png) + +### 3.2.2 切入点表达式了解 + +**切入点表达式的作用**:用于表达对哪个类里面的哪个方法(切入点)进行增强 + +**语法结构**: + +```java +举例 1:对 com.eayon.dao.BookDao 类里面的 add 进行增强 execution(* com.dao.dao.BookDao.add(..)) +举例 2:对 com.eayon.dao.BookDao 类里面的所有的方法进行增强execution(* com.dao.dao.BookDao.* (..)) +举例 3:对 com.eayon.dao 包里面所有类,类里面所有方法进行增强execution(* com.dao.dao.*.* (..)) +``` + +**PS**:上面举例三个都是去切具体的某个方法、类。我们也可以去切到某个包下所有的方法,也可以去切某包下带有某注解的方法等等。 + +**常见的切入点表达式示例** + +- 所有方法执行 + + ```java + execution(public * *(..)) + ``` + +- 名称以"set"开头的所有方法执行 + + ```java + execution(* set*(..)) + ``` + +- AccountService接口中的所有方法执行 + + ```java + execution(* com.eayon.service.AccountService.*(..)) + ``` + +- service包下所有方法执行 + + ```java + execution(* com.eayon.service..*.*(..)) + ``` + +- service包下的所有连接点(仅在Spring AOP中执行方法) + + ```java + within(com.eayon.service..*) + ``` + +- 代理实现AccountService接口的任何连接点(仅在Spring AOP中执行方法) + + ```java + this(com.eayon.service.AccountService) + ``` + +- 所有带有@checkLogin注解的方法或类 + + ```java + @annotation(com.eayon.annotation.checkLogin) + ``` + +## 3.3 AOP操作 - 基于AspectJ注解 + +### 3.3.1 创建被增强类及方法 + +```java +package com.eayon.aop; +import org.springframework.stereotype.Component; + +/** + * @Description AOP被增强类 + */ +@Component//通过IOC中的注解将该类实例化到Spring容器 +public class Car { + + //汽车前进方法 + public void forward(String carName){ + System.out.println(carName + "牌汽车前进了"); + } + + //汽车后退方法 + public void backoff(String carName){ + System.out.println(carName + "牌汽车后退了"); + } +} +``` + +### 3.3.2 创建切面类(写方法增强逻辑的地方) + +```java +package com.eayon.aspect; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.*; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +@Component// 通过IOC中的注解将该类实例化到Spring容器 +@Aspect// 声明切面类,并为本类生成代理对象 +public class CarAspect { + + /** + * 相同切入点抽取 + */ + @Pointcut(value = "execution(* com.eayon.aop.Car.*(..))") + public void pointCut(){ + + } + + /** + * 前置通知 + * @Before注解表示作为前置通知 + */ + //@@Before(value = "execution(* com.eayon.aop.Car.*(..))") + @Before(value = "pointCut()")//抽取切入点 + public void before(){ + System.out.println("Before..."); + } + + /** + * 后置通知(返回通知) + */ + //@AfterReturning + @AfterReturning(value = "pointCut()") + public void afterReturning(){ + System.out.println("AfterReturning..."); + } + + /** + * 异常通知 + */ + // + @AfterThrowing(value = "pointCut()") + public void afterThrowing(){ + System.out.println("AfterThrowing..."); + } + + /** + * 最终通知 + */ + //@After + @After(value = "pointCut()") + public void after(){ + System.out.println("After..."); + } + + + /** + * @Around 代表环绕通知 value代表切入点,即Car类中的所有方法 + * 同理 你想用其他通知只需要变更注解就可以了 value都是一个意思 + * @Before 前置通知注解 + * @After 后置通知注解 + * @AfterThrowing 抛出(异常)后执行通知注解 + * @AfterReturning 返回之后通知注解 + * + * @return + */ + //@Around + @Around(value = "pointCut()") + public Object before(ProceedingJoinPoint point) throws Throwable { + //获取切点方法上的参数 + Object[] args = point.getArgs(); + + //进行方法增强 修改参数值 + if(null != args && args.length> 0){ + //原来的参数值 + Object carName = args[0]; + System.out.println("Around 原来的参数值" + carName); + + //更换参数 + args[0] = "奔驰"; + } + + //继续执行切点方法 并使用更换后的参数 + return point.proceed(args); + } +} +``` + +### 3.3.3 加载bean到Spring容器并开启Aspect + +**XML方式** + +```xml + + + + + + + + +``` + +**配置类方式** + +```java +package com.eayon.conf; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +/** + * @Description Spring配置类 用于扫描带有注解的类将其实例化到IOC容器 + * 开启Aspect生成代理对象 也就是扫描带有@Aspect注解的类并生成代理对象 + */ +@Configuration//声明当前类是配置类并加载到IOC容器 +@ComponentScan(basePackages = {"com.eayon"})//开启组件扫描 扫描com.eayon包下所有带有注解(@Component @Service等)的类 然后去实例化 +@EnableAspectJAutoProxy(proxyTargetClass = true)//开启Aspect生成代理对象 也就是扫描带有@Aspect注解的类并生成代理对象 +public class SpringConf { + +} +``` + +### 3.3.4 测试机结果 + +```java +package com.eayon.demo; + +public class AopTest { + + @Test + public void test_car_aop() { + // 加载配置类实例化所有bean、并开启Aspect生成代理对象 也就是扫描带有@Aspect注解的类并生成代理对象 + // ApplicationContext context = new ClassPathXmlApplicationContext("spring_conf.xml");//加载配置文件,效果一样 + ApplicationContext context = new AnnotationConfigApplicationContext(SpringConf.class);//加载配置类,效果一样 + Car car = context.getBean("car", Car.class); + car.forward("奥迪"); + } +} +``` + +![image-20210819144011637](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819144011637.png) + +### 3.3.5 通知的执行顺序 + +``环绕通知 -> 前置通知 -> 目标方法 -> 后置通知 -> 最终通知`` + +抛出异常通知随时可能执行,根据异常触发决定 + +### 3.3.6 设置增强类的加载优先级 + +有多个增强类对同一个方法进行增强,可设置增强类的加载优先级。 + +**举例**:比如上面有一个CarAspect增强类对User类中的方法进行增强,现在有一个CarAspect2增强类也对User类中的方法进行增强,那么哪个肯定是哪个增强类先被加载,则先执行哪个增强类。所以我们可以通过在增强类上面添加注解 ``@Order(数字类型值)``进行设置类的加载优先级,数字类型值越小优先级越高 + +```java +@Order(1)//加载优先级 +@Component // 通过IOC中的注解将该类实例化到Spring容器 +@Aspect // 声明切面类,并为本类生成代理对象 +public class CarAspect { + .....省略..... +} +``` \ No newline at end of file diff --git "a/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/02.AOP/README.md" "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/02.AOP/README.md" new file mode 100644 index 0000000..cac038d --- /dev/null +++ "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/02.AOP/README.md" @@ -0,0 +1,549 @@ +# Spring AOP 概念及实战 + +# 说在前面 + +- **本章相关代码及笔记地址**:飞机票🚀 +- **🌍**Github:**[**🚀Java超神之路:【🍔Java全生态技术学习笔记,一起超神吧🍔】](https://github.com/EayonLee/JavaGod) +- **🪐**CSDN:**[**🚀Java超神之路:【🍔Java全生态技术学习笔记,一起超神吧🍔】](https://blog.csdn.net/qq_20492277/article/details/114269863) + +# 目录 +- [Spring AOP 概念及实战](#spring-aop-概念及实战) +- [说在前面](#说在前面) +- [目录](#目录) +- [一. AOP基础概念](#一-aop基础概念) + - [1.1 AOP?](#11-aop) + - [1.2 AOP中的一些常用概念](#12-aop中的一些常用概念) + - [1.3 通知类型](#13-通知类型) +- [二. AOP底层实现](#二-aop底层实现) + - [2.1 AOP底层原理](#21-aop底层原理) + - [2.2 代理概述](#22-代理概述) + - [2.3 静态代理实现](#23-静态代理实现) + - [2.3 JDK动态代理实现](#23-jdk动态代理实现) +- [三. Spring AOP的操作](#三-spring-aop的操作) + - [3.1 什么是AspectJ?](#31-什么是aspectj) + - [3.1.1 在Spring框架中,一般都是基于AspectJ来实现AOP的相关操作](#311-在spring框架中一般都是基于aspectj来实现aop的相关操作) + - [3.1.2 基于AspectJ实现AOP操作](#312-基于aspectj实现aop操作) + - [3.2 准备工作](#32-准备工作) + - [3.2.1 引入Spring AOP相关依赖](#321-引入spring-aop相关依赖) + - [3.2.2 切入点表达式了解](#322-切入点表达式了解) + - [3.3 AOP操作 - 基于AspectJ注解](#33-aop操作---基于aspectj注解) + - [3.3.1 创建被增强类及方法](#331-创建被增强类及方法) + - [3.3.2 创建切面类(写方法增强逻辑的地方)](#332-创建切面类写方法增强逻辑的地方) + - [3.3.3 加载bean到Spring容器并开启Aspect](#333-加载bean到spring容器并开启aspect) + - [3.3.4 测试机结果](#334-测试机结果) + - [3.3.5 通知的执行顺序](#335-通知的执行顺序) + - [3.3.6 设置增强类的加载优先级](#336-设置增强类的加载优先级) + +# 一. AOP基础概念 + +## 1.1 AOP? + +**AOP面向切面编程**,可以``不修改源代码进行方法增强``,AOP是OOP(面向对象编程)的延续,主要用于日志记录、性能统计、安全控制、事务处理等方面。它是基于``代理设计模式``,而代理设计模式又分为静态代理和``动态代理``,静态代理比较简单就是一个接口,分别由一个真实实现和一个代理实现,而动态代理分为基于接口的``JDK动态代理``和基于类的``cglib的动态代理``,咱们正常都是面向接口开发,所以AOP使用的是基于接口的JDK动态代理。 + +## 1.2 AOP中的一些常用概念 + +- **切面(Aspect)**:AOP核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在Spring AOP中,切面通过带有@Aspect注解的类实现。 +- **连接点(Join Point)**:哪些方法需要被AOP增强,这些方法就叫做连接点。 +- **通知(Advice)**:AOP在特定的切入点上执行的增强处理,有``before``, ``after``,`` afterReturning``, ``afterThrowing``, ``around`` +- **切入点(Pointcut)**:实际真正被增强的方法,称为切入点。 + +## 1.3 通知类型 + +通知(advice)是你在你的程序中想要应用在其他模块中的横切关注点的实现。Advice主要有以下5种类型: + +- **前置通知(Before Advice)**:在连接点之前执行的Advice,不过除非它抛出异常,否则没有能力中断执行流。使用``@Before`` 注解使用这个Advice。 + +- **返回之后通知(After Retuning Advice)**:在连接点正常结束之后执行的Advice。例如,如果一个方法没有抛出异常正常返回。通过 ``@AfterReturning`` 关注使用它。 + +- **抛出(异常)后执行通知(After Throwing Advice)**:如果一个方法通过抛出异常来退出的话,这个Advice就会被执行。通过 ``@AfterThrowing`` 注解来使用。 + +- **后置通知(After Advice)**:无论连接点是通过什么方式退出的(正常返回或者抛出异常)都会执行在结束后执行这些Advice。通过 ``@After`` 注解使用。 + +- **围绕通知(Around Advice)**:围绕连接点执行的Advice,就你一个方法调用。这是最强大的Advice。通过``@Around`` 注解使用。 + + + +# 二. AOP底层实现 + +## 2.1 AOP底层原理 + +它是基于代理设计模式,而代理设计模式又分为静态代理和动态代理,静态代理比较简单就是一个接口,分别由一个真实实现和一个代理实现,而动态代理分为基于接口的JDK动态代理和基于类的CGLIB的动态代理。 + +**第一种 有接口情况,使用 JDK 动态代理** + +创建接口实现类代理对象,增强类的方法 + +![image-20210819142955341](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819142955341.png) + +JDK动态代理是去创建一个UserDao接口的实现类的代理对象,该接口实现类的代理对象会调用该接口的真实实现,并且在代理对象中调用真实实现类的前后做方法增强 + +**第二种 没有接口情况,使用 CGLIB 动态代理** + +创建子类的代理对象,增强类的方法 + +![image-20210819143018532](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819143018532.png) + +CGLIB动态代理是去创建一个User类子类的代理对象,该子类的代理对象会去调用父类User中的方法,并且在子类代理对象调用其父类方法签后去做增强 + +## 2.2 代理概述 + +代理就是在不修改源代码的情况下使得原本不具备某种行为能力的类、对象具有该种行为能力,实现对目标对象的功能扩展 + +**代理的应用场景** + +- 事务处理 +- 权限管理 +- 日志收集 +- AOP切面 +- ......... + +**Java的代理分为静态代理和动态代理** + +静态代理的局限性:只能代理某一类型接口的实例,不能代理任意接口任意方法的操作。 + +静态代理只能代理固定或单一接口的方法,也就是说不能做到任何类任何方法的代理。 + +## 2.3 静态代理实现 + +**Movie 接口的实现** + +```java +/** + * 委托类的父接口 + */ +public interface Movie { + void player(); +} +``` + +**实现了Movie 接口的 真实实现类(委托类)**: + +```java +public class RealMovie implements Movie { + @Override + public void player() { + System.out.println(">>>>>>>> 您正在观看《士兵突击》"); + } +} +``` + +**实现了Movie 接口的 代理实现类**: + +```java +public class Cinema implements Movie { + + RealMovie realMovie; + + public Cinema(RealMovie realmovie) { + this.realMovie = realmovie; + } + + public void player() { + //对目标方法进行方法增强 + System.out.println("|||||||||||||||||||||||电影开始前,卖爆米花"); + + //执行真实实现的目标方法 + realMovie.player(); + + //对目标方法进行方法增强 + System.out.println("----------------------电影结束了,打扫卫生"); + } + + +} +``` + +**具体的调用如下**: + +```java +public class ProxyTest { + public static void main(String[] args) { + //创建电影院(静态代理) + Cinema cinema = new Cinema(new RealMovie()); + cinema.player(); + } +} +``` + +**使用静态代理的好处:** + +使得真实角色处理的业务更加纯粹,不再去关注一些公共的事情。 + +公共的业务由代理来完成---实现业务的分工。 + +公共业务发生扩展时变得更加集中和方便。 + +**缺点**:每一个代理类都必须实现一遍真实 实现类(也就是realMovie)的接口,如果接口增加方法,则代理类也必须跟着修改。其次,每一个代理类对应一个真实实现类(委托类),如果真实实现(委托类)非常多,则静态代理类就非常臃肿,难以胜任。 + +## 2.3 JDK动态代理实现 + +动态代理有别于静态代理,是根据代理的对象,动态创建代理类。这样,就可以避免静态代理中代理类接口过多的问题。动态代理是通过反射来实现的,借助Java自带的java.lang.reflect.Proxy,通过固定的规则生成。 + +其步骤如下: + +1. 创建一个需要动态代理的接口,即Movie接口 +2. 创建一个需要动态代理接口的真实实现,即RealMovie类 +3. 创建一个动态代理处理器,实现``InvocationHandler接口``,并重写invoke方法去增强真实实现中的目标方法 +4. 在测试类中,生成动态代理的对象。 + +**第一二步骤,和静态代理一样,不过说了。第三步,代码如下**: + +```java +/** + * 动态代理处理类 + */ +public class MyInvocationHandler implements InvocationHandler { + + //需要动态代理接口的真实实现类 + private Object object; + + //通过构造方法去给需要动态代理接口的真实实现类赋值 + public MyInvocationHandler(Object object) { + this.object = object; + } + + + /** + * 对真实实现(被代理对象)的目标方法进行增强 + * 当代理对象调用真实实现类的方法时,就会执行动态代理处理器中的该invoke方法 + * + * @param proxy 生成的代理对象 + * @param method 代理对象调用的方法 + * @param args 调用的方法中的参数 + * @return + * @throws Throwable + */ + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + //方法增强 + System.out.println("卖爆米花"); + + //object是真实实现,args是调用方法的参数 + //当代理对象调用真实实现的方法,那么这里就会将真实实现和方法参数传递过去,去调用真实实现的方法 + method.invoke(object,args); + + //方法增强 + System.out.println("扫地"); + return null; + } +} +``` + +**第四步,创建动态代理的对象** + +```java +public class DynamicProxyTest { + + public static void main(String[] args) { + // 保存生成的代理类的字节码文件 + //由于设置sun.misc.ProxyGenerator.saveGeneratedFiles 的值为true,所以代理类的字节码内容保存在了项目根目录下,文件名为$Proxy0.class + System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); + + //需要动态代理接口的真实实现 + RealMovie realMovie = new RealMovie(); + //动态代理处理类 + MyInvocationHandler handler = new MyInvocationHandler(realMovie); + //获取动态代理对象 + //第一个参数:真实实现(被代理对象)的类加载器 + //第二个参数:真实实现类(被代理对象)它所实现的所有接口的数组 + //第三个参数:动态代理处理器 + Movie movie = (Movie) Proxy.newProxyInstance(realMovie.getClass().getClassLoader(), + realMovie.getClass().getInterfaces(), + handler); + movie.player(); + } + +} +``` + +**结果** + +![image-20210819143321623](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819143321623.png) + +由于设置 sun.misc.ProxyGenerator.saveGeneratedFiles 的值为true,所以代理类的字节码内容保存在了项目根目录下,文件名为$Proxy0.class + +![image-20210819143343829](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819143343829.png) + + + +# 三. Spring AOP的操作 + +## 3.1 什么是AspectJ? + +### 3.1.1 在Spring框架中,一般都是基于AspectJ来实现AOP的相关操作 + +AspectJ并不是Spring的组成部分,它是独立的AOP框架(不需要Spring也能独立使用), + +所以我们一般把AspectJ和Spring框架一起使用,去进行一些AOP操作。 + +### 3.1.2 基于AspectJ实现AOP操作 + +- 基于XML配置文件实现 +- 基于注解方式实现(常用、方便) + +## 3.2 准备工作 + +### 3.2.1 引入Spring AOP相关依赖 + +在原有的依赖的基础上添加jar包(包括AspectJ) + +![image-20210819143538837](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819143538837.png) + +### 3.2.2 切入点表达式了解 + +**切入点表达式的作用**:用于表达对哪个类里面的哪个方法(切入点)进行增强 + +**语法结构**: + +```java +举例 1:对 com.eayon.dao.BookDao 类里面的 add 进行增强 execution(* com.dao.dao.BookDao.add(..)) +举例 2:对 com.eayon.dao.BookDao 类里面的所有的方法进行增强execution(* com.dao.dao.BookDao.* (..)) +举例 3:对 com.eayon.dao 包里面所有类,类里面所有方法进行增强execution(* com.dao.dao.*.* (..)) +``` + +**PS**:上面举例三个都是去切具体的某个方法、类。我们也可以去切到某个包下所有的方法,也可以去切某包下带有某注解的方法等等。 + +**常见的切入点表达式示例** + +- 所有方法执行 + + ```java + execution(public * *(..)) + ``` + +- 名称以"set"开头的所有方法执行 + + ```java + execution(* set*(..)) + ``` + +- AccountService接口中的所有方法执行 + + ```java + execution(* com.eayon.service.AccountService.*(..)) + ``` + +- service包下所有方法执行 + + ```java + execution(* com.eayon.service..*.*(..)) + ``` + +- service包下的所有连接点(仅在Spring AOP中执行方法) + + ```java + within(com.eayon.service..*) + ``` + +- 代理实现AccountService接口的任何连接点(仅在Spring AOP中执行方法) + + ```java + this(com.eayon.service.AccountService) + ``` + +- 所有带有@checkLogin注解的方法或类 + + ```java + @annotation(com.eayon.annotation.checkLogin) + ``` + +## 3.3 AOP操作 - 基于AspectJ注解 + +### 3.3.1 创建被增强类及方法 + +```java +package com.eayon.aop; +import org.springframework.stereotype.Component; + +/** + * @Description AOP被增强类 + */ +@Component//通过IOC中的注解将该类实例化到Spring容器 +public class Car { + + //汽车前进方法 + public void forward(String carName){ + System.out.println(carName + "牌汽车前进了"); + } + + //汽车后退方法 + public void backoff(String carName){ + System.out.println(carName + "牌汽车后退了"); + } +} +``` + +### 3.3.2 创建切面类(写方法增强逻辑的地方) + +```java +package com.eayon.aspect; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.*; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +@Component// 通过IOC中的注解将该类实例化到Spring容器 +@Aspect// 声明切面类,并为本类生成代理对象 +public class CarAspect { + + /** + * 相同切入点抽取 + */ + @Pointcut(value = "execution(* com.eayon.aop.Car.*(..))") + public void pointCut(){ + + } + + /** + * 前置通知 + * @Before注解表示作为前置通知 + */ + //@@Before(value = "execution(* com.eayon.aop.Car.*(..))") + @Before(value = "pointCut()")//抽取切入点 + public void before(){ + System.out.println("Before..."); + } + + /** + * 后置通知(返回通知) + */ + //@AfterReturning + @AfterReturning(value = "pointCut()") + public void afterReturning(){ + System.out.println("AfterReturning..."); + } + + /** + * 异常通知 + */ + // + @AfterThrowing(value = "pointCut()") + public void afterThrowing(){ + System.out.println("AfterThrowing..."); + } + + /** + * 最终通知 + */ + //@After + @After(value = "pointCut()") + public void after(){ + System.out.println("After..."); + } + + + /** + * @Around 代表环绕通知 value代表切入点,即Car类中的所有方法 + * 同理 你想用其他通知只需要变更注解就可以了 value都是一个意思 + * @Before 前置通知注解 + * @After 后置通知注解 + * @AfterThrowing 抛出(异常)后执行通知注解 + * @AfterReturning 返回之后通知注解 + * + * @return + */ + //@Around + @Around(value = "pointCut()") + public Object before(ProceedingJoinPoint point) throws Throwable { + //获取切点方法上的参数 + Object[] args = point.getArgs(); + + //进行方法增强 修改参数值 + if(null != args && args.length> 0){ + //原来的参数值 + Object carName = args[0]; + System.out.println("Around 原来的参数值" + carName); + + //更换参数 + args[0] = "奔驰"; + } + + //继续执行切点方法 并使用更换后的参数 + return point.proceed(args); + } +} +``` + +### 3.3.3 加载bean到Spring容器并开启Aspect + +**XML方式** + +```xml + + + + + + + + +``` + +**配置类方式** + +```java +package com.eayon.conf; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +/** + * @Description Spring配置类 用于扫描带有注解的类将其实例化到IOC容器 + * 开启Aspect生成代理对象 也就是扫描带有@Aspect注解的类并生成代理对象 + */ +@Configuration//声明当前类是配置类并加载到IOC容器 +@ComponentScan(basePackages = {"com.eayon"})//开启组件扫描 扫描com.eayon包下所有带有注解(@Component @Service等)的类 然后去实例化 +@EnableAspectJAutoProxy(proxyTargetClass = true)//开启Aspect生成代理对象 也就是扫描带有@Aspect注解的类并生成代理对象 +public class SpringConf { + +} +``` + +### 3.3.4 测试机结果 + +```java +package com.eayon.demo; + +public class AopTest { + + @Test + public void test_car_aop() { + // 加载配置类实例化所有bean、并开启Aspect生成代理对象 也就是扫描带有@Aspect注解的类并生成代理对象 + // ApplicationContext context = new ClassPathXmlApplicationContext("spring_conf.xml");//加载配置文件,效果一样 + ApplicationContext context = new AnnotationConfigApplicationContext(SpringConf.class);//加载配置类,效果一样 + Car car = context.getBean("car", Car.class); + car.forward("奥迪"); + } +} +``` + +![image-20210819144011637](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819144011637.png) + +### 3.3.5 通知的执行顺序 + +``环绕通知 -> 前置通知 -> 目标方法 -> 后置通知 -> 最终通知`` + +抛出异常通知随时可能执行,根据异常触发决定 + +### 3.3.6 设置增强类的加载优先级 + +有多个增强类对同一个方法进行增强,可设置增强类的加载优先级。 + +**举例**:比如上面有一个CarAspect增强类对User类中的方法进行增强,现在有一个CarAspect2增强类也对User类中的方法进行增强,那么哪个肯定是哪个增强类先被加载,则先执行哪个增强类。所以我们可以通过在增强类上面添加注解 ``@Order(数字类型值)``进行设置类的加载优先级,数字类型值越小优先级越高 + +```java +@Order(1)//加载优先级 +@Component // 通过IOC中的注解将该类实例化到Spring容器 +@Aspect // 声明切面类,并为本类生成代理对象 +public class CarAspect { + .....省略..... +} +``` \ No newline at end of file diff --git "a/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/03.JdbcTemplate/01.350円257円276円347円250円213円350円265円204円346円226円231円/jdbcTemplate347円232円204円jar345円214円205円.zip" "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/03.JdbcTemplate/01.350円257円276円347円250円213円350円265円204円346円226円231円/jdbcTemplate347円232円204円jar345円214円205円.zip" new file mode 100644 index 0000000..d8eccc5 Binary files /dev/null and "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/03.JdbcTemplate/01.350円257円276円347円250円213円350円265円204円346円226円231円/jdbcTemplate347円232円204円jar345円214円205円.zip" differ diff --git "a/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/03.JdbcTemplate/02.350円257円276円347円250円213円347円254円224円350円256円260円/Spring JdbcTemplate346円246円202円345円277円265円345円217円212円345円256円236円346円210円230円.md" "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/03.JdbcTemplate/02.350円257円276円347円250円213円347円254円224円350円256円260円/Spring JdbcTemplate346円246円202円345円277円265円345円217円212円345円256円236円346円210円230円.md" new file mode 100644 index 0000000..4322668 --- /dev/null +++ "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/03.JdbcTemplate/02.350円257円276円347円250円213円347円254円224円350円256円260円/Spring JdbcTemplate346円246円202円345円277円265円345円217円212円345円256円236円346円210円230円.md" @@ -0,0 +1,726 @@ +# Spring JdbcTemplate概念及实战 + +# 说在前面 + +- **本章相关代码及笔记地址**:飞机票🚀 +- **🌍**Github:**[**🚀Java超神之路:【🍔Java全生态技术学习笔记,一起超神吧🍔】](https://github.com/EayonLee/JavaGod) +- **🪐**CSDN:**[**🚀Java超神之路:【🍔Java全生态技术学习笔记,一起超神吧🍔】](https://blog.csdn.net/qq_20492277/article/details/114269863) + +# 目录 +- [Spring AOP JdbcTemplate概念及实战](#spring-aop--jdbctemplate概念及实战) +- [说在前面](#说在前面) +- [目录](#目录) +- [一. Spring JDBC概述](#一-spring-jdbc概述) + - [1.1 什么是JDBC Template](#11-什么是jdbc-template) + - [1.2 Spring JDBC能做什么](#12-spring-jdbc能做什么) +- [二. 使用JdbcTemplate](#二-使用jdbctemplate) + - [2.1 新建Module,引入依赖](#21-新建module引入依赖) + - [2.2 配置数据库连接池 / JdbcTemplate](#22-配置数据库连接池--jdbctemplate) + - [2.2.1 XML方式](#221-xml方式) + - [2.2.2 配置类方式](#222-配置类方式) + - [2.3 数据库表](#23-数据库表) + - [2.4 实体类](#24-实体类) + - [2.5 Service](#25-service) + - [2.6 Dao](#26-dao) + - [2.6.1 Dao接口](#261-dao接口) + - [2.6.2 Dao实现类](#262-dao实现类) + - [2.7 测试](#27-测试) +- [三. Spring事务管理](#三-spring事务管理) + - [3.1 什么是事务?](#31-什么是事务) + - [3.1.1 事务的定义](#311-事务的定义) + - [3.1.2 事务的四大特征(ACID)](#312-事务的四大特征acid) + - [3.1.3 Spring事务管理](#313-spring事务管理) + - [3.1.3.1 编程式事务管理](#3131-编程式事务管理) + - [3.1.3.2 声明式事务管理](#3132-声明式事务管理) + - [3.2 搭建事务操作环境](#32-搭建事务操作环境) + - [3.2.1 Service](#321-service) + - [3.2.2 Dao](#322-dao) + - [3.2.3 测试](#323-测试) + - [3.2.4 事务问题引入](#324-事务问题引入) + - [3.3 事务管理](#33-事务管理) + - [3.3.1 声明式事务管理的实现方式](#331-声明式事务管理的实现方式) + - [3.3.2 PlatformTransactionManager事务管理器](#332-platformtransactionmanager事务管理器) + - [3.3.3 声明式事务管理注解方式配置 - XML文件](#333-声明式事务管理注解方式配置---xml文件) + - [3.3.4 声明式事务管理注解方式配置 - 配置类](#334-声明式事务管理注解方式配置---配置类) + - [3.3.5 开启事务管理](#335-开启事务管理) + - [3.3.6 测试](#336-测试) + - [3.3.7 @Transactional注解中的参数](#337-transactional注解中的参数) + - [3.4 事务的7中传播行为](#34-事务的7中传播行为) + - [3.5 事务的隔离级别](#35-事务的隔离级别) + - [3.5.1 并发情况下事务会出现的问题](#351-并发情况下事务会出现的问题) + - [3.5.2 隔离级别](#352-隔离级别) + + +# 一. Spring JDBC概述 + +## 1.1 什么是JDBC Template + +它是 Spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装。除了JdbcTemplate,Spring 框架还为我们提供了很多的操作模板类。 + +**操作关系型数据的**:JdbcTemplate和HibernateTemplate。 + +**操作 nosql 数据库的**:RedisTemplate。 + +**操作消息队列的**:JmsTemplate。 + +## 1.2 Spring JDBC能做什么 + +| **操作项目** | **Spring帮你做的** | **你需要做的** | +| ---------------------------- | ------------------ | -------------- | +| 定义连接参数 | | ✔ | +| 打开连接 | ✔ | | +| 指定SQL语句 | | ✔ | +| 声明参数并提供参数值 | | ✔ | +| 准备并运行该语句 | ✔ | | +| 设置循环以遍历结果(如果有) | ✔ | | +| 进行每次迭代的工作 | | ✔ | +| 处理任何异常 | ✔ | | +| 处理交易 | ✔ | | +| 关闭连接,语句和结果集 | ✔ | | + + + +# 二. 使用JdbcTemplate + +## 2.1 新建Module,引入依赖 + +![image-20210819150246938](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819150246938.png) + +## 2.2 配置数据库连接池 / JdbcTemplate + +### 2.2.1 XML方式 + +```xml + + + + + + + + + + + + + + + + + + + + +``` + +### 2.2.2 配置类方式 + +```java +@Configuration//声明当前类是配置类并加载到IOC容器 +@ComponentScan(basePackages = {"com.eayon"})//开启组件扫描 扫描com.eayon包下所有带有注解(@Component @Service等)的类 然后去实例化 +public class JdbcConfiguration { + + /** + * 数据库连接池 + * + * @return + */ + @Bean + public DriverManagerDataSource getDataSource() { + DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName("com.mysql.jdbc.Driver"); + dataSource.setUrl("jdbc:mysql:///user_db"); + dataSource.setUsername("root"); + dataSource.setPassword("1234"); + return dataSource; + } + + /** + * jdbctemplate + */ + @Bean + public JdbcTemplate getJdbcTemplate(){ + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + jdbcTemplate.setDataSource(getDataSource()); + return jdbcTemplate; + } +} +``` + +## 2.3 数据库表 + +![image-20210819150219314](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819150219314.png) + +## 2.4 实体类 + +```java +package com.eayon.entity; + +public class User { + private Integer uid; + private String username; + private Double money; + + .....省略构造方法及get/set方法.... +} +``` + +## 2.5 Service + +```java +@Service +public class UserService { + + @Autowired + private UserDao userDao; + + public User getById(Integer uid) { + return userDao.getById(uid); + } + + public List getAll() { + return userDao.getAll(); + } + + public void add(User user){ + userDao.add(user); + } + + public void update(User user){ + userDao.update(user); + } + + public void delete(Integer uid){ + userDao.delete(uid); + } +} +``` + +## 2.6 Dao + +### 2.6.1 Dao接口 + +```java +public interface UserDao { + + User getById(Integer uid); + + List getAll(); + + void add(User user); + + void update(User user); + + void delete(Integer uid); +} +``` + +### 2.6.2 Dao实现类 + +```java +@Repository//实例化到Spring容器 +public class UserDaoImpl implements UserDao { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Override + public User getById(Integer uid) { + //第一个参数:sql语句 + String sql = "select * from user where uid = ?"; + //第二个参数:查询结果返回对象/集合时通过RowMapper的实现类完成数据的封装 + BeanPropertyRowMapper rowMapper = new BeanPropertyRowMapper(User.class); + //第三个参数:sql中占位符的值 + User user = jdbcTemplate.queryForObject(sql, rowMapper, uid); + return user; + } + + @Override + public List getAll() { + //第一个参数:sql语句 + String sql = "select * from user"; + //第二个参数:查询结果返回对象/集合时通过RowMapper的实现类完成数据的封装 + BeanPropertyRowMapper rowMapper = new BeanPropertyRowMapper(User.class); + List users = jdbcTemplate.query(sql, rowMapper); + return users; + } + + @Override + public void add(User user) { + Object[] args = {user.getUid(), user.getUsername(), user.getMoney()}; + String sql = "insert into user values(?,?,?)"; + int add = jdbcTemplate.update(sql, args); + System.out.println("add success num:" + add); + } + + @Override + public void update(User user) { + Object[] args = {user.getUsername(), user.getUid(), user.getMoney()}; + String sql = "update user set username = ? and money = ? where uid = ?"; + int update = jdbcTemplate.update(sql, args); + System.out.println("update success num:" + update); + } + + @Override + public void delete(Integer uid) { + Object[] args = {uid}; + String sql = "delete from user where uid = ?"; + int delete = jdbcTemplate.update(sql, args); + System.out.println("delete success num:" + delete); + } +} +``` + +## 2.7 测试 + +```java +public class JdbcDemo { + + @Test + public void getById(){ + ApplicationContext ac = new AnnotationConfigApplicationContext(JdbcConfiguration.class);//加载配置类 + //ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");//加载配置文件 + UserService userService = ac.getBean("userService", UserService.class); + User user = userService.getById(1); + System.out.println(user); + } + + @Test + public void getAll(){ + ApplicationContext ac = new AnnotationConfigApplicationContext(JdbcConfiguration.class);//加载配置类 + //ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");//加载配置文件 + UserService userService = ac.getBean("userService", UserService.class); + List users = userService.getAll(); + System.out.println(users); + } + + @Test + public void add(){ + ApplicationContext ac = new AnnotationConfigApplicationContext(JdbcConfiguration.class);//加载配置类 + //ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");//加载配置文件 + UserService userService = ac.getBean("userService", UserService.class); + User user = new User(); + user.setUid(2); + user.setUsername("李四"); + user.setMoney(100.00); + userService.add(user); + } + + @Test + public void update(){ + ApplicationContext ac = new AnnotationConfigApplicationContext(JdbcConfiguration.class);//加载配置类 + //ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");//加载配置文件 + UserService userService = ac.getBean("userService", UserService.class); + User user = new User(); + user.setUid(2); + user.setUsername("王五"); + user.setMoney(100.00); + userService.update(user); + } + + @Test + public void delete(){ + ApplicationContext ac = new AnnotationConfigApplicationContext(JdbcConfiguration.class);//加载配置类 + //ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");//加载配置文件 + UserService userService = ac.getBean("userService", UserService.class); + userService.delete(2); + } +} +``` + + + +# 三. Spring事务管理 + +## 3.1 什么是事务? + +### 3.1.1 事务的定义 + +事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败。 + +**概述** + +一个事务是由一个单元内的一个或多个SQL(操作)组成,这个单元中的每个SQL(操作)都是互相依赖的,单元作为一个整体是不可分割的。如果单元内的一个SQL不能够成功完成,整个单元就会回滚,所影响到的数据将返回到事务开始以前的状态。因此,只有事务中的所有操作都被执行成功,才能说是一个事务被执行成功 + +**举例** + +![image-20210819150447356](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819150447356.png) + +如上述例子,Micah给Maruko转账,只有在转账成功的情况下,Micah的账户余额才会减少,Maruko的账户余额增加,不存在Micah账户的余额减少了,而Maruko的账户余额却不变。要么转账成功,2边余额都改变;要么转账失败,2边余额都保持不变。 + +### 3.1.2 事务的四大特征(ACID) + +- **原子性(Atomicity)**:事务中所有的操作都捆绑成一个原子弹元,所以对于事物所进行修改等操作时,要么全部执行,要么全部不执行。 +- **一致性(Consistency)**:事务在完成时,必须所有的数据都保持一致。 +- **隔离性(Isolation)**:事务与事务之间式是隔离开的,事务只缺提交之前,它的结果不应该显示给其他事务。 +- **持久性(Durability)**:事务正确提交之后,结果将永远保存在数据库之中。 + +### 3.1.3 Spring事务管理 + +#### 3.1.3.1 编程式事务管理 + +编程式事务管理是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。 + +#### 3.1.3.2 声明式事务管理 + +声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。 + +显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。唯一不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成声明式事务管理的配置。 + +## 3.2 搭建事务操作环境 + +![image-20210819150620214](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819150620214.png) + +### 3.2.1 Service + +```java +@Service +public class UserService { + + @Autowired + private UserDao userDao; + + /** + * 转账 + * + * @param reduceUid 扣款用户 + * @param addUid 收钱用户 + * @param transferMoney 转账金额 + */ + public void transfer_accounts(Integer reduceUid, Integer addUid, Double transferMoney) { + //扣钱 + userDao.reduceMoney(reduceUid, transferMoney); + //加钱 + userDao.addMoney(addUid, transferMoney); + } +} +``` + +### 3.2.2 Dao + +**Dao接口** + +```java +public interface UserDao { + void reduceMoney(Integer uid, Double money); + + void addMoney(Integer uid, Double money); +} +``` + +**Dao实现类** + +```java +@Repository//实例化到Spring容器 +public class UserDaoImpl implements UserDao { + + @Autowired + private JdbcTemplate jdbcTemplate; + + /** + * 扣钱 + * + * @param uid + * @param money + */ + @Override + public void reduceMoney(Integer uid, Double money) { + String sql = "update user set money = money - ? where uid = ?"; + jdbcTemplate.update(sql, money, uid); + } + + /** + * 加钱 + * @param uid + * @param money + */ + @Override + public void addMoney(Integer uid, Double money) { + String sql = "update user set money = money + ? where uid = ?"; + jdbcTemplate.update(sql, money, uid); + } +} +``` + +### 3.2.3 测试 + +```java +public class JdbcDemo { + /** + * 转账【事务管理】 + */ + @Test + public void transfer_accounts() { + ApplicationContext ac = new AnnotationConfigApplicationContext(JdbcConfiguration.class);//加载配置类 + //ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");//加载配置文件 + UserService userService = ac.getBean("userService", UserService.class); + + //扣钱 + Integer reduceUid = 1;//扣款用户uid + Integer addUid = 2;//加钱用户uid + Double transferMoney = 50.00;//转账金额 + userService.transfer_accounts(reduceUid, addUid, transferMoney); + } +} +``` + +### 3.2.4 事务问题引入 + +如上代码可以正确进行转账,但是如果我们在service层给用户成功扣款后出现了异常导致程序执行中断,没有给用户完成加钱操作,则就出现了事务的问题 + +![image-20210819150753019](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819150753019.png) + +## 3.3 事务管理 + +在 Spring 进行事务管理操作,有两种方式:**编程式事务管理**和**声明式事务管理(使用)** + +### 3.3.1 声明式事务管理的实现方式 + +声明式事务管理基于AOP + +- 基于注解方式(使用) +- 基于xml配置方式 + +### 3.3.2 PlatformTransactionManager事务管理器 + +它是一个接口,这个接口针对不同的框架提供了不同事务控制的实现类 + +![image-20210819150849933](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819150849933.png) + +### 3.3.3 声明式事务管理注解方式配置 - XML文件 + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +### 3.3.4 声明式事务管理注解方式配置 - 配置类 + +```java +@Configuration//声明当前类是配置类并加载到IOC容器 +@ComponentScan(basePackages = {"com.eayon"})//开启组件扫描 扫描com.eayon包下所有带有注解(@Component @Service等)的类 然后去实例化 +@EnableTransactionManagement//开启事务管 +public class JdbcConfiguration { + + /** + * 数据库连接池 + * + * @return + */ + @Bean + public DriverManagerDataSource getDataSource() { + DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName("com.mysql.jdbc.Driver"); + dataSource.setUrl("jdbc:mysql:///user_db"); + dataSource.setUsername("root"); + dataSource.setPassword("1234"); + return dataSource; + } + + /** + * jdbctemplate + */ + @Bean + public JdbcTemplate getJdbcTemplate(){ + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + jdbcTemplate.setDataSource(getDataSource()); + return jdbcTemplate; + } + + /** + * 创建事务管理器 + */ + @Bean + public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){ + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); + transactionManager.setDataSource(dataSource); + return transactionManager; + } +} +``` + +### 3.3.5 开启事务管理 + +**我们可以在需要进行事务控制的service类加上``@Transactional``注解,这样该类下所有方法都会进行事务控制** + +```java +@Service +@Transactional//事务 +public class UserService { + + @Autowired + private UserDao userDao; + + /** + * 转账 + * + * @param reduceUid 扣款用户 + * @param addUid 收钱用户 + * @param transferMoney 转账金额 + */ + public void transfer_accounts(Integer reduceUid, Integer addUid, Double transferMoney) { + //扣钱 + userDao.reduceMoney(reduceUid, transferMoney); + + //模拟异常 + int i = 10 / 0; + + //加钱 + userDao.addMoney(addUid, transferMoney); + } +} +``` + +**也可以精确到方法上就只针对与该方法进行事务控制** + +```java +@Service +public class UserService { + + @Autowired + private UserDao userDao; + + /** + * 转账 + * + * @param reduceUid 扣款用户 + * @param addUid 收钱用户 + * @param transferMoney 转账金额 + */ + @Transactional//事务 + public void transfer_accounts(Integer reduceUid, Integer addUid, Double transferMoney) { + //扣钱 + userDao.reduceMoney(reduceUid, transferMoney); + + //模拟异常 + int i = 10 / 0; + + //加钱 + userDao.addMoney(addUid, transferMoney); + } +} +``` + +### 3.3.6 测试 + +测试转账后出现异常时,被扣款账户的金额是否还会成功扣除 + +```java +/** + * 转账【事务管理】 + */ +@Test +public void transfer_accounts() { + ApplicationContext ac = new AnnotationConfigApplicationContext(JdbcConfiguration.class);//加载配置类 + //ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");//加载配置文件 + UserService userService = ac.getBean("userService", UserService.class); + + //扣钱 + Integer reduceUid = 1;//扣款用户uid + Integer addUid = 2;//加钱用户uid + Double transferMoney = 50.00;//转账金额 + userService.transfer_accounts(reduceUid, addUid, transferMoney); +} +``` + +### 3.3.7 @Transactional注解中的参数 + +![image-20210819151116972](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819151116972.png) + +- **propagation(事务传播行为)**:多个事务方法调用时,事务是如何执行的。 + +- **isolation(事务隔离级别)**:在并发情况下事务会出现一些脏读、幻读、不可重复读的问题,所以我们可以通过事物的隔离级别进行处理。 + +- **timeout(超时时间)**:事务需要在一定时间内进行提交,如果不提交进行回滚,默认值是 -1(无时间限制),设置时间以秒单位进行计算。 + +- **readOnly(是否只读)**: + +- - readOnly默认值 false,表示可以查询,可以添加修改删除操作 + - 设置 readOnly值是 true,设置成 true之后,只能查询 + +- **rollbackFor(发生什么异常会去回滚)**:设置出现哪些异常进行事务回滚 + +- **noRollbackFor(发生什么异常不会回滚)**:设置出现哪些异常不进行事务回滚 + +## 3.4 事务的7中传播行为 + +事务的传播性一般用在事务嵌套的场景,比如一个事务方法里面调用了另外一个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。 + +**事务的传播行为有以下七种**: + +- **PROPAGATION_REQUIRED(****默认****)**:Spring默认的传播机制,能满足绝大部分业务需求,如果外层有事务则当前事务加入到外层事务一块提交回滚。如果外层没有事务则新建一个事务执行。 +- **PROPAGATION_REQUES_NEW**:当前的方法每次都会新建一个事务,并在自己的事务内运行,如果有事务正在运行就将他挂起。 +- **PROPAGATION_SUPPORT**:如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务 +- **PROPAGATION_NOT_SUPPORT:**该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码 +- **PROPAGATION_NEVER**:该传播机制不支持外层事务,即如果外层有事务就抛出异常 +- **PROPAGATION_MANDATORY**:与NEVER相反,如果外层没有事务,则抛出异常 +- **PROPAGATION_NESTED:**如果当前存在事务,就嵌套在当前事务内执行,如果没有就新建一个事务。 + +**传播规则回答了这样一个问题:一个新的事务应该被启动还是被挂起,或者是一个方法是否应该在事务性上下文中运行。** + +## 3.5 事务的隔离级别 + +在并发情况下事务会出现一些如下2.5.1读的问题,所以我们可以通过事务的隔离级别去进行处理。可以把事务的隔离级别想象为这个事务对于事物处理数据的自私程度。 + +### 3.5.1 并发情况下事务会出现的问题 + +在一个典型的应用程序中,多个事务同时运行,经常会为了完成他们的工作而操作同一个数据。并发虽然是必需的,但是会导致以下问题: + +- **脏读**:一个事务读取到另一个事务还没有提交的数据。如果这些数据稍后被回滚了,那么第一个事务读取到的数据是无效的,则为脏读。 + +- **不可重复读**:一个事务两个相同的查询却返回了不同的数据。通常是由于另一个并发事务在两次查询之间更新了数据。不可重复读的重点在修改 + +- **幻读**:和不可重复读类似。当一个事务第一次读取几行数据之后由于另一个并发事务往里面插入了一些数据。幻读就发生了。在后面的查询中,第一个事务会发现原来没有查询到的数据。幻读重点在新增或删除。 + +### 3.5.2 隔离级别 + +在理想状态下,事务之间将完全隔离,从而可以防止这些问题发生。然而,完全隔离会影响性能,因为隔离经常涉及到锁定在数据库中的记录(甚至有时是锁表)。完全隔离要求事务相互等待来完成工作,会阻碍并发。因此,可以根据业务场景选择不同的隔离级别 + +- **读操作未提交(ISOLATION_READ_UNCOMMITTED)**:允许读取尚未提交的更改。无法避免脏读、幻读、 不可重复读 +- **读操作已提交(ISOLATION_READ_COMMITTED)**:Oracle默认级别。允许从已经提交的并发事务读取。可以避免脏读,但无法避免幻读和不可重复读。 +- **可重复读(ISOLATION_REPEATABLE_READ)**:MySql默认隔离级别。可以保证多次读取的结果一致。除非数据被当前事务本身改变。可避免脏读,不可重复读,不可避免幻读。 +- **串行化(ISOLATION_SERIALIZABLE)**:完全服从ACID的隔离级别。能够保证任何并发情况下的问题发生(脏读、幻读、不可重复读)但是效率很低,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。 + +![image-20210819151408747](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819151408747.png) \ No newline at end of file diff --git "a/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/03.JdbcTemplate/README.md" "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/03.JdbcTemplate/README.md" new file mode 100644 index 0000000..4322668 --- /dev/null +++ "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/03.JdbcTemplate/README.md" @@ -0,0 +1,726 @@ +# Spring JdbcTemplate概念及实战 + +# 说在前面 + +- **本章相关代码及笔记地址**:飞机票🚀 +- **🌍**Github:**[**🚀Java超神之路:【🍔Java全生态技术学习笔记,一起超神吧🍔】](https://github.com/EayonLee/JavaGod) +- **🪐**CSDN:**[**🚀Java超神之路:【🍔Java全生态技术学习笔记,一起超神吧🍔】](https://blog.csdn.net/qq_20492277/article/details/114269863) + +# 目录 +- [Spring AOP JdbcTemplate概念及实战](#spring-aop--jdbctemplate概念及实战) +- [说在前面](#说在前面) +- [目录](#目录) +- [一. Spring JDBC概述](#一-spring-jdbc概述) + - [1.1 什么是JDBC Template](#11-什么是jdbc-template) + - [1.2 Spring JDBC能做什么](#12-spring-jdbc能做什么) +- [二. 使用JdbcTemplate](#二-使用jdbctemplate) + - [2.1 新建Module,引入依赖](#21-新建module引入依赖) + - [2.2 配置数据库连接池 / JdbcTemplate](#22-配置数据库连接池--jdbctemplate) + - [2.2.1 XML方式](#221-xml方式) + - [2.2.2 配置类方式](#222-配置类方式) + - [2.3 数据库表](#23-数据库表) + - [2.4 实体类](#24-实体类) + - [2.5 Service](#25-service) + - [2.6 Dao](#26-dao) + - [2.6.1 Dao接口](#261-dao接口) + - [2.6.2 Dao实现类](#262-dao实现类) + - [2.7 测试](#27-测试) +- [三. Spring事务管理](#三-spring事务管理) + - [3.1 什么是事务?](#31-什么是事务) + - [3.1.1 事务的定义](#311-事务的定义) + - [3.1.2 事务的四大特征(ACID)](#312-事务的四大特征acid) + - [3.1.3 Spring事务管理](#313-spring事务管理) + - [3.1.3.1 编程式事务管理](#3131-编程式事务管理) + - [3.1.3.2 声明式事务管理](#3132-声明式事务管理) + - [3.2 搭建事务操作环境](#32-搭建事务操作环境) + - [3.2.1 Service](#321-service) + - [3.2.2 Dao](#322-dao) + - [3.2.3 测试](#323-测试) + - [3.2.4 事务问题引入](#324-事务问题引入) + - [3.3 事务管理](#33-事务管理) + - [3.3.1 声明式事务管理的实现方式](#331-声明式事务管理的实现方式) + - [3.3.2 PlatformTransactionManager事务管理器](#332-platformtransactionmanager事务管理器) + - [3.3.3 声明式事务管理注解方式配置 - XML文件](#333-声明式事务管理注解方式配置---xml文件) + - [3.3.4 声明式事务管理注解方式配置 - 配置类](#334-声明式事务管理注解方式配置---配置类) + - [3.3.5 开启事务管理](#335-开启事务管理) + - [3.3.6 测试](#336-测试) + - [3.3.7 @Transactional注解中的参数](#337-transactional注解中的参数) + - [3.4 事务的7中传播行为](#34-事务的7中传播行为) + - [3.5 事务的隔离级别](#35-事务的隔离级别) + - [3.5.1 并发情况下事务会出现的问题](#351-并发情况下事务会出现的问题) + - [3.5.2 隔离级别](#352-隔离级别) + + +# 一. Spring JDBC概述 + +## 1.1 什么是JDBC Template + +它是 Spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装。除了JdbcTemplate,Spring 框架还为我们提供了很多的操作模板类。 + +**操作关系型数据的**:JdbcTemplate和HibernateTemplate。 + +**操作 nosql 数据库的**:RedisTemplate。 + +**操作消息队列的**:JmsTemplate。 + +## 1.2 Spring JDBC能做什么 + +| **操作项目** | **Spring帮你做的** | **你需要做的** | +| ---------------------------- | ------------------ | -------------- | +| 定义连接参数 | | ✔ | +| 打开连接 | ✔ | | +| 指定SQL语句 | | ✔ | +| 声明参数并提供参数值 | | ✔ | +| 准备并运行该语句 | ✔ | | +| 设置循环以遍历结果(如果有) | ✔ | | +| 进行每次迭代的工作 | | ✔ | +| 处理任何异常 | ✔ | | +| 处理交易 | ✔ | | +| 关闭连接,语句和结果集 | ✔ | | + + + +# 二. 使用JdbcTemplate + +## 2.1 新建Module,引入依赖 + +![image-20210819150246938](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819150246938.png) + +## 2.2 配置数据库连接池 / JdbcTemplate + +### 2.2.1 XML方式 + +```xml + + + + + + + + + + + + + + + + + + + + +``` + +### 2.2.2 配置类方式 + +```java +@Configuration//声明当前类是配置类并加载到IOC容器 +@ComponentScan(basePackages = {"com.eayon"})//开启组件扫描 扫描com.eayon包下所有带有注解(@Component @Service等)的类 然后去实例化 +public class JdbcConfiguration { + + /** + * 数据库连接池 + * + * @return + */ + @Bean + public DriverManagerDataSource getDataSource() { + DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName("com.mysql.jdbc.Driver"); + dataSource.setUrl("jdbc:mysql:///user_db"); + dataSource.setUsername("root"); + dataSource.setPassword("1234"); + return dataSource; + } + + /** + * jdbctemplate + */ + @Bean + public JdbcTemplate getJdbcTemplate(){ + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + jdbcTemplate.setDataSource(getDataSource()); + return jdbcTemplate; + } +} +``` + +## 2.3 数据库表 + +![image-20210819150219314](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819150219314.png) + +## 2.4 实体类 + +```java +package com.eayon.entity; + +public class User { + private Integer uid; + private String username; + private Double money; + + .....省略构造方法及get/set方法.... +} +``` + +## 2.5 Service + +```java +@Service +public class UserService { + + @Autowired + private UserDao userDao; + + public User getById(Integer uid) { + return userDao.getById(uid); + } + + public List getAll() { + return userDao.getAll(); + } + + public void add(User user){ + userDao.add(user); + } + + public void update(User user){ + userDao.update(user); + } + + public void delete(Integer uid){ + userDao.delete(uid); + } +} +``` + +## 2.6 Dao + +### 2.6.1 Dao接口 + +```java +public interface UserDao { + + User getById(Integer uid); + + List getAll(); + + void add(User user); + + void update(User user); + + void delete(Integer uid); +} +``` + +### 2.6.2 Dao实现类 + +```java +@Repository//实例化到Spring容器 +public class UserDaoImpl implements UserDao { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Override + public User getById(Integer uid) { + //第一个参数:sql语句 + String sql = "select * from user where uid = ?"; + //第二个参数:查询结果返回对象/集合时通过RowMapper的实现类完成数据的封装 + BeanPropertyRowMapper rowMapper = new BeanPropertyRowMapper(User.class); + //第三个参数:sql中占位符的值 + User user = jdbcTemplate.queryForObject(sql, rowMapper, uid); + return user; + } + + @Override + public List getAll() { + //第一个参数:sql语句 + String sql = "select * from user"; + //第二个参数:查询结果返回对象/集合时通过RowMapper的实现类完成数据的封装 + BeanPropertyRowMapper rowMapper = new BeanPropertyRowMapper(User.class); + List users = jdbcTemplate.query(sql, rowMapper); + return users; + } + + @Override + public void add(User user) { + Object[] args = {user.getUid(), user.getUsername(), user.getMoney()}; + String sql = "insert into user values(?,?,?)"; + int add = jdbcTemplate.update(sql, args); + System.out.println("add success num:" + add); + } + + @Override + public void update(User user) { + Object[] args = {user.getUsername(), user.getUid(), user.getMoney()}; + String sql = "update user set username = ? and money = ? where uid = ?"; + int update = jdbcTemplate.update(sql, args); + System.out.println("update success num:" + update); + } + + @Override + public void delete(Integer uid) { + Object[] args = {uid}; + String sql = "delete from user where uid = ?"; + int delete = jdbcTemplate.update(sql, args); + System.out.println("delete success num:" + delete); + } +} +``` + +## 2.7 测试 + +```java +public class JdbcDemo { + + @Test + public void getById(){ + ApplicationContext ac = new AnnotationConfigApplicationContext(JdbcConfiguration.class);//加载配置类 + //ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");//加载配置文件 + UserService userService = ac.getBean("userService", UserService.class); + User user = userService.getById(1); + System.out.println(user); + } + + @Test + public void getAll(){ + ApplicationContext ac = new AnnotationConfigApplicationContext(JdbcConfiguration.class);//加载配置类 + //ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");//加载配置文件 + UserService userService = ac.getBean("userService", UserService.class); + List users = userService.getAll(); + System.out.println(users); + } + + @Test + public void add(){ + ApplicationContext ac = new AnnotationConfigApplicationContext(JdbcConfiguration.class);//加载配置类 + //ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");//加载配置文件 + UserService userService = ac.getBean("userService", UserService.class); + User user = new User(); + user.setUid(2); + user.setUsername("李四"); + user.setMoney(100.00); + userService.add(user); + } + + @Test + public void update(){ + ApplicationContext ac = new AnnotationConfigApplicationContext(JdbcConfiguration.class);//加载配置类 + //ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");//加载配置文件 + UserService userService = ac.getBean("userService", UserService.class); + User user = new User(); + user.setUid(2); + user.setUsername("王五"); + user.setMoney(100.00); + userService.update(user); + } + + @Test + public void delete(){ + ApplicationContext ac = new AnnotationConfigApplicationContext(JdbcConfiguration.class);//加载配置类 + //ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");//加载配置文件 + UserService userService = ac.getBean("userService", UserService.class); + userService.delete(2); + } +} +``` + + + +# 三. Spring事务管理 + +## 3.1 什么是事务? + +### 3.1.1 事务的定义 + +事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败。 + +**概述** + +一个事务是由一个单元内的一个或多个SQL(操作)组成,这个单元中的每个SQL(操作)都是互相依赖的,单元作为一个整体是不可分割的。如果单元内的一个SQL不能够成功完成,整个单元就会回滚,所影响到的数据将返回到事务开始以前的状态。因此,只有事务中的所有操作都被执行成功,才能说是一个事务被执行成功 + +**举例** + +![image-20210819150447356](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819150447356.png) + +如上述例子,Micah给Maruko转账,只有在转账成功的情况下,Micah的账户余额才会减少,Maruko的账户余额增加,不存在Micah账户的余额减少了,而Maruko的账户余额却不变。要么转账成功,2边余额都改变;要么转账失败,2边余额都保持不变。 + +### 3.1.2 事务的四大特征(ACID) + +- **原子性(Atomicity)**:事务中所有的操作都捆绑成一个原子弹元,所以对于事物所进行修改等操作时,要么全部执行,要么全部不执行。 +- **一致性(Consistency)**:事务在完成时,必须所有的数据都保持一致。 +- **隔离性(Isolation)**:事务与事务之间式是隔离开的,事务只缺提交之前,它的结果不应该显示给其他事务。 +- **持久性(Durability)**:事务正确提交之后,结果将永远保存在数据库之中。 + +### 3.1.3 Spring事务管理 + +#### 3.1.3.1 编程式事务管理 + +编程式事务管理是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。 + +#### 3.1.3.2 声明式事务管理 + +声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。 + +显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。唯一不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成声明式事务管理的配置。 + +## 3.2 搭建事务操作环境 + +![image-20210819150620214](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819150620214.png) + +### 3.2.1 Service + +```java +@Service +public class UserService { + + @Autowired + private UserDao userDao; + + /** + * 转账 + * + * @param reduceUid 扣款用户 + * @param addUid 收钱用户 + * @param transferMoney 转账金额 + */ + public void transfer_accounts(Integer reduceUid, Integer addUid, Double transferMoney) { + //扣钱 + userDao.reduceMoney(reduceUid, transferMoney); + //加钱 + userDao.addMoney(addUid, transferMoney); + } +} +``` + +### 3.2.2 Dao + +**Dao接口** + +```java +public interface UserDao { + void reduceMoney(Integer uid, Double money); + + void addMoney(Integer uid, Double money); +} +``` + +**Dao实现类** + +```java +@Repository//实例化到Spring容器 +public class UserDaoImpl implements UserDao { + + @Autowired + private JdbcTemplate jdbcTemplate; + + /** + * 扣钱 + * + * @param uid + * @param money + */ + @Override + public void reduceMoney(Integer uid, Double money) { + String sql = "update user set money = money - ? where uid = ?"; + jdbcTemplate.update(sql, money, uid); + } + + /** + * 加钱 + * @param uid + * @param money + */ + @Override + public void addMoney(Integer uid, Double money) { + String sql = "update user set money = money + ? where uid = ?"; + jdbcTemplate.update(sql, money, uid); + } +} +``` + +### 3.2.3 测试 + +```java +public class JdbcDemo { + /** + * 转账【事务管理】 + */ + @Test + public void transfer_accounts() { + ApplicationContext ac = new AnnotationConfigApplicationContext(JdbcConfiguration.class);//加载配置类 + //ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");//加载配置文件 + UserService userService = ac.getBean("userService", UserService.class); + + //扣钱 + Integer reduceUid = 1;//扣款用户uid + Integer addUid = 2;//加钱用户uid + Double transferMoney = 50.00;//转账金额 + userService.transfer_accounts(reduceUid, addUid, transferMoney); + } +} +``` + +### 3.2.4 事务问题引入 + +如上代码可以正确进行转账,但是如果我们在service层给用户成功扣款后出现了异常导致程序执行中断,没有给用户完成加钱操作,则就出现了事务的问题 + +![image-20210819150753019](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819150753019.png) + +## 3.3 事务管理 + +在 Spring 进行事务管理操作,有两种方式:**编程式事务管理**和**声明式事务管理(使用)** + +### 3.3.1 声明式事务管理的实现方式 + +声明式事务管理基于AOP + +- 基于注解方式(使用) +- 基于xml配置方式 + +### 3.3.2 PlatformTransactionManager事务管理器 + +它是一个接口,这个接口针对不同的框架提供了不同事务控制的实现类 + +![image-20210819150849933](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819150849933.png) + +### 3.3.3 声明式事务管理注解方式配置 - XML文件 + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +### 3.3.4 声明式事务管理注解方式配置 - 配置类 + +```java +@Configuration//声明当前类是配置类并加载到IOC容器 +@ComponentScan(basePackages = {"com.eayon"})//开启组件扫描 扫描com.eayon包下所有带有注解(@Component @Service等)的类 然后去实例化 +@EnableTransactionManagement//开启事务管 +public class JdbcConfiguration { + + /** + * 数据库连接池 + * + * @return + */ + @Bean + public DriverManagerDataSource getDataSource() { + DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName("com.mysql.jdbc.Driver"); + dataSource.setUrl("jdbc:mysql:///user_db"); + dataSource.setUsername("root"); + dataSource.setPassword("1234"); + return dataSource; + } + + /** + * jdbctemplate + */ + @Bean + public JdbcTemplate getJdbcTemplate(){ + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + jdbcTemplate.setDataSource(getDataSource()); + return jdbcTemplate; + } + + /** + * 创建事务管理器 + */ + @Bean + public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){ + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); + transactionManager.setDataSource(dataSource); + return transactionManager; + } +} +``` + +### 3.3.5 开启事务管理 + +**我们可以在需要进行事务控制的service类加上``@Transactional``注解,这样该类下所有方法都会进行事务控制** + +```java +@Service +@Transactional//事务 +public class UserService { + + @Autowired + private UserDao userDao; + + /** + * 转账 + * + * @param reduceUid 扣款用户 + * @param addUid 收钱用户 + * @param transferMoney 转账金额 + */ + public void transfer_accounts(Integer reduceUid, Integer addUid, Double transferMoney) { + //扣钱 + userDao.reduceMoney(reduceUid, transferMoney); + + //模拟异常 + int i = 10 / 0; + + //加钱 + userDao.addMoney(addUid, transferMoney); + } +} +``` + +**也可以精确到方法上就只针对与该方法进行事务控制** + +```java +@Service +public class UserService { + + @Autowired + private UserDao userDao; + + /** + * 转账 + * + * @param reduceUid 扣款用户 + * @param addUid 收钱用户 + * @param transferMoney 转账金额 + */ + @Transactional//事务 + public void transfer_accounts(Integer reduceUid, Integer addUid, Double transferMoney) { + //扣钱 + userDao.reduceMoney(reduceUid, transferMoney); + + //模拟异常 + int i = 10 / 0; + + //加钱 + userDao.addMoney(addUid, transferMoney); + } +} +``` + +### 3.3.6 测试 + +测试转账后出现异常时,被扣款账户的金额是否还会成功扣除 + +```java +/** + * 转账【事务管理】 + */ +@Test +public void transfer_accounts() { + ApplicationContext ac = new AnnotationConfigApplicationContext(JdbcConfiguration.class);//加载配置类 + //ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");//加载配置文件 + UserService userService = ac.getBean("userService", UserService.class); + + //扣钱 + Integer reduceUid = 1;//扣款用户uid + Integer addUid = 2;//加钱用户uid + Double transferMoney = 50.00;//转账金额 + userService.transfer_accounts(reduceUid, addUid, transferMoney); +} +``` + +### 3.3.7 @Transactional注解中的参数 + +![image-20210819151116972](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819151116972.png) + +- **propagation(事务传播行为)**:多个事务方法调用时,事务是如何执行的。 + +- **isolation(事务隔离级别)**:在并发情况下事务会出现一些脏读、幻读、不可重复读的问题,所以我们可以通过事物的隔离级别进行处理。 + +- **timeout(超时时间)**:事务需要在一定时间内进行提交,如果不提交进行回滚,默认值是 -1(无时间限制),设置时间以秒单位进行计算。 + +- **readOnly(是否只读)**: + +- - readOnly默认值 false,表示可以查询,可以添加修改删除操作 + - 设置 readOnly值是 true,设置成 true之后,只能查询 + +- **rollbackFor(发生什么异常会去回滚)**:设置出现哪些异常进行事务回滚 + +- **noRollbackFor(发生什么异常不会回滚)**:设置出现哪些异常不进行事务回滚 + +## 3.4 事务的7中传播行为 + +事务的传播性一般用在事务嵌套的场景,比如一个事务方法里面调用了另外一个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。 + +**事务的传播行为有以下七种**: + +- **PROPAGATION_REQUIRED(****默认****)**:Spring默认的传播机制,能满足绝大部分业务需求,如果外层有事务则当前事务加入到外层事务一块提交回滚。如果外层没有事务则新建一个事务执行。 +- **PROPAGATION_REQUES_NEW**:当前的方法每次都会新建一个事务,并在自己的事务内运行,如果有事务正在运行就将他挂起。 +- **PROPAGATION_SUPPORT**:如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务 +- **PROPAGATION_NOT_SUPPORT:**该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码 +- **PROPAGATION_NEVER**:该传播机制不支持外层事务,即如果外层有事务就抛出异常 +- **PROPAGATION_MANDATORY**:与NEVER相反,如果外层没有事务,则抛出异常 +- **PROPAGATION_NESTED:**如果当前存在事务,就嵌套在当前事务内执行,如果没有就新建一个事务。 + +**传播规则回答了这样一个问题:一个新的事务应该被启动还是被挂起,或者是一个方法是否应该在事务性上下文中运行。** + +## 3.5 事务的隔离级别 + +在并发情况下事务会出现一些如下2.5.1读的问题,所以我们可以通过事务的隔离级别去进行处理。可以把事务的隔离级别想象为这个事务对于事物处理数据的自私程度。 + +### 3.5.1 并发情况下事务会出现的问题 + +在一个典型的应用程序中,多个事务同时运行,经常会为了完成他们的工作而操作同一个数据。并发虽然是必需的,但是会导致以下问题: + +- **脏读**:一个事务读取到另一个事务还没有提交的数据。如果这些数据稍后被回滚了,那么第一个事务读取到的数据是无效的,则为脏读。 + +- **不可重复读**:一个事务两个相同的查询却返回了不同的数据。通常是由于另一个并发事务在两次查询之间更新了数据。不可重复读的重点在修改 + +- **幻读**:和不可重复读类似。当一个事务第一次读取几行数据之后由于另一个并发事务往里面插入了一些数据。幻读就发生了。在后面的查询中,第一个事务会发现原来没有查询到的数据。幻读重点在新增或删除。 + +### 3.5.2 隔离级别 + +在理想状态下,事务之间将完全隔离,从而可以防止这些问题发生。然而,完全隔离会影响性能,因为隔离经常涉及到锁定在数据库中的记录(甚至有时是锁表)。完全隔离要求事务相互等待来完成工作,会阻碍并发。因此,可以根据业务场景选择不同的隔离级别 + +- **读操作未提交(ISOLATION_READ_UNCOMMITTED)**:允许读取尚未提交的更改。无法避免脏读、幻读、 不可重复读 +- **读操作已提交(ISOLATION_READ_COMMITTED)**:Oracle默认级别。允许从已经提交的并发事务读取。可以避免脏读,但无法避免幻读和不可重复读。 +- **可重复读(ISOLATION_REPEATABLE_READ)**:MySql默认隔离级别。可以保证多次读取的结果一致。除非数据被当前事务本身改变。可避免脏读,不可重复读,不可避免幻读。 +- **串行化(ISOLATION_SERIALIZABLE)**:完全服从ACID的隔离级别。能够保证任何并发情况下的问题发生(脏读、幻读、不可重复读)但是效率很低,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。 + +![image-20210819151408747](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819151408747.png) \ No newline at end of file diff --git "a/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/04.Spring346円272円220円347円240円201円345円211円226円346円236円220円/01.350円257円276円347円250円213円347円254円224円350円256円260円/Spring346円272円220円347円240円201円345円211円226円346円236円220円.md" "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/04.Spring346円272円220円347円240円201円345円211円226円346円236円220円/01.350円257円276円347円250円213円347円254円224円350円256円260円/Spring346円272円220円347円240円201円345円211円226円346円236円220円.md" new file mode 100644 index 0000000..47c00e3 --- /dev/null +++ "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/04.Spring346円272円220円347円240円201円345円211円226円346円236円220円/01.350円257円276円347円250円213円347円254円224円350円256円260円/Spring346円272円220円347円240円201円345円211円226円346円236円220円.md" @@ -0,0 +1,1994 @@ +# Spring 源码剖析 + +# 说在前面 + +- **本章相关代码及笔记地址**:飞机票🚀 +- **🌍**Github:[🚀Java超神之路:【🍔Java全生态技术学习笔记,一起超神吧🍔】](https://github.com/EayonLee/JavaGod) +- **🪐**CSDN:[🚀Java超神之路:【🍔Java全生态技术学习笔记,一起超神吧🍔】](https://blog.csdn.net/qq_20492277/article/details/114269863) + +# 目录 +- [Spring 源码剖析](#spring--源码剖析) +- [说在前面](#说在前面) +- [目录](#目录) +- [一. Bean完整的生命周期](#一-bean完整的生命周期) + - [1.1 BeanDefinition(Bean定义信息)](#11-beandefinitionbean定义信息) + - [1.2 BeanFactoryPostProcessor(BeanFactory的后置处理器)](#12-beanfactorypostprocessorbeanfactory的后置处理器) + - [1.3 Bean实例化、填充属性、Aware接口](#13-bean实例化填充属性aware接口) + - [1.4 BeanPostProcessor接口 / init-method](#14-beanpostprocessor接口--init-method) + - [1.5 destory-method销毁方法-完整的Bean声明周期结束](#15-destory-method销毁方法-完整的bean声明周期结束) +- [二. Bean的生命周期 源码解析](#二-bean的生命周期-源码解析) + - [2.1 准备工作](#21-准备工作) + - [2.2 ClassPathXmlApplicationContext方法](#22-classpathxmlapplicationcontext方法) + - [2.3 验证](#23-验证) + - [2.3.1 refresh():方法](#231-refresh方法) + - [2.3.2 freshBeanFactory():创建bean工厂并加载xml文件到BeanDefinition](#232-freshbeanfactory创建bean工厂并加载xml文件到beandefinition) + - [2.3.3 finishBeanFactoryInitialization():完成上下文bean工厂的初始化](#233-finishbeanfactoryinitialization完成上下文bean工厂的初始化) + - [2.3.4 getBean():获取bean](#234-getbean获取bean) + - [2.3.5 createBean():创建Bean](#235-createbean创建bean) + - [2.3.6 popelateBean():Bean的属性赋值](#236-popelatebeanbean的属性赋值) + - [2.3.7 invokeAwareMethods():执行bean所实现Aware接口方法](#237-invokeawaremethods执行bean所实现aware接口方法) + - [2.3.8 beanPostProcessors的before方法](#238-beanpostprocessors的before方法) + - [2.3.9 init-method:执行初始化方法](#239-init-method执行初始化方法) + - [2.3.10 beanPostProcessors的after方法](#2310-beanpostprocessors的after方法) + - [2.4 总结](#24-总结) +- [三. 循环依赖](#三-循环依赖) + - [3.1 什么是循环依赖?](#31-什么是循环依赖) + - [3.2 测试循环依赖](#32-测试循环依赖) + - [3.2.1 准备实体](#321-准备实体) + - [3.2.2 XML文件创建Bean并属性注入](#322-xml文件创建bean并属性注入) + - [3.2.3 测试及结果分析](#323-测试及结果分析) + - [3.2 循环依赖的几种场景](#32-循环依赖的几种场景) + - [3.3 单例的setter注入如何解决循环依赖](#33-单例的setter注入如何解决循环依赖) + - [3.3.1 为什么正常情况下Spring不会出现循环依赖问题](#331-为什么正常情况下spring不会出现循环依赖问题) + - [3.3.2 Spring默认针对单例setter注入是如何解决循环依赖的](#332-spring默认针对单例setter注入是如何解决循环依赖的) + - [3.3.3 什么是三级缓存](#333-什么是三级缓存) + - [3.3.4 源码验证](#334-源码验证) + - [3.4 为什么需要三级缓存,二级行不行?](#34-为什么需要三级缓存二级行不行) +- [四. JDK动态代理源码分析](#四-jdk动态代理源码分析) + - [4.1 JDK动态代理的实现](#41-jdk动态代理的实现) + - [4.2 源码分析](#42-源码分析) + - [4.3 总结](#43-总结) + +# 一. Bean完整的生命周期 + +## 1.1 BeanDefinition(Bean定义信息) + +![image-20210819164327964](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819164327964.png) + +一般我们会通过XML配置文件、注解、配置类去定义bean。当我们去加载配置文件或者配置类的时候首先会去通过BeanDefinitionReader接口的实现类去读取bean的一些定义。比如我们通过XML配置文件去进行bean定义,则会通过XMLBeanDefinitionReader进行解析并将bean的定义信息、依赖关系等保存至BeanDefinition中。 + +## 1.2 BeanFactoryPostProcessor(BeanFactory的后置处理器) + +![image-20210819164409397](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819164409397.png) + +**为什么要有BeanFactoryPostProcessor?它的作用是什么?** + +一般情况我们将bean的一些定义信息保存至BeanDefinition中之后,就可以通过BeanFactory中的反射将bean进行实例化了。 + +但是根据我们开发经验,我们有时候会将数据源的一些配置信息(DataSource)通过XML或者配置类进行定义。并且会将属性值通过 ${} 占位符的方式去引入外部文件赋值,如下图: + +![image-20210819164438580](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819164438580.png) + +但是我们通过BeanDefinitionReader解析配置文件保存至BeanDefinition这个过程中并没有去解析`` ${}`` 占位符中的值,BeanDefinition中保存的还是``${jdbc.url}``这样的属性值。那么这个时候我们肯定是不能直接通过BeanFactory去实例化的,而是通过BeanFactoryPostProcessor去进行后置处理,解析占位符,再通过BeanFactory去实例化。 + +## 1.3 Bean实例化、填充属性、Aware接口 + +通过如上步骤我们将bean的定义信息全部解析完毕保存至BeanDefinition中并通过BeanFactoryPostProcessor进行增强后,就可以进行实例化和初始化。 + +实例化没啥好说就是通过反射去new对象,但是此时的bean中的属性值都是空的,赋值操作是由初始化来完成的。 + +**初始化又分为两步**: + +- 属性赋值 +- 执行bean所实现Aware接口的方法。 + +![image-20210819164521220](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819164521220.png) + +属性赋值很简单我们就不说了,主要看看什么叫执行bean所实现的Aware接口方法。 + +**执行bean所实现的Aware接口方法**: + +Aware接口的意义在于方便通过Spring中的Bean对象来获取其在容器中对应的相关属性值 + +**定义一个对象并实现Aware接口** + +```java +public class Teacher implements EnvironmentAware , BeanNameAware { + /** + * bean的一些基础属性(业务层面的属性) + */ + private String teacherName; + private Integer age; + + /** + * bean在容器中的属性 + */ + private Environment environment; + private String beanName; + + /** + * 通过EnvironmentAware设置该Bean在容器中的环境 + * @param environment + */ + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + public Environment getEnvironment() { + return environment; + } + + /** + * 设置该Bean再容器中的名称 + * @param s + */ + @Override + public void setBeanName(String s) { + this.beanName = s; + } + + public String getBeanName() { + return beanName; + } +} +``` + +**获取bean在容器中的属性值** + +```java +@Test +public void test_aware(){ + ApplicationContext ac = new AnnotationConfigApplicationContext(JdbcConfiguration.class);//加载配置类 + Teacher bean = ac.getBean("teacher", Teacher.class); + //Aware接口的意义在于方便通过Spring中的Bean对象来获取其在容器中对应的相关属性值 + String beanName = bean.getBeanName();//bean的名称 + Environment environment = bean.getEnvironment();//环境 + System.out.println(beanName); + System.out.println(environment); +} +``` + +![image-20210819164555973](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819164555973.png) + +## 1.4 BeanPostProcessor接口 / init-method + +![image-20210819164617452](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819164617452.png) + +- 如果Bean实现了**BeanPostProcess**接口,Spring将调用它们的**postProcessBeforeInitialization**方法 + +- - (作用是在Bean实例创建成功后对进行增强处理,如对Bean进行修改,增加某个功能) + +- 如果Bean声明了**init-method方法**则会进行调用,完成初始化 + +- 如果Bean实现了**BeanPostProcess**接口,Spring将调用它们的**postProcessAfterInitialization**方法 + +- - (和执行before方法一样都是进行增强 ,只不过一个在初始化之前,一个在初始化方法之后) + +## 1.5 destory-method销毁方法-完整的Bean声明周期结束 + +![image-20210819164643740](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819164643740.png) + +如果该Bean声明了**destory-method**方法则会进行调用销毁。但是对于我们高级语言java来说,我们日常很少会声明销毁,都是通过垃圾回收进行解决。 + + + +# 二. Bean的生命周期 源码解析 + +## 2.1 准备工作 + +**定义一个对象** + +```java +public class Teacher{ + + private String teacherName; + private Integer age; + + ...省略get/set方法..... +} +``` + +**通过XML文件创建Bean及注入属性** + +```java + + + + + + + + + +``` + +**测试加载Bean** + +```java +@Test +public void test_load_bean(){ + ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");//加载XML + Teacher teacher = ac.getBean("teacher", Teacher.class); + System.out.println(teacher.getTeacherName()); + System.out.println(teacher.getAge()); +} +``` + +## 2.2 ClassPathXmlApplicationContext方法 + +```java +public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { + super(parent); + //configLocations参数也就是1.6.1准备工作中的创建Bean的XML文件 + //将XML配置文件设置到类的成员属性中方便后面的读取和加载 + this.setConfigLocations(configLocations); + if (refresh) { + //refresh方法刷新Spring应用的上下文,这个方法里面包含了所有Bean生命周期的方法 + this.refresh(); + } +} +``` + +## 2.3 验证 + +本节``refresh``方法我们只去找创建bean的方法(createBean),其他的暂时我们不去研究 + +### 2.3.1 refresh():方法 + +```java +public void refresh() throws BeansException, IllegalStateException { + synchronized(this.startupShutdownMonitor) { + /** + * (不重要 知道是准备工作即可) + * prepareRefresh()方法 做容器刷新前的准备工作: + * 1、设置容器的启动时间 + * 2、设置活跃状态为true + * 3、设置关闭状态为false + * 4、获取Environment对象,并加载当前系统的属性值到Environment对象中 + * 5、准备监听器和事件的集合对象,默认为空的集合 + */ + this.prepareRefresh(); + + //(重要)创建BeanFactory工厂,并加载XML配置文件中的属性到当前工厂中的BeanDefinition + ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory(); + + //(不重要)对上一步创建的beanFactory做一些初始化属性填充的准备工作 + this.prepareBeanFactory(beanFactory); + + try { + //(不重要)源码中该方法内部为空,是留给它子类做一些扩展用的。 + this.postProcessBeanFactory(beanFactory); + + //(重要)invoke是执行的意思 那该方法也就是执行beanFactoryPostProcessors(beanFactory的后置处理器) + //我们之前所说的将xml文件的属性值的占位符解析成实际的值等扩展操作都是在这里执行。 + this.invokeBeanFactoryPostProcessors(beanFactory); + + //(有点重要)注册beanPostProcessors,这里只是注册(准备出来) + //按之前的流程图来说本来应该实例化对象了,但是实例化之后的初始化操作中需要beanPostProcessors,所以我们在实例化之前先将他准备出来 + //注意:这里是beanPostProcessors和上面的beanFactoryPostProcessors不一样,所以这里只是注册,真正的调用是在getBean()的时候调用 + this.registerBeanPostProcessors(beanFactory); + + //(不重要)为上下文初始化message源,也就是不同语言的消息体,国际化处理 + this.initMessageSource(); + + //(有点重要)初始化事件监听多路广播器 + //如果我们现在bean生命周期不同的阶段做不同的事情我们需要通过观察者模式:监听器、监听事件、多播器来实现,所以这里先进性初始化 + this.initApplicationEventMulticaster(); + + //(不重要)一个空方法,留给子类来初始化其他bean的 + this.onRefresh(); + + //(有点重要)注册监听器,也属于实例化bean的一些前期准备工作 + this.registerListeners(); + + //(重要)实例化所有的单例bean(非懒加载的bean) + this.finishBeanFactoryInitialization(beanFactory); + + //最后一步:发布相应的事件。 + this.finishRefresh(); + } catch (BeansException var9) { + if (this.logger.isWarnEnabled()) { + this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9); + } + + this.destroyBeans(); + this.cancelRefresh(var9); + throw var9; + } finally { + this.resetCommonCaches(); + } + + } +} +``` + +### 2.3.2 freshBeanFactory():创建bean工厂并加载xml文件到BeanDefinition + +``ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();`` + +创建BeanFactory工厂,并加载XML配置文件中的属性到当前工厂中的BeanDefinition + +![image-20210819165049320](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819165049320.png) + +**prepareBeanFactory()** + +进入到该prepareBeanFactory方法中可以找到下图的``refreshBeanFactory``方法,该方法中就是通过``loadBeanDefinitions(beanFactory)``方法去加载BeanDefinition。 + +![image-20210819165107806](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819165107806.png) + +**loadBeanDefinitions()** + +那么就是通过``XmlBeanDefinitionReader()``方法解析xml文件中的定义并保存到BeanDefinition中 + +![image-20210819170633093](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819170633093.png) + +### 2.3.3 finishBeanFactoryInitialization():完成上下文bean工厂的初始化 + +实例化所有**非懒加载的单例bean** + +```java +/** +* 完成此上下文的bean工厂的初始化, +* 初始化所有剩余的单例bean。 +*/ +protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { + // (不用看)为此上下文初始化转换服务。 + if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) && + beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) { + beanFactory.setConversionService( + beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)); + } + + // (不用看)如果beanFactory之前没有注册嵌入值解析器,则注册默认的嵌入值解析器,主要用于注解属性值的解析 + if (!beanFactory.hasEmbeddedValueResolver()) { + beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal)); + } + + // (不用看)尽早初始化loadTimeAware bean,以便尽早注册它们的转换器 + String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false); + for (String weaverAwareName : weaverAwareNames) { + getBean(weaverAwareName); + } + + //(不用看)禁止使用临时类加载器进行类型匹配 + beanFactory.setTempClassLoader(null); + + //(不用看)冻结所有bean的定义,说明注册的bean定义将不被修改或任何进一步的处理。 + beanFactory.freezeConfiguration(); + + //(重要)这一步开始去真正的去实例化剩下的所有非懒加载的单例对象 + beanFactory.preInstantiateSingletons(); +} +``` + +**preInstantiateSingletons()** + +实例化之前再做一些判断和前期准备工作 + +```java +public void preInstantiateSingletons() throws BeansException { + if (logger.isTraceEnabled()) { + logger.trace("Pre-instantiating singletons in " + this); + } + + //将所有beanDefinition的名字创建成一个集合,其实也就是bean的名称 + List beanNames = new ArrayList(this.beanDefinitionNames); + + //遍历该集合。初始化所有非懒加载的单例bean + for (String beanName : beanNames) { + //合并父类beanDefinition + RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); + //条件判断:是否是抽象的、是否是单例、是否是懒加载 + if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { + //判断该bean是否实现了FactoryBean接口 + if (isFactoryBean(beanName)) { + //FACTORY_BEAN_PREFIX 是定义的常量 & + //也就是根据 & + beanName来获取具体的bean + Object bean = getBean(FACTORY_BEAN_PREFIX + beanName); + // 判断bean是否等于 FactoryBean + if (bean instanceof FactoryBean) { + final FactoryBean factory = (FactoryBean) bean; + //判断这个beanFactory是否急切的需要实例化的标识 + boolean isEagerInit; + if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) { + isEagerInit = AccessController.doPrivileged((PrivilegedAction) + ((SmartFactoryBean) factory)::isEagerInit, + getAccessControlContext()); + } + else { + isEagerInit = (factory instanceof SmartFactoryBean && + ((SmartFactoryBean) factory).isEagerInit()); + } + //如果急切实例化则通过beanName获取实例化bean + if (isEagerInit) { + getBean(beanName); + } + } + } + else { + //如果beanName对应的bean不是FactoryBean类型,或者说没有实现FactoryBean接口,只是一个普通bean,则执行getBean() + //getBean()是去看容器中存不存在该beanName的bean实例,有直接获取,没有则创建 + getBean(beanName); + } + } + } + + //遍历beanNames,触发所有SmartInitializingSingleton的后初始化回调 + for (String beanName : beanNames) { + //获取beanName对应的bean实例 + Object singletonInstance = getSingleton(beanName); + if (singletonInstance instanceof SmartInitializingSingleton) { + final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance; + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedAction) () -> { + smartSingleton.afterSingletonsInstantiated(); + return null; + }, getAccessControlContext()); + } + else { + smartSingleton.afterSingletonsInstantiated(); + } + } + } +} +``` + +### 2.3.4 getBean():获取bean + +**getBean()** + +```java +public Object getBean(String name) throws BeansException { + //此方法才是真正实例化bean的方法,前面都是前期准备工作,也是依赖注入的主要方法 + //Spring源码中,do开头的方法才是真正干活的方法 + return doGetBean(name, null, null, false); +} +``` + +**doGetBean()** + +此方法才是真正去调用(仅仅是调用)实例化bean的方法,前面都是前期准备工作,同时该方法也是依赖注入的主要方法 + +ps:Spring源码中,do开头的方法才是真正干活的方法 + +此方法较长,包含需多循环依赖的东西,可以暂时不看,后面会单独将,只需要找到一个**createBean()**方法即可 + +```java +protected T doGetBean(final String name, @Nullable final Class requiredType, + @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { + //beanName转换 + final String beanName = transformedBeanName(name); + Object bean; + + // 提前检查单例缓存中是否有手动注册的单例对象,此处跟循环依赖有关 + Object sharedInstance = getSingleton(beanName); + if (sharedInstance != null && args == null) { + if (logger.isTraceEnabled()) { + if (isSingletonCurrentlyInCreation(beanName)) { + logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + + "' that is not fully initialized yet - a consequence of a circular reference"); + } + else { + logger.trace("Returning cached instance of singleton bean '" + beanName + "'"); + } + } + //返回对象的实例,当我们的bean实现了FactoryBean接口,需要获取具体的对象的时候就需要此方法来进行获取 + bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); + } + + else { + // 当对象都是单例的时候会尝试解决循环依赖的问题,但是原型模式下如果存在循环依赖的情况,那么直接抛出异常 + if (isPrototypeCurrentlyInCreation(beanName)) { + throw new BeanCurrentlyInCreationException(beanName); + } + + // 获取父容器 + BeanFactory parentBeanFactory = getParentBeanFactory(); + //如果beanDefinitionMap中也就是在所有已经加载的类中不包含beanName,那么就尝试从父容器中获取 + if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { + // Not found -> check parent. + String nameToLookup = originalBeanName(name); + if (parentBeanFactory instanceof AbstractBeanFactory) { + return ((AbstractBeanFactory) parentBeanFactory).doGetBean( + nameToLookup, requiredType, args, typeCheckOnly); + } + else if (args != null) { + return (T) parentBeanFactory.getBean(nameToLookup, args); + } + else if (requiredType != null) { + return parentBeanFactory.getBean(nameToLookup, requiredType); + } + else { + return (T) parentBeanFactory.getBean(nameToLookup); + } + } + //如果不是做类型检查,那么表示要创建bean,此处在集合中做一个记录 + if (!typeCheckOnly) { + markBeanAsCreated(beanName); + } + + try { + //此处做了BeanDefinition对象的转换,当我们从xml文件中加载beanDefinition对象的时候,封装的对象是GenericBeanDefinition + //此处要做类型转换,如果是子类bean的话,会合并父类的相关属性 + final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + checkMergedBeanDefinition(mbd, beanName, args); + + //如果存在依赖的bean的话,那么则优先实例化依赖的bean + String[] dependsOn = mbd.getDependsOn(); + if (dependsOn != null) { + //如果存在依赖,则需要递归实例化依赖的bean + for (String dep : dependsOn) { + if (isDependent(beanName, dep)) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); + } + //注册各个bean的依赖关系,以便进行销毁 + registerDependentBean(dep, beanName); + try { + getBean(dep); + } + catch (NoSuchBeanDefinitionException ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "'" + beanName + "' depends on missing bean '" + dep + "'", ex); + } + } + } + + //(本小节重点,前面可以不看)创建单例模式bean的实例对象 + if (mbd.isSingleton()) { + sharedInstance = getSingleton(beanName, () -> { + try { + //这里去创建bean + return createBean(beanName, mbd, args); + } + catch (BeansException ex) { + destroySingleton(beanName); + throw ex; + } + }); + bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); + } + //创建原型模式的bean实例对象 + else if (mbd.isPrototype()) { + Object prototypeInstance = null; + try { + beforePrototypeCreation(beanName); + prototypeInstance = createBean(beanName, mbd, args); + } + finally { + afterPrototypeCreation(beanName); + } + bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); + } + +//===============================省略一堆无用代码==================================== + + return (T) bean; +} +``` + +### 2.3.5 createBean():创建Bean + +**createBean()** + +之前我们说过,spring源码中真正干活的都是带do的方法,那么我们只需要在当前方法中找到``doCreateBean``即可。 + +```java +protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) + throws BeanCreationException { + + if (logger.isTraceEnabled()) { + logger.trace("Creating instance of bean '" + beanName + "'"); + } + RootBeanDefinition mbdToUse = mbd; + + //锁定class,根据设置的class属性或者根据className来解析class + Class resolvedClass = resolveBeanClass(mbd, beanName); + if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) { + mbdToUse = new RootBeanDefinition(mbd); + mbdToUse.setBeanClass(resolvedClass); + } + + //验证及准备覆盖的方法 + try { + mbdToUse.prepareMethodOverrides(); + } + catch (BeanDefinitionValidationException ex) { + throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(), + beanName, "Validation of method overrides failed", ex); + } + + try { + // 给BeanPostProcessors一个机会来返回代理来替代真正的实例 + Object bean = resolveBeforeInstantiation(beanName, mbdToUse); + if (bean != null) { + return bean; + } + } + catch (Throwable ex) { + throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, + "BeanPostProcessor before instantiation of bean failed", ex); + } + + try { + //(此小节重点方法)真正实际创建bean的调用 + Object beanInstance = doCreateBean(beanName, mbdToUse, args); + if (logger.isTraceEnabled()) { + logger.trace("Finished creating instance of bean '" + beanName + "'"); + } + return beanInstance; + } + catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { + throw ex; + } + catch (Throwable ex) { + throw new BeanCreationException( + mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex); + } +} +``` + +**doCreateBean()** + +ok!见到亲人了,终于找到真正干活的人了 + +```java +protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) + throws BeanCreationException { + + // 这个beanWrapper是用来持有创建出来的bean对象的 + BeanWrapper instanceWrapper = null; + if (mbd.isSingleton()) { + //如果是单例对象,从factoryBean实例缓存中移除当前bean定义信息 + instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); + } + if (instanceWrapper == null) { + //(重要,通过反射创建实例)根据执行bean使用对应的策略创建新的实例,如:工厂方法、构造函数主动注入、简单初始化 + instanceWrapper = createBeanInstance(beanName, mbd, args); + } + //创建完成后的实例 + final Object bean = instanceWrapper.getWrappedInstance(); + Class beanType = instanceWrapper.getWrappedClass(); + if (beanType != NullBean.class) { + mbd.resolvedTargetType = beanType; + } + + // 允许后置处理器postPorcessor修改合并的bean定义 + synchronized (mbd.postProcessingLock) { + if (!mbd.postProcessed) { + try { + //应用MergedBeanDefinitionPostProcessor + applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); + } + catch (Throwable ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Post-processing of merged bean definition failed", ex); + } + mbd.postProcessed = true; + } + } + + //==========省略一堆无用代码=========================== + + return exposedObject; +} +``` + +**createBeanInstance()** + +```java +protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { + //确认需要创建的bean实例的类可以实例化 + Class beanClass = resolveBeanClass(mbd, beanName); + + if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Bean class isn't public, and non-public access not allowed: " + beanClass.getName()); + } + + Supplier instanceSupplier = mbd.getInstanceSupplier(); + if (instanceSupplier != null) { + return obtainFromSupplier(instanceSupplier, beanName); + } + + //如果工厂方法不为空则使用工厂方法初始化策略 + if (mbd.getFactoryMethodName() != null) { + return instantiateUsingFactoryMethod(beanName, mbd, args); + } + + // Shortcut when re-creating the same bean... + boolean resolved = false; + boolean autowireNecessary = false; + if (args == null) { + synchronized (mbd.constructorArgumentLock) { + //一个类中有多个构造函数,每个构造函数都有不同的参数,所以调用前需要现根据参数锁定构造函数或对应的工厂方法 + if (mbd.resolvedConstructorOrFactoryMethod != null) { + resolved = true; + autowireNecessary = mbd.constructorArgumentsResolved; + } + } + } + //如果已经解析过则使用解析好的构造函数方法,不需要再次锁定 + if (resolved) { + if (autowireNecessary) { + //(重要)构造函数自动注入 + return autowireConstructor(beanName, mbd, null, null); + } + else { + //(重要)使用默认构造函数构造 + return instantiateBean(beanName, mbd); + } + } + + // 需要根据参数解析构造函数 + Constructor[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); + if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR || + mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { + return autowireConstructor(beanName, mbd, ctors, args); + } + + // Preferred constructors for default construction? + ctors = mbd.getPreferredConstructors(); + if (ctors != null) { + //(重要)构造函数自动注入 + return autowireConstructor(beanName, mbd, ctors, null); + } + + //(重要)使用默认构造函数构造 + return instantiateBean(beanName, mbd); +} +``` + +**instantiateBean()** + +那我们主要来看这个使用默认构造函数构造的方法即可 + +```java +protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) { + try { + Object beanInstance; + final BeanFactory parent = this; + if (System.getSecurityManager() != null) { + beanInstance = AccessController.doPrivileged((PrivilegedAction) () -> + getInstantiationStrategy().instantiate(mbd, beanName, parent), + getAccessControlContext()); + } + else { + //getInstantiationStrategy():获取实例化的策略 + //instantiate():进行实例化 + //得到实例化后的bean,所以我们再来看看这个instantiate()方法是如何进行实例化 + beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent); + } + BeanWrapper bw = new BeanWrapperImpl(beanInstance); + initBeanWrapper(bw); + return bw; + } + catch (Throwable ex) { + throw new BeanCreationException( + mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex); + } +} +``` + +**instantiate()** + +```java +public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) { + //如果需要覆盖或者动态替换的方法则使用cglig进行动态代理,因为可以在创建代理的同时将动态方法织入类中,但是如果没有需要动态改变的方法,为了方便直接反射就可以了 + if (!bd.hasMethodOverrides()) { + //此处获取到指定的构造器对bean进行实例化 + Constructor constructorToUse; + synchronized (bd.constructorArgumentLock) { + constructorToUse = (Constructor) bd.resolvedConstructorOrFactoryMethod; + if (constructorToUse == null) { + final Class clazz = bd.getBeanClass(); + if (clazz.isInterface()) { + throw new BeanInstantiationException(clazz, "Specified class is an interface"); + } + try { + if (System.getSecurityManager() != null) { + constructorToUse = AccessController.doPrivileged( + (PrivilegedExceptionAction>) clazz::getDeclaredConstructor); + } + else { + //根据beanClass获取到构造器 + constructorToUse = clazz.getDeclaredConstructor(); + } + bd.resolvedConstructorOrFactoryMethod = constructorToUse; + } + catch (Throwable ex) { + throw new BeanInstantiationException(clazz, "No default constructor found", ex); + } + } + } + //(重要)使用BeanUtils工具类根据bean的构造器进行反射实例化并返回 + return BeanUtils.instantiateClass(constructorToUse); + } + else { + // Must generate CGLIB subclass. + return instantiateWithMethodInjection(bd, beanName, owner); + } +} +``` + +**BeanUtils.instantiateClass(constructorToUse)** + +```java +public static T instantiateClass(Constructor ctor, Object... args) throws BeanInstantiationException { + Assert.notNull(ctor, "Constructor must not be null"); + try { + ReflectionUtils.makeAccessible(ctor); + if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) { + return KotlinDelegate.instantiateClass(ctor, args); + } + else { + Class[] parameterTypes = ctor.getParameterTypes(); + Assert.isTrue(args.length <= parameterTypes.length, "Can't specify more arguments than constructor parameters"); + Object[] argsWithDefaultValues = new Object[args.length]; + for (int i = 0 ; i < args.length; i++) { + if (args[i] == null) { + Class parameterType = parameterTypes[i]; + argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null); + } + else { + argsWithDefaultValues[i] = args[i]; + } + } + //(重要)通过bean的构造器进行实例化,到这里实例化bean的操作就结束了 + return ctor.newInstance(argsWithDefaultValues); + } + } + catch (InstantiationException ex) { + throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex); + } + catch (IllegalAccessException ex) { + throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex); + } + catch (IllegalArgumentException ex) { + throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex); + } + catch (InvocationTargetException ex) { + throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException()); + } +} +``` + +这个时候我们创建完bean回到``doCreateBean()``方法查看创建的bean可以发现,仅仅是进行了实例化,并没有进行属性赋值 + +![image-20210819171239341](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819171239341.png) + +### 2.3.6 popelateBean():Bean的属性赋值 + +在刚刚的``doCreateBean()``方法中我们已经获取到了实例化的bean,并且发现是没有进行属性赋值的,所以我们还在当前``doCreateBean()``方法中找到``popelateBean()``属性赋值方法。 + +**再来看看doCreateBean()方法** + +```java +protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) + throws BeanCreationException { + + // 这个beanWrapper是用来持有创建出来的bean对象的 + BeanWrapper instanceWrapper = null; + if (mbd.isSingleton()) { + //如果是单例对象,从factoryBean实例缓存中移除当前bean定义信息 + instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); + } + if (instanceWrapper == null) { + //(重要,通过反射创建实例)根据执行bean使用对应的策略创建新的实例,如:工厂方法、构造函数主动注入、简单初始化 + instanceWrapper = createBeanInstance(beanName, mbd, args); + } + //创建完成后的实例bean + final Object bean = instanceWrapper.getWrappedInstance(); + Class beanType = instanceWrapper.getWrappedClass(); + if (beanType != NullBean.class) { + mbd.resolvedTargetType = beanType; + } + + // 允许后置处理器postPorcessor修改合并的bean定义 + synchronized (mbd.postProcessingLock) { + if (!mbd.postProcessed) { + try { + //应用MergedBeanDefinitionPostProcessor + applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); + } + catch (Throwable ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Post-processing of merged bean definition failed", ex); + } + mbd.postProcessed = true; + } + } + + //判断当前bean是否需要提前曝光:单例&允许循环依赖&当前bean正在创建中,检测循环依赖 + boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && + isSingletonCurrentlyInCreation(beanName)); + if (earlySingletonExposure) { + if (logger.isTraceEnabled()) { + logger.trace("Eagerly caching bean '" + beanName + + "' to allow for resolving potential circular references"); + } + //为避免后期循环依赖,可以在bean初始化完成前将创建实例的ObjectFactory加入工厂 + addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); + } + + // 初始化bean + Object exposedObject = bean; + try { + //(重要)对bean的属性进行填充,将各个属性值注入,其中。可能存在依赖于其他bean的属性,则会递归初始化依赖的bean + //只需要知道这里进行bean的一些普通属性填充即可 + populateBean(beanName, mbd, instanceWrapper); + //(重要)执行初始化逻辑 + exposedObject = initializeBean(beanName, exposedObject, mbd); + } + + //=====================省略一些无用代码========================= + + return exposedObject; +} +``` + +![image-20210819171325907](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819171325907.png) + +该bean只去填充了一些普通属性,对于实现的Aware接口的方法不在此处进行调用。 + +### 2.3.7 invokeAwareMethods():执行bean所实现Aware接口方法 + +那么对于bean实现的Aware接口的方法在何处进行调用?其实就在``populateBean()``方法的下面,``initializeBean()``当中,我们可以来看一下 + +**initializeBean()** + +```java +protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedAction) () -> { + invokeAwareMethods(beanName, bean); + return null; + }, getAccessControlContext()); + } + else { + //(重要)执行bean所实现Aware接口的方法 + invokeAwareMethods(beanName, bean); + } + + Object wrappedBean = bean; + if (mbd == null || !mbd.isSynthetic()) { + //如果bean实现了BeanPostProcessors接口则调用该before方法进行一些额外的扩展增强操作 + wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); + } + + try { + //(重要)执行初始化方法 + invokeInitMethods(beanName, wrappedBean, mbd); + } + catch (Throwable ex) { + throw new BeanCreationException( + (mbd != null ? mbd.getResourceDescription() : null), + beanName, "Invocation of init method failed", ex); + } + if (mbd == null || !mbd.isSynthetic()) { + //(重要)如果bean实现了BeanPostProcessors接口则调用该after方法进行一些额外的扩展增强操作 + wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); + } + + return wrappedBean; +} +``` + +![image-20210819171416527](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819171416527.png) + +执行完``invokeAwareMethods()``方法后我们发现,已经执行了bean所实现的Aware接口中的方法,并完成属性的赋值。 + +所以也证实了bean所实现的Aware接口中的方法的调用是在bean属性填充操作后去执行的。 + +**PS**:environmen为什么没有调用赋值?是因为spring源码在前期的准备工作者屏蔽了environment,他应该在``beanPostProcessors``的``before``方法中去调用。 + +### 2.3.8 beanPostProcessors的before方法 + +before方法的全称其实叫:**applyBeanPostProcessorsBeforeInitialization()** + +before方法也在``initializeBean()``方法中,就在``invokeAwareMethods方法``的下面 + +如果bean实现了beanPostProcessors接口则会执行``before方法``做一些扩展增强操作,另外environmentAware也会在此处进行调用赋值 + +**initializeBean()** + +```java +protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedAction) () -> { + invokeAwareMethods(beanName, bean); + return null; + }, getAccessControlContext()); + } + else { + //(重要)执行bean所实现Aware接口的方法 + invokeAwareMethods(beanName, bean); + } + + Object wrappedBean = bean; + if (mbd == null || !mbd.isSynthetic()) { + //(重要)如果bean实现了BeanPostProcessors接口则调用该before方法进行一些额外的扩展增强操作 + wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); + } + + try { + //(重要)执行初始化方法 + invokeInitMethods(beanName, wrappedBean, mbd); + } + catch (Throwable ex) { + throw new BeanCreationException( + (mbd != null ? mbd.getResourceDescription() : null), + beanName, "Invocation of init method failed", ex); + } + if (mbd == null || !mbd.isSynthetic()) { + //(重要)如果bean实现了BeanPostProcessors接口则调用该after方法进行一些额外的扩展增强操作 + wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); + } + + return wrappedBean; +} +``` + +我们就不进before方法当中去看了,里面无非就是获取bean所实现的BeanPostProcessors接口方法然后去执行。 + +### 2.3.9 init-method:执行初始化方法 + +初始化方法的全称其实叫:**invokeInitMethods()** + +``init-method方法``也在``initializeBean()``方法中,就在``before方法``的下面 + +**initializeBean()** + +```java +protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedAction) () -> { + invokeAwareMethods(beanName, bean); + return null; + }, getAccessControlContext()); + } + else { + //(重要)执行bean所实现Aware接口的方法 + invokeAwareMethods(beanName, bean); + } + + Object wrappedBean = bean; + if (mbd == null || !mbd.isSynthetic()) { + //(重要)如果bean实现了BeanPostProcessors接口则调用该before方法进行一些额外的扩展增强操作 + wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); + } + + try { + //(重要)执行初始化方法 + invokeInitMethods(beanName, wrappedBean, mbd); + } + catch (Throwable ex) { + throw new BeanCreationException( + (mbd != null ? mbd.getResourceDescription() : null), + beanName, "Invocation of init method failed", ex); + } + if (mbd == null || !mbd.isSynthetic()) { + //(重要)如果bean实现了BeanPostProcessors接口则调用该after方法进行一些额外的扩展增强操作 + wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); + } + + return wrappedBean; +} +``` + +我们就不进``init-method方法``当中去看了,里面无非就是执行用户自定义的``init-method方法``并且执行 + +### 2.3.10 beanPostProcessors的after方法 + +after方法的全称其实叫:**applyBeanPostProcessorsAfterInitialization()** + +after方法也在``initializeBean()``方法中,就在``invokeInitMethods方法``的下面 + +如果bean实现了beanPostProcessors接口则会执行``after方法``做一些扩展增强操作 + +**initializeBean()** + +```java +protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedAction) () -> { + invokeAwareMethods(beanName, bean); + return null; + }, getAccessControlContext()); + } + else { + //(重要)执行bean所实现Aware接口的方法 + invokeAwareMethods(beanName, bean); + } + + Object wrappedBean = bean; + if (mbd == null || !mbd.isSynthetic()) { + //(重要)如果bean实现了BeanPostProcessors接口则调用该before方法进行一些额外的扩展增强操作 + wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); + } + + try { + //(重要)执行初始化方法 + invokeInitMethods(beanName, wrappedBean, mbd); + } + catch (Throwable ex) { + throw new BeanCreationException( + (mbd != null ? mbd.getResourceDescription() : null), + beanName, "Invocation of init method failed", ex); + } + if (mbd == null || !mbd.isSynthetic()) { + //(重要)如果bean实现了BeanPostProcessors接口则调用该after方法进行一些额外的扩展增强操作 + wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); + } + + return wrappedBean; +} +``` + +我们就不进after方法当中去看了,里面无非就是获取bean所实现的BeanPostProcessors接口方法然后去执行。 + +**至此我们获得一个完整的bean对象** + +## 2.4 总结 + +总结我们还是按照之前画的流程图来总结吧 + +![image-20210819171723890](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819171723890.png) + +1. 我们首先都会在xml中定义bean,或者直接通过注解,这里我们之说xml的情况。 + +2. 首先会通过**BeanDefinitionReader**(根据不同的bean定义方式来选择响应的实现类)来解析xml文件并保存至**BeanDefinition**中。 + +3. 然后由于实际开发中我们经常会在xml配置文件定义bean时使用占位符的方式引入外部属性值,所以此时保存到BeanDefinition的属性值会存在为解析的占位符的情况。这种情况我们肯定不能将他直接通过BeanFactory去反射实例化。所以我们会通过BeanFactoryPostProcessor来对bean进行实例化前的扩展增强,这其中就包含了解析占位符的操作。 + +4. 得到完整的一个BeanDefinition后我们就可以通过**反射的方式实例化bean**,注意:实例化的都是懒加载的bean。 + +5. 接着进入初始化步骤: + +6. 1. bean的**属性填充** + 2. 如果bean实现了**Aware接口**中的方法则在属性填充后进行方法调用并赋值 + 3. 如果bean实现了**BeanPostProcessor接口的before方法**则进行调用增强 + 4. 如果bean定义了**init-method方法**则进行调用此初始化方法 + 5. 如果bean实现了**BeanPostProcessor接口的after方法**则进行调用增强 + +7. 获取到**完整的bean实例** + +8. 如果bean定义了**destory-method销毁方法**则还会进行销毁,但一般我们都通过java的垃圾回收解决此问题 + + + +# 三. 循环依赖 + +## 3.1 什么是循环依赖? + +**循环依赖分为三种**:自身依赖于自身、互相循环依赖、多组循环依赖。 + +![image-20210819171809288](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819171809288.png) + +但无论循环依赖的数量有多少,循环依赖的本质是一样的。就是你的完整创建依赖于我,而我的完整创建也依赖于你,但我们互相没法解耦,最终导致依赖创建失败。 + +所以 Spring 提供了除了构造函数注入和原型注入外的,setter循环依赖注入解决方案。那么我们也可以先来尝试下这样的依赖,如果是我们自己处理的话该怎么解决。 + +## 3.2 测试循环依赖 + +### 3.2.1 准备实体 + +准备两个实体A和B,A中注入B实体属性,B中注入A实体属性 + +```java +//实体A +public class A { + private B b; + + public void setB(B b) { + this.b = b; + } +} + +//实体B +public class B { + private A a; + + public void setA(A a) { + this.a = a; + } +} +``` + +### 3.2.2 XML文件创建Bean并属性注入 + +```java + + + + + + + + + + + +``` + +### 3.2.3 测试及结果分析 + +按刚刚``3.1章节``我们分析什么是循坏依赖时的想法,这时候我们加载XML文件他会去实例化A和B,然后抛出循环依赖的异常,那我们来测试一下 + +![image-20210819171937666](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819171937666.png) + +我们发现,它最后正常的实例化出了A和B实例,并没有发生循环依赖。这是为什么呢?我们可以看下一节 ``3.3单例的setter注入如何解决循环依赖`` + +## 3.2 循环依赖的几种场景 + +![image-20210819172017667](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172017667.png) + +## 3.3 单例的setter注入如何解决循环依赖 + +### 3.3.1 为什么正常情况下Spring不会出现循环依赖问题 + +还记得我们在最开始跟踪Spring源码分析bean的生命周期时有一个``preInstantiateSingletons()``方法,该方法再去调用``getBean()``方法之前有一个判断,判断当前bean是否是抽象的、单例的、懒加载的。所以我们实例化的bean都是单例非懒加载的。 + +![image-20210819172126743](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172126743.png) + +**重要**: + +那么再回头看一下3.2.2小节我们给bean属性注入的方式是set方法。 + +那么其实在Spring中对于单例set注入的bean是默认解决了循环依赖问题的。解决方法是 实例提前暴露+三层缓存 + +但是前提必须是单例的setter注入,如果是多例setter注入或者是单例/多例构造器注入都是无法解决的。 + +### 3.3.2 Spring默认针对单例setter注入是如何解决循环依赖的 + +针对于之前我们了解的bean生命周期得出。bean的实例化操作和属性赋值(初始化)操作是分开的。我们先实例化得到一个bean的半成品然后去属性赋值,或者说注入其他实体。 + +那么我们首先加载实体A,第一次肯定从缓存中获取不到,所以去创建实例,然后提前暴露添加到三级缓存,当我们去初始化A属性注入时,发现需要实例化B,然后我们再从缓存中获取不到B,则取创建B实例,然后将B暴露添加到三级缓存,当我们去初始化属性注入时发现是实体A,我们则从三级缓存中获取到了实体A,并添加到了二级缓存。至此实体B初始化完成,得到一个完整的bean,最后将B添加到一级缓存。紧接着回到实体A,成功完成实体A的属性注入,初始化完成得到一个完整的bean,并将A添加到一级缓存。至此bean加载结束。完美解决了循环依赖的问题。 + +![image-20210819172206521](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172206521.png) + +### 3.3.3 什么是三级缓存 + +**三级缓存的定义** + +![image-20210819172227057](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172227057.png) + +**为什么三级缓存保存的是ObjectFactory对象?直接保存实例对象不行?** + +ObjectFactory对象其实是getEarlyBeanReference函数表达式,执行该表达式可以获取到bean早期的bean引用,并判断该bean是否需要代理,简单地说就是,如果一个bean我们定义了他需要代理,那么通过三级缓存的函数表达式执行获取到的就是代理对象,如果不需要代理,获取到的就是普通对象。 + +**一级缓存**:存储的是完整实例 + +**二级缓存**:存储的是半成品实例,也就是还未属性赋值的实例 + +**三级缓存**:存储的是getEarlyBeanReference()函数表达式,该表达式可以获取到bean早期的bean引用,并判断该bean是否需要代理,最终返回代理对象或普通对象 + +**为什么要三级缓存,二级行不行?** + +所以说我们只用二级缓存行不行其实取决你到底需不需要代理对象。 + +如果需要代理,我们则在创建过程中其实是创建了两个bean,一个是普通bean,一个是代理bean。**所以他就会在通过调用getEarlyBeanReference()函数表达式想要用你这个bean的时候,将代理bean覆盖掉普通bean,保证你这个bean的全局唯一**。如果说你不需要代理,那可以,你可以不需要三级缓存,二级缓存足以 + +### 3.3.4 源码验证 + +我们先把之前的流程图拿过来对照着看源码 + +![image-20210819172300357](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172300357.png) + +**首先第一步从一级缓存中获取实例** + +在``doGetBean()``方法中调用了``getSingleton()``方法从缓存中(这里单指一级缓存)获取bean。获取到bean就不去创建直接返回了。 + +![image-20210819172322779](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172322779.png) + +**getSingleton()方法** + +在方法中可看到他会根据beanName先从一级缓存中获取,如果一级缓存中不存在该bean并且该bean正在被创建的话才会去二级缓存中查找。 + +而此时我们的bean是首次创建,所以一级缓存里肯定没有,并且bean还没有进行创建,所以当从一级缓存中获取不到bean后直接返回一个空的object + +![image-20210819172343807](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172343807.png) + +**创建实例 A** + +紧接着上面从一级缓存中未获取到bean返回空后,我们再来看看**doGetBean()** 方法的后半部分 + +经过一系列判断及准备操作之后通过mbd(beanDefinition)判断是否是单例的。 + +如果是单例的则调用**getSingleton()去创建bean**,其中该方法有两个参数,以格式beanName,另一个是函数表达式,我们就理解传递了**createBean()**方法过去。 + +该``getSingletion() ``方法主要逻辑就是,**再次从一级缓存查找,找不到则调用createBean()方法去创建bean**。 + +![image-20210819172359843](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172359843.png) + +**getSingleton()** + +方法的上半部分如下图 + +![image-20210819172426038](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172426038.png) + +**doCeateBean()** + +好!现在我们知道了他要去创建bean了,那我们就来看看``doCreateBean()``方法怎么执行的, + +我们发现实例化A成功并返回了。但是注意现在还没有进行初始化,所以还没有B的依赖注入 + +![image-20210819172444018](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172444018.png) + +我们再来看看**doCreateBean()**方法的下半部分 + +我们之前提到过,创建完bean后会将这个半成品添加到三级缓存,那么就是通过**addSingletonFactory()**实现的 + +![image-20210819172508987](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172508987.png) + +**addSingletonFactory()** + +那么该方法其实就是去将创建出来的半成品bean添加到三级缓存 + +![image-20210819172522491](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172522491.png) + +**注意**:存入三级缓存中的value就是singletonFactory这个函数表达式。也就是上一步doCreateBean()方法中的 getEarlyBeanReference()方法。 + +getEarlyBeanReference方法用于获取代理对象,其实底层是通过AbstractAutoProxyCreator类的getEarlyBeanReference生成代理对象。 + +**为什么三级缓存保存的是ObjectFactory对象?直接保存实例对象不行?** + +首先通过上面我们知道了``getEarlyBeanReference函数表达式``就是传递到``addSingletonFactory()``中singletonFactory参数。该表达式作用是用于获取代理对象,那其实三级缓存存的是``getEarlyBeanReference函数表达式`` 也可以理解为bean的代理对象。那为什么不能直接保存实例对象呢?因为假如你想对添加到三级缓存中的实例对象进行增强,直接用实例对象是行不通的。所以我们需要保存代理对象。 + +**populateBean() 属性赋值-依赖注入** + +上面我们将半成品bean添加到三级缓存后,我们返回``doCreateBean方法``。将bean添加到三级缓存后,这时需要给bean进行初始化,也就是属性赋值依赖注入了。 + +关键的方法就在``populateBean()``。 + +![image-20210819172559868](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172559868.png) + +那我们进入``populateBean()``方法看一下,由于该方法前面都是一些判断和预备操作,我们直接找到该方法最后面调用``applyPropertyValues()``方法查看如何进行属性注入 + +![image-20210819172626511](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172626511.png) + +**applyPropertyValues()** + +那么这时候我们其实是给A对象去进行属性赋值,那么需要注入的属性就是B,需要注入的属性值也就是B对象再运行时bean的引用 + +那么再次调用``resolveValueIfNecessary()``方法进行属性值处理 + +![image-20210819172650664](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172650664.png) + +**resolveValueIfNecessary()** + +![image-20210819172704304](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172704304.png) + +**resolveReference()** + +很明显,这个时候我们需要给A实体的属性b赋值,所以他这里调用了``getBean()``方法去容器中找B实例,我们知道``getBean()``方法会调用``doGetBean()``方法,``doGetBean()``方法中会去一级缓存中查找bean,找不到则去创建。那我们继续往下看看**doGetBean()** + +**doGetBean()** + +![image-20210819172740493](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172740493.png) + +既然缓存没有那就实例化b呗,继续看该方法的后半段,他就去调用了``getSingleton()``方法 再次从一级缓存中获取b实例,获取不到就去调用``createBean()``方法创建 + +**getSingleton()** + +![image-20210819172805889](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172805889.png) + +**doCreateBean()** + +我们不看``createBean()``了 直接看``doCreateBean()`` + +![image-20210819172822455](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172822455.png) + +**addSingletonFactory()** + +![image-20210819172836587](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172836587.png) + +**populateBean()** + +创建完b实例的半成品并添加到三级缓存之后,我们返回``doCreateBean()``方法找到给实例b进行属性赋值操作。 + +![image-20210819172851492](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172851492.png) + +那进入方法看一下,找到``applyPropertyValues()``方法 + +![image-20210819172910520](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172910520.png) + +**applyPropertyValues()** + +![image-20210819172925167](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172925167.png) + +**resolveValueIfNecessary()** + +![image-20210819172939493](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172939493.png) + +**resolveReference()** + +![image-20210819172953142](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172953142.png) + +**doGetBean()** + +![image-20210819173005437](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173005437.png) + +**getSingleton()** + +![image-20210819173017370](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173017370.png) + +该函数表达式其实就是``getEarlyBeanReference()``方法,然后获取到A实例,注意这个A实例此时是个半成品 并没有给b属性赋值 + +**doGetBean()** + +这时我们回到给B实例属性赋值的方法 + +![image-20210819173037168](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173037168.png) + +**getSingleton()** + +现在我们已经将B实例创建好也初始化好了,现在我们返回到了``getSingleton()``方法,现在需要将B这个完成的对象添加到一级缓存 + +![image-20210819173051610](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173051610.png) + +添加到一级缓存 + +![image-20210819173110987](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173110987.png) + +**doCreateBean()** + +这时我们返回到创建A实例的``doCreateBean()``方法,此时实例A的属性赋值也全部完成 + +![image-20210819173132696](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173132696.png) + +**getSingleton()** + +现在我们已经将A实例创建好也初始化好了,现在我们返回到了getSingleton()方法,现在需要将A这个完成的对象添加到一级缓存 + +![image-20210819173146212](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173146212.png) + +![image-20210819173155325](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173155325.png) + +由于后面太乱了 截图太多不方便,所以就不继续走源码了,但是大概的逻辑大家应该都清楚了。 + +我们将实例A的属性值b赋值完后添加到一级缓存之后,我们就可以从一级缓存获取到实例A,最后完成整个对象的创建。 + +## 3.4 为什么需要三级缓存,二级行不行? + +**一级缓存**:存储的是完整实例 + +**二级缓存**:存储的是半成品实例,也就是还未属性赋值的实例 + +**三级缓存**:存储的是``getEarlyBeanReference()``函数表达式,该表达式可以获取到bean早期的bean引用,并判断该bean是否需要代理,最终返回代理对象或普通对象 + +``getEarlyBeanReference``函数表达式,执行该表达式可以获取到bean早期的bean引用,并判断该bean是否需要代理,简单地说就是,如果一个bean我们定义了他需要代理,那么通过三级缓存的函数表达式执行获取到的就是代理对象,如果不需要代理,获取到的就是普通对象。 + +所以说我们只用二级缓存行不行其实取决你到底需不需要代理对象。 + +如果需要代理,我们则在创建过程中其实是创建了两个bean,一个是普通bean,一个是代理bean。**所以他就会在通过调用getEarlyBeanReference()函数表达式想要用你这个bean的时候,将代理bean覆盖掉普通bean,保证你这个bean的全局唯一**。如果说你不需要代理,那可以,你可以不需要三级缓存,二级缓存足以 + + + +# 四. JDK动态代理源码分析 + +## 4.1 JDK动态代理的实现 + +**需要动态代理的接口** + +```java +/** + * 需要动态代理的接口 + */ +public interface Movie { + + void player(); + + void speak(); +} +``` + +**需要动态代理的接口的真实实现** + +```java +/** + * 需要动态代理接口的真实实现 + */ +public class RealMovie implements Movie { + + @Override + public void player() { + System.out.println("看个电影"); + } + + @Override + public void speak() { + System.out.println("说句话"); + } +} +``` + +**动态代理处理器** + +```java +/** + * 动态代理处理类 + */ +public class MyInvocationHandler implements InvocationHandler { + + //需要动态代理接口的真实实现类 + private Object object; + + //通过构造方法去给需要动态代理接口的真实实现类赋值 + public MyInvocationHandler(Object object) { + this.object = object; + } + + + /** + * 对真实实现的目标方法进行增强 + * 当代理对象调用真实实现类的方法时,就会执行动态代理处理器中的该invoke方法 + * + * @param proxy 生成的代理对象 + * @param method 代理对象调用的方法 + * @param args 调用的方法中的参数 + * @return + * @throws Throwable + */ + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + //方法增强 + System.out.println("卖爆米花"); + + //object是真实实现,args是调用方法的参数 + //当代理对象调用真实实现的方法,那么这里就会将真实实现和方法参数传递过去,去调用真实实现的方法 + method.invoke(object,args); + + //方法增强 + System.out.println("扫地"); + return null; + } +} +``` + +**创建代理对象** + +```java +public class DynamicProxyTest { + + public static void main(String[] args) { + // 保存生成的代理类的字节码文件 + //由于设置sun.misc.ProxyGenerator.saveGeneratedFiles 的值为true,所以代理类的字节码内容保存在了项目根目录下,文件名为$Proxy0.class + System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); + + //需要动态代理接口的真实实现 + RealMovie realMovie = new RealMovie(); + //动态代理处理类 + MyInvocationHandler handler = new MyInvocationHandler(realMovie); + //获取动态代理对象 + //第一个参数:真实实现的类加载器 + //第二个参数:真实实现类它所实现的所有接口的数组 + //第三个参数:动态代理处理器 + Movie movie = (Movie) Proxy.newProxyInstance(realMovie.getClass().getClassLoader(), + realMovie.getClass().getInterfaces(), + handler); + movie.player(); + + } + +} +``` + +**结果** + +![image-20210819173404132](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173404132.png) + +由于设置 ``sun.misc.ProxyGenerator.saveGeneratedFiles`` 的值为``true``,所以代理类的字节码内容保存在了项目根目录下,文件名为``$Proxy0.class`` + +![image-20210819173435065](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173435065.png) + +**生成的代理对象字节码文件** + +```java +public final class $Proxy0 extends Proxy implements Movie { + private static Method m1; + private static Method m3; + private static Method m2; + private static Method m4; + private static Method m0; + + static { + try { + m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); + m3 = Class.forName("com.eayon.dynamic.Movie").getMethod("player"); + m2 = Class.forName("java.lang.Object").getMethod("toString"); + m4 = Class.forName("com.eayon.dynamic.Movie").getMethod("speak"); + m0 = Class.forName("java.lang.Object").getMethod("hashCode"); + } catch (NoSuchMethodException var2) { + throw new NoSuchMethodError(var2.getMessage()); + } catch (ClassNotFoundException var3) { + throw new NoClassDefFoundError(var3.getMessage()); + } + } + + public $Proxy0(InvocationHandler var1) throws { + super(var1); + } + + /** + * 重写被代理接口的方法 + * 因为生成的代理对象会实现被代理接口,所以我们在外部可以直接通过代理对象嗲偶哦那个被代理接口中的方法 + */ + public final void speak() throws { + try { + //当外部通过代理对象调用被代理接口的方法时,其实是通过invocationHandler中的invoke()方法去调用的。 + //这个h就是invocationHandler(我们之前创建的MyInvocationHandler代理处理器) + //this就是当前这个Proxy0代理对象 + //m4则具体要调用的方法 + super.h.invoke(this, m4, (Object[])null); + } catch (RuntimeException | Error var2) { + throw var2; + } catch (Throwable var3) { + throw new UndeclaredThrowableException(var3); + } + } + + //与上面的speak方法同理,都是重写的被代理接口中的方法 + public final void player() throws { + try { + super.h.invoke(this, m3, (Object[])null); + } catch (RuntimeException | Error var2) { + throw var2; + } catch (Throwable var3) { + throw new UndeclaredThrowableException(var3); + } + } + + public final String toString() throws { + try { + return (String)super.h.invoke(this, m2, (Object[])null); + } catch (RuntimeException | Error var2) { + throw var2; + } catch (Throwable var3) { + throw new UndeclaredThrowableException(var3); + } + } + + public final int hashCode() throws { + try { + return (Integer)super.h.invoke(this, m0, (Object[])null); + } catch (RuntimeException | Error var2) { + throw var2; + } catch (Throwable var3) { + throw new UndeclaredThrowableException(var3); + } + } + + public final boolean equals(Object var1) throws { + try { + return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); + } catch (RuntimeException | Error var3) { + throw var3; + } catch (Throwable var4) { + throw new UndeclaredThrowableException(var4); + } + } +} +``` + +**代理对象的特点** + +1、代理类继承了Proxy类并且**代理对象和真实实现一样都实现了要代理的接口** + +2、重写了equals、hashCode、toString + +3、有一个静态代码块,通过反射或者代理类的所有方法 + +4、通过invoke执行代理类中的目标方法doSomething + +## 4.2 源码分析 + +从上述代码中不难看出,创建代理对象的关键代码为: + +```java +//获取动态代理对象 +//第一个参数:真实实现的类加载器 +//第二个参数:真实实现类它所实现的所有接口的数组 +//第三个参数:动态代理处理器 +Movie movie = (Movie) Proxy.newProxyInstance(realMovie.getClass().getClassLoader(), + realMovie.getClass().getInterfaces(), + handler); +``` + +然后当执行如下代码的时候,也就是当代理对象调用真实实现的方法时,会自动跳转到动态代理处理器的invoke方法来进行调用。 + +```java +movie.player(); +``` + +这是为什么呢? + +那其实归根结底都在``Proxy.newProxyInstance() ``方法创建代理对象的源码中,我们一起来看看做了些什么 + +**Proxy.newProxyInstance()** + +```java +public static Object newProxyInstance(ClassLoader loader, + Class[] interfaces, + InvocationHandler h) + throws IllegalArgumentException +{ + //判断代理处理器是否为空,为空则抛出空指针异常 + Objects.requireNonNull(h); + + //将真实实现类它所实现的所有接口的数据进行拷贝 + final Class[] intfs = interfaces.clone(); + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + checkProxyAccess(Reflection.getCallerClass(), loader, intfs); + } + + //生成接口的代理对象的字节码文件(主要方法) + Class cl = getProxyClass0(loader, intfs); + + /* + * 使用自定义的InvocationHandler作为参数,调用构造函数获取代理对象示例 + */ + try { + if (sm != null) { + checkNewProxyPermission(Reflection.getCallerClass(), cl); + } + + //通过代理对象的字节码文件获取代理对象的构造器 + final Constructor cons = cl.getConstructor(constructorParams); + final InvocationHandler ih = h; + if (!Modifier.isPublic(cl.getModifiers())) { + AccessController.doPrivileged(new PrivilegedAction() { + public Void run() { + cons.setAccessible(true); + return null; + } + }); + } + //通过代理对象的构造器调用newInstance()反射获取代理对象实例 + return cons.newInstance(new Object[]{h}); + } catch (IllegalAccessException|InstantiationException e) { + throw new InternalError(e.toString(), e); + } catch (InvocationTargetException e) { + Throwable t = e.getCause(); + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } else { + throw new InternalError(t.toString(), t); + } + } catch (NoSuchMethodException e) { + throw new InternalError(e.toString(), e); + } +} +``` + +由上述代码我们发现,主要是通过``getProxyClass0()``方法去获取或者创建代理对象的字节码文件,通过代理对象字节码文件获取其构造器并通过反射生成代理对象实例。 + +那现在的重点就是如何获取或者创建代理对象的字节码文件,我们继续往下。 + +**getProxyClass0(loader, intfs)** + +那其实真正生成代理对象字节码文件的是这个方法,他会传入真实实现的类加载器和他所实现的接口数组。 + +```java +private static Class getProxyClass0(ClassLoader loader, + Class... interfaces) { + //限制真实实现所实现的接口数量不能大于65535个 + if (interfaces.length> 65535) { + throw new IllegalArgumentException("interface limit exceeded"); + } + + // 首先从缓存中获取该接口对于的代理对象,如果有则返回,没有则通过ProxyClassFactory创建 + return proxyClassCache.get(loader, interfaces); +} +``` + +**ProxyClassFactory** + +缓存中获取我们比较好理解,但是我们并没有在上述方法中发现proxyClassFactory + +我们可以点击进入``proxyClassCache.get()``方法看看是如何从缓存中获取的 + +```java +public V get(K key, P parameter) { + Objects.requireNonNull(parameter); + + expungeStaleEntries(); + + // 这里我们就理解成将真实实现的类加载器作为缓存key即可 + Object cacheKey = CacheKey.valueOf(key, refQueue); + + // 从缓存中获取代理对象 + ConcurrentMap> valuesMap = map.get(cacheKey); + if (valuesMap == null) { + ConcurrentMap> oldValuesMap + = map.putIfAbsent(cacheKey, + valuesMap = new ConcurrentHashMap()); + if (oldValuesMap != null) { + valuesMap = oldValuesMap; + } + } + + // 缓存中不存在则根据subKeyFactory.apply(key, parameter)方法进行创建 + Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); + Supplier supplier = valuesMap.get(subKey); + + .......省略无用代码........ +} +``` + +**subKeyFactory.apply(key, parameter)** + +我们点击进入apply方法发现其实是BiFunction接口,我们找到它的实现 + +![image-20210819173614848](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173614848.png) + +此时我们发现,我们进入的这个apply方法所在的位置是Proxy类下ProxyClassFactory这个静态内部类中 + +所以当缓存中没有相应的代理对象,则会调用ProxyClassFactory类的``apply``方法来创建代理类。 + +**ProxyClassFactory.apply()** + +```java +private static final class ProxyClassFactory + implements BiFunction[], Class> +{ + // 生成代理对象的字节码文件名的前缀,用于组装文件名 + private static final String proxyClassNamePrefix = "$Proxy"; + + // 生成代理对象字节码文件名的计数器,用于组装文件名(计数器默认从0开始) + private static final AtomicLong nextUniqueNumber = new AtomicLong(); + + @Override + public Class apply(ClassLoader loader, Class[] interfaces) { + + Map, Boolean> interfaceSet = new IdentityHashMap(interfaces.length); + for (Class intf : interfaces) { + //校验类加载器是否能通过接口名称加载该类 + Class interfaceClass = null; + try { + interfaceClass = Class.forName(intf.getName(), false, loader); + } catch (ClassNotFoundException e) { + } + if (interfaceClass != intf) { + throw new IllegalArgumentException( + intf + " is not visible from class loader"); + } + + //校验该类是否是接口类型 + if (!interfaceClass.isInterface()) { + throw new IllegalArgumentException( + interfaceClass.getName() + " is not an interface"); + } + + //校验接口是否重复 + if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { + throw new IllegalArgumentException( + "repeated interface: " + interfaceClass.getName()); + } + } + + String proxyPkg = null; // 代理对象包名 + int accessFlags = Modifier.PUBLIC | Modifier.FINAL; + + /* + * 用于生成代理对象需要使用的包名 + * 非public接口,代理类的包名与接口的包名相同 + */ + for (Class intf : interfaces) { + int flags = intf.getModifiers(); + if (!Modifier.isPublic(flags)) { + accessFlags = Modifier.FINAL; + String name = intf.getName(); + int n = name.lastIndexOf('.'); + String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); + if (proxyPkg == null) { + proxyPkg = pkg; + } else if (!pkg.equals(proxyPkg)) { + throw new IllegalArgumentException( + "non-public interfaces from different packages"); + } + } + } + + if (proxyPkg == null) { + // 如果代理接口是public修饰的,则使用默认的com.sun.proxy package作为包名 + proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; + } + + /* + * 为代理对象生成字节码文件名 + * 文件名格式:proxyName = 之前生成的包名 + $Proxy + 当前计数器的值(计数器默认从0开始) + * 比如 proxyName = com.sun.proxy.$Proxy0 + */ + long num = nextUniqueNumber.getAndIncrement(); + String proxyName = proxyPkg + proxyClassNamePrefix + num; + + /* + * 真正生成代理对象字节码文件的地方 + */ + //生成代理对象字节码数组 + byte[] proxyClassFile = ProxyGenerator.generateProxyClass( + proxyName, interfaces, accessFlags); + try { + // 将代理对象字节码数组生成字节码文件,并使用类加载器将代理对象的字节码文件加载到JVM内存中 + return defineClass0(loader, proxyName, + proxyClassFile, 0, proxyClassFile.length); + } catch (ClassFormatError e) { + throw new IllegalArgumentException(e.toString()); + } + } +} +``` + +我们可以看出它是通过``ProxyGenerator.generateProxyClass() ``先生成代理对象字节码数组, + +然后通过``defineClass0()``方法将代理对象的字节码数组生成字节码文件,并将该字节码通过类加载器加载到JVM中。 + +但是``defineClass0()``方法底层是通过``native``调用的``C++``,我们看不了,知道有这个事就行 + +![image-20210819173659056](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173659056.png) + +**ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags)** + +这个方法随便看看就行,不用过多理解 + +```java +public static byte[] generateProxyClass(final String var0, Class[] var1, int var2) { + ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2); + final byte[] var4 = var3.generateClassFile(); + // 是否要将生成代理对象的字节码文件保存到磁盘中 + // 该步骤也就是为什么之前我们在测试生成代理对象的时候使用System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");来将代理对象字节码文件保存下来 + if (saveGeneratedFiles) { + AccessController.doPrivileged(new PrivilegedAction() { + public Void run() { + try { + int var1 = var0.lastIndexOf(46); + Path var2; + if (var1> 0) { + Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar)); + Files.createDirectories(var3); + var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class"); + } else { + var2 = Paths.get(var0 + ".class"); + } + + Files.write(var2, var4, new OpenOption[0]); + return null; + } catch (IOException var4x) { + throw new InternalError("I/O exception saving generated file: " + var4x); + } + } + }); + } + + return var4; +} +``` + +## 4.3 总结 + +![image-20210819173737409](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173737409.png) + +![image-20210819173728693](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173728693.png) + +创建代理对象的核心方法就是**Proxy.newProxyInstance()**。该方法首先会调用**getProxyClass0() 从缓存中获取或者创建代理对象字节码文件**,拿到代理对象字节码文件后调用**getConstructor()方法获取代理对象的构造器**,最后**通过cons.newInstance() 根据代理对象的构造器反射生成代理对象实例**并返回。 + +我们回过头来看getProxyClass0()方法,该方法**首先判断真实实现所实现的接口数量是否超限**,没有超限则proxyClassCache.get()**从缓存中获取代理实例**。如果缓存中没有,则去**创建代理对象**。那么是如何创建的呢?首先会根据:**包名 + $proxy0 + 当前计数器的值(默认从0开始) 生成代理对象的名称**,其次根据名称和代理对象需要实现的被代理接口去**生成代理对象字节码数组**。拿到字节码数组之后,就可以通过**调用的native方法去生成代理对象字节码文件**,最后进行返回。 \ No newline at end of file diff --git "a/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/04.Spring346円272円220円347円240円201円345円211円226円346円236円220円/README.md" "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/04.Spring346円272円220円347円240円201円345円211円226円346円236円220円/README.md" new file mode 100644 index 0000000..47c00e3 --- /dev/null +++ "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円Spring345円272円224円347円224円250円345円217円212円346円272円220円347円240円201円345円211円226円346円236円220円(345円256円214円347円273円223円)/04.Spring346円272円220円347円240円201円345円211円226円346円236円220円/README.md" @@ -0,0 +1,1994 @@ +# Spring 源码剖析 + +# 说在前面 + +- **本章相关代码及笔记地址**:飞机票🚀 +- **🌍**Github:[🚀Java超神之路:【🍔Java全生态技术学习笔记,一起超神吧🍔】](https://github.com/EayonLee/JavaGod) +- **🪐**CSDN:[🚀Java超神之路:【🍔Java全生态技术学习笔记,一起超神吧🍔】](https://blog.csdn.net/qq_20492277/article/details/114269863) + +# 目录 +- [Spring 源码剖析](#spring--源码剖析) +- [说在前面](#说在前面) +- [目录](#目录) +- [一. Bean完整的生命周期](#一-bean完整的生命周期) + - [1.1 BeanDefinition(Bean定义信息)](#11-beandefinitionbean定义信息) + - [1.2 BeanFactoryPostProcessor(BeanFactory的后置处理器)](#12-beanfactorypostprocessorbeanfactory的后置处理器) + - [1.3 Bean实例化、填充属性、Aware接口](#13-bean实例化填充属性aware接口) + - [1.4 BeanPostProcessor接口 / init-method](#14-beanpostprocessor接口--init-method) + - [1.5 destory-method销毁方法-完整的Bean声明周期结束](#15-destory-method销毁方法-完整的bean声明周期结束) +- [二. Bean的生命周期 源码解析](#二-bean的生命周期-源码解析) + - [2.1 准备工作](#21-准备工作) + - [2.2 ClassPathXmlApplicationContext方法](#22-classpathxmlapplicationcontext方法) + - [2.3 验证](#23-验证) + - [2.3.1 refresh():方法](#231-refresh方法) + - [2.3.2 freshBeanFactory():创建bean工厂并加载xml文件到BeanDefinition](#232-freshbeanfactory创建bean工厂并加载xml文件到beandefinition) + - [2.3.3 finishBeanFactoryInitialization():完成上下文bean工厂的初始化](#233-finishbeanfactoryinitialization完成上下文bean工厂的初始化) + - [2.3.4 getBean():获取bean](#234-getbean获取bean) + - [2.3.5 createBean():创建Bean](#235-createbean创建bean) + - [2.3.6 popelateBean():Bean的属性赋值](#236-popelatebeanbean的属性赋值) + - [2.3.7 invokeAwareMethods():执行bean所实现Aware接口方法](#237-invokeawaremethods执行bean所实现aware接口方法) + - [2.3.8 beanPostProcessors的before方法](#238-beanpostprocessors的before方法) + - [2.3.9 init-method:执行初始化方法](#239-init-method执行初始化方法) + - [2.3.10 beanPostProcessors的after方法](#2310-beanpostprocessors的after方法) + - [2.4 总结](#24-总结) +- [三. 循环依赖](#三-循环依赖) + - [3.1 什么是循环依赖?](#31-什么是循环依赖) + - [3.2 测试循环依赖](#32-测试循环依赖) + - [3.2.1 准备实体](#321-准备实体) + - [3.2.2 XML文件创建Bean并属性注入](#322-xml文件创建bean并属性注入) + - [3.2.3 测试及结果分析](#323-测试及结果分析) + - [3.2 循环依赖的几种场景](#32-循环依赖的几种场景) + - [3.3 单例的setter注入如何解决循环依赖](#33-单例的setter注入如何解决循环依赖) + - [3.3.1 为什么正常情况下Spring不会出现循环依赖问题](#331-为什么正常情况下spring不会出现循环依赖问题) + - [3.3.2 Spring默认针对单例setter注入是如何解决循环依赖的](#332-spring默认针对单例setter注入是如何解决循环依赖的) + - [3.3.3 什么是三级缓存](#333-什么是三级缓存) + - [3.3.4 源码验证](#334-源码验证) + - [3.4 为什么需要三级缓存,二级行不行?](#34-为什么需要三级缓存二级行不行) +- [四. JDK动态代理源码分析](#四-jdk动态代理源码分析) + - [4.1 JDK动态代理的实现](#41-jdk动态代理的实现) + - [4.2 源码分析](#42-源码分析) + - [4.3 总结](#43-总结) + +# 一. Bean完整的生命周期 + +## 1.1 BeanDefinition(Bean定义信息) + +![image-20210819164327964](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819164327964.png) + +一般我们会通过XML配置文件、注解、配置类去定义bean。当我们去加载配置文件或者配置类的时候首先会去通过BeanDefinitionReader接口的实现类去读取bean的一些定义。比如我们通过XML配置文件去进行bean定义,则会通过XMLBeanDefinitionReader进行解析并将bean的定义信息、依赖关系等保存至BeanDefinition中。 + +## 1.2 BeanFactoryPostProcessor(BeanFactory的后置处理器) + +![image-20210819164409397](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819164409397.png) + +**为什么要有BeanFactoryPostProcessor?它的作用是什么?** + +一般情况我们将bean的一些定义信息保存至BeanDefinition中之后,就可以通过BeanFactory中的反射将bean进行实例化了。 + +但是根据我们开发经验,我们有时候会将数据源的一些配置信息(DataSource)通过XML或者配置类进行定义。并且会将属性值通过 ${} 占位符的方式去引入外部文件赋值,如下图: + +![image-20210819164438580](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819164438580.png) + +但是我们通过BeanDefinitionReader解析配置文件保存至BeanDefinition这个过程中并没有去解析`` ${}`` 占位符中的值,BeanDefinition中保存的还是``${jdbc.url}``这样的属性值。那么这个时候我们肯定是不能直接通过BeanFactory去实例化的,而是通过BeanFactoryPostProcessor去进行后置处理,解析占位符,再通过BeanFactory去实例化。 + +## 1.3 Bean实例化、填充属性、Aware接口 + +通过如上步骤我们将bean的定义信息全部解析完毕保存至BeanDefinition中并通过BeanFactoryPostProcessor进行增强后,就可以进行实例化和初始化。 + +实例化没啥好说就是通过反射去new对象,但是此时的bean中的属性值都是空的,赋值操作是由初始化来完成的。 + +**初始化又分为两步**: + +- 属性赋值 +- 执行bean所实现Aware接口的方法。 + +![image-20210819164521220](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819164521220.png) + +属性赋值很简单我们就不说了,主要看看什么叫执行bean所实现的Aware接口方法。 + +**执行bean所实现的Aware接口方法**: + +Aware接口的意义在于方便通过Spring中的Bean对象来获取其在容器中对应的相关属性值 + +**定义一个对象并实现Aware接口** + +```java +public class Teacher implements EnvironmentAware , BeanNameAware { + /** + * bean的一些基础属性(业务层面的属性) + */ + private String teacherName; + private Integer age; + + /** + * bean在容器中的属性 + */ + private Environment environment; + private String beanName; + + /** + * 通过EnvironmentAware设置该Bean在容器中的环境 + * @param environment + */ + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + public Environment getEnvironment() { + return environment; + } + + /** + * 设置该Bean再容器中的名称 + * @param s + */ + @Override + public void setBeanName(String s) { + this.beanName = s; + } + + public String getBeanName() { + return beanName; + } +} +``` + +**获取bean在容器中的属性值** + +```java +@Test +public void test_aware(){ + ApplicationContext ac = new AnnotationConfigApplicationContext(JdbcConfiguration.class);//加载配置类 + Teacher bean = ac.getBean("teacher", Teacher.class); + //Aware接口的意义在于方便通过Spring中的Bean对象来获取其在容器中对应的相关属性值 + String beanName = bean.getBeanName();//bean的名称 + Environment environment = bean.getEnvironment();//环境 + System.out.println(beanName); + System.out.println(environment); +} +``` + +![image-20210819164555973](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819164555973.png) + +## 1.4 BeanPostProcessor接口 / init-method + +![image-20210819164617452](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819164617452.png) + +- 如果Bean实现了**BeanPostProcess**接口,Spring将调用它们的**postProcessBeforeInitialization**方法 + +- - (作用是在Bean实例创建成功后对进行增强处理,如对Bean进行修改,增加某个功能) + +- 如果Bean声明了**init-method方法**则会进行调用,完成初始化 + +- 如果Bean实现了**BeanPostProcess**接口,Spring将调用它们的**postProcessAfterInitialization**方法 + +- - (和执行before方法一样都是进行增强 ,只不过一个在初始化之前,一个在初始化方法之后) + +## 1.5 destory-method销毁方法-完整的Bean声明周期结束 + +![image-20210819164643740](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819164643740.png) + +如果该Bean声明了**destory-method**方法则会进行调用销毁。但是对于我们高级语言java来说,我们日常很少会声明销毁,都是通过垃圾回收进行解决。 + + + +# 二. Bean的生命周期 源码解析 + +## 2.1 准备工作 + +**定义一个对象** + +```java +public class Teacher{ + + private String teacherName; + private Integer age; + + ...省略get/set方法..... +} +``` + +**通过XML文件创建Bean及注入属性** + +```java + + + + + + + + + +``` + +**测试加载Bean** + +```java +@Test +public void test_load_bean(){ + ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");//加载XML + Teacher teacher = ac.getBean("teacher", Teacher.class); + System.out.println(teacher.getTeacherName()); + System.out.println(teacher.getAge()); +} +``` + +## 2.2 ClassPathXmlApplicationContext方法 + +```java +public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { + super(parent); + //configLocations参数也就是1.6.1准备工作中的创建Bean的XML文件 + //将XML配置文件设置到类的成员属性中方便后面的读取和加载 + this.setConfigLocations(configLocations); + if (refresh) { + //refresh方法刷新Spring应用的上下文,这个方法里面包含了所有Bean生命周期的方法 + this.refresh(); + } +} +``` + +## 2.3 验证 + +本节``refresh``方法我们只去找创建bean的方法(createBean),其他的暂时我们不去研究 + +### 2.3.1 refresh():方法 + +```java +public void refresh() throws BeansException, IllegalStateException { + synchronized(this.startupShutdownMonitor) { + /** + * (不重要 知道是准备工作即可) + * prepareRefresh()方法 做容器刷新前的准备工作: + * 1、设置容器的启动时间 + * 2、设置活跃状态为true + * 3、设置关闭状态为false + * 4、获取Environment对象,并加载当前系统的属性值到Environment对象中 + * 5、准备监听器和事件的集合对象,默认为空的集合 + */ + this.prepareRefresh(); + + //(重要)创建BeanFactory工厂,并加载XML配置文件中的属性到当前工厂中的BeanDefinition + ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory(); + + //(不重要)对上一步创建的beanFactory做一些初始化属性填充的准备工作 + this.prepareBeanFactory(beanFactory); + + try { + //(不重要)源码中该方法内部为空,是留给它子类做一些扩展用的。 + this.postProcessBeanFactory(beanFactory); + + //(重要)invoke是执行的意思 那该方法也就是执行beanFactoryPostProcessors(beanFactory的后置处理器) + //我们之前所说的将xml文件的属性值的占位符解析成实际的值等扩展操作都是在这里执行。 + this.invokeBeanFactoryPostProcessors(beanFactory); + + //(有点重要)注册beanPostProcessors,这里只是注册(准备出来) + //按之前的流程图来说本来应该实例化对象了,但是实例化之后的初始化操作中需要beanPostProcessors,所以我们在实例化之前先将他准备出来 + //注意:这里是beanPostProcessors和上面的beanFactoryPostProcessors不一样,所以这里只是注册,真正的调用是在getBean()的时候调用 + this.registerBeanPostProcessors(beanFactory); + + //(不重要)为上下文初始化message源,也就是不同语言的消息体,国际化处理 + this.initMessageSource(); + + //(有点重要)初始化事件监听多路广播器 + //如果我们现在bean生命周期不同的阶段做不同的事情我们需要通过观察者模式:监听器、监听事件、多播器来实现,所以这里先进性初始化 + this.initApplicationEventMulticaster(); + + //(不重要)一个空方法,留给子类来初始化其他bean的 + this.onRefresh(); + + //(有点重要)注册监听器,也属于实例化bean的一些前期准备工作 + this.registerListeners(); + + //(重要)实例化所有的单例bean(非懒加载的bean) + this.finishBeanFactoryInitialization(beanFactory); + + //最后一步:发布相应的事件。 + this.finishRefresh(); + } catch (BeansException var9) { + if (this.logger.isWarnEnabled()) { + this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9); + } + + this.destroyBeans(); + this.cancelRefresh(var9); + throw var9; + } finally { + this.resetCommonCaches(); + } + + } +} +``` + +### 2.3.2 freshBeanFactory():创建bean工厂并加载xml文件到BeanDefinition + +``ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();`` + +创建BeanFactory工厂,并加载XML配置文件中的属性到当前工厂中的BeanDefinition + +![image-20210819165049320](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819165049320.png) + +**prepareBeanFactory()** + +进入到该prepareBeanFactory方法中可以找到下图的``refreshBeanFactory``方法,该方法中就是通过``loadBeanDefinitions(beanFactory)``方法去加载BeanDefinition。 + +![image-20210819165107806](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819165107806.png) + +**loadBeanDefinitions()** + +那么就是通过``XmlBeanDefinitionReader()``方法解析xml文件中的定义并保存到BeanDefinition中 + +![image-20210819170633093](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819170633093.png) + +### 2.3.3 finishBeanFactoryInitialization():完成上下文bean工厂的初始化 + +实例化所有**非懒加载的单例bean** + +```java +/** +* 完成此上下文的bean工厂的初始化, +* 初始化所有剩余的单例bean。 +*/ +protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { + // (不用看)为此上下文初始化转换服务。 + if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) && + beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) { + beanFactory.setConversionService( + beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)); + } + + // (不用看)如果beanFactory之前没有注册嵌入值解析器,则注册默认的嵌入值解析器,主要用于注解属性值的解析 + if (!beanFactory.hasEmbeddedValueResolver()) { + beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal)); + } + + // (不用看)尽早初始化loadTimeAware bean,以便尽早注册它们的转换器 + String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false); + for (String weaverAwareName : weaverAwareNames) { + getBean(weaverAwareName); + } + + //(不用看)禁止使用临时类加载器进行类型匹配 + beanFactory.setTempClassLoader(null); + + //(不用看)冻结所有bean的定义,说明注册的bean定义将不被修改或任何进一步的处理。 + beanFactory.freezeConfiguration(); + + //(重要)这一步开始去真正的去实例化剩下的所有非懒加载的单例对象 + beanFactory.preInstantiateSingletons(); +} +``` + +**preInstantiateSingletons()** + +实例化之前再做一些判断和前期准备工作 + +```java +public void preInstantiateSingletons() throws BeansException { + if (logger.isTraceEnabled()) { + logger.trace("Pre-instantiating singletons in " + this); + } + + //将所有beanDefinition的名字创建成一个集合,其实也就是bean的名称 + List beanNames = new ArrayList(this.beanDefinitionNames); + + //遍历该集合。初始化所有非懒加载的单例bean + for (String beanName : beanNames) { + //合并父类beanDefinition + RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); + //条件判断:是否是抽象的、是否是单例、是否是懒加载 + if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { + //判断该bean是否实现了FactoryBean接口 + if (isFactoryBean(beanName)) { + //FACTORY_BEAN_PREFIX 是定义的常量 & + //也就是根据 & + beanName来获取具体的bean + Object bean = getBean(FACTORY_BEAN_PREFIX + beanName); + // 判断bean是否等于 FactoryBean + if (bean instanceof FactoryBean) { + final FactoryBean factory = (FactoryBean) bean; + //判断这个beanFactory是否急切的需要实例化的标识 + boolean isEagerInit; + if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) { + isEagerInit = AccessController.doPrivileged((PrivilegedAction) + ((SmartFactoryBean) factory)::isEagerInit, + getAccessControlContext()); + } + else { + isEagerInit = (factory instanceof SmartFactoryBean && + ((SmartFactoryBean) factory).isEagerInit()); + } + //如果急切实例化则通过beanName获取实例化bean + if (isEagerInit) { + getBean(beanName); + } + } + } + else { + //如果beanName对应的bean不是FactoryBean类型,或者说没有实现FactoryBean接口,只是一个普通bean,则执行getBean() + //getBean()是去看容器中存不存在该beanName的bean实例,有直接获取,没有则创建 + getBean(beanName); + } + } + } + + //遍历beanNames,触发所有SmartInitializingSingleton的后初始化回调 + for (String beanName : beanNames) { + //获取beanName对应的bean实例 + Object singletonInstance = getSingleton(beanName); + if (singletonInstance instanceof SmartInitializingSingleton) { + final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance; + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedAction) () -> { + smartSingleton.afterSingletonsInstantiated(); + return null; + }, getAccessControlContext()); + } + else { + smartSingleton.afterSingletonsInstantiated(); + } + } + } +} +``` + +### 2.3.4 getBean():获取bean + +**getBean()** + +```java +public Object getBean(String name) throws BeansException { + //此方法才是真正实例化bean的方法,前面都是前期准备工作,也是依赖注入的主要方法 + //Spring源码中,do开头的方法才是真正干活的方法 + return doGetBean(name, null, null, false); +} +``` + +**doGetBean()** + +此方法才是真正去调用(仅仅是调用)实例化bean的方法,前面都是前期准备工作,同时该方法也是依赖注入的主要方法 + +ps:Spring源码中,do开头的方法才是真正干活的方法 + +此方法较长,包含需多循环依赖的东西,可以暂时不看,后面会单独将,只需要找到一个**createBean()**方法即可 + +```java +protected T doGetBean(final String name, @Nullable final Class requiredType, + @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { + //beanName转换 + final String beanName = transformedBeanName(name); + Object bean; + + // 提前检查单例缓存中是否有手动注册的单例对象,此处跟循环依赖有关 + Object sharedInstance = getSingleton(beanName); + if (sharedInstance != null && args == null) { + if (logger.isTraceEnabled()) { + if (isSingletonCurrentlyInCreation(beanName)) { + logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + + "' that is not fully initialized yet - a consequence of a circular reference"); + } + else { + logger.trace("Returning cached instance of singleton bean '" + beanName + "'"); + } + } + //返回对象的实例,当我们的bean实现了FactoryBean接口,需要获取具体的对象的时候就需要此方法来进行获取 + bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); + } + + else { + // 当对象都是单例的时候会尝试解决循环依赖的问题,但是原型模式下如果存在循环依赖的情况,那么直接抛出异常 + if (isPrototypeCurrentlyInCreation(beanName)) { + throw new BeanCurrentlyInCreationException(beanName); + } + + // 获取父容器 + BeanFactory parentBeanFactory = getParentBeanFactory(); + //如果beanDefinitionMap中也就是在所有已经加载的类中不包含beanName,那么就尝试从父容器中获取 + if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { + // Not found -> check parent. + String nameToLookup = originalBeanName(name); + if (parentBeanFactory instanceof AbstractBeanFactory) { + return ((AbstractBeanFactory) parentBeanFactory).doGetBean( + nameToLookup, requiredType, args, typeCheckOnly); + } + else if (args != null) { + return (T) parentBeanFactory.getBean(nameToLookup, args); + } + else if (requiredType != null) { + return parentBeanFactory.getBean(nameToLookup, requiredType); + } + else { + return (T) parentBeanFactory.getBean(nameToLookup); + } + } + //如果不是做类型检查,那么表示要创建bean,此处在集合中做一个记录 + if (!typeCheckOnly) { + markBeanAsCreated(beanName); + } + + try { + //此处做了BeanDefinition对象的转换,当我们从xml文件中加载beanDefinition对象的时候,封装的对象是GenericBeanDefinition + //此处要做类型转换,如果是子类bean的话,会合并父类的相关属性 + final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + checkMergedBeanDefinition(mbd, beanName, args); + + //如果存在依赖的bean的话,那么则优先实例化依赖的bean + String[] dependsOn = mbd.getDependsOn(); + if (dependsOn != null) { + //如果存在依赖,则需要递归实例化依赖的bean + for (String dep : dependsOn) { + if (isDependent(beanName, dep)) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); + } + //注册各个bean的依赖关系,以便进行销毁 + registerDependentBean(dep, beanName); + try { + getBean(dep); + } + catch (NoSuchBeanDefinitionException ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "'" + beanName + "' depends on missing bean '" + dep + "'", ex); + } + } + } + + //(本小节重点,前面可以不看)创建单例模式bean的实例对象 + if (mbd.isSingleton()) { + sharedInstance = getSingleton(beanName, () -> { + try { + //这里去创建bean + return createBean(beanName, mbd, args); + } + catch (BeansException ex) { + destroySingleton(beanName); + throw ex; + } + }); + bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); + } + //创建原型模式的bean实例对象 + else if (mbd.isPrototype()) { + Object prototypeInstance = null; + try { + beforePrototypeCreation(beanName); + prototypeInstance = createBean(beanName, mbd, args); + } + finally { + afterPrototypeCreation(beanName); + } + bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); + } + +//===============================省略一堆无用代码==================================== + + return (T) bean; +} +``` + +### 2.3.5 createBean():创建Bean + +**createBean()** + +之前我们说过,spring源码中真正干活的都是带do的方法,那么我们只需要在当前方法中找到``doCreateBean``即可。 + +```java +protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) + throws BeanCreationException { + + if (logger.isTraceEnabled()) { + logger.trace("Creating instance of bean '" + beanName + "'"); + } + RootBeanDefinition mbdToUse = mbd; + + //锁定class,根据设置的class属性或者根据className来解析class + Class resolvedClass = resolveBeanClass(mbd, beanName); + if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) { + mbdToUse = new RootBeanDefinition(mbd); + mbdToUse.setBeanClass(resolvedClass); + } + + //验证及准备覆盖的方法 + try { + mbdToUse.prepareMethodOverrides(); + } + catch (BeanDefinitionValidationException ex) { + throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(), + beanName, "Validation of method overrides failed", ex); + } + + try { + // 给BeanPostProcessors一个机会来返回代理来替代真正的实例 + Object bean = resolveBeforeInstantiation(beanName, mbdToUse); + if (bean != null) { + return bean; + } + } + catch (Throwable ex) { + throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, + "BeanPostProcessor before instantiation of bean failed", ex); + } + + try { + //(此小节重点方法)真正实际创建bean的调用 + Object beanInstance = doCreateBean(beanName, mbdToUse, args); + if (logger.isTraceEnabled()) { + logger.trace("Finished creating instance of bean '" + beanName + "'"); + } + return beanInstance; + } + catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { + throw ex; + } + catch (Throwable ex) { + throw new BeanCreationException( + mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex); + } +} +``` + +**doCreateBean()** + +ok!见到亲人了,终于找到真正干活的人了 + +```java +protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) + throws BeanCreationException { + + // 这个beanWrapper是用来持有创建出来的bean对象的 + BeanWrapper instanceWrapper = null; + if (mbd.isSingleton()) { + //如果是单例对象,从factoryBean实例缓存中移除当前bean定义信息 + instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); + } + if (instanceWrapper == null) { + //(重要,通过反射创建实例)根据执行bean使用对应的策略创建新的实例,如:工厂方法、构造函数主动注入、简单初始化 + instanceWrapper = createBeanInstance(beanName, mbd, args); + } + //创建完成后的实例 + final Object bean = instanceWrapper.getWrappedInstance(); + Class beanType = instanceWrapper.getWrappedClass(); + if (beanType != NullBean.class) { + mbd.resolvedTargetType = beanType; + } + + // 允许后置处理器postPorcessor修改合并的bean定义 + synchronized (mbd.postProcessingLock) { + if (!mbd.postProcessed) { + try { + //应用MergedBeanDefinitionPostProcessor + applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); + } + catch (Throwable ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Post-processing of merged bean definition failed", ex); + } + mbd.postProcessed = true; + } + } + + //==========省略一堆无用代码=========================== + + return exposedObject; +} +``` + +**createBeanInstance()** + +```java +protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { + //确认需要创建的bean实例的类可以实例化 + Class beanClass = resolveBeanClass(mbd, beanName); + + if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Bean class isn't public, and non-public access not allowed: " + beanClass.getName()); + } + + Supplier instanceSupplier = mbd.getInstanceSupplier(); + if (instanceSupplier != null) { + return obtainFromSupplier(instanceSupplier, beanName); + } + + //如果工厂方法不为空则使用工厂方法初始化策略 + if (mbd.getFactoryMethodName() != null) { + return instantiateUsingFactoryMethod(beanName, mbd, args); + } + + // Shortcut when re-creating the same bean... + boolean resolved = false; + boolean autowireNecessary = false; + if (args == null) { + synchronized (mbd.constructorArgumentLock) { + //一个类中有多个构造函数,每个构造函数都有不同的参数,所以调用前需要现根据参数锁定构造函数或对应的工厂方法 + if (mbd.resolvedConstructorOrFactoryMethod != null) { + resolved = true; + autowireNecessary = mbd.constructorArgumentsResolved; + } + } + } + //如果已经解析过则使用解析好的构造函数方法,不需要再次锁定 + if (resolved) { + if (autowireNecessary) { + //(重要)构造函数自动注入 + return autowireConstructor(beanName, mbd, null, null); + } + else { + //(重要)使用默认构造函数构造 + return instantiateBean(beanName, mbd); + } + } + + // 需要根据参数解析构造函数 + Constructor[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); + if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR || + mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { + return autowireConstructor(beanName, mbd, ctors, args); + } + + // Preferred constructors for default construction? + ctors = mbd.getPreferredConstructors(); + if (ctors != null) { + //(重要)构造函数自动注入 + return autowireConstructor(beanName, mbd, ctors, null); + } + + //(重要)使用默认构造函数构造 + return instantiateBean(beanName, mbd); +} +``` + +**instantiateBean()** + +那我们主要来看这个使用默认构造函数构造的方法即可 + +```java +protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) { + try { + Object beanInstance; + final BeanFactory parent = this; + if (System.getSecurityManager() != null) { + beanInstance = AccessController.doPrivileged((PrivilegedAction) () -> + getInstantiationStrategy().instantiate(mbd, beanName, parent), + getAccessControlContext()); + } + else { + //getInstantiationStrategy():获取实例化的策略 + //instantiate():进行实例化 + //得到实例化后的bean,所以我们再来看看这个instantiate()方法是如何进行实例化 + beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent); + } + BeanWrapper bw = new BeanWrapperImpl(beanInstance); + initBeanWrapper(bw); + return bw; + } + catch (Throwable ex) { + throw new BeanCreationException( + mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex); + } +} +``` + +**instantiate()** + +```java +public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) { + //如果需要覆盖或者动态替换的方法则使用cglig进行动态代理,因为可以在创建代理的同时将动态方法织入类中,但是如果没有需要动态改变的方法,为了方便直接反射就可以了 + if (!bd.hasMethodOverrides()) { + //此处获取到指定的构造器对bean进行实例化 + Constructor constructorToUse; + synchronized (bd.constructorArgumentLock) { + constructorToUse = (Constructor) bd.resolvedConstructorOrFactoryMethod; + if (constructorToUse == null) { + final Class clazz = bd.getBeanClass(); + if (clazz.isInterface()) { + throw new BeanInstantiationException(clazz, "Specified class is an interface"); + } + try { + if (System.getSecurityManager() != null) { + constructorToUse = AccessController.doPrivileged( + (PrivilegedExceptionAction>) clazz::getDeclaredConstructor); + } + else { + //根据beanClass获取到构造器 + constructorToUse = clazz.getDeclaredConstructor(); + } + bd.resolvedConstructorOrFactoryMethod = constructorToUse; + } + catch (Throwable ex) { + throw new BeanInstantiationException(clazz, "No default constructor found", ex); + } + } + } + //(重要)使用BeanUtils工具类根据bean的构造器进行反射实例化并返回 + return BeanUtils.instantiateClass(constructorToUse); + } + else { + // Must generate CGLIB subclass. + return instantiateWithMethodInjection(bd, beanName, owner); + } +} +``` + +**BeanUtils.instantiateClass(constructorToUse)** + +```java +public static T instantiateClass(Constructor ctor, Object... args) throws BeanInstantiationException { + Assert.notNull(ctor, "Constructor must not be null"); + try { + ReflectionUtils.makeAccessible(ctor); + if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) { + return KotlinDelegate.instantiateClass(ctor, args); + } + else { + Class[] parameterTypes = ctor.getParameterTypes(); + Assert.isTrue(args.length <= parameterTypes.length, "Can't specify more arguments than constructor parameters"); + Object[] argsWithDefaultValues = new Object[args.length]; + for (int i = 0 ; i < args.length; i++) { + if (args[i] == null) { + Class parameterType = parameterTypes[i]; + argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null); + } + else { + argsWithDefaultValues[i] = args[i]; + } + } + //(重要)通过bean的构造器进行实例化,到这里实例化bean的操作就结束了 + return ctor.newInstance(argsWithDefaultValues); + } + } + catch (InstantiationException ex) { + throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex); + } + catch (IllegalAccessException ex) { + throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex); + } + catch (IllegalArgumentException ex) { + throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex); + } + catch (InvocationTargetException ex) { + throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException()); + } +} +``` + +这个时候我们创建完bean回到``doCreateBean()``方法查看创建的bean可以发现,仅仅是进行了实例化,并没有进行属性赋值 + +![image-20210819171239341](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819171239341.png) + +### 2.3.6 popelateBean():Bean的属性赋值 + +在刚刚的``doCreateBean()``方法中我们已经获取到了实例化的bean,并且发现是没有进行属性赋值的,所以我们还在当前``doCreateBean()``方法中找到``popelateBean()``属性赋值方法。 + +**再来看看doCreateBean()方法** + +```java +protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) + throws BeanCreationException { + + // 这个beanWrapper是用来持有创建出来的bean对象的 + BeanWrapper instanceWrapper = null; + if (mbd.isSingleton()) { + //如果是单例对象,从factoryBean实例缓存中移除当前bean定义信息 + instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); + } + if (instanceWrapper == null) { + //(重要,通过反射创建实例)根据执行bean使用对应的策略创建新的实例,如:工厂方法、构造函数主动注入、简单初始化 + instanceWrapper = createBeanInstance(beanName, mbd, args); + } + //创建完成后的实例bean + final Object bean = instanceWrapper.getWrappedInstance(); + Class beanType = instanceWrapper.getWrappedClass(); + if (beanType != NullBean.class) { + mbd.resolvedTargetType = beanType; + } + + // 允许后置处理器postPorcessor修改合并的bean定义 + synchronized (mbd.postProcessingLock) { + if (!mbd.postProcessed) { + try { + //应用MergedBeanDefinitionPostProcessor + applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); + } + catch (Throwable ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Post-processing of merged bean definition failed", ex); + } + mbd.postProcessed = true; + } + } + + //判断当前bean是否需要提前曝光:单例&允许循环依赖&当前bean正在创建中,检测循环依赖 + boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && + isSingletonCurrentlyInCreation(beanName)); + if (earlySingletonExposure) { + if (logger.isTraceEnabled()) { + logger.trace("Eagerly caching bean '" + beanName + + "' to allow for resolving potential circular references"); + } + //为避免后期循环依赖,可以在bean初始化完成前将创建实例的ObjectFactory加入工厂 + addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); + } + + // 初始化bean + Object exposedObject = bean; + try { + //(重要)对bean的属性进行填充,将各个属性值注入,其中。可能存在依赖于其他bean的属性,则会递归初始化依赖的bean + //只需要知道这里进行bean的一些普通属性填充即可 + populateBean(beanName, mbd, instanceWrapper); + //(重要)执行初始化逻辑 + exposedObject = initializeBean(beanName, exposedObject, mbd); + } + + //=====================省略一些无用代码========================= + + return exposedObject; +} +``` + +![image-20210819171325907](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819171325907.png) + +该bean只去填充了一些普通属性,对于实现的Aware接口的方法不在此处进行调用。 + +### 2.3.7 invokeAwareMethods():执行bean所实现Aware接口方法 + +那么对于bean实现的Aware接口的方法在何处进行调用?其实就在``populateBean()``方法的下面,``initializeBean()``当中,我们可以来看一下 + +**initializeBean()** + +```java +protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedAction) () -> { + invokeAwareMethods(beanName, bean); + return null; + }, getAccessControlContext()); + } + else { + //(重要)执行bean所实现Aware接口的方法 + invokeAwareMethods(beanName, bean); + } + + Object wrappedBean = bean; + if (mbd == null || !mbd.isSynthetic()) { + //如果bean实现了BeanPostProcessors接口则调用该before方法进行一些额外的扩展增强操作 + wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); + } + + try { + //(重要)执行初始化方法 + invokeInitMethods(beanName, wrappedBean, mbd); + } + catch (Throwable ex) { + throw new BeanCreationException( + (mbd != null ? mbd.getResourceDescription() : null), + beanName, "Invocation of init method failed", ex); + } + if (mbd == null || !mbd.isSynthetic()) { + //(重要)如果bean实现了BeanPostProcessors接口则调用该after方法进行一些额外的扩展增强操作 + wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); + } + + return wrappedBean; +} +``` + +![image-20210819171416527](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819171416527.png) + +执行完``invokeAwareMethods()``方法后我们发现,已经执行了bean所实现的Aware接口中的方法,并完成属性的赋值。 + +所以也证实了bean所实现的Aware接口中的方法的调用是在bean属性填充操作后去执行的。 + +**PS**:environmen为什么没有调用赋值?是因为spring源码在前期的准备工作者屏蔽了environment,他应该在``beanPostProcessors``的``before``方法中去调用。 + +### 2.3.8 beanPostProcessors的before方法 + +before方法的全称其实叫:**applyBeanPostProcessorsBeforeInitialization()** + +before方法也在``initializeBean()``方法中,就在``invokeAwareMethods方法``的下面 + +如果bean实现了beanPostProcessors接口则会执行``before方法``做一些扩展增强操作,另外environmentAware也会在此处进行调用赋值 + +**initializeBean()** + +```java +protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedAction) () -> { + invokeAwareMethods(beanName, bean); + return null; + }, getAccessControlContext()); + } + else { + //(重要)执行bean所实现Aware接口的方法 + invokeAwareMethods(beanName, bean); + } + + Object wrappedBean = bean; + if (mbd == null || !mbd.isSynthetic()) { + //(重要)如果bean实现了BeanPostProcessors接口则调用该before方法进行一些额外的扩展增强操作 + wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); + } + + try { + //(重要)执行初始化方法 + invokeInitMethods(beanName, wrappedBean, mbd); + } + catch (Throwable ex) { + throw new BeanCreationException( + (mbd != null ? mbd.getResourceDescription() : null), + beanName, "Invocation of init method failed", ex); + } + if (mbd == null || !mbd.isSynthetic()) { + //(重要)如果bean实现了BeanPostProcessors接口则调用该after方法进行一些额外的扩展增强操作 + wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); + } + + return wrappedBean; +} +``` + +我们就不进before方法当中去看了,里面无非就是获取bean所实现的BeanPostProcessors接口方法然后去执行。 + +### 2.3.9 init-method:执行初始化方法 + +初始化方法的全称其实叫:**invokeInitMethods()** + +``init-method方法``也在``initializeBean()``方法中,就在``before方法``的下面 + +**initializeBean()** + +```java +protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedAction) () -> { + invokeAwareMethods(beanName, bean); + return null; + }, getAccessControlContext()); + } + else { + //(重要)执行bean所实现Aware接口的方法 + invokeAwareMethods(beanName, bean); + } + + Object wrappedBean = bean; + if (mbd == null || !mbd.isSynthetic()) { + //(重要)如果bean实现了BeanPostProcessors接口则调用该before方法进行一些额外的扩展增强操作 + wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); + } + + try { + //(重要)执行初始化方法 + invokeInitMethods(beanName, wrappedBean, mbd); + } + catch (Throwable ex) { + throw new BeanCreationException( + (mbd != null ? mbd.getResourceDescription() : null), + beanName, "Invocation of init method failed", ex); + } + if (mbd == null || !mbd.isSynthetic()) { + //(重要)如果bean实现了BeanPostProcessors接口则调用该after方法进行一些额外的扩展增强操作 + wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); + } + + return wrappedBean; +} +``` + +我们就不进``init-method方法``当中去看了,里面无非就是执行用户自定义的``init-method方法``并且执行 + +### 2.3.10 beanPostProcessors的after方法 + +after方法的全称其实叫:**applyBeanPostProcessorsAfterInitialization()** + +after方法也在``initializeBean()``方法中,就在``invokeInitMethods方法``的下面 + +如果bean实现了beanPostProcessors接口则会执行``after方法``做一些扩展增强操作 + +**initializeBean()** + +```java +protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedAction) () -> { + invokeAwareMethods(beanName, bean); + return null; + }, getAccessControlContext()); + } + else { + //(重要)执行bean所实现Aware接口的方法 + invokeAwareMethods(beanName, bean); + } + + Object wrappedBean = bean; + if (mbd == null || !mbd.isSynthetic()) { + //(重要)如果bean实现了BeanPostProcessors接口则调用该before方法进行一些额外的扩展增强操作 + wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); + } + + try { + //(重要)执行初始化方法 + invokeInitMethods(beanName, wrappedBean, mbd); + } + catch (Throwable ex) { + throw new BeanCreationException( + (mbd != null ? mbd.getResourceDescription() : null), + beanName, "Invocation of init method failed", ex); + } + if (mbd == null || !mbd.isSynthetic()) { + //(重要)如果bean实现了BeanPostProcessors接口则调用该after方法进行一些额外的扩展增强操作 + wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); + } + + return wrappedBean; +} +``` + +我们就不进after方法当中去看了,里面无非就是获取bean所实现的BeanPostProcessors接口方法然后去执行。 + +**至此我们获得一个完整的bean对象** + +## 2.4 总结 + +总结我们还是按照之前画的流程图来总结吧 + +![image-20210819171723890](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819171723890.png) + +1. 我们首先都会在xml中定义bean,或者直接通过注解,这里我们之说xml的情况。 + +2. 首先会通过**BeanDefinitionReader**(根据不同的bean定义方式来选择响应的实现类)来解析xml文件并保存至**BeanDefinition**中。 + +3. 然后由于实际开发中我们经常会在xml配置文件定义bean时使用占位符的方式引入外部属性值,所以此时保存到BeanDefinition的属性值会存在为解析的占位符的情况。这种情况我们肯定不能将他直接通过BeanFactory去反射实例化。所以我们会通过BeanFactoryPostProcessor来对bean进行实例化前的扩展增强,这其中就包含了解析占位符的操作。 + +4. 得到完整的一个BeanDefinition后我们就可以通过**反射的方式实例化bean**,注意:实例化的都是懒加载的bean。 + +5. 接着进入初始化步骤: + +6. 1. bean的**属性填充** + 2. 如果bean实现了**Aware接口**中的方法则在属性填充后进行方法调用并赋值 + 3. 如果bean实现了**BeanPostProcessor接口的before方法**则进行调用增强 + 4. 如果bean定义了**init-method方法**则进行调用此初始化方法 + 5. 如果bean实现了**BeanPostProcessor接口的after方法**则进行调用增强 + +7. 获取到**完整的bean实例** + +8. 如果bean定义了**destory-method销毁方法**则还会进行销毁,但一般我们都通过java的垃圾回收解决此问题 + + + +# 三. 循环依赖 + +## 3.1 什么是循环依赖? + +**循环依赖分为三种**:自身依赖于自身、互相循环依赖、多组循环依赖。 + +![image-20210819171809288](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819171809288.png) + +但无论循环依赖的数量有多少,循环依赖的本质是一样的。就是你的完整创建依赖于我,而我的完整创建也依赖于你,但我们互相没法解耦,最终导致依赖创建失败。 + +所以 Spring 提供了除了构造函数注入和原型注入外的,setter循环依赖注入解决方案。那么我们也可以先来尝试下这样的依赖,如果是我们自己处理的话该怎么解决。 + +## 3.2 测试循环依赖 + +### 3.2.1 准备实体 + +准备两个实体A和B,A中注入B实体属性,B中注入A实体属性 + +```java +//实体A +public class A { + private B b; + + public void setB(B b) { + this.b = b; + } +} + +//实体B +public class B { + private A a; + + public void setA(A a) { + this.a = a; + } +} +``` + +### 3.2.2 XML文件创建Bean并属性注入 + +```java + + + + + + + + + + + +``` + +### 3.2.3 测试及结果分析 + +按刚刚``3.1章节``我们分析什么是循坏依赖时的想法,这时候我们加载XML文件他会去实例化A和B,然后抛出循环依赖的异常,那我们来测试一下 + +![image-20210819171937666](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819171937666.png) + +我们发现,它最后正常的实例化出了A和B实例,并没有发生循环依赖。这是为什么呢?我们可以看下一节 ``3.3单例的setter注入如何解决循环依赖`` + +## 3.2 循环依赖的几种场景 + +![image-20210819172017667](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172017667.png) + +## 3.3 单例的setter注入如何解决循环依赖 + +### 3.3.1 为什么正常情况下Spring不会出现循环依赖问题 + +还记得我们在最开始跟踪Spring源码分析bean的生命周期时有一个``preInstantiateSingletons()``方法,该方法再去调用``getBean()``方法之前有一个判断,判断当前bean是否是抽象的、单例的、懒加载的。所以我们实例化的bean都是单例非懒加载的。 + +![image-20210819172126743](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172126743.png) + +**重要**: + +那么再回头看一下3.2.2小节我们给bean属性注入的方式是set方法。 + +那么其实在Spring中对于单例set注入的bean是默认解决了循环依赖问题的。解决方法是 实例提前暴露+三层缓存 + +但是前提必须是单例的setter注入,如果是多例setter注入或者是单例/多例构造器注入都是无法解决的。 + +### 3.3.2 Spring默认针对单例setter注入是如何解决循环依赖的 + +针对于之前我们了解的bean生命周期得出。bean的实例化操作和属性赋值(初始化)操作是分开的。我们先实例化得到一个bean的半成品然后去属性赋值,或者说注入其他实体。 + +那么我们首先加载实体A,第一次肯定从缓存中获取不到,所以去创建实例,然后提前暴露添加到三级缓存,当我们去初始化A属性注入时,发现需要实例化B,然后我们再从缓存中获取不到B,则取创建B实例,然后将B暴露添加到三级缓存,当我们去初始化属性注入时发现是实体A,我们则从三级缓存中获取到了实体A,并添加到了二级缓存。至此实体B初始化完成,得到一个完整的bean,最后将B添加到一级缓存。紧接着回到实体A,成功完成实体A的属性注入,初始化完成得到一个完整的bean,并将A添加到一级缓存。至此bean加载结束。完美解决了循环依赖的问题。 + +![image-20210819172206521](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172206521.png) + +### 3.3.3 什么是三级缓存 + +**三级缓存的定义** + +![image-20210819172227057](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172227057.png) + +**为什么三级缓存保存的是ObjectFactory对象?直接保存实例对象不行?** + +ObjectFactory对象其实是getEarlyBeanReference函数表达式,执行该表达式可以获取到bean早期的bean引用,并判断该bean是否需要代理,简单地说就是,如果一个bean我们定义了他需要代理,那么通过三级缓存的函数表达式执行获取到的就是代理对象,如果不需要代理,获取到的就是普通对象。 + +**一级缓存**:存储的是完整实例 + +**二级缓存**:存储的是半成品实例,也就是还未属性赋值的实例 + +**三级缓存**:存储的是getEarlyBeanReference()函数表达式,该表达式可以获取到bean早期的bean引用,并判断该bean是否需要代理,最终返回代理对象或普通对象 + +**为什么要三级缓存,二级行不行?** + +所以说我们只用二级缓存行不行其实取决你到底需不需要代理对象。 + +如果需要代理,我们则在创建过程中其实是创建了两个bean,一个是普通bean,一个是代理bean。**所以他就会在通过调用getEarlyBeanReference()函数表达式想要用你这个bean的时候,将代理bean覆盖掉普通bean,保证你这个bean的全局唯一**。如果说你不需要代理,那可以,你可以不需要三级缓存,二级缓存足以 + +### 3.3.4 源码验证 + +我们先把之前的流程图拿过来对照着看源码 + +![image-20210819172300357](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172300357.png) + +**首先第一步从一级缓存中获取实例** + +在``doGetBean()``方法中调用了``getSingleton()``方法从缓存中(这里单指一级缓存)获取bean。获取到bean就不去创建直接返回了。 + +![image-20210819172322779](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172322779.png) + +**getSingleton()方法** + +在方法中可看到他会根据beanName先从一级缓存中获取,如果一级缓存中不存在该bean并且该bean正在被创建的话才会去二级缓存中查找。 + +而此时我们的bean是首次创建,所以一级缓存里肯定没有,并且bean还没有进行创建,所以当从一级缓存中获取不到bean后直接返回一个空的object + +![image-20210819172343807](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172343807.png) + +**创建实例 A** + +紧接着上面从一级缓存中未获取到bean返回空后,我们再来看看**doGetBean()** 方法的后半部分 + +经过一系列判断及准备操作之后通过mbd(beanDefinition)判断是否是单例的。 + +如果是单例的则调用**getSingleton()去创建bean**,其中该方法有两个参数,以格式beanName,另一个是函数表达式,我们就理解传递了**createBean()**方法过去。 + +该``getSingletion() ``方法主要逻辑就是,**再次从一级缓存查找,找不到则调用createBean()方法去创建bean**。 + +![image-20210819172359843](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172359843.png) + +**getSingleton()** + +方法的上半部分如下图 + +![image-20210819172426038](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172426038.png) + +**doCeateBean()** + +好!现在我们知道了他要去创建bean了,那我们就来看看``doCreateBean()``方法怎么执行的, + +我们发现实例化A成功并返回了。但是注意现在还没有进行初始化,所以还没有B的依赖注入 + +![image-20210819172444018](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172444018.png) + +我们再来看看**doCreateBean()**方法的下半部分 + +我们之前提到过,创建完bean后会将这个半成品添加到三级缓存,那么就是通过**addSingletonFactory()**实现的 + +![image-20210819172508987](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172508987.png) + +**addSingletonFactory()** + +那么该方法其实就是去将创建出来的半成品bean添加到三级缓存 + +![image-20210819172522491](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172522491.png) + +**注意**:存入三级缓存中的value就是singletonFactory这个函数表达式。也就是上一步doCreateBean()方法中的 getEarlyBeanReference()方法。 + +getEarlyBeanReference方法用于获取代理对象,其实底层是通过AbstractAutoProxyCreator类的getEarlyBeanReference生成代理对象。 + +**为什么三级缓存保存的是ObjectFactory对象?直接保存实例对象不行?** + +首先通过上面我们知道了``getEarlyBeanReference函数表达式``就是传递到``addSingletonFactory()``中singletonFactory参数。该表达式作用是用于获取代理对象,那其实三级缓存存的是``getEarlyBeanReference函数表达式`` 也可以理解为bean的代理对象。那为什么不能直接保存实例对象呢?因为假如你想对添加到三级缓存中的实例对象进行增强,直接用实例对象是行不通的。所以我们需要保存代理对象。 + +**populateBean() 属性赋值-依赖注入** + +上面我们将半成品bean添加到三级缓存后,我们返回``doCreateBean方法``。将bean添加到三级缓存后,这时需要给bean进行初始化,也就是属性赋值依赖注入了。 + +关键的方法就在``populateBean()``。 + +![image-20210819172559868](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172559868.png) + +那我们进入``populateBean()``方法看一下,由于该方法前面都是一些判断和预备操作,我们直接找到该方法最后面调用``applyPropertyValues()``方法查看如何进行属性注入 + +![image-20210819172626511](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172626511.png) + +**applyPropertyValues()** + +那么这时候我们其实是给A对象去进行属性赋值,那么需要注入的属性就是B,需要注入的属性值也就是B对象再运行时bean的引用 + +那么再次调用``resolveValueIfNecessary()``方法进行属性值处理 + +![image-20210819172650664](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172650664.png) + +**resolveValueIfNecessary()** + +![image-20210819172704304](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172704304.png) + +**resolveReference()** + +很明显,这个时候我们需要给A实体的属性b赋值,所以他这里调用了``getBean()``方法去容器中找B实例,我们知道``getBean()``方法会调用``doGetBean()``方法,``doGetBean()``方法中会去一级缓存中查找bean,找不到则去创建。那我们继续往下看看**doGetBean()** + +**doGetBean()** + +![image-20210819172740493](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172740493.png) + +既然缓存没有那就实例化b呗,继续看该方法的后半段,他就去调用了``getSingleton()``方法 再次从一级缓存中获取b实例,获取不到就去调用``createBean()``方法创建 + +**getSingleton()** + +![image-20210819172805889](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172805889.png) + +**doCreateBean()** + +我们不看``createBean()``了 直接看``doCreateBean()`` + +![image-20210819172822455](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172822455.png) + +**addSingletonFactory()** + +![image-20210819172836587](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172836587.png) + +**populateBean()** + +创建完b实例的半成品并添加到三级缓存之后,我们返回``doCreateBean()``方法找到给实例b进行属性赋值操作。 + +![image-20210819172851492](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172851492.png) + +那进入方法看一下,找到``applyPropertyValues()``方法 + +![image-20210819172910520](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172910520.png) + +**applyPropertyValues()** + +![image-20210819172925167](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172925167.png) + +**resolveValueIfNecessary()** + +![image-20210819172939493](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172939493.png) + +**resolveReference()** + +![image-20210819172953142](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819172953142.png) + +**doGetBean()** + +![image-20210819173005437](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173005437.png) + +**getSingleton()** + +![image-20210819173017370](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173017370.png) + +该函数表达式其实就是``getEarlyBeanReference()``方法,然后获取到A实例,注意这个A实例此时是个半成品 并没有给b属性赋值 + +**doGetBean()** + +这时我们回到给B实例属性赋值的方法 + +![image-20210819173037168](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173037168.png) + +**getSingleton()** + +现在我们已经将B实例创建好也初始化好了,现在我们返回到了``getSingleton()``方法,现在需要将B这个完成的对象添加到一级缓存 + +![image-20210819173051610](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173051610.png) + +添加到一级缓存 + +![image-20210819173110987](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173110987.png) + +**doCreateBean()** + +这时我们返回到创建A实例的``doCreateBean()``方法,此时实例A的属性赋值也全部完成 + +![image-20210819173132696](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173132696.png) + +**getSingleton()** + +现在我们已经将A实例创建好也初始化好了,现在我们返回到了getSingleton()方法,现在需要将A这个完成的对象添加到一级缓存 + +![image-20210819173146212](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173146212.png) + +![image-20210819173155325](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173155325.png) + +由于后面太乱了 截图太多不方便,所以就不继续走源码了,但是大概的逻辑大家应该都清楚了。 + +我们将实例A的属性值b赋值完后添加到一级缓存之后,我们就可以从一级缓存获取到实例A,最后完成整个对象的创建。 + +## 3.4 为什么需要三级缓存,二级行不行? + +**一级缓存**:存储的是完整实例 + +**二级缓存**:存储的是半成品实例,也就是还未属性赋值的实例 + +**三级缓存**:存储的是``getEarlyBeanReference()``函数表达式,该表达式可以获取到bean早期的bean引用,并判断该bean是否需要代理,最终返回代理对象或普通对象 + +``getEarlyBeanReference``函数表达式,执行该表达式可以获取到bean早期的bean引用,并判断该bean是否需要代理,简单地说就是,如果一个bean我们定义了他需要代理,那么通过三级缓存的函数表达式执行获取到的就是代理对象,如果不需要代理,获取到的就是普通对象。 + +所以说我们只用二级缓存行不行其实取决你到底需不需要代理对象。 + +如果需要代理,我们则在创建过程中其实是创建了两个bean,一个是普通bean,一个是代理bean。**所以他就会在通过调用getEarlyBeanReference()函数表达式想要用你这个bean的时候,将代理bean覆盖掉普通bean,保证你这个bean的全局唯一**。如果说你不需要代理,那可以,你可以不需要三级缓存,二级缓存足以 + + + +# 四. JDK动态代理源码分析 + +## 4.1 JDK动态代理的实现 + +**需要动态代理的接口** + +```java +/** + * 需要动态代理的接口 + */ +public interface Movie { + + void player(); + + void speak(); +} +``` + +**需要动态代理的接口的真实实现** + +```java +/** + * 需要动态代理接口的真实实现 + */ +public class RealMovie implements Movie { + + @Override + public void player() { + System.out.println("看个电影"); + } + + @Override + public void speak() { + System.out.println("说句话"); + } +} +``` + +**动态代理处理器** + +```java +/** + * 动态代理处理类 + */ +public class MyInvocationHandler implements InvocationHandler { + + //需要动态代理接口的真实实现类 + private Object object; + + //通过构造方法去给需要动态代理接口的真实实现类赋值 + public MyInvocationHandler(Object object) { + this.object = object; + } + + + /** + * 对真实实现的目标方法进行增强 + * 当代理对象调用真实实现类的方法时,就会执行动态代理处理器中的该invoke方法 + * + * @param proxy 生成的代理对象 + * @param method 代理对象调用的方法 + * @param args 调用的方法中的参数 + * @return + * @throws Throwable + */ + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + //方法增强 + System.out.println("卖爆米花"); + + //object是真实实现,args是调用方法的参数 + //当代理对象调用真实实现的方法,那么这里就会将真实实现和方法参数传递过去,去调用真实实现的方法 + method.invoke(object,args); + + //方法增强 + System.out.println("扫地"); + return null; + } +} +``` + +**创建代理对象** + +```java +public class DynamicProxyTest { + + public static void main(String[] args) { + // 保存生成的代理类的字节码文件 + //由于设置sun.misc.ProxyGenerator.saveGeneratedFiles 的值为true,所以代理类的字节码内容保存在了项目根目录下,文件名为$Proxy0.class + System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); + + //需要动态代理接口的真实实现 + RealMovie realMovie = new RealMovie(); + //动态代理处理类 + MyInvocationHandler handler = new MyInvocationHandler(realMovie); + //获取动态代理对象 + //第一个参数:真实实现的类加载器 + //第二个参数:真实实现类它所实现的所有接口的数组 + //第三个参数:动态代理处理器 + Movie movie = (Movie) Proxy.newProxyInstance(realMovie.getClass().getClassLoader(), + realMovie.getClass().getInterfaces(), + handler); + movie.player(); + + } + +} +``` + +**结果** + +![image-20210819173404132](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173404132.png) + +由于设置 ``sun.misc.ProxyGenerator.saveGeneratedFiles`` 的值为``true``,所以代理类的字节码内容保存在了项目根目录下,文件名为``$Proxy0.class`` + +![image-20210819173435065](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173435065.png) + +**生成的代理对象字节码文件** + +```java +public final class $Proxy0 extends Proxy implements Movie { + private static Method m1; + private static Method m3; + private static Method m2; + private static Method m4; + private static Method m0; + + static { + try { + m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); + m3 = Class.forName("com.eayon.dynamic.Movie").getMethod("player"); + m2 = Class.forName("java.lang.Object").getMethod("toString"); + m4 = Class.forName("com.eayon.dynamic.Movie").getMethod("speak"); + m0 = Class.forName("java.lang.Object").getMethod("hashCode"); + } catch (NoSuchMethodException var2) { + throw new NoSuchMethodError(var2.getMessage()); + } catch (ClassNotFoundException var3) { + throw new NoClassDefFoundError(var3.getMessage()); + } + } + + public $Proxy0(InvocationHandler var1) throws { + super(var1); + } + + /** + * 重写被代理接口的方法 + * 因为生成的代理对象会实现被代理接口,所以我们在外部可以直接通过代理对象嗲偶哦那个被代理接口中的方法 + */ + public final void speak() throws { + try { + //当外部通过代理对象调用被代理接口的方法时,其实是通过invocationHandler中的invoke()方法去调用的。 + //这个h就是invocationHandler(我们之前创建的MyInvocationHandler代理处理器) + //this就是当前这个Proxy0代理对象 + //m4则具体要调用的方法 + super.h.invoke(this, m4, (Object[])null); + } catch (RuntimeException | Error var2) { + throw var2; + } catch (Throwable var3) { + throw new UndeclaredThrowableException(var3); + } + } + + //与上面的speak方法同理,都是重写的被代理接口中的方法 + public final void player() throws { + try { + super.h.invoke(this, m3, (Object[])null); + } catch (RuntimeException | Error var2) { + throw var2; + } catch (Throwable var3) { + throw new UndeclaredThrowableException(var3); + } + } + + public final String toString() throws { + try { + return (String)super.h.invoke(this, m2, (Object[])null); + } catch (RuntimeException | Error var2) { + throw var2; + } catch (Throwable var3) { + throw new UndeclaredThrowableException(var3); + } + } + + public final int hashCode() throws { + try { + return (Integer)super.h.invoke(this, m0, (Object[])null); + } catch (RuntimeException | Error var2) { + throw var2; + } catch (Throwable var3) { + throw new UndeclaredThrowableException(var3); + } + } + + public final boolean equals(Object var1) throws { + try { + return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); + } catch (RuntimeException | Error var3) { + throw var3; + } catch (Throwable var4) { + throw new UndeclaredThrowableException(var4); + } + } +} +``` + +**代理对象的特点** + +1、代理类继承了Proxy类并且**代理对象和真实实现一样都实现了要代理的接口** + +2、重写了equals、hashCode、toString + +3、有一个静态代码块,通过反射或者代理类的所有方法 + +4、通过invoke执行代理类中的目标方法doSomething + +## 4.2 源码分析 + +从上述代码中不难看出,创建代理对象的关键代码为: + +```java +//获取动态代理对象 +//第一个参数:真实实现的类加载器 +//第二个参数:真实实现类它所实现的所有接口的数组 +//第三个参数:动态代理处理器 +Movie movie = (Movie) Proxy.newProxyInstance(realMovie.getClass().getClassLoader(), + realMovie.getClass().getInterfaces(), + handler); +``` + +然后当执行如下代码的时候,也就是当代理对象调用真实实现的方法时,会自动跳转到动态代理处理器的invoke方法来进行调用。 + +```java +movie.player(); +``` + +这是为什么呢? + +那其实归根结底都在``Proxy.newProxyInstance() ``方法创建代理对象的源码中,我们一起来看看做了些什么 + +**Proxy.newProxyInstance()** + +```java +public static Object newProxyInstance(ClassLoader loader, + Class[] interfaces, + InvocationHandler h) + throws IllegalArgumentException +{ + //判断代理处理器是否为空,为空则抛出空指针异常 + Objects.requireNonNull(h); + + //将真实实现类它所实现的所有接口的数据进行拷贝 + final Class[] intfs = interfaces.clone(); + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + checkProxyAccess(Reflection.getCallerClass(), loader, intfs); + } + + //生成接口的代理对象的字节码文件(主要方法) + Class cl = getProxyClass0(loader, intfs); + + /* + * 使用自定义的InvocationHandler作为参数,调用构造函数获取代理对象示例 + */ + try { + if (sm != null) { + checkNewProxyPermission(Reflection.getCallerClass(), cl); + } + + //通过代理对象的字节码文件获取代理对象的构造器 + final Constructor cons = cl.getConstructor(constructorParams); + final InvocationHandler ih = h; + if (!Modifier.isPublic(cl.getModifiers())) { + AccessController.doPrivileged(new PrivilegedAction() { + public Void run() { + cons.setAccessible(true); + return null; + } + }); + } + //通过代理对象的构造器调用newInstance()反射获取代理对象实例 + return cons.newInstance(new Object[]{h}); + } catch (IllegalAccessException|InstantiationException e) { + throw new InternalError(e.toString(), e); + } catch (InvocationTargetException e) { + Throwable t = e.getCause(); + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } else { + throw new InternalError(t.toString(), t); + } + } catch (NoSuchMethodException e) { + throw new InternalError(e.toString(), e); + } +} +``` + +由上述代码我们发现,主要是通过``getProxyClass0()``方法去获取或者创建代理对象的字节码文件,通过代理对象字节码文件获取其构造器并通过反射生成代理对象实例。 + +那现在的重点就是如何获取或者创建代理对象的字节码文件,我们继续往下。 + +**getProxyClass0(loader, intfs)** + +那其实真正生成代理对象字节码文件的是这个方法,他会传入真实实现的类加载器和他所实现的接口数组。 + +```java +private static Class getProxyClass0(ClassLoader loader, + Class... interfaces) { + //限制真实实现所实现的接口数量不能大于65535个 + if (interfaces.length> 65535) { + throw new IllegalArgumentException("interface limit exceeded"); + } + + // 首先从缓存中获取该接口对于的代理对象,如果有则返回,没有则通过ProxyClassFactory创建 + return proxyClassCache.get(loader, interfaces); +} +``` + +**ProxyClassFactory** + +缓存中获取我们比较好理解,但是我们并没有在上述方法中发现proxyClassFactory + +我们可以点击进入``proxyClassCache.get()``方法看看是如何从缓存中获取的 + +```java +public V get(K key, P parameter) { + Objects.requireNonNull(parameter); + + expungeStaleEntries(); + + // 这里我们就理解成将真实实现的类加载器作为缓存key即可 + Object cacheKey = CacheKey.valueOf(key, refQueue); + + // 从缓存中获取代理对象 + ConcurrentMap> valuesMap = map.get(cacheKey); + if (valuesMap == null) { + ConcurrentMap> oldValuesMap + = map.putIfAbsent(cacheKey, + valuesMap = new ConcurrentHashMap()); + if (oldValuesMap != null) { + valuesMap = oldValuesMap; + } + } + + // 缓存中不存在则根据subKeyFactory.apply(key, parameter)方法进行创建 + Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); + Supplier supplier = valuesMap.get(subKey); + + .......省略无用代码........ +} +``` + +**subKeyFactory.apply(key, parameter)** + +我们点击进入apply方法发现其实是BiFunction接口,我们找到它的实现 + +![image-20210819173614848](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173614848.png) + +此时我们发现,我们进入的这个apply方法所在的位置是Proxy类下ProxyClassFactory这个静态内部类中 + +所以当缓存中没有相应的代理对象,则会调用ProxyClassFactory类的``apply``方法来创建代理类。 + +**ProxyClassFactory.apply()** + +```java +private static final class ProxyClassFactory + implements BiFunction[], Class> +{ + // 生成代理对象的字节码文件名的前缀,用于组装文件名 + private static final String proxyClassNamePrefix = "$Proxy"; + + // 生成代理对象字节码文件名的计数器,用于组装文件名(计数器默认从0开始) + private static final AtomicLong nextUniqueNumber = new AtomicLong(); + + @Override + public Class apply(ClassLoader loader, Class[] interfaces) { + + Map, Boolean> interfaceSet = new IdentityHashMap(interfaces.length); + for (Class intf : interfaces) { + //校验类加载器是否能通过接口名称加载该类 + Class interfaceClass = null; + try { + interfaceClass = Class.forName(intf.getName(), false, loader); + } catch (ClassNotFoundException e) { + } + if (interfaceClass != intf) { + throw new IllegalArgumentException( + intf + " is not visible from class loader"); + } + + //校验该类是否是接口类型 + if (!interfaceClass.isInterface()) { + throw new IllegalArgumentException( + interfaceClass.getName() + " is not an interface"); + } + + //校验接口是否重复 + if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { + throw new IllegalArgumentException( + "repeated interface: " + interfaceClass.getName()); + } + } + + String proxyPkg = null; // 代理对象包名 + int accessFlags = Modifier.PUBLIC | Modifier.FINAL; + + /* + * 用于生成代理对象需要使用的包名 + * 非public接口,代理类的包名与接口的包名相同 + */ + for (Class intf : interfaces) { + int flags = intf.getModifiers(); + if (!Modifier.isPublic(flags)) { + accessFlags = Modifier.FINAL; + String name = intf.getName(); + int n = name.lastIndexOf('.'); + String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); + if (proxyPkg == null) { + proxyPkg = pkg; + } else if (!pkg.equals(proxyPkg)) { + throw new IllegalArgumentException( + "non-public interfaces from different packages"); + } + } + } + + if (proxyPkg == null) { + // 如果代理接口是public修饰的,则使用默认的com.sun.proxy package作为包名 + proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; + } + + /* + * 为代理对象生成字节码文件名 + * 文件名格式:proxyName = 之前生成的包名 + $Proxy + 当前计数器的值(计数器默认从0开始) + * 比如 proxyName = com.sun.proxy.$Proxy0 + */ + long num = nextUniqueNumber.getAndIncrement(); + String proxyName = proxyPkg + proxyClassNamePrefix + num; + + /* + * 真正生成代理对象字节码文件的地方 + */ + //生成代理对象字节码数组 + byte[] proxyClassFile = ProxyGenerator.generateProxyClass( + proxyName, interfaces, accessFlags); + try { + // 将代理对象字节码数组生成字节码文件,并使用类加载器将代理对象的字节码文件加载到JVM内存中 + return defineClass0(loader, proxyName, + proxyClassFile, 0, proxyClassFile.length); + } catch (ClassFormatError e) { + throw new IllegalArgumentException(e.toString()); + } + } +} +``` + +我们可以看出它是通过``ProxyGenerator.generateProxyClass() ``先生成代理对象字节码数组, + +然后通过``defineClass0()``方法将代理对象的字节码数组生成字节码文件,并将该字节码通过类加载器加载到JVM中。 + +但是``defineClass0()``方法底层是通过``native``调用的``C++``,我们看不了,知道有这个事就行 + +![image-20210819173659056](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173659056.png) + +**ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags)** + +这个方法随便看看就行,不用过多理解 + +```java +public static byte[] generateProxyClass(final String var0, Class[] var1, int var2) { + ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2); + final byte[] var4 = var3.generateClassFile(); + // 是否要将生成代理对象的字节码文件保存到磁盘中 + // 该步骤也就是为什么之前我们在测试生成代理对象的时候使用System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");来将代理对象字节码文件保存下来 + if (saveGeneratedFiles) { + AccessController.doPrivileged(new PrivilegedAction() { + public Void run() { + try { + int var1 = var0.lastIndexOf(46); + Path var2; + if (var1> 0) { + Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar)); + Files.createDirectories(var3); + var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class"); + } else { + var2 = Paths.get(var0 + ".class"); + } + + Files.write(var2, var4, new OpenOption[0]); + return null; + } catch (IOException var4x) { + throw new InternalError("I/O exception saving generated file: " + var4x); + } + } + }); + } + + return var4; +} +``` + +## 4.3 总结 + +![image-20210819173737409](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173737409.png) + +![image-20210819173728693](https://cdn.jsdelivr.net/gh/EayonLee/IMG-Cloud@master/data/image-20210819173728693.png) + +创建代理对象的核心方法就是**Proxy.newProxyInstance()**。该方法首先会调用**getProxyClass0() 从缓存中获取或者创建代理对象字节码文件**,拿到代理对象字节码文件后调用**getConstructor()方法获取代理对象的构造器**,最后**通过cons.newInstance() 根据代理对象的构造器反射生成代理对象实例**并返回。 + +我们回过头来看getProxyClass0()方法,该方法**首先判断真实实现所实现的接口数量是否超限**,没有超限则proxyClassCache.get()**从缓存中获取代理实例**。如果缓存中没有,则去**创建代理对象**。那么是如何创建的呢?首先会根据:**包名 + $proxy0 + 当前计数器的值(默认从0开始) 生成代理对象的名称**,其次根据名称和代理对象需要实现的被代理接口去**生成代理对象字节码数组**。拿到字节码数组之后,就可以通过**调用的native方法去生成代理对象字节码文件**,最后进行返回。 \ No newline at end of file diff --git "a/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円350円207円252円345円256円232円344円271円211円IoC345円256円271円345円231円250円350円256円276円350円256円241円345円217円212円Spring346円272円220円347円240円201円345円210円206円346円236円220円/346円233円264円346円226円260円344円270円255円357円274円214円346円225円254円350円257円267円346円234円237円345円276円205円" "b/1351円230円266円346円256円265円357円274円232円345円274円200円346円272円220円346円241円206円346円236円266円346円272円220円347円240円201円345円211円226円346円236円220円/02346円250円241円345円235円227円357円274円232円350円207円252円345円256円232円344円271円211円IoC345円256円271円345円231円250円350円256円276円350円256円241円345円217円212円Spring346円272円220円347円240円201円345円210円206円346円236円220円/346円233円264円346円226円260円344円270円255円357円274円214円346円225円254円350円257円267円346円234円237円345円276円205円" deleted file mode 100644 index e69de29..0000000 diff --git a/README.md b/README.md index 99f8931..732031b 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,11 @@ * [任务一:自定义持久层框架(😎完结)](https://github.com/EayonLee/JavaGod/tree/main/1%E9%98%B6%E6%AE%B5%EF%BC%9A%E5%BC%80%E6%BA%90%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/01%E6%A8%A1%E5%9D%97%EF%BC%9A%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8C%81%E4%B9%85%E5%B1%82%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1%E5%8F%8AMyBatis%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90(%E5%AE%8C%E7%BB%93)/01.%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8C%81%E4%B9%85%E5%B1%82%E6%A1%86%E6%9E%B6) * [任务二:MyBatis基础回顾及高级应用(😎完结)](https://github.com/EayonLee/JavaGod/tree/main/1%E9%98%B6%E6%AE%B5%EF%BC%9A%E5%BC%80%E6%BA%90%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/01%E6%A8%A1%E5%9D%97%EF%BC%9A%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8C%81%E4%B9%85%E5%B1%82%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1%E5%8F%8AMyBatis%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90(%E5%AE%8C%E7%BB%93)/02.MyBatis%E5%9F%BA%E7%A1%80%E5%9B%9E%E9%A1%BE%E5%8F%8A%E9%AB%98%E7%BA%A7%E5%BA%94%E7%94%A8) * [任务三:MyBatis源码剖析(😎完结)](https://github.com/EayonLee/JavaGod/tree/main/1%E9%98%B6%E6%AE%B5%EF%BC%9A%E5%BC%80%E6%BA%90%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/01%E6%A8%A1%E5%9D%97%EF%BC%9A%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8C%81%E4%B9%85%E5%B1%82%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1%E5%8F%8AMyBatis%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90(%E5%AE%8C%E7%BB%93)/03.MyBatis%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90) - * [**模块二:自定义IoC容器及Spring源码分析(🐱‍👤更新中..)**](https://github.com/EayonLee/JavaGod/tree/main/1%E9%98%B6%E6%AE%B5%EF%BC%9A%E5%BC%80%E6%BA%90%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/02%E6%A8%A1%E5%9D%97%EF%BC%9A%E8%87%AA%E5%AE%9A%E4%B9%89IoC%E5%AE%B9%E5%99%A8%E8%AE%BE%E8%AE%A1%E5%8F%8ASpring%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90) - * [任务一: 自定义Ioc&AOP框架(👾更新中..)]() - * 任务二: Spring IoC高级应用与源码剖析 - * 任务三: Spring AOP高级应用与源码剖析 + * [**模块二:Spring应用及源码剖析**](https://github.com/EayonLee/JavaGod/tree/main/1%E9%98%B6%E6%AE%B5%EF%BC%9A%E5%BC%80%E6%BA%90%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/02%E6%A8%A1%E5%9D%97%EF%BC%9A%E8%87%AA%E5%AE%9A%E4%B9%89IoC%E5%AE%B9%E5%99%A8%E8%AE%BE%E8%AE%A1%E5%8F%8ASpring%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90) + * [任务一: Spring IoC概念及应用(😎完结)](https://github.com/EayonLee/JavaGod/tree/main/1%E9%98%B6%E6%AE%B5%EF%BC%9A%E5%BC%80%E6%BA%90%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/02%E6%A8%A1%E5%9D%97%EF%BC%9ASpring%E5%BA%94%E7%94%A8%E5%8F%8A%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90(%E5%AE%8C%E7%BB%93)/01.IoC) + * [任务二: Spring AOP概念及应用(😎完结)](https://github.com/EayonLee/JavaGod/tree/main/1%E9%98%B6%E6%AE%B5%EF%BC%9A%E5%BC%80%E6%BA%90%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/02%E6%A8%A1%E5%9D%97%EF%BC%9ASpring%E5%BA%94%E7%94%A8%E5%8F%8A%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90(%E5%AE%8C%E7%BB%93)/02.AOP) + * [任务三: Spring JdbcTemplate概念及应用(😎完结)](https://github.com/EayonLee/JavaGod/tree/main/1%E9%98%B6%E6%AE%B5%EF%BC%9A%E5%BC%80%E6%BA%90%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/02%E6%A8%A1%E5%9D%97%EF%BC%9ASpring%E5%BA%94%E7%94%A8%E5%8F%8A%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90(%E5%AE%8C%E7%BB%93)/03.JdbcTemplate) + * [任务四: Spring 源码剖析(😎完结)](https://github.com/EayonLee/JavaGod/tree/main/1%E9%98%B6%E6%AE%B5%EF%BC%9A%E5%BC%80%E6%BA%90%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/02%E6%A8%A1%E5%9D%97%EF%BC%9ASpring%E5%BA%94%E7%94%A8%E5%8F%8A%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90(%E5%AE%8C%E7%BB%93)/04.Spring%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90) * 模块三:自定义MVC框架、SpringMVC应用及源码分析、SpringData应用及源码分析 * 模块四:约定优于配置设计范式及Spring Boot源码剖析

AltStyle によって変換されたページ (->オリジナル) /