关于 MySQL 新版连接驱动时区对齐问题的研究

关于 MySQL 新版连接驱动时区对齐问题的研究

D瓜哥
在一个项目开量验证过程中,发现 createDate 字段不正确,比正确时间晚了十四个小时。调研发现,这是一个非常典型的问题。现在把定位问题的思路和解决办法给大家做个分享。 首先,检查数据库配置,查询线上生产环境配置,结果如下: 图 1. MySQL 变量 同时,检查线上生产环境 MySQL 版本,为问题复现做准备: 图 2. MySQL 版本 从数据库配置上来说,基本正常,没有发现什么问题。(持续运行了这么长时间,有问题应该早就发现了。) 其次,检查数据库连接配置,正式环境的链接配置如下: jdbc:mysql://<host>:3306/<schema>?createDatabaseIfNotExist=true &characterEncoding=utf-8&useUnicode=true&connectTimeout=2000 &socketTimeout=2000&autoReconnect=true 数据库连接也没有问题。 第三,询问 SA 线上服务器时区配置,回复上是 CST,这个和数据库对应,没有问题。 图 3. 与 SA 沟通 配置检查正常,那么只好在本地搭建环境,重现问题,再寻求解决方案。由于项目是基于 Spring Boot 2.3.7.RELEASE 开发的,相关依赖也尽量使用 Spring Boot 指定版本的,所以,很快把开发环境搭好了。 在配置服务器环境时,遇到一点小小的问题:我一直以为有个时区名称叫 CST,就在网上去查怎么设置,结果徒劳半天也没有找到。后来上开发机检查开发机时区配置,发现是 Asia/Shanghai。将测试服务器设置为该时区,数据库内部查询时区,显示和服务器一直。 调试代码中,发现 MySQL 连接驱动的代码中,有配置时区的相关代码,如下: com.mysql.cj.protocol.a.NativeProtocol#configureTimezone /** * Configures the client's timezone if required. * * @throws CJException * if the timezone the server is configured to use can't be * mapped to a Java timezone. */ public void configureTimezone() { // 获取服务器时区 String configuredTimeZoneOnServer = this.serverSession.getServerVariable("time_zone"); // 如果服务器时区是 SYSTEM,则使用服务器的 system_time_zone 时区设置 if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) { configuredTimeZoneOnServer = this.serverSession.getServerVariable("system_time_zone"); } // 获取客户端时区配置 String canonicalTimezone = getPropertySet().getStringProperty(PropertyKey.serverTimezone).getValue(); // 如果服务器时区不为空,切客户端时区配置不可用,则使用服务器的时区配置 if (configuredTimeZoneOnServer != null) { // user can override this with driver properties, so don't detect if that's the case if (canonicalTimezone == null || StringUtils.isEmptyOrWhitespaceOnly(canonicalTimezone)) { try { canonicalTimezone = TimeUtil.getCanonicalTimezone(configuredTimeZoneOnServer, getExceptionInterceptor()); } catch (IllegalArgumentException iae) { throw ExceptionFactory.createException(WrongArgumentException.class, iae.getMessage(), getExceptionInterceptor()); } } } if (canonicalTimezone != null && canonicalTimezone.length() > 0) { // 为该会话设置时区 this.serverSession.setServerTimeZone(TimeZone.getTimeZone(canonicalTimezone)); // // The Calendar class has the behavior of mapping unknown timezones to 'GMT' instead of throwing an exception, so we must check for this... // if (!canonicalTimezone.equalsIgnoreCase("GMT") && this.serverSession.getServerTimeZone().getID().equals("GMT")) { throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("Connection.9", new Object[] { canonicalTimezone }), getExceptionInterceptor()); } } }
Raft 论文摘要(二)

Raft 论文摘要(二)

D瓜哥
在上一篇文章中,通过阅读 《In Search of an Understandable Consensus Algorithm》 前三节的内容,对论文的大致内容做了简介,简单说明了一下 Replicated state machines 的用途以及 Paxos 本身存在的问题。 4. Designing for understandability several goals in designing Raft: it must providea complete and practical foundation for system building; it must be safe under all conditions and available under typical operating conditions; it must be efficient for common operations. Our most important goal — and most difficult challenge — was understandability. 从这里可以看出,Raft 设计的初衷就是为了易于理解和便于构建。 There were numerous points in the design of Raft where we had to choose among alternative approaches. In these situations we evaluated the alternatives based on understandability.
Raft 论文摘要(一)

Raft 论文摘要(一)

D瓜哥
前一段时间,在一次开组会的时候,给小组成员简单介绍了一下 Raft 协议。大概四年前读过 Raft 的论文,这次分享的时候,好多好多细节都忘了。所以,再次把 《In Search of an Understandable Consensus Algorithm》 这篇论文找出来,重读一遍,做个笔记和摘要,方便后续学习和复习。 Abstract Raft is a consensus algorithm for managing a replicated log. 开篇摘要就点出了 Raft 的特点: Raft 是一种管理复制日志的共识算法。 In order to enhance understandability, Raft separates the key elements of consensus, such as leader election, log replication, and safety, and it enforcesa stronger degree of coherency to reduce the number of states that must be considered. 为了增强可理解性,Raft 将共识分解成几个关键元素,例如 Leader 选举,日志复制,以及安全性等;同时,为了降低需要考虑的状态的数量,还强制实施了更强的一致性。 1. Introduction Consensus algorithms allow a collection of machines to work as a coherent group that can survive the failures of some of its members.
从 Spring PR 中学习代码技巧

从 Spring PR 中学习代码技巧

D瓜哥
D瓜哥经常关注 Spring 的 PR 与 Issue。在众多 Contributor 中,除了 Spring 团队成员之外,我对 stsypanov (Сергей Цыпанов) 印象很深刻。这哥们给 Spring 提了非常多的 PR,请看列表 Pull requests · spring-projects/spring-framework,而且这个哥们的 PR 都非常有特点,绝大部分是性能提升方面的 PR,而且还会给出 JMH 的测试结果。不愧是毛熊人,做事细致严谨。 这周心血来潮,把这哥们的 PR 翻一翻,希望可以学习一些编码技巧。简单记录一下,以备以后回顾学习。 提高 Map 的遍历性能 请看: SPR-17074 Replace iteration over Map::keySet with Map::entrySet by stsypanov · Pull Request #1891 摘取一个示例如下: // --before update------------------------------------------------------ for (String attributeName : attributes.keySet()) { Object value = attributes.get(attributeName); // --after update------------------------------------------------------- for (Map.Entry<String, Object> attributeEntry : attributes.entrySet()) { String attributeName = attributeEntry.getKey(); Object value = attributeEntry.getValue(); 这个改动很小,但是对性能的改善还是比较显著的。翻看自己项目的代码,还是有不少是改动前的写法。 针对这点,D瓜哥也给 Spring 发了一个 PR: Improve performance of iteration in GroovyBeanDefinitionReader by diguage · Pull Request #27100。相信不久就会合并到 main 分支的。 所以,给 Spring 以及其他开源项目提 PR,其实一点也不难。只要,你花心思去研究,肯定有机会的。不过,也反思一点:我这个 PR 有点东施效颦的感觉,有点刷 KPI 的样子。还是应该脚踏实地去好好研究,提更多更有建设性意见的 PR。
在七夕“摘”情诗

在七夕“摘”情诗

D瓜哥
今天七夕情人节,选摘几首情诗做个纪念。 邶风 · 静女 静女其姝,俟我于城隅。爱而不见,搔首踟蹰。 静女其娈,贻我彤管。彤管有炜,说怿女美。 自牧归荑,洵美且异。匪女之为美,美人之贻。 郑风 · 子衿 青青子衿,悠悠我心。纵我不往,子宁不嗣音? 青青子佩,悠悠我思。纵我不往,子宁不来? 挑兮达兮,在城阙兮。一日不见,如三月兮! 舒婷 · 致橡树 我如果爱你—— 绝不像攀援的凌霄花, 借你的高枝炫耀自己; 我如果爱你—— 绝不学痴情的鸟儿, 为绿荫重复单调的歌曲; 也不止像泉源, 常年送来清凉的慰藉; 也不止像险峰, 增加你的高度,衬托你的威仪。 甚至日光。 甚至春雨。 不,这些都还不够! 我必须是你近旁的一株木棉, 作为树的形像和你站在一起。 根,紧握在地下, 叶,相触在云里。 每一阵风过, 我们都互相致意, 但没有人, 听懂我们的言语。 你有你的铜枝铁干, 像刀,像剑, 也像戟; 我有我红硕的花朵, 像沉重的叹息, 又像英勇的火炬。 我们分担寒潮、风雷、霹雳; 我们共享雾霭、流岚、虹霓。 仿佛永远分离, 却又终身相依。 这才是伟大的爱情, 坚贞就在这里: 爱—— 不仅爱你伟岸的身躯, 也爱你坚持的位置,足下的土地。 在 告别 2019,迎接 2020 中已经展示过这张照片了。但是,没有多做说明。这里就简要介绍一下。 2019 年端午自驾游时,在内蒙古的一座山上,无意间看到了上面照片中的这两棵树,形象神似《致橡树》中“站在一起的两棵树”。所以,就专门拍下了这张照片留作纪念。 席慕蓉 · 一棵开花的树 如何让你遇见我 在我最美丽的时刻为这 我已在佛前求了五百年 求它让我们结一段尘缘 佛于是把我化作一棵树 长在你必经的路旁 阳光下慎重地开满了花 朵朵都是我前世的盼望
《远见》之读书笔记

《远见》之读书笔记

D瓜哥
最近向一个朋友了解一家公司情况时,聊到职业发展的问题。就随手推荐了《远见:如何规划职业生涯3大阶段》这本书。正好利用这个机会,把以前的读书摘要发布出来。 这本书,D瓜哥在 告别 2019,迎接 2020:《远见》 中也提到过。这里把当时写的读书笔记直接拷贝过来: 本书讲人生的职业生涯分为三个阶段: 第一阶段是强势开局的时候。你在职业上的努力必须着重于为前方的漫长道路挖掘和装备自己。你的学习曲线要比职位、职称更加重要。在这一阶段,要为职业生涯打好基础并建立起良好的早期习惯。 第二阶段是聚焦长板的时候。该阶段的首要目标是寻找自己的甜蜜区,即你所擅长的、所热爱的和这个世界所需要的这三者之间的交集。这个时候你要展现自我,让自己鹤立鸡群,想方设法平稳地走在那条收获最大的职场路径上。你要专注于自己的长板,且大可忽略自己的短板。 第三阶段致力于实现持续的影响力,以及寻找一条可以稳定延续到60多岁甚至70多岁的新的可持续职业道路。你要在第三阶段完成三个关键任务:完成继任计划、保持关联性,以及为自己点燃一团新的职业之火。 用一句话来总结:第一阶段:加添燃料,强势开局; 第二阶段:聚焦长板,达到高点; 第三阶段:优化长尾,持续发挥影响力。 三大职场燃料来源:可迁移技能、有意义的经验和持久的关系。 5个数字,树立正确的职场思维 职业生涯的长度:用62减去你目前的年龄。 精通一项技能所需的时间:要花多少小时才能在某一方面达到“精通”? 40岁之后能赚到的个人财富百分比:在40岁之后,你赚到的钱会占你一生个人财富的百分之多少?大部分人的估计是60%。 社交货币:你有多少社交网络好友? 职场支持者的人数:你认为能在“职业生涯的天堂”里遇到多少人,也就是说有多少人能对你的职业生涯和人生带来真正的变化? 这本书强烈推荐给对未来职业有追求的小伙伴! 好戏开始,下面👇是读书摘要: PART 1 远见思维与工具箱 远见思维:多行动,少忧虑 职业生涯的持续时间长得惊人,包括了三个截然不同的阶段。 第一阶段是强势开局的时候。你在职业上的努力必须着重于为前方的漫长道路挖掘和装备自己。你的学习曲线要比职位、职称更加重要。 第二阶段是聚焦长板的时候。该阶段的首要目标是寻找自己的甜蜜区,即你所擅长的、所热爱的和这个世界所需要的这三者之间的交集。 第三阶段致力于实现持续的影响力,以及寻找一条可以稳定延续到 60多岁甚至 70多岁的新的可持续职业道路。 职场燃料很重要,因为职业生涯的基础决定结果。 这种燃料有三个主要来源:可迁移技能、有意义的经验和持久的关系。 职业生涯需要通过对时间的巧妙投资来构建。 职业生涯并不是以线性或者可预测的方式发展的。 职业生涯远不止于一份工作,而是生活的一大部分。 实现职业规划要做到的5件事 学习职场数学,树立正确的长期思维方式。 盘点职场清单,梳理你最有用处的技能、经验和关系。 进行“ 100小时测试”并完成一份“个人时间档案”,从而了解你目前的时间投资状况。 在尝试建立新的职场路径或者在多个选项中抉择时,运用“职场路径向导”( Career Path Navigator)。 时刻更新你的职业生涯 我如何避免被机器取代? 我能在哪里以什么方式找到工作? 未来我将如何分配时间? 我会把钱花光吗? 工作如何能让我更幸福? 三大阶段,聚焦 45 年职业生涯 第一阶段:加添燃料,强势开局; 第二阶段:聚焦长板,达到高点; 第三阶段:优化长尾,持续发挥影响力。 第一阶段:加添燃料,强势开局 职业生涯前 15年的唯一目标就是为接下来的两个阶段打好基础。 第一阶段是探索和弥补自身短板的时候。如果你是个糟糕的演讲者,那就去参加相关的培训课程。如果你对待团队成员过于强势或弱势,那就去参加领导力培训。学习要比纯粹的成功更重要。有时跌倒并不可怕,只要你能吸取教训,并将这些经验加以利用就行。 第二阶段:锚点甜蜜区,聚焦长板 第二阶段是在你的长板、你的爱好以及这个世界的需求之间寻找交集的时候。 坦率地承认自己的短板,针对它们招募盟友,你就可以把大多数时间放在核心长板上了。 第三阶段:优化长尾,持续发挥影响力
神奇的 Morris 树遍历

神奇的 Morris 树遍历

D瓜哥
无论是在计算机课上,还是在网上,对数据结构有一定了解的童鞋,应该都对树的遍历不陌生。常见的遍历方式有如下两种: 基于栈的遍历:需要额外的空间复杂度。实现略复杂。 基于递归的遍历:实现简单。空间复杂度上,与栈类似,只是这里的栈维护是由系统自动完成的。 在看左程云的《程序员代码面试指南》时,里面介绍了一种只需 O(1) 的额外空间复杂度的遍历方法:Morris 树遍历。感觉非常神奇。这里给大家介绍一下。 Morris 树遍历的神奇之处在于,它充分利用了树里面大量的空闲节点(比如叶子节点的左右子树节点就为空,可以利用起来)来建立起必要的连接,推动遍历的进行。核心思想非常简单:找出当前节点左子树的最右节点,此时最右节点的右子树为空,将最右节点的右子树指向当前节点。然后左移,递归完成所有相关连接。没有左子树时,则向右移动,依次完成上述操作。我们来结合图来说明。如图: 图 1. Morris 树遍历 如上图所示,当访问根节点 4 时,它的左子树的最右节点就是 3,将 3 的右子树指向当前节点 4,如线条 ⑥ 所示。向左启动,建立起 1 到 2 的连接 ④。再向左移动到 1,1 没有左子树,则向右移动,此时就利用上了刚刚建立起的连接 ④。依次类推,即可完成遍历。在遍历过程中,也需要把建立的临时连接给取消掉。 /** * Morris 树遍历 * * @author D瓜哥 · https://www.diguage.com */ public void morris(TreeNode head) { TreeNode curr = head; TreeNode mostRight = null; while (curr != null) { // 当前节点左子树的最右节点,当然是从左子树开始了 mostRight = curr.left; if (mostRight != null) { // 左子树不为空,则找出左子树的最右节点 while (mostRight.right != null // 由于需要建立最右节点到当前节点的连接,所以,需要判断是否已建立连接来打破死循环 && mostRight.right != curr) { mostRight = mostRight.right; } if (mostRight.right == null) { // 最右节点的右子树为空,则第一次访问,那么建立起连接 mostRight.right = curr; curr = curr.left; continue; } else { // 最右节点的右子树不为空,则第二次访问,打破连接,恢复原貌 mostRight.right = null; } } // 最子树为空,则向右移动 curr = curr.right; } } 根据代码,结合图示,很容易得到 Morris 遍历的结果: 4、2、1、22、3、42、6、5、62、7。分析这个结果可以发现:有左子树的节点都会被访问两次。 前根遍历 那么该树的前根遍历是什么呢?这个也很容易得出:4、2、1、3、6、5、7。如何从 Morris 遍历中,得到前根遍历的结果呢?对比两边的结果,可以很容易发现:将访问两次的元素,只在第一次访问时输出;只访问一次的原始直接输出即可。代码如下: /** * Morris 树前根遍历 * * @author D瓜哥 · https://www.diguage.com */ public void morrisPre(TreeNode head) { TreeNode curr = head; TreeNode mostRight = null; while (curr != null) { // 当前节点左子树的最右节点,当然是从左子树开始了 mostRight = curr.left; if (mostRight != null) { // 左子树不为空,则找出左子树的最右节点 while (mostRight.right != null // 由于需要建立最右节点到当前节点的连接,所以,需要判断是否已建立连接来打破死循环 && mostRight.right != curr) { mostRight = mostRight.right; } if (mostRight.right == null) { // 最右节点的右子树为空,则第一次访问,那么建立起连接 mostRight.right = curr; // 第一次访问时,即输出 System.out.print(curr.val + " "); curr = curr.left; continue; } else { // 最右节点的右子树不为空,则第二次访问,打破连接,恢复原貌 mostRight.right = null; } } else { // 前面内容已经分析过:有左子树的节点就会被访问两次 // 那么没有左子树的节点,就自会访问一次,访问到时直接输出即可。 System.out.print(curr.val + " "); } // 最子树为空,则向右移动 curr = curr.right; } }
Spring AOP 源码分析:创建代理(二)

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

D瓜哥
Spring AOP 源码分析:入门 中,梳理出来了 Spring AOP 的入口。 Spring AOP 源码分析:获得通知 中着重介绍了如何获取通知。上一篇文章 Spring AOP 源码分析:创建代理(一) 重点介绍了一下切面链的组装和基于 JDK 动态代理的 AOP 的实现,这篇文章介绍一下基于 cglib 的代理类是生成。 cglib 简介 CGLIB(Code Generator Library)是一个高性能的代码生成库,被广泛应用于 AOP 框架(Spring)中以提供方法拦截功能,主要以继承目标类的方式来进行拦截实现,因此 CGLIB 可以对无接口的类进行代理。 CGLIB代理主要通过操作字节码的方式为对象引入方法调用时访问操作,底层使用了ASM来操作字节码生成新的类,ASM是一个短小精悍的字节码操作框架。CGLIB的应用栈如下: 最新版的 Hibernate 已经把字节码库从 cglib 切换为 Byte Buddy。 JDK 动态代理是通过实现 InvocationHandler 接口,在其 invoke 方法中添加切面逻辑。而 cglib 则是通过实现 MethodInterceptor 接口,在其 invoke 方法中添加切面逻辑。 下面看一下在 Spring 中,是如何实现利用 cglib 来实现 AOP 编程的? CglibAopProxy 先看一下创建代理对象的方法: CglibAopProxy#getProxy(ClassLoader) @Override public Object getProxy(@Nullable ClassLoader classLoader) { if (logger.isTraceEnabled()) { logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource()); } try { Class<?> rootClass = this.advised.getTargetClass(); Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy"); Class<?> proxySuperClass = rootClass; if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) { proxySuperClass = rootClass.getSuperclass(); Class<?>[] additionalInterfaces = rootClass.getInterfaces(); for (Class<?> additionalInterface : additionalInterfaces) { this.advised.addInterface(additionalInterface); } } // Validate the class, writing log messages as necessary. // 验证 Class validateClassIfNecessary(proxySuperClass, classLoader); // Configure CGLIB Enhancer... Enhancer enhancer = createEnhancer(); if (classLoader != null) { enhancer.setClassLoader(classLoader); if (classLoader instanceof SmartClassLoader && ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) { enhancer.setUseCache(false); } } enhancer.setSuperclass(proxySuperClass); enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader)); // 设置拦截器 Callback[] callbacks = getCallbacks(rootClass); Class<?>[] types = new Class<?>[callbacks.length]; for (int x = 0; x < types.length; x++) { types[x] = callbacks[x].getClass(); } // fixedInterceptorMap only populated at this point, after getCallbacks call above enhancer.setCallbackFilter(new ProxyCallbackFilter( this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset)); enhancer.setCallbackTypes(types); // Generate the proxy class and create a proxy instance. // 生成代理类以及创建代理 return createProxyClassAndInstance(enhancer, callbacks); } catch (CodeGenerationException | IllegalArgumentException ex) { throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() + ": Common causes of this problem include using a final class or a non-visible class", ex); } catch (Throwable ex) { // TargetSource.getTarget() failed throw new AopConfigException("Unexpected AOP exception", ex); } }
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.addAdvisors(advisors); // 此处的targetSource一般为SingletonTargetSource proxyFactory.setTargetSource(targetSource); // 定制代理,扩展点,空实现 customizeProxyFactory(proxyFactory); // 用来控制代理工厂被配置后,是否还允许修改通知 // 缺省为 false proxyFactory.setFrozen(this.freezeProxy); // 是否设置预过滤模式,此处针对本文为true if (advisorsPreFiltered()) { proxyFactory.setPreFiltered(true); } // 获取使用JDK动态代理或者cglib动态代理产生的对象 return proxyFactory.getProxy(getProxyClassLoader()); } ProxyFactory#getProxy(ClassLoader) public Object getProxy(@Nullable ClassLoader classLoader) { // 1、创建JDK方式的AOP代理或者CGLib方式的AOP代理 // 2、调用具体的AopProxy来创建Proxy代理对象 return createAopProxy().getProxy(classLoader); } 在 createAopProxy() 方法中就不再列出,因为 AopProxyFactory 接口只有一个实现类 DefaultAopProxyFactory。所以,直接来看看 getProxy(classLoader) 方法: DefaultAopProxyFactory#createAopProxy @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { // 如果实现接口,默认采用Java动态代理 // 如果没有接口,或者有接口却强制使用 cglib if (!IN_NATIVE_IMAGE && // optimize 是否实用激进的优化策略 // proxyTargetClass 为 true,则代理类本身而不是接口 // 是否存在代理接口 (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }
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. //当使用注解方式配置AOP的时候并不是丢弃了对XML配置的支持 //在这里调用父类方法加载配置文件中的AOP声明 List<Advisor> advisors = super.findCandidateAdvisors(); // Build Advisors for all AspectJ aspects in the bean factory. if (this.aspectJAdvisorsBuilder != null) { advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors()); } return advisors; }