class文件简介及加载
Java编译器编译好Java文件之后,产生.class 文件在磁盘中。这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码。JVM虚拟机读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息,生成对应的 Class对象:
class字节码文件是根据JVM虚拟机规范中规定的字节码组织规则生成的、具体class文件是怎样组织类信息的,可以参考 此博文:深入理解Java Class文件格式系列。或者是Java虚拟机规范。
下面通过一段代码演示手动加载 class文件字节码到系统内,转换成class对象,然后再实例化的过程:
a. 定义一个 Programmer类:
- package samples;
- /**
- * 程序猿类
- * @author louluan
- */
- public class Programmer {
- public void code()
- {
- System.out.println("I'm a Programmer,Just Coding.....");
- }
- }
- package samples;
- /**
- * 程序猿类
- * @author louluan
- */
- public class Programmer {
- public void code()
- {
- System.out.println("I'm a Programmer,Just Coding.....");
- }
- }
b. 自定义一个类加载器:
- package samples;
- /**
- * 自定义一个类加载器,用于将字节码转换为class对象
- * @author louluan
- */
- public class MyClassLoader extends ClassLoader {
- public Class<?> defineMyClass( byte[] b, int off, int len)
- {
- return super.defineClass(b, off, len);
- }
- }
- package samples;
- /**
- * 自定义一个类加载器,用于将字节码转换为class对象
- * @author louluan
- */
- public class MyClassLoader extends ClassLoader {
- public Class<?> defineMyClass( byte[] b, int off, int len)
- {
- return super.defineClass(b, off, len);
- }
- }
c. 然后编译成Programmer.class文件,在程序中读取字节码,然后转换成相应的class对象,再实例化:
- package samples;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import java.io.InputStream;
- import java.net.URL;
- public class MyTest {
- public static void main(String[] args) throws IOException {
- //读取本地的class文件内的字节码,转换成字节码数组
- File file = new File(".");
- InputStream input = new FileInputStream(file.getCanonicalPath()+"\\bin\\samples\\Programmer.class");
- byte[] result = new byte[1024];
- int count = input.read(result);
- // 使用自定义的类加载器将 byte字节码数组转换为对应的class对象
- MyClassLoader loader = new MyClassLoader();
- Class clazz = loader.defineMyClass( result, 0, count);
- //测试加载是否成功,打印class 对象的名称
- System.out.println(clazz.getCanonicalName());
- //实例化一个Programmer对象
- Object o= clazz.newInstance();
- try {
- //调用Programmer的code方法
- clazz.getMethod("code", ).invoke(o, );
- } catch (IllegalArgumentException | InvocationTargetException
- | NoSuchMethodException | SecurityException e) {
- e.printStackTrace();
- }
- }
- }
- package samples;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import java.io.InputStream;
- import java.net.URL;
- public class MyTest {
- public static void main(String[] args) throws IOException {
- //读取本地的class文件内的字节码,转换成字节码数组
- File file = new File(".");
- InputStream input = new FileInputStream(file.getCanonicalPath()+"\\bin\\samples\\Programmer.class");
- byte[] result = new byte[1024];
- int count = input.read(result);
- // 使用自定义的类加载器将 byte字节码数组转换为对应的class对象
- MyClassLoader loader = new MyClassLoader();
- Class clazz = loader.defineMyClass( result, 0, count);
- //测试加载是否成功,打印class 对象的名称
- System.out.println(clazz.getCanonicalName());
- //实例化一个Programmer对象
- Object o= clazz.newInstance();
- try {
- //调用Programmer的code方法
- clazz.getMethod("code", ).invoke(o, );
- } catch (IllegalArgumentException | InvocationTargetException
- | NoSuchMethodException | SecurityException e) {
- e.printStackTrace();
- }
- }
- }
以上代码演示了,通过字节码加载成class 对象的能力,下面看一下在代码中如何生成class文件的字节码。
在运行期的代码中生成二进制字节码
由于JVM通过字节码的二进制信息加载类的,那么,如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力了。
在运行时期可以按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码。当前有很多开源框架可以完成这些功能,如ASM,Javassist。
Java字节码生成开源框架介绍--ASM:
ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
不过ASM在创建class字节码的过程中,操纵的级别是底层JVM的汇编指令级别,这要求ASM使用者要对class组织结构和JVM汇编指令有一定的了解。
下面通过ASM 生成下面类Programmer的class字节码:
- package com.samples;
- import java.io.PrintStream;
- public class Programmer {
- public void code()
- {
- System.out.println("I'm a Programmer,Just Coding.....");
- }
- }
- package com.samples;
- import java.io.PrintStream;
- public class Programmer {
- public void code()
- {
- System.out.println("I'm a Programmer,Just Coding.....");
- }
- }
使用ASM框架提供了ClassWriter 接口,通过访问者模式进行动态创建class字节码,看下面的例子:
- package samples;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import org.objectweb.asm.ClassWriter;
- import org.objectweb.asm.MethodVisitor;
- import org.objectweb.asm.Opcodes;
- public class MyGenerator {
- 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,// 类修饰符
- "Programmer", // 类的全限定名
- , "java/lang/Object", );
- //创建构造函数
- MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", , );
- 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();
- // 定义code方法
- MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "code", "()V",
- , );
- methodVisitor.visitCode();
- methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
- "Ljava/io/PrintStream;");
- methodVisitor.visitLdcInsn("I'm a Programmer,Just Coding.....");
- 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("D://Programmer.class");
- FileOutputStream fout = new FileOutputStream(file);
- fout.write(data);
- fout.close();
- }
- }
- package samples;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import org.objectweb.asm.ClassWriter;
- import org.objectweb.asm.MethodVisitor;
- import org.objectweb.asm.Opcodes;
- public class MyGenerator {
- 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,// 类修饰符
- "Programmer", // 类的全限定名
- , "java/lang/Object", );
- //创建构造函数
- MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", , );
- 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();
- // 定义code方法
- MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "code", "()V",
- , );
- methodVisitor.visitCode();
- methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
- "Ljava/io/PrintStream;");
- methodVisitor.visitLdcInsn("I'm a Programmer,Just Coding.....");
- 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("D://Programmer.class");
- FileOutputStream fout = new FileOutputStream(file);
- fout.write(data);
- fout.close();
- }
- }
上述的代码执行过后,用Java反编译工具(如JD_GUI)打开D盘下生成的Programmer.class,可以看到以下信息:
再用上面我们定义的类加载器将这个class文件加载到内存中,然后 创建class对象,并且实例化一个对象,调用code方法,会看到下面的结果:
以上表明:在代码里生成字节码,并动态地加载成class对象、创建实例是完全可以实现的。
Java字节码生成开源框架介绍--Javassist:
Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
下面通过Javassist创建上述的Programmer类:
通过JD-gui反编译工具打开Programmer.class 可以看到以下代码:
- import javassist.ClassPool;
- import javassist.CtClass;
- import javassist.CtMethod;
- import javassist.CtNewMethod;
- public class MyGenerator {
- public static void main(String[] args) throws Exception {
- ClassPool pool = ClassPool.getDefault();
- //创建Programmer类
- CtClass cc= pool.makeClass("com.samples.Programmer");
- //定义code方法
- CtMethod method = CtNewMethod.make("public void code(){}", cc);
- //插入方法代码
- method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
- cc.addMethod(method);
- //保存生成的字节码
- cc.writeFile("d://temp");
- }
- }
- import javassist.ClassPool;
- import javassist.CtClass;
- import javassist.CtMethod;
- import javassist.CtNewMethod;
- public class MyGenerator {
- public static void main(String[] args) throws Exception {
- ClassPool pool = ClassPool.getDefault();
- //创建Programmer类
- CtClass cc= pool.makeClass("com.samples.Programmer");
- //定义code方法
- CtMethod method = CtNewMethod.make("public void code(){}", cc);
- //插入方法代码
- method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
- cc.addMethod(method);
- //保存生成的字节码
- cc.writeFile("d://temp");
- }
- }
代理的基本构成:
代理模式上,基本上有Subject角色,RealSubject角色,Proxy角色。其中:Subject角色负责定义RealSubject和Proxy角色应该实现的接口;RealSubject角色用来真正完成业务服务功能;Proxy角色负责将自身的Request请求,调用realsubject 对应的request功能来实现业务功能,自己不真正做业务。
上面的这幅代理结构图是典型的静态的代理模式:
当在代码阶段规定这种代理关系,Proxy类通过编译器编译成class文件,当系统运行时,此class已经存在了。这种静态的代理模式固然在访问无法访问的资源,增强现有的接口业务功能方面有很大的优点,但是大量使用这种静态代理,会使我们系统内的类的规模增大,并且不易维护;并且由于Proxy和RealSubject的功能 本质上是相同的,Proxy只是起到了中介的作用,这种代理在系统中的存在,导致系统结构比较臃肿和松散。
为了解决这个问题,就有了动态地创建Proxy的想法:在运行状态中,需要代理的地方,根据Subject 和RealSubject,动态地创建一个Proxy,用完之后,就会销毁,这样就可以避免了Proxy 角色的class在系统中冗杂的问题了。
下面以一个代理模式实例阐述这一问题:
将车站的售票服务抽象出一个接口TicketService,包含问询,卖票,退票功能,车站类Station实现了TicketService接口,车票代售点StationProxy则实现了代理角色的功能,类图如下所示。
对应的静态的代理模式代码如下所示:
- package com.foo.proxy;
- /**
- * 售票服务接口实现类,车站
- * @author louluan
- */
- public class Station implements TicketService {
- @Override
- public void sellTicket() {
- System.out.println("\n\t售票.....\n");
- }
- @Override
- public void inquire() {
- System.out.println("\n\t问询。。。。\n");
- }
- @Override
- public void withdraw() {
- System.out.println("\n\t退票......\n");
- }
- }
- package com.foo.proxy;
- /**
- * 售票服务接口实现类,车站
- * @author louluan
- */
- public class Station implements TicketService {
- @Override
- public void sellTicket() {
- System.out.println("\n\t售票.....\n");
- }
- @Override
- public void inquire() {
- System.out.println("\n\t问询。。。。\n");
- }
- @Override
- public void withdraw() {
- System.out.println("\n\t退票......\n");
- }
- }
- package com.foo.proxy;
- /**