其实,目前来说,很多容器就是采用这样的方式,我这里简单梳理了一下:
容器 支持设置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实现ltwjboss实现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.