曹工说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),基本内容是讲清楚了(附源码)
工程代码地址 思维导图地址
工程结构图:
ltw实现方式之定制classloader(适用容器环境)本篇已经是spring源码第14篇,前一篇讲了怎么使用aspectJ的LTW(load-time-weaver),也理解了它的原理,主要是基于java提供的intrumentation机制来实现。
这里强烈建议看下前一篇,对我们下面的理解有相当大的帮助。
我这里简单重复一次,LTW是有多种实现方式的,它的意思是加载class时,进行切面织入。大家知道,我们加载class,主要是通过java.lang.ClassLoader#loadClass(java.lang.String, boolean),这个方法在执行过程中,会先交给父类classloader去加载,如果不行的话,再丢给本classloader的findClass方法来加载。
java.lang.ClassLoader#loadClass(java.lang.String, boolean) protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { // 委托父类classloader c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime() // 父类classloader搞不定,自己来处理 c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } }其中,findClass呢,是个空逻辑,主要供子类覆盖。我们看看典型的java.net.URLClassLoader#findClass是怎么覆盖该方法的,这个classloader主要是根据我们指定的url,去该url处获取字节流,加载class:
protected Class<?> findClass(final String name) throws ClassNotFoundException { return AccessController.doPrivileged( new PrivilegedExceptionAction<Class>() { public Class run() throws ClassNotFoundException { String path = name.replace('.', 'http://www.likecs.com/').concat(".class"); // 这里,获取url对应的Resource Resource res = ucp.getResource(path, false); if (res != null) { try { // 内部会调用JVM方法,define Class return defineClass(name, res); } catch (IOException e) { throw new ClassNotFoundException(name, e); } } else { throw new ClassNotFoundException(name); } } }, acc); } }其中我们关注defineClass:
private Class defineClass(String name, Resource res) throws IOException { URL url = res.getCodeSourceURL(); ... // 获取url对应的资源的字节数组 byte[] b = res.getBytes(); // must read certificates AFTER reading bytes. CodeSigner[] signers = res.getCodeSigners(); CodeSource cs = new CodeSource(url, signers); sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0); // 下面这个方法,最终就会调用一个JVM本地方法,交给虚拟机来加载class return defineClass(name, b, 0, b.length, cs); }其中defineClass最终会调用如下方法:
private native Class defineClass1(String name, byte[] b, int off, int len, ProtectionDomain pd, String source);所以,大家能看到的是,loadClass其实有两个步骤:
获取class对应的字节数组
调用native方法,让JVM根据步骤1获取到的字节数组,来define一个Class。
所以,LTW的其中一种做法(前一篇文章里提到了),就是使用自定义的classloader,在第一步完成后,第二步开始前,插入一个步骤:织入切面。