Spring

Spring AOP 源码分析:创建代理(一)

Spring AOP 源码分析:创建代理(一)

D瓜哥
Spring AOP 源码分析:入门 中,梳理出来了 Spring AOP 的入口。上一篇文章 Spring AOP 源码分析:获得通知 中着重介绍了如何获取通知。接着上一篇文章,这篇文章介绍一下如何创建代理。 AbstractAutoProxyCreator#createProxy protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) { if (this.beanFactory instanceof ConfigurableListableBeanFactory) { AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass); } // 创建代理工厂对象 ProxyFactory proxyFactory = new ProxyFactory(); // 获取当前类的属性 proxyFactory.copyFrom(this); //如果没有使用CGLib代理 if (!proxyFactory.isProxyTargetClass()) { // 是否可能使用CGLib代理 // 决定对于给定的 Bean 是否应该使用 targetClass 而不是他的接口代理 if (shouldProxyTargetClass(beanClass, beanName)) { proxyFactory.setProxyTargetClass(true); } else { // 查看beanClass对应的类是否含有InitializingBean.class/DisposableBean.class/Aware.class接口 // 无则采用JDK动态代理,有则采用CGLib动态代理 evaluateProxyInterfaces(beanClass, proxyFactory); } } // 获得所有关联的Advisor集合(该分支待补充) Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); proxyFactory.
Spring AOP 源码分析:获得通知

Spring AOP 源码分析:获得通知

D瓜哥
在文章 Spring AOP 处理流程概述 中,对 Spring AOP 有了一个整体认识。在文章 Spring AOP 源码分析:入门 中,对 Spring AOP 的相关入口做了分析。这篇文章就带大家看一看,Spring AOP 是如何获取通知的? 示例代码 在 如何阅读 Spring 源码?: 示例代码 中,已经给出了一个完整的 AOP 示例代码。为了节省篇幅,请直接参考那篇文章的示例代码,这里就不在赘述。 注册 Advice(通知/增强) 请根据 Spring AOP 源码分析:入门 中提到的关键方法入口处,打上断点,开始调试。 首先,需要明确一点的是:对于切面(使用 @Aspect 注解标注过的类)在 Spring 容器中,也是被统一f封装为 BeanDefinition 实例的,也需要通过一个方式,将其注册到 Spring 容器中。比如,就像 示例代码 那样,通过 ImportSelector 方式,使用类名,将其注册到容器中。这样,就可以利用 Spring 容器对 Bean 的 API 来统一处理了。 Advice(通知/增强)几乎是在意想不到的地方完成注册的:在第一次调用 AbstractAutoProxyCreator#postProcessBeforeInstantiation 方法时,通过 AspectJAwareAdvisorAutoProxyCreator#shouldSkip 方法,完成了切面的注册。下面,我们对这个过程抽丝剥茧,逐步分析。 先来看看 findCandidateAdvisors 方法: AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors @Override protected List<Advisor> findCandidateAdvisors() { // Add all the Spring advisors found according to superclass rules.
如何阅读 Spring 源码?

如何阅读 Spring 源码?

D瓜哥
昨晚原计划给几个朋友简单介绍一下阅读 Spring 源码的方法。结果,大家因为各种原因没能及时参加。后来,就取消分享了。干脆写一篇文章出来,感兴趣欢迎自取。 代码准备 Spring Framework 是开源的,代码托管在 GitHub 上: Spring Framework。任何人都可以方便地获得它的源代码。所以,如果想阅读 Spring 的源代码,当然是直接把代码克隆到本地,然后直接在 IDE(推荐 IDEA)中进行调试了。另外,还需要存放自己写一些测试和文档。所以,最好把代码 fork 到自己的账户下,从 master 上切出一个新分支并 push 到自己的 Repo 中,这样自己就可以随意更新了。具体步骤如下: 克隆代码 # 直接克隆原始仓库为 origin git clone git@github.com:spring-projects/spring-framework.git fork 代码,D瓜哥直接 fork 到自己账户下了: diguage/spring-framework。 添加原创仓库地址: # 添加自己仓库为 diguage # 这样就能在所有项目中保持命名的一致性,方便标识 git remote add diguage git@github.com:diguage/spring-framework.git 创建新分支 # 创建新分支 git switch -c analysis # 将新分支 push 到自己的 Repo 中 git push diguage analysis 这样,在这个新分支上,就可以随意折腾了。 下载依赖 # Mac or Linux .
Spring AOP 源码分析:入门

Spring AOP 源码分析:入门

D瓜哥
在上一篇文章 Spring AOP 处理流程概述 中,对 Spring AOP 有了一个整体认识。这篇文章就带大家做一个细致的源码分析。 登堂入室 使用 Spring AOP 也很简单,只需要在配置类上加上 @EnableAspectJAutoProxy 注解即可。这个注解处理过程与 Spring 扩展点实践:整合 MyBATIS 中 “@MapperScan 处理” 类似,不同的是,Spring AOP 注册了 AnnotationAwareAspectJAutoProxyCreator,它是一个 InstantiationAwareBeanPostProcessor。具体的类图如下: 图 1. AnnotationAwareAspectJAutoProxyCreator 的继承体系 在正式开始源码分析之前,有一点必须强调一下:Spring AOP 只是借用了 AspectJ 的一些注解和个别关键 API,而整体实现是 Spring 自己完成的,并不是基于 AspectJ 实现的。这一点跟很多人的认识是不一样的,需要特别指出。 D瓜哥在 Spring Bean 生命周期概述 中指出:创建 AOP 代理对象,有两个时机: 调用 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 时,通过调用 AnnotationAwareAspectJAutoProxyCreator 对象的 postProcessBeforeInstantiation 方法来创建对象; 调用 BeanPostProcessor#postProcessAfterInitialization 时,通过调用 AnnotationAwareAspectJAutoProxyCreator 对象的 postProcessAfterInitialization 方法来创建对象; 下面分别对这两个方法做更详细的介绍。 AnnotationAwareAspectJAutoProxyCreator#postProcessBeforeInstantiation AnnotationAwareAspectJAutoProxyCreator 的 postProcessBeforeInstantiation 方法是从 AbstractAutoProxyCreator 继承过来的。代码如下:
源码剖析 Spring 循环依赖

源码剖析 Spring 循环依赖

D瓜哥
循环依赖在编程中是一个常见问题(当然,这并不是最佳实践)。并且,Spring 如何解决循环依赖这个问题在面试中也经常见。下面,D瓜哥就从源码的层面深入剖析一下这个问题。 示例程序 先展示一下示例程序: package com.diguage.truman.context; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; import org.springframework.stereotype.Component; /** * @author D瓜哥, https://www.diguage.com/ * @since 2020-05-24 13:02 */ public class CircularDependenceSingletonTest { public static final Log log = LogFactory.getLog(CircularDependenceSingletonTest.class); @Test public void test() { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.register(Config.class); applicationContext.refresh(); log.info(applicationContext.getBean(A.class)); log.info(applicationContext.getBean(B.class)); log.info(applicationContext.getBean(C.class)); log.info("-A--------"); A a = applicationContext.getBean(A.class); log.info(a); log.info(a.b); log.
Spring 扩展点实践:整合 Apache Dubbo(二)

Spring 扩展点实践:整合 Apache Dubbo(二)

D瓜哥
在 Spring 扩展点实践:整合 Apache Dubbo(一) 中,D瓜哥介绍了 Dubbo 如何使用 Spring 的插件机制与 Spring 整合。限于篇幅原因,上一篇文章只介绍到了服务提供者的注册。本篇文章继续上一篇文章的主题,继续介绍 Spring 与 Dubbo 的整合过程。先来讲解一下服务消费者的生成过程。 Dubbo 生成服务消费者的过程 先来看看 XML 配置文件: dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/src/main/resources/spring/dubbo-consumer.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> <dubbo:application name="demo-consumer"/> <dubbo:registry address="zookeeper://127.0.0.1:2181"/> <dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService"/> </beans> 我们先看一下 ReferenceBean 类的声明: org.apache.dubbo.config.spring.ReferenceBean public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean { // 此处省略 N 行代码 @Override public Object getObject() { return get(); } // 此处省略 N 行代码 @Override @SuppressWarnings({"unchecked"}) public void afterPropertiesSet() throws Exception { // Initializes Dubbo's Config Beans before @Reference bean autowiring prepareDubboConfigBeans(); // lazy init by default.
Spring 扩展点实践:整合 Apache Dubbo(一)

Spring 扩展点实践:整合 Apache Dubbo(一)

D瓜哥
在上一篇文章 Spring 扩展点概览及实践 中介绍了 Spring 内部存在的扩展点。 Spring 扩展点实践:整合 MyBATIS 中,D瓜哥带大家了解了一下 MyBATIS 如何利用 Spring 的扩展点实现了与 Spring 的完美整合。现在,学以致用,我们继续来分析一下 Spring 与 Apache Dubbo 的整合流程。 示例程序 Apache Dubbo 仓库中就有很完整的示例。D瓜哥直接拿来使用就不再搭建示例程序了。 首先,需要启动一个 ZooKeeper 实例。查看 Dubbo 的依赖可以看出,最新版代码依赖的 ZooKeeper 是 3.4.13 版。所以,为了最好的兼容性,就要选用 3.4.X 版的 ZooKeeper 服务器。D瓜哥直接使用 Docker 启动 ZooKeeper 了。命令如下: docker run --rm --name zookeeper -d -p 2181:2181 zookeeper:3.4.14 这次我们使用 Apache Dubbo 的 dubbo-demo/dubbo-demo-xml 示例。 第二步,启动服务提供者程序,找到 DUBBO/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider/src/main/java/org/apache/dubbo/demo/provider/Application.java,运行该类。 第三步,运行服务消费者程序,找到 DUBBO/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/src/main/java/org/apache/dubbo/demo/consumer/Application.java,运行该类。 如果没有任何错误,则在终端可以看到 result: async result 输出。 在开始正餐之前,D瓜哥先给大家来个开胃菜。 Spring 插件机制简介 不知道大家有没有想过一个问题:Spring 框架是如何支持越来越多的功能的?
深入剖析 Spring 核心数据结构:BeanFactory

深入剖析 Spring 核心数据结构:BeanFactory

D瓜哥
在 深入剖析 Spring 核心数据结构:BeanDefinition 中,介绍了 BeanDefinition。网上很多文章介绍 BeanDefinition 的 API,D瓜哥却要反其道而行之,从内部属性来分析一下。下面我们开始。 继承体系 Spring 非常好地遵循了面向对象的设计原则:面向接口编程。不放过任何可以提取出成接口的机会。虽然感觉似乎增加了类的继承关系,增加了一点的复杂度。但是,却带来了非常好的可扩展性。而 BeanFactory 的继承体系就是一个非常典型的例子。我们来看一下它的继承体系: 图 1. BeanFactory 继承体系 AliasRegistry:别名注册器。Spring 中,别名注册相关的功能就是从这里实现的。 SimpleAliasRegistry:别名注册器的一个简单实现,从内部属性可以看出,它是把别名映射信息存到一个 Map 中了。 DefaultSingletonBeanRegistry:默认的单例 Bean 注册器,从内部属性来说,也是基于 Map 实现的。 FactoryBeanRegistrySupport: FactoryBean 注册器。 SingletonBeanRegistry:单例 Bean 注册器。 BeanDefinitionRegistry: BeanDefinition 注册器。 BeanFactory:容器的基类。 ListableBeanFactory:在基本容器基础上,增加了遍历相关功能。 HierarchicalBeanFactory:在基本容器基础上,增加了父子上下级容器关联。 AutowireCapableBeanFactory:在基本容器基础上,增加了自动注入功能。 ConfigurableBeanFactory:对容器增加可配置性,比如父级容器、ClassLoader、TypeConverter 等。 ConfigurableListableBeanFactory:可配置可遍历容器。 AbstractBeanFactory:容器的抽象实现类,实现了容器的基础功能。 AbstractAutowireCapableBeanFactory:带自动装配功能的抽象容器类。 DefaultListableBeanFactory:这是 Spring 内部使用的默认容器实现。也是 Spring 中最重要的一个类。 核心属性 Registry Map<String, String> aliasMap = new ConcurrentHashMap<>(16):别名到 Bean 名称的映射。 Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256):Bean 名称到单例 Bean 的映射。可以理解成,这就是所谓的容器。
深入剖析 Spring 核心数据结构:BeanDefinition

深入剖析 Spring 核心数据结构:BeanDefinition

D瓜哥
林纳斯·托瓦兹(Linus Torvalds)说:“我从心底认为,优秀的程序员与平庸的程序员之间的区别,是在于认为自己的代码重要还是数据结构更加重要。平庸的程序员眼里只有代码,优秀的程序员则关注数据结构及之前的关系。” 也许很多人觉得 Spring 神秘莫测,但是如果了解了它的核心数据结构,很多问题迎刃而解。 Spring 中两个数据结构最核心:① BeanDefinition,用于表示 Bean 的定义;② BeanFactory,用于表示整个 IoC 容器。 在前面文章 Spring Bean 生命周期概述中,介绍了 Spring Bean 的生命周期。不知道大家有没有思考过 Spring 在内部是如何表示一个 Bean 的?本篇文章,就来聊一聊 BeanDefinition 问题 使用 Spring 时,尤其是使用 XML 配置的时候,也许我们会这样的问题: Bean 怎么表示? Bean 的依赖怎么表示? init-method 方法怎么存储? Bean 的一些属性,比如 lazy-init 等,怎么表示? Bean 构造函数的参数怎么存储? …​ Java 也有类似的问题,比如怎么表示一个类?Java 通过反射 API 来解决这个问题: Class Method Field Constructor Annotation 但是,为什么 Spring 还要自己定义一套呢?主要原因是 Java 反射 API 不满足 Spring 的需求,比如,它没办法表示哪些类是 SCOPE_SINGLETON,哪些类是 SCOPE_PROTOTYPE。 另外,Spring 的 Bean 抽象也并不是完全自定义的,它是基于 Java 反射 API 又增加了自定义功能,其核心 API 就是 BeanDefinition。下面,我们来仔细看一下它的继承体系以及内部核心属性。
Spring AOP 处理流程概述

Spring AOP 处理流程概述

D瓜哥
AOP 是 Spring 框架的最核心的两个功能之一,在前面的 Spring 启动流程概述 和 Spring Bean 生命周期概述 两篇文章中,分别介绍了 Spring 启动过程和 Spring Bean 的生命周期,对 IoC 有了一个细致介绍。这里来细致分析一下 Spring AOP 的实现原理和处理流程。 基本概念 先来了解几个基本概念,D瓜哥私以为这些概念是 AOP 中最核心的内容,了解了基本概念,可以说基本上掌握了一半的 AOP 内容。 学习概念最权威的地方,当然就是官方文档。所以,这些概念可以在 Spring Framework Documentation: AOP Concepts 中看到最权威的介绍。 Join point(连接点): 所谓的连接点是指那些被拦截到的点。在 Spring 中,连接点指的是方法,因为 Spring 只支持方法类型的连接点。在 Spring 中,使用 Pointcut(切入点): 所谓的切入点,是指要对哪些 Join point(连接点) 进行拦截的定义。如果 Join point(连接点) 是全集,那么 Pointcut(切入点) 就是被选中的子集。写 AOP 代码的时候,一般是用 Pointcut(切入点) 表达式进行对 Join point(连接点) 进行选择。 Advice(通知/增强): 所谓的通知就是指拦截到 Join point(连接点) 之后所要做的事情。通知根据作用位置不同,又细分为: Before advice(前置通知): 在 Join point(连接点) 之前运行的通知。这种通知,不能阻止执行流程继续到 Join point(连接点)。