曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享
曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解
曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下
曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?
曹工说Spring Boot源码(5)-- 怎么从properties文件读取bean
曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的
曹工说Spring Boot源码(7)-- Spring解析xml文件,到底从中得到了什么(上)
曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中得到了什么(util命名空间)
曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中得到了什么(context命名空间上)
曹工说Spring Boot源码(10)-- Spring解析xml文件,到底从中得到了什么(context:annotation-config 解析)
曹工说Spring Boot源码(11)-- context:component-scan,你真的会用吗(这次来说说它的奇技淫巧)
曹工说Spring Boot源码(12)-- Spring解析xml文件,到底从中得到了什么(context:component-scan完整解析)
曹工说Spring Boot源码(13)-- AspectJ的运行时织入(Load-Time-Weaving),基本内容是讲清楚了(附源码)
曹工说Spring Boot源码(14)-- AspectJ的Load-Time-Weaving的两种实现方式细细讲解,以及怎么和Spring Instrumentation集成
曹工说Spring Boot源码(15)-- Spring从xml文件里到底得到了什么(context:load-time-weaver 完整解析)
曹工说Spring Boot源码(16)-- Spring从xml文件里到底得到了什么(aop:config完整解析【上】)
曹工说Spring Boot源码(17)-- Spring从xml文件里到底得到了什么(aop:config完整解析【中】)
曹工说Spring Boot源码(18)-- Spring AOP源码分析三部曲,终于快讲完了 (aop:config完整解析【下】)
曹工说Spring Boot源码(19)-- Spring 带给我们的工具利器,创建代理不用愁(ProxyFactory)
曹工说Spring Boot源码(20)-- 码网恢恢,疏而不漏,如何记录Spring RedisTemplate每次操作日志
曹工说Spring Boot源码(21)-- 为了让大家理解Spring Aop利器ProxyFactory,我已经拼了
曹工说Spring Boot源码(22)-- 你说我Spring Aop依赖AspectJ,我依赖它什么了
曹工说Spring Boot源码(23)-- ASM又立功了,Spring原来是这么递归获取注解的元注解的
曹工说Spring Boot源码(24)-- Spring注解扫描的瑞士军刀,asm技术实战(上)
工程代码地址 思维导图地址
工程结构图:
概要上一篇,我们讲了ASM基本的使用方法,具体包括:复制一个class、修改class版本号、增加一个field、去掉一个field/method等等;同时,我们也知道了怎么才能生成一个全新的class。
但是,仅凭这点粗浅的知识,我们依然不太理解能干嘛,本篇会带大家实现简单的AOP功能,当然了,学完了之后,可能你像我一样,更困惑了,那说明你变强了。
本篇的核心是,在JVM加载class的时候,去修改class,修改class的时候,加入我们的aop逻辑。JVM加载class的时候,去修改class,这项技术就是load-time-weaver,实现load-time-weaver有两种方式,这两种方式,核心差别在于修改class的时机不同。
第一种,定制classloader,在把字节码交给JVM去defineClass之前,去织入切面逻辑
第二种,利用Java 官方提供的instrumentation机制,注册一个类转换器到 JVM。JVM在加载class的时候,就会传入class的原始字节码数组,回调我们的类转换器,我们类转换器中可以修改原始字节码,并将修改后的字节码数组返回回去,JVM就会用我们修改后的字节码去defineClass了
在直接开始前,声明本篇文章,是基于下面这篇文章中的代码demo,我自己稍做了修改,并附上源码(原文是贴了代码,但是没有直接提供代码地址,不贴心啊)。
初探 Java agent
要达成的目标目标就是给下面的测试类,加上一点点切面功能。
package org.xunche.app; public class HelloXunChe { public static void main(String[] args) throws InterruptedException { HelloXunChe helloXunChe = new HelloXunChe(); helloXunChe.sayHi(); } public void sayHi() throws InterruptedException { System.out.println("hi, xunche"); sleep(); } public void sleep() throws InterruptedException { Thread.sleep((long) (Math.random() * 200)); } }我们希望,class在执行的时候,能够打印方法执行的耗时,也就是,最终的class,需要是下面这样的。
package org.xunche.app; import org.xunche.agent.TimeHolder; public class HelloXunChe { public HelloXunChe() { } public static void main(String[] args) throws InterruptedException { TimeHolder.start(args.getClass().getName() + "." + "main"); // 业务逻辑开始 HelloXunChe helloXunChe = new HelloXunChe(); helloXunChe.sayHi(); //业务逻辑结束 HelloXunChe helloXunChe = args.getClass().getName() + "." + "main"; System.out.println(helloXunChe + ": " + TimeHolder.cost(helloXunChe)); } public void sayHi() throws InterruptedException { TimeHolder.start(this.getClass().getName() + "." + "sayHi"); System.out.println("hi, xunche"); // 业务逻辑开始 this.sleep(); //业务逻辑结束 String var1 = this.getClass().getName() + "." + "sayHi"; System.out.println(var1 + ": " + TimeHolder.cost(var1)); } public void sleep() throws InterruptedException { TimeHolder.start(this.getClass().getName() + "." + "sleep"); // 业务逻辑开始 Thread.sleep((long)(Math.random() * 200.0D)); //业务逻辑结束 String var1 = this.getClass().getName() + "." + "sleep"; System.out.println(var1 + ": " + TimeHolder.cost(var1)); } }