字节码技术及动态代理

简述class文件加载过程

       Java编译器编译Java文件,生成class文件,JVM加载class文件,解析文件信息,生成实例对象。在运行期的代码中生成二进制字节码由JVM通过字节码的二进制信息加载类的,那么,如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力了。

Java字节码生成框架

  • ASM
    ASM 是一个够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
    eg:
public class Test {
public void testAsm() {
System.out.println("testAsm");
}
}
public class TestAsmMain {
public static void main(String[] args) throws IOException {
System.out.println();
ClassWriter classWriter = new ClassWriter(0);
// 通过visit方法确定类的头部信息
classWriter.visit(Opcodes.V1_7,// java版本
Opcodes.ACC_PUBLIC,// 类修饰符
"com.liud.classtec.Test", // 类的全限定名
null, "java/lang/Object", null);
//创建构造函数
MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>","()V");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
// 定义testAsm方法
MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "testAsm", "()V",
null, null);
methodVisitor.visitCode();
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
methodVisitor.visitLdcInsn("testAsm");
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",
"(Ljava/lang/String;)V");
methodVisitor.visitInsn(Opcodes.RETURN);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
classWriter.visitEnd();
// 使classWriter类已经完成
// 将classWriter转换成字节数组写到文件里面去
byte[] data = classWriter.toByteArray();
File file = new File("C:\test\Test.class");
FileOutputStream fout = new FileOutputStream(file);
fout.write(data);
fout.close();
}
}
  • Javassist
    Javassist是一个开源的分析、编辑和创建Java字节码的类库。
    eg:
 class Test {
public void testAsm() {
System.out.println("testAsm");
}
}
public class TestJavassist {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
//创建类
CtClass cc= pool.makeClass("com.liud.classtec.Test");
//定义方法
CtMethod method = CtNewMethod.make("public void testAsm(){}", cc);
//插入方法代码
method.insertBefore("System.out.println("test");");
cc.addMethod(method);
//保存生成的字节码
cc.writeFile("C:\test\javassit");
}
}

字节码技术与动态代理

  先上个旧图,之前写代理模式时候的UML类图。

       所谓动态代理是区别于静态代理的,除去了静态代理的类,在运行中动态创建代理类。运行中动态创建用到的就是字节码技术,现在实现的方式主流是两种 JDK原生的模式和CGLIB的模式。

  • JDK原生的模式
    步骤如下:
  1. 获取 RealSubject上的所有接口列表
  2. 确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX
  3. 根据需要实现的接口信息,在代码中动态创建 该Proxy类的字节码
    将对应的字节码转换为对应的class 对象
  4. 创建InvocationHandler 实例handler,用来处理Proxy所有方法调用
  5. Proxy 的class对象 以创建的handler对象为参数,实例化一个proxy对象
    eg:
public class DynamicProxy {
public static void main(String args[]) {
RealSubject real = new RealSubject();
Subject proxySubject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),
new Class[]{Subject.class},
new ProxyHandler(real));
proxySubject.doSomething();
createProxyClassFile();
}
public static void createProxyClassFile() {
String name = "ProxySubject";
byte[] data = ProxyGenerator.generateProxyClass( name, new Class[] { Subject.class } );
try
{
FileOutputStream out = new FileOutputStream( name + ".class" );
out.write(data);
out.close();
} catch(Exception e) {
e.printStackTrace();
}
}
}
interface Subject
{
public void doSomething();
}
class RealSubject implements Subject
{
public void doSomething()
{
System.out.println( "call doSomething()" );
}
}
public class ProxyHandler implements InvocationHandler
{
private Object proxied;
public ProxyHandler( Object proxied )
{
this.proxied = proxied;
}
public Object invoke(Object proxy, Method method, Object[] args ) throws Throwable
{
//在转调具体目标对象之前,可以执行一些功能处理
//转调具体目标对象的方法
return method.invoke(proxied, args);
//在转调具体目标对象之后,可以执行一些功能处理
}
}

不足:
Proxy已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏。

  • CGLIB的模式
    cglib其被广泛应用于AOP框架(Spring等)中,用以提供方法拦截操作。
    cglib代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。
    这里上一张cglib的图从这里可以看出cglib底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。

eg:当时再写注解的时候就拿cglib自定义过注解,这里在另外举一个例子

public class CglibProxyClass {
public void test(){
System.out.println("run method");
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(CglibProxyClass.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable
{
System.out.println("before method run");
Object result = proxy.invokeSuper(obj, args);
System.out.println("after method run");
return result;
}
});
CglibProxyClass proxyClass = (CglibProxyClass) enhancer.create();
proxyClass.test();
}
}

总结

这篇主要还是说道我们日常常接触并应用的动态代理技术,及其背后的字节码技术,在此做下备忘,网路上还有一个旧文,《动态代理的前世今生》,
也是很经典的东西啦。

参考博文:

http://blog.csdn.net/luanlouis/article/details/24589193
http://blog.csdn.net/danchu/article/details/70238002
https://www.cnblogs.com/flyoung2008/archive/2013/08/11/3251148.html

原文地址:https://www.cnblogs.com/daily-note/p/8258623.html