From 81078113e94c4106b729d46c85cb8c1fcc2c1c49 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Tue, 24 Nov 2020 21:15:12 +0800 Subject: [PATCH 01/81] instantiation-strategy --- .../ioc/BeanDefinitionAndBeanDefinitionRegistryTest.java} | 7 +++---- .../{beans/factory => test/ioc}/HelloService.java | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) rename src/test/java/org/springframework/{beans/factory/BeanFactoryTest.java => test/ioc/BeanDefinitionAndBeanDefinitionRegistryTest.java} (83%) rename src/test/java/org/springframework/{beans/factory => test/ioc}/HelloService.java (78%) diff --git a/src/test/java/org/springframework/beans/factory/BeanFactoryTest.java b/src/test/java/org/springframework/test/ioc/BeanDefinitionAndBeanDefinitionRegistryTest.java similarity index 83% rename from src/test/java/org/springframework/beans/factory/BeanFactoryTest.java rename to src/test/java/org/springframework/test/ioc/BeanDefinitionAndBeanDefinitionRegistryTest.java index 9b143c9..71e39bc 100644 --- a/src/test/java/org/springframework/beans/factory/BeanFactoryTest.java +++ b/src/test/java/org/springframework/test/ioc/BeanDefinitionAndBeanDefinitionRegistryTest.java @@ -1,4 +1,4 @@ -package org.springframework.beans.factory; +package org.springframework.test.ioc; import org.junit.Test; import org.springframework.beans.factory.config.BeanDefinition; @@ -6,9 +6,9 @@ /** * @author derekyi - * @date 2020/11/22 + * @date 2020/11/24 */ -public class BeanFactoryTest { +public class BeanDefinitionAndBeanDefinitionRegistryTest { @Test public void testBeanFactory() throws Exception { @@ -20,4 +20,3 @@ public void testBeanFactory() throws Exception { helloService.sayHello(); } } - diff --git a/src/test/java/org/springframework/beans/factory/HelloService.java b/src/test/java/org/springframework/test/ioc/HelloService.java similarity index 78% rename from src/test/java/org/springframework/beans/factory/HelloService.java rename to src/test/java/org/springframework/test/ioc/HelloService.java index 3dae861..da03555 100644 --- a/src/test/java/org/springframework/beans/factory/HelloService.java +++ b/src/test/java/org/springframework/test/ioc/HelloService.java @@ -1,4 +1,4 @@ -package org.springframework.beans.factory; +package org.springframework.test.ioc; /** * @author derekyi From bf88d6ae67bd553c7e8b1afec6baf503102b30cc Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Tue, 24 Nov 2020 21:57:20 +0800 Subject: [PATCH 02/81] instantiation-strategy --- assets/instantiation-strategy.png | Bin 0 -> 10573 bytes changelog.md | 6 +++++- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 assets/instantiation-strategy.png diff --git a/assets/instantiation-strategy.png b/assets/instantiation-strategy.png new file mode 100644 index 0000000000000000000000000000000000000000..36f0419ca44b3fc32764216abdfc3bb74ec1afbb GIT binary patch literal 10573 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 分支:instantiation-strategy -现在bean是在AbstractAutowireCapableBeanFactory.doCreateBean方法中用beanClass.newInstance()来实例化,仅适用于bean有无参构造函数的情况。针对bean的实例化,抽象出一个实例化策略的接口InstantiationStrategy,有两个实现类: +现在bean是在AbstractAutowireCapableBeanFactory.doCreateBean方法中用beanClass.newInstance()来实例化,仅适用于bean有无参构造函数的情况。 + +![](./assets/instantiation-strategy.png) + +针对bean的实例化,抽象出一个实例化策略的接口InstantiationStrategy,有两个实现类: - SimpleInstantiationStrategy,使用bean的构造函数来实例化 - CglibSubclassingInstantiationStrategy,使用CGLIB动态生成子类 From aa3ec5aeb822f2c469c67403d60d0da3a1934f6f Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 26 Dec 2020 21:52:51 +0800 Subject: [PATCH 03/81] =?UTF-8?q?=E5=8C=85=E6=89=AB=E6=8F=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 +++- README_CN.md | 13 +++- changelog.md | 40 +++++++++++ .../beans/factory/config/BeanDefinition.java | 15 ++++ .../factory/xml/XmlBeanDefinitionReader.java | 29 +++++++- .../ClassPathBeanDefinitionScanner.java | 71 +++++++++++++++++++ ...athScanningCandidateComponentProvider.java | 26 +++++++ .../context/annotation/Scope.java | 15 ++++ .../springframework/stereotype/Component.java | 15 ++++ .../org/springframework/test/bean/Car.java | 3 + .../test/ioc/PackageScanTest.java | 22 ++++++ src/test/resources/package-scan.xml | 12 ++++ 12 files changed, 267 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java create mode 100644 src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java create mode 100644 src/main/java/org/springframework/context/annotation/Scope.java create mode 100644 src/main/java/org/springframework/stereotype/Component.java create mode 100644 src/test/java/org/springframework/test/ioc/PackageScanTest.java create mode 100644 src/test/resources/package-scan.xml diff --git a/README.md b/README.md index af8032b..cf7f7b2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # mini-spring [![Build Status](https://img.shields.io/badge/build-passing-brightgreen)](https://github.com/DerekYRC/mini-spring) [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) +[![Stars](https://img.shields.io/github/stars/DerekYRC/mini-spring)](https://img.shields.io/github/stars/DerekYRC/mini-spring) +[![Forks](https://img.shields.io/github/forks/DerekYRC/mini-spring)](https://img.shields.io/github/forks/DerekYRC/mini-spring) ## About * [中文版](./README_CN.md) @@ -38,9 +40,9 @@ If this project can help you, please give a **STAR, thank you!!!** #### Expanding * [PropertyPlaceholderConfigurer](#PropertyPlaceholderConfigurer) -* [Type conversion](#类型转换) * [Package scan](#包扫描) -* [Autowired annotation](#基于注解的依赖注入Autowired) +* [@Autowired and @Value annotation](#基于注解@Autowired和@Value的依赖注入) +* [Type conversion](#类型转换) #### Advanced * [Solve the problem of circular dependencies](#解决循环依赖问题) @@ -48,6 +50,12 @@ If this project can help you, please give a **STAR, thank you!!!** ## Usage Each function point corresponds to a branch. Switch to the branch corresponding to the function point to see the new function. The incremental change point is described in the [changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) file. +## Contributing +Any contributions you make are greatly appreciated. + +## Contact +Please feel free to ask me any questions related to mini-spring and other technologies. My email is **15521077528@163.com**. + ## Reference - [《Spring源码深度解析》](https://book.douban.com/subject/25866350/) - [《精通Spring 4.x》](https://book.douban.com/subject/26952826/) diff --git a/README_CN.md b/README_CN.md index 42c344b..0ef4cc5 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,6 +1,8 @@ # mini-spring [![Build Status](https://img.shields.io/badge/build-passing-brightgreen)](https://github.com/DerekYRC/mini-spring) [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) +[![Stars](https://img.shields.io/github/stars/DerekYRC/mini-spring)](https://img.shields.io/github/stars/DerekYRC/mini-spring) +[![Forks](https://img.shields.io/github/forks/DerekYRC/mini-spring)](https://img.shields.io/github/forks/DerekYRC/mini-spring) ## 关于 * [English version](./README.md) @@ -38,9 +40,9 @@ #### 扩展篇 * [PropertyPlaceholderConfigurer](#PropertyPlaceholderConfigurer) -* [类型转换](#类型转换) * [包扫描](#包扫描) -* [基于注解的依赖注入Autowired](#基于注解的依赖注入Autowired) +* [基于注解@Autowired和@Value的依赖注入](#基于注解@Autowired和@Value的依赖注入) +* [类型转换](#类型转换) #### 高级篇 * [解决循环依赖问题](#解决循环依赖问题) @@ -48,6 +50,13 @@ ## 使用方法 每个功能点对应一个分支,切换到功能点对应的分支了解新增的功能,增量改动点在[changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md)文件中描述。 +## 贡献 +欢迎Pull Request + +## 联系我 +欢迎探讨跟mini-spring和其他技术相关的问题,个人邮箱:**15521077528@163.com** + + ## 参考 - [《Spring源码深度解析》](https://book.douban.com/subject/25866350/) - [《精通Spring 4.x》](https://book.douban.com/subject/26952826/) diff --git a/changelog.md b/changelog.md index 3b42239..c8d815c 100644 --- a/changelog.md +++ b/changelog.md @@ -976,7 +976,47 @@ public class PropertyPlaceholderConfigurerTest { } ``` +## 包扫描 +> 分支:package-scan +结合bean的生命周期,包扫描只不过是扫描特定注解的类,提取类的相关信息组装成BeanDefinition注册到容器中。 + +在XmlBeanDefinitionReader中解析**``````**标签,扫描类组装BeanDefinition然后注册到容器中的操作在ClassPathBeanDefinitionScanner#doScan中实现。 + +测试: +``` +@Component +public class Car { + +} +``` +package-scan.xml +``` + + + + + + +``` +``` +public class PackageScanTest { + + @Test + public void testScanPackage() throws Exception { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:package-scan.xml"); + + Car car = applicationContext.getBean("car", Car.class); + assertThat(car).isNotNull(); + } +} +``` diff --git a/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java b/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java index 66a08c8..6719772 100644 --- a/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java +++ b/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java @@ -2,6 +2,8 @@ import org.springframework.beans.PropertyValues; +import java.util.Objects; + /** * BeanDefinition实例保存bean的信息,包括class类型、方法构造参数、bean属性、bean的scope等,此处简化只包含class类型和bean属性 * @@ -82,4 +84,17 @@ public String getDestroyMethodName() { public void setDestroyMethodName(String destroyMethodName) { this.destroyMethodName = destroyMethodName; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BeanDefinition that = (BeanDefinition) o; + return beanClass.equals(that.beanClass); + } + + @Override + public int hashCode() { + return Objects.hash(beanClass); + } } diff --git a/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java b/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java index 91c3a00..8882ddf 100644 --- a/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java +++ b/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java @@ -12,6 +12,7 @@ import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.support.AbstractBeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; @@ -37,6 +38,8 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { public static final String INIT_METHOD_ATTRIBUTE = "init-method"; public static final String DESTROY_METHOD_ATTRIBUTE = "destroy-method"; public static final String SCOPE_ATTRIBUTE = "scope"; + public static final String BASE_PACKAGE_ATTRIBUTE = "base-package"; + public static final String COMPONENT_SCAN_ELEMENT = "component-scan"; public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) { super(registry); @@ -71,8 +74,19 @@ protected void doLoadBeanDefinitions(InputStream inputStream) throws DocumentExc SAXReader reader = new SAXReader(); Document document = reader.read(inputStream); - Element beans = document.getRootElement(); - List beanList = beans.elements(BEAN_ELEMENT); + Element root = document.getRootElement(); + + //解析context:component-scan标签并扫描指定包中的类,提取类信息,组装成BeanDefinition + Element componentScan = root.element(COMPONENT_SCAN_ELEMENT); + if (componentScan != null) { + String scanPath = componentScan.attributeValue(BASE_PACKAGE_ATTRIBUTE); + if (StrUtil.isEmpty(scanPath)) { + throw new BeansException("The value of base-package attribute can not be empty or null"); + } + scanPackage(scanPath); + } + + List beanList = root.elements(BEAN_ELEMENT); for (Element bean : beanList) { String beanId = bean.attributeValue(ID_ATTRIBUTE); String beanName = bean.attributeValue(NAME_ATTRIBUTE); @@ -126,4 +140,15 @@ protected void doLoadBeanDefinitions(InputStream inputStream) throws DocumentExc getRegistry().registerBeanDefinition(beanName, beanDefinition); } } + + /** + * 扫描注解Component的类,提取信息,组装成BeanDefinition + * + * @param scanPath + */ + private void scanPackage(String scanPath) { + String[] basePackages = StrUtil.splitToArray(scanPath, ','); + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(getRegistry()); + scanner.doScan(basePackages); + } } diff --git a/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java new file mode 100644 index 0000000..4c7d03a --- /dev/null +++ b/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java @@ -0,0 +1,71 @@ +package org.springframework.context.annotation; + +import cn.hutool.core.util.StrUtil; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.stereotype.Component; + +import java.util.Set; + +/** + * @author derekyi + * @date 2020/12/26 + */ +public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider { + + private BeanDefinitionRegistry registry; + + public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) { + this.registry = registry; + } + + public void doScan(String... basePackages) { + for (String basePackage : basePackages) { + Set candidates = findCandidateComponents(basePackage); + for (BeanDefinition candidate : candidates) { + // 解析bean的作用域 + String beanScope = resolveBeanScope(candidate); + if (StrUtil.isNotEmpty(beanScope)) { + candidate.setScope(beanScope); + } + //生成bean的名称 + String beanName = determineBeanName(candidate); + //注册BeanDefinition + registry.registerBeanDefinition(beanName, candidate); + } + } + } + + /** + * 获取bean的作用域 + * + * @param beanDefinition + * @return + */ + private String resolveBeanScope(BeanDefinition beanDefinition) { + Class beanClass = beanDefinition.getBeanClass(); + Scope scope = beanClass.getAnnotation(Scope.class); + if (scope != null) { + return scope.value(); + } + + return StrUtil.EMPTY; + } + + + /** + * 生成bean的名称 + * + * @param beanDefinition + * @return + */ + private String determineBeanName(BeanDefinition beanDefinition) { + Class beanClass = beanDefinition.getBeanClass(); + Component component = beanClass.getAnnotation(Component.class); + String value = component.value(); + if (StrUtil.isEmpty(value)) { + value = StrUtil.lowerFirst(beanClass.getSimpleName()); + } + return value; + } +} diff --git a/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java new file mode 100644 index 0000000..e66567c --- /dev/null +++ b/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -0,0 +1,26 @@ +package org.springframework.context.annotation; + +import cn.hutool.core.util.ClassUtil; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.stereotype.Component; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * @author derekyi + * @date 2020/12/26 + */ +public class ClassPathScanningCandidateComponentProvider { + + public Set findCandidateComponents(String basePackage) { + Set candidates = new LinkedHashSet(); + // 扫描有org.springframework.stereotype.Component注解的类 + Set> classes = ClassUtil.scanPackageByAnnotation(basePackage, Component.class); + for (Class clazz : classes) { + BeanDefinition beanDefinition = new BeanDefinition(clazz); + candidates.add(beanDefinition); + } + return candidates; + } +} diff --git a/src/main/java/org/springframework/context/annotation/Scope.java b/src/main/java/org/springframework/context/annotation/Scope.java new file mode 100644 index 0000000..20bc7f7 --- /dev/null +++ b/src/main/java/org/springframework/context/annotation/Scope.java @@ -0,0 +1,15 @@ +package org.springframework.context.annotation; + +import java.lang.annotation.*; + +/** + * @author derekyi + * @date 2020/12/26 + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Scope { + + String value() default "singleton"; +} diff --git a/src/main/java/org/springframework/stereotype/Component.java b/src/main/java/org/springframework/stereotype/Component.java new file mode 100644 index 0000000..005c9b4 --- /dev/null +++ b/src/main/java/org/springframework/stereotype/Component.java @@ -0,0 +1,15 @@ +package org.springframework.stereotype; + +import java.lang.annotation.*; + +/** + * @author derekyi + * @date 2020/12/26 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Component { + + String value() default ""; +} \ No newline at end of file diff --git a/src/test/java/org/springframework/test/bean/Car.java b/src/test/java/org/springframework/test/bean/Car.java index 9406211..6e68b15 100644 --- a/src/test/java/org/springframework/test/bean/Car.java +++ b/src/test/java/org/springframework/test/bean/Car.java @@ -1,9 +1,12 @@ package org.springframework.test.bean; +import org.springframework.stereotype.Component; + /** * @author derekyi * @date 2020/11/24 */ +@Component public class Car { private String brand; diff --git a/src/test/java/org/springframework/test/ioc/PackageScanTest.java b/src/test/java/org/springframework/test/ioc/PackageScanTest.java new file mode 100644 index 0000000..40d8244 --- /dev/null +++ b/src/test/java/org/springframework/test/ioc/PackageScanTest.java @@ -0,0 +1,22 @@ +package org.springframework.test.ioc; + +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.test.bean.Car; + +import static org.assertj.core.api.Java6Assertions.assertThat; + +/** + * @author derekyi + * @date 2020/12/26 + */ +public class PackageScanTest { + + @Test + public void testScanPackage() throws Exception { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:package-scan.xml"); + + Car car = applicationContext.getBean("car", Car.class); + assertThat(car).isNotNull(); + } +} diff --git a/src/test/resources/package-scan.xml b/src/test/resources/package-scan.xml new file mode 100644 index 0000000..98cf664 --- /dev/null +++ b/src/test/resources/package-scan.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file From ad87fc0511df389094647943917a276518f09501 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 26 Dec 2020 21:56:31 +0800 Subject: [PATCH 04/81] update readme.md --- README_CN.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README_CN.md b/README_CN.md index 0ef4cc5..7808167 100644 --- a/README_CN.md +++ b/README_CN.md @@ -51,11 +51,10 @@ 每个功能点对应一个分支,切换到功能点对应的分支了解新增的功能,增量改动点在[changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md)文件中描述。 ## 贡献 -欢迎Pull Request +欢迎Pull Request。 ## 联系我 -欢迎探讨跟mini-spring和其他技术相关的问题,个人邮箱:**15521077528@163.com** - +欢迎探讨跟mini-spring和其他技术相关的问题,个人邮箱:**15521077528@163.com**。 ## 参考 - [《Spring源码深度解析》](https://book.douban.com/subject/25866350/) From be7526fe0de1a48df02698fff04246d1450103f9 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 26 Dec 2020 22:08:49 +0800 Subject: [PATCH 05/81] update changelog.md --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index c8d815c..4a84f45 100644 --- a/changelog.md +++ b/changelog.md @@ -981,7 +981,7 @@ public class PropertyPlaceholderConfigurerTest { 结合bean的生命周期,包扫描只不过是扫描特定注解的类,提取类的相关信息组装成BeanDefinition注册到容器中。 -在XmlBeanDefinitionReader中解析**``````**标签,扫描类组装BeanDefinition然后注册到容器中的操作在ClassPathBeanDefinitionScanner#doScan中实现。 +在XmlBeanDefinitionReader中解析``````标签,扫描类组装BeanDefinition然后注册到容器中的操作在ClassPathBeanDefinitionScanner#doScan中实现。 测试: ``` From 07d5ed085cf981d2fd3c93738923fbf03c6ec964 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sun, 27 Dec 2020 14:54:46 +0800 Subject: [PATCH 06/81] =?UTF-8?q?@Value=E6=B3=A8=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- README_CN.md | 3 +- changelog.md | 47 ++++++++++++++ .../DefaultAdvisorAutoProxyCreator.java | 6 ++ .../PropertyPlaceholderConfigurer.java | 45 ++++++++++---- .../AutowiredAnnotationBeanPostProcessor.java | 61 +++++++++++++++++++ .../beans/factory/annotation/Value.java | 17 ++++++ .../config/ConfigurableBeanFactory.java | 5 ++ .../InstantiationAwareBeanPostProcessor.java | 13 ++++ .../AbstractAutowireCapableBeanFactory.java | 23 +++++++ .../factory/support/AbstractBeanFactory.java | 18 +++++- .../ClassPathBeanDefinitionScanner.java | 6 ++ .../util/StringValueResolver.java | 10 +++ .../org/springframework/test/bean/Car.java | 2 + .../test/ioc/ValueAnnotationTest.java | 22 +++++++ src/test/resources/value-annotation.xml | 16 +++++ 16 files changed, 281 insertions(+), 16 deletions(-) create mode 100644 src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java create mode 100644 src/main/java/org/springframework/beans/factory/annotation/Value.java create mode 100644 src/main/java/org/springframework/util/StringValueResolver.java create mode 100644 src/test/java/org/springframework/test/ioc/ValueAnnotationTest.java create mode 100644 src/test/resources/value-annotation.xml diff --git a/README.md b/README.md index cf7f7b2..76ae567 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,8 @@ If this project can help you, please give a **STAR, thank you!!!** #### Expanding * [PropertyPlaceholderConfigurer](#PropertyPlaceholderConfigurer) * [Package scan](#包扫描) -* [@Autowired and @Value annotation](#基于注解@Autowired和@Value的依赖注入) +* [@Value annotation](#@Value annotation) +* [@Autowired annotation](#@Autowired annotation) * [Type conversion](#类型转换) #### Advanced diff --git a/README_CN.md b/README_CN.md index 7808167..143079e 100644 --- a/README_CN.md +++ b/README_CN.md @@ -41,7 +41,8 @@ #### 扩展篇 * [PropertyPlaceholderConfigurer](#PropertyPlaceholderConfigurer) * [包扫描](#包扫描) -* [基于注解@Autowired和@Value的依赖注入](#基于注解@Autowired和@Value的依赖注入) +* [@Value注解](#@Value注解) +* [基于注解@Autowired的依赖注入](#基于注解@Autowired的依赖注入) * [类型转换](#类型转换) #### 高级篇 diff --git a/changelog.md b/changelog.md index 4a84f45..b01bedf 100644 --- a/changelog.md +++ b/changelog.md @@ -1018,11 +1018,58 @@ public class PackageScanTest { } ``` +## @Value注解 +> 分支:value-annotation +注解@Value和@Autowired通过BeanPostProcessor处理。InstantiationAwareBeanPostProcessor增加postProcessPropertyValues方法,在bean实例化之后设置属性之前执行,查看AbstractAutowireCapableBeanFactory#doCreateBean方法。 +增加AutowiredAnnotationBeanPostProcessor用于处理注解@Value,@Autowired的处理在下一节实现,在ClassPathBeanDefinitionScanner#doScan将其添加到容器中。查看AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues,其中字符解析器StringValueResolver在PropertyPlaceholderConfigurer中添加到BeanFactory中。 +测试: +``` +@Component +public class Car { + + @Value("${brand}") + private String brand; +} +``` +value-annotation.xml +``` + + + + + + + + +``` +car.properties +``` +brand=lamborghini +``` +``` +public class ValueAnnotationTest { + + @Test + public void testValueAnnotation() throws Exception { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:value-annotation.xml"); + + Car car = applicationContext.getBean("car", Car.class); + assertThat(car.getBrand()).isEqualTo("lamborghini"); + } +} + +``` diff --git a/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java b/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java index 4153838..8668ac4 100644 --- a/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java +++ b/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java @@ -6,6 +6,7 @@ import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyValues; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.BeanDefinition; @@ -73,4 +74,9 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) thro public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } + + @Override + public PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeansException { + return pvs; + } } diff --git a/src/main/java/org/springframework/beans/factory/PropertyPlaceholderConfigurer.java b/src/main/java/org/springframework/beans/factory/PropertyPlaceholderConfigurer.java index 63fbd30..92124cb 100644 --- a/src/main/java/org/springframework/beans/factory/PropertyPlaceholderConfigurer.java +++ b/src/main/java/org/springframework/beans/factory/PropertyPlaceholderConfigurer.java @@ -7,6 +7,7 @@ import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; +import org.springframework.util.StringValueResolver; import java.io.IOException; import java.util.Properties; @@ -30,6 +31,10 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) //属性值替换占位符 processProperties(beanFactory, properties); + + //往容器中添加字符解析器,供解析@Value注解使用 + StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(properties); + beanFactory.addEmbeddedValueResolver(valueResolver); } /** @@ -69,22 +74,40 @@ private void resolvePropertyValues(BeanDefinition beanDefinition, Properties pro for (PropertyValue propertyValue : propertyValues.getPropertyValues()) { Object value = propertyValue.getValue(); if (value instanceof String) { - //TODO 仅简单支持一个占位符的格式 - String strVal = (String) value; - StringBuffer buf = new StringBuffer(strVal); - int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX); - int endIndex = strVal.indexOf(PLACEHOLDER_SUFFIX); - if (startIndex != -1 && endIndex != -1 && startIndex < endIndex) { - String propKey = strVal.substring(startIndex + 2, endIndex); - String propVal = properties.getProperty(propKey); - buf.replace(startIndex, endIndex + 1, propVal); - propertyValues.addPropertyValue(new PropertyValue(propertyValue.getName(), buf.toString())); - } + value = resolvePlaceholder((String) value, properties); + propertyValues.addPropertyValue(new PropertyValue(propertyValue.getName(), value)); } } } + private String resolvePlaceholder(String value, Properties properties) { + //TODO 仅简单支持一个占位符的格式 + String strVal = value; + StringBuffer buf = new StringBuffer(strVal); + int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX); + int endIndex = strVal.indexOf(PLACEHOLDER_SUFFIX); + if (startIndex != -1 && endIndex != -1 && startIndex < endIndex) { + String propKey = strVal.substring(startIndex + 2, endIndex); + String propVal = properties.getProperty(propKey); + buf.replace(startIndex, endIndex + 1, propVal); + } + return buf.toString(); + } + public void setLocation(String location) { this.location = location; } + + private class PlaceholderResolvingStringValueResolver implements StringValueResolver { + + private final Properties properties; + + public PlaceholderResolvingStringValueResolver(Properties properties) { + this.properties = properties; + } + + public String resolveStringValue(String strVal) throws BeansException { + return PropertyPlaceholderConfigurer.this.resolvePlaceholder(strVal, properties); + } + } } diff --git a/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java new file mode 100644 index 0000000..e07be4a --- /dev/null +++ b/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -0,0 +1,61 @@ +package org.springframework.beans.factory.annotation; + +import cn.hutool.core.bean.BeanUtil; +import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyValues; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; + +import java.lang.reflect.Field; + +/** + * 处理@Autowired和@Value注解的BeanPostProcessor + * + * @author derekyi + * @date 2020/12/27 + */ +public class AutowiredAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, BeanFactoryAware { + + private ConfigurableListableBeanFactory beanFactory; + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + } + + @Override + public PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeansException { + //处理@Value注解 + Class clazz = bean.getClass(); + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + Value valueAnnotation = field.getAnnotation(Value.class); + if (valueAnnotation != null) { + String value = valueAnnotation.value(); + value = beanFactory.resolveEmbeddedValue(value); + BeanUtil.setFieldValue(bean, field.getName(), value); + } + } + + //处理@Autowired注解(下一节实现) + + return pvs; + } + + @Override + public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException { + return null; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return null; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return null; + } +} diff --git a/src/main/java/org/springframework/beans/factory/annotation/Value.java b/src/main/java/org/springframework/beans/factory/annotation/Value.java new file mode 100644 index 0000000..f585874 --- /dev/null +++ b/src/main/java/org/springframework/beans/factory/annotation/Value.java @@ -0,0 +1,17 @@ +package org.springframework.beans.factory.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author derekyi + * @date 2020/12/27 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) +public @interface Value { + + String value(); +} \ No newline at end of file diff --git a/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java b/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java index 1f7b0c2..8e83dfb 100644 --- a/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java @@ -1,6 +1,7 @@ package org.springframework.beans.factory.config; import org.springframework.beans.factory.HierarchicalBeanFactory; +import org.springframework.util.StringValueResolver; /** * @author derekyi @@ -17,4 +18,8 @@ public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, Single * 销毁单例bean */ void destroySingletons(); + + void addEmbeddedValueResolver(StringValueResolver valueResolver); + + String resolveEmbeddedValue(String value); } diff --git a/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java b/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java index 6025cbc..4d66940 100644 --- a/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java +++ b/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java @@ -1,6 +1,7 @@ package org.springframework.beans.factory.config; import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyValues; /** * @author derekyi @@ -17,4 +18,16 @@ public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor { * @throws BeansException */ Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException; + + /** + * bean实例化之后,设置属性之前执行 + * + * @param pvs + * @param bean + * @param beanName + * @return + * @throws BeansException + */ + PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) + throws BeansException; } diff --git a/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index dcd6910..619abcf 100644 --- a/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -5,6 +5,7 @@ import cn.hutool.core.util.StrUtil; import org.springframework.beans.BeansException; import org.springframework.beans.PropertyValue; +import org.springframework.beans.PropertyValues; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; @@ -64,6 +65,8 @@ protected Object doCreateBean(String beanName, BeanDefinition beanDefinition) { Object bean = null; try { bean = createBeanInstance(beanDefinition); + //在设置bean属性之前,允许BeanPostProcessor修改属性值 + applyBeanPostprocessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition); //为bean填充属性 applyPropertyValues(beanName, bean, beanDefinition); //执行bean的初始化方法和BeanPostProcessor的前置和后置处理方法 @@ -81,6 +84,26 @@ protected Object doCreateBean(String beanName, BeanDefinition beanDefinition) { return bean; } + /** + * 在设置bean属性之前,允许BeanPostProcessor修改属性值 + * + * @param beanName + * @param bean + * @param beanDefinition + */ + protected void applyBeanPostprocessorsBeforeApplyingPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) { + for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) { + if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) { + PropertyValues pvs = ((InstantiationAwareBeanPostProcessor) beanPostProcessor).postProcessPropertyValues(beanDefinition.getPropertyValues(), bean, beanName); + if (pvs != null) { + for (PropertyValue propertyValue : pvs.getPropertyValues()) { + beanDefinition.getPropertyValues().addPropertyValue(propertyValue); + } + } + } + } + } + /** * 注册有销毁方法的bean,即bean继承自DisposableBean或有自定义的销毁方法 * diff --git a/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index f7ca99d..7a5ada7 100644 --- a/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -1,18 +1,16 @@ package org.springframework.beans.factory.support; -import cn.hutool.core.lang.Assert; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.util.StringValueResolver; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.CopyOnWriteArrayList; /** * @author derekyi @@ -24,6 +22,8 @@ public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry i private final Map factoryBeanObjectCache = new HashMap<>(); + private final List embeddedValueResolvers = new ArrayList(); + @Override public Object getBean(String name) throws BeansException { Object sharedInstance = getSingleton(name); @@ -87,4 +87,16 @@ public void addBeanPostProcessor(BeanPostProcessor beanPostProcessor) { public List getBeanPostProcessors() { return this.beanPostProcessors; } + + public void addEmbeddedValueResolver(StringValueResolver valueResolver) { + this.embeddedValueResolvers.add(valueResolver); + } + + public String resolveEmbeddedValue(String value) { + String result = value; + for (StringValueResolver resolver : this.embeddedValueResolvers) { + result = resolver.resolveStringValue(result); + } + return result; + } } diff --git a/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java index 4c7d03a..cfce9cd 100644 --- a/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java +++ b/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java @@ -1,6 +1,7 @@ package org.springframework.context.annotation; import cn.hutool.core.util.StrUtil; +import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.stereotype.Component; @@ -13,6 +14,8 @@ */ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider { + public static final String AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME = "org.springframework.context.annotation.internalAutowiredAnnotationProcessor"; + private BeanDefinitionRegistry registry; public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) { @@ -34,6 +37,9 @@ public void doScan(String... basePackages) { registry.registerBeanDefinition(beanName, candidate); } } + + //注册处理@Autowired和@Value注解的BeanPostProcessor + registry.registerBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME, new BeanDefinition(AutowiredAnnotationBeanPostProcessor.class)); } /** diff --git a/src/main/java/org/springframework/util/StringValueResolver.java b/src/main/java/org/springframework/util/StringValueResolver.java new file mode 100644 index 0000000..fed2c1e --- /dev/null +++ b/src/main/java/org/springframework/util/StringValueResolver.java @@ -0,0 +1,10 @@ +package org.springframework.util; + +/** + * @author derekyi + * @date 2020/12/27 + */ +public interface StringValueResolver { + + String resolveStringValue(String strVal); +} diff --git a/src/test/java/org/springframework/test/bean/Car.java b/src/test/java/org/springframework/test/bean/Car.java index 6e68b15..c20e35f 100644 --- a/src/test/java/org/springframework/test/bean/Car.java +++ b/src/test/java/org/springframework/test/bean/Car.java @@ -1,5 +1,6 @@ package org.springframework.test.bean; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; /** @@ -9,6 +10,7 @@ @Component public class Car { + @Value("${brand}") private String brand; public String getBrand() { diff --git a/src/test/java/org/springframework/test/ioc/ValueAnnotationTest.java b/src/test/java/org/springframework/test/ioc/ValueAnnotationTest.java new file mode 100644 index 0000000..09e9211 --- /dev/null +++ b/src/test/java/org/springframework/test/ioc/ValueAnnotationTest.java @@ -0,0 +1,22 @@ +package org.springframework.test.ioc; + +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.test.bean.Car; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author derekyi + * @date 2020/12/27 + */ +public class ValueAnnotationTest { + + @Test + public void testValueAnnotation() throws Exception { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:value-annotation.xml"); + + Car car = applicationContext.getBean("car", Car.class); + assertThat(car.getBrand()).isEqualTo("lamborghini"); + } +} diff --git a/src/test/resources/value-annotation.xml b/src/test/resources/value-annotation.xml new file mode 100644 index 0000000..3f9f12d --- /dev/null +++ b/src/test/resources/value-annotation.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file From b59d2195fdc744a8633d4a6402609eca567f3020 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sun, 27 Dec 2020 15:00:14 +0800 Subject: [PATCH 07/81] update readme.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 76ae567..f59c68e 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,8 @@ If this project can help you, please give a **STAR, thank you!!!** #### Expanding * [PropertyPlaceholderConfigurer](#PropertyPlaceholderConfigurer) * [Package scan](#包扫描) -* [@Value annotation](#@Value annotation) -* [@Autowired annotation](#@Autowired annotation) +* [@Value annotation](#Value annotation) +* [@Autowired annotation](#Autowired annotation) * [Type conversion](#类型转换) #### Advanced From fb9077bd338fa8d0542b07f6d07a41fe2a66e98e Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sun, 27 Dec 2020 15:00:54 +0800 Subject: [PATCH 08/81] update readme.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f59c68e..119ac19 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,8 @@ If this project can help you, please give a **STAR, thank you!!!** #### Expanding * [PropertyPlaceholderConfigurer](#PropertyPlaceholderConfigurer) * [Package scan](#包扫描) -* [@Value annotation](#Value annotation) -* [@Autowired annotation](#Autowired annotation) +* [Value annotation](#Value annotation) +* [Autowired annotation](#Autowired annotation) * [Type conversion](#类型转换) #### Advanced From 0806b4e99f465bb2ca1eeb9581155b1ee91df8a2 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sun, 27 Dec 2020 15:02:12 +0800 Subject: [PATCH 09/81] update readme.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 119ac19..78a2795 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,8 @@ If this project can help you, please give a **STAR, thank you!!!** #### Expanding * [PropertyPlaceholderConfigurer](#PropertyPlaceholderConfigurer) * [Package scan](#包扫描) -* [Value annotation](#Value annotation) -* [Autowired annotation](#Autowired annotation) +* [Value annotation](#Value) +* [Autowired annotation](#Autowired) * [Type conversion](#类型转换) #### Advanced From 408a37ac32d7d60fe01f6660cfca39a97babddee Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sun, 27 Dec 2020 15:06:06 +0800 Subject: [PATCH 10/81] update changelog.md --- changelog.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index b01bedf..0510154 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,4 @@ - # IOC + # 基础篇:IoC ## 最简单的bean容器 > 分支:simple-bean-container @@ -661,6 +661,8 @@ org.springframework.test.common.event.CustomEventListener org.springframework.test.common.event.ContextClosedEventListener ``` +# 基础篇:AOP + ## 切点表达式 > 分支:pointcut-expression @@ -931,6 +933,8 @@ public class AutoProxyTest { } ``` +# 扩展篇 + ## PropertyPlaceholderConfigurer > 分支:property-placeholder-configurer From f90b1d2e5f2cb34903b44853c6c6611947a56e4b Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sun, 27 Dec 2020 16:17:20 +0800 Subject: [PATCH 11/81] =?UTF-8?q?@Autowired=E6=B3=A8=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 41 +++++++++++++++++++ .../beans/factory/BeanFactory.java | 2 + .../beans/factory/annotation/Autowired.java | 16 ++++++++ .../AutowiredAnnotationBeanPostProcessor.java | 18 +++++++- .../beans/factory/annotation/Qualifier.java | 17 ++++++++ .../support/DefaultListableBeanFactory.java | 20 +++++++-- .../support/AbstractApplicationContext.java | 4 ++ .../org/springframework/test/bean/Person.java | 4 ++ .../test/ioc/AutowiredAnnotationTest.java | 22 ++++++++++ src/test/resources/autowired-annotation.xml | 16 ++++++++ 10 files changed, 156 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/springframework/beans/factory/annotation/Autowired.java create mode 100644 src/main/java/org/springframework/beans/factory/annotation/Qualifier.java create mode 100644 src/test/java/org/springframework/test/ioc/AutowiredAnnotationTest.java create mode 100644 src/test/resources/autowired-annotation.xml diff --git a/changelog.md b/changelog.md index 0510154..517ff45 100644 --- a/changelog.md +++ b/changelog.md @@ -1075,11 +1075,52 @@ public class ValueAnnotationTest { ``` +## @Autowired注解 +> 分支:autowired-annotation +@Autowired注解的处理见AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues +测试: +``` +@Component +public class Car { +} +@Component +public class Person implements InitializingBean, DisposableBean { + @Autowired + private Car car; +} +``` +autowired-annotation.xml +``` + + + + + + +``` +``` +public class AutowiredAnnotationTest { + + @Test + public void testAutowiredAnnotation() throws Exception { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:autowired-annotation.xml"); + + Person person = applicationContext.getBean(Person.class); + assertThat(person.getCar()).isNotNull(); + } +} +``` diff --git a/src/main/java/org/springframework/beans/factory/BeanFactory.java b/src/main/java/org/springframework/beans/factory/BeanFactory.java index b3b1c45..2ebbc21 100644 --- a/src/main/java/org/springframework/beans/factory/BeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/BeanFactory.java @@ -29,4 +29,6 @@ public interface BeanFactory { * @throws BeansException */ T getBean(String name, Class requiredType) throws BeansException; + + T getBean(Class requiredType) throws BeansException; } diff --git a/src/main/java/org/springframework/beans/factory/annotation/Autowired.java b/src/main/java/org/springframework/beans/factory/annotation/Autowired.java new file mode 100644 index 0000000..42c9203 --- /dev/null +++ b/src/main/java/org/springframework/beans/factory/annotation/Autowired.java @@ -0,0 +1,16 @@ +package org.springframework.beans.factory.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author derekyi + * @date 2020/12/27 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD}) +public @interface Autowired { + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index e07be4a..c9c9ba3 100644 --- a/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -39,7 +39,23 @@ public PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, } } - //处理@Autowired注解(下一节实现) + //处理@Autowired注解 + for (Field field : fields) { + Autowired autowiredAnnotation = field.getAnnotation(Autowired.class); + if (autowiredAnnotation != null) { + Class fieldType = field.getType(); + String dependentBeanName = null; + Qualifier qualifierAnnotation = field.getAnnotation(Qualifier.class); + Object dependentBean = null; + if (qualifierAnnotation != null) { + dependentBeanName = qualifierAnnotation.value(); + dependentBean = beanFactory.getBean(dependentBeanName, fieldType); + } else { + dependentBean = beanFactory.getBean(fieldType); + } + BeanUtil.setFieldValue(bean, field.getName(), dependentBean); + } + } return pvs; } diff --git a/src/main/java/org/springframework/beans/factory/annotation/Qualifier.java b/src/main/java/org/springframework/beans/factory/annotation/Qualifier.java new file mode 100644 index 0000000..ca8c487 --- /dev/null +++ b/src/main/java/org/springframework/beans/factory/annotation/Qualifier.java @@ -0,0 +1,17 @@ +package org.springframework.beans.factory.annotation; + +import java.lang.annotation.*; + +/** + * @author derekyi + * @date 2020/12/27 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE}) +@Inherited +@Documented +public @interface Qualifier { + + String value() default ""; + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 7e9c1a0..55ea11b 100644 --- a/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -4,9 +4,7 @@ import org.springframework.beans.factory.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.BeanDefinition; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * @author derekyi @@ -50,6 +48,22 @@ public Map getBeansOfType(Class type) throws BeansException { return result; } + public T getBean(Class requiredType) throws BeansException { + List beanNames = new ArrayList<>(); + for (Map.Entry entry : beanDefinitionMap.entrySet()) { + Class beanClass = entry.getValue().getBeanClass(); + if (requiredType.isAssignableFrom(beanClass)) { + beanNames.add(entry.getKey()); + } + } + if (beanNames.size() == 1) { + return getBean(beanNames.get(0), requiredType); + } + + throw new BeansException(requiredType + "expected single bean but found " + + beanNames.size() + ": " + beanNames); + } + @Override public String[] getBeanDefinitionNames() { Set beanNames = beanDefinitionMap.keySet(); diff --git a/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index a971bd4..bd8d637 100644 --- a/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -128,6 +128,10 @@ public Map getBeansOfType(Class type) throws BeansException { return getBeanFactory().getBeansOfType(type); } + public T getBean(Class requiredType) throws BeansException { + return getBeanFactory().getBean(requiredType); + } + public Object getBean(String name) throws BeansException { return getBeanFactory().getBean(name); } diff --git a/src/test/java/org/springframework/test/bean/Person.java b/src/test/java/org/springframework/test/bean/Person.java index c6f8684..c83da16 100644 --- a/src/test/java/org/springframework/test/bean/Person.java +++ b/src/test/java/org/springframework/test/bean/Person.java @@ -2,17 +2,21 @@ import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; /** * @author derekyi * @date 2020/11/24 */ +@Component public class Person implements InitializingBean, DisposableBean { private String name; private int age; + @Autowired private Car car; public void customInitMethod() { diff --git a/src/test/java/org/springframework/test/ioc/AutowiredAnnotationTest.java b/src/test/java/org/springframework/test/ioc/AutowiredAnnotationTest.java new file mode 100644 index 0000000..f06de4d --- /dev/null +++ b/src/test/java/org/springframework/test/ioc/AutowiredAnnotationTest.java @@ -0,0 +1,22 @@ +package org.springframework.test.ioc; + +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.test.bean.Person; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author derekyi + * @date 2020/12/27 + */ +public class AutowiredAnnotationTest { + + @Test + public void testAutowiredAnnotation() throws Exception { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:autowired-annotation.xml"); + + Person person = applicationContext.getBean(Person.class); + assertThat(person.getCar()).isNotNull(); + } +} diff --git a/src/test/resources/autowired-annotation.xml b/src/test/resources/autowired-annotation.xml new file mode 100644 index 0000000..3f9f12d --- /dev/null +++ b/src/test/resources/autowired-annotation.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file From 9e2b1e451e9ea465aa48b0e0e5f026c4d5185a56 Mon Sep 17 00:00:00 2001 From: "feng.guo" Date: Wed, 30 Dec 2020 10:16:56 +0800 Subject: [PATCH 12/81] =?UTF-8?q?=E4=B8=BAAOP=E4=BB=A3=E7=90=86=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E6=B3=A8=E5=85=A5=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 51 +++++++++++++++++++ .../DefaultAdvisorAutoProxyCreator.java | 49 +++++++++--------- .../AbstractAutowireCapableBeanFactory.java | 41 ++------------- .../test/aop/AutoProxyTest.java | 4 ++ .../test/service/WorldServiceImpl.java | 19 +++++-- src/test/resources/auto-proxy.xml | 4 +- 6 files changed, 100 insertions(+), 68 deletions(-) diff --git a/changelog.md b/changelog.md index 517ff45..e1db2bb 100644 --- a/changelog.md +++ b/changelog.md @@ -1121,7 +1121,58 @@ public class AutowiredAnnotationTest { } } ``` +## 为代理bean填充属性 +> 分支: populate-proxy-bean-with-property-values +DefaultAdvisorAutoProxyCreator#postProcessBeforeInstantiation的内容挪到postProcessAfterInitialization方法中, postProcessBeforeInstantiation返回null + +用bean接受AbstractAutowireCapableBeanFactory#initializeBean的返回值 + +``` + + + + + + + + + + + + + + + + + + + + + + +``` +``` +public class AutoProxyTest { + + @Test + public void testAutoProxy() throws Exception { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:auto-proxy.xml"); + + //获取代理对象 + WorldService worldService = applicationContext.getBean("worldService", WorldService.class); + worldService.explode(); + WorldService worldService1 = applicationContext.getBean("worldService", WorldService.class); + assertThat(worldService == worldService1).isTrue(); + } +} +``` diff --git a/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java b/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java index 8668ac4..0aa1cd8 100644 --- a/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java +++ b/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java @@ -9,7 +9,6 @@ import org.springframework.beans.PropertyValues; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; import org.springframework.beans.factory.support.DefaultListableBeanFactory; @@ -25,32 +24,7 @@ public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPos @Override public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException { - //避免死循环 - if (isInfrastructureClass(beanClass)) { - return null; - } - - Collection advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values(); - try { - for (AspectJExpressionPointcutAdvisor advisor : advisors) { - ClassFilter classFilter = advisor.getPointcut().getClassFilter(); - if (classFilter.matches(beanClass)) { - AdvisedSupport advisedSupport = new AdvisedSupport(); - - BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); - Object bean = beanFactory.getInstantiationStrategy().instantiate(beanDefinition); - TargetSource targetSource = new TargetSource(bean); - advisedSupport.setTargetSource(targetSource); - advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice()); - advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher()); - //返回代理对象 - return new ProxyFactory(advisedSupport).getProxy(); - } - } - } catch (Exception ex) { - throw new BeansException("Error create proxy bean for: " + beanName, ex); - } return null; } @@ -72,6 +46,29 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) thro @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + //避免死循环 + if (isInfrastructureClass(bean.getClass())) { + return bean; + } + + Collection advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values(); + try { + for (AspectJExpressionPointcutAdvisor advisor : advisors) { + ClassFilter classFilter = advisor.getPointcut().getClassFilter(); + if (classFilter.matches(bean.getClass())) { + AdvisedSupport advisedSupport = new AdvisedSupport(); + TargetSource targetSource = new TargetSource(bean); + advisedSupport.setTargetSource(targetSource); + advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice()); + advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher()); + + //返回代理对象 + return new ProxyFactory(advisedSupport).getProxy(); + } + } + } catch (Exception ex) { + throw new BeansException("Error create proxy bean for: " + beanName, ex); + } return bean; } diff --git a/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 619abcf..8338522 100644 --- a/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -24,43 +24,10 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac @Override protected Object createBean(String beanName, BeanDefinition beanDefinition) throws BeansException { - //如果bean需要代理,则直接返回代理对象 - Object bean = resolveBeforeInstantiation(beanName, beanDefinition); - if (bean != null) { - return bean; - } return doCreateBean(beanName, beanDefinition); } - /** - * 执行InstantiationAwareBeanPostProcessor的方法,如果bean需要代理,直接返回代理对象 - * - * @param beanName - * @param beanDefinition - * @return - */ - protected Object resolveBeforeInstantiation(String beanName, BeanDefinition beanDefinition) { - Object bean = applyBeanPostProcessorsBeforeInstantiation(beanDefinition.getBeanClass(), beanName); - if (bean != null) { - bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); - } - return bean; - } - - protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass, String beanName) { - for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) { - if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) { - Object result = ((InstantiationAwareBeanPostProcessor) beanPostProcessor).postProcessBeforeInstantiation(beanClass, beanName); - if (result != null) { - return result; - } - } - } - - return null; - } - protected Object doCreateBean(String beanName, BeanDefinition beanDefinition) { Object bean = null; try { @@ -70,7 +37,7 @@ protected Object doCreateBean(String beanName, BeanDefinition beanDefinition) { //为bean填充属性 applyPropertyValues(beanName, bean, beanDefinition); //执行bean的初始化方法和BeanPostProcessor的前置和后置处理方法 - initializeBean(beanName, bean, beanDefinition); + bean = initializeBean(beanName, bean, beanDefinition); } catch (Exception e) { throw new BeansException("Instantiation of bean failed", e); } @@ -170,7 +137,7 @@ protected Object initializeBean(String beanName, Object bean, BeanDefinition bea } //执行BeanPostProcessor的后置处理 - wrappedBean = applyBeanPostProcessorsAfterInitialization(bean, beanName); + wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); return wrappedBean; } @@ -181,7 +148,7 @@ public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, S for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessBeforeInitialization(result, beanName); if (current == null) { - return result; + continue; } result = current; } @@ -196,7 +163,7 @@ public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, St for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { - return result; + continue; } result = current; } diff --git a/src/test/java/org/springframework/test/aop/AutoProxyTest.java b/src/test/java/org/springframework/test/aop/AutoProxyTest.java index 1202ad3..ef0128f 100644 --- a/src/test/java/org/springframework/test/aop/AutoProxyTest.java +++ b/src/test/java/org/springframework/test/aop/AutoProxyTest.java @@ -4,6 +4,8 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.test.service.WorldService; +import static org.assertj.core.api.Assertions.assertThat; + /** * @author derekyi * @date 2020/12/6 @@ -17,5 +19,7 @@ public void testAutoProxy() throws Exception { //获取代理对象 WorldService worldService = applicationContext.getBean("worldService", WorldService.class); worldService.explode(); + WorldService worldService1 = applicationContext.getBean("worldService", WorldService.class); + assertThat(worldService == worldService1).isTrue(); } } diff --git a/src/test/java/org/springframework/test/service/WorldServiceImpl.java b/src/test/java/org/springframework/test/service/WorldServiceImpl.java index dc15e0c..aafcbdd 100644 --- a/src/test/java/org/springframework/test/service/WorldServiceImpl.java +++ b/src/test/java/org/springframework/test/service/WorldServiceImpl.java @@ -6,8 +6,19 @@ */ public class WorldServiceImpl implements WorldService { - @Override - public void explode() { - System.out.println("The Earth is going to explode"); - } + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public void explode() { + System.out.println("The Earth is going to explode"); + System.out.println("name: " + getName()); + } } diff --git a/src/test/resources/auto-proxy.xml b/src/test/resources/auto-proxy.xml index ffa3baa..20c0f01 100644 --- a/src/test/resources/auto-proxy.xml +++ b/src/test/resources/auto-proxy.xml @@ -7,7 +7,9 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> - + + + From e12a2101a65072ca3d7cdaae5efe71adf66c77d0 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Wed, 30 Dec 2020 20:32:25 +0800 Subject: [PATCH 13/81] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E4=B8=BA=E4=BB=A3=E7=90=86bean=E8=AE=BE=E7=BD=AE=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README_CN.md | 3 + assets/application-context-life-cycle.drawio | 1 + assets/auto-proxy.drawio | 1 + assets/aware-interface.drawio | 1 + assets/init-and-destroy-method.drawio | 1 + ...ate-proxy-bean-with-property-values.drawio | 1 + ...pulate-proxy-bean-with-property-values.png | Bin 0 -> 168894 bytes assets/prototype-bean.drawio | 1 + changelog.md | 36 ++++++++-- .../DefaultAdvisorAutoProxyCreator.java | 48 +++++++------ .../AutowiredAnnotationBeanPostProcessor.java | 5 ++ .../InstantiationAwareBeanPostProcessor.java | 10 +++ .../AbstractAutowireCapableBeanFactory.java | 66 ++++++++++++++++-- .../test/aop/AutoProxyTest.java | 12 +++- .../test/service/WorldService.java | 2 + .../test/service/WorldServiceImpl.java | 24 +++---- src/test/resources/auto-proxy.xml | 4 +- ...pulate-proxy-bean-with-property-values.xml | 28 ++++++++ 18 files changed, 194 insertions(+), 50 deletions(-) create mode 100644 assets/application-context-life-cycle.drawio create mode 100644 assets/auto-proxy.drawio create mode 100644 assets/aware-interface.drawio create mode 100644 assets/init-and-destroy-method.drawio create mode 100644 assets/populate-proxy-bean-with-property-values.drawio create mode 100644 assets/populate-proxy-bean-with-property-values.png create mode 100644 assets/prototype-bean.drawio create mode 100644 src/test/resources/populate-proxy-bean-with-property-values.xml diff --git a/README_CN.md b/README_CN.md index 143079e..120107b 100644 --- a/README_CN.md +++ b/README_CN.md @@ -48,6 +48,9 @@ #### 高级篇 * [解决循环依赖问题](#解决循环依赖问题) +#### bug fix +* [没有为代理bean设置属性(discovered and fixed by @kerwin89)](#没有为代理bean设置属性(discovered and fixed by kerwin89)) + ## 使用方法 每个功能点对应一个分支,切换到功能点对应的分支了解新增的功能,增量改动点在[changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md)文件中描述。 diff --git a/assets/application-context-life-cycle.drawio b/assets/application-context-life-cycle.drawio new file mode 100644 index 0000000..b6f4432 --- /dev/null +++ b/assets/application-context-life-cycle.drawio @@ -0,0 +1 @@ +7ZnbcpswEIafRpfpmJMQl2CTtDPJTDqetkluOopRgBYjV8ixnaevJCQDPiRup3Zd11dGv1Yg7e4nwRo4/fH8iuFJdkMTUgC7l8yBMwC2bfegLX6ksqgVy0JaSVmeaK0RhvkL0WJPq9M8IVXHkFNa8HzSFUe0LMmIdzTMGJ11zZ5o0X3qBKdkTRiOcLGufskTntUqsv1Gf0/yNDNPtmBQ94yxMdYrqTKc0FlLcmLg9BmlvL4az/ukkN4zfqnHXW7pXU6MkZLvMuDl68tt+uFj+HmYk/wxCL2H5NMF0nPjC7Ngkoj16yZlPKMpLXERN2rE6LRMiLxrT7Qam2tKJ0K0hPiNcL7QwcRTToWU8XGhe8k853dy+DtPt+5bPYO5vrNqLEyj5GzRGiSb9+2+ZphqmXHrXtKOq+iUjcgrrjHZhllK+Ct2Op+l31oP0DG4InRMxHyEASMF5vlzN6+wTs90addEUFzoIP5CQPV9n3Ex1U8CNizE/KMnKjzQDjX8MaWm46JSwQqFgYUm86ZTXKX6V92lmuDSaHc31yCGIIAA+SB2QRSBCBpDMfu2bUuuJ2LklfRrkkvmwyzLORlOsArUTGwx3USqOKPfSZ8WlKnRDrIfHQjlmvKiaOmJR1Di6rWa7N5AkPEeYZzMX42m2eGQptvsb4b2WbNZWFBrWWujcHt7SoDgTPQbpL5JtHNURNt7IjoiuByQp7zMeU7LU+XT7fJp99b5XNochE/zYnMGdCt4bwIKjwpQZ4+AXuIRp2xxSyt+y+iIVJVARh61lyCM1eErroP/AuXl0WpQRusou+igKNtnlLf5xt0RZf+oUHb3iPIKwx5AAUADEPsgGiiYPRC6ALlSCXoAwRPleHkEa45dd51jxz4ox96Z422+8Xbk2HKPCmTvkCBLWuMzyHDDu/WBQbbWfHqkIP8+kHBHII+LR7gnHh8Fjwq4GASxejGOAYrU6dqXhakTJW+l6uTAv111spx/hbzDH6H+jsR6R0Wsvydi5UerePMNfRAjEFqC05phcVaG6tAU6CIQDBTVfgfmutYcBfIickDgnSreK0Urb0NR+cBFq319GKkteyDLGTIBXBCiE42ps1K9gBuqF3+oeCGazb+Kqq/156wT/wQ= \ No newline at end of file diff --git a/assets/auto-proxy.drawio b/assets/auto-proxy.drawio new file mode 100644 index 0000000..33bac5a --- /dev/null +++ b/assets/auto-proxy.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/assets/aware-interface.drawio b/assets/aware-interface.drawio new file mode 100644 index 0000000..2d088c9 --- /dev/null +++ b/assets/aware-interface.drawio @@ -0,0 +1 @@ +7Ztfk6I4EMA/TR69kv/wiMrsbtXOlnvW3e3ey1WUDOYWCQdx1Pn0l2AQEERnSpASnySdAKE7v3SnE4EyXm0/RTBcPhMX+UAeulugTIAsy6ZpsR8u2e0lkmTKe4kXYVfIMsEMvyEhHArpGrsoLjSkhPgUh0XhggQBWtCCDEYR2RSbvRC/+NYQeqgkmC2gX5b+hV263EtN2cjknxH2lumbJV188QqmjcWXxEvokk1OpDhAGUeE0P3VajtGPtdeqpf9fU8nag8di1BAL7nh7Z+3qfflu/3nDCM8t2ztb/ePgSn6RnfpByOXfb8okoguiUcC6DuZdBSRdeAi/tQhK2VtvhISMqHEhP8iSnfCmHBNCRMt6coXtWiL6Q9++2+aKP3M1Uy24slJYZcWAhrtcjfx4s98XXZbUkrvK2tJKC4m62iBalSTjjYYeYjWtBPjmest9wJhg0+IrBDrD2sQIR9S/FocV1AMT+/QLrMguxBGfIdBxXNfob8WbwKy7rP+j14I00De1Pp/a5JWDOLEWDZrIJnhNqtkV574TZ4ShzBIZT+evwJHB5YOTAM4KhiNwEhPG7Le59vmxPuOpOKj4ZcNLj4eNktM0SyEiaE2bIopDqSYRuQXGhOfRMndiinPFV3n34R9Pyd3NWS6qvjWdHRXEJRqD0UUbWuteZjhBN1iflNFcZPNFZIuZMvcPJG2u7r9rQfQZ0A9C7TSKaDlhoAeIRhM0AsOMMUkuFc81SKeklzm89CmFT7TuOYB6EnwzgKqdwpQpUFAn+CCkmg3JTGdRmSB4pghwz3tE7CdxPeya6sXKB/QFSjLwzLKqtkqyvID5VO6US9E2egUymqDKB8xrAG2NDYnwDHAaJLArAFbBabKJdYQmPqdcnzgNo2YK0JmRW6VY+3B8SndaBdyLKmdAllrE2ROq/MAWa9wyI2B7FMyCH69ehB6u+3Udp/jcJYule6d4yPGPga2Xga7Uqc3xVhvCOM5wzjh1AGWk8TTDjBHiVMe83TWnQJ7lKs6lG+WrJKUfhD7EUCNCz2vdivHq8fuHBuf4+/Ot/FU+ab//jbwBmWDXs9cOWNlpjtjLqlgrMx2bZirUkFSp+Iko6EJlqcm2PrGNoBjAlti0+p+ymURkZ2ERmymNYE1SSZhozD37jcURha/GCnA0u51Nj5KTWptpiYrh2b9XmBAgh7NvlJFAqNSad1KYEhNZTCSIGnC846cYRXY5p1iqRylGfWKIKmpLGPlAFP7SWWd8zwLpdypmKipLfkvPN8PffyGA48nJhgW8IWiaBqRkI13jOIZoneK6aCURqzgtLHsQ6WZ67OI1wZVAh+Ihofg+tFwHYDdTf/X9boB52nyEzK2neYc7MSdWjxEfk8kzDf4BuxTl8TtC9aafmusJamk1EYdcFdWuXXR7lkHbHWK65utci0eKjPHxNrZT8CUeru4NSowbndxe2azvl9xtHUhxulhpY5wbDXE8QTHIYnh3EcijHYRR2h3p3CWnKxRkXpq2cnWbwT0K3hOqetu7qm22+2Gz+ddrIC5Z4GzefPd+J7s7b13N/70LvsF62W1JcDrOtnkQVd7w5THOIkRzUm7y2wJ0ArzX3yARmlzrVtp4dsehGtt5XsVZC894XpTZJvaDrLD0McLyI+bj1l7tKU5csuVdwqwfsFRVvVKq1xWzP4qmtTl/nGrOP8D \ No newline at end of file diff --git a/assets/init-and-destroy-method.drawio b/assets/init-and-destroy-method.drawio new file mode 100644 index 0000000..dde0db2 --- /dev/null +++ b/assets/init-and-destroy-method.drawio @@ -0,0 +1 @@ +7Ztbs9o2EIB/jR7p4Lv8aINPkpkkQ8q0TfrSMVjHVmssaotw+fWVjIRtbC5JseEAT1gryUhafburFQBtMFu9S/159IkEKAZqP1gBbQhUVYXQZh9cst5KFAWqW0mY4kDICsEYb5AQ9oV0gQOUVRpSQmKK51XhlCQJmtKKzE9Tsqw2eyVx9VvnfohqgvHUj+vSP3BAo60UqlYhf49wGMlvVkwx45kvG4uZZJEfkGVJpHlAG6SE0O3TbDVAMV89uS7bfi8HancDS1FCz+mw+WszCj98cX4fY4QntmP8GfzWg2JsdC0njAI2f1EkKY1ISBI/9gqpm5JFEiD+1j4rFW0+EjJnQoUJ/0aUroUy/QUlTBTRWSxq0QrTr7z7L4YofSvVDFfizXlhLQsJTdelTrz4rVxXdMtLsl99lcTCZWSRTtGRpZG7zU9DRI+0E/uZr1vpC4QO3iEyQ2w8rEGKYp/i79V95YvtGe7aFRpkD0KJP6BQ8d7vfrwQ3wRUM2bjd18JW4Gyqs1/F0RW9LJcWQ5roMD5qqhkT6H4zN+Szf1Eyr5++gg8E9gmgBbwdOC6wDVlQzb6ctuSeDsQKd7bfsXm4vthGWGKxnM/V9SSmZjqRspoSv5BAxKTNO+tQXWimSafE47jkjwwEAx0MVe5uxsIkquHUopWR7W5s3CCbmnfJO3LwlgoppBFJUOh91vaAPaT6BOkniRauymi1ZaIdpGfDNErTjDFJLlXPvUqn2q/zueuTSd8ysDmCehB8E4Cat4UoFqLgL74U0rS9YhkdJSSKcoyhgx3tS/A8XLny57th0B551olyrCOsg47RVl9onxobfQzUbZuCmW9RZT3GDYAOxvDIfAs4A5zmA3g6ADqXGL3ATTvlOOdCxYc63qdY03tlGPjyfGhtTHO5FjRbwpko0uQOa3eE2SzIbbuGGSltqY3CvLPA2meCeRt8Wi2xOOE8ZgD5wHbywNjD0A3964Dnpi6U/L2sk6aee2sk6K9FfK6d6HWmcQa1yLWzIIJtt5nX7zPg5H22fx10wt7dYVeTl0lZRWqO6EupaKsQndH1bW3lhfTX+OKXdXAWi0ZWJ5jYAcVxwIeBI7CzOrW5LLQxsljHGZpIbCHuRG2KrZ3ezXg2vzB1YBt3Ks13ssxGg13AK3lGBt34vFbvYQkb9T6XgRnpSE10biK1jV5VtpKReRB0pAnEDnDOnDgnWKp7eULzYZ8YVvpwsb9pD+pPOE8z4BSvWZM1Nbl+geeuPdjvMFJyDMMDAv/laJ0lJI52+8YZWNEbxfTGpMNW+Agpr1aPrCB09bSCI1qPp4OvDSoCviJaLgPOoiGDwN4bmL/WqCqrTlPyH/r4jgy5+Dk7tTmIfKPRML8pq7HphqR4FGwNhpyFN1ifSI7eHEHfLOn3MPR7hkO2L4m11c75do8VGaOibVzXgBU3tLh9n9xvH+4tRow7vZwe+LW/cHjaPtMjJV+s9q74dhuieMhzuYk8ycxEmF0gDhC6zuFs+ZkrYbUU8dO9vhFwIMHz/LHfzeTezo6ym7D59MuVsD8YIEzbPFanRWLP6DkdaX/8Wjefw== \ No newline at end of file diff --git a/assets/populate-proxy-bean-with-property-values.drawio b/assets/populate-proxy-bean-with-property-values.drawio new file mode 100644 index 0000000..7f3e4e9 --- /dev/null +++ b/assets/populate-proxy-bean-with-property-values.drawio @@ -0,0 +1 @@ +7Vxbd5s4EP41evQeczU8go3b7ml63M3Z3l72YKNgUoIoKLGdX7+SEDEX+ZIUMLX9ZBgkLGbmmxmNRgLK+GH9LnHj5Q3yYAjkobcGygTIsmwYJvmhlA2nKJqUUfwk8DJagXAbPENOHHLqY+DBtNQQIxTiIC4TFyiK4AKXaG6SoFW52R0Ky/8auz6sEW4Xblinfg08vMyohjza0t/DwF/m/yzp/Isf3Lwx/5J06XpoVSApDlDGCUI4u3pYj2FIuZfzJes33fH0ZWAJjPAxHZ7/e575Hz5bX24DGMxNS/vh/Tsw+NjwJv9g6JHv57cowUvko8gNnS3VTtBj5EH61iG527b5iFBMiBIh3kOMN1yY7iNGhLTEDyF/CtcB/ka7/6Xxu++FJ5M1fzO72eQ3EU42hU709nvx2bYbu8v71bnEGZeix2QB97Am1zY38SHe007O2lG+Ff6Ay+AdRA+QjIc0SGDo4uCprFcuV0//pd1WguSCC/EVAuXvfXLDR/5PQNZDMn77DhEOFEWt/3pE+YNByoRlkQaSEa+3D8mVz3/ZW9LYjXLat5uPwNGBqQNjBBwV2DawdUoxDEJchG6aCp7zF5GvK76rQM4GmpMr6rlVPqovq2WA4W3sMkGuiAkqK1qKE/QTjlGIEtZbMeS5ouv0m4MwLNA9DRqeynmRa78AYTl3YYLheq+0cwuocvRz+zfI71dbY/LSZlkwJDmtcQUxr4jvDMm86wwFDHpcJxS9rBOSXJF1ZnJ4r4q4X4bxdg2QWzIRNnSjCbwLogAHKLoQQHeJZ5jcG8t/br9NUfzjyfmyNm1jnkvziue6oit1Dy5kodQrD660CM+pu8Ao2cxQimcJWsA0JYChnnkKLIf5anJtXgSQX6xu1QoXkKwaXXpm6QrlnbxRjwzGR72CstoilCsY1gCZahsT4IyAPWFg1oClAkOlFHMIDP1McSwPyzjWjTqOFblTHGtXHO/ijXYkjiW1V0DWugQyRatzBbIhCK1bA/JqJt3jX+poejOe/60s081z+HmgXwaOKxh7G7D1OrCFPFVOCWO9JRjPCYwZTh1gOiyedoBhM6c8pumvMwWsUQasLPC8kt6p51UuA7FvAejoSM+rncrx6qk3D0bv08/Op/FM+aT/8zzwB3WBNieugrC2ojsgLqkkrK3suhCXkEH9yl2MWjKwbFHBBNYIOAawJGJWM5NLIiKLhUYaW3OYMCM8KtnebAHCNumFrQBTO1drXElMjvQOM5PmwJHhwHknaf4iSZZffX0lDfIl3Ks13j2/KeJbzMRe4VtqK6PBgqYJzUNSTKvAMs4Upkol7WgK0o5tZR2FDkTdC9IIReeJyn3O9KDTlXsVI7W15P+B5v/dMHgOIp8mKggs3DsMk1mCYqLvAUxvIT5TmA6q2YiRAKetZSOEYt6fVWwaqBJ4Q3Q8BM1Hx/sA2N/lgH2jbsF5GrTCxrLyHITF3KlJQ+bXRMZ0wW9APnWJvEuBtTE8NawlqcbUVh1wX2a9QmYcO+s1T4VrYYx+LaJ8jUCFLOyXoT5ZGsOkcx8S/pN21hQY0sVmLyRROUZr6QuxYZavM6OqwT2cjhz2CshmS0CeBGmMUnceQj4x8iDF0OZM0VkLm6Rhl1UWYk3bv9ZzWfOhHHYH8dkvP5sPu9sZ0WEny9F8YXMhSRIsGbQG6hCjQfTzyXddf7OeWd5NGt8KlvsuMZIWsubY7UgnK5wSlnv8OWtAQrb9hqha3zbyUujY/LaRfQrYZl26tSLcJjYvhbhAPVP7Wytc7TIVJZTwaetW+5KYErLm2Przk1nffaNuHLJWHIfBwqW7Q8akPVzjAnLrD88UwPoRledqQykLNL+nu9vlYejOYZjLUj6UmZQLH8AVpv+euNXYal+RRW9mT+SWC/wVWeiTbux90/S4Det9rHy7yUKzrlaSuJtCg5hGaOnuQE/TqskWtXKyw6EOcrkDucjG8NZ4cB9TG3cu6QLFEDhTYI+BOT3adyzRw/wxPew3dnmH3f6kAVdRrfqRhoJgT1T2U43xG1uzamtHSxpEfgjx8dtGWYBQklACyZ/Q3CrHPbU62d+yx24Y+BG5XhBxQCIrm4qBBByhxR88BJ7HrBsHGvkbzQbapCJ8niEsy12YNmxCAap7/odmXQFMgQJUzwZoTAHa2gsRJwgjvCEQvipAQQHUmgUQLHB1qgCiRc5qABGGQZzCw0bVTePs8KW7YE251hMrK3doZYUnC1zzmTtZc2w+U+8o5j5OoO3VWqbYjXDAps1sMl3fGior8fbehncogaVuZzrNrlQGiDCtt1UYIMxpC+zkOabJXpsKF+f/6yjfjauTLVK0BGq2+KgDQ2YrjA4wx2zNcUL3c293dRt014PJKKZNmmUlQWz6Y4zZsuWYrV9qdLWSXGdLm3a2H3xKX0dfMaJPS72M/DgImxUcZResDpTuTh3mbQRFSf01JDWrIdDKnYbEGEoHLUlT+brjFG90GeFBI5ZEcJiTsJ16SkvS1tlNrw4PLLpR4w+JDn4L1JXoQBGUMHQbHVxIMXAjmBasqgnbaafEdFuLaK/GNN93tflCR5JeBp5VwSEw3eL5Qs5LbQTPgtOdWimQ+S2BtpX5ZuGzQ4Pr4tFNtsSOhNFpxE1C7PMEbfUkGE2Q22zqJBhyuz0zPVvL2h49rzj/Aw== \ No newline at end of file diff --git a/assets/populate-proxy-bean-with-property-values.png b/assets/populate-proxy-bean-with-property-values.png new file mode 100644 index 0000000000000000000000000000000000000000..e27eac5b366374d5dc8ce213a7a9588d2f9c7395 GIT binary patch literal 168894 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%0A?h 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 literal 0 HcmV?d00001 diff --git a/assets/prototype-bean.drawio b/assets/prototype-bean.drawio new file mode 100644 index 0000000..d93e34a --- /dev/null +++ b/assets/prototype-bean.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/changelog.md b/changelog.md index e1db2bb..6a2852e 100644 --- a/changelog.md +++ b/changelog.md @@ -1121,13 +1121,23 @@ public class AutowiredAnnotationTest { } } ``` -## 为代理bean填充属性 +## bug fix:没有为代理bean设置属性(discovered and fixed by @kerwin89) > 分支: populate-proxy-bean-with-property-values -DefaultAdvisorAutoProxyCreator#postProcessBeforeInstantiation的内容挪到postProcessAfterInitialization方法中, postProcessBeforeInstantiation返回null +问题现象:没有为代理bean设置属性 -用bean接受AbstractAutowireCapableBeanFactory#initializeBean的返回值 +问题原因:织入逻辑在InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation中执行,而该方法如果返回非null,会导致"短路",不会执行后面的设置属性逻辑。因此如果该方法中返回代理bean后,不会为代理bean设置属性。 +修复方案:跟spring保持一致,将织入逻辑迁移到BeanPostProcessor#postProcessAfterInitialization,即将DefaultAdvisorAutoProxyCreator#postProcessBeforeInstantiation的内容迁移到DefaultAdvisorAutoProxyCreator#postProcessAfterInitialization中。 + +顺便完善spring的扩展机制,为InstantiationAwareBeanPostProcessor增加postProcessAfterInstantiation方法,该方法在bean实例化之后设置属性之前执行。 + +至此,bean的生命周期比较完整了,如下: + +![](./assets/populate-proxy-bean-with-property-values.png) + +测试: +populate-proxy-bean-with-property-values.xml ``` ``` ``` +public class WorldServiceImpl implements WorldService { + + private String name; + + @Override + public void explode() { + System.out.println("The " + name + " is going to explode"); + } + + //setter and getter +} +``` +``` public class AutoProxyTest { @Test - public void testAutoProxy() throws Exception { - ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:auto-proxy.xml"); + public void testPopulateProxyBeanWithPropertyValues() throws Exception { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:populate-proxy-bean-with-property-values.xml"); //获取代理对象 WorldService worldService = applicationContext.getBean("worldService", WorldService.class); worldService.explode(); - WorldService worldService1 = applicationContext.getBean("worldService", WorldService.class); - assertThat(worldService == worldService1).isTrue(); + assertThat(worldService.getName()).isEqualTo("earth"); } } ``` diff --git a/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java b/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java index 0aa1cd8..18d9f48 100644 --- a/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java +++ b/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java @@ -22,28 +22,6 @@ public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPos private DefaultListableBeanFactory beanFactory; - @Override - public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException { - - return null; - } - - private boolean isInfrastructureClass(Class beanClass) { - return Advice.class.isAssignableFrom(beanClass) - || Pointcut.class.isAssignableFrom(beanClass) - || Advisor.class.isAssignableFrom(beanClass); - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = (DefaultListableBeanFactory) beanFactory; - } - - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - return bean; - } - @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { //避免死循环 @@ -72,6 +50,32 @@ public Object postProcessAfterInitialization(Object bean, String beanName) throw return bean; } + private boolean isInfrastructureClass(Class beanClass) { + return Advice.class.isAssignableFrom(beanClass) + || Pointcut.class.isAssignableFrom(beanClass) + || Advisor.class.isAssignableFrom(beanClass); + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = (DefaultListableBeanFactory) beanFactory; + } + + @Override + public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException { + return null; + } + + @Override + public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { + return true; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + @Override public PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeansException { return pvs; diff --git a/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index c9c9ba3..e2cd262 100644 --- a/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -65,6 +65,11 @@ public Object postProcessBeforeInstantiation(Class beanClass, String beanName return null; } + @Override + public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { + return true; + } + @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return null; diff --git a/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java b/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java index 4d66940..db0f10e 100644 --- a/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java +++ b/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java @@ -19,6 +19,16 @@ public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor { */ Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException; + /** + * bean实例化之后,设置属性之前执行 + * + * @param bean + * @param beanName + * @return + * @throws BeansException + */ + boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException; + /** * bean实例化之后,设置属性之前执行 * diff --git a/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 8338522..6b9bbb7 100644 --- a/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -24,16 +24,54 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac @Override protected Object createBean(String beanName, BeanDefinition beanDefinition) throws BeansException { + //如果bean需要代理,则直接返回代理对象 + Object bean = resolveBeforeInstantiation(beanName, beanDefinition); + if (bean != null) { + return bean; + } return doCreateBean(beanName, beanDefinition); } + /** + * 执行InstantiationAwareBeanPostProcessor的方法,如果bean需要代理,直接返回代理对象 + * + * @param beanName + * @param beanDefinition + * @return + */ + protected Object resolveBeforeInstantiation(String beanName, BeanDefinition beanDefinition) { + Object bean = applyBeanPostProcessorsBeforeInstantiation(beanDefinition.getBeanClass(), beanName); + if (bean != null) { + bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); + } + return bean; + } + + protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass, String beanName) { + for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) { + if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) { + Object result = ((InstantiationAwareBeanPostProcessor) beanPostProcessor).postProcessBeforeInstantiation(beanClass, beanName); + if (result != null) { + return result; + } + } + } + + return null; + } + protected Object doCreateBean(String beanName, BeanDefinition beanDefinition) { Object bean = null; try { bean = createBeanInstance(beanDefinition); + //实例化bean之后执行 + boolean continueWithPropertyPopulation = applyBeanPostProcessorsAfterInstantiation(beanName, bean); + if (!continueWithPropertyPopulation) { + return bean; + } //在设置bean属性之前,允许BeanPostProcessor修改属性值 - applyBeanPostprocessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition); + applyBeanPostProcessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition); //为bean填充属性 applyPropertyValues(beanName, bean, beanDefinition); //执行bean的初始化方法和BeanPostProcessor的前置和后置处理方法 @@ -51,6 +89,26 @@ protected Object doCreateBean(String beanName, BeanDefinition beanDefinition) { return bean; } + /** + * bean实例化后执行,如果返回false,不执行后续设置属性的逻辑 + * + * @param beanName + * @param bean + * @return + */ + private boolean applyBeanPostProcessorsAfterInstantiation(String beanName, Object bean) { + boolean continueWithPropertyPopulation = true; + for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) { + if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) { + if (!((InstantiationAwareBeanPostProcessor) beanPostProcessor).postProcessAfterInstantiation(bean, beanName)) { + continueWithPropertyPopulation = false; + break; + } + } + } + return continueWithPropertyPopulation; + } + /** * 在设置bean属性之前,允许BeanPostProcessor修改属性值 * @@ -58,7 +116,7 @@ protected Object doCreateBean(String beanName, BeanDefinition beanDefinition) { * @param bean * @param beanDefinition */ - protected void applyBeanPostprocessorsBeforeApplyingPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) { + protected void applyBeanPostProcessorsBeforeApplyingPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) { for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) { if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) { PropertyValues pvs = ((InstantiationAwareBeanPostProcessor) beanPostProcessor).postProcessPropertyValues(beanDefinition.getPropertyValues(), bean, beanName); @@ -148,7 +206,7 @@ public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, S for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessBeforeInitialization(result, beanName); if (current == null) { - continue; + return result; } result = current; } @@ -163,7 +221,7 @@ public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, St for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { - continue; + return result; } result = current; } diff --git a/src/test/java/org/springframework/test/aop/AutoProxyTest.java b/src/test/java/org/springframework/test/aop/AutoProxyTest.java index ef0128f..6d8f93a 100644 --- a/src/test/java/org/springframework/test/aop/AutoProxyTest.java +++ b/src/test/java/org/springframework/test/aop/AutoProxyTest.java @@ -19,7 +19,15 @@ public void testAutoProxy() throws Exception { //获取代理对象 WorldService worldService = applicationContext.getBean("worldService", WorldService.class); worldService.explode(); - WorldService worldService1 = applicationContext.getBean("worldService", WorldService.class); - assertThat(worldService == worldService1).isTrue(); + } + + @Test + public void testPopulateProxyBeanWithPropertyValues() throws Exception { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:populate-proxy-bean-with-property-values.xml"); + + //获取代理对象 + WorldService worldService = applicationContext.getBean("worldService", WorldService.class); + worldService.explode(); + assertThat(worldService.getName()).isEqualTo("earth"); } } diff --git a/src/test/java/org/springframework/test/service/WorldService.java b/src/test/java/org/springframework/test/service/WorldService.java index fe3646f..6ee4e0f 100644 --- a/src/test/java/org/springframework/test/service/WorldService.java +++ b/src/test/java/org/springframework/test/service/WorldService.java @@ -7,4 +7,6 @@ public interface WorldService { void explode(); + + String getName(); } diff --git a/src/test/java/org/springframework/test/service/WorldServiceImpl.java b/src/test/java/org/springframework/test/service/WorldServiceImpl.java index aafcbdd..5e81d8a 100644 --- a/src/test/java/org/springframework/test/service/WorldServiceImpl.java +++ b/src/test/java/org/springframework/test/service/WorldServiceImpl.java @@ -6,19 +6,19 @@ */ public class WorldServiceImpl implements WorldService { - private String name; + private String name; - public String getName() { - return name; - } + @Override + public void explode() { + System.out.println("The " + name + " is going to explode"); + } - public void setName(String name) { - this.name = name; - } + @Override + public String getName() { + return name; + } - @Override - public void explode() { - System.out.println("The Earth is going to explode"); - System.out.println("name: " + getName()); - } + public void setName(String name) { + this.name = name; + } } diff --git a/src/test/resources/auto-proxy.xml b/src/test/resources/auto-proxy.xml index 20c0f01..ffa3baa 100644 --- a/src/test/resources/auto-proxy.xml +++ b/src/test/resources/auto-proxy.xml @@ -7,9 +7,7 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> - - - + diff --git a/src/test/resources/populate-proxy-bean-with-property-values.xml b/src/test/resources/populate-proxy-bean-with-property-values.xml new file mode 100644 index 0000000..20c0f01 --- /dev/null +++ b/src/test/resources/populate-proxy-bean-with-property-values.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 44f6498801abbfb57df3404f2f385841389a3bd4 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Wed, 30 Dec 2020 20:34:23 +0800 Subject: [PATCH 14/81] update readme.md --- README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CN.md b/README_CN.md index 120107b..e2aa218 100644 --- a/README_CN.md +++ b/README_CN.md @@ -49,7 +49,7 @@ * [解决循环依赖问题](#解决循环依赖问题) #### bug fix -* [没有为代理bean设置属性(discovered and fixed by @kerwin89)](#没有为代理bean设置属性(discovered and fixed by kerwin89)) +* [没有为代理bean设置属性(discovered and fixed by kerwin89)](#没有为代理bean设置属性(discovered and fixed by kerwin89)) ## 使用方法 每个功能点对应一个分支,切换到功能点对应的分支了解新增的功能,增量改动点在[changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md)文件中描述。 From ff04402165573e0130632a484d250b1af7f111c9 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Wed, 30 Dec 2020 20:36:36 +0800 Subject: [PATCH 15/81] update readme.md --- README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CN.md b/README_CN.md index e2aa218..a73096b 100644 --- a/README_CN.md +++ b/README_CN.md @@ -49,7 +49,7 @@ * [解决循环依赖问题](#解决循环依赖问题) #### bug fix -* [没有为代理bean设置属性(discovered and fixed by kerwin89)](#没有为代理bean设置属性(discovered and fixed by kerwin89)) +* [没有为代理bean设置属性(discovered and fixed by kerwin89)](#没有为代理bean设置属性) ## 使用方法 每个功能点对应一个分支,切换到功能点对应的分支了解新增的功能,增量改动点在[changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md)文件中描述。 From fc7de0671511e03c7d58fb8a70983a27d4037168 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 16 Jan 2021 17:18:18 +0800 Subject: [PATCH 16/81] =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E8=BD=AC=E6=8D=A2?= =?UTF-8?q?=EF=BC=88=E4=B8=80=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 110 ++++++++++++++ .../core/convert/ConversionService.java | 14 ++ .../core/convert/converter/Converter.java | 15 ++ .../convert/converter/ConverterFactory.java | 12 ++ .../convert/converter/ConverterRegistry.java | 16 +++ .../convert/converter/GenericConverter.java | 53 +++++++ .../support/DefaultConversionService.java | 19 +++ .../support/GenericConversionService.java | 136 ++++++++++++++++++ .../StringToNumberConverterFactory.java | 45 ++++++ .../test/common/StringToBooleanConverter.java | 22 +++ .../test/common/StringToIntegerConverter.java | 14 ++ .../test/ioc/TypeConversionFirstPartTest.java | 66 +++++++++ 12 files changed, 522 insertions(+) create mode 100644 src/main/java/org/springframework/core/convert/ConversionService.java create mode 100644 src/main/java/org/springframework/core/convert/converter/Converter.java create mode 100644 src/main/java/org/springframework/core/convert/converter/ConverterFactory.java create mode 100644 src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java create mode 100644 src/main/java/org/springframework/core/convert/converter/GenericConverter.java create mode 100644 src/main/java/org/springframework/core/convert/support/DefaultConversionService.java create mode 100644 src/main/java/org/springframework/core/convert/support/GenericConversionService.java create mode 100644 src/main/java/org/springframework/core/convert/support/StringToNumberConverterFactory.java create mode 100644 src/test/java/org/springframework/test/common/StringToBooleanConverter.java create mode 100644 src/test/java/org/springframework/test/common/StringToIntegerConverter.java create mode 100644 src/test/java/org/springframework/test/ioc/TypeConversionFirstPartTest.java diff --git a/changelog.md b/changelog.md index 6a2852e..eeb5f92 100644 --- a/changelog.md +++ b/changelog.md @@ -1196,9 +1196,119 @@ public class AutoProxyTest { } ``` +## 类型转换(一) +> 分支:type-conversion-first-part +spring在org.springframework.core.convert.converter包中定义了三种类型转换器接口:Converter、ConverterFactory、GenericConverter。 +### 一、Converter +``` +public interface Converter { + + /** + * 类型转换 + */ + T convert(S source); +} +``` +Converter能将S类型的对象转换为T类型的对象,比如将String类型的对象转换为Integer类型的对象的实现类: +``` +public class StringToIntegerConverter implements Converter { + @Override + public Integer convert(String source) { + return Integer.valueOf(source); + } +} +``` +使用: +``` +Integer num = new StringToIntegerConverter().convert("8888"); +``` + +### 二、ConverterFactory +``` +public interface ConverterFactory { + + Converter getConverter(Class targetType); +} +``` +Converter接口适合一对一的类型转换,如果要将String类型转换为Ineger/Long/Float/Double/Decimal等类型,就要实现一系列的StringToInteger/StringToLongConverter/StringToFloatConverter转换器,非常不优雅。 + +ConverterFactory接口则适合一对多的类型转换,可以将一种类型转换为另一种类型及其子类。比如将String类型转换为Ineger/Long/Float/Double/Decimal等Number类型时,只需定义一个ConverterFactory转换器: +``` +public class StringToNumberConverterFactory implements ConverterFactory { + + @Override + public Converter getConverter(Class targetType) { + return new StringToNumber(targetType); + } + + private static final class StringToNumber implements Converter { + + private final Class targetType; + public StringToNumber(Class targetType) { + this.targetType = targetType; + } + + @Override + public T convert(String source) { + if (source.length() == 0) { + return null; + } + + if (targetType.equals(Integer.class)) { + return (T) Integer.valueOf(source); + } else if (targetType.equals(Long.class)) { + return (T) Long.valueOf(source); + } + //TODO 其他数字类型 + + else { + throw new IllegalArgumentException( + "Cannot convert String [" + source + "] to target class [" + targetType.getName() + "]"); + } + } + } + +} +``` +使用: +``` +StringToNumberConverterFactory converterFactory = new StringToNumberConverterFactory(); +Converter stringToIntegerConverter = converterFactory.getConverter(Integer.class); +Integer num = stringToIntegerConverter.convert("8888"); +``` + +### 三、GenericConverter +``` +public interface GenericConverter { + + Set getConvertibleTypes(); + + Object convert(Object source, Class sourceType, Class targetType); +} +``` +String类型转换为Boolean类型的实现类: +``` +public class StringToBooleanConverter implements GenericConverter { + @Override + public Set getConvertibleTypes() { + return Collections.singleton(new ConvertiblePair(String.class, Boolean.class)); + } + + @Override + public Object convert(Object source, Class sourceType, Class targetType) { + return Boolean.valueOf((String) source); + } +} +``` +使用: +``` +Boolean flag = new StringToBooleanConverter().convert("true", String.class, Boolean.class); +``` +ConversionService是类型转换体系的核心接口,将以上三种类型转换器整合到一起,GenericConversionService是其实现类,DefaultConversionService在GenericConversionService是其实现类的基础上添加内置转换器。 +测试见TypeConversionFirstPartTest。 diff --git a/src/main/java/org/springframework/core/convert/ConversionService.java b/src/main/java/org/springframework/core/convert/ConversionService.java new file mode 100644 index 0000000..2da3a9a --- /dev/null +++ b/src/main/java/org/springframework/core/convert/ConversionService.java @@ -0,0 +1,14 @@ +package org.springframework.core.convert; + +/** + * 类型转换抽象接口 + * + * @author derekyi + * @date 2021/1/10 + */ +public interface ConversionService { + + boolean canConvert(Class sourceType, Class targetType); + + T convert(Object source, Class targetType); +} diff --git a/src/main/java/org/springframework/core/convert/converter/Converter.java b/src/main/java/org/springframework/core/convert/converter/Converter.java new file mode 100644 index 0000000..b3b4995 --- /dev/null +++ b/src/main/java/org/springframework/core/convert/converter/Converter.java @@ -0,0 +1,15 @@ +package org.springframework.core.convert.converter; + +/** + * 类型转换抽象接口 + * + * @author derekyi + * @date 2021/1/10 + */ +public interface Converter { + + /** + * 类型转换 + */ + T convert(S source); +} diff --git a/src/main/java/org/springframework/core/convert/converter/ConverterFactory.java b/src/main/java/org/springframework/core/convert/converter/ConverterFactory.java new file mode 100644 index 0000000..0d2baa6 --- /dev/null +++ b/src/main/java/org/springframework/core/convert/converter/ConverterFactory.java @@ -0,0 +1,12 @@ +package org.springframework.core.convert.converter; + +/** + * 类型转换工厂 + * + * @author derekyi + * @date 2021/1/10 + */ +public interface ConverterFactory { + + Converter getConverter(Class targetType); +} \ No newline at end of file diff --git a/src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java b/src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java new file mode 100644 index 0000000..dbcff6c --- /dev/null +++ b/src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java @@ -0,0 +1,16 @@ +package org.springframework.core.convert.converter; + +/** + * 类型转换器注册接口 + * + * @author derekyi + * @date 2021/1/10 + */ +public interface ConverterRegistry { + + void addConverter(Converter converter); + + void addConverterFactory(ConverterFactory converterFactory); + + void addConverter(GenericConverter converter); +} diff --git a/src/main/java/org/springframework/core/convert/converter/GenericConverter.java b/src/main/java/org/springframework/core/convert/converter/GenericConverter.java new file mode 100644 index 0000000..15bf723 --- /dev/null +++ b/src/main/java/org/springframework/core/convert/converter/GenericConverter.java @@ -0,0 +1,53 @@ +package org.springframework.core.convert.converter; + +import java.util.Set; + +/** + * @author derekyi + * @date 2021/1/16 + */ +public interface GenericConverter { + + Set getConvertibleTypes(); + + Object convert(Object source, Class sourceType, Class targetType); + + public static final class ConvertiblePair { + + private final Class sourceType; + + private final Class targetType; + + public ConvertiblePair(Class sourceType, Class targetType) { + this.sourceType = sourceType; + this.targetType = targetType; + } + + public Class getSourceType() { + return this.sourceType; + } + + public Class getTargetType() { + return this.targetType; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != ConvertiblePair.class) { + return false; + } + ConvertiblePair other = (ConvertiblePair) obj; + return this.sourceType.equals(other.sourceType) && this.targetType.equals(other.targetType); + + } + + @Override + public int hashCode() { + return this.sourceType.hashCode() * 31 + this.targetType.hashCode(); + } + } + +} diff --git a/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java b/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java new file mode 100644 index 0000000..72fe55d --- /dev/null +++ b/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java @@ -0,0 +1,19 @@ +package org.springframework.core.convert.support; + +import org.springframework.core.convert.converter.ConverterRegistry; + +/** + * @author derekyi + * @date 2021/1/16 + */ +public class DefaultConversionService extends GenericConversionService { + + public DefaultConversionService() { + addDefaultConverters(this); + } + + public static void addDefaultConverters(ConverterRegistry converterRegistry) { + converterRegistry.addConverterFactory(new StringToNumberConverterFactory()); + //TODO 添加其他ConverterFactory + } +} diff --git a/src/main/java/org/springframework/core/convert/support/GenericConversionService.java b/src/main/java/org/springframework/core/convert/support/GenericConversionService.java new file mode 100644 index 0000000..ba02073 --- /dev/null +++ b/src/main/java/org/springframework/core/convert/support/GenericConversionService.java @@ -0,0 +1,136 @@ +package org.springframework.core.convert.support; + +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.ConverterFactory; +import org.springframework.core.convert.converter.ConverterRegistry; +import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.*; + +/** + * @author derekyi + * @date 2021/1/16 + */ +public class GenericConversionService implements ConversionService, ConverterRegistry { + + private Map converters = new HashMap<>(); + + @Override + public boolean canConvert(Class sourceType, Class targetType) { + GenericConverter converter = getConverter(sourceType, targetType); + return converter != null; + } + + @Override + public T convert(Object source, Class targetType) { + Class sourceType = source.getClass(); + GenericConverter converter = getConverter(sourceType, targetType); + return (T) converter.convert(source, sourceType, targetType); + } + + @Override + public void addConverter(Converter converter) { + ConvertiblePair typeInfo = getRequiredTypeInfo(converter); + ConverterAdapter converterAdapter = new ConverterAdapter(typeInfo, converter); + for (ConvertiblePair convertibleType : converterAdapter.getConvertibleTypes()) { + converters.put(convertibleType, converterAdapter); + } + } + + @Override + public void addConverterFactory(ConverterFactory converterFactory) { + ConvertiblePair typeInfo = getRequiredTypeInfo(converterFactory); + ConverterFactoryAdapter converterFactoryAdapter = new ConverterFactoryAdapter(typeInfo, converterFactory); + for (ConvertiblePair convertibleType : converterFactoryAdapter.getConvertibleTypes()) { + converters.put(convertibleType, converterFactoryAdapter); + } + } + + @Override + public void addConverter(GenericConverter converter) { + for (ConvertiblePair convertibleType : converter.getConvertibleTypes()) { + converters.put(convertibleType, converter); + } + } + + private ConvertiblePair getRequiredTypeInfo(Object object) { + Type[] types = object.getClass().getGenericInterfaces(); + ParameterizedType parameterized = (ParameterizedType) types[0]; + Type[] actualTypeArguments = parameterized.getActualTypeArguments(); + Class sourceType = (Class) actualTypeArguments[0]; + Class targetType = (Class) actualTypeArguments[1]; + return new ConvertiblePair(sourceType, targetType); + } + + protected GenericConverter getConverter(Class sourceType, Class targetType) { + List> sourceCandidates = getClassHierarchy(sourceType); + List> targetCandidates = getClassHierarchy(targetType); + for (Class sourceCandidate : sourceCandidates) { + for (Class targetCandidate : targetCandidates) { + ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate); + GenericConverter converter = converters.get(convertiblePair); + if (converter != null) { + return converter; + } + } + } + return null; + } + + private List> getClassHierarchy(Class clazz) { + List> hierarchy = new ArrayList<>(); + while (clazz != null) { + hierarchy.add(clazz); + clazz = clazz.getSuperclass(); + } + return hierarchy; + } + + private final class ConverterAdapter implements GenericConverter { + + private final ConvertiblePair typeInfo; + + private final Converter converter; + + public ConverterAdapter(ConvertiblePair typeInfo, Converter converter) { + this.typeInfo = typeInfo; + this.converter = (Converter) converter; + } + + @Override + public Set getConvertibleTypes() { + return Collections.singleton(typeInfo); + } + + @Override + public Object convert(Object source, Class sourceType, Class targetType) { + return converter.convert(source); + } + } + + private final class ConverterFactoryAdapter implements GenericConverter { + + private final ConvertiblePair typeInfo; + + private final ConverterFactory converterFactory; + + public ConverterFactoryAdapter(ConvertiblePair typeInfo, ConverterFactory converterFactory) { + this.typeInfo = typeInfo; + this.converterFactory = (ConverterFactory) converterFactory; + } + + @Override + public Set getConvertibleTypes() { + return Collections.singleton(typeInfo); + } + + @Override + public Object convert(Object source, Class sourceType, Class targetType) { + return converterFactory.getConverter(targetType).convert(source); + } + } +} diff --git a/src/main/java/org/springframework/core/convert/support/StringToNumberConverterFactory.java b/src/main/java/org/springframework/core/convert/support/StringToNumberConverterFactory.java new file mode 100644 index 0000000..321ff8a --- /dev/null +++ b/src/main/java/org/springframework/core/convert/support/StringToNumberConverterFactory.java @@ -0,0 +1,45 @@ +package org.springframework.core.convert.support; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.ConverterFactory; + +/** + * @author derekyi + * @date 2021/1/10 + */ +public class StringToNumberConverterFactory implements ConverterFactory { + + @Override + public Converter getConverter(Class targetType) { + return new StringToNumber(targetType); + } + + private static final class StringToNumber implements Converter { + + private final Class targetType; + + public StringToNumber(Class targetType) { + this.targetType = targetType; + } + + @Override + public T convert(String source) { + if (source.length() == 0) { + return null; + } + + if (targetType.equals(Integer.class)) { + return (T) Integer.valueOf(source); + } else if (targetType.equals(Long.class)) { + return (T) Long.valueOf(source); + } + //TODO 其他数字类型 + + else { + throw new IllegalArgumentException( + "Cannot convert String [" + source + "] to target class [" + targetType.getName() + "]"); + } + } + } + +} \ No newline at end of file diff --git a/src/test/java/org/springframework/test/common/StringToBooleanConverter.java b/src/test/java/org/springframework/test/common/StringToBooleanConverter.java new file mode 100644 index 0000000..505be83 --- /dev/null +++ b/src/test/java/org/springframework/test/common/StringToBooleanConverter.java @@ -0,0 +1,22 @@ +package org.springframework.test.common; + +import org.springframework.core.convert.converter.GenericConverter; + +import java.util.Collections; +import java.util.Set; + +/** + * @author derekyi + * @date 2021/1/16 + */ +public class StringToBooleanConverter implements GenericConverter { + @Override + public Set getConvertibleTypes() { + return Collections.singleton(new ConvertiblePair(String.class, Boolean.class)); + } + + @Override + public Object convert(Object source, Class sourceType, Class targetType) { + return Boolean.valueOf((String) source); + } +} diff --git a/src/test/java/org/springframework/test/common/StringToIntegerConverter.java b/src/test/java/org/springframework/test/common/StringToIntegerConverter.java new file mode 100644 index 0000000..f656b97 --- /dev/null +++ b/src/test/java/org/springframework/test/common/StringToIntegerConverter.java @@ -0,0 +1,14 @@ +package org.springframework.test.common; + +import org.springframework.core.convert.converter.Converter; + +/** + * @author derekyi + * @date 2021/1/16 + */ +public class StringToIntegerConverter implements Converter { + @Override + public Integer convert(String source) { + return Integer.valueOf(source); + } +} diff --git a/src/test/java/org/springframework/test/ioc/TypeConversionFirstPartTest.java b/src/test/java/org/springframework/test/ioc/TypeConversionFirstPartTest.java new file mode 100644 index 0000000..d2e77c0 --- /dev/null +++ b/src/test/java/org/springframework/test/ioc/TypeConversionFirstPartTest.java @@ -0,0 +1,66 @@ +package org.springframework.test.ioc; + +import org.junit.Test; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.core.convert.support.StringToNumberConverterFactory; +import org.springframework.test.common.StringToBooleanConverter; +import org.springframework.test.common.StringToIntegerConverter; + +import static org.assertj.core.api.Assertions.assertThat; + + +/** + * @author derekyi + * @date 2021/1/16 + */ +public class TypeConversionFirstPartTest { + + @Test + public void testStringToIntegerConverter() throws Exception { + StringToIntegerConverter converter = new StringToIntegerConverter(); + Integer num = converter.convert("8888"); + assertThat(num).isEqualTo(8888); + } + + @Test + public void testStringToNumberConverterFactory() throws Exception { + StringToNumberConverterFactory converterFactory = new StringToNumberConverterFactory(); + + Converter stringToIntegerConverter = converterFactory.getConverter(Integer.class); + Integer intNum = stringToIntegerConverter.convert("8888"); + assertThat(intNum).isEqualTo(8888); + + Converter stringToLongConverter = converterFactory.getConverter(Long.class); + Long longNum = stringToLongConverter.convert("8888"); + assertThat(longNum).isEqualTo(8888L); + } + + @Test + public void testGenericConverter() throws Exception { + StringToBooleanConverter converter = new StringToBooleanConverter(); + + Boolean flag = (Boolean) converter.convert("true", String.class, Boolean.class); + assertThat(flag).isTrue(); + } + + @Test + public void testGenericConversionService() throws Exception { + GenericConversionService conversionService = new GenericConversionService(); + conversionService.addConverter(new StringToIntegerConverter()); + + Integer intNum = conversionService.convert("8888", Integer.class); + assertThat(conversionService.canConvert(String.class, Integer.class)).isTrue(); + assertThat(intNum).isEqualTo(8888); + + conversionService.addConverterFactory(new StringToNumberConverterFactory()); + assertThat(conversionService.canConvert(String.class, Long.class)).isTrue(); + Long longNum = conversionService.convert("8888", Long.class); + assertThat(longNum).isEqualTo(8888L); + + conversionService.addConverter(new StringToBooleanConverter()); + assertThat(conversionService.canConvert(String.class, Boolean.class)).isTrue(); + Boolean flag = conversionService.convert("true", Boolean.class); + assertThat(flag).isTrue(); + } +} \ No newline at end of file From 433a1c61a6d0809cc29d4b419941fcbbbac5dec9 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 16 Jan 2021 17:22:09 +0800 Subject: [PATCH 17/81] =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E8=BD=AC=E6=8D=A2?= =?UTF-8?q?=EF=BC=88=E4=B8=80=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +++++- README_CN.md | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 78a2795..f3fed95 100644 --- a/README.md +++ b/README.md @@ -43,11 +43,15 @@ If this project can help you, please give a **STAR, thank you!!!** * [Package scan](#包扫描) * [Value annotation](#Value) * [Autowired annotation](#Autowired) -* [Type conversion](#类型转换) +* [Type conversion(first part)](#类型转换一) +* [Type conversion(second part)](#类型转换二) #### Advanced * [Solve the problem of circular dependencies](#解决循环依赖问题) +#### bug fix +* [populate proxy bean with property values(discovered and fixed by kerwin89)](#没有为代理bean设置属性) + ## Usage Each function point corresponds to a branch. Switch to the branch corresponding to the function point to see the new function. The incremental change point is described in the [changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) file. diff --git a/README_CN.md b/README_CN.md index a73096b..1b0201f 100644 --- a/README_CN.md +++ b/README_CN.md @@ -43,7 +43,8 @@ * [包扫描](#包扫描) * [@Value注解](#@Value注解) * [基于注解@Autowired的依赖注入](#基于注解@Autowired的依赖注入) -* [类型转换](#类型转换) +* [类型转换(一)](#类型转换一) +* [类型转换(二)](#类型转换二) #### 高级篇 * [解决循环依赖问题](#解决循环依赖问题) From 07b54b291f448322136faa56e14e5e0b03e9bbdd Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 16 Jan 2021 17:27:06 +0800 Subject: [PATCH 18/81] update change.log --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index eeb5f92..68c418c 100644 --- a/changelog.md +++ b/changelog.md @@ -1308,7 +1308,7 @@ public class StringToBooleanConverter implements GenericConverter { Boolean flag = new StringToBooleanConverter().convert("true", String.class, Boolean.class); ``` -ConversionService是类型转换体系的核心接口,将以上三种类型转换器整合到一起,GenericConversionService是其实现类,DefaultConversionService在GenericConversionService是其实现类的基础上添加内置转换器。 +ConversionService是类型转换体系的核心接口,将以上三种类型转换器整合到一起,GenericConversionService是其实现类,DefaultConversionService在GenericConversionService的基础上添加内置转换器。 测试见TypeConversionFirstPartTest。 From 838821ec9f764d1ffe188db2dc50c9fb166a693c Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 16 Jan 2021 17:30:03 +0800 Subject: [PATCH 19/81] update readme.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f3fed95..4d43d48 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ The **mini-spring** is a simplified version of the Spring framework that will help you quickly get familiar with the Spring source code and grasp the core principles of Spring. The core logic of Spring is extracted, the code is extremely simplified, and the core functions of Spring, such as IoC and AOP, resource loaders, event listeners, type conversion, container extension points, bean life cycle and scope, and application context, are retained. -If this project can help you, please give a **STAR, thank you!!!** +If this project can help you, please **STAR the project, thank you!!!** ## Contents #### Basics From 21b831a37c959f6b8db964f30af76c529163ac0c Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sun, 17 Jan 2021 20:43:07 +0800 Subject: [PATCH 20/81] =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E8=BD=AC=E6=8D=A2?= =?UTF-8?q?=E4=BA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 77 +++++++++++++++++++ .../beans/factory/BeanFactory.java | 2 + .../AutowiredAnnotationBeanPostProcessor.java | 17 +++- .../config/ConfigurableBeanFactory.java | 6 ++ .../AbstractAutowireCapableBeanFactory.java | 12 +++ .../factory/support/AbstractBeanFactory.java | 21 +++++ .../support/AbstractApplicationContext.java | 25 +++++- .../support/ConversionServiceFactoryBean.java | 61 +++++++++++++++ .../support/GenericConversionService.java | 4 + .../org/springframework/test/bean/Car.java | 26 ++++++- .../test/common/ConvertersFactoryBean.java | 26 +++++++ .../common/StringToLocalDateConverter.java | 24 ++++++ .../ioc/TypeConversionSecondPartTest.java | 25 ++++++ .../resources/type-conversion-second-part.xml | 21 +++++ 14 files changed, 342 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/springframework/context/support/ConversionServiceFactoryBean.java create mode 100644 src/test/java/org/springframework/test/common/ConvertersFactoryBean.java create mode 100644 src/test/java/org/springframework/test/common/StringToLocalDateConverter.java create mode 100644 src/test/java/org/springframework/test/ioc/TypeConversionSecondPartTest.java create mode 100644 src/test/resources/type-conversion-second-part.xml diff --git a/changelog.md b/changelog.md index 68c418c..15016e7 100644 --- a/changelog.md +++ b/changelog.md @@ -1312,3 +1312,80 @@ ConversionService是类型转换体系的核心接口,将以上三种类型转 测试见TypeConversionFirstPartTest。 +## 类型转换(二) +> 分支:type-conversion-second-part + +上一节实现了spring中的类型转换体系,本节将类型转换的能力整合到容器中。 + +为了方便使用,提供了创建ConversionService的FactoryBean——ConversionServiceFactoryBean。 + +如果有定义ConversionService,在AbstractApplicationContext#finishBeanFactoryInitialization方法中设置到容器中。 + +类型转换的时机有两个: + +- 为bean填充属性时,见AbstractAutowireCapableBeanFactory#applyPropertyValues +- 处理@Value注解时,见AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues + +你可能会有疑问,如果没有定义ConversionService,是怎么进行基本类型的转换的?其实spring为了向下兼容保留了一套比较旧的类型转换机制,没有定义ConversionService时会使用其进行基本类型的转换工作,不必关注旧的类型转换机制。 + +测试: +``` +public class Car { + + private int price; + + private LocalDate produceDate; +} +``` +``` +public class StringToLocalDateConverter implements Converter { + + private final DateTimeFormatter DATE_TIME_FORMATTER; + + public StringToLocalDateConverter(String pattern) { + DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(pattern); + } + + @Override + public LocalDate convert(String source) { + return LocalDate.parse(source, DATE_TIME_FORMATTER); + } +} +``` +type-conversion-second-part.xml +``` + + + + + + + + + + + + + + + +``` +``` +public class TypeConversionSecondPartTest { + + @Test + public void testConversionService() throws Exception { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:type-conversion-second-part.xml"); + + Car car = applicationContext.getBean("car", Car.class); + assertThat(car.getPrice()).isEqualTo(1000000); + assertThat(car.getProduceDate()).isEqualTo(LocalDate.of(2021, 1, 1)); + } +} +``` \ No newline at end of file diff --git a/src/main/java/org/springframework/beans/factory/BeanFactory.java b/src/main/java/org/springframework/beans/factory/BeanFactory.java index 2ebbc21..ca2384a 100644 --- a/src/main/java/org/springframework/beans/factory/BeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/BeanFactory.java @@ -31,4 +31,6 @@ public interface BeanFactory { T getBean(String name, Class requiredType) throws BeansException; T getBean(Class requiredType) throws BeansException; + + boolean containsBean(String name); } diff --git a/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index e2cd262..5b28520 100644 --- a/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -1,12 +1,14 @@ package org.springframework.beans.factory.annotation; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.TypeUtil; import org.springframework.beans.BeansException; import org.springframework.beans.PropertyValues; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; +import org.springframework.core.convert.ConversionService; import java.lang.reflect.Field; @@ -33,8 +35,19 @@ public PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, for (Field field : fields) { Value valueAnnotation = field.getAnnotation(Value.class); if (valueAnnotation != null) { - String value = valueAnnotation.value(); - value = beanFactory.resolveEmbeddedValue(value); + Object value = valueAnnotation.value(); + value = beanFactory.resolveEmbeddedValue((String) value); + + //类型转换 + Class sourceType = value.getClass(); + Class targetType = (Class) TypeUtil.getType(field); + ConversionService conversionService = beanFactory.getConversionService(); + if (conversionService != null) { + if (conversionService.canConvert(sourceType, targetType)) { + value = conversionService.convert(value, targetType); + } + } + BeanUtil.setFieldValue(bean, field.getName(), value); } } diff --git a/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java b/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java index 8e83dfb..939125d 100644 --- a/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java @@ -1,6 +1,7 @@ package org.springframework.beans.factory.config; import org.springframework.beans.factory.HierarchicalBeanFactory; +import org.springframework.core.convert.ConversionService; import org.springframework.util.StringValueResolver; /** @@ -22,4 +23,9 @@ public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, Single void addEmbeddedValueResolver(StringValueResolver valueResolver); String resolveEmbeddedValue(String value); + + void setConversionService(ConversionService conversionService); + + ConversionService getConversionService(); + } diff --git a/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 6b9bbb7..f346605 100644 --- a/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -3,6 +3,7 @@ import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.TypeUtil; import org.springframework.beans.BeansException; import org.springframework.beans.PropertyValue; import org.springframework.beans.PropertyValues; @@ -10,6 +11,7 @@ import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.config.*; +import org.springframework.core.convert.ConversionService; import java.lang.reflect.Method; @@ -170,6 +172,16 @@ protected void applyPropertyValues(String beanName, Object bean, BeanDefinition // beanA依赖beanB,先实例化beanB BeanReference beanReference = (BeanReference) value; value = getBean(beanReference.getBeanName()); + } else { + //类型转换 + Class sourceType = value.getClass(); + Class targetType = (Class) TypeUtil.getFieldType(bean.getClass(), name); + ConversionService conversionService = getConversionService(); + if (conversionService != null) { + if (conversionService.canConvert(sourceType, targetType)) { + value = conversionService.convert(value, targetType); + } + } } //通过反射设置属性 diff --git a/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index 7a5ada7..5c12aa8 100644 --- a/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -5,6 +5,7 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.core.convert.ConversionService; import org.springframework.util.StringValueResolver; import java.util.ArrayList; @@ -24,6 +25,9 @@ public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry i private final List embeddedValueResolvers = new ArrayList(); + private ConversionService conversionService; + + @Override public Object getBean(String name) throws BeansException { Object sharedInstance = getSingleton(name); @@ -73,6 +77,13 @@ public T getBean(String name, Class requiredType) throws BeansException { return ((T) getBean(name)); } + @Override + public boolean containsBean(String name) { + return containsBeanDefinition(name); + } + + protected abstract boolean containsBeanDefinition(String beanName); + protected abstract Object createBean(String beanName, BeanDefinition beanDefinition) throws BeansException; protected abstract BeanDefinition getBeanDefinition(String beanName) throws BeansException; @@ -99,4 +110,14 @@ public String resolveEmbeddedValue(String value) { } return result; } + + @Override + public ConversionService getConversionService() { + return conversionService; + } + + @Override + public void setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + } } diff --git a/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index bd8d637..33e48fe 100644 --- a/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -11,6 +11,7 @@ import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.SimpleApplicationEventMulticaster; +import org.springframework.core.convert.ConversionService; import org.springframework.core.io.DefaultResourceLoader; import java.util.Collection; @@ -26,6 +27,8 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader i public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster"; + public static final String CONVERSION_SERVICE_BEAN_NAME = "conversionService"; + private ApplicationEventMulticaster applicationEventMulticaster; @Override @@ -49,13 +52,26 @@ public void refresh() throws BeansException { //注册事件监听器 registerListeners(); - //提前实例化单例bean - beanFactory.preInstantiateSingletons(); + //注册类型转换器和提前实例化单例bean + finishBeanFactoryInitialization(beanFactory); //发布容器刷新完成事件 finishRefresh(); } + protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { + //设置类型转换器 + if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME)) { + Object conversionService = beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME); + if (conversionService instanceof ConversionService) { + beanFactory.setConversionService((ConversionService) conversionService); + } + } + + //提前实例化单例bean + beanFactory.preInstantiateSingletons(); + } + /** * 创建BeanFactory,并加载BeanDefinition * @@ -118,6 +134,11 @@ public void publishEvent(ApplicationEvent event) { applicationEventMulticaster.multicastEvent(event); } + @Override + public boolean containsBean(String name) { + return getBeanFactory().containsBean(name); + } + @Override public T getBean(String name, Class requiredType) throws BeansException { return getBeanFactory().getBean(name, requiredType); diff --git a/src/main/java/org/springframework/context/support/ConversionServiceFactoryBean.java b/src/main/java/org/springframework/context/support/ConversionServiceFactoryBean.java new file mode 100644 index 0000000..2d8bc4c --- /dev/null +++ b/src/main/java/org/springframework/context/support/ConversionServiceFactoryBean.java @@ -0,0 +1,61 @@ +package org.springframework.context.support; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.ConverterFactory; +import org.springframework.core.convert.converter.ConverterRegistry; +import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.core.convert.support.GenericConversionService; + +import java.util.Set; + +/** + * @author derekyi + * @date 2021/1/17 + */ +public class ConversionServiceFactoryBean implements FactoryBean, InitializingBean { + + private Set converters; + + private GenericConversionService conversionService; + + @Override + public void afterPropertiesSet() throws Exception { + conversionService = new DefaultConversionService(); + registerConverters(converters, conversionService); + } + + private void registerConverters(Set converters, ConverterRegistry registry) { + if (converters != null) { + for (Object converter : converters) { + if (converter instanceof GenericConverter) { + registry.addConverter((GenericConverter) converter); + } else if (converter instanceof Converter) { + registry.addConverter((Converter) converter); + } else if (converter instanceof ConverterFactory) { + registry.addConverterFactory((ConverterFactory) converter); + } else { + throw new IllegalArgumentException("Each converter object must implement one of the " + + "Converter, ConverterFactory, or GenericConverter interfaces"); + } + } + } + } + + @Override + public ConversionService getObject() throws Exception { + return conversionService; + } + + @Override + public boolean isSingleton() { + return true; + } + + public void setConverters(Set converters) { + this.converters = converters; + } +} diff --git a/src/main/java/org/springframework/core/convert/support/GenericConversionService.java b/src/main/java/org/springframework/core/convert/support/GenericConversionService.java index ba02073..bcbbd2a 100644 --- a/src/main/java/org/springframework/core/convert/support/GenericConversionService.java +++ b/src/main/java/org/springframework/core/convert/support/GenericConversionService.java @@ -1,5 +1,6 @@ package org.springframework.core.convert.support; +import cn.hutool.core.convert.BasicType; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; @@ -28,6 +29,7 @@ public boolean canConvert(Class sourceType, Class targetType) { @Override public T convert(Object source, Class targetType) { Class sourceType = source.getClass(); + targetType = (Class) BasicType.wrap(targetType); GenericConverter converter = getConverter(sourceType, targetType); return (T) converter.convert(source, sourceType, targetType); } @@ -83,6 +85,8 @@ protected GenericConverter getConverter(Class sourceType, Class targetType private List> getClassHierarchy(Class clazz) { List> hierarchy = new ArrayList<>(); + //原始类转为包装类 + clazz = BasicType.wrap(clazz); while (clazz != null) { hierarchy.add(clazz); clazz = clazz.getSuperclass(); diff --git a/src/test/java/org/springframework/test/bean/Car.java b/src/test/java/org/springframework/test/bean/Car.java index c20e35f..890c0f4 100644 --- a/src/test/java/org/springframework/test/bean/Car.java +++ b/src/test/java/org/springframework/test/bean/Car.java @@ -3,6 +3,8 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import java.time.LocalDate; + /** * @author derekyi * @date 2020/11/24 @@ -10,6 +12,10 @@ @Component public class Car { + private int price; + + private LocalDate produceDate; + @Value("${brand}") private String brand; @@ -21,10 +27,28 @@ public void setBrand(String brand) { this.brand = brand; } + public int getPrice() { + return price; + } + + public void setPrice(int price) { + this.price = price; + } + + public LocalDate getProduceDate() { + return produceDate; + } + + public void setProduceDate(LocalDate produceDate) { + this.produceDate = produceDate; + } + @Override public String toString() { return "Car{" + - "brand='" + brand + '\'' + + "price=" + price + + ", produceDate=" + produceDate + + ", brand='" + brand + '\'' + '}'; } } diff --git a/src/test/java/org/springframework/test/common/ConvertersFactoryBean.java b/src/test/java/org/springframework/test/common/ConvertersFactoryBean.java new file mode 100644 index 0000000..a445fb3 --- /dev/null +++ b/src/test/java/org/springframework/test/common/ConvertersFactoryBean.java @@ -0,0 +1,26 @@ +package org.springframework.test.common; + +import org.springframework.beans.factory.FactoryBean; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author derekyi + * @date 2021/1/17 + */ +public class ConvertersFactoryBean implements FactoryBean> { + + @Override + public Set getObject() throws Exception { + HashSet converters = new HashSet<>(); + StringToLocalDateConverter stringToLocalDateConverter = new StringToLocalDateConverter("yyyy-MM-dd"); + converters.add(stringToLocalDateConverter); + return converters; + } + + @Override + public boolean isSingleton() { + return true; + } +} diff --git a/src/test/java/org/springframework/test/common/StringToLocalDateConverter.java b/src/test/java/org/springframework/test/common/StringToLocalDateConverter.java new file mode 100644 index 0000000..ed6f9ec --- /dev/null +++ b/src/test/java/org/springframework/test/common/StringToLocalDateConverter.java @@ -0,0 +1,24 @@ +package org.springframework.test.common; + +import org.springframework.core.convert.converter.Converter; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +/** + * @author derekyi + * @date 2021/1/17 + */ +public class StringToLocalDateConverter implements Converter { + + private final DateTimeFormatter DATE_TIME_FORMATTER; + + public StringToLocalDateConverter(String pattern) { + DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(pattern); + } + + @Override + public LocalDate convert(String source) { + return LocalDate.parse(source, DATE_TIME_FORMATTER); + } +} diff --git a/src/test/java/org/springframework/test/ioc/TypeConversionSecondPartTest.java b/src/test/java/org/springframework/test/ioc/TypeConversionSecondPartTest.java new file mode 100644 index 0000000..d5ffe95 --- /dev/null +++ b/src/test/java/org/springframework/test/ioc/TypeConversionSecondPartTest.java @@ -0,0 +1,25 @@ +package org.springframework.test.ioc; + +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.test.bean.Car; + +import java.time.LocalDate; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author derekyi + * @date 2021/1/17 + */ +public class TypeConversionSecondPartTest { + + @Test + public void testConversionService() throws Exception { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:type-conversion-second-part.xml"); + + Car car = applicationContext.getBean("car", Car.class); + assertThat(car.getPrice()).isEqualTo(1000000); + assertThat(car.getProduceDate()).isEqualTo(LocalDate.of(2021, 1, 1)); + } +} diff --git a/src/test/resources/type-conversion-second-part.xml b/src/test/resources/type-conversion-second-part.xml new file mode 100644 index 0000000..2764aac --- /dev/null +++ b/src/test/resources/type-conversion-second-part.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + \ No newline at end of file From e0ddf60da5fc938bfe6212879aa2057b6c34720a Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sun, 17 Jan 2021 21:13:58 +0800 Subject: [PATCH 21/81] update readme.md --- README.md | 3 ++- README_CN.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4d43d48..29a8cd0 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,10 @@ [![Stars](https://img.shields.io/github/stars/DerekYRC/mini-spring)](https://img.shields.io/github/stars/DerekYRC/mini-spring) [![Forks](https://img.shields.io/github/forks/DerekYRC/mini-spring)](https://img.shields.io/github/forks/DerekYRC/mini-spring) -## About * [中文版](./README_CN.md) +## About + The **mini-spring** is a simplified version of the Spring framework that will help you quickly get familiar with the Spring source code and grasp the core principles of Spring. The core logic of Spring is extracted, the code is extremely simplified, and the core functions of Spring, such as IoC and AOP, resource loaders, event listeners, type conversion, container extension points, bean life cycle and scope, and application context, are retained. If this project can help you, please **STAR the project, thank you!!!** diff --git a/README_CN.md b/README_CN.md index 1b0201f..65adf1e 100644 --- a/README_CN.md +++ b/README_CN.md @@ -4,9 +4,10 @@ [![Stars](https://img.shields.io/github/stars/DerekYRC/mini-spring)](https://img.shields.io/github/stars/DerekYRC/mini-spring) [![Forks](https://img.shields.io/github/forks/DerekYRC/mini-spring)](https://img.shields.io/github/forks/DerekYRC/mini-spring) -## 关于 * [English version](./README.md) +## 关于 + **mini-spring**是简化版的spring框架,能帮助你快速熟悉spring源码和掌握spring的核心原理。抽取了spring的核心逻辑,代码极度简化,保留spring的核心功能,如IoC和AOP、资源加载器、事件监听器、类型转换、容器扩展点、bean生命周期和作用域、应用上下文等核心功能。 如果本项目能帮助到你,请给个**STAR,谢谢!!!** From 7a3c32da2410e64b8001d17b9cd170b3a6dda060 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Mon, 25 Jan 2021 19:59:50 +0800 Subject: [PATCH 22/81] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=BE=AA=E7=8E=AF?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 6 ++++- .../AbstractAutowireCapableBeanFactory.java | 6 ++++- .../support/DefaultSingletonBeanRegistry.java | 9 +++++++- .../support/AbstractApplicationContext.java | 1 + .../java/org/springframework/test/bean/A.java | 18 +++++++++++++++ .../java/org/springframework/test/bean/B.java | 18 +++++++++++++++ ...CircularReferenceWithoutProxyBeanTest.java | 23 +++++++++++++++++++ .../circular-reference-without-proxy-bean.xml | 18 +++++++++++++++ 8 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 src/test/java/org/springframework/test/bean/A.java create mode 100644 src/test/java/org/springframework/test/bean/B.java create mode 100644 src/test/java/org/springframework/test/ioc/CircularReferenceWithoutProxyBeanTest.java create mode 100644 src/test/resources/circular-reference-without-proxy-bean.xml diff --git a/changelog.md b/changelog.md index 15016e7..da84224 100644 --- a/changelog.md +++ b/changelog.md @@ -1388,4 +1388,8 @@ public class TypeConversionSecondPartTest { assertThat(car.getProduceDate()).isEqualTo(LocalDate.of(2021, 1, 1)); } } -``` \ No newline at end of file +``` + +## 解决循环依赖问题(一):没有代理对象 +> 分支:circular-reference-without-proxy-bean + diff --git a/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index f346605..b95b180 100644 --- a/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -64,9 +64,13 @@ protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass, Str } protected Object doCreateBean(String beanName, BeanDefinition beanDefinition) { - Object bean = null; + Object bean; try { bean = createBeanInstance(beanDefinition); + + //为解决循环依赖问题,将实例化后的bean放进缓存中提前暴露 + earlySingletonObjects.put(beanName, bean); + //实例化bean之后执行 boolean continueWithPropertyPopulation = applyBeanPostProcessorsAfterInstantiation(beanName, bean); if (!continueWithPropertyPopulation) { diff --git a/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java index 8ee3656..84eb027 100644 --- a/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java +++ b/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java @@ -17,13 +17,20 @@ public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry { private Map singletonObjects = new HashMap<>(); + protected Map earlySingletonObjects = new HashMap<>(); + private final Map disposableBeans = new HashMap<>(); @Override public Object getSingleton(String beanName) { - return singletonObjects.get(beanName); + Object bean = singletonObjects.get(beanName); + if (bean == null) { + bean = earlySingletonObjects.get(beanName); + } + return bean; } + @Override public void addSingleton(String beanName, Object singletonObject) { singletonObjects.put(beanName, singletonObject); } diff --git a/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 33e48fe..89ea47a 100644 --- a/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -153,6 +153,7 @@ public T getBean(Class requiredType) throws BeansException { return getBeanFactory().getBean(requiredType); } + @Override public Object getBean(String name) throws BeansException { return getBeanFactory().getBean(name); } diff --git a/src/test/java/org/springframework/test/bean/A.java b/src/test/java/org/springframework/test/bean/A.java new file mode 100644 index 0000000..832c1f4 --- /dev/null +++ b/src/test/java/org/springframework/test/bean/A.java @@ -0,0 +1,18 @@ +package org.springframework.test.bean; + +/** + * @author derekyi + * @date 2021/1/25 + */ +public class A { + + private B b; + + public B getB() { + return b; + } + + public void setB(B b) { + this.b = b; + } +} diff --git a/src/test/java/org/springframework/test/bean/B.java b/src/test/java/org/springframework/test/bean/B.java new file mode 100644 index 0000000..ba467bf --- /dev/null +++ b/src/test/java/org/springframework/test/bean/B.java @@ -0,0 +1,18 @@ +package org.springframework.test.bean; + +/** + * @author derekyi + * @date 2021/1/25 + */ +public class B { + + private A a; + + public A getA() { + return a; + } + + public void setA(A a) { + this.a = a; + } +} diff --git a/src/test/java/org/springframework/test/ioc/CircularReferenceWithoutProxyBeanTest.java b/src/test/java/org/springframework/test/ioc/CircularReferenceWithoutProxyBeanTest.java new file mode 100644 index 0000000..5fa4b67 --- /dev/null +++ b/src/test/java/org/springframework/test/ioc/CircularReferenceWithoutProxyBeanTest.java @@ -0,0 +1,23 @@ +package org.springframework.test.ioc; + +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.test.bean.A; +import org.springframework.test.bean.B; + +import static org.assertj.core.api.Java6Assertions.assertThat; + +/** + * @author derekyi + * @date 2021/1/25 + */ +public class CircularReferenceWithoutProxyBeanTest { + + @Test + public void testCircularRefreence() throws Exception { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:circular-reference-without-proxy-bean.xml"); + A a = applicationContext.getBean("a", A.class); + B b = applicationContext.getBean("b", B.class); + assertThat(a.getB() == b).isTrue(); + } +} diff --git a/src/test/resources/circular-reference-without-proxy-bean.xml b/src/test/resources/circular-reference-without-proxy-bean.xml new file mode 100644 index 0000000..9babe20 --- /dev/null +++ b/src/test/resources/circular-reference-without-proxy-bean.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + \ No newline at end of file From b2cdbb2b6a42aa8324676ad805688578a559cacf Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 30 Jan 2021 11:05:10 +0800 Subject: [PATCH 23/81] =?UTF-8?q?=E6=B2=A1=E6=9C=89=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E7=9A=84=E5=BE=AA=E7=8E=AF=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 104 ++++++++++++++++++ .../springframework/aop/AdvisedSupport.java | 2 +- .../DefaultAdvisorAutoProxyCreator.java | 1 + .../AbstractAutowireCapableBeanFactory.java | 4 +- .../java/org/springframework/test/bean/A.java | 2 + .../test/common/ABeforeAdvice.java | 16 +++ .../CircularReferenceWithProxyBeanTest.java | 26 +++++ ...CircularReferenceWithoutProxyBeanTest.java | 2 +- .../circular-reference-with-proxy-bean.xml | 33 ++++++ 9 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 src/test/java/org/springframework/test/common/ABeforeAdvice.java create mode 100644 src/test/java/org/springframework/test/ioc/CircularReferenceWithProxyBeanTest.java create mode 100644 src/test/resources/circular-reference-with-proxy-bean.xml diff --git a/changelog.md b/changelog.md index da84224..dfedbc2 100644 --- a/changelog.md +++ b/changelog.md @@ -1392,4 +1392,108 @@ public class TypeConversionSecondPartTest { ## 解决循环依赖问题(一):没有代理对象 > 分支:circular-reference-without-proxy-bean + +虽然放在高级篇,其实解决循环依赖问题的方法非常简单。 + +先理解spring中为什么会有循环依赖的问题。比如如下的代码 + +``` +public class A { + + private B b; + + //getter and setter +} +``` +``` +public class B { + + private A a; + + //getter and setter +} +``` +``` + + + + + + + + +``` + +A依赖B,B又依赖A,循环依赖。容器加载时会执行依赖流程: + +- 实例化A,发现依赖B,然后实例化B +- 实例化B,发现依赖A,然后实例化A +- 实例化A,发现依赖B,然后实例化B +- ... + +死循环直至栈溢出。 + +解决该问题的关键在于何时将实例化后的bean放进容器中,设置属性前还是设置属性后。现有的执行流程,bean实例化后并且设置属性后会被放进singletonObjects单例缓存中。如果我们调整一下顺序,当bean实例化后就放进singletonObjects单例缓存中,然后再设置属性,就能解决上面的循环依赖问题,执行流程变为: + +- 步骤一:getBean(a),检查singletonObjects是否包含a,singletonObjects不包含a,实例化A放进singletonObjects,设置属性b,发现依赖B,尝试getBean(b) +- 步骤二:getBean(b),检查singletonObjects是否包含b,singletonObjects不包含a,实例化B放进singletonObjects,设置属性a,发现依赖A,尝试getBean(a) +- 步骤三:getBean(a),检查singletonObjects是否包含a,singletonObjects包含a,返回a +- 步骤四:步骤二中的b拿到a,设置属性a,然后返回b +- 步骤五:步骤一种的a拿到b,设置属性b,然后返回a + +可见调整bean放进singletonObjects(人称一级缓存)的时机到bean实例化后即可解决循环依赖问题。但为了和spring保持一致,我们增加一个二级缓存earlySingletonObjects,在bean实例化后将bean放进earlySingletonObjects中(见AbstractAutowireCapableBeanFactory#doCreateBean方法第6行),getBean()时检查一级缓存singletonObjects和二级缓存earlySingletonObjects中是否包含该bean,包含则直接返回(见AbstractBeanFactory#getBean第1行)。 + +单测见CircularReferenceWithoutProxyBeanTest#testCircularReference。 + +增加二级缓存,不能解决有代理对象时的循环依赖。原因是放进二级缓存earlySingletonObjects中的bean是实例化后的bean,而放进一级缓存singletonObjects中的bean是代理对象(代理对象在BeanPostProcessor#postProcessAfterInitialization中返回),两个缓存中的bean不一致。比如上面的例子,如果A被代理,那么B拿到的a是实例化后的A,而a是被代理后的对象,即b.getA() != a,见单测 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/springframework/aop/AdvisedSupport.java b/src/main/java/org/springframework/aop/AdvisedSupport.java index 37bb76a..17bfe2b 100644 --- a/src/main/java/org/springframework/aop/AdvisedSupport.java +++ b/src/main/java/org/springframework/aop/AdvisedSupport.java @@ -9,7 +9,7 @@ public class AdvisedSupport { //是否使用cglib代理 - private boolean proxyTargetClass = false; + private boolean proxyTargetClass = true; private TargetSource targetSource; diff --git a/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java b/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java index 18d9f48..80c1ac0 100644 --- a/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java +++ b/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java @@ -36,6 +36,7 @@ public Object postProcessAfterInitialization(Object bean, String beanName) throw if (classFilter.matches(bean.getClass())) { AdvisedSupport advisedSupport = new AdvisedSupport(); TargetSource targetSource = new TargetSource(bean); + advisedSupport.setTargetSource(targetSource); advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice()); advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher()); diff --git a/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index b95b180..a904252 100644 --- a/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -69,7 +69,9 @@ protected Object doCreateBean(String beanName, BeanDefinition beanDefinition) { bean = createBeanInstance(beanDefinition); //为解决循环依赖问题,将实例化后的bean放进缓存中提前暴露 - earlySingletonObjects.put(beanName, bean); + if (beanDefinition.isSingleton()) { + earlySingletonObjects.put(beanName, bean); + } //实例化bean之后执行 boolean continueWithPropertyPopulation = applyBeanPostProcessorsAfterInstantiation(beanName, bean); diff --git a/src/test/java/org/springframework/test/bean/A.java b/src/test/java/org/springframework/test/bean/A.java index 832c1f4..36ad3c0 100644 --- a/src/test/java/org/springframework/test/bean/A.java +++ b/src/test/java/org/springframework/test/bean/A.java @@ -8,6 +8,8 @@ public class A { private B b; + public void func(){} + public B getB() { return b; } diff --git a/src/test/java/org/springframework/test/common/ABeforeAdvice.java b/src/test/java/org/springframework/test/common/ABeforeAdvice.java new file mode 100644 index 0000000..86a5237 --- /dev/null +++ b/src/test/java/org/springframework/test/common/ABeforeAdvice.java @@ -0,0 +1,16 @@ +package org.springframework.test.common; + +import org.springframework.aop.MethodBeforeAdvice; + +import java.lang.reflect.Method; + +/** + * @author derekyi + * @date 2021/1/30 + */ +public class ABeforeAdvice implements MethodBeforeAdvice { + @Override + public void before(Method method, Object[] args, Object target) throws Throwable { + + } +} diff --git a/src/test/java/org/springframework/test/ioc/CircularReferenceWithProxyBeanTest.java b/src/test/java/org/springframework/test/ioc/CircularReferenceWithProxyBeanTest.java new file mode 100644 index 0000000..35618f2 --- /dev/null +++ b/src/test/java/org/springframework/test/ioc/CircularReferenceWithProxyBeanTest.java @@ -0,0 +1,26 @@ +package org.springframework.test.ioc; + +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.test.bean.A; +import org.springframework.test.bean.B; + +import static org.assertj.core.api.Java6Assertions.assertThat; + +/** + * @author derekyi + * @date 2021/1/30 + */ +public class CircularReferenceWithProxyBeanTest { + + @Test + public void testCircularReference() throws Exception { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:circular-reference-with-proxy-bean.xml"); + A a = applicationContext.getBean("a", A.class); + B b = applicationContext.getBean("b", B.class); + + //增加二级缓存不能解决有代理对象时的循环依赖。 + //a被代理,放进二级缓存earlySingletonObjects中的是实例化后的A,而放进一级缓存singletonObjects中的是被代理后的A,实例化b时从earlySingletonObjects获取a,所以b.getA() != a + assertThat(b.getA() != a).isTrue(); + } +} diff --git a/src/test/java/org/springframework/test/ioc/CircularReferenceWithoutProxyBeanTest.java b/src/test/java/org/springframework/test/ioc/CircularReferenceWithoutProxyBeanTest.java index 5fa4b67..9c87899 100644 --- a/src/test/java/org/springframework/test/ioc/CircularReferenceWithoutProxyBeanTest.java +++ b/src/test/java/org/springframework/test/ioc/CircularReferenceWithoutProxyBeanTest.java @@ -14,7 +14,7 @@ public class CircularReferenceWithoutProxyBeanTest { @Test - public void testCircularRefreence() throws Exception { + public void testCircularReference() throws Exception { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:circular-reference-without-proxy-bean.xml"); A a = applicationContext.getBean("a", A.class); B b = applicationContext.getBean("b", B.class); diff --git a/src/test/resources/circular-reference-with-proxy-bean.xml b/src/test/resources/circular-reference-with-proxy-bean.xml new file mode 100644 index 0000000..c479df9 --- /dev/null +++ b/src/test/resources/circular-reference-with-proxy-bean.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 389c721eadd445491b7c0264cee7d59bd45b6c04 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 30 Jan 2021 11:08:32 +0800 Subject: [PATCH 24/81] =?UTF-8?q?=E6=B2=A1=E6=9C=89=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E7=9A=84=E5=BE=AA=E7=8E=AF=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- README_CN.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 29a8cd0..f7b29c6 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,8 @@ If this project can help you, please **STAR the project, thank you!!!** * [Type conversion(second part)](#类型转换二) #### Advanced -* [Solve the problem of circular dependencies](#解决循环依赖问题) +* [Solve the problem of circular dependencies(first part): without proxy bean](#解决循环依赖问题一) +* [Solve the problem of circular dependencies(second part): with proxy bean](#解决循环依赖问题二) #### bug fix * [populate proxy bean with property values(discovered and fixed by kerwin89)](#没有为代理bean设置属性) diff --git a/README_CN.md b/README_CN.md index 65adf1e..7245c72 100644 --- a/README_CN.md +++ b/README_CN.md @@ -48,7 +48,8 @@ * [类型转换(二)](#类型转换二) #### 高级篇 -* [解决循环依赖问题](#解决循环依赖问题) +* [解决循环依赖问题(一):没有代理对象](#解决循环依赖问题(一):没有代理对象) +* [解决循环依赖问题(二):有代理对象](#解决循环依赖问题(二):有代理对象) #### bug fix * [没有为代理bean设置属性(discovered and fixed by kerwin89)](#没有为代理bean设置属性) From 6eba0adb0ae5f336361858d877b3aa099377781b Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 30 Jan 2021 11:09:05 +0800 Subject: [PATCH 25/81] =?UTF-8?q?=E6=B2=A1=E6=9C=89=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E7=9A=84=E5=BE=AA=E7=8E=AF=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index dfedbc2..451b291 100644 --- a/changelog.md +++ b/changelog.md @@ -1447,7 +1447,7 @@ A依赖B,B又依赖A,循环依赖。容器加载时会执行依赖流程: 增加二级缓存,不能解决有代理对象时的循环依赖。原因是放进二级缓存earlySingletonObjects中的bean是实例化后的bean,而放进一级缓存singletonObjects中的bean是代理对象(代理对象在BeanPostProcessor#postProcessAfterInitialization中返回),两个缓存中的bean不一致。比如上面的例子,如果A被代理,那么B拿到的a是实例化后的A,而a是被代理后的对象,即b.getA() != a,见单测 - +下一节填坑。 From bfd342b0a45823d463f8df92fcbcfaef7d69c8be Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 30 Jan 2021 12:41:15 +0800 Subject: [PATCH 26/81] =?UTF-8?q?=E6=9C=89=E4=BB=A3=E7=90=86=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E7=9A=84=E5=BE=AA=E7=8E=AF=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 3 ++ .../DefaultAdvisorAutoProxyCreator.java | 18 +++++++++++ .../beans/factory/ObjectFactory.java | 12 ++++++++ .../InstantiationAwareBeanPostProcessor.java | 13 ++++++++ .../AbstractAutowireCapableBeanFactory.java | 27 +++++++++++++++-- .../support/DefaultSingletonBeanRegistry.java | 30 +++++++++++++++---- .../CircularReferenceWithProxyBeanTest.java | 4 +-- 7 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/springframework/beans/factory/ObjectFactory.java diff --git a/changelog.md b/changelog.md index 451b291..b003e5f 100644 --- a/changelog.md +++ b/changelog.md @@ -1449,6 +1449,9 @@ A依赖B,B又依赖A,循环依赖。容器加载时会执行依赖流程: 下一节填坑。 +## 解决循环依赖问题(二):有代理对象 +> 分支:circular-reference-with-proxy-bean + diff --git a/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java b/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java index 80c1ac0..b1d1ecc 100644 --- a/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java +++ b/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java @@ -13,6 +13,8 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory; import java.util.Collection; +import java.util.HashSet; +import java.util.Set; /** * @author derekyi @@ -22,8 +24,24 @@ public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPos private DefaultListableBeanFactory beanFactory; + private Set earlyProxyReferences = new HashSet<>(); + @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (!earlyProxyReferences.contains(beanName)) { + return wrapIfNecessary(bean, beanName); + } + + return bean; + } + + @Override + public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { + earlyProxyReferences.add(beanName); + return wrapIfNecessary(bean, beanName); + } + + protected Object wrapIfNecessary(Object bean, String beanName) { //避免死循环 if (isInfrastructureClass(bean.getClass())) { return bean; diff --git a/src/main/java/org/springframework/beans/factory/ObjectFactory.java b/src/main/java/org/springframework/beans/factory/ObjectFactory.java new file mode 100644 index 0000000..8c52b3e --- /dev/null +++ b/src/main/java/org/springframework/beans/factory/ObjectFactory.java @@ -0,0 +1,12 @@ +package org.springframework.beans.factory; + +import org.springframework.beans.BeansException; + +/** + * @author derekyi + * @date 2021/1/30 + */ +public interface ObjectFactory { + + T getObject() throws BeansException; +} \ No newline at end of file diff --git a/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java b/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java index db0f10e..77fdaff 100644 --- a/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java +++ b/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java @@ -40,4 +40,17 @@ public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor { */ PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeansException; + + /** + * 提前暴露bean + * + * @param bean + * @param beanName + * @return + * @throws BeansException + */ + default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { + return bean; + } + } diff --git a/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index a904252..3a65fd3 100644 --- a/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -10,6 +10,7 @@ import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.config.*; import org.springframework.core.convert.ConversionService; @@ -70,7 +71,13 @@ protected Object doCreateBean(String beanName, BeanDefinition beanDefinition) { //为解决循环依赖问题,将实例化后的bean放进缓存中提前暴露 if (beanDefinition.isSingleton()) { - earlySingletonObjects.put(beanName, bean); + Object finalBean = bean; + addSingletonFactory(beanName, new ObjectFactory() { + @Override + public Object getObject() throws BeansException { + return getEarlyBeanReference(beanName, beanDefinition, finalBean); + } + }); } //实例化bean之后执行 @@ -92,11 +99,27 @@ protected Object doCreateBean(String beanName, BeanDefinition beanDefinition) { registerDisposableBeanIfNecessary(beanName, bean, beanDefinition); if (beanDefinition.isSingleton()) { - addSingleton(beanName, bean); + //如果有代理对象,此处获取代理对象 + Object exposedObject = getSingleton(beanName); + addSingleton(beanName, exposedObject); } return bean; } + protected Object getEarlyBeanReference(String beanName, BeanDefinition beanDefinition, Object bean) { + Object exposedObject = bean; + for (BeanPostProcessor bp : getBeanPostProcessors()) { + if (bp instanceof InstantiationAwareBeanPostProcessor) { + exposedObject = ((InstantiationAwareBeanPostProcessor) bp).getEarlyBeanReference(exposedObject, beanName); + if (exposedObject == null) { + return exposedObject; + } + } + } + + return exposedObject; + } + /** * bean实例化后执行,如果返回false,不执行后续设置属性的逻辑 * diff --git a/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java index 84eb027..408c49f 100644 --- a/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java +++ b/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java @@ -2,10 +2,10 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.config.SingletonBeanRegistry; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -15,19 +15,33 @@ */ public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry { + //一级缓存 private Map singletonObjects = new HashMap<>(); - protected Map earlySingletonObjects = new HashMap<>(); + //二级缓存 + private Map earlySingletonObjects = new HashMap<>(); + + //三级缓存 + private Map> singletonFactories = new HashMap>(); private final Map disposableBeans = new HashMap<>(); @Override public Object getSingleton(String beanName) { - Object bean = singletonObjects.get(beanName); - if (bean == null) { - bean = earlySingletonObjects.get(beanName); + Object singletonObject = singletonObjects.get(beanName); + if (singletonObject == null) { + singletonObject = earlySingletonObjects.get(beanName); + if (singletonObject == null) { + ObjectFactory singletonFactory = singletonFactories.get(beanName); + if (singletonFactory != null) { + singletonObject = singletonFactory.getObject(); + //从三级缓存放进二级缓存 + earlySingletonObjects.put(beanName, singletonObject); + singletonFactories.remove(beanName); + } + } } - return bean; + return singletonObject; } @Override @@ -35,6 +49,10 @@ public void addSingleton(String beanName, Object singletonObject) { singletonObjects.put(beanName, singletonObject); } + protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) { + singletonFactories.put(beanName, singletonFactory); + } + public void registerDisposableBean(String beanName, DisposableBean bean) { disposableBeans.put(beanName, bean); } diff --git a/src/test/java/org/springframework/test/ioc/CircularReferenceWithProxyBeanTest.java b/src/test/java/org/springframework/test/ioc/CircularReferenceWithProxyBeanTest.java index 35618f2..bd87d71 100644 --- a/src/test/java/org/springframework/test/ioc/CircularReferenceWithProxyBeanTest.java +++ b/src/test/java/org/springframework/test/ioc/CircularReferenceWithProxyBeanTest.java @@ -19,8 +19,6 @@ public void testCircularReference() throws Exception { A a = applicationContext.getBean("a", A.class); B b = applicationContext.getBean("b", B.class); - //增加二级缓存不能解决有代理对象时的循环依赖。 - //a被代理,放进二级缓存earlySingletonObjects中的是实例化后的A,而放进一级缓存singletonObjects中的是被代理后的A,实例化b时从earlySingletonObjects获取a,所以b.getA() != a - assertThat(b.getA() != a).isTrue(); + assertThat(b.getA() == a).isTrue(); } } From 26306ec61cb3c1a90dffa5f5ff7323abdadd5f22 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 30 Jan 2021 16:09:34 +0800 Subject: [PATCH 27/81] =?UTF-8?q?=E6=9C=89=E4=BB=A3=E7=90=86=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E6=97=B6=E7=9A=84=E5=BE=AA=E7=8E=AF=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 13 ++++++++++++- .../support/AbstractAutowireCapableBeanFactory.java | 5 +++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index b003e5f..2347bad 100644 --- a/changelog.md +++ b/changelog.md @@ -1433,7 +1433,7 @@ A依赖B,B又依赖A,循环依赖。容器加载时会执行依赖流程: 死循环直至栈溢出。 -解决该问题的关键在于何时将实例化后的bean放进容器中,设置属性前还是设置属性后。现有的执行流程,bean实例化后并且设置属性后会被放进singletonObjects单例缓存中。如果我们调整一下顺序,当bean实例化后就放进singletonObjects单例缓存中,然后再设置属性,就能解决上面的循环依赖问题,执行流程变为: +解决该问题的关键在于何时将实例化后的bean放进容器中,设置属性前还是设置属性后。现有的执行流程,bean实例化后并且设置属性后会被放进singletonObjects单例缓存中。如果我们调整一下顺序,当bean实例化后就放进singletonObjects单例缓存中,提前暴露引用,然后再设置属性,就能解决上面的循环依赖问题,执行流程变为: - 步骤一:getBean(a),检查singletonObjects是否包含a,singletonObjects不包含a,实例化A放进singletonObjects,设置属性b,发现依赖B,尝试getBean(b) - 步骤二:getBean(b),检查singletonObjects是否包含b,singletonObjects不包含a,实例化B放进singletonObjects,设置属性a,发现依赖A,尝试getBean(a) @@ -1452,6 +1452,17 @@ A依赖B,B又依赖A,循环依赖。容器加载时会执行依赖流程: ## 解决循环依赖问题(二):有代理对象 > 分支:circular-reference-with-proxy-bean +解决有代理对象时的循环依赖问题,需要提前暴露代理对象的引用,而不是暴露实例化后的bean的引用(这是上节的遗留问题的原因,应该提前暴露A的代理对象的引用)。 + +spring中用singletonFactories(一般称第三级缓存)解决有代理对象时的循环依赖问题。在实例化后提前暴露代理对象的引用(见AbstractAutowireCapableBeanFactory#doCreateBean方法第6行)。 + +getBean()时依次检查一级缓存singletonObjects、二级缓存earlySingletonObjects和三级缓存singletonFactories中是否包含该bean。如果三级缓存中包含该bean,则挪至二级缓存中,然后直接返回该bean。见AbstractBeanFactory#getBean方法第1行。 + +最后将代理bean放进一级缓存singletonObjects,见AbstractAutowireCapableBeanFactory第104行。 + +单测见CircularReferenceWithProxyBeanTest。 + +## =======================不容易啊,完美撒花======================= diff --git a/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 3a65fd3..da01813 100644 --- a/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -98,12 +98,13 @@ public Object getObject() throws BeansException { //注册有销毁方法的bean registerDisposableBeanIfNecessary(beanName, bean, beanDefinition); + Object exposedObject = bean; if (beanDefinition.isSingleton()) { //如果有代理对象,此处获取代理对象 - Object exposedObject = getSingleton(beanName); + exposedObject = getSingleton(beanName); addSingleton(beanName, exposedObject); } - return bean; + return exposedObject; } protected Object getEarlyBeanReference(String beanName, BeanDefinition beanDefinition, Object bean) { From c380a8b5ff436e84eee89ddc55cdeb22541d3a6a Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 30 Jan 2021 16:14:22 +0800 Subject: [PATCH 28/81] =?UTF-8?q?=E6=9C=89=E4=BB=A3=E7=90=86=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E6=97=B6=E7=9A=84=E5=BE=AA=E7=8E=AF=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 2347bad..be40006 100644 --- a/changelog.md +++ b/changelog.md @@ -1462,7 +1462,7 @@ getBean()时依次检查一级缓存singletonObjects、二级缓存earlySingleto 单测见CircularReferenceWithProxyBeanTest。 -## =======================不容易啊,完美撒花======================= +##

====================不容易啊,完美撒花====================

From fc5e5dc2362cb823bbf684f97c443f7f4a415f67 Mon Sep 17 00:00:00 2001 From: DerekYRC <44155264+DerekYRC@users.noreply.github.com> Date: Sat, 30 Jan 2021 16:21:04 +0800 Subject: [PATCH 29/81] Update README_CN.md --- README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CN.md b/README_CN.md index 7245c72..e00b696 100644 --- a/README_CN.md +++ b/README_CN.md @@ -23,7 +23,7 @@ * [资源和资源加载器](#资源和资源加载器) * [在xml文件中定义bean](#在xml文件中定义bean) * [容器扩展机制BeanFactoryPostProcess和BeanPostProcessor](#容器扩展机制BeanFactoryPostProcess和BeanPostProcessor) - * [应用上下文ApplicationContext](#应用上下文ApplicationContext) + * [应用上下文ApplicationContext](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E5%BA%94%E7%94%A8%E4%B8%8A%E4%B8%8B%E6%96%87ApplicationContext) * [bean的初始化和销毁方法](#bean的初始化和销毁方法) * [Aware接口](#Aware接口) * [bean作用域,增加prototype的支持](#bean作用域,增加prototype的支持) From d66a48f6b5cb324f3e93f4460798e8280054551f Mon Sep 17 00:00:00 2001 From: DerekYRC <44155264+DerekYRC@users.noreply.github.com> Date: Sat, 30 Jan 2021 16:22:48 +0800 Subject: [PATCH 30/81] Update README_CN.md --- README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CN.md b/README_CN.md index e00b696..5e6c37e 100644 --- a/README_CN.md +++ b/README_CN.md @@ -23,7 +23,7 @@ * [资源和资源加载器](#资源和资源加载器) * [在xml文件中定义bean](#在xml文件中定义bean) * [容器扩展机制BeanFactoryPostProcess和BeanPostProcessor](#容器扩展机制BeanFactoryPostProcess和BeanPostProcessor) - * [应用上下文ApplicationContext](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E5%BA%94%E7%94%A8%E4%B8%8A%E4%B8%8B%E6%96%87ApplicationContext) + * [应用上下文ApplicationContext](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E5%BA%94%E7%94%A8%E4%B8%8A%E4%B8%8B%E6%96%87ApplicationContext){:target="_blank"} * [bean的初始化和销毁方法](#bean的初始化和销毁方法) * [Aware接口](#Aware接口) * [bean作用域,增加prototype的支持](#bean作用域,增加prototype的支持) From 2697e295d539a4cfda34ca9f5307fe3c515ff4ed Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 30 Jan 2021 16:36:51 +0800 Subject: [PATCH 31/81] update readme.md --- README_CN.md | 24 ++++++++++++------------ changelog.md | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README_CN.md b/README_CN.md index 5e6c37e..9352a0a 100644 --- a/README_CN.md +++ b/README_CN.md @@ -15,18 +15,18 @@ ## 功能 #### 基础篇 * [IoC](#Ioc) - * [实现一个简单的容器](#实现一个简单的容器) - * [BeanDefinition和BeanDefinitionRegistry](#BeanDefinition和BeanDefinitionRegistry) - * [Bean实例化策略InstantiationStrategy](#Bean实例化策略InstantiationStrategy) - * [为bean填充属性](#为bean填充属性) - * [为bean注入bean](#为bean注入bean) - * [资源和资源加载器](#资源和资源加载器) - * [在xml文件中定义bean](#在xml文件中定义bean) - * [容器扩展机制BeanFactoryPostProcess和BeanPostProcessor](#容器扩展机制BeanFactoryPostProcess和BeanPostProcessor) - * [应用上下文ApplicationContext](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E5%BA%94%E7%94%A8%E4%B8%8A%E4%B8%8B%E6%96%87ApplicationContext){:target="_blank"} - * [bean的初始化和销毁方法](#bean的初始化和销毁方法) - * [Aware接口](#Aware接口) - * [bean作用域,增加prototype的支持](#bean作用域,增加prototype的支持) + * [实现一个简单的容器](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84bean%E5%AE%B9%E5%99%A8) + * [BeanDefinition和BeanDefinitionRegistry](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#BeanDefinition%E5%92%8CBeanDefinitionRegistry) + * [Bean实例化策略InstantiationStrategy](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#Bean%E5%AE%9E%E4%BE%8B%E5%8C%96%E7%AD%96%E7%95%A5InstantiationStrategy) + * [为bean填充属性](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E4%B8%BAbean%E5%A1%AB%E5%85%85%E5%B1%9E%E6%80%A7) + * [为bean注入bean](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E4%B8%BAbean%E6%B3%A8%E5%85%A5bean) + * [资源和资源加载器](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E8%B5%84%E6%BA%90%E5%92%8C%E8%B5%84%E6%BA%90%E5%8A%A0%E8%BD%BD%E5%99%A8) + * [在xml文件中定义bean](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E5%9C%A8xml%E6%96%87%E4%BB%B6%E4%B8%AD%E5%AE%9A%E4%B9%89bean) + * [容器扩展机制BeanFactoryPostProcess和BeanPostProcessor](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#BeanFactoryPostProcess%E5%92%8CBeanPostProcessor) + * [应用上下文ApplicationContext](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E5%BA%94%E7%94%A8%E4%B8%8A%E4%B8%8B%E6%96%87ApplicationContext) + * [bean的初始化和销毁方法](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bean%E7%9A%84%E5%88%9D%E5%A7%8B%E5%8C%96%E5%92%8C%E9%94%80%E6%AF%81%E6%96%B9%E6%B3%95) + * [Aware接口](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#Aware%E6%8E%A5%E5%8F%A3) + * [bean作用域,增加prototype的支持](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bean%E4%BD%9C%E7%94%A8%E5%9F%9F) * [FactoryBean](#FactoryBean) * [容器事件和事件监听器](#容器事件和事件监听器) * [AOP](#AOP) diff --git a/changelog.md b/changelog.md index be40006..e03ed0b 100644 --- a/changelog.md +++ b/changelog.md @@ -514,7 +514,7 @@ public class AwareInterfaceTest { } ``` -## bean作用域,增加prototype的支持 +## [bean作用域,增加prototype的支持](#bean作用域) > 分支:prototype-bean 每次向容器获取prototype作用域bean时,容器都会创建一个新的实例。在BeanDefinition中增加描述bean的作用域的字段scope/singleton/prototype,创建prototype作用域bean时(AbstractAutowireCapableBeanFactory#doCreateBean),不往singletonObjects中增加该bean。prototype作用域bean不执行销毁方法,查看AbstractAutowireCapableBeanFactory#registerDisposableBeanIfNecessary方法。 From 1a50a46edd213c5a11ad41ef25af8e08ae70d930 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 30 Jan 2021 16:49:48 +0800 Subject: [PATCH 32/81] update readme.md --- README_CN.md | 6 +++--- changelog.md | 30 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/README_CN.md b/README_CN.md index 9352a0a..98a6b87 100644 --- a/README_CN.md +++ b/README_CN.md @@ -15,8 +15,8 @@ ## 功能 #### 基础篇 * [IoC](#Ioc) - * [实现一个简单的容器](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84bean%E5%AE%B9%E5%99%A8) - * [BeanDefinition和BeanDefinitionRegistry](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#BeanDefinition%E5%92%8CBeanDefinitionRegistry) + * [实现一个简单的容器](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#最简单的bean容器) + * [BeanDefinition和BeanDefinitionRegistry](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#BeanDefinition和BeanDefinitionRegistry) * [Bean实例化策略InstantiationStrategy](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#Bean%E5%AE%9E%E4%BE%8B%E5%8C%96%E7%AD%96%E7%95%A5InstantiationStrategy) * [为bean填充属性](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E4%B8%BAbean%E5%A1%AB%E5%85%85%E5%B1%9E%E6%80%A7) * [为bean注入bean](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E4%B8%BAbean%E6%B3%A8%E5%85%A5bean) @@ -26,7 +26,7 @@ * [应用上下文ApplicationContext](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E5%BA%94%E7%94%A8%E4%B8%8A%E4%B8%8B%E6%96%87ApplicationContext) * [bean的初始化和销毁方法](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bean%E7%9A%84%E5%88%9D%E5%A7%8B%E5%8C%96%E5%92%8C%E9%94%80%E6%AF%81%E6%96%B9%E6%B3%95) * [Aware接口](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#Aware%E6%8E%A5%E5%8F%A3) - * [bean作用域,增加prototype的支持](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bean%E4%BD%9C%E7%94%A8%E5%9F%9F) + * [bean作用域,增加prototype的支持](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bean作用域增加prototype的支持) * [FactoryBean](#FactoryBean) * [容器事件和事件监听器](#容器事件和事件监听器) * [AOP](#AOP) diff --git a/changelog.md b/changelog.md index e03ed0b..a7e3621 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,5 @@ - # 基础篇:IoC - ## 最简单的bean容器 + # [基础篇:IoC](#基础篇:IoC) + ## [最简单的bean容器](#最简单的bean容器) > 分支:simple-bean-container 定义一个简单的bean容器BeanFactory,内部包含一个map用以保存bean,只有注册bean和获取bean两个方法 @@ -39,7 +39,7 @@ public class SimpleBeanContainerTest { } ``` -## BeanDefinition和BeanDefinitionRegistry +## [BeanDefinition和BeanDefinitionRegistry](#BeanDefinition和BeanDefinitionRegistry) > 分支:bean-definition-and-bean-definition-registry 主要增加如下类: @@ -74,7 +74,7 @@ class HelloService { } ``` -## Bean实例化策略InstantiationStrategy +## [Bean实例化策略InstantiationStrategy](#Bean实例化策略InstantiationStrategy) > 分支:instantiation-strategy 现在bean是在AbstractAutowireCapableBeanFactory.doCreateBean方法中用beanClass.newInstance()来实例化,仅适用于bean有无参构造函数的情况。 @@ -85,7 +85,7 @@ class HelloService { - SimpleInstantiationStrategy,使用bean的构造函数来实例化 - CglibSubclassingInstantiationStrategy,使用CGLIB动态生成子类 -## 为bean填充属性 +## [为bean填充属性](#为bean填充属性) > 分支:populate-bean-with-property-values 在BeanDefinition中增加和bean属性对应的PropertyValues,实例化bean之后,为bean填充属性(AbstractAutowireCapableBeanFactory#applyPropertyValues)。 @@ -111,7 +111,7 @@ public class PopulateBeanWithPropertyValuesTest { } ``` -## 为bean注入bean +## [为bean注入bean](#为bean注入bean) > 分支:populate-bean-with-bean 增加BeanReference类,包装一个bean对另一个bean的引用。实例化beanA后填充属性时,若PropertyValue#value为BeanReference,引用beanB,则先去实例化beanB。 @@ -176,7 +176,7 @@ public class PopulateBeanWithPropertyValuesTest { } ``` -## 资源和资源加载器 +## [资源和资源加载器](#资源和资源加载器) > 分支:resource-and-resource-loader Resource是资源的抽象和访问接口,简单写了三个实现类 @@ -222,7 +222,7 @@ public class ResourceAndResourceLoaderTest { } ``` -## 在xml文件中定义bean +## [在xml文件中定义bean](#在xml文件中定义bean) > 分支:xml-file-define-bean 有了资源加载器,就可以在xml格式配置文件中声明式地定义bean的信息,资源加载器读取xml文件,解析出bean的信息,然后往容器中注册BeanDefinition。 @@ -280,7 +280,7 @@ public class XmlFileDefineBeanTest { } ``` -## BeanFactoryPostProcess和BeanPostProcessor +## [BeanFactoryPostProcess和BeanPostProcessor](#BeanFactoryPostProcess和BeanPostProcessor) > 分支:bean-factory-post-processor-and-bean-post-processor BeanFactoryPostProcess和BeanPostProcessor是spring框架中具有重量级地位的两个接口,理解了这两个接口的作用,基本就理解spring的核心原理了。为了降低理解难度分两个小节实现。 @@ -345,7 +345,7 @@ public class BeanFactoryProcessorAndBeanPostProcessorTest { } ``` -## 应用上下文ApplicationContext +## [应用上下文ApplicationContext](#应用上下文ApplicationContext) > 分支:application-context 应用上下文ApplicationContext是spring中较之于BeanFactory更为先进的IOC容器,ApplicationContext除了拥有BeanFactory的所有功能外,还支持特殊类型bean如上一节中的BeanFactoryPostProcessor和BeanPostProcessor的自动识别、资源加载、容器事件和监听器、国际化支持、单例bean自动初始化等。 @@ -360,7 +360,7 @@ BeanFactory是spring的基础设施,面向spring本身;而ApplicationContext 测试:见ApplicationContextTest -## bean的初始化和销毁方法 +## [bean的初始化和销毁方法](#bean的初始化和销毁方法) > 分支:init-and-destroy-method 在spring中,定义bean的初始化和销毁方法有三种方法: @@ -444,7 +444,7 @@ public class InitAndDestoryMethodTest { } ``` -## Aware接口 +## [Aware接口](#Aware接口) > 分支:aware-interface Aware是感知、意识的意思,Aware接口是标记性接口,其实现子类能感知容器相关的对象。常用的Aware接口有BeanFactoryAware和ApplicationContextAware,分别能让其实现者感知所属的BeanFactory和ApplicationContext。 @@ -514,7 +514,7 @@ public class AwareInterfaceTest { } ``` -## [bean作用域,增加prototype的支持](#bean作用域) +## [bean作用域,增加prototype的支持](#bean作用域增加prototype的支持) > 分支:prototype-bean 每次向容器获取prototype作用域bean时,容器都会创建一个新的实例。在BeanDefinition中增加描述bean的作用域的字段scope/singleton/prototype,创建prototype作用域bean时(AbstractAutowireCapableBeanFactory#doCreateBean),不往singletonObjects中增加该bean。prototype作用域bean不执行销毁方法,查看AbstractAutowireCapableBeanFactory#registerDisposableBeanIfNecessary方法。 @@ -555,7 +555,7 @@ public class PrototypeBeanTest { } ``` -## FactoryBean +## [FactoryBean](#FactoryBean) > 分支:factory-bean FactoryBean是一种特殊的bean,当向容器获取该bean时,容器不是返回其本身,而是返回其FactoryBean#getObject方法的返回值,可通过编码方式定义复杂的bean。 @@ -616,7 +616,7 @@ public class FactoryBeanTest { } ``` -## 容器事件和事件监听器 +## [容器事件和事件监听器](#容器事件和事件监听器) > 分支:event-and-event-listener ApplicationContext容器提供了完善的时间发布和时间监听功能。 From b29bc9b0d69bbd4fcf92973bcb32aff21fe3b0c1 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 30 Jan 2021 16:55:26 +0800 Subject: [PATCH 33/81] update readme.md --- README_CN.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README_CN.md b/README_CN.md index 98a6b87..93fdcb5 100644 --- a/README_CN.md +++ b/README_CN.md @@ -17,18 +17,18 @@ * [IoC](#Ioc) * [实现一个简单的容器](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#最简单的bean容器) * [BeanDefinition和BeanDefinitionRegistry](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#BeanDefinition和BeanDefinitionRegistry) - * [Bean实例化策略InstantiationStrategy](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#Bean%E5%AE%9E%E4%BE%8B%E5%8C%96%E7%AD%96%E7%95%A5InstantiationStrategy) - * [为bean填充属性](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E4%B8%BAbean%E5%A1%AB%E5%85%85%E5%B1%9E%E6%80%A7) - * [为bean注入bean](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E4%B8%BAbean%E6%B3%A8%E5%85%A5bean) - * [资源和资源加载器](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E8%B5%84%E6%BA%90%E5%92%8C%E8%B5%84%E6%BA%90%E5%8A%A0%E8%BD%BD%E5%99%A8) - * [在xml文件中定义bean](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E5%9C%A8xml%E6%96%87%E4%BB%B6%E4%B8%AD%E5%AE%9A%E4%B9%89bean) - * [容器扩展机制BeanFactoryPostProcess和BeanPostProcessor](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#BeanFactoryPostProcess%E5%92%8CBeanPostProcessor) - * [应用上下文ApplicationContext](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#%E5%BA%94%E7%94%A8%E4%B8%8A%E4%B8%8B%E6%96%87ApplicationContext) - * [bean的初始化和销毁方法](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bean%E7%9A%84%E5%88%9D%E5%A7%8B%E5%8C%96%E5%92%8C%E9%94%80%E6%AF%81%E6%96%B9%E6%B3%95) - * [Aware接口](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#Aware%E6%8E%A5%E5%8F%A3) + * [Bean实例化策略InstantiationStrategy](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#Bean实例化策略InstantiationStrategy) + * [为bean填充属性](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#为bean填充属性) + * [为bean注入bean](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#为bean注入bean) + * [资源和资源加载器](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#资源和资源加载器) + * [在xml文件中定义bean](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#在xml文件中定义bean) + * [容器扩展机制BeanFactoryPostProcess和BeanPostProcessor](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#BeanFactoryPostProcess和BeanPostProcessor) + * [应用上下文ApplicationContext](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#应用上下文ApplicationContext) + * [bean的初始化和销毁方法](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bean的初始化和销毁方法) + * [Aware接口](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#Aware接口) * [bean作用域,增加prototype的支持](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bean作用域增加prototype的支持) - * [FactoryBean](#FactoryBean) - * [容器事件和事件监听器](#容器事件和事件监听器) + * [FactoryBean](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#FactoryBean) + * [容器事件和事件监听器](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#容器事件和事件监听器) * [AOP](#AOP) * [切点表达式](#切点表达式) * [基于JDK的动态代理](#基于JDK的动态代理) From a2dd47267575a0bee17dd5ffada2d55afcd043e7 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 30 Jan 2021 17:07:32 +0800 Subject: [PATCH 34/81] update readme.md --- README_CN.md | 36 ++++++++++++++++++------------------ changelog.md | 18 +++++++++--------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/README_CN.md b/README_CN.md index 93fdcb5..51e3928 100644 --- a/README_CN.md +++ b/README_CN.md @@ -14,7 +14,7 @@ ## 功能 #### 基础篇 -* [IoC](#Ioc) +* [IoC](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#基础篇IoC) * [实现一个简单的容器](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#最简单的bean容器) * [BeanDefinition和BeanDefinitionRegistry](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#BeanDefinition和BeanDefinitionRegistry) * [Bean实例化策略InstantiationStrategy](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#Bean实例化策略InstantiationStrategy) @@ -29,30 +29,30 @@ * [bean作用域,增加prototype的支持](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bean作用域增加prototype的支持) * [FactoryBean](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#FactoryBean) * [容器事件和事件监听器](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#容器事件和事件监听器) -* [AOP](#AOP) - * [切点表达式](#切点表达式) - * [基于JDK的动态代理](#基于JDK的动态代理) - * [基于CGLIB的动态代理](#基于CGLIB的动态代理) - * [AOP代理工厂ProxyFactory](#AOP代理工厂ProxyFactory) - * [几种常用的Advice: BeforeAdvice/AfterAdvice/AfterReturningAdvice/ThrowsAdvice](#几种常用的Advice) - * [PointcutAdvisor:Pointcut和Advice的组合](#PointcutAdvisor:Pointcut和Advice的组合) - * [动态代理融入bean生命周期](#动态代理融入bean生命周期) +* [AOP](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#基础篇AOP) + * [切点表达式](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#切点表达式) + * [基于JDK的动态代理](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#基于JDK的动态代理) + * [基于CGLIB的动态代理](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#基于CGLIB的动态代理) + * [AOP代理工厂ProxyFactory](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#AOP代理工厂) + * [几种常用的Advice: BeforeAdvice/AfterAdvice/AfterReturningAdvice/ThrowsAdvice](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#几种常用的AdviceBeforeAdviceAfterAdviceAfterReturningAdviceThrowsAdvice) + * [PointcutAdvisor:Pointcut和Advice的组合](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#PointcutAdvisorPointcut和Advice的组合) + * [动态代理融入bean生命周期](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#动态代理融入bean生命周期) #### 扩展篇 -* [PropertyPlaceholderConfigurer](#PropertyPlaceholderConfigurer) -* [包扫描](#包扫描) -* [@Value注解](#@Value注解) -* [基于注解@Autowired的依赖注入](#基于注解@Autowired的依赖注入) -* [类型转换(一)](#类型转换一) -* [类型转换(二)](#类型转换二) +* [PropertyPlaceholderConfigurer](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) +* [包扫描](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) +* [@Value注解](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) +* [基于注解@Autowired的依赖注入](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) +* [类型转换(一)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) +* [类型转换(二)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) #### 高级篇 -* [解决循环依赖问题(一):没有代理对象](#解决循环依赖问题(一):没有代理对象) -* [解决循环依赖问题(二):有代理对象](#解决循环依赖问题(二):有代理对象) +* [解决循环依赖问题(一):没有代理对象](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) +* [解决循环依赖问题(二):有代理对象](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) #### bug fix -* [没有为代理bean设置属性(discovered and fixed by kerwin89)](#没有为代理bean设置属性) +* [没有为代理bean设置属性(discovered and fixed by kerwin89)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) ## 使用方法 每个功能点对应一个分支,切换到功能点对应的分支了解新增的功能,增量改动点在[changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md)文件中描述。 diff --git a/changelog.md b/changelog.md index a7e3621..e3308b6 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,4 @@ - # [基础篇:IoC](#基础篇:IoC) + # [基础篇:IoC](#基础篇IoC) ## [最简单的bean容器](#最简单的bean容器) > 分支:simple-bean-container @@ -661,9 +661,9 @@ org.springframework.test.common.event.CustomEventListener org.springframework.test.common.event.ContextClosedEventListener ``` -# 基础篇:AOP +# [基础篇:AOP](#基础篇AOP) -## 切点表达式 +## [切点表达式](#切点表达式) > 分支:pointcut-expression Joinpoint,织入点,指需要执行代理操作的某个类的某个方法(仅支持方法级别的JoinPoint);Pointcut是JoinPoint的表述方式,能捕获JoinPoint。 @@ -694,7 +694,7 @@ public class PointcutExpressionTest { } ``` -## 基于JDK的动态代理 +## [基于JDK的动态代理](#基于JDK的动态代理) > 分支:jdk-dynamic-proxy AopProxy是获取代理对象的抽象接口,JdkDynamicAopProxy的基于JDK动态代理的具体实现。TargetSource,被代理对象的封装。MethodInterceptor,方法拦截器,是AOP Alliance的"公民",顾名思义,可以拦截方法,可在被代理执行的方法前后增加代理行为。 @@ -721,7 +721,7 @@ public class DynamicProxyTest { } ``` -## 基于CGLIB的动态代理 +## [基于CGLIB的动态代理](#基于CGLIB的动态代理) > 分支:cglib-dynamic-proxy 基于CGLIB的动态代理实现逻辑也比较简单,查看CglibAopProxy。与基于JDK的动态代理在运行期间为接口生成对象的代理对象不同,基于CGLIB的动态代理能在运行期间动态构建字节码的class文件,为类生成子类,因此被代理类不需要继承自任何接口。 @@ -753,7 +753,7 @@ public class DynamicProxyTest { } ``` -## AOP代理工厂 +## [AOP代理工厂](#AOP代理工厂) > 分支:proxy-factory 增加AOP代理工厂ProxyFactory,由AdvisedSupport#proxyTargetClass属性决定使用JDK动态代理还是CGLIB动态代理。 @@ -792,7 +792,7 @@ public class DynamicProxyTest { } ``` -## 几种常用的Advice:BeforeAdvice/AfterAdvice/AfterReturningAdvice/ThrowsAdvice... +## [几种常用的Advice:BeforeAdvice/AfterAdvice/AfterReturningAdvice/ThrowsAdvice...](#几种常用的AdviceBeforeAdviceAfterAdviceAfterReturningAdviceThrowsAdvice) > 分支: common-advice Spring将AOP联盟中的Advice细化出各种类型的Advice,常用的有BeforeAdvice/AfterAdvice/AfterReturningAdvice/ThrowsAdvice,我们可以通过扩展MethodInterceptor来实现。 @@ -842,7 +842,7 @@ public class DynamicProxyTest { } ``` -## PointcutAdvisor:Pointcut和Advice的组合 +## PointcutAdvisor:Pointcut和Advice的组合(#PointcutAdvisorPointcut和Advice的组合) > 分支:pointcut-advisor Advisor是包含一个Pointcut和一个Advice的组合,Pointcut用于捕获JoinPoint,Advice决定在JoinPoint执行某种操作。实现了一个支持aspectj表达式的AspectJExpressionPointcutAdvisor。 @@ -878,7 +878,7 @@ public class DynamicProxyTest { } ``` -## 动态代理融入bean生命周期 +## [动态代理融入bean生命周期](#动态代理融入bean生命周期) > 分支:auto-proxy 结合前面讲解的bean的生命周期,BeanPostProcessor处理阶段可以修改和替换bean,正好可以在此阶段返回代理对象替换原对象。不过我们引入一种特殊的BeanPostProcessor——InstantiationAwareBeanPostProcessor,如果InstantiationAwareBeanPostProcessor处理阶段返回代理对象,会导致短路,不会继续走原来的创建bean的流程,具体实现查看AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation。 From 385492e87b857db5e4b6678593e0111f2d0c5d64 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 30 Jan 2021 20:32:48 +0800 Subject: [PATCH 35/81] update readme.md --- README_CN.md | 18 +++++++++--------- changelog.md | 20 ++++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/README_CN.md b/README_CN.md index 51e3928..2e8da5b 100644 --- a/README_CN.md +++ b/README_CN.md @@ -40,19 +40,19 @@ #### 扩展篇 -* [PropertyPlaceholderConfigurer](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) -* [包扫描](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) -* [@Value注解](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) -* [基于注解@Autowired的依赖注入](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) -* [类型转换(一)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) -* [类型转换(二)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) +* [PropertyPlaceholderConfigurer](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#PropertyPlaceholderConfigurer) +* [包扫描](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#包扫描) +* [@Value注解](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#Value注解) +* [基于注解@Autowired的依赖注入](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#Autowired注解) +* [类型转换(一)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#类型转换一) +* [类型转换(二)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#类型转换二) #### 高级篇 -* [解决循环依赖问题(一):没有代理对象](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) -* [解决循环依赖问题(二):有代理对象](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) +* [解决循环依赖问题(一):没有代理对象](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#解决循环依赖问题一没有代理对象) +* [解决循环依赖问题(二):有代理对象](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#解决循环依赖问题二有代理对象) #### bug fix -* [没有为代理bean设置属性(discovered and fixed by kerwin89)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) +* [没有为代理bean设置属性(discovered and fixed by kerwin89)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bug-fix没有为代理bean设置属性discovered-and-fixed-by-kerwin89) ## 使用方法 每个功能点对应一个分支,切换到功能点对应的分支了解新增的功能,增量改动点在[changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md)文件中描述。 diff --git a/changelog.md b/changelog.md index e3308b6..d368368 100644 --- a/changelog.md +++ b/changelog.md @@ -842,7 +842,7 @@ public class DynamicProxyTest { } ``` -## PointcutAdvisor:Pointcut和Advice的组合(#PointcutAdvisorPointcut和Advice的组合) +## [PointcutAdvisor:Pointcut和Advice的组合](#PointcutAdvisorPointcut和Advice的组合) > 分支:pointcut-advisor Advisor是包含一个Pointcut和一个Advice的组合,Pointcut用于捕获JoinPoint,Advice决定在JoinPoint执行某种操作。实现了一个支持aspectj表达式的AspectJExpressionPointcutAdvisor。 @@ -935,7 +935,7 @@ public class AutoProxyTest { # 扩展篇 -## PropertyPlaceholderConfigurer +## [PropertyPlaceholderConfigurer](#PropertyPlaceholderConfigurer) > 分支:property-placeholder-configurer 经常需要将配置信息配置在properties文件中,然后在XML文件中以占位符的方式引用。 @@ -980,7 +980,7 @@ public class PropertyPlaceholderConfigurerTest { } ``` -## 包扫描 +## [包扫描](#包扫描) > 分支:package-scan 结合bean的生命周期,包扫描只不过是扫描特定注解的类,提取类的相关信息组装成BeanDefinition注册到容器中。 @@ -1022,7 +1022,7 @@ public class PackageScanTest { } ``` -## @Value注解 +## [@Value注解](#Value注解) > 分支:value-annotation 注解@Value和@Autowired通过BeanPostProcessor处理。InstantiationAwareBeanPostProcessor增加postProcessPropertyValues方法,在bean实例化之后设置属性之前执行,查看AbstractAutowireCapableBeanFactory#doCreateBean方法。 @@ -1075,7 +1075,7 @@ public class ValueAnnotationTest { ``` -## @Autowired注解 +## [@Autowired注解](#Autowired注解) > 分支:autowired-annotation @Autowired注解的处理见AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues @@ -1121,7 +1121,7 @@ public class AutowiredAnnotationTest { } } ``` -## bug fix:没有为代理bean设置属性(discovered and fixed by @kerwin89) +## [bug fix:没有为代理bean设置属性(discovered and fixed by @kerwin89)](#bug-fix没有为代理bean设置属性discovered-and-fixed-by-kerwin89) > 分支: populate-proxy-bean-with-property-values 问题现象:没有为代理bean设置属性 @@ -1196,7 +1196,7 @@ public class AutoProxyTest { } ``` -## 类型转换(一) +## [类型转换(一)](#类型转换一) > 分支:type-conversion-first-part spring在org.springframework.core.convert.converter包中定义了三种类型转换器接口:Converter、ConverterFactory、GenericConverter。 @@ -1312,7 +1312,7 @@ ConversionService是类型转换体系的核心接口,将以上三种类型转 测试见TypeConversionFirstPartTest。 -## 类型转换(二) +## [类型转换(二)](#类型转换二) > 分支:type-conversion-second-part 上一节实现了spring中的类型转换体系,本节将类型转换的能力整合到容器中。 @@ -1390,7 +1390,7 @@ public class TypeConversionSecondPartTest { } ``` -## 解决循环依赖问题(一):没有代理对象 +## [解决循环依赖问题(一):没有代理对象](#解决循环依赖问题一没有代理对象) > 分支:circular-reference-without-proxy-bean 虽然放在高级篇,其实解决循环依赖问题的方法非常简单。 @@ -1449,7 +1449,7 @@ A依赖B,B又依赖A,循环依赖。容器加载时会执行依赖流程: 下一节填坑。 -## 解决循环依赖问题(二):有代理对象 +## [解决循环依赖问题(二):有代理对象](#解决循环依赖问题二有代理对象) > 分支:circular-reference-with-proxy-bean 解决有代理对象时的循环依赖问题,需要提前暴露代理对象的引用,而不是暴露实例化后的bean的引用(这是上节的遗留问题的原因,应该提前暴露A的代理对象的引用)。 From 2086a0cd76d229b9ed6565980ed921f78d3f3bfe Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 30 Jan 2021 20:38:56 +0800 Subject: [PATCH 36/81] update readme.md --- README_CN.md | 8 ++++---- changelog.md | 50 +------------------------------------------------- 2 files changed, 5 insertions(+), 53 deletions(-) diff --git a/README_CN.md b/README_CN.md index 2e8da5b..32cc875 100644 --- a/README_CN.md +++ b/README_CN.md @@ -35,15 +35,15 @@ * [基于CGLIB的动态代理](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#基于CGLIB的动态代理) * [AOP代理工厂ProxyFactory](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#AOP代理工厂) * [几种常用的Advice: BeforeAdvice/AfterAdvice/AfterReturningAdvice/ThrowsAdvice](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#几种常用的AdviceBeforeAdviceAfterAdviceAfterReturningAdviceThrowsAdvice) - * [PointcutAdvisor:Pointcut和Advice的组合](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#PointcutAdvisorPointcut和Advice的组合) + * [PointcutAdvisor:Pointcut和Advice的组合](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#pointcutadvisorpointcut和advice的组合) * [动态代理融入bean生命周期](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#动态代理融入bean生命周期) #### 扩展篇 -* [PropertyPlaceholderConfigurer](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#PropertyPlaceholderConfigurer) +* [PropertyPlaceholderConfigurer](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#propertyplaceholderconfigurer) * [包扫描](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#包扫描) -* [@Value注解](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#Value注解) -* [基于注解@Autowired的依赖注入](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#Autowired注解) +* [@Value注解](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#value注解) +* [基于注解@Autowired的依赖注入](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#autowired注解) * [类型转换(一)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#类型转换一) * [类型转换(二)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#类型转换二) diff --git a/changelog.md b/changelog.md index d368368..ba551a4 100644 --- a/changelog.md +++ b/changelog.md @@ -1462,52 +1462,4 @@ getBean()时依次检查一级缓存singletonObjects、二级缓存earlySingleto 单测见CircularReferenceWithProxyBeanTest。 -##

====================不容易啊,完美撒花====================

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +##

====================不容易啊,完美撒花====================

\ No newline at end of file From a41ad3e759912bdb61a8d5e3fb83cfac60ae71b1 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 30 Jan 2021 20:54:32 +0800 Subject: [PATCH 37/81] update changelog.md --- changelog.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index ba551a4..87aa84c 100644 --- a/changelog.md +++ b/changelog.md @@ -933,7 +933,7 @@ public class AutoProxyTest { } ``` -# 扩展篇 +# [扩展篇](#扩展篇) ## [PropertyPlaceholderConfigurer](#PropertyPlaceholderConfigurer) > 分支:property-placeholder-configurer @@ -1390,6 +1390,8 @@ public class TypeConversionSecondPartTest { } ``` +# [高级篇](#高级篇) + ## [解决循环依赖问题(一):没有代理对象](#解决循环依赖问题一没有代理对象) > 分支:circular-reference-without-proxy-bean From 75e8309fbe9a6261345e0882efd6093078b1cdcb Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 30 Jan 2021 20:58:50 +0800 Subject: [PATCH 38/81] update changelog.md --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 87aa84c..224d309 100644 --- a/changelog.md +++ b/changelog.md @@ -1447,7 +1447,7 @@ A依赖B,B又依赖A,循环依赖。容器加载时会执行依赖流程: 单测见CircularReferenceWithoutProxyBeanTest#testCircularReference。 -增加二级缓存,不能解决有代理对象时的循环依赖。原因是放进二级缓存earlySingletonObjects中的bean是实例化后的bean,而放进一级缓存singletonObjects中的bean是代理对象(代理对象在BeanPostProcessor#postProcessAfterInitialization中返回),两个缓存中的bean不一致。比如上面的例子,如果A被代理,那么B拿到的a是实例化后的A,而a是被代理后的对象,即b.getA() != a,见单测 +增加二级缓存,不能解决有代理对象时的循环依赖。原因是放进二级缓存earlySingletonObjects中的bean是实例化后的bean,而放进一级缓存singletonObjects中的bean是代理对象(代理对象在BeanPostProcessor#postProcessAfterInitialization中返回),两个缓存中的bean不一致。比如上面的例子,如果A被代理,那么B拿到的a是实例化后的A,而a是被代理后的对象,即b.getA() != a,见单测CircularReferenceWithProxyBeanTest。 下一节填坑。 From cf3d98b796d13390f5d145683a2e73604200d141 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sun, 31 Jan 2021 10:39:38 +0800 Subject: [PATCH 39/81] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E6=9C=89=E4=BB=A3?= =?UTF-8?q?=E7=90=86=E5=AF=B9=E8=B1=A1=E6=97=B6=E7=9A=84=E5=BE=AA=E7=8E=AF?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../beans/factory/support/DefaultSingletonBeanRegistry.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java index 408c49f..4338dd5 100644 --- a/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java +++ b/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java @@ -47,6 +47,8 @@ public Object getSingleton(String beanName) { @Override public void addSingleton(String beanName, Object singletonObject) { singletonObjects.put(beanName, singletonObject); + earlySingletonObjects.remove(beanName); + singletonFactories.remove(beanName); } protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) { From 22e329b139bffa14a7127f1f3b06c928d9165a68 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 27 Feb 2021 16:46:51 +0800 Subject: [PATCH 40/81] =?UTF-8?q?=E6=8F=90=E5=89=8D=E5=AE=9E=E4=BE=8B?= =?UTF-8?q?=E5=8C=96=E5=8D=95=E4=BE=8Bbean=E6=97=B6=E5=AF=B9beanDefinition?= =?UTF-8?q?=E7=9A=84scope=E6=B7=BB=E5=8A=A0=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../beans/factory/support/DefaultListableBeanFactory.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 55ea11b..d4eb956 100644 --- a/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -72,6 +72,10 @@ public String[] getBeanDefinitionNames() { @Override public void preInstantiateSingletons() throws BeansException { - beanDefinitionMap.keySet().forEach(this::getBean); + beanDefinitionMap.forEach((beanName, beanDefinition) -> { + if(beanDefinition.isSingleton()){ + getBean(beanName); + } + }); } } From 3b79ca8c119162b66b4f465cb3c2609bc6b3c76f Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Wed, 7 Apr 2021 23:04:57 +0800 Subject: [PATCH 41/81] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E4=B9=A6=E5=86=99=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/changelog.md b/changelog.md index 224d309..12d1284 100644 --- a/changelog.md +++ b/changelog.md @@ -43,11 +43,11 @@ public class SimpleBeanContainerTest { > 分支:bean-definition-and-bean-definition-registry 主要增加如下类: -- BeanDefinition,顾名思义,用于定义bean信息的类,包含bean的class类型、构造参数、属性值等信息,每个bean对应一个BeanDefinition的实例。简化BeanDefition仅包含bean的class类型。 -- BeanDefinitionRegistry,BeanDefinition注册表接口,定义注册BeanDefintion的方法。 +- BeanDefinition,顾名思义,用于定义bean信息的类,包含bean的class类型、构造参数、属性值等信息,每个bean对应一个BeanDefinition的实例。简化BeanDefinition仅包含bean的class类型。 +- BeanDefinitionRegistry,BeanDefinition注册表接口,定义注册BeanDefinition的方法。 - SingletonBeanRegistry及其实现类DefaultSingletonBeanRegistry,定义添加和获取单例bean的方法。 -bean容器作为BeanDefinitionRegistry和SingletonBeanRegistry的实现类,具备两者的能力。向bean容器中注册BeanDefintion后,使用bean时才会实例化。 +bean容器作为BeanDefinitionRegistry和SingletonBeanRegistry的实现类,具备两者的能力。向bean容器中注册BeanDefinition后,使用bean时才会实例化。 ![](./assets/bean-definition-and-bean-definition-registry.png) @@ -1438,7 +1438,7 @@ A依赖B,B又依赖A,循环依赖。容器加载时会执行依赖流程: 解决该问题的关键在于何时将实例化后的bean放进容器中,设置属性前还是设置属性后。现有的执行流程,bean实例化后并且设置属性后会被放进singletonObjects单例缓存中。如果我们调整一下顺序,当bean实例化后就放进singletonObjects单例缓存中,提前暴露引用,然后再设置属性,就能解决上面的循环依赖问题,执行流程变为: - 步骤一:getBean(a),检查singletonObjects是否包含a,singletonObjects不包含a,实例化A放进singletonObjects,设置属性b,发现依赖B,尝试getBean(b) -- 步骤二:getBean(b),检查singletonObjects是否包含b,singletonObjects不包含a,实例化B放进singletonObjects,设置属性a,发现依赖A,尝试getBean(a) +- 步骤二:getBean(b),检查singletonObjects是否包含b,singletonObjects不包含b,实例化B放进singletonObjects,设置属性a,发现依赖A,尝试getBean(a) - 步骤三:getBean(a),检查singletonObjects是否包含a,singletonObjects包含a,返回a - 步骤四:步骤二中的b拿到a,设置属性a,然后返回b - 步骤五:步骤一种的a拿到b,设置属性b,然后返回a From bd59c5033f4c66d8380dce04a51af1d9a1427be8 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Wed, 7 Apr 2021 23:07:13 +0800 Subject: [PATCH 42/81] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E4=B9=A6=E5=86=99=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- README_CN.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f7b29c6..42a3594 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ If this project can help you, please **STAR the project, thank you!!!** * [Solve the problem of circular dependencies(first part): without proxy bean](#解决循环依赖问题一) * [Solve the problem of circular dependencies(second part): with proxy bean](#解决循环依赖问题二) -#### bug fix +#### Bug fix * [populate proxy bean with property values(discovered and fixed by kerwin89)](#没有为代理bean设置属性) ## Usage diff --git a/README_CN.md b/README_CN.md index 32cc875..1f4688c 100644 --- a/README_CN.md +++ b/README_CN.md @@ -51,7 +51,7 @@ * [解决循环依赖问题(一):没有代理对象](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#解决循环依赖问题一没有代理对象) * [解决循环依赖问题(二):有代理对象](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#解决循环依赖问题二有代理对象) -#### bug fix +#### Bug fix * [没有为代理bean设置属性(discovered and fixed by kerwin89)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bug-fix没有为代理bean设置属性discovered-and-fixed-by-kerwin89) ## 使用方法 From c280322d520e7f2b67870d8d5e624a5dfa9afba4 Mon Sep 17 00:00:00 2001 From: DerekYRC <44155264+DerekYRC@users.noreply.github.com> Date: Tue, 15 Mar 2022 19:00:03 +0800 Subject: [PATCH 43/81] Update README_CN.md --- README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CN.md b/README_CN.md index 1f4688c..f61cf9f 100644 --- a/README_CN.md +++ b/README_CN.md @@ -61,7 +61,7 @@ 欢迎Pull Request。 ## 联系我 -欢迎探讨跟mini-spring和其他技术相关的问题,个人邮箱:**15521077528@163.com**。 +邮箱:**15521077528@163.com** ## 参考 - [《Spring源码深度解析》](https://book.douban.com/subject/25866350/) From db0889164e6ba7a72ad495ddeb851106221c428d Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 19 Mar 2022 12:16:43 +0800 Subject: [PATCH 44/81] =?UTF-8?q?=E6=B7=BB=E5=8A=A0mini-spring-cloud?= =?UTF-8?q?=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README_CN.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README_CN.md b/README_CN.md index f61cf9f..e373383 100644 --- a/README_CN.md +++ b/README_CN.md @@ -4,6 +4,8 @@ [![Stars](https://img.shields.io/github/stars/DerekYRC/mini-spring)](https://img.shields.io/github/stars/DerekYRC/mini-spring) [![Forks](https://img.shields.io/github/forks/DerekYRC/mini-spring)](https://img.shields.io/github/forks/DerekYRC/mini-spring) +姊妹版:[mini-spring-cloud(简化版的spring cloud框架)](https://github.com/DerekYRC/mini-spring-cloud) + * [English version](./README.md) ## 关于 From 8b5b3112d0d2562db4ec7978407774324ac332fd Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 19 Mar 2022 12:19:01 +0800 Subject: [PATCH 45/81] =?UTF-8?q?=E6=B7=BB=E5=8A=A0mini-spring-cloud?= =?UTF-8?q?=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README_CN.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README_CN.md b/README_CN.md index e373383..19ce1e2 100644 --- a/README_CN.md +++ b/README_CN.md @@ -4,10 +4,10 @@ [![Stars](https://img.shields.io/github/stars/DerekYRC/mini-spring)](https://img.shields.io/github/stars/DerekYRC/mini-spring) [![Forks](https://img.shields.io/github/forks/DerekYRC/mini-spring)](https://img.shields.io/github/forks/DerekYRC/mini-spring) -姊妹版:[mini-spring-cloud(简化版的spring cloud框架)](https://github.com/DerekYRC/mini-spring-cloud) - * [English version](./README.md) +**姊妹版:**[**mini-spring-cloud** (简化版的spring cloud框架)](https://github.com/DerekYRC/mini-spring-cloud) + ## 关于 **mini-spring**是简化版的spring框架,能帮助你快速熟悉spring源码和掌握spring的核心原理。抽取了spring的核心逻辑,代码极度简化,保留spring的核心功能,如IoC和AOP、资源加载器、事件监听器、类型转换、容器扩展点、bean生命周期和作用域、应用上下文等核心功能。 From ba969c822381d6575638aa2d4a5d5eac61d3ba99 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 19 Mar 2022 12:20:03 +0800 Subject: [PATCH 46/81] =?UTF-8?q?=E6=B7=BB=E5=8A=A0mini-spring-cloud?= =?UTF-8?q?=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CN.md b/README_CN.md index 19ce1e2..55ab8de 100644 --- a/README_CN.md +++ b/README_CN.md @@ -6,7 +6,7 @@ * [English version](./README.md) -**姊妹版:**[**mini-spring-cloud** (简化版的spring cloud框架)](https://github.com/DerekYRC/mini-spring-cloud) +**姊妹版:**[**mini-spring-cloud (简化版的spring cloud框架)**](https://github.com/DerekYRC/mini-spring-cloud) ## 关于 From 9847004cfb328b6df63fbd6161474d42bd816f3c Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 19 Mar 2022 12:21:36 +0800 Subject: [PATCH 47/81] =?UTF-8?q?=E6=B7=BB=E5=8A=A0mini-spring-cloud?= =?UTF-8?q?=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CN.md b/README_CN.md index 55ab8de..cf17fa4 100644 --- a/README_CN.md +++ b/README_CN.md @@ -6,7 +6,7 @@ * [English version](./README.md) -**姊妹版:**[**mini-spring-cloud (简化版的spring cloud框架)**](https://github.com/DerekYRC/mini-spring-cloud) +**姊妹版:**[**mini-spring-cloud**](https://github.com/DerekYRC/mini-spring-cloud) **(简化版的spring cloud框架)** ## 关于 From 00ec69928ca39dbcbc416a83a864c3b5086c71b0 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sat, 26 Mar 2022 18:35:44 +0800 Subject: [PATCH 48/81] update readme --- README_CN.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README_CN.md b/README_CN.md index cf17fa4..f20bd5e 100644 --- a/README_CN.md +++ b/README_CN.md @@ -57,12 +57,17 @@ * [没有为代理bean设置属性(discovered and fixed by kerwin89)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bug-fix没有为代理bean设置属性discovered-and-fixed-by-kerwin89) ## 使用方法 -每个功能点对应一个分支,切换到功能点对应的分支了解新增的功能,增量改动点在[changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md)文件中描述。 +阅读[changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) + +## 提问 +[**点此提问**](https://github.com/DerekYRC/mini-spring/issues/4) ## 贡献 -欢迎Pull Request。 +欢迎Pull Request ## 联系我 +手机/微信:**15521077528** + 邮箱:**15521077528@163.com** ## 参考 From 83ecf3d609d3cad98610cb1eb23fa10c8cf7eb09 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Fri, 1 Apr 2022 20:43:14 +0800 Subject: [PATCH 49/81] update readme --- README_CN.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README_CN.md b/README_CN.md index f20bd5e..ebc93dc 100644 --- a/README_CN.md +++ b/README_CN.md @@ -74,3 +74,7 @@ - [《Spring源码深度解析》](https://book.douban.com/subject/25866350/) - [《精通Spring 4.x》](https://book.douban.com/subject/26952826/) - [tiny-spring](https://github.com/code4craft/tiny-spring) + +## 版权说明 + +本项目可用于个人学习、非商业性或非盈利性用途。将本项目用于其他用途时,须征得本人的书面许可。 \ No newline at end of file From 7b03beeb6ff0db19d68b13ad650b2a133252c10e Mon Sep 17 00:00:00 2001 From: DerekYRC <44155264+DerekYRC@users.noreply.github.com> Date: Sat, 2 Apr 2022 13:43:49 +0800 Subject: [PATCH 50/81] Update README_CN.md --- README_CN.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README_CN.md b/README_CN.md index ebc93dc..f20bd5e 100644 --- a/README_CN.md +++ b/README_CN.md @@ -74,7 +74,3 @@ - [《Spring源码深度解析》](https://book.douban.com/subject/25866350/) - [《精通Spring 4.x》](https://book.douban.com/subject/26952826/) - [tiny-spring](https://github.com/code4craft/tiny-spring) - -## 版权说明 - -本项目可用于个人学习、非商业性或非盈利性用途。将本项目用于其他用途时,须征得本人的书面许可。 \ No newline at end of file From ac470cba8603220e1bd2a6ecfa99ee2f106f955c Mon Sep 17 00:00:00 2001 From: DerekYRC <44155264+DerekYRC@users.noreply.github.com> Date: Sat, 2 Apr 2022 13:47:10 +0800 Subject: [PATCH 51/81] Update README_CN.md --- README_CN.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README_CN.md b/README_CN.md index f20bd5e..4163d92 100644 --- a/README_CN.md +++ b/README_CN.md @@ -72,5 +72,6 @@ ## 参考 - [《Spring源码深度解析》](https://book.douban.com/subject/25866350/) +- [《Spring 源码解析》](http://svip.iocoder.cn/categories/Spring) - [《精通Spring 4.x》](https://book.douban.com/subject/26952826/) - [tiny-spring](https://github.com/code4craft/tiny-spring) From 17ee28291562be1be8a435d2d0a7c405aa58bb5e Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Wed, 6 Apr 2022 20:29:19 +0800 Subject: [PATCH 52/81] update readme --- README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CN.md b/README_CN.md index 4163d92..61c89b7 100644 --- a/README_CN.md +++ b/README_CN.md @@ -74,4 +74,4 @@ - [《Spring源码深度解析》](https://book.douban.com/subject/25866350/) - [《Spring 源码解析》](http://svip.iocoder.cn/categories/Spring) - [《精通Spring 4.x》](https://book.douban.com/subject/26952826/) -- [tiny-spring](https://github.com/code4craft/tiny-spring) +- [《tiny-spring》](https://github.com/code4craft/tiny-spring) From 2efb823c323a8907530a01b30a6ed7d0c41d1a09 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Thu, 7 Apr 2022 19:58:52 +0800 Subject: [PATCH 53/81] update changelog --- changelog.md | 60 ++++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/changelog.md b/changelog.md index 12d1284..55b0673 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,6 @@ # [基础篇:IoC](#基础篇IoC) ## [最简单的bean容器](#最简单的bean容器) - > 分支:simple-bean-container + > 代码分支:simple-bean-container 定义一个简单的bean容器BeanFactory,内部包含一个map用以保存bean,只有注册bean和获取bean两个方法 ``` @@ -40,7 +40,7 @@ public class SimpleBeanContainerTest { ``` ## [BeanDefinition和BeanDefinitionRegistry](#BeanDefinition和BeanDefinitionRegistry) -> 分支:bean-definition-and-bean-definition-registry +> 代码分支:bean-definition-and-bean-definition-registry 主要增加如下类: - BeanDefinition,顾名思义,用于定义bean信息的类,包含bean的class类型、构造参数、属性值等信息,每个bean对应一个BeanDefinition的实例。简化BeanDefinition仅包含bean的class类型。 @@ -75,7 +75,7 @@ class HelloService { ``` ## [Bean实例化策略InstantiationStrategy](#Bean实例化策略InstantiationStrategy) -> 分支:instantiation-strategy +> 代码分支:instantiation-strategy 现在bean是在AbstractAutowireCapableBeanFactory.doCreateBean方法中用beanClass.newInstance()来实例化,仅适用于bean有无参构造函数的情况。 @@ -86,7 +86,7 @@ class HelloService { - CglibSubclassingInstantiationStrategy,使用CGLIB动态生成子类 ## [为bean填充属性](#为bean填充属性) -> 分支:populate-bean-with-property-values +> 代码分支:populate-bean-with-property-values 在BeanDefinition中增加和bean属性对应的PropertyValues,实例化bean之后,为bean填充属性(AbstractAutowireCapableBeanFactory#applyPropertyValues)。 @@ -112,7 +112,7 @@ public class PopulateBeanWithPropertyValuesTest { ``` ## [为bean注入bean](#为bean注入bean) -> 分支:populate-bean-with-bean +> 代码分支:populate-bean-with-bean 增加BeanReference类,包装一个bean对另一个bean的引用。实例化beanA后填充属性时,若PropertyValue#value为BeanReference,引用beanB,则先去实例化beanB。 由于不想增加代码的复杂度提高理解难度,暂时不支持循环依赖,后面会在高级篇中解决该问题。 @@ -177,7 +177,7 @@ public class PopulateBeanWithPropertyValuesTest { ``` ## [资源和资源加载器](#资源和资源加载器) -> 分支:resource-and-resource-loader +> 代码分支:resource-and-resource-loader Resource是资源的抽象和访问接口,简单写了三个实现类 @@ -223,7 +223,7 @@ public class ResourceAndResourceLoaderTest { ``` ## [在xml文件中定义bean](#在xml文件中定义bean) -> 分支:xml-file-define-bean +> 代码分支:xml-file-define-bean 有了资源加载器,就可以在xml格式配置文件中声明式地定义bean的信息,资源加载器读取xml文件,解析出bean的信息,然后往容器中注册BeanDefinition。 @@ -281,7 +281,7 @@ public class XmlFileDefineBeanTest { ``` ## [BeanFactoryPostProcess和BeanPostProcessor](#BeanFactoryPostProcess和BeanPostProcessor) -> 分支:bean-factory-post-processor-and-bean-post-processor +> 代码分支:bean-factory-post-processor-and-bean-post-processor BeanFactoryPostProcess和BeanPostProcessor是spring框架中具有重量级地位的两个接口,理解了这两个接口的作用,基本就理解spring的核心原理了。为了降低理解难度分两个小节实现。 @@ -346,7 +346,7 @@ public class BeanFactoryProcessorAndBeanPostProcessorTest { ``` ## [应用上下文ApplicationContext](#应用上下文ApplicationContext) -> 分支:application-context +> 代码分支:application-context 应用上下文ApplicationContext是spring中较之于BeanFactory更为先进的IOC容器,ApplicationContext除了拥有BeanFactory的所有功能外,还支持特殊类型bean如上一节中的BeanFactoryPostProcessor和BeanPostProcessor的自动识别、资源加载、容器事件和监听器、国际化支持、单例bean自动初始化等。 @@ -361,7 +361,7 @@ BeanFactory是spring的基础设施,面向spring本身;而ApplicationContext 测试:见ApplicationContextTest ## [bean的初始化和销毁方法](#bean的初始化和销毁方法) -> 分支:init-and-destroy-method +> 代码分支:init-and-destroy-method 在spring中,定义bean的初始化和销毁方法有三种方法: - 在xml文件中制定init-method和destroy-method @@ -445,7 +445,7 @@ public class InitAndDestoryMethodTest { ``` ## [Aware接口](#Aware接口) -> 分支:aware-interface +> 代码分支:aware-interface Aware是感知、意识的意思,Aware接口是标记性接口,其实现子类能感知容器相关的对象。常用的Aware接口有BeanFactoryAware和ApplicationContextAware,分别能让其实现者感知所属的BeanFactory和ApplicationContext。 @@ -515,7 +515,7 @@ public class AwareInterfaceTest { ``` ## [bean作用域,增加prototype的支持](#bean作用域增加prototype的支持) -> 分支:prototype-bean +> 代码分支:prototype-bean 每次向容器获取prototype作用域bean时,容器都会创建一个新的实例。在BeanDefinition中增加描述bean的作用域的字段scope/singleton/prototype,创建prototype作用域bean时(AbstractAutowireCapableBeanFactory#doCreateBean),不往singletonObjects中增加该bean。prototype作用域bean不执行销毁方法,查看AbstractAutowireCapableBeanFactory#registerDisposableBeanIfNecessary方法。 @@ -556,7 +556,7 @@ public class PrototypeBeanTest { ``` ## [FactoryBean](#FactoryBean) -> 分支:factory-bean +> 代码分支:factory-bean FactoryBean是一种特殊的bean,当向容器获取该bean时,容器不是返回其本身,而是返回其FactoryBean#getObject方法的返回值,可通过编码方式定义复杂的bean。 @@ -617,7 +617,7 @@ public class FactoryBeanTest { ``` ## [容器事件和事件监听器](#容器事件和事件监听器) -> 分支:event-and-event-listener +> 代码分支:event-and-event-listener ApplicationContext容器提供了完善的时间发布和时间监听功能。 @@ -664,7 +664,7 @@ org.springframework.test.common.event.ContextClosedEventListener # [基础篇:AOP](#基础篇AOP) ## [切点表达式](#切点表达式) -> 分支:pointcut-expression +> 代码分支:pointcut-expression Joinpoint,织入点,指需要执行代理操作的某个类的某个方法(仅支持方法级别的JoinPoint);Pointcut是JoinPoint的表述方式,能捕获JoinPoint。 @@ -695,7 +695,7 @@ public class PointcutExpressionTest { ``` ## [基于JDK的动态代理](#基于JDK的动态代理) -> 分支:jdk-dynamic-proxy +> 代码分支:jdk-dynamic-proxy AopProxy是获取代理对象的抽象接口,JdkDynamicAopProxy的基于JDK动态代理的具体实现。TargetSource,被代理对象的封装。MethodInterceptor,方法拦截器,是AOP Alliance的"公民",顾名思义,可以拦截方法,可在被代理执行的方法前后增加代理行为。 @@ -722,7 +722,7 @@ public class DynamicProxyTest { ``` ## [基于CGLIB的动态代理](#基于CGLIB的动态代理) -> 分支:cglib-dynamic-proxy +> 代码分支:cglib-dynamic-proxy 基于CGLIB的动态代理实现逻辑也比较简单,查看CglibAopProxy。与基于JDK的动态代理在运行期间为接口生成对象的代理对象不同,基于CGLIB的动态代理能在运行期间动态构建字节码的class文件,为类生成子类,因此被代理类不需要继承自任何接口。 @@ -754,7 +754,7 @@ public class DynamicProxyTest { ``` ## [AOP代理工厂](#AOP代理工厂) -> 分支:proxy-factory +> 代码分支:proxy-factory 增加AOP代理工厂ProxyFactory,由AdvisedSupport#proxyTargetClass属性决定使用JDK动态代理还是CGLIB动态代理。 @@ -793,7 +793,7 @@ public class DynamicProxyTest { ``` ## [几种常用的Advice:BeforeAdvice/AfterAdvice/AfterReturningAdvice/ThrowsAdvice...](#几种常用的AdviceBeforeAdviceAfterAdviceAfterReturningAdviceThrowsAdvice) -> 分支: common-advice +> 代码分支: common-advice Spring将AOP联盟中的Advice细化出各种类型的Advice,常用的有BeforeAdvice/AfterAdvice/AfterReturningAdvice/ThrowsAdvice,我们可以通过扩展MethodInterceptor来实现。 @@ -843,7 +843,7 @@ public class DynamicProxyTest { ``` ## [PointcutAdvisor:Pointcut和Advice的组合](#PointcutAdvisorPointcut和Advice的组合) -> 分支:pointcut-advisor +> 代码分支:pointcut-advisor Advisor是包含一个Pointcut和一个Advice的组合,Pointcut用于捕获JoinPoint,Advice决定在JoinPoint执行某种操作。实现了一个支持aspectj表达式的AspectJExpressionPointcutAdvisor。 @@ -879,7 +879,7 @@ public class DynamicProxyTest { ``` ## [动态代理融入bean生命周期](#动态代理融入bean生命周期) -> 分支:auto-proxy +> 代码分支:auto-proxy 结合前面讲解的bean的生命周期,BeanPostProcessor处理阶段可以修改和替换bean,正好可以在此阶段返回代理对象替换原对象。不过我们引入一种特殊的BeanPostProcessor——InstantiationAwareBeanPostProcessor,如果InstantiationAwareBeanPostProcessor处理阶段返回代理对象,会导致短路,不会继续走原来的创建bean的流程,具体实现查看AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation。 @@ -936,7 +936,7 @@ public class AutoProxyTest { # [扩展篇](#扩展篇) ## [PropertyPlaceholderConfigurer](#PropertyPlaceholderConfigurer) -> 分支:property-placeholder-configurer +> 代码分支:property-placeholder-configurer 经常需要将配置信息配置在properties文件中,然后在XML文件中以占位符的方式引用。 @@ -981,7 +981,7 @@ public class PropertyPlaceholderConfigurerTest { ``` ## [包扫描](#包扫描) -> 分支:package-scan +> 代码分支:package-scan 结合bean的生命周期,包扫描只不过是扫描特定注解的类,提取类的相关信息组装成BeanDefinition注册到容器中。 @@ -1023,7 +1023,7 @@ public class PackageScanTest { ``` ## [@Value注解](#Value注解) -> 分支:value-annotation +> 代码分支:value-annotation 注解@Value和@Autowired通过BeanPostProcessor处理。InstantiationAwareBeanPostProcessor增加postProcessPropertyValues方法,在bean实例化之后设置属性之前执行,查看AbstractAutowireCapableBeanFactory#doCreateBean方法。 @@ -1076,7 +1076,7 @@ public class ValueAnnotationTest { ``` ## [@Autowired注解](#Autowired注解) -> 分支:autowired-annotation +> 代码分支:autowired-annotation @Autowired注解的处理见AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues @@ -1122,7 +1122,7 @@ public class AutowiredAnnotationTest { } ``` ## [bug fix:没有为代理bean设置属性(discovered and fixed by @kerwin89)](#bug-fix没有为代理bean设置属性discovered-and-fixed-by-kerwin89) -> 分支: populate-proxy-bean-with-property-values +> 代码分支: populate-proxy-bean-with-property-values 问题现象:没有为代理bean设置属性 @@ -1197,7 +1197,7 @@ public class AutoProxyTest { ``` ## [类型转换(一)](#类型转换一) -> 分支:type-conversion-first-part +> 代码分支:type-conversion-first-part spring在org.springframework.core.convert.converter包中定义了三种类型转换器接口:Converter、ConverterFactory、GenericConverter。 @@ -1313,7 +1313,7 @@ ConversionService是类型转换体系的核心接口,将以上三种类型转 测试见TypeConversionFirstPartTest。 ## [类型转换(二)](#类型转换二) -> 分支:type-conversion-second-part +> 代码分支:type-conversion-second-part 上一节实现了spring中的类型转换体系,本节将类型转换的能力整合到容器中。 @@ -1393,7 +1393,7 @@ public class TypeConversionSecondPartTest { # [高级篇](#高级篇) ## [解决循环依赖问题(一):没有代理对象](#解决循环依赖问题一没有代理对象) -> 分支:circular-reference-without-proxy-bean +> 代码分支:circular-reference-without-proxy-bean 虽然放在高级篇,其实解决循环依赖问题的方法非常简单。 @@ -1452,7 +1452,7 @@ A依赖B,B又依赖A,循环依赖。容器加载时会执行依赖流程: 下一节填坑。 ## [解决循环依赖问题(二):有代理对象](#解决循环依赖问题二有代理对象) -> 分支:circular-reference-with-proxy-bean +> 代码分支:circular-reference-with-proxy-bean 解决有代理对象时的循环依赖问题,需要提前暴露代理对象的引用,而不是暴露实例化后的bean的引用(这是上节的遗留问题的原因,应该提前暴露A的代理对象的引用)。 From 4cd9cd8e03ef7a1864048899227c48901f7c7997 Mon Sep 17 00:00:00 2001 From: songxin <504252262@qq.com> Date: Tue, 14 Jun 2022 15:07:29 +0800 Subject: [PATCH 54/81] implement cglib instantiation strategy --- pom.xml | 6 ++++++ .../support/CglibSubclassingInstantiationStrategy.java | 8 ++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3a553d7..abd0971 100644 --- a/pom.xml +++ b/pom.xml @@ -30,5 +30,11 @@ test + + cglib + cglib + 3.3.0 + + diff --git a/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java b/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java index 96e1b07..b04a011 100644 --- a/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java +++ b/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java @@ -1,5 +1,7 @@ package org.springframework.beans.factory.support; +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; @@ -18,7 +20,9 @@ public class CglibSubclassingInstantiationStrategy implements InstantiationStrat */ @Override public Object instantiate(BeanDefinition beanDefinition) throws BeansException { - //TODO 感兴趣的小伙伴可以实现下 - throw new UnsupportedOperationException("CGLIB instantiation strategy is not supported"); + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(beanDefinition.getBeanClass()); + enhancer.setCallback((MethodInterceptor) (obj, method, argsTemp, proxy) -> proxy.invokeSuper(obj,argsTemp)); + return enhancer.create(); } } From 26c48e5c34f4882213d24c07285af3b6adfcfa84 Mon Sep 17 00:00:00 2001 From: zhenglinhui Date: Thu, 23 Jun 2022 15:42:14 +0800 Subject: [PATCH 55/81] update typos in changelog.md --- changelog.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 55b0673..c699466 100644 --- a/changelog.md +++ b/changelog.md @@ -280,10 +280,10 @@ public class XmlFileDefineBeanTest { } ``` -## [BeanFactoryPostProcess和BeanPostProcessor](#BeanFactoryPostProcess和BeanPostProcessor) +## [BeanFactoryPostProcessor和BeanPostProcessor](#BeanFactoryPostProcessor和BeanPostProcessor) > 代码分支:bean-factory-post-processor-and-bean-post-processor -BeanFactoryPostProcess和BeanPostProcessor是spring框架中具有重量级地位的两个接口,理解了这两个接口的作用,基本就理解spring的核心原理了。为了降低理解难度分两个小节实现。 +BeanFactoryPostProcessor和BeanPostProcessor是spring框架中具有重量级地位的两个接口,理解了这两个接口的作用,基本就理解spring的核心原理了。为了降低理解难度分两个小节实现。 BeanFactoryPostProcessor是spring提供的容器扩展机制,允许我们在bean实例化之前修改bean的定义信息即BeanDefinition的信息。其重要的实现类有PropertyPlaceholderConfigurer和CustomEditorConfigurer,PropertyPlaceholderConfigurer的作用是用properties文件的配置值替换xml文件中的占位符,CustomEditorConfigurer的作用是实现类型转换。BeanFactoryPostProcessor的实现比较简单,看单元测试BeanFactoryPostProcessorAndBeanPostProcessorTest#testBeanFactoryPostProcessor追下代码。 From df3bb7c6026c76cfa11c57130502853648973d52 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Tue, 5 Jul 2022 21:05:13 +0800 Subject: [PATCH 56/81] update changelog --- changelog.md | 159 +++++++++++++++++++++++++++++---------------------- 1 file changed, 92 insertions(+), 67 deletions(-) diff --git a/changelog.md b/changelog.md index 55b0673..75693a1 100644 --- a/changelog.md +++ b/changelog.md @@ -3,7 +3,7 @@ > 代码分支:simple-bean-container 定义一个简单的bean容器BeanFactory,内部包含一个map用以保存bean,只有注册bean和获取bean两个方法 -``` +```java public class BeanFactory { private Map beanMap = new HashMap<>(); @@ -18,7 +18,7 @@ public class BeanFactory { ``` 测试: -``` +```java public class SimpleBeanContainerTest { @Test @@ -52,7 +52,7 @@ bean容器作为BeanDefinitionRegistry和SingletonBeanRegistry的实现类,具 ![](./assets/bean-definition-and-bean-definition-registry.png) 测试: -``` +```java public class BeanDefinitionAndBeanDefinitionRegistryTest { @Test @@ -91,7 +91,7 @@ class HelloService { 在BeanDefinition中增加和bean属性对应的PropertyValues,实例化bean之后,为bean填充属性(AbstractAutowireCapableBeanFactory#applyPropertyValues)。 测试: -``` +```java public class PopulateBeanWithPropertyValuesTest { @Test @@ -116,7 +116,7 @@ public class PopulateBeanWithPropertyValuesTest { 增加BeanReference类,包装一个bean对另一个bean的引用。实例化beanA后填充属性时,若PropertyValue#value为BeanReference,引用beanB,则先去实例化beanB。 由于不想增加代码的复杂度提高理解难度,暂时不支持循环依赖,后面会在高级篇中解决该问题。 -``` +```java protected void applyPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) { try { for (PropertyValue propertyValue : beanDefinition.getPropertyValues().getPropertyValues()) { @@ -138,7 +138,7 @@ protected void applyPropertyValues(String beanName, Object bean, BeanDefinition ``` 测试: -``` +```java public class PopulateBeanWithPropertyValuesTest { /** @@ -190,7 +190,7 @@ Resource是资源的抽象和访问接口,简单写了三个实现类 ResourceLoader接口则是资源查找定位策略的抽象,DefaultResourceLoader是其默认实现类 测试: -``` +```java public class ResourceAndResourceLoaderTest { @Test @@ -237,7 +237,7 @@ BeanDefinitionReader是读取bean定义信息的抽象接口,XmlBeanDefinition 测试: bean定义文件spring.xml -``` +```xml ``` -``` +```java public class XmlFileDefineBeanTest { @Test @@ -291,7 +291,7 @@ BeanPostProcessor也是spring提供的容器扩展机制,不同于BeanFactoryP BeanPostProcessor的两个方法分别在bean执行初始化方法(后面实现)之前和之后执行,理解其实现重点看单元测试BeanFactoryPostProcessorAndBeanPostProcessorTest#testBeanPostProcessor和AbstractAutowireCapableBeanFactory#initializeBean方法,有些地方做了微调,可不必关注。 -``` +```java public interface BeanPostProcessor { /** * 在bean执行初始化方法之前执行此方法 @@ -308,7 +308,7 @@ public interface BeanPostProcessor { 下一节将引入ApplicationContext,能自动识别BeanFactoryPostProcessor和BeanPostProcessor,就可以在xml文件中配置而不需要手动添加到BeanFactory了。 测试: -``` +```java public class BeanFactoryProcessorAndBeanPostProcessorTest { @Test @@ -382,7 +382,7 @@ BeanFactory是spring的基础设施,面向spring本身;而ApplicationContext 测试: init-and-destroy-method.xml -``` +```java ``` -``` + +```java public class Person implements InitializingBean, DisposableBean { private String name; @@ -433,7 +434,8 @@ public class Person implements InitializingBean, DisposableBean { //setter and getter } ``` -``` + +```java public class InitAndDestoryMethodTest { @Test @@ -461,7 +463,7 @@ Aware是感知、意识的意思,Aware接口是标记性接口,其实现子 测试: spring.xml -``` +```xml ``` -``` + +```java public class HelloService implements ApplicationContextAware, BeanFactoryAware { private ApplicationContext applicationContext; @@ -501,7 +504,8 @@ public class HelloService implements ApplicationContextAware, BeanFactoryAware { } } ``` -``` + +```java public class AwareInterfaceTest { @Test @@ -525,7 +529,7 @@ public class AwareInterfaceTest { 测试: prototype-bean.xml -``` +```xml ``` -``` + +```java public class PrototypeBeanTest { @Test @@ -564,7 +569,7 @@ FactoryBean是一种特殊的bean,当向容器获取该bean时,容器不是 测试: factory-bean.xml -``` +```xml ``` -``` + +```java public class CarFactoryBean implements FactoryBean { private String brand; @@ -602,7 +608,8 @@ public class CarFactoryBean implements FactoryBean { } } ``` -``` + +```java public class FactoryBeanTest { @Test @@ -625,7 +632,7 @@ ApplicationEventMulticaster接口是注册监听器和发布事件的抽象接 测试: event-and-event-listener.xml -``` +```xml ``` -``` + +```java public class EventAndEventListenerTest { @Test @@ -654,6 +662,7 @@ public class EventAndEventListenerTest { } } ``` + 观察输出: ``` org.springframework.test.common.event.ContextRefreshedEventListener @@ -671,7 +680,7 @@ Joinpoint,织入点,指需要执行代理操作的某个类的某个方法( 最常用的切点表达式是AspectJ的切点表达式。需要匹配类,定义ClassFilter接口;匹配方法,定义MethodMatcher接口。PointCut需要同时匹配类和方法,包含ClassFilter和MethodMatcher,AspectJExpressionPointcut是支持AspectJ切点表达式的PointCut实现,简单实现仅支持execution函数。 测试: -``` +```java public class HelloService { public String sayHello() { System.out.println("hello"); @@ -679,7 +688,8 @@ public class HelloService { } } ``` -``` + +```java public class PointcutExpressionTest { @Test @@ -700,7 +710,7 @@ public class PointcutExpressionTest { AopProxy是获取代理对象的抽象接口,JdkDynamicAopProxy的基于JDK动态代理的具体实现。TargetSource,被代理对象的封装。MethodInterceptor,方法拦截器,是AOP Alliance的"公民",顾名思义,可以拦截方法,可在被代理执行的方法前后增加代理行为。 测试; -``` +```java public class DynamicProxyTest { @Test @@ -727,7 +737,7 @@ public class DynamicProxyTest { 基于CGLIB的动态代理实现逻辑也比较简单,查看CglibAopProxy。与基于JDK的动态代理在运行期间为接口生成对象的代理对象不同,基于CGLIB的动态代理能在运行期间动态构建字节码的class文件,为类生成子类,因此被代理类不需要继承自任何接口。 测试: -``` +```java public class DynamicProxyTest { private AdvisedSupport advisedSupport; @@ -759,7 +769,7 @@ public class DynamicProxyTest { 增加AOP代理工厂ProxyFactory,由AdvisedSupport#proxyTargetClass属性决定使用JDK动态代理还是CGLIB动态代理。 测试: -``` +```java public class DynamicProxyTest { private AdvisedSupport advisedSupport; @@ -804,7 +814,7 @@ Spring将AOP联盟中的Advice细化出各种类型的Advice,常用的有Befor - [ ] ThrowsAdvice 测试: -``` +```java public class WorldServiceBeforeAdvice implements MethodBeforeAdvice { @Override @@ -813,7 +823,8 @@ public class WorldServiceBeforeAdvice implements MethodBeforeAdvice { } } ``` -``` + +```java public class DynamicProxyTest { private AdvisedSupport advisedSupport; @@ -848,7 +859,7 @@ public class DynamicProxyTest { Advisor是包含一个Pointcut和一个Advice的组合,Pointcut用于捕获JoinPoint,Advice决定在JoinPoint执行某种操作。实现了一个支持aspectj表达式的AspectJExpressionPointcutAdvisor。 测试: -``` +```java public class DynamicProxyTest { @Test @@ -891,7 +902,7 @@ DefaultAdvisorAutoProxyCreator是处理横切逻辑的织入返回代理对象 测试: auto-proxy.xml -``` +```xml ``` -``` +```java public class AutoProxyTest { @Test @@ -944,10 +955,11 @@ public class AutoProxyTest { 测试: car.properties -``` +```properties brand=lamborghini ``` -``` + +```java ``` -``` + +```java public class PropertyPlaceholderConfigurerTest { @Test @@ -988,14 +1001,15 @@ public class PropertyPlaceholderConfigurerTest { 在XmlBeanDefinitionReader中解析``````标签,扫描类组装BeanDefinition然后注册到容器中的操作在ClassPathBeanDefinitionScanner#doScan中实现。 测试: -``` +```java @Component public class Car { } ``` + package-scan.xml -``` +```xml ``` -``` + +```java public class PackageScanTest { @Test @@ -1030,7 +1045,7 @@ public class PackageScanTest { 增加AutowiredAnnotationBeanPostProcessor用于处理注解@Value,@Autowired的处理在下一节实现,在ClassPathBeanDefinitionScanner#doScan将其添加到容器中。查看AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues,其中字符解析器StringValueResolver在PropertyPlaceholderConfigurer中添加到BeanFactory中。 测试: -``` +```java @Component public class Car { @@ -1038,8 +1053,9 @@ public class Car { private String brand; } ``` + value-annotation.xml -``` +```xml ``` + car.properties -``` +```properties brand=lamborghini ``` -``` + +```java public class ValueAnnotationTest { @Test @@ -1072,7 +1090,6 @@ public class ValueAnnotationTest { assertThat(car.getBrand()).isEqualTo("lamborghini"); } } - ``` ## [@Autowired注解](#Autowired注解) @@ -1081,7 +1098,7 @@ public class ValueAnnotationTest { @Autowired注解的处理见AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues 测试: -``` +```java @Component public class Car { @@ -1095,7 +1112,7 @@ public class Person implements InitializingBean, DisposableBean { } ``` autowired-annotation.xml -``` +```xml ``` -``` + +```java public class AutowiredAnnotationTest { @Test @@ -1138,7 +1156,7 @@ public class AutowiredAnnotationTest { 测试: populate-proxy-bean-with-property-values.xml -``` +```xml ``` -``` + +```java public class WorldServiceImpl implements WorldService { private String name; @@ -1181,7 +1200,8 @@ public class WorldServiceImpl implements WorldService { //setter and getter } ``` -``` + +```java public class AutoProxyTest { @Test @@ -1202,7 +1222,7 @@ public class AutoProxyTest { spring在org.springframework.core.convert.converter包中定义了三种类型转换器接口:Converter、ConverterFactory、GenericConverter。 ### 一、Converter -``` +```java public interface Converter { /** @@ -1212,7 +1232,7 @@ public interface Converter { } ``` Converter能将S类型的对象转换为T类型的对象,比如将String类型的对象转换为Integer类型的对象的实现类: -``` +```java public class StringToIntegerConverter implements Converter { @Override public Integer convert(String source) { @@ -1221,12 +1241,12 @@ public class StringToIntegerConverter implements Converter { } ``` 使用: -``` +```java Integer num = new StringToIntegerConverter().convert("8888"); ``` ### 二、ConverterFactory -``` +```java public interface ConverterFactory { Converter getConverter(Class targetType); @@ -1235,7 +1255,7 @@ public interface ConverterFactory { Converter接口适合一对一的类型转换,如果要将String类型转换为Ineger/Long/Float/Double/Decimal等类型,就要实现一系列的StringToInteger/StringToLongConverter/StringToFloatConverter转换器,非常不优雅。 ConverterFactory接口则适合一对多的类型转换,可以将一种类型转换为另一种类型及其子类。比如将String类型转换为Ineger/Long/Float/Double/Decimal等Number类型时,只需定义一个ConverterFactory转换器: -``` +```java public class StringToNumberConverterFactory implements ConverterFactory { @Override @@ -1274,14 +1294,14 @@ public class StringToNumberConverterFactory implements ConverterFactory stringToIntegerConverter = converterFactory.getConverter(Integer.class); Integer num = stringToIntegerConverter.convert("8888"); ``` ### 三、GenericConverter -``` +```java public interface GenericConverter { Set getConvertibleTypes(); @@ -1290,7 +1310,7 @@ public interface GenericConverter { } ``` String类型转换为Boolean类型的实现类: -``` +```java public class StringToBooleanConverter implements GenericConverter { @Override public Set getConvertibleTypes() { @@ -1304,7 +1324,7 @@ public class StringToBooleanConverter implements GenericConverter { } ``` 使用: -``` +```java Boolean flag = new StringToBooleanConverter().convert("true", String.class, Boolean.class); ``` @@ -1329,7 +1349,7 @@ ConversionService是类型转换体系的核心接口,将以上三种类型转 你可能会有疑问,如果没有定义ConversionService,是怎么进行基本类型的转换的?其实spring为了向下兼容保留了一套比较旧的类型转换机制,没有定义ConversionService时会使用其进行基本类型的转换工作,不必关注旧的类型转换机制。 测试: -``` +```java public class Car { private int price; @@ -1337,7 +1357,8 @@ public class Car { private LocalDate produceDate; } ``` -``` + +```java public class StringToLocalDateConverter implements Converter { private final DateTimeFormatter DATE_TIME_FORMATTER; @@ -1352,8 +1373,9 @@ public class StringToLocalDateConverter implements Converter } } ``` + type-conversion-second-part.xml -``` +```xml ``` -``` + +```java public class TypeConversionSecondPartTest { @Test @@ -1399,7 +1422,7 @@ public class TypeConversionSecondPartTest { 先理解spring中为什么会有循环依赖的问题。比如如下的代码 -``` +```java public class A { private B b; @@ -1407,7 +1430,8 @@ public class A { //getter and setter } ``` -``` + +```java public class B { private A a; @@ -1415,7 +1439,8 @@ public class B { //getter and setter } ``` -``` + +```xml From dcbcc7432585dcc22eaf6519017402cfa0df177c Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Tue, 5 Jul 2022 21:08:18 +0800 Subject: [PATCH 57/81] update changelog --- changelog.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index fe836d8..98aabb8 100644 --- a/changelog.md +++ b/changelog.md @@ -382,7 +382,7 @@ BeanFactory是spring的基础设施,面向spring本身;而ApplicationContext 测试: init-and-destroy-method.xml -```java +```xml Date: Tue, 23 Aug 2022 19:38:48 +0800 Subject: [PATCH 58/81] =?UTF-8?q?bean=E5=88=9D=E5=A7=8B=E5=8C=96=E5=92=8C?= =?UTF-8?q?=E9=94=80=E6=AF=81=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../beans/factory/support/DefaultSingletonBeanRegistry.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java index 85df26d..ebbc088 100644 --- a/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java +++ b/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.config.SingletonBeanRegistry; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -33,7 +34,7 @@ public void registerDisposableBean(String beanName, DisposableBean bean) { } public void destroySingletons() { - Set beanNames = disposableBeans.keySet(); + ArrayList beanNames = new ArrayList<>(disposableBeans.keySet()); for (String beanName : beanNames) { DisposableBean disposableBean = disposableBeans.remove(beanName); try { From fe144dcd54ae4ee369ddd2b1326714cac09fe80f Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Tue, 23 Aug 2022 19:40:26 +0800 Subject: [PATCH 59/81] =?UTF-8?q?bean=E5=88=9D=E5=A7=8B=E5=8C=96=E5=92=8C?= =?UTF-8?q?=E9=94=80=E6=AF=81=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../factory/support/DefaultSingletonBeanRegistry.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java index ebbc088..763048d 100644 --- a/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java +++ b/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java @@ -1,14 +1,12 @@ package org.springframework.beans.factory.support; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.config.SingletonBeanRegistry; - import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.Map; -import java.util.Set; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.config.SingletonBeanRegistry; /** * @author derekyi From a632e4cb9a1894414011a4453ff0b6054bed5bc1 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Tue, 23 Aug 2022 19:43:49 +0800 Subject: [PATCH 60/81] update readme --- README.md | 104 ++++++++++-------- README_CN.md | 77 ------------- README_en.md | 69 ++++++++++++ .../support/DefaultSingletonBeanRegistry.java | 10 +- 4 files changed, 130 insertions(+), 130 deletions(-) delete mode 100644 README_CN.md create mode 100644 README_en.md diff --git a/README.md b/README.md index 42a3594..4732570 100644 --- a/README.md +++ b/README.md @@ -4,66 +4,74 @@ [![Stars](https://img.shields.io/github/stars/DerekYRC/mini-spring)](https://img.shields.io/github/stars/DerekYRC/mini-spring) [![Forks](https://img.shields.io/github/forks/DerekYRC/mini-spring)](https://img.shields.io/github/forks/DerekYRC/mini-spring) -* [中文版](./README_CN.md) +[English](./README_en.md) | 简体中文 -## About +**姊妹版:**[**mini-spring-cloud**](https://github.com/DerekYRC/mini-spring-cloud) **(简化版的spring cloud框架)** -The **mini-spring** is a simplified version of the Spring framework that will help you quickly get familiar with the Spring source code and grasp the core principles of Spring. The core logic of Spring is extracted, the code is extremely simplified, and the core functions of Spring, such as IoC and AOP, resource loaders, event listeners, type conversion, container extension points, bean life cycle and scope, and application context, are retained. +## 关于 -If this project can help you, please **STAR the project, thank you!!!** +**mini-spring**是简化版的spring框架,能帮助你快速熟悉spring源码和掌握spring的核心原理。抽取了spring的核心逻辑,代码极度简化,保留spring的核心功能,如IoC和AOP、资源加载器、事件监听器、类型转换、容器扩展点、bean生命周期和作用域、应用上下文等核心功能。 -## Contents -#### Basics -* [IoC](#Ioc) - * [Implement a simple container](#实现一个简单的容器) - * [BeanDefinition and BeanDefinitionRegistry](#BeanDefinition和BeanDefinitionRegistry) - * [Bean Instantiation Strategy](#Bean实例化策略InstantiationStrategy) - * [Populate bean with property values](#为bean填充属性) - * [Populate bean with bean](#为bean注入bean) - * [Resource and ResourceLoader](#资源和资源加载器) - * [Define the bean in the XML file](#在xml文件中定义bean) - * [Container extension mechanism:BeanFactoryPostProcess and BeanPostProcessor](#容器扩展机制BeanFactoryPostProcess和BeanPostProcessor) - * [ApplicationContext](#应用上下文ApplicationContext) - * [Init method and destroy method of bean](#bean的初始化和销毁方法) - * [Aware interface](#Aware接口) - * [Bean scope, added prototype support](#bean作用域,增加prototype的支持) - * [FactoryBean](#FactoryBean) - * [Event and event listener](#容器事件和事件监听器) -* [AOP](#AOP) - * [Pointcut expression](#切点表达式) - * [JDK-based dynamic proxy](#基于JDK的动态代理) - * [CGLIB-based dynamic proxy](#基于CGLIB的动态代理) - * [AOP ProxyFactory](#AOP代理工厂ProxyFactory) - * [Common Advice: BeforeAdvice/AfterAdvice/AfterReturningAdvice/ThrowsAdvice](#几种常用的Advice) - * [PointcutAdvisor:A combination of Pointcut and Advice](#PointcutAdvisor:Pointcut和Advice的组合) - * [Dynamic proxies are integrated into the bean lifecycle](#动态代理融入bean生命周期) +如果本项目能帮助到你,请给个**STAR,谢谢!!!** + +## 功能 +#### 基础篇 +* [IoC](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#基础篇IoC) + * [实现一个简单的容器](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#最简单的bean容器) + * [BeanDefinition和BeanDefinitionRegistry](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#BeanDefinition和BeanDefinitionRegistry) + * [Bean实例化策略InstantiationStrategy](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#Bean实例化策略InstantiationStrategy) + * [为bean填充属性](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#为bean填充属性) + * [为bean注入bean](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#为bean注入bean) + * [资源和资源加载器](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#资源和资源加载器) + * [在xml文件中定义bean](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#在xml文件中定义bean) + * [容器扩展机制BeanFactoryPostProcess和BeanPostProcessor](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#BeanFactoryPostProcess和BeanPostProcessor) + * [应用上下文ApplicationContext](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#应用上下文ApplicationContext) + * [bean的初始化和销毁方法](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bean的初始化和销毁方法) + * [Aware接口](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#Aware接口) + * [bean作用域,增加prototype的支持](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bean作用域增加prototype的支持) + * [FactoryBean](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#FactoryBean) + * [容器事件和事件监听器](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#容器事件和事件监听器) +* [AOP](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#基础篇AOP) + * [切点表达式](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#切点表达式) + * [基于JDK的动态代理](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#基于JDK的动态代理) + * [基于CGLIB的动态代理](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#基于CGLIB的动态代理) + * [AOP代理工厂ProxyFactory](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#AOP代理工厂) + * [几种常用的Advice: BeforeAdvice/AfterAdvice/AfterReturningAdvice/ThrowsAdvice](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#几种常用的AdviceBeforeAdviceAfterAdviceAfterReturningAdviceThrowsAdvice) + * [PointcutAdvisor:Pointcut和Advice的组合](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#pointcutadvisorpointcut和advice的组合) + * [动态代理融入bean生命周期](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#动态代理融入bean生命周期) -#### Expanding -* [PropertyPlaceholderConfigurer](#PropertyPlaceholderConfigurer) -* [Package scan](#包扫描) -* [Value annotation](#Value) -* [Autowired annotation](#Autowired) -* [Type conversion(first part)](#类型转换一) -* [Type conversion(second part)](#类型转换二) +#### 扩展篇 +* [PropertyPlaceholderConfigurer](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#propertyplaceholderconfigurer) +* [包扫描](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#包扫描) +* [@Value注解](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#value注解) +* [基于注解@Autowired的依赖注入](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#autowired注解) +* [类型转换(一)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#类型转换一) +* [类型转换(二)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#类型转换二) -#### Advanced -* [Solve the problem of circular dependencies(first part): without proxy bean](#解决循环依赖问题一) -* [Solve the problem of circular dependencies(second part): with proxy bean](#解决循环依赖问题二) +#### 高级篇 +* [解决循环依赖问题(一):没有代理对象](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#解决循环依赖问题一没有代理对象) +* [解决循环依赖问题(二):有代理对象](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#解决循环依赖问题二有代理对象) #### Bug fix -* [populate proxy bean with property values(discovered and fixed by kerwin89)](#没有为代理bean设置属性) +* [没有为代理bean设置属性(discovered and fixed by kerwin89)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bug-fix没有为代理bean设置属性discovered-and-fixed-by-kerwin89) + +## 使用方法 +阅读[changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) + +## 提问 +[**点此提问**](https://github.com/DerekYRC/mini-spring/issues/4) -## Usage -Each function point corresponds to a branch. Switch to the branch corresponding to the function point to see the new function. The incremental change point is described in the [changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) file. +## 贡献 +欢迎Pull Request -## Contributing -Any contributions you make are greatly appreciated. +## 联系我 +手机/微信:**15521077528** -## Contact -Please feel free to ask me any questions related to mini-spring and other technologies. My email is **15521077528@163.com**. +邮箱:**15521077528@163.com** -## Reference +## 参考 - [《Spring源码深度解析》](https://book.douban.com/subject/25866350/) +- [《Spring 源码解析》](http://svip.iocoder.cn/categories/Spring) - [《精通Spring 4.x》](https://book.douban.com/subject/26952826/) -- [tiny-spring](https://github.com/code4craft/tiny-spring) +- [《tiny-spring》](https://github.com/code4craft/tiny-spring) diff --git a/README_CN.md b/README_CN.md deleted file mode 100644 index 61c89b7..0000000 --- a/README_CN.md +++ /dev/null @@ -1,77 +0,0 @@ -# mini-spring -[![Build Status](https://img.shields.io/badge/build-passing-brightgreen)](https://github.com/DerekYRC/mini-spring) -[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) -[![Stars](https://img.shields.io/github/stars/DerekYRC/mini-spring)](https://img.shields.io/github/stars/DerekYRC/mini-spring) -[![Forks](https://img.shields.io/github/forks/DerekYRC/mini-spring)](https://img.shields.io/github/forks/DerekYRC/mini-spring) - -* [English version](./README.md) - -**姊妹版:**[**mini-spring-cloud**](https://github.com/DerekYRC/mini-spring-cloud) **(简化版的spring cloud框架)** - -## 关于 - -**mini-spring**是简化版的spring框架,能帮助你快速熟悉spring源码和掌握spring的核心原理。抽取了spring的核心逻辑,代码极度简化,保留spring的核心功能,如IoC和AOP、资源加载器、事件监听器、类型转换、容器扩展点、bean生命周期和作用域、应用上下文等核心功能。 - -如果本项目能帮助到你,请给个**STAR,谢谢!!!** - -## 功能 -#### 基础篇 -* [IoC](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#基础篇IoC) - * [实现一个简单的容器](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#最简单的bean容器) - * [BeanDefinition和BeanDefinitionRegistry](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#BeanDefinition和BeanDefinitionRegistry) - * [Bean实例化策略InstantiationStrategy](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#Bean实例化策略InstantiationStrategy) - * [为bean填充属性](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#为bean填充属性) - * [为bean注入bean](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#为bean注入bean) - * [资源和资源加载器](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#资源和资源加载器) - * [在xml文件中定义bean](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#在xml文件中定义bean) - * [容器扩展机制BeanFactoryPostProcess和BeanPostProcessor](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#BeanFactoryPostProcess和BeanPostProcessor) - * [应用上下文ApplicationContext](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#应用上下文ApplicationContext) - * [bean的初始化和销毁方法](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bean的初始化和销毁方法) - * [Aware接口](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#Aware接口) - * [bean作用域,增加prototype的支持](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bean作用域增加prototype的支持) - * [FactoryBean](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#FactoryBean) - * [容器事件和事件监听器](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#容器事件和事件监听器) -* [AOP](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#基础篇AOP) - * [切点表达式](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#切点表达式) - * [基于JDK的动态代理](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#基于JDK的动态代理) - * [基于CGLIB的动态代理](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#基于CGLIB的动态代理) - * [AOP代理工厂ProxyFactory](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#AOP代理工厂) - * [几种常用的Advice: BeforeAdvice/AfterAdvice/AfterReturningAdvice/ThrowsAdvice](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#几种常用的AdviceBeforeAdviceAfterAdviceAfterReturningAdviceThrowsAdvice) - * [PointcutAdvisor:Pointcut和Advice的组合](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#pointcutadvisorpointcut和advice的组合) - * [动态代理融入bean生命周期](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#动态代理融入bean生命周期) - - -#### 扩展篇 -* [PropertyPlaceholderConfigurer](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#propertyplaceholderconfigurer) -* [包扫描](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#包扫描) -* [@Value注解](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#value注解) -* [基于注解@Autowired的依赖注入](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#autowired注解) -* [类型转换(一)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#类型转换一) -* [类型转换(二)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#类型转换二) - -#### 高级篇 -* [解决循环依赖问题(一):没有代理对象](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#解决循环依赖问题一没有代理对象) -* [解决循环依赖问题(二):有代理对象](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#解决循环依赖问题二有代理对象) - -#### Bug fix -* [没有为代理bean设置属性(discovered and fixed by kerwin89)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bug-fix没有为代理bean设置属性discovered-and-fixed-by-kerwin89) - -## 使用方法 -阅读[changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) - -## 提问 -[**点此提问**](https://github.com/DerekYRC/mini-spring/issues/4) - -## 贡献 -欢迎Pull Request - -## 联系我 -手机/微信:**15521077528** - -邮箱:**15521077528@163.com** - -## 参考 -- [《Spring源码深度解析》](https://book.douban.com/subject/25866350/) -- [《Spring 源码解析》](http://svip.iocoder.cn/categories/Spring) -- [《精通Spring 4.x》](https://book.douban.com/subject/26952826/) -- [《tiny-spring》](https://github.com/code4craft/tiny-spring) diff --git a/README_en.md b/README_en.md new file mode 100644 index 0000000..dfdf605 --- /dev/null +++ b/README_en.md @@ -0,0 +1,69 @@ +# mini-spring +[![Build Status](https://img.shields.io/badge/build-passing-brightgreen)](https://github.com/DerekYRC/mini-spring) +[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) +[![Stars](https://img.shields.io/github/stars/DerekYRC/mini-spring)](https://img.shields.io/github/stars/DerekYRC/mini-spring) +[![Forks](https://img.shields.io/github/forks/DerekYRC/mini-spring)](https://img.shields.io/github/forks/DerekYRC/mini-spring) + +English | [简体中文](./README.md) + +## About + +The **mini-spring** is a simplified version of the Spring framework that will help you quickly get familiar with the Spring source code and grasp the core principles of Spring. The core logic of Spring is extracted, the code is extremely simplified, and the core functions of Spring, such as IoC and AOP, resource loaders, event listeners, type conversion, container extension points, bean life cycle and scope, and application context, are retained. + +If this project can help you, please **STAR the project, thank you!!!** + +## Contents +#### Basics +* [IoC](#Ioc) + * [Implement a simple container](#实现一个简单的容器) + * [BeanDefinition and BeanDefinitionRegistry](#BeanDefinition和BeanDefinitionRegistry) + * [Bean Instantiation Strategy](#Bean实例化策略InstantiationStrategy) + * [Populate bean with property values](#为bean填充属性) + * [Populate bean with bean](#为bean注入bean) + * [Resource and ResourceLoader](#资源和资源加载器) + * [Define the bean in the XML file](#在xml文件中定义bean) + * [Container extension mechanism:BeanFactoryPostProcess and BeanPostProcessor](#容器扩展机制BeanFactoryPostProcess和BeanPostProcessor) + * [ApplicationContext](#应用上下文ApplicationContext) + * [Init method and destroy method of bean](#bean的初始化和销毁方法) + * [Aware interface](#Aware接口) + * [Bean scope, added prototype support](#bean作用域,增加prototype的支持) + * [FactoryBean](#FactoryBean) + * [Event and event listener](#容器事件和事件监听器) +* [AOP](#AOP) + * [Pointcut expression](#切点表达式) + * [JDK-based dynamic proxy](#基于JDK的动态代理) + * [CGLIB-based dynamic proxy](#基于CGLIB的动态代理) + * [AOP ProxyFactory](#AOP代理工厂ProxyFactory) + * [Common Advice: BeforeAdvice/AfterAdvice/AfterReturningAdvice/ThrowsAdvice](#几种常用的Advice) + * [PointcutAdvisor:A combination of Pointcut and Advice](#PointcutAdvisor:Pointcut和Advice的组合) + * [Dynamic proxies are integrated into the bean lifecycle](#动态代理融入bean生命周期) + + +#### Expanding +* [PropertyPlaceholderConfigurer](#PropertyPlaceholderConfigurer) +* [Package scan](#包扫描) +* [Value annotation](#Value) +* [Autowired annotation](#Autowired) +* [Type conversion(first part)](#类型转换一) +* [Type conversion(second part)](#类型转换二) + +#### Advanced +* [Solve the problem of circular dependencies(first part): without proxy bean](#解决循环依赖问题一) +* [Solve the problem of circular dependencies(second part): with proxy bean](#解决循环依赖问题二) + +#### Bug fix +* [populate proxy bean with property values(discovered and fixed by kerwin89)](#没有为代理bean设置属性) + +## Usage +Each function point corresponds to a branch. Switch to the branch corresponding to the function point to see the new function. The incremental change point is described in the [changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) file. + +## Contributing +Any contributions you make are greatly appreciated. + +## Contact +Please feel free to ask me any questions related to mini-spring and other technologies. My email is **15521077528@163.com**. + +## Reference +- [《Spring源码深度解析》](https://book.douban.com/subject/25866350/) +- [《精通Spring 4.x》](https://book.douban.com/subject/26952826/) +- [tiny-spring](https://github.com/code4craft/tiny-spring) diff --git a/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java index 4338dd5..97936dd 100644 --- a/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java +++ b/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java @@ -1,14 +1,14 @@ package org.springframework.beans.factory.support; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.config.SingletonBeanRegistry; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - /** * @author derekyi * @date 2020/11/22 @@ -60,7 +60,7 @@ public void registerDisposableBean(String beanName, DisposableBean bean) { } public void destroySingletons() { - Set beanNames = disposableBeans.keySet(); + ArrayList beanNames = new ArrayList<>(disposableBeans.keySet()); for (String beanName : beanNames) { DisposableBean disposableBean = disposableBeans.remove(beanName); try { From 187aebc594c11824add786a4f862dc31d4972022 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Tue, 23 Aug 2022 19:45:01 +0800 Subject: [PATCH 61/81] update readme --- README.md | 2 +- README_en.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4732570..3b69a5e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Stars](https://img.shields.io/github/stars/DerekYRC/mini-spring)](https://img.shields.io/github/stars/DerekYRC/mini-spring) [![Forks](https://img.shields.io/github/forks/DerekYRC/mini-spring)](https://img.shields.io/github/forks/DerekYRC/mini-spring) -[English](./README_en.md) | 简体中文 +**[English](./README_en.md) | 简体中文** **姊妹版:**[**mini-spring-cloud**](https://github.com/DerekYRC/mini-spring-cloud) **(简化版的spring cloud框架)** diff --git a/README_en.md b/README_en.md index dfdf605..6cac413 100644 --- a/README_en.md +++ b/README_en.md @@ -4,7 +4,7 @@ [![Stars](https://img.shields.io/github/stars/DerekYRC/mini-spring)](https://img.shields.io/github/stars/DerekYRC/mini-spring) [![Forks](https://img.shields.io/github/forks/DerekYRC/mini-spring)](https://img.shields.io/github/forks/DerekYRC/mini-spring) -English | [简体中文](./README.md) +**English | [简体中文](./README.md)** ## About From 39e37474a9885fab3227a93b8a658d3bb4f04510 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Tue, 23 Aug 2022 19:50:56 +0800 Subject: [PATCH 62/81] update readme --- README_en.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README_en.md b/README_en.md index 6cac413..79175b2 100644 --- a/README_en.md +++ b/README_en.md @@ -6,6 +6,8 @@ **English | [简体中文](./README.md)** +[**mini-spring-cloud**](https://github.com/DerekYRC/mini-spring-cloud) **(simplified version of the Spring Cloud framework)** + ## About The **mini-spring** is a simplified version of the Spring framework that will help you quickly get familiar with the Spring source code and grasp the core principles of Spring. The core logic of Spring is extracted, the code is extremely simplified, and the core functions of Spring, such as IoC and AOP, resource loaders, event listeners, type conversion, container extension points, bean life cycle and scope, and application context, are retained. From 8b500d5a2a20c42d1732bcfe9d9f461e7067db84 Mon Sep 17 00:00:00 2001 From: DerekYRC <44155264+DerekYRC@users.noreply.github.com> Date: Wed, 23 Nov 2022 09:35:17 +0800 Subject: [PATCH 63/81] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 3b69a5e..e14b408 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,9 @@ 邮箱:**15521077528@163.com** +## 版权说明 +未取得本人书面许可,不得将该项目用于商业用途 + ## 参考 - [《Spring源码深度解析》](https://book.douban.com/subject/25866350/) - [《Spring 源码解析》](http://svip.iocoder.cn/categories/Spring) From c91c533a40122fa32b5c29b676bb71fe33abdecc Mon Sep 17 00:00:00 2001 From: DerekYRC <44155264+DerekYRC@users.noreply.github.com> Date: Wed, 23 Nov 2022 09:48:37 +0800 Subject: [PATCH 64/81] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index e14b408..1695694 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,10 @@ 邮箱:**15521077528@163.com** +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=DerekYRC/mini-spring&type=Date)](https://star-history.com/#DerekYRC/mini-spring&Date) + ## 版权说明 未取得本人书面许可,不得将该项目用于商业用途 From 4d013a6fd741b579a69f464ed3fc3d9ed3db5e25 Mon Sep 17 00:00:00 2001 From: DerekYRC <44155264+DerekYRC@users.noreply.github.com> Date: Fri, 25 Nov 2022 10:30:56 +0800 Subject: [PATCH 65/81] Update changelog.md --- changelog.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index 98aabb8..68dfb6a 100644 --- a/changelog.md +++ b/changelog.md @@ -521,7 +521,7 @@ public class AwareInterfaceTest { ## [bean作用域,增加prototype的支持](#bean作用域增加prototype的支持) > 代码分支:prototype-bean -每次向容器获取prototype作用域bean时,容器都会创建一个新的实例。在BeanDefinition中增加描述bean的作用域的字段scope/singleton/prototype,创建prototype作用域bean时(AbstractAutowireCapableBeanFactory#doCreateBean),不往singletonObjects中增加该bean。prototype作用域bean不执行销毁方法,查看AbstractAutowireCapableBeanFactory#registerDisposableBeanIfNecessary方法。 +每次向容器获取prototype作用域bean时,容器都会创建一个新的实例。在BeanDefinition中增加描述bean的作用域的字段scope,创建prototype作用域bean时(AbstractAutowireCapableBeanFactory#doCreateBean),不往singletonObjects中增加该bean。prototype作用域bean不执行销毁方法,查看AbstractAutowireCapableBeanFactory#registerDisposableBeanIfNecessary方法。 至止,bean的生命周期如下: @@ -626,7 +626,7 @@ public class FactoryBeanTest { ## [容器事件和事件监听器](#容器事件和事件监听器) > 代码分支:event-and-event-listener -ApplicationContext容器提供了完善的时间发布和时间监听功能。 +ApplicationContext容器提供了完善的事件发布和事件监听功能。 ApplicationEventMulticaster接口是注册监听器和发布事件的抽象接口,AbstractApplicationContext包含其实现类实例作为其属性,使得ApplicationContext容器具有注册监听器和发布事件的能力。在AbstractApplicationContext#refresh方法中,会实例化ApplicationEventMulticaster、注册监听器并发布容器刷新事件ContextRefreshedEvent;在AbstractApplicationContext#doClose方法中,发布容器关闭事件ContextClosedEvent。 @@ -1489,4 +1489,4 @@ getBean()时依次检查一级缓存singletonObjects、二级缓存earlySingleto 单测见CircularReferenceWithProxyBeanTest。 -##

====================不容易啊,完美撒花====================

\ No newline at end of file +##

====================不容易啊,完美撒花====================

From 5dfe53b332bdf35db2ef4d3c0c915843b158714b Mon Sep 17 00:00:00 2001 From: DerekYRC <44155264+DerekYRC@users.noreply.github.com> Date: Fri, 25 Nov 2022 10:54:08 +0800 Subject: [PATCH 66/81] Update changelog.md --- changelog.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 68dfb6a..37196a6 100644 --- a/changelog.md +++ b/changelog.md @@ -229,7 +229,7 @@ public class ResourceAndResourceLoaderTest { BeanDefinitionReader是读取bean定义信息的抽象接口,XmlBeanDefinitionReader是从xml文件中读取的实现类。BeanDefinitionReader需要有获取资源的能力,且读取bean定义信息后需要往容器中注册BeanDefinition,因此BeanDefinitionReader的抽象实现类AbstractBeanDefinitionReader拥有ResourceLoader和BeanDefinitionRegistry两个属性。 -由于从xml文件中读取的内容是String类型,所以属性仅支持String类型和引用其他Bean。后面会讲到属性编辑器PropertyEditor,实现类型转换。 +由于从xml文件中读取的内容是String类型,所以属性仅支持String类型和引用其他Bean。后面会讲到类型转换器,实现类型转换。 为了方便后面的讲解和功能实现,并且尽量保持和spring中BeanFactory的继承层次一致,对BeanFactory的继承层次稍微做了调整。 @@ -1466,7 +1466,7 @@ A依赖B,B又依赖A,循环依赖。容器加载时会执行依赖流程: - 步骤二:getBean(b),检查singletonObjects是否包含b,singletonObjects不包含b,实例化B放进singletonObjects,设置属性a,发现依赖A,尝试getBean(a) - 步骤三:getBean(a),检查singletonObjects是否包含a,singletonObjects包含a,返回a - 步骤四:步骤二中的b拿到a,设置属性a,然后返回b -- 步骤五:步骤一种的a拿到b,设置属性b,然后返回a +- 步骤五:步骤一中的a拿到b,设置属性b,然后返回a 可见调整bean放进singletonObjects(人称一级缓存)的时机到bean实例化后即可解决循环依赖问题。但为了和spring保持一致,我们增加一个二级缓存earlySingletonObjects,在bean实例化后将bean放进earlySingletonObjects中(见AbstractAutowireCapableBeanFactory#doCreateBean方法第6行),getBean()时检查一级缓存singletonObjects和二级缓存earlySingletonObjects中是否包含该bean,包含则直接返回(见AbstractBeanFactory#getBean第1行)。 From 9d9ed15a5a3add28768c9dcbd6ef522e1c756682 Mon Sep 17 00:00:00 2001 From: DerekYRC <44155264+DerekYRC@users.noreply.github.com> Date: Mon, 5 Dec 2022 10:58:30 +0800 Subject: [PATCH 67/81] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1695694..64eb127 100644 --- a/README.md +++ b/README.md @@ -65,10 +65,10 @@ ## 贡献 欢迎Pull Request -## 联系我 -手机/微信:**15521077528** +## 关于我 +[**点此了解**](https://github.com/DerekYRC) -邮箱:**15521077528@163.com** +手机/微信:**15521077528** 邮箱:**15521077528@163.com** ## Star History From bf1f13df4afb7037e97fdca276f8856d5386dfa5 Mon Sep 17 00:00:00 2001 From: zqc <1820901097@qq.com> Date: Wed, 21 Dec 2022 18:51:06 +0800 Subject: [PATCH 68/81] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=87=92=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=EF=BC=8C=E9=87=8D=E5=86=99=E4=BA=86AOP=E7=9A=84?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springframework/aop/AdvisedSupport.java | 42 ++++++++++++++++++- .../org/springframework/aop/AfterAdvice.java | 10 +++++ .../aop/AfterReturningAdvice.java | 13 ++++++ .../aop/framework/AdvisorChainFactory.java | 17 ++++++++ .../aop/framework/CglibAopProxy.java | 34 ++++++++------- .../framework/DefaultAdvisorChainFactory.java | 42 +++++++++++++++++++ .../aop/framework/JdkDynamicAopProxy.java | 28 +++++++++---- .../aop/framework/ProxyFactory.java | 17 ++++---- .../framework/ReflectiveMethodInvocation.java | 31 ++++++++++++-- .../AfterReturningAdviceInterceptor.java | 30 +++++++++++++ .../MethodBeforeAdviceInterceptor.java | 10 ++--- .../DefaultAdvisorAutoProxyCreator.java | 13 +++--- .../beans/factory/config/BeanDefinition.java | 10 +++++ .../support/DefaultListableBeanFactory.java | 2 +- .../factory/xml/XmlBeanDefinitionReader.java | 4 +- .../test/aop/DynamicProxyTest.java | 8 ++-- .../test/aop/ProxyFactoryTest.java | 38 +++++++++++++++++ .../org/springframework/test/bean/Car.java | 11 ++++- .../common/WorldServiceAfterReturnAdvice.java | 12 ++++++ .../test/ioc/LazyInitTest.java | 18 ++++++++ src/test/resources/auto-proxy.xml | 12 ++++-- src/test/resources/lazy-test.xml | 8 ++++ 22 files changed, 349 insertions(+), 61 deletions(-) create mode 100644 src/main/java/org/springframework/aop/AfterAdvice.java create mode 100644 src/main/java/org/springframework/aop/AfterReturningAdvice.java create mode 100644 src/main/java/org/springframework/aop/framework/AdvisorChainFactory.java create mode 100644 src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java create mode 100644 src/main/java/org/springframework/aop/framework/adapter/AfterReturningAdviceInterceptor.java create mode 100644 src/test/java/org/springframework/test/aop/ProxyFactoryTest.java create mode 100644 src/test/java/org/springframework/test/common/WorldServiceAfterReturnAdvice.java create mode 100644 src/test/java/org/springframework/test/ioc/LazyInitTest.java create mode 100644 src/test/resources/lazy-test.xml diff --git a/src/main/java/org/springframework/aop/AdvisedSupport.java b/src/main/java/org/springframework/aop/AdvisedSupport.java index 17bfe2b..f42c1ed 100644 --- a/src/main/java/org/springframework/aop/AdvisedSupport.java +++ b/src/main/java/org/springframework/aop/AdvisedSupport.java @@ -1,10 +1,18 @@ package org.springframework.aop; import org.aopalliance.intercept.MethodInterceptor; +import org.springframework.aop.framework.AdvisorChainFactory; +import org.springframework.aop.framework.DefaultAdvisorChainFactory; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** - * @author derekyi - * @date 2020/12/6 + * @author zqc + * @date 2022/12/16 */ public class AdvisedSupport { @@ -17,6 +25,15 @@ public class AdvisedSupport { private MethodMatcher methodMatcher; + private transient Map> methodCache; + + AdvisorChainFactory advisorChainFactory = new DefaultAdvisorChainFactory(); + + private List advisors = new ArrayList<>(); + + public AdvisedSupport() { + this.methodCache = new ConcurrentHashMap<>(32); + } public boolean isProxyTargetClass() { return proxyTargetClass; } @@ -25,6 +42,14 @@ public void setProxyTargetClass(boolean proxyTargetClass) { this.proxyTargetClass = proxyTargetClass; } + public void addAdvisor(Advisor advisor) { + advisors.add(advisor); + } + + public List getAdvisors() { + return advisors; + } + public TargetSource getTargetSource() { return targetSource; } @@ -48,4 +73,17 @@ public MethodMatcher getMethodMatcher() { public void setMethodMatcher(MethodMatcher methodMatcher) { this.methodMatcher = methodMatcher; } + /** + * 用来返回方法的拦截器链 + */ + public List getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) { + Integer cacheKey=method.hashCode(); + List cached = this.methodCache.get(cacheKey); + if (cached == null) { + cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice( + this, method, targetClass); + this.methodCache.put(cacheKey, cached); + } + return cached; + } } diff --git a/src/main/java/org/springframework/aop/AfterAdvice.java b/src/main/java/org/springframework/aop/AfterAdvice.java new file mode 100644 index 0000000..6cb21ff --- /dev/null +++ b/src/main/java/org/springframework/aop/AfterAdvice.java @@ -0,0 +1,10 @@ +package org.springframework.aop; + +import org.aopalliance.aop.Advice; + +/** + * @author zqc + * @date 2022/12/16 + */ +public interface AfterAdvice extends Advice { +} diff --git a/src/main/java/org/springframework/aop/AfterReturningAdvice.java b/src/main/java/org/springframework/aop/AfterReturningAdvice.java new file mode 100644 index 0000000..ada354e --- /dev/null +++ b/src/main/java/org/springframework/aop/AfterReturningAdvice.java @@ -0,0 +1,13 @@ +package org.springframework.aop; + +import org.springframework.aop.AfterAdvice; + +import java.lang.reflect.Method; + +/** + * @author zqc + * @date 2022/12/16 + */ +public interface AfterReturningAdvice extends AfterAdvice { + void afterReturning( Object returnValue, Method method, Object[] args, Object target) throws Throwable; +} diff --git a/src/main/java/org/springframework/aop/framework/AdvisorChainFactory.java b/src/main/java/org/springframework/aop/framework/AdvisorChainFactory.java new file mode 100644 index 0000000..a84fff5 --- /dev/null +++ b/src/main/java/org/springframework/aop/framework/AdvisorChainFactory.java @@ -0,0 +1,17 @@ +package org.springframework.aop.framework; + +import org.springframework.aop.AdvisedSupport; + +import java.lang.reflect.Method; +import java.util.List; + +/** + * @author zqc + * @date 2022/12/16 + */ +public interface AdvisorChainFactory { + + + List getInterceptorsAndDynamicInterceptionAdvice(AdvisedSupport config, Method method, Class targetClass); + +} diff --git a/src/main/java/org/springframework/aop/framework/CglibAopProxy.java b/src/main/java/org/springframework/aop/framework/CglibAopProxy.java index 20efa77..782754b 100644 --- a/src/main/java/org/springframework/aop/framework/CglibAopProxy.java +++ b/src/main/java/org/springframework/aop/framework/CglibAopProxy.java @@ -6,12 +6,11 @@ import org.springframework.aop.AdvisedSupport; import java.lang.reflect.Method; +import java.util.List; /** - * cgli动态代理 - * - * @author derekyi - * @date 2020/12/6 + * @author zqc + * @date 2022/12/17 */ public class CglibAopProxy implements AopProxy { @@ -24,6 +23,7 @@ public CglibAopProxy(AdvisedSupport advised) { @Override public Object getProxy() { + // 创建动态代理增强类 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(advised.getTargetSource().getTarget().getClass()); enhancer.setInterfaces(advised.getTargetSource().getTargetClass()); @@ -43,13 +43,17 @@ private DynamicAdvisedInterceptor(AdvisedSupport advised) { } @Override - public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { - CglibMethodInvocation methodInvocation = new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, objects, methodProxy); - if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) { - //代理方法 - return advised.getMethodInterceptor().invoke(methodInvocation); - } - return methodInvocation.proceed(); + public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { + // 获取目标对象 + Object target=advised.getTargetSource().getTarget(); + Class targetClass = target.getClass(); + Object retVal = null; + List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); + CglibMethodInvocation methodInvocation = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy); + if(chain==null||chain.isEmpty()){ + retVal =methodProxy.invoke(target, args); + }else retVal=methodInvocation.proceed(); + return retVal; } } @@ -57,14 +61,16 @@ private static class CglibMethodInvocation extends ReflectiveMethodInvocation { private final MethodProxy methodProxy; - public CglibMethodInvocation(Object target, Method method, Object[] arguments, MethodProxy methodProxy) { - super(target, method, arguments); + public CglibMethodInvocation(Object proxy, Object target, Method method, + Object[] arguments, Class targetClass, + List interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) { + super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers); this.methodProxy = methodProxy; } @Override public Object proceed() throws Throwable { - return this.methodProxy.invoke(this.target, this.arguments); + return super.proceed(); } } } diff --git a/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java b/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java new file mode 100644 index 0000000..2a0377a --- /dev/null +++ b/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java @@ -0,0 +1,42 @@ +package org.springframework.aop.framework; + +import org.aopalliance.intercept.MethodInterceptor; +import org.springframework.aop.AdvisedSupport; +import org.springframework.aop.Advisor; +import org.springframework.aop.MethodMatcher; +import org.springframework.aop.PointcutAdvisor; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * @author zqc + * @date 2022/12/17 + */ +public class DefaultAdvisorChainFactory implements AdvisorChainFactory { + @Override + public List getInterceptorsAndDynamicInterceptionAdvice(AdvisedSupport config, Method method, Class targetClass) { + Advisor[] advisors = config.getAdvisors().toArray(new Advisor[0]); + List interceptorList = new ArrayList<>(advisors.length); + Class actualClass = (targetClass != null ? targetClass : method.getDeclaringClass()); + for (Advisor advisor : advisors) { + if (advisor instanceof PointcutAdvisor) { + // Add it conditionally. + PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor; + // 校验当前Advisor是否适用于当前对象 + if (pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) { + MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher(); + boolean match; + // 校验Advisor是否应用到当前方法上 + match = mm.matches(method,actualClass); + if (match) { + MethodInterceptor interceptor = (MethodInterceptor) advisor.getAdvice(); + interceptorList.add(interceptor); + } + } + } + } + return interceptorList; + } +} diff --git a/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java b/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java index 27972cc..2f59d9c 100644 --- a/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java +++ b/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java @@ -1,17 +1,18 @@ package org.springframework.aop.framework; -import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.AdvisedSupport; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.util.List; /** * JDK动态代理 * - * @author derekyi - * @date 2020/12/5 + * @author zqc + * @date 2022/12/19 */ public class JdkDynamicAopProxy implements AopProxy, InvocationHandler { @@ -33,11 +34,22 @@ public Object getProxy() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) { - //代理方法 - MethodInterceptor methodInterceptor = advised.getMethodInterceptor(); - return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(), method, args)); + // 获取目标对象 + Object target=advised.getTargetSource().getTarget(); + Class targetClass = target.getClass(); + Object retVal = null; + // 获取拦截器链 + List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); + if(chain==null||chain.isEmpty()){ + return method.invoke(target, args); + }else{ + // 将拦截器统一封装成ReflectiveMethodInvocation + MethodInvocation invocation = + new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); + // Proceed to the joinpoint through the interceptor chain. + // 执行拦截器链 + retVal = invocation.proceed(); } - return method.invoke(advised.getTargetSource().getTarget(), args); + return retVal; } } diff --git a/src/main/java/org/springframework/aop/framework/ProxyFactory.java b/src/main/java/org/springframework/aop/framework/ProxyFactory.java index 6df930e..f87a4f7 100644 --- a/src/main/java/org/springframework/aop/framework/ProxyFactory.java +++ b/src/main/java/org/springframework/aop/framework/ProxyFactory.java @@ -3,15 +3,13 @@ import org.springframework.aop.AdvisedSupport; /** - * @author derekyi - * @date 2020/12/6 + * @author zqc + * @date 2022/12/16 */ -public class ProxyFactory { +public class ProxyFactory extends AdvisedSupport{ - private AdvisedSupport advisedSupport; - public ProxyFactory(AdvisedSupport advisedSupport) { - this.advisedSupport = advisedSupport; + public ProxyFactory() { } public Object getProxy() { @@ -19,10 +17,9 @@ public Object getProxy() { } private AopProxy createAopProxy() { - if (advisedSupport.isProxyTargetClass()) { - return new CglibAopProxy(advisedSupport); + if (this.isProxyTargetClass()||this.getTargetSource().getTargetClass().length==0) { + return new CglibAopProxy(this); } - - return new JdkDynamicAopProxy(advisedSupport); + return new JdkDynamicAopProxy(this); } } diff --git a/src/main/java/org/springframework/aop/framework/ReflectiveMethodInvocation.java b/src/main/java/org/springframework/aop/framework/ReflectiveMethodInvocation.java index 97b2121..9a5da41 100644 --- a/src/main/java/org/springframework/aop/framework/ReflectiveMethodInvocation.java +++ b/src/main/java/org/springframework/aop/framework/ReflectiveMethodInvocation.java @@ -1,31 +1,54 @@ package org.springframework.aop.framework; +import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Method; +import java.util.List; /** - * @author derekyi - * @date 2020/12/6 + * @author zqc + * @date 2022/12/16 */ public class ReflectiveMethodInvocation implements MethodInvocation { + protected final Object proxy; + protected final Object target; protected final Method method; protected final Object[] arguments; - public ReflectiveMethodInvocation(Object target, Method method, Object[] arguments) { + protected final Class targetClass; + + protected final List interceptorsAndDynamicMethodMatchers; + + private int currentInterceptorIndex = -1; + + public ReflectiveMethodInvocation(Object proxy,Object target, Method method, Object[] arguments,Class targetClass,List chain) { + this.proxy=proxy; this.target = target; this.method = method; this.arguments = arguments; + this.targetClass=targetClass; + this.interceptorsAndDynamicMethodMatchers=chain; } @Override public Object proceed() throws Throwable { - return method.invoke(target, arguments); + // 初始currentInterceptorIndex为-1,每调用一次proceed就把currentInterceptorIndex+1 + if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { + // 当调用次数 = 拦截器个数时 + // 触发当前method方法 + return method.invoke(this.target, this.arguments); + } + + Object interceptorOrInterceptionAdvice = + this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); + // 普通拦截器,直接触发拦截器invoke方法 + return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); } @Override diff --git a/src/main/java/org/springframework/aop/framework/adapter/AfterReturningAdviceInterceptor.java b/src/main/java/org/springframework/aop/framework/adapter/AfterReturningAdviceInterceptor.java new file mode 100644 index 0000000..d1b4070 --- /dev/null +++ b/src/main/java/org/springframework/aop/framework/adapter/AfterReturningAdviceInterceptor.java @@ -0,0 +1,30 @@ +package org.springframework.aop.framework.adapter; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.AfterAdvice; +import org.springframework.aop.AfterReturningAdvice; + +/** + * @author zqc + * @date 2022/12/20 + */ +public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice { + + private AfterReturningAdvice advice; + + public AfterReturningAdviceInterceptor() { + } + + public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) { + this.advice = advice; + } + + + @Override + public Object invoke(MethodInvocation mi) throws Throwable { + Object retVal = mi.proceed(); + this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis()); + return retVal; + } +} diff --git a/src/main/java/org/springframework/aop/framework/adapter/MethodBeforeAdviceInterceptor.java b/src/main/java/org/springframework/aop/framework/adapter/MethodBeforeAdviceInterceptor.java index 2b3e365..d4fd0b7 100644 --- a/src/main/java/org/springframework/aop/framework/adapter/MethodBeforeAdviceInterceptor.java +++ b/src/main/java/org/springframework/aop/framework/adapter/MethodBeforeAdviceInterceptor.java @@ -2,13 +2,14 @@ import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.BeforeAdvice; import org.springframework.aop.MethodBeforeAdvice; /** * @author derekyi * @date 2020/12/6 */ -public class MethodBeforeAdviceInterceptor implements MethodInterceptor { +public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice { private MethodBeforeAdvice advice; @@ -24,9 +25,8 @@ public void setAdvice(MethodBeforeAdvice advice) { } @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - //在执行被代理方法之前,先执行before advice操作 - this.advice.before(invocation.getMethod(), invocation.getArguments(), invocation.getThis()); - return invocation.proceed(); + public Object invoke(MethodInvocation mi) throws Throwable { + this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis()); + return mi.proceed(); } } diff --git a/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java b/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java index b1d1ecc..06256b7 100644 --- a/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java +++ b/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java @@ -49,20 +49,17 @@ protected Object wrapIfNecessary(Object bean, String beanName) { Collection advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values(); try { + ProxyFactory proxyFactory = new ProxyFactory(); for (AspectJExpressionPointcutAdvisor advisor : advisors) { ClassFilter classFilter = advisor.getPointcut().getClassFilter(); if (classFilter.matches(bean.getClass())) { - AdvisedSupport advisedSupport = new AdvisedSupport(); TargetSource targetSource = new TargetSource(bean); - - advisedSupport.setTargetSource(targetSource); - advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice()); - advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher()); - - //返回代理对象 - return new ProxyFactory(advisedSupport).getProxy(); + proxyFactory.setTargetSource(targetSource); + proxyFactory.addAdvisor(advisor); + proxyFactory.setMethodMatcher(advisor.getPointcut().getMethodMatcher()); } } + if(!proxyFactory.getAdvisors().isEmpty()) return proxyFactory.getProxy(); } catch (Exception ex) { throw new BeansException("Error create proxy bean for: " + beanName, ex); } diff --git a/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java b/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java index 6719772..db9a1c3 100644 --- a/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java +++ b/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java @@ -30,6 +30,8 @@ public class BeanDefinition { private boolean prototype = false; + private boolean lazyInit=false; + public BeanDefinition(Class beanClass) { this(beanClass, null); } @@ -97,4 +99,12 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(beanClass); } + + public void setLazyInit(boolean b){ + lazyInit=b; + } + + public boolean isLazyInit(){ + return lazyInit; + } } diff --git a/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index d4eb956..659d0c8 100644 --- a/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -73,7 +73,7 @@ public String[] getBeanDefinitionNames() { @Override public void preInstantiateSingletons() throws BeansException { beanDefinitionMap.forEach((beanName, beanDefinition) -> { - if(beanDefinition.isSingleton()){ + if(beanDefinition.isSingleton()&&!beanDefinition.isLazyInit()){ getBean(beanName); } }); diff --git a/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java b/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java index 8882ddf..935421c 100644 --- a/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java +++ b/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java @@ -38,6 +38,7 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { public static final String INIT_METHOD_ATTRIBUTE = "init-method"; public static final String DESTROY_METHOD_ATTRIBUTE = "destroy-method"; public static final String SCOPE_ATTRIBUTE = "scope"; + public static final String LAZYINIT_ATTRIBUTE = "lazyInit"; public static final String BASE_PACKAGE_ATTRIBUTE = "base-package"; public static final String COMPONENT_SCAN_ELEMENT = "component-scan"; @@ -94,7 +95,7 @@ protected void doLoadBeanDefinitions(InputStream inputStream) throws DocumentExc String initMethodName = bean.attributeValue(INIT_METHOD_ATTRIBUTE); String destroyMethodName = bean.attributeValue(DESTROY_METHOD_ATTRIBUTE); String beanScope = bean.attributeValue(SCOPE_ATTRIBUTE); - + String lazyInit=bean.attributeValue(LAZYINIT_ATTRIBUTE); Class clazz; try { clazz = Class.forName(className); @@ -111,6 +112,7 @@ protected void doLoadBeanDefinitions(InputStream inputStream) throws DocumentExc BeanDefinition beanDefinition = new BeanDefinition(clazz); beanDefinition.setInitMethodName(initMethodName); beanDefinition.setDestroyMethodName(destroyMethodName); + beanDefinition.setLazyInit("true".equals(lazyInit)); if (StrUtil.isNotEmpty(beanScope)) { beanDefinition.setScope(beanScope); } diff --git a/src/test/java/org/springframework/test/aop/DynamicProxyTest.java b/src/test/java/org/springframework/test/aop/DynamicProxyTest.java index bb2bf49..6e34ac4 100644 --- a/src/test/java/org/springframework/test/aop/DynamicProxyTest.java +++ b/src/test/java/org/springframework/test/aop/DynamicProxyTest.java @@ -55,12 +55,12 @@ public void testCglibDynamicProxy() throws Exception { public void testProxyFactory() throws Exception { // 使用JDK动态代理 advisedSupport.setProxyTargetClass(false); - WorldService proxy = (WorldService) new ProxyFactory(advisedSupport).getProxy(); + WorldService proxy = (WorldService) new ProxyFactory().getProxy(); proxy.explode(); // 使用CGLIB动态代理 advisedSupport.setProxyTargetClass(true); - proxy = (WorldService) new ProxyFactory(advisedSupport).getProxy(); + proxy = (WorldService) new ProxyFactory().getProxy(); proxy.explode(); } @@ -71,7 +71,7 @@ public void testBeforeAdvice() throws Exception { MethodBeforeAdviceInterceptor methodInterceptor = new MethodBeforeAdviceInterceptor(beforeAdvice); advisedSupport.setMethodInterceptor(methodInterceptor); - WorldService proxy = (WorldService) new ProxyFactory(advisedSupport).getProxy(); + WorldService proxy = (WorldService) new ProxyFactory().getProxy(); proxy.explode(); } @@ -96,7 +96,7 @@ public void testAdvisor() throws Exception { advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher()); // advisedSupport.setProxyTargetClass(true); //JDK or CGLIB - WorldService proxy = (WorldService) new ProxyFactory(advisedSupport).getProxy(); + WorldService proxy = (WorldService) new ProxyFactory().getProxy(); proxy.explode(); } } diff --git a/src/test/java/org/springframework/test/aop/ProxyFactoryTest.java b/src/test/java/org/springframework/test/aop/ProxyFactoryTest.java new file mode 100644 index 0000000..21efa1f --- /dev/null +++ b/src/test/java/org/springframework/test/aop/ProxyFactoryTest.java @@ -0,0 +1,38 @@ +package org.springframework.test.aop; + +import org.junit.Test; +import org.springframework.aop.TargetSource; +import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor; +import org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor; +import org.springframework.test.common.WorldServiceAfterReturnAdvice; +import org.springframework.test.common.WorldServiceBeforeAdvice; +import org.springframework.test.service.WorldService; +import org.springframework.test.service.WorldServiceImpl; + +public class ProxyFactoryTest { + @Test + public void testAdvisor() throws Exception { + WorldService worldService = new WorldServiceImpl(); + + //Advisor是Pointcut和Advice的组合 + String expression = "execution(* org.springframework.test.service.WorldService.explode(..))"; + AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor(); + advisor.setExpression(expression); + MethodBeforeAdviceInterceptor methodInterceptor = new MethodBeforeAdviceInterceptor(new WorldServiceBeforeAdvice()); + advisor.setAdvice(methodInterceptor); + AspectJExpressionPointcutAdvisor advisor1=new AspectJExpressionPointcutAdvisor(); + advisor1.setExpression(expression); + AfterReturningAdviceInterceptor afterReturningAdviceInterceptor=new AfterReturningAdviceInterceptor(new WorldServiceAfterReturnAdvice()); + advisor1.setAdvice(afterReturningAdviceInterceptor); + ProxyFactory factory = new ProxyFactory(); + TargetSource targetSource = new TargetSource(worldService); + factory.setTargetSource(targetSource); + factory.setProxyTargetClass(true); + factory.addAdvisor(advisor); + factory.addAdvisor(advisor1); + WorldService proxy = (WorldService) factory.getProxy(); + proxy.explode(); + } +} diff --git a/src/test/java/org/springframework/test/bean/Car.java b/src/test/java/org/springframework/test/bean/Car.java index 890c0f4..1cb5433 100644 --- a/src/test/java/org/springframework/test/bean/Car.java +++ b/src/test/java/org/springframework/test/bean/Car.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import java.text.SimpleDateFormat; import java.time.LocalDate; /** @@ -16,6 +17,8 @@ public class Car { private LocalDate produceDate; + private long date; + @Value("${brand}") private String brand; @@ -42,7 +45,13 @@ public LocalDate getProduceDate() { public void setProduceDate(LocalDate produceDate) { this.produceDate = produceDate; } - + public void init(){ + date=System.currentTimeMillis(); + } + public void showTime(){ + SimpleDateFormat dateFormat= new SimpleDateFormat("yyyy-MM-dd :hh:mm:ss"); + System.out.println(date+":bean create"); + } @Override public String toString() { return "Car{" + diff --git a/src/test/java/org/springframework/test/common/WorldServiceAfterReturnAdvice.java b/src/test/java/org/springframework/test/common/WorldServiceAfterReturnAdvice.java new file mode 100644 index 0000000..db26f53 --- /dev/null +++ b/src/test/java/org/springframework/test/common/WorldServiceAfterReturnAdvice.java @@ -0,0 +1,12 @@ +package org.springframework.test.common; + +import org.springframework.aop.AfterReturningAdvice; + +import java.lang.reflect.Method; + +public class WorldServiceAfterReturnAdvice implements AfterReturningAdvice { + @Override + public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { + System.out.println("AfterAdvice: do something after the earth explodes"); + } +} diff --git a/src/test/java/org/springframework/test/ioc/LazyInitTest.java b/src/test/java/org/springframework/test/ioc/LazyInitTest.java new file mode 100644 index 0000000..3365ad3 --- /dev/null +++ b/src/test/java/org/springframework/test/ioc/LazyInitTest.java @@ -0,0 +1,18 @@ +package org.springframework.test.ioc; + +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.test.bean.Car; + +import java.util.concurrent.TimeUnit; + +public class LazyInitTest { + @Test + public void testLazyInit() throws InterruptedException { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:lazy-test.xml"); + System.out.println(System.currentTimeMillis()+":applicationContext-over"); + TimeUnit.SECONDS.sleep(1); + Car c= (Car) applicationContext.getBean("car"); + c.showTime(); + } +} diff --git a/src/test/resources/auto-proxy.xml b/src/test/resources/auto-proxy.xml index ffa3baa..8ae2ee2 100644 --- a/src/test/resources/auto-proxy.xml +++ b/src/test/resources/auto-proxy.xml @@ -15,12 +15,18 @@ - + + + + - + + + + - \ No newline at end of file + diff --git a/src/test/resources/lazy-test.xml b/src/test/resources/lazy-test.xml new file mode 100644 index 0000000..672aed4 --- /dev/null +++ b/src/test/resources/lazy-test.xml @@ -0,0 +1,8 @@ + + + + + + From e142da1a6030cc3fc8856a2465110c457368208b Mon Sep 17 00:00:00 2001 From: zqc <1820901097@qq.com> Date: Thu, 22 Dec 2022 18:54:18 +0800 Subject: [PATCH 69/81] =?UTF-8?q?=E4=BF=AE=E6=94=B9changelog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- assets/chainProceed.png | Bin 0 -> 164252 bytes changelog.md | 438 ++++++++++++++++++ .../support/DefaultListableBeanFactory.java | 1 + .../test/aop/ProxyFactoryTest.java | 19 +- .../test/ioc/LazyInitTest.java | 2 +- src/test/resources/lazy-test.xml | 2 +- 7 files changed, 454 insertions(+), 11 deletions(-) create mode 100644 assets/chainProceed.png diff --git a/README.md b/README.md index 64eb127..b80925a 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,9 @@ * [解决循环依赖问题(一):没有代理对象](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#解决循环依赖问题一没有代理对象) * [解决循环依赖问题(二):有代理对象](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#解决循环依赖问题二有代理对象) -#### Bug fix +#### 其他 * [没有为代理bean设置属性(discovered and fixed by kerwin89)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bug-fix没有为代理bean设置属性discovered-and-fixed-by-kerwin89) +* [支持懒加载和多个增强(by zqczgl)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#支持懒加载和多个增强(By zqczgl)) ## 使用方法 阅读[changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) diff --git a/assets/chainProceed.png b/assets/chainProceed.png new file mode 100644 index 0000000000000000000000000000000000000000..bda72e3cd2dc8ed125201a0d5a1658e5326b195d GIT binary patch literal 164252 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�)yz+}=!36TBnuk=jg-*+`E+P^yx+|o?Re>1Yf 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 JNzEbRe*i0F9+3b5 literal 0 HcmV?d00001 diff --git a/changelog.md b/changelog.md index 37196a6..12e5d8c 100644 --- a/changelog.md +++ b/changelog.md @@ -624,6 +624,7 @@ public class FactoryBeanTest { ``` ## [容器事件和事件监听器](#容器事件和事件监听器) + > 代码分支:event-and-event-listener ApplicationContext容器提供了完善的事件发布和事件监听功能。 @@ -890,6 +891,7 @@ public class DynamicProxyTest { ``` ## [动态代理融入bean生命周期](#动态代理融入bean生命周期) + > 代码分支:auto-proxy 结合前面讲解的bean的生命周期,BeanPostProcessor处理阶段可以修改和替换bean,正好可以在此阶段返回代理对象替换原对象。不过我们引入一种特殊的BeanPostProcessor——InstantiationAwareBeanPostProcessor,如果InstantiationAwareBeanPostProcessor处理阶段返回代理对象,会导致短路,不会继续走原来的创建bean的流程,具体实现查看AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation。 @@ -1489,4 +1491,440 @@ getBean()时依次检查一级缓存singletonObjects、二级缓存earlySingleto 单测见CircularReferenceWithProxyBeanTest。 +## [支持懒加载和多个增强(By zqczgl)](#支持懒加载和多个增强(By zqczgl)) + +### [懒加载](#懒加载) + +> 代码分支: lazy-init-and-multi-advice + +事实上,并不是所有的bean在初始化容器的时候都会创建。随着项目规模的不断扩大,bean的数目也越来越多。如果每次启动容器都需要加载大量的bean,这无疑会带来大量的资源浪费。所有spring提供了懒加载机制,我们可以将我们认为暂时用不到的bean设为懒加载,这样只有在我们需要这个bean的时候这个bean才会被创建。 + +测试 + +lazy-test.xml + +```java +//只有当bean是单例且不为懒加载才会被创建 +public void preInstantiateSingletons() throws BeansException { + beanDefinitionMap.forEach((beanName, beanDefinition) -> { + if(beanDefinition.isSingleton()&&!beanDefinition.isLazyInit()){ + getBean(beanName); + } + }); + } +``` + +```java +public class LazyInitTest { + @Test + public void testLazyInit() throws InterruptedException { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:lazy-test.xml"); + System.out.println(System.currentTimeMillis()+":applicationContext-over"); + TimeUnit.SECONDS.sleep(1); + Car c= (Car) applicationContext.getBean("car"); + c.showTime();//显示bean的创建时间 + } +} +``` + +```xml + + + + + + + +``` + +关闭懒加载的输出: + +``` +1671698959957:applicationContext-over +1671698959951:bean create +``` + +开启懒加载: + +``` +1671699030293:applicationContext-over +1671699031328:bean create +``` + +可以清楚的看到开启和不开启懒加载bean的创建时机的差异 + +### [多个切面匹配同一方法](#多个切面匹配同一方法) + +> 代码分支: lazy-init-and-multi-advice + +虽然在前面我们完成了对方法的增强,但并不完美。我们的目前的代码只能支持对方法的单个增强。作为spring的核心功能如果不支持多切面的话有点太别扭了。spring利用了拦截器链来完成了对多个切面的支持。 + +#### [ProxyFactory](#ProxyFactory) + +让我们从ProxyFactory开始,来看一下代理对象的整个创建流程。至于为什么从ProxyFactory开,这是因为代理对象最终是用ProxyFactory的getProxy()函数来获得的。 + +```java +public class ProxyFactory extends AdvisedSupport{ + + + public ProxyFactory() { + } + + public Object getProxy() { + return createAopProxy().getProxy(); + } + + private AopProxy createAopProxy() { + if (this.isProxyTargetClass()||this.getTargetSource().getTargetClass().length==0) { + return new CglibAopProxy(this); + } + return new JdkDynamicAopProxy(this); + } +} +``` + +为了更贴合spring的实现,这里更改了ProxyFactory使其继承了AdvisedSupport,正如spring源码中做的那样。 + +#### [基于JDK动态代理](#基于JDK动态代理) + +ProxyFactory只是简单的做了下选择,当我们设置proxyTargetClass属性或者被代理对象没有接口时会调用cjlib动态代理,否则调用jdk动态代理。二者实现并没有太大区别,这里只贴出jdk动态代理的实现。 + +```java + public Object getProxy() { + return Proxy.newProxyInstance(getClass().getClassLoader(), advised.getTargetSource().getTargetClass(), this); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + // 获取目标对象 + Object target=advised.getTargetSource().getTarget(); + Class targetClass = target.getClass(); + Object retVal = null; + // 获取拦截器链 + List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); + if(chain==null||chain.isEmpty()){ + return method.invoke(target, args); + }else{ + // 将拦截器统一封装成ReflectiveMethodInvocation + MethodInvocation invocation = + new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); + // Proceed to the joinpoint through the interceptor chain. + // 执行拦截器链 + retVal = invocation.proceed(); + } + return retVal; + } +``` + +jdk动态代理可以分为获取拦截器链,将拦截器统一封装成ReflectiveMethodInvocation,执行拦截器链三部分。我们来逐一看一下这三部分。 + +##### [1. 获取拦截器链](#1. 获取拦截器链) + +首先将获取到所有与当前method匹配的advice(增强),跟踪getInterceptorsAndDynamicInterceptionAdvice代码,我们发现Spring AOP也使用缓存进行提高性能,如果该方法已经获取过拦截器,则直接取缓存,否则通过advisorChainFactory获取拦截器链。AdvisorChainFactory是用来获得拦截器链接口。它的一个实现类为DefaultAdvisorChainFactory + +AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice: + +```java + public List getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) { + Integer cacheKey=method.hashCode(); + List cached = this.methodCache.get(cacheKey); + if (cached == null) { + cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice( + this, method, targetClass); + this.methodCache.put(cacheKey, cached); + } + return cached; + } +``` + +整体代码并不复杂,首先获取所有Advisor(切面),通过pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)校验当前代理对象是否匹配该Advisor,再通过pointcutAdvisor.getPointcut().getMethodMatcher()校验是否匹配当前调用method。如果通过校验,则提取advisor中的interceptors增强,添加到interceptorList中。这里可能有读者会疑惑,我们明明是要获取MethodInterceptor,可AdvisedSupport的getAdvice()返回的是Advice(增强),其实如果我们点开MethodInterceptor的源码,我们会发现MethodInterceptor继承了Interceptor接口,而Interceptor又继承了Advice接口。因为这里的Advice和MethodInterceptor我们都是用的AOP联盟的接口,所以特此说明一下。 + +DefultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice + +```java +public List getInterceptorsAndDynamicInterceptionAdvice(AdvisedSupport config, Method method, Class targetClass) { + Advisor[] advisors = config.getAdvisors().toArray(new Advisor[0]); + List interceptorList = new ArrayList<>(advisors.length); + Class actualClass = (targetClass != null ? targetClass : method.getDeclaringClass()); + for (Advisor advisor : advisors) { + if (advisor instanceof PointcutAdvisor) { + // Add it conditionally. + PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor; + // 校验当前Advisor是否适用于当前对象 + if (pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) { + MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher(); + boolean match; + // 校验Advisor是否应用到当前方法上 + match = mm.matches(method,actualClass); + if (match) { + MethodInterceptor interceptor = (MethodInterceptor) advisor.getAdvice(); + interceptorList.add(interceptor); + } + } + } + } + return interceptorList; + } +``` + +##### [2.将拦截器封装成ReflectiveMethodInvocation](#2.将拦截器封装成ReflectiveMethodInvocation) + +这里也是重写了ReflectiveMethodInvocation的实现,来支持多切面。 + +```java + public ReflectiveMethodInvocation(Object proxy,Object target, Method method, Object[] arguments,Class targetClass,List chain) { + this.proxy=proxy; + this.target = target; + this.method = method; + this.arguments = arguments; + this.targetClass=targetClass; + this.interceptorsAndDynamicMethodMatchers=chain; + } +``` + + + +##### [3. 执行拦截器链](#3. 执行拦截器链) + +spring能够保证多个切面同时匹配同一方法的而不出现乱序的关键就在下面一段代码了。 + +ReflectiveMethodInvocation#proceed() + +```java + public Object proceed() throws Throwable { + // 初始currentInterceptorIndex为-1,每调用一次proceed就把currentInterceptorIndex+1 + if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { + // 当调用次数 = 拦截器个数时 + // 触发当前method方法 + return method.invoke(this.target, this.arguments); + } + + Object interceptorOrInterceptionAdvice = + this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); + // 普通拦截器,直接触发拦截器invoke方法 + return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); + } +``` + +我们看到,MethodInvocation只是简单的将拦截器链的所有拦截器一一执行,最后再触发当前的method方法。这是很简单高效的方法,但问题是我们希望某些增强比如AfterReturningAdvice能够在方法执行完才被执行,这就涉及到不同增强的执行顺序的问题了。而MethodInvocation显然没有考虑顺序的问题,一个AfterReturningAdvice很可能在BeforeAdvice之前被调用。那么该如何保证顺序问题呢? + +答案是,控制增强的调用顺序其实由每个拦截器负责,所以我们需要分析`MethodBeforeAdviceInterceptor`和`AfterReturningAdviceInterceptor` + +```java +public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice { + + private MethodBeforeAdvice advice; + + public MethodBeforeAdviceInterceptor() { + } + + public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) { + this.advice = advice; + } + + public void setAdvice(MethodBeforeAdvice advice) { + this.advice = advice; + } + + @Override + public Object invoke(MethodInvocation mi) throws Throwable { + this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis()); + return mi.proceed(); + } +} +``` + +```java +package org.springframework.aop.framework.adapter; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.AfterAdvice; +import org.springframework.aop.AfterReturningAdvice; + +/** + * @author zqc + * @date 2022/12/20 + */ +public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice { + + private AfterReturningAdvice advice; + + public AfterReturningAdviceInterceptor() { + } + + public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) { + this.advice = advice; + } + + + @Override + public Object invoke(MethodInvocation mi) throws Throwable { + Object retVal = mi.proceed(); + this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis()); + return retVal; + } +} + +``` + +看了源码大家应该就清楚了,拦截器链执行的顺序正时在各个拦截器的`invoke`方法中实现的。`before`会先执行`advice`增强方法再链式调用,这个比较好理解而`after`则是先执行链式调用,再调用`advice`增强方法,也就是一个递归的过程。和二叉树的遍历有些异曲同工之处。 + +![](./assets/chainProceed.png) + +#### [测试](#测试) + +!!!!!!!注意,使用过高版本的java可以因为java版本和cjlib冲突导致报错。建议使用java8进行测试 + +```java +public class WorldServiceImpl implements WorldService { + + private String name; + + @Override + public void explode() { + System.out.println("The " + name + " is going to explode"); + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} +``` + +前置增强: + +```java +public class WorldServiceBeforeAdvice implements MethodBeforeAdvice { + + @Override + public void before(Method method, Object[] args, Object target) throws Throwable { + System.out.println("BeforeAdvice: do something before the earth explodes"); + } +} +``` + +后置返回增强: + +```java +public class WorldServiceAfterReturnAdvice implements AfterReturningAdvice { + @Override + public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { + System.out.println("AfterAdvice: do something after the earth explodes"); + } +} +``` + +测试代码: + +```java +public class ProxyFactoryTest { + @Test + public void testAdvisor() throws Exception { + WorldService worldService = new WorldServiceImpl(); + + //Advisor是Pointcut和Advice的组合 + String expression = "execution(* org.springframework.test.service.WorldService.explode(..))"; + //第一个切面 + AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor(); + advisor.setExpression(expression); + MethodBeforeAdviceInterceptor methodInterceptor = new MethodBeforeAdviceInterceptor(new WorldServiceBeforeAdvice()); + advisor.setAdvice(methodInterceptor); + //第二个切面 + AspectJExpressionPointcutAdvisor advisor1=new AspectJExpressionPointcutAdvisor(); + advisor1.setExpression(expression); + AfterReturningAdviceInterceptor afterReturningAdviceInterceptor=new AfterReturningAdviceInterceptor(new WorldServiceAfterReturnAdvice()); + advisor1.setAdvice(afterReturningAdviceInterceptor); + //通过ProxyFactory来获得代理 + ProxyFactory factory = new ProxyFactory(); + TargetSource targetSource = new TargetSource(worldService); + factory.setTargetSource(targetSource); + factory.setProxyTargetClass(true); + factory.addAdvisor(advisor); + factory.addAdvisor(advisor1); + WorldService proxy = (WorldService) factory.getProxy(); + proxy.explode(); + } +} +``` + +输出: + +``` +BeforeAdvice: do something before the earth explodes +The null is going to explode +AfterAdvice: do something after the earth explodes + +进程已结束,退出代码为 0 +``` + +#### [多切面动态代理融入bean生命周期](#多切面动态代理融入bean生命周期) + +```java + public void testAutoProxy() throws Exception { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:auto-proxy.xml"); + + //获取代理对象 + WorldService worldService = applicationContext.getBean("worldService", WorldService.class); + worldService.explode(); + } +``` + +auto-proxy.xml: + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +输出: + +``` +BeforeAdvice: do something before the earth explodes +The null is going to explode +AfterAdvice: do something after the earth explodes + +进程已结束,退出代码为 0 +``` + +至此,我们已经解决多切面匹配同一方法的问题。 + ##

====================不容易啊,完美撒花====================

diff --git a/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 659d0c8..bc3ade0 100644 --- a/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -71,6 +71,7 @@ public String[] getBeanDefinitionNames() { } @Override + //只有当bean是单例且不为懒加载才会被创建 public void preInstantiateSingletons() throws BeansException { beanDefinitionMap.forEach((beanName, beanDefinition) -> { if(beanDefinition.isSingleton()&&!beanDefinition.isLazyInit()){ diff --git a/src/test/java/org/springframework/test/aop/ProxyFactoryTest.java b/src/test/java/org/springframework/test/aop/ProxyFactoryTest.java index 21efa1f..86d729c 100644 --- a/src/test/java/org/springframework/test/aop/ProxyFactoryTest.java +++ b/src/test/java/org/springframework/test/aop/ProxyFactoryTest.java @@ -18,21 +18,24 @@ public void testAdvisor() throws Exception { //Advisor是Pointcut和Advice的组合 String expression = "execution(* org.springframework.test.service.WorldService.explode(..))"; + //第一个切面 AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor(); advisor.setExpression(expression); MethodBeforeAdviceInterceptor methodInterceptor = new MethodBeforeAdviceInterceptor(new WorldServiceBeforeAdvice()); advisor.setAdvice(methodInterceptor); + //第二个切面 AspectJExpressionPointcutAdvisor advisor1=new AspectJExpressionPointcutAdvisor(); advisor1.setExpression(expression); AfterReturningAdviceInterceptor afterReturningAdviceInterceptor=new AfterReturningAdviceInterceptor(new WorldServiceAfterReturnAdvice()); advisor1.setAdvice(afterReturningAdviceInterceptor); - ProxyFactory factory = new ProxyFactory(); - TargetSource targetSource = new TargetSource(worldService); - factory.setTargetSource(targetSource); - factory.setProxyTargetClass(true); - factory.addAdvisor(advisor); - factory.addAdvisor(advisor1); - WorldService proxy = (WorldService) factory.getProxy(); - proxy.explode(); + //通过ProxyFactory来获得代理 + ProxyFactory factory = new ProxyFactory(); + TargetSource targetSource = new TargetSource(worldService); + factory.setTargetSource(targetSource); + factory.setProxyTargetClass(true); + factory.addAdvisor(advisor); + factory.addAdvisor(advisor1); + WorldService proxy = (WorldService) factory.getProxy(); + proxy.explode(); } } diff --git a/src/test/java/org/springframework/test/ioc/LazyInitTest.java b/src/test/java/org/springframework/test/ioc/LazyInitTest.java index 3365ad3..6cfdb42 100644 --- a/src/test/java/org/springframework/test/ioc/LazyInitTest.java +++ b/src/test/java/org/springframework/test/ioc/LazyInitTest.java @@ -13,6 +13,6 @@ public void testLazyInit() throws InterruptedException { System.out.println(System.currentTimeMillis()+":applicationContext-over"); TimeUnit.SECONDS.sleep(1); Car c= (Car) applicationContext.getBean("car"); - c.showTime(); + c.showTime();//显示bean的创建时间 } } diff --git a/src/test/resources/lazy-test.xml b/src/test/resources/lazy-test.xml index 672aed4..57ca4f8 100644 --- a/src/test/resources/lazy-test.xml +++ b/src/test/resources/lazy-test.xml @@ -2,7 +2,7 @@ - + From e771a6ea88699e11c205ad558b390b0934ff481d Mon Sep 17 00:00:00 2001 From: zqc <1820901097@qq.com> Date: Fri, 23 Dec 2022 12:42:58 +0800 Subject: [PATCH 70/81] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86DynamicProxyTe?= =?UTF-8?q?st?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springframework/aop/AdvisedSupport.java | 8 --- .../test/aop/DynamicProxyTest.java | 53 ++++++++++--------- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/springframework/aop/AdvisedSupport.java b/src/main/java/org/springframework/aop/AdvisedSupport.java index f42c1ed..de926ba 100644 --- a/src/main/java/org/springframework/aop/AdvisedSupport.java +++ b/src/main/java/org/springframework/aop/AdvisedSupport.java @@ -21,7 +21,6 @@ public class AdvisedSupport { private TargetSource targetSource; - private MethodInterceptor methodInterceptor; private MethodMatcher methodMatcher; @@ -58,13 +57,6 @@ public void setTargetSource(TargetSource targetSource) { this.targetSource = targetSource; } - public MethodInterceptor getMethodInterceptor() { - return methodInterceptor; - } - - public void setMethodInterceptor(MethodInterceptor methodInterceptor) { - this.methodInterceptor = methodInterceptor; - } public MethodMatcher getMethodMatcher() { return methodMatcher; diff --git a/src/test/java/org/springframework/test/aop/DynamicProxyTest.java b/src/test/java/org/springframework/test/aop/DynamicProxyTest.java index 6e34ac4..9468801 100644 --- a/src/test/java/org/springframework/test/aop/DynamicProxyTest.java +++ b/src/test/java/org/springframework/test/aop/DynamicProxyTest.java @@ -3,16 +3,15 @@ import org.aopalliance.intercept.MethodInterceptor; import org.junit.Before; import org.junit.Test; -import org.springframework.aop.AdvisedSupport; -import org.springframework.aop.ClassFilter; -import org.springframework.aop.MethodMatcher; -import org.springframework.aop.TargetSource; +import org.springframework.aop.*; import org.springframework.aop.aspectj.AspectJExpressionPointcut; import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor; import org.springframework.aop.framework.CglibAopProxy; import org.springframework.aop.framework.JdkDynamicAopProxy; import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor; import org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor; +import org.springframework.test.common.WorldServiceAfterReturnAdvice; import org.springframework.test.common.WorldServiceBeforeAdvice; import org.springframework.test.common.WorldServiceInterceptor; import org.springframework.test.service.WorldService; @@ -29,14 +28,16 @@ public class DynamicProxyTest { @Before public void setup() { WorldService worldService = new WorldServiceImpl(); - - advisedSupport = new AdvisedSupport(); + advisedSupport=new ProxyFactory(); + //Advisor是Pointcut和Advice的组合 + String expression = "execution(* org.springframework.test.service.WorldService.explode(..))"; + AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor(); + advisor.setExpression(expression); + AfterReturningAdviceInterceptor methodInterceptor = new AfterReturningAdviceInterceptor(new WorldServiceAfterReturnAdvice()); + advisor.setAdvice(methodInterceptor); TargetSource targetSource = new TargetSource(worldService); - WorldServiceInterceptor methodInterceptor = new WorldServiceInterceptor(); - MethodMatcher methodMatcher = new AspectJExpressionPointcut("execution(* org.springframework.test.service.WorldService.explode(..))").getMethodMatcher(); advisedSupport.setTargetSource(targetSource); - advisedSupport.setMethodInterceptor(methodInterceptor); - advisedSupport.setMethodMatcher(methodMatcher); + advisedSupport.addAdvisor(advisor); } @Test @@ -54,24 +55,28 @@ public void testCglibDynamicProxy() throws Exception { @Test public void testProxyFactory() throws Exception { // 使用JDK动态代理 - advisedSupport.setProxyTargetClass(false); - WorldService proxy = (WorldService) new ProxyFactory().getProxy(); + ProxyFactory factory=(ProxyFactory) advisedSupport; + factory.setProxyTargetClass(false); + WorldService proxy = (WorldService) factory.getProxy(); proxy.explode(); // 使用CGLIB动态代理 - advisedSupport.setProxyTargetClass(true); - proxy = (WorldService) new ProxyFactory().getProxy(); + factory.setProxyTargetClass(true); + proxy = (WorldService) factory.getProxy(); proxy.explode(); } @Test public void testBeforeAdvice() throws Exception { //设置BeforeAdvice - WorldServiceBeforeAdvice beforeAdvice = new WorldServiceBeforeAdvice(); - MethodBeforeAdviceInterceptor methodInterceptor = new MethodBeforeAdviceInterceptor(beforeAdvice); - advisedSupport.setMethodInterceptor(methodInterceptor); - - WorldService proxy = (WorldService) new ProxyFactory().getProxy(); + String expression = "execution(* org.springframework.test.service.WorldService.explode(..))"; + AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor(); + advisor.setExpression(expression); + MethodBeforeAdviceInterceptor methodInterceptor = new MethodBeforeAdviceInterceptor(new WorldServiceBeforeAdvice()); + advisor.setAdvice(methodInterceptor); + advisedSupport.addAdvisor(advisor); + ProxyFactory factory=(ProxyFactory) advisedSupport; + WorldService proxy = (WorldService) factory.getProxy(); proxy.explode(); } @@ -88,15 +93,15 @@ public void testAdvisor() throws Exception { ClassFilter classFilter = advisor.getPointcut().getClassFilter(); if (classFilter.matches(worldService.getClass())) { - AdvisedSupport advisedSupport = new AdvisedSupport(); + ProxyFactory proxyFactory = new ProxyFactory(); TargetSource targetSource = new TargetSource(worldService); - advisedSupport.setTargetSource(targetSource); - advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice()); - advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher()); + proxyFactory.setTargetSource(targetSource); + proxyFactory.addAdvisor(advisor); + //proxyFactory.setMethodMatcher(advisor.getPointcut().getMethodMatcher()); // advisedSupport.setProxyTargetClass(true); //JDK or CGLIB - WorldService proxy = (WorldService) new ProxyFactory().getProxy(); + WorldService proxy = (WorldService) proxyFactory.getProxy(); proxy.explode(); } } From 277afdc2bedcb4eb5620ad4a0127652d16cae2f8 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Mon, 26 Dec 2022 22:01:29 +0800 Subject: [PATCH 71/81] =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=87=92=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E5=92=8C=E5=A4=9A=E5=88=87=E9=9D=A2=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 2 - .../org/springframework/aop/AfterAdvice.java | 2 + .../aop/AfterReturningAdvice.java | 5 +- .../aop/framework/AdvisorChainFactory.java | 2 +- .../aop/framework/CglibAopProxy.java | 24 +++++---- .../framework/DefaultAdvisorChainFactory.java | 50 +++++++++--------- .../aop/framework/JdkDynamicAopProxy.java | 7 +-- .../aop/framework/ProxyFactory.java | 5 +- .../AfterReturningAdviceInterceptor.java | 27 +++++----- .../MethodBeforeAdviceInterceptor.java | 1 + .../DefaultAdvisorAutoProxyCreator.java | 22 +++++--- .../support/DefaultListableBeanFactory.java | 4 +- .../factory/xml/XmlBeanDefinitionReader.java | 5 +- .../test/aop/DynamicProxyTest.java | 16 +++--- .../test/aop/ProxyFactoryTest.java | 51 ++++++++++--------- .../org/springframework/test/bean/Car.java | 13 +++-- .../common/WorldServiceAfterReturnAdvice.java | 9 ++-- .../test/ioc/LazyInitTest.java | 22 ++++---- src/test/resources/auto-proxy.xml | 3 ++ 19 files changed, 151 insertions(+), 119 deletions(-) diff --git a/changelog.md b/changelog.md index 12e5d8c..651ad31 100644 --- a/changelog.md +++ b/changelog.md @@ -1926,5 +1926,3 @@ AfterAdvice: do something after the earth explodes ``` 至此,我们已经解决多切面匹配同一方法的问题。 - -##

====================不容易啊,完美撒花====================

diff --git a/src/main/java/org/springframework/aop/AfterAdvice.java b/src/main/java/org/springframework/aop/AfterAdvice.java index 6cb21ff..1598270 100644 --- a/src/main/java/org/springframework/aop/AfterAdvice.java +++ b/src/main/java/org/springframework/aop/AfterAdvice.java @@ -3,6 +3,8 @@ import org.aopalliance.aop.Advice; /** + * 后置增强 + * * @author zqc * @date 2022/12/16 */ diff --git a/src/main/java/org/springframework/aop/AfterReturningAdvice.java b/src/main/java/org/springframework/aop/AfterReturningAdvice.java index ada354e..bdb6e8a 100644 --- a/src/main/java/org/springframework/aop/AfterReturningAdvice.java +++ b/src/main/java/org/springframework/aop/AfterReturningAdvice.java @@ -1,13 +1,14 @@ package org.springframework.aop; -import org.springframework.aop.AfterAdvice; - import java.lang.reflect.Method; /** + * 后置增强 + * * @author zqc * @date 2022/12/16 */ public interface AfterReturningAdvice extends AfterAdvice { + void afterReturning( Object returnValue, Method method, Object[] args, Object target) throws Throwable; } diff --git a/src/main/java/org/springframework/aop/framework/AdvisorChainFactory.java b/src/main/java/org/springframework/aop/framework/AdvisorChainFactory.java index a84fff5..cb88dd0 100644 --- a/src/main/java/org/springframework/aop/framework/AdvisorChainFactory.java +++ b/src/main/java/org/springframework/aop/framework/AdvisorChainFactory.java @@ -12,6 +12,6 @@ public interface AdvisorChainFactory { - List getInterceptorsAndDynamicInterceptionAdvice(AdvisedSupport config, Method method, Class targetClass); + List getInterceptorsAndDynamicInterceptionAdvice(AdvisedSupport config, Method method, Class targetClass); } diff --git a/src/main/java/org/springframework/aop/framework/CglibAopProxy.java b/src/main/java/org/springframework/aop/framework/CglibAopProxy.java index 782754b..21bbf56 100644 --- a/src/main/java/org/springframework/aop/framework/CglibAopProxy.java +++ b/src/main/java/org/springframework/aop/framework/CglibAopProxy.java @@ -1,14 +1,17 @@ package org.springframework.aop.framework; +import java.lang.reflect.Method; +import java.util.List; + import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; -import org.springframework.aop.AdvisedSupport; -import java.lang.reflect.Method; -import java.util.List; +import org.springframework.aop.AdvisedSupport; /** + * cglib动态代理 + * * @author zqc * @date 2022/12/17 */ @@ -45,14 +48,17 @@ private DynamicAdvisedInterceptor(AdvisedSupport advised) { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // 获取目标对象 - Object target=advised.getTargetSource().getTarget(); + Object target = advised.getTargetSource().getTarget(); Class targetClass = target.getClass(); Object retVal = null; List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); CglibMethodInvocation methodInvocation = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy); - if(chain==null||chain.isEmpty()){ - retVal =methodProxy.invoke(target, args); - }else retVal=methodInvocation.proceed(); + if (chain == null || chain.isEmpty()) { + //代理方法 + retVal = methodProxy.invoke(target, args); + } else { + retVal = methodInvocation.proceed(); + } return retVal; } } @@ -62,8 +68,8 @@ private static class CglibMethodInvocation extends ReflectiveMethodInvocation { private final MethodProxy methodProxy; public CglibMethodInvocation(Object proxy, Object target, Method method, - Object[] arguments, Class targetClass, - List interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) { + Object[] arguments, Class targetClass, + List interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) { super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers); this.methodProxy = methodProxy; } diff --git a/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java b/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java index 2a0377a..8d00a95 100644 --- a/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java +++ b/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java @@ -1,6 +1,7 @@ package org.springframework.aop.framework; import org.aopalliance.intercept.MethodInterceptor; + import org.springframework.aop.AdvisedSupport; import org.springframework.aop.Advisor; import org.springframework.aop.MethodMatcher; @@ -15,28 +16,29 @@ * @date 2022/12/17 */ public class DefaultAdvisorChainFactory implements AdvisorChainFactory { - @Override - public List getInterceptorsAndDynamicInterceptionAdvice(AdvisedSupport config, Method method, Class targetClass) { - Advisor[] advisors = config.getAdvisors().toArray(new Advisor[0]); - List interceptorList = new ArrayList<>(advisors.length); - Class actualClass = (targetClass != null ? targetClass : method.getDeclaringClass()); - for (Advisor advisor : advisors) { - if (advisor instanceof PointcutAdvisor) { - // Add it conditionally. - PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor; - // 校验当前Advisor是否适用于当前对象 - if (pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) { - MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher(); - boolean match; - // 校验Advisor是否应用到当前方法上 - match = mm.matches(method,actualClass); - if (match) { - MethodInterceptor interceptor = (MethodInterceptor) advisor.getAdvice(); - interceptorList.add(interceptor); - } - } - } - } - return interceptorList; - } + + @Override + public List getInterceptorsAndDynamicInterceptionAdvice(AdvisedSupport config, Method method, Class targetClass) { + Advisor[] advisors = config.getAdvisors().toArray(new Advisor[0]); + List interceptorList = new ArrayList<>(advisors.length); + Class actualClass = (targetClass != null ? targetClass : method.getDeclaringClass()); + for (Advisor advisor : advisors) { + if (advisor instanceof PointcutAdvisor) { + // Add it conditionally. + PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor; + // 校验当前Advisor是否适用于当前对象 + if (pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) { + MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher(); + boolean match; + // 校验Advisor是否应用到当前方法上 + match = mm.matches(method, actualClass); + if (match) { + MethodInterceptor interceptor = (MethodInterceptor) advisor.getAdvice(); + interceptorList.add(interceptor); + } + } + } + } + return interceptorList; + } } diff --git a/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java b/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java index 2f59d9c..2f8e7e5 100644 --- a/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java +++ b/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java @@ -1,6 +1,7 @@ package org.springframework.aop.framework; import org.aopalliance.intercept.MethodInvocation; + import org.springframework.aop.AdvisedSupport; import java.lang.reflect.InvocationHandler; @@ -35,14 +36,14 @@ public Object getProxy() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 获取目标对象 - Object target=advised.getTargetSource().getTarget(); + Object target = advised.getTargetSource().getTarget(); Class targetClass = target.getClass(); Object retVal = null; // 获取拦截器链 List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); - if(chain==null||chain.isEmpty()){ + if (chain == null || chain.isEmpty()) { return method.invoke(target, args); - }else{ + } else { // 将拦截器统一封装成ReflectiveMethodInvocation MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); diff --git a/src/main/java/org/springframework/aop/framework/ProxyFactory.java b/src/main/java/org/springframework/aop/framework/ProxyFactory.java index f87a4f7..f0cc3a3 100644 --- a/src/main/java/org/springframework/aop/framework/ProxyFactory.java +++ b/src/main/java/org/springframework/aop/framework/ProxyFactory.java @@ -6,7 +6,7 @@ * @author zqc * @date 2022/12/16 */ -public class ProxyFactory extends AdvisedSupport{ +public class ProxyFactory extends AdvisedSupport { public ProxyFactory() { @@ -17,9 +17,10 @@ public Object getProxy() { } private AopProxy createAopProxy() { - if (this.isProxyTargetClass()||this.getTargetSource().getTargetClass().length==0) { + if (this.isProxyTargetClass() || this.getTargetSource().getTargetClass().length == 0) { return new CglibAopProxy(this); } + return new JdkDynamicAopProxy(this); } } diff --git a/src/main/java/org/springframework/aop/framework/adapter/AfterReturningAdviceInterceptor.java b/src/main/java/org/springframework/aop/framework/adapter/AfterReturningAdviceInterceptor.java index d1b4070..12d4709 100644 --- a/src/main/java/org/springframework/aop/framework/adapter/AfterReturningAdviceInterceptor.java +++ b/src/main/java/org/springframework/aop/framework/adapter/AfterReturningAdviceInterceptor.java @@ -2,29 +2,32 @@ import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; + import org.springframework.aop.AfterAdvice; import org.springframework.aop.AfterReturningAdvice; /** + * 后置增强拦截器 + * * @author zqc * @date 2022/12/20 */ public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice { - private AfterReturningAdvice advice; + private AfterReturningAdvice advice; - public AfterReturningAdviceInterceptor() { - } + public AfterReturningAdviceInterceptor() { + } - public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) { - this.advice = advice; - } + public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) { + this.advice = advice; + } - @Override - public Object invoke(MethodInvocation mi) throws Throwable { - Object retVal = mi.proceed(); - this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis()); - return retVal; - } + @Override + public Object invoke(MethodInvocation mi) throws Throwable { + Object retVal = mi.proceed(); + this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis()); + return retVal; + } } diff --git a/src/main/java/org/springframework/aop/framework/adapter/MethodBeforeAdviceInterceptor.java b/src/main/java/org/springframework/aop/framework/adapter/MethodBeforeAdviceInterceptor.java index d4fd0b7..58b057b 100644 --- a/src/main/java/org/springframework/aop/framework/adapter/MethodBeforeAdviceInterceptor.java +++ b/src/main/java/org/springframework/aop/framework/adapter/MethodBeforeAdviceInterceptor.java @@ -26,6 +26,7 @@ public void setAdvice(MethodBeforeAdvice advice) { @Override public Object invoke(MethodInvocation mi) throws Throwable { + //在执行被代理方法之前,先执行before advice操作 this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis()); return mi.proceed(); } diff --git a/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java b/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java index 06256b7..e66a272 100644 --- a/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java +++ b/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java @@ -1,8 +1,15 @@ package org.springframework.aop.framework.autoproxy; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + import org.aopalliance.aop.Advice; -import org.aopalliance.intercept.MethodInterceptor; -import org.springframework.aop.*; + +import org.springframework.aop.Advisor; +import org.springframework.aop.ClassFilter; +import org.springframework.aop.Pointcut; +import org.springframework.aop.TargetSource; import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.BeansException; @@ -12,10 +19,6 @@ import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - /** * @author derekyi * @date 2020/12/6 @@ -47,7 +50,8 @@ protected Object wrapIfNecessary(Object bean, String beanName) { return bean; } - Collection advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values(); + Collection advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class) + .values(); try { ProxyFactory proxyFactory = new ProxyFactory(); for (AspectJExpressionPointcutAdvisor advisor : advisors) { @@ -59,7 +63,9 @@ protected Object wrapIfNecessary(Object bean, String beanName) { proxyFactory.setMethodMatcher(advisor.getPointcut().getMethodMatcher()); } } - if(!proxyFactory.getAdvisors().isEmpty()) return proxyFactory.getProxy(); + if (!proxyFactory.getAdvisors().isEmpty()) { + return proxyFactory.getProxy(); + } } catch (Exception ex) { throw new BeansException("Error create proxy bean for: " + beanName, ex); } diff --git a/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index bc3ade0..7d4cf44 100644 --- a/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -71,10 +71,10 @@ public String[] getBeanDefinitionNames() { } @Override - //只有当bean是单例且不为懒加载才会被创建 public void preInstantiateSingletons() throws BeansException { beanDefinitionMap.forEach((beanName, beanDefinition) -> { - if(beanDefinition.isSingleton()&&!beanDefinition.isLazyInit()){ + //只有当bean是单例且不为懒加载才会被创建 + if (beanDefinition.isSingleton() && !beanDefinition.isLazyInit()) { getBean(beanName); } }); diff --git a/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java b/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java index 935421c..c63d778 100644 --- a/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java +++ b/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java @@ -6,6 +6,7 @@ import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; + import org.springframework.beans.BeansException; import org.springframework.beans.PropertyValue; import org.springframework.beans.factory.config.BeanDefinition; @@ -95,7 +96,7 @@ protected void doLoadBeanDefinitions(InputStream inputStream) throws DocumentExc String initMethodName = bean.attributeValue(INIT_METHOD_ATTRIBUTE); String destroyMethodName = bean.attributeValue(DESTROY_METHOD_ATTRIBUTE); String beanScope = bean.attributeValue(SCOPE_ATTRIBUTE); - String lazyInit=bean.attributeValue(LAZYINIT_ATTRIBUTE); + String lazyInit = bean.attributeValue(LAZYINIT_ATTRIBUTE); Class clazz; try { clazz = Class.forName(className); @@ -112,7 +113,7 @@ protected void doLoadBeanDefinitions(InputStream inputStream) throws DocumentExc BeanDefinition beanDefinition = new BeanDefinition(clazz); beanDefinition.setInitMethodName(initMethodName); beanDefinition.setDestroyMethodName(destroyMethodName); - beanDefinition.setLazyInit("true".equals(lazyInit)); + beanDefinition.setLazyInit(Boolean.parseBoolean(lazyInit)); if (StrUtil.isNotEmpty(beanScope)) { beanDefinition.setScope(beanScope); } diff --git a/src/test/java/org/springframework/test/aop/DynamicProxyTest.java b/src/test/java/org/springframework/test/aop/DynamicProxyTest.java index 9468801..94c8992 100644 --- a/src/test/java/org/springframework/test/aop/DynamicProxyTest.java +++ b/src/test/java/org/springframework/test/aop/DynamicProxyTest.java @@ -1,10 +1,11 @@ package org.springframework.test.aop; -import org.aopalliance.intercept.MethodInterceptor; import org.junit.Before; import org.junit.Test; -import org.springframework.aop.*; -import org.springframework.aop.aspectj.AspectJExpressionPointcut; + +import org.springframework.aop.AdvisedSupport; +import org.springframework.aop.ClassFilter; +import org.springframework.aop.TargetSource; import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor; import org.springframework.aop.framework.CglibAopProxy; import org.springframework.aop.framework.JdkDynamicAopProxy; @@ -13,7 +14,6 @@ import org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor; import org.springframework.test.common.WorldServiceAfterReturnAdvice; import org.springframework.test.common.WorldServiceBeforeAdvice; -import org.springframework.test.common.WorldServiceInterceptor; import org.springframework.test.service.WorldService; import org.springframework.test.service.WorldServiceImpl; @@ -28,7 +28,7 @@ public class DynamicProxyTest { @Before public void setup() { WorldService worldService = new WorldServiceImpl(); - advisedSupport=new ProxyFactory(); + advisedSupport = new ProxyFactory(); //Advisor是Pointcut和Advice的组合 String expression = "execution(* org.springframework.test.service.WorldService.explode(..))"; AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor(); @@ -55,7 +55,7 @@ public void testCglibDynamicProxy() throws Exception { @Test public void testProxyFactory() throws Exception { // 使用JDK动态代理 - ProxyFactory factory=(ProxyFactory) advisedSupport; + ProxyFactory factory = (ProxyFactory) advisedSupport; factory.setProxyTargetClass(false); WorldService proxy = (WorldService) factory.getProxy(); proxy.explode(); @@ -75,7 +75,7 @@ public void testBeforeAdvice() throws Exception { MethodBeforeAdviceInterceptor methodInterceptor = new MethodBeforeAdviceInterceptor(new WorldServiceBeforeAdvice()); advisor.setAdvice(methodInterceptor); advisedSupport.addAdvisor(advisor); - ProxyFactory factory=(ProxyFactory) advisedSupport; + ProxyFactory factory = (ProxyFactory) advisedSupport; WorldService proxy = (WorldService) factory.getProxy(); proxy.explode(); } @@ -98,7 +98,7 @@ public void testAdvisor() throws Exception { TargetSource targetSource = new TargetSource(worldService); proxyFactory.setTargetSource(targetSource); proxyFactory.addAdvisor(advisor); - //proxyFactory.setMethodMatcher(advisor.getPointcut().getMethodMatcher()); +// proxyFactory.setMethodMatcher(advisor.getPointcut().getMethodMatcher()); // advisedSupport.setProxyTargetClass(true); //JDK or CGLIB WorldService proxy = (WorldService) proxyFactory.getProxy(); diff --git a/src/test/java/org/springframework/test/aop/ProxyFactoryTest.java b/src/test/java/org/springframework/test/aop/ProxyFactoryTest.java index 86d729c..01d1b14 100644 --- a/src/test/java/org/springframework/test/aop/ProxyFactoryTest.java +++ b/src/test/java/org/springframework/test/aop/ProxyFactoryTest.java @@ -1,6 +1,7 @@ package org.springframework.test.aop; import org.junit.Test; + import org.springframework.aop.TargetSource; import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor; import org.springframework.aop.framework.ProxyFactory; @@ -12,30 +13,30 @@ import org.springframework.test.service.WorldServiceImpl; public class ProxyFactoryTest { - @Test - public void testAdvisor() throws Exception { - WorldService worldService = new WorldServiceImpl(); + @Test + public void testAdvisor() throws Exception { + WorldService worldService = new WorldServiceImpl(); - //Advisor是Pointcut和Advice的组合 - String expression = "execution(* org.springframework.test.service.WorldService.explode(..))"; - //第一个切面 - AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor(); - advisor.setExpression(expression); - MethodBeforeAdviceInterceptor methodInterceptor = new MethodBeforeAdviceInterceptor(new WorldServiceBeforeAdvice()); - advisor.setAdvice(methodInterceptor); - //第二个切面 - AspectJExpressionPointcutAdvisor advisor1=new AspectJExpressionPointcutAdvisor(); - advisor1.setExpression(expression); - AfterReturningAdviceInterceptor afterReturningAdviceInterceptor=new AfterReturningAdviceInterceptor(new WorldServiceAfterReturnAdvice()); - advisor1.setAdvice(afterReturningAdviceInterceptor); - //通过ProxyFactory来获得代理 - ProxyFactory factory = new ProxyFactory(); - TargetSource targetSource = new TargetSource(worldService); - factory.setTargetSource(targetSource); - factory.setProxyTargetClass(true); - factory.addAdvisor(advisor); - factory.addAdvisor(advisor1); - WorldService proxy = (WorldService) factory.getProxy(); - proxy.explode(); - } + //Advisor是Pointcut和Advice的组合 + String expression = "execution(* org.springframework.test.service.WorldService.explode(..))"; + //第一个切面 + AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor(); + advisor.setExpression(expression); + MethodBeforeAdviceInterceptor methodInterceptor = new MethodBeforeAdviceInterceptor(new WorldServiceBeforeAdvice()); + advisor.setAdvice(methodInterceptor); + //第二个切面 + AspectJExpressionPointcutAdvisor advisor1 = new AspectJExpressionPointcutAdvisor(); + advisor1.setExpression(expression); + AfterReturningAdviceInterceptor afterReturningAdviceInterceptor = new AfterReturningAdviceInterceptor(new WorldServiceAfterReturnAdvice()); + advisor1.setAdvice(afterReturningAdviceInterceptor); + //通过ProxyFactory来获得代理 + ProxyFactory factory = new ProxyFactory(); + TargetSource targetSource = new TargetSource(worldService); + factory.setTargetSource(targetSource); + factory.setProxyTargetClass(true); + factory.addAdvisor(advisor); + factory.addAdvisor(advisor1); + WorldService proxy = (WorldService) factory.getProxy(); + proxy.explode(); + } } diff --git a/src/test/java/org/springframework/test/bean/Car.java b/src/test/java/org/springframework/test/bean/Car.java index 1cb5433..53be9ba 100644 --- a/src/test/java/org/springframework/test/bean/Car.java +++ b/src/test/java/org/springframework/test/bean/Car.java @@ -45,13 +45,16 @@ public LocalDate getProduceDate() { public void setProduceDate(LocalDate produceDate) { this.produceDate = produceDate; } - public void init(){ - date=System.currentTimeMillis(); + + public void init() { + date = System.currentTimeMillis(); } - public void showTime(){ - SimpleDateFormat dateFormat= new SimpleDateFormat("yyyy-MM-dd :hh:mm:ss"); - System.out.println(date+":bean create"); + + public void showTime() { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd :hh:mm:ss"); + System.out.println(date + ":bean create"); } + @Override public String toString() { return "Car{" + diff --git a/src/test/java/org/springframework/test/common/WorldServiceAfterReturnAdvice.java b/src/test/java/org/springframework/test/common/WorldServiceAfterReturnAdvice.java index db26f53..9d9dd7e 100644 --- a/src/test/java/org/springframework/test/common/WorldServiceAfterReturnAdvice.java +++ b/src/test/java/org/springframework/test/common/WorldServiceAfterReturnAdvice.java @@ -5,8 +5,9 @@ import java.lang.reflect.Method; public class WorldServiceAfterReturnAdvice implements AfterReturningAdvice { - @Override - public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { - System.out.println("AfterAdvice: do something after the earth explodes"); - } + + @Override + public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { + System.out.println("AfterAdvice: do something after the earth explodes"); + } } diff --git a/src/test/java/org/springframework/test/ioc/LazyInitTest.java b/src/test/java/org/springframework/test/ioc/LazyInitTest.java index 6cfdb42..27fcd76 100644 --- a/src/test/java/org/springframework/test/ioc/LazyInitTest.java +++ b/src/test/java/org/springframework/test/ioc/LazyInitTest.java @@ -1,18 +1,20 @@ package org.springframework.test.ioc; +import java.util.concurrent.TimeUnit; + import org.junit.Test; + import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.test.bean.Car; -import java.util.concurrent.TimeUnit; - public class LazyInitTest { - @Test - public void testLazyInit() throws InterruptedException { - ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:lazy-test.xml"); - System.out.println(System.currentTimeMillis()+":applicationContext-over"); - TimeUnit.SECONDS.sleep(1); - Car c= (Car) applicationContext.getBean("car"); - c.showTime();//显示bean的创建时间 - } + + @Test + public void testLazyInit() throws InterruptedException { + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:lazy-test.xml"); + System.out.println(System.currentTimeMillis() + ":applicationContext-over"); + TimeUnit.SECONDS.sleep(1); + Car c = (Car) applicationContext.getBean("car"); + c.showTime();//显示bean的创建时间 + } } diff --git a/src/test/resources/auto-proxy.xml b/src/test/resources/auto-proxy.xml index 8ae2ee2..2f65c7b 100644 --- a/src/test/resources/auto-proxy.xml +++ b/src/test/resources/auto-proxy.xml @@ -23,10 +23,13 @@ + + + From ffc451c393b42099a3fa726bffc37d710cbd88fa Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Mon, 26 Dec 2022 22:06:44 +0800 Subject: [PATCH 72/81] update readme --- README.md | 2 +- changelog.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b80925a..fe19ba8 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ #### 其他 * [没有为代理bean设置属性(discovered and fixed by kerwin89)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bug-fix没有为代理bean设置属性discovered-and-fixed-by-kerwin89) -* [支持懒加载和多个增强(by zqczgl)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#支持懒加载和多个增强(By zqczgl)) +* [支持懒加载和多切面增强(by zqczgl)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#支持懒加载和多切面增强-by-zqczgl) ## 使用方法 阅读[changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) diff --git a/changelog.md b/changelog.md index 651ad31..23f9f7d 100644 --- a/changelog.md +++ b/changelog.md @@ -1491,7 +1491,7 @@ getBean()时依次检查一级缓存singletonObjects、二级缓存earlySingleto 单测见CircularReferenceWithProxyBeanTest。 -## [支持懒加载和多个增强(By zqczgl)](#支持懒加载和多个增强(By zqczgl)) +## [支持懒加载和多切面增强(By zqczgl)](#支持懒加载和多切面增强-by-zqczgl) ### [懒加载](#懒加载) From db1ba10b1f453320a1aaec5535be5df868b3026a Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Mon, 26 Dec 2022 22:10:33 +0800 Subject: [PATCH 73/81] update readme --- README.md | 2 +- changelog.md | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fe19ba8..efbed9e 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ #### 其他 * [没有为代理bean设置属性(discovered and fixed by kerwin89)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#bug-fix没有为代理bean设置属性discovered-and-fixed-by-kerwin89) -* [支持懒加载和多切面增强(by zqczgl)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#支持懒加载和多切面增强-by-zqczgl) +* [支持懒加载和多切面增强(by zqczgl)](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#支持懒加载和多切面增强by-zqczgl) ## 使用方法 阅读[changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) diff --git a/changelog.md b/changelog.md index 23f9f7d..62191c4 100644 --- a/changelog.md +++ b/changelog.md @@ -1491,7 +1491,10 @@ getBean()时依次检查一级缓存singletonObjects、二级缓存earlySingleto 单测见CircularReferenceWithProxyBeanTest。 -## [支持懒加载和多切面增强(By zqczgl)](#支持懒加载和多切面增强-by-zqczgl) +## [bug fix:没有为代理bean设置属性(discovered and fixed by @kerwin89)](#bug-fix没有为代理bean设置属性discovered-and-fixed-by-kerwin89) + + +## [支持懒加载和多切面增强(By @zqczgl)](#支持懒加载和多切面增强by-zqczgl) ### [懒加载](#懒加载) From d204fd6744bb4445de169776599f8554862621e9 Mon Sep 17 00:00:00 2001 From: zqczgl <73417932+zqczgl@users.noreply.github.com> Date: Wed, 28 Dec 2022 20:12:02 +0800 Subject: [PATCH 74/81] Update changelog.md --- changelog.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 62191c4..b4ca37f 100644 --- a/changelog.md +++ b/changelog.md @@ -1623,7 +1623,7 @@ ProxyFactory只是简单的做了下选择,当我们设置proxyTargetClass属 jdk动态代理可以分为获取拦截器链,将拦截器统一封装成ReflectiveMethodInvocation,执行拦截器链三部分。我们来逐一看一下这三部分。 -##### [1. 获取拦截器链](#1. 获取拦截器链) +##### [1.获取拦截器链](#1.获取拦截器链) 首先将获取到所有与当前method匹配的advice(增强),跟踪getInterceptorsAndDynamicInterceptionAdvice代码,我们发现Spring AOP也使用缓存进行提高性能,如果该方法已经获取过拦截器,则直接取缓存,否则通过advisorChainFactory获取拦截器链。AdvisorChainFactory是用来获得拦截器链接口。它的一个实现类为DefaultAdvisorChainFactory @@ -1689,7 +1689,7 @@ public List getInterceptorsAndDynamicInterceptionAdvice(AdvisedSupport c -##### [3. 执行拦截器链](#3. 执行拦截器链) +##### [3.执行拦截器链](#3.执行拦截器链) spring能够保证多个切面同时匹配同一方法的而不出现乱序的关键就在下面一段代码了。 From 060de4f32b8817d45424111b78c22720e00dcb0b Mon Sep 17 00:00:00 2001 From: DerekYRC <44155264+DerekYRC@users.noreply.github.com> Date: Tue, 31 Jan 2023 11:09:29 +0800 Subject: [PATCH 75/81] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index efbed9e..e6729e6 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ## 关于 -**mini-spring**是简化版的spring框架,能帮助你快速熟悉spring源码和掌握spring的核心原理。抽取了spring的核心逻辑,代码极度简化,保留spring的核心功能,如IoC和AOP、资源加载器、事件监听器、类型转换、容器扩展点、bean生命周期和作用域、应用上下文等核心功能。 +**mini-spring**是简化版的spring框架,能帮助你快速熟悉spring源码和掌握spring的核心原理。抽取了spring的核心逻辑,**代码极度简化,保留spring的核心功能**,如IoC和AOP、资源加载器、事件监听器、类型转换、容器扩展点、bean生命周期和作用域、应用上下文等核心功能。 如果本项目能帮助到你,请给个**STAR,谢谢!!!** From 00b823729a6c3f582f709db21a8d36de4cd8a1c3 Mon Sep 17 00:00:00 2001 From: DerekYRC <44155264+DerekYRC@users.noreply.github.com> Date: Fri, 17 Feb 2023 11:33:52 +0800 Subject: [PATCH 76/81] Update changelog.md --- changelog.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/changelog.md b/changelog.md index b4ca37f..801c78d 100644 --- a/changelog.md +++ b/changelog.md @@ -352,7 +352,7 @@ public class BeanFactoryProcessorAndBeanPostProcessorTest { BeanFactory是spring的基础设施,面向spring本身;而ApplicationContext面向spring的使用者,应用场合使用ApplicationContext。 -具体实现查看AbstractApplicationContext#refresh方法即可。注意BeanFactoryPostProcessor和BeanPostProcessor的自定识别,这样就可以在xml文件中配置二者而不需要像上一节一样手动添加到容器中了。 +具体实现查看AbstractApplicationContext#refresh方法即可。注意BeanFactoryPostProcessor和BeanPostProcessor的自动识别,这样就可以在xml文件中配置二者而不需要像上一节一样手动添加到容器中了。 从bean的角度看,目前生命周期如下: @@ -1491,9 +1491,6 @@ getBean()时依次检查一级缓存singletonObjects、二级缓存earlySingleto 单测见CircularReferenceWithProxyBeanTest。 -## [bug fix:没有为代理bean设置属性(discovered and fixed by @kerwin89)](#bug-fix没有为代理bean设置属性discovered-and-fixed-by-kerwin89) - - ## [支持懒加载和多切面增强(By @zqczgl)](#支持懒加载和多切面增强by-zqczgl) ### [懒加载](#懒加载) From 5cafaa0f57428d6ddaf5a3172d5df978661a9b2a Mon Sep 17 00:00:00 2001 From: xiaoliu <1713612859@qq.com> Date: Sun, 8 Oct 2023 11:03:26 +0800 Subject: [PATCH 77/81] =?UTF-8?q?=E5=B0=86BeanDefinition=20=E7=9A=84Map=20?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=88=90=E4=B8=BA=E5=B9=B6=E5=8F=91Map=20?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E8=AE=BE=E7=BD=AE=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E5=A4=A7=E5=B0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../beans/factory/config/BeanDefinition.java | 24 +++++++++++++++---- .../support/DefaultListableBeanFactory.java | 3 ++- .../support/DefaultSingletonBeanRegistry.java | 18 +++++++++----- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java b/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java index db9a1c3..2440451 100644 --- a/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java +++ b/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java @@ -16,20 +16,36 @@ public class BeanDefinition { public static String SCOPE_PROTOTYPE = "prototype"; - private Class beanClass; - private PropertyValues propertyValues; + /** + * bean class类 + */ + private Class beanClass; + /** + * class 属性值 + */ + private PropertyValues propertyValues; + /** + * 通过反射 初始化方法名称 + */ private String initMethodName; - + /** + * 销毁方法名称 + */ private String destroyMethodName; - + /** + * 作用域 默认单例Bean + */ private String scope = SCOPE_SINGLETON; private boolean singleton = true; private boolean prototype = false; + /*/* + 懒加载 + */ private boolean lazyInit=false; public BeanDefinition(Class beanClass) { diff --git a/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 7d4cf44..b40f8ce 100644 --- a/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -5,6 +5,7 @@ import org.springframework.beans.factory.config.BeanDefinition; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; /** * @author derekyi @@ -13,7 +14,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry { - private Map beanDefinitionMap = new HashMap<>(); + private Map beanDefinitionMap = new ConcurrentHashMap<>(256); @Override public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) { diff --git a/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java index 97936dd..2f4623c 100644 --- a/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java +++ b/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java @@ -15,13 +15,19 @@ */ public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry { - //一级缓存 + /** + * 一级缓存 + */ private Map singletonObjects = new HashMap<>(); - //二级缓存 + /** + * 二级缓存 + */ private Map earlySingletonObjects = new HashMap<>(); - //三级缓存 + /** + * 三级缓存 + */ private Map> singletonFactories = new HashMap>(); private final Map disposableBeans = new HashMap<>(); @@ -46,9 +52,9 @@ public Object getSingleton(String beanName) { @Override public void addSingleton(String beanName, Object singletonObject) { - singletonObjects.put(beanName, singletonObject); - earlySingletonObjects.remove(beanName); - singletonFactories.remove(beanName); + singletonObjects.put(beanName, singletonObject); // 1 + earlySingletonObjects.remove(beanName); // 2 + singletonFactories.remove(beanName); // 3 } protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) { From 727abcaa674ce5dffb457b5b9923ba99dc3e03a7 Mon Sep 17 00:00:00 2001 From: whale <1466978121@qq.com> Date: Thu, 30 Nov 2023 17:08:06 +0800 Subject: [PATCH 78/81] =?UTF-8?q?=E9=81=BF=E5=85=8D=E7=BB=A7=E6=89=BF?= =?UTF-8?q?=E8=87=AAInitializingBean=EF=BC=8C=E4=B8=94=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E6=96=B9=E6=B3=95=E4=B8=8EInitializingBean=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=90=8C=E5=90=8D=EF=BC=8C=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E6=89=A7=E8=A1=8C=E4=B8=A4=E6=AC=A1=E7=9A=84?= =?UTF-8?q?=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../factory/support/AbstractAutowireCapableBeanFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 2be2b67..cc6bd43 100644 --- a/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -153,7 +153,7 @@ protected void invokeInitMethods(String beanName, Object bean, BeanDefinition be ((InitializingBean) bean).afterPropertiesSet(); } String initMethodName = beanDefinition.getInitMethodName(); - if (StrUtil.isNotEmpty(initMethodName)) { + if (StrUtil.isNotEmpty(initMethodName) && !(bean instanceof InitializingBean && "afterPropertiesSet".equals(initMethodName))) { Method initMethod = ClassUtil.getPublicMethod(beanDefinition.getBeanClass(), initMethodName); if (initMethod == null) { throw new BeansException("Could not find an init method named '" + initMethodName + "' on bean with name '" + beanName + "'"); From b9f4673d206d4a7a95d0935036f536a8f3152d4b Mon Sep 17 00:00:00 2001 From: wangflyzihao <70521286+wangflyzihao@users.noreply.github.com> Date: Tue, 20 Feb 2024 11:54:40 +0800 Subject: [PATCH 79/81] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 。 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index e6729e6..9826c5d 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,6 @@ * [PointcutAdvisor:Pointcut和Advice的组合](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#pointcutadvisorpointcut和advice的组合) * [动态代理融入bean生命周期](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#动态代理融入bean生命周期) - #### 扩展篇 * [PropertyPlaceholderConfigurer](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#propertyplaceholderconfigurer) * [包扫描](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md#包扫描) From 1b93838f95b9d3c230dafb235bda9177f2406c92 Mon Sep 17 00:00:00 2001 From: rice666 <979581558@qq.com> Date: Tue, 5 Mar 2024 11:30:36 +0800 Subject: [PATCH 80/81] =?UTF-8?q?=E5=9C=A8README.md=E7=9A=84=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E6=96=B9=E6=B3=95=E7=AB=A0=E8=8A=82=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BA=86=E8=A7=86=E9=A2=91=E6=95=99=E7=A8=8B=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9826c5d..47c1d13 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,8 @@ ## 使用方法 阅读[changelog.md](https://github.com/DerekYRC/mini-spring/blob/main/changelog.md) +[视频教程(完整版)](https://www.bilibili.com/video/BV1nb4y1A7YJ) + ## 提问 [**点此提问**](https://github.com/DerekYRC/mini-spring/issues/4) From 2b4079e5d0372732ece7e75e93b5204228799fa0 Mon Sep 17 00:00:00 2001 From: zzziCode <1980136696@qq.com> Date: Mon, 11 Mar 2024 15:02:15 +0800 Subject: [PATCH 81/81] =?UTF-8?q?=E5=B1=9E=E6=80=A7=E5=8D=A0=E4=BD=8D?= =?UTF-8?q?=E7=AC=A6=E6=9B=BF=E6=8D=A2=E6=97=B6=E5=A2=9E=E5=8A=A0=E5=88=A4?= =?UTF-8?q?=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../beans/factory/PropertyPlaceholderConfigurer.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/beans/factory/PropertyPlaceholderConfigurer.java b/src/main/java/org/springframework/beans/factory/PropertyPlaceholderConfigurer.java index 92124cb..3ff7fa7 100644 --- a/src/main/java/org/springframework/beans/factory/PropertyPlaceholderConfigurer.java +++ b/src/main/java/org/springframework/beans/factory/PropertyPlaceholderConfigurer.java @@ -74,8 +74,11 @@ private void resolvePropertyValues(BeanDefinition beanDefinition, Properties pro for (PropertyValue propertyValue : propertyValues.getPropertyValues()) { Object value = propertyValue.getValue(); if (value instanceof String) { - value = resolvePlaceholder((String) value, properties); - propertyValues.addPropertyValue(new PropertyValue(propertyValue.getName(), value)); + //改进前后进行对比 + String strVal=(String) value; + String res = resolvePlaceholder(strVal, properties); + if(!strVal.equals(res)) + propertyValues.addPropertyValue(new PropertyValue(propertyValue.getName(), res)); } } }