曹工说Spring Boot源码(14)-- AspectJ的Load-Time-Weaving的两种实现方式细细讲解,以及怎么和Spring Instrumentation集成 (2)

其实,目前来说,很多容器就是采用这样的方式,我这里简单梳理了一下:

容器 支持设置ClassFileTransformer的classloader LTW实现方式
weblogic   weblogic.utils.classloaders.GenericClassLoader   自定义classloader  
glassfish   org.glassfish.api.deployment.InstrumentableClassLoader   自定义classloader  
tomcat   org.apache.tomcat.InstrumentableClassLoader   自定义classloader  
jboss   ?source_dir=jboss-modules/src/main/java/org/jboss/modules/ModuleClassLoader.java 直接获取了容器使用的classloader,该classloader内含有transformer字段,可以调用该字段的addTransformer方法来添加切面逻辑。具体可参考:org.springframework.instrument.classloading.jboss.JBossModulesAdapter   自定义classloader  
wehsphere   com.ibm.ws.classloader.CompoundClassLoader   自定义classloader  
jar包方式启动的独立应用(比如说pring )   无支持的classloader,默认使用的sun.misc.Launcher.AppClassLoader是不支持设置ClassFileTransformer的   java instrumentation方式(即javaagent)  

以上有一点要注意,第六种方式,即jar包独立应用(非tomcat容器那种),其使用的classloader,不支持设置ClassFileTransformer,所以其实现LTW是采用了其他方式的,上面也说了,是java instrumentation方式。

jboss自定义classloader实现ltw

jboss实现ltw的逻辑,是放在org.springframework.instrument.classloading.jboss.JBossLoadTimeWeaver。

这里面的逻辑简单来说,就是:

获取当前线程使用的classloader,通过网上资料,猜测是使用了org.jboss.modules.ModuleClassLoader

获取classloader中的transformer field

调用transformer field的addTransformer方法,该方法接收一个ClassFileTransformer类型的参数

这里的第一步使用的classloader,估计是正确的,我在网上也找到了该类的代码:

?source_dir=jboss-modules/src/main/java/org/jboss/modules/ModuleClassLoader.java

package org.jboss.modules; public class ModuleClassLoader extends ConcurrentClassLoader { static { try { ClassLoader.registerAsParallelCapable(); } catch (Throwable ignored) { } } static final ResourceLoaderSpec[] NO_RESOURCE_LOADERS = new ResourceLoaderSpec[0]; private final Module module; // 这里就是我说的那个transformer 字段 private final ClassFileTransformer transformer; ... }

因为不了解jboss,这个classloader,和我前面说的逻辑有一点点出入,有可能实际使用的classloader,是本classloader的一个子类,不过不影响分析。

我们看看本classloader怎么loadClass的(完整代码参考以上链接):

private Class<?> defineClass(final String name, final ClassSpec classSpec, final ResourceLoader resourceLoader) { final ModuleLogger log = Module.log; final Module module = this.module; log.trace("Attempting to define class %s in %s", name, module); ... final Class<?> newClass; try { byte[] bytes = classSpec.getBytes(); try { if (transformer != null) { // 看这里啊,如果transformer不为空,就使用transformer对原有的class进行转换 bytes = transformer.transform(this, name.replace('.', 'http://www.likecs.com/'), null, null, bytes); } //使用转换后得到的bytes,去define一个新的class:newClass newClass = doDefineOrLoadClass(name, bytes, 0, bytes.length, classSpec.getCodeSource()); module.getModuleLoader().addClassLoadTime(Metrics.getCurrentCPUTime() - start); log.classDefined(name, module); } } return newClass; }

所以,从这里,大家可以看到,自定义classloader,实现ltw的思路,就在于将原始的class的字节数组拿到后,对其进行transform后,即可获取到增强或修改后的字节码,然后拿这个字节码丢给jvm去加载class。

接下来,我们再看看tomcat的例子。

tomcat自定义classloader实现ltw

我们可以简单看下spring的org.springframework.instrument.classloading.tomcat.TomcatLoadTimeWeaver#TomcatLoadTimeWeaver(java.lang.ClassLoader),里面的逻辑就是:在tomcat容器环境下,怎么实现ltw的。

里面大概有以下步骤:

利用当前线程的classloader,判断是否为org.apache.tomcat.InstrumentableClassLoader

如果是,则反射获取该classloader的addTransformer方法并保存起来,该方法接收一个ClassFileTransformer对象;

后续spring启动过程中,就会调用第二步获取到的addTransformer来设置ClassFileTransformer

我本地有tomcat的源码,org.apache.tomcat.InstrumentableClassLoader 实际为一个接口:

package org.apache.tomcat; import java.lang.instrument.ClassFileTransformer; /** * Specifies a class loader capable of being decorated with * {@link ClassFileTransformer}s. These transformers can instrument * (or weave) the byte code of classes loaded through this class loader * to alter their behavior. Currently only * {@link org.apache.catalina.loader.WebappClassLoaderBase} implements this * interface. This allows web application frameworks or JPA providers * bundled with a web application to instrument web application classes * as necessary. * * @since 8.0, 7.0.64 */ public interface InstrumentableClassLoader { /** * Adds the specified class file transformer to this class loader. The * transformer will then be able to instrument the bytecode of any * classes loaded by this class loader after the invocation of this * method. * * @param transformer The transformer to add to the class loader * @throws IllegalArgumentException if the {@literal transformer} is null. */ void addTransformer(ClassFileTransformer transformer); /** * Removes the specified class file transformer from this class loader. * It will no longer be able to instrument the byte code of any classes * loaded by the class loader after the invocation of this method. * However, any classes already instrumented by this transformer before * this method call will remain in their instrumented state. * * @param transformer The transformer to remove */ void removeTransformer(ClassFileTransformer transformer); ... }

大家也看到了,这个接口,主要的方法就是添加或者删除一个ClassFileTransformer对象。我们可以仔细看看这个类的javadoc:

Specifies a class loader capable of being decorated with

{@link ClassFileTransformer}s. These transformers can instrument

(or weave) the byte code of classes loaded through this class loader

to alter their behavior. Currently only

{@link org.apache.catalina.loader.WebappClassLoaderBase} implements this

interface. This allows web application frameworks or JPA providers

bundled with a web application to instrument web application classes

as necessary.

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzjzzy.html