spring源码学习笔记,要点归纳和代码理解
前言
上一节学习了Spring动态代理的两种方式,jdk动态代理和Cglib动态代理,对他们的原理和Spring的实现做了了解。Spring还提供了一种基于静态代理的代理方式,其底层实现基于instrument。
Java在1.5引入java.lang.instrument,你可以有次实现一个代理,通过此代理来修改类的字节码改变一个类。它类似一种更低级、更松耦合的AOP,可以从底层来改变一个类的行为。你由此可以产生无限的遐想。[1]
instrument的使用
instrument的使用大致分为以下几个步骤:
- 实现
ClassFileTransformer
接口,重写它的public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException
方法。
首先来分析一下这个方法的方法签名:
- 返回值:byte[] 代理后的类字节码
- 参数列表:
* @param loader the defining loader of the class to be transformed,
* may be <code>null</code> if the bootstrap loader 要修改的类的类加载器,如果是根类加载器则为空
* @param className the name of the class in the internal form of fully
* qualified class and interface names as defined in
* <i>The Java Virtual Machine Specification</i>. JVM中的全类名
* For example, <code>"java/util/List"</code>.
* @param classBeingRedefined if this is triggered by a redefine or retransform,
* the class being redefined or retransformed;
* if this is a class load, <code>null</code> 如果方法被 redefine或retransform触发,则这个参数是触发的类,否则为空
* @param protectionDomain the protection domain of the class being defined or redefined 正在定义或重新定义类的保护域
* @param classfileBuffer the input byte buffer in class file format - must not be modified 原类的字节码-严禁修改
下面是我写的demo的实现,我这里用了第三方的javassist用来操作字节码.
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtBehavior;
import javassist.CtClass;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class PerfMonXformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] transformed = null;
ClassPool pool = ClassPool.getDefault();
CtClass cl = null;
try {
cl = pool.makeClass(new java.io.ByteArrayInputStream(classfileBuffer));
if (!cl.isInterface()) {
CtBehavior[] methods = cl.getDeclaredBehaviors();
for (int i = 0; i < methods.length; i++) {
if (!methods[i].isEmpty()) {
doMethod(methods[i]);
}
}
transformed = cl.toBytecode();
}
} catch (Exception e) {
System.err.println("could not instrument " + className + ", exception : " + e.getMessage());
} finally {
if (cl != null) {
cl.detach();
}
}
return transformed;
}
private void doMethod(CtBehavior method) throws CannotCompileException {
method.insertBefore("long stime = System.nanoTime();");
String s = "System.out.println(\"leave" + method.getName() + " and time : 1111 \" );";// + (System.nanoTime()-stime));";
method.insertAfter(s);
}
}
- 创建一个代理类,将(1)实现的类文件转换器注册到代理类的静态Instrumentation 对象中.
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
public class PerfMonAgent {
private static Instrumentation inst = null;
public static void premain(String agentArgs, Instrumentation instrumentation) {
inst = instrumentation;
ClassFileTransformer transformer = new PerfMonXformer();
System.out.println("adding a PerfMonXformer instance to the JVM");
inst.addTransformer(transformer);
}
}
- 将代理类注册到classpath下的
./META-INF/MANIFEST.MF
文件中
没有这个文件需要自己新建,如果你在transformer类中使用了第三方组件,需要在这里加上你的第三方组件的jar包
Premain-Class: PerfMonAgent
Boot-Class-Path: /Users/lucifer/.m2/repository/javassist/javassist/3.8.0.GA/javassist-3.8.0.GA.jar
- 打包agent项目
我这里使用maven作为打包工具,完整的pom文件如下,需要特别注意的是你的代理注册类和引用的第三方jar包都要在pom中注册.见<manifestEntries>
标签.
之后在项目根目录中运行maven clean package
即可.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.pctf</groupId>
<artifactId>instrument-test</artifactId>
<version>1.0-SNAPSHOT</version>
<name>instrument-test</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.8.0.GA</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Premain-Class>PerfMonAgent</Premain-Class>
<Boot-Class-Path>/Users/lucifer/.m2/repository/javassist/javassist/3.8.0.GA/javassist-3.8.0.GA.jar</Boot-Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
整个代理项目的目录结构见截图:
- 在要代理的启动参数中加入
-javaagent:<jarpath>[=<options>]
编写一个简单的启动类:
public class AppTest {
public static void main(String[] args) {
new AppTest().test();
}
public void test() {
System.out.println("Hello, world");
}
}
在运行参数中加入:-javaagent:/Users/lucifer/Desktop/instrument-test-1.0-SNAPSHOT.jar
这里的jar包就是我们刚才打包生成的代理jar包,这里我直接在ide中添加了.
运行之后,结果如下,所有的方法都被代理了.
Spring中开启AspectJ
Spring中如果需要使用AspextJ的功能,首先要在配置文件中加入<context:load-time-weaver/>
,其原理也是一种自定义标签的解析。
标签的核心作用其实就是注册了一个杜宇AspectJ处理的类org.Springframework.context.weaving.AspcetJWeavingEnabler
public class AspectJWeavingEnabler
implements BeanFactoryPostProcessor, BeanClassLoaderAware, LoadTimeWeaverAware, Ordered {
/**
* The {@code aop.xml} resource location.
*/
public static final String ASPECTJ_AOP_XML_RESOURCE = "META-INF/aop.xml";
@Nullable
private ClassLoader beanClassLoader;
@Nullable
private LoadTimeWeaver loadTimeWeaver;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
@Override
public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) {
this.loadTimeWeaver = loadTimeWeaver;
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
enableAspectJWeaving(this.loadTimeWeaver, this.beanClassLoader);
}
/**
* Enable AspectJ weaving with the given {@link LoadTimeWeaver}.
* @param weaverToUse the LoadTimeWeaver to apply to (or {@code null} for a default weaver)
* @param beanClassLoader the class loader to create a default weaver for (if necessary)
*/
public static void enableAspectJWeaving(
@Nullable LoadTimeWeaver weaverToUse, @Nullable ClassLoader beanClassLoader) {
if (weaverToUse == null) {
if (InstrumentationLoadTimeWeaver.isInstrumentationAvailable()) {
weaverToUse = new InstrumentationLoadTimeWeaver(beanClassLoader);
}
else {
throw new IllegalStateException("No LoadTimeWeaver available");
}
}
weaverToUse.addTransformer(
new AspectJClassBypassingClassFileTransformer(new ClassPreProcessorAgentAdapter()));
}
/**
* ClassFileTransformer decorator that suppresses processing of AspectJ
* classes in order to avoid potential LinkageErrors.
* @see org.springframework.context.annotation.LoadTimeWeavingConfiguration
*/
private static class AspectJClassBypassingClassFileTransformer implements ClassFileTransformer {
private final ClassFileTransformer delegate;
public AspectJClassBypassingClassFileTransformer(ClassFileTransformer delegate) {
this.delegate = delegate;
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (className.startsWith("org.aspectj") || className.startsWith("org/aspectj")) {
return classfileBuffer;
}
return this.delegate.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
}
}
}
可以看到这个类实现了BeanFactoryPostProcessor
接口,并在内部实现了之前提到的ClassFileTransformer
.
在所有BeanDefinition解析完之后,会调用其postProcessBeanFactory方法,实现类的代理。
[1] 讲真,这本书虽然是中文作者,但有好多地方感觉是外文书籍直接翻译过来的。包括很多demo。
网友评论