在初学Java的时候,我们都知道.java文件转换为.class文件的过程叫做编译。
编译的过程主要存在三步
我们可以通过一些命令(javap)去查看.class文件的字节码
公众号其他相关文章:
加载是虚拟机将.class文件加载到内存中供程序使用
注: 通常我们说的类加载是是类的生命周期中加载、连接、初始化三个阶段。但是在某一段程序中,我们引用第三方的类的不一定会触发类初始化(主动引用和被动引用大家可自行了解)。如下代码:
class A{
public final static int x = 10;
static {
System.out.println("1000");
}
}
我们在其他类中调用A,并不会初始化A
class B{
public static void main(String[] args) {
System.out.println(A.x);
}
}
执行结果就是A中静态代码块没有执行,只是输出10,没有输出1000。
Java中类的加载都是在运行期执行,这种方式会带来一定的性能开销,但是也增加了应用程序的灵活性。 类的加载自然离不开类加载器,Java中类加载器分为启动类加载器,扩展类加载器,系统类加载器,自定义类加载器。其中启动类加载器为顶级加载器,自定义类加载器最低。
在说明自定义类加载器之前我们先去简单的看一下ClassLoader这个类的loadClass方法,双亲委派模式核心也在这里,里面有一个递归loadClass方法(个人是这样理解的)
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//同步
synchronized (getClassLoadingLock(name)) {
//findLoadedClass是检测class文件是否被加载,如果加载就调用本地方法findLoadedClass0返回class
Class<?> c = findLoadedClass(name);
//没有被加载
if (c == null) {
//类似System.currentTimeMillis()
long t0 = System.nanoTime();
try {
//如果父加载器不为空,那么调用父加载器的loadClass方法加载类,如果父加载器为空,那么调用虚拟机的加载器来加载类
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
//如果以上两个步骤都没有成功的加载到类,那么调用自己的findClass(name)方法来加载类。
if (c == null) {
long t1 = System.nanoTime();
//这里是我们实现自定义类加载的关键
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
我们发现findClass是一段如下代码,意思很明显,如果没有重写就抛出ClassNotFoundException。在注释上也描述到 该方法应由类加载器实现重写。所以实现自定义类加载器离不开findClass方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
1.实现自定义类加载器后我们可以对于class文件进行加密
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//对于加密的class文件,我们先解密然后再交给加载器加载
byte[] data = this.decrypt(name);
return this.defineClass(name, data, 0, data.length);
}
2. 加载其他途径来源的Class文件,因为默认类加载器只能加载固定路径下的class,如果有特定路径下的class,需要自定义
首先我们来写两个简单的类,供我们测试
public class TestDyna {
public void test() {
System.out.println("TestDyna");
new Test2().sss();
}
}
public class Test2 {
static {
System.out.println("Test2调用TestDyna开始");
new TestDyna().test();
System.out.println("Test2调用TestDyna结束");
}
public void sss() {
System.out.println("Test2");
}
}
如果我们不使用自定义类加载器,调用TestDyna的test方法,输出结果是什么呢?(类的类构造器中会收集所有的static块和字段并执行,static块只执行一次,由JVM保证其只执行一次)
TestDyna
Test2调用TestDyna开始
TestDyna
Test2
Test2调用TestDyna结束
Test2
下面我们来实现自定义的类加载器来执行TestDyna的test方法。
首先我们自定义类继承ClassLoader,构造器中super(null)是为了将ClassLoader中parent设置为null。这样才会执行自定义加载器,否则仍然会执行虚拟机提供的类加载器
class MyClassLoader extends ClassLoader {
public MyClassLoader() {
super(null);
}
}
重写findClass方法
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("执行自定义的类");
//获取文件
File file = getClassFile(name);
try {
//这里是将class文件转为字节
byte[] bytes = getClassBytes(file);
//把字节码转化为Class
Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
return c;
} catch (Exception e) {
e.printStackTrace();
}
//执行失败,则又父类findClass执行--也就是抛出ClassNotFoundException
return super.findClass(name);
}
getClassFile/getClassBytes方法
private File getClassFile(String name) {
//前面是固定的文件路径,由于name为xx.xx.xx,我们要转换为xx/xx/xx.class
File file = new File("/Users/chenlong/Documents/weaver/demo/ecology/classbean/" +
name.replace(".", "/") + ".class");
return file;
}
private byte[] getClassBytes(File file) throws Exception {
// 这里要读入.class的字节,因此要使用字节流
FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel wbc = Channels.newChannel(baos);
ByteBuffer by = ByteBuffer.allocate(1024);
while (true) {
int i = fc.read(by);
if (i == 0 || i == -1)
break;
by.flip();
wbc.write(by);
by.clear();
}
fis.close();
return baos.toByteArray();
}
到这里我们就实现了一个类加载器,我们来看一下执行,利用了反射的API
public static void main(String[] args) throws Exception {
MyClassLoader mcl = new MyClassLoader();
Class<?> c1 = Class.forName("cl.test.TestDyna", true, mcl);
Object obj = c1.newInstance();
Method m = c1.getMethod("test");
m.invoke(obj);
}
结果:是跟我们上面普通的调用方式没什么区别。
执行自定义的类
TestDyna
执行自定义的类
Test2调用TestDyna开始
TestDyna
Test2
Test2调用TestDyna结束
Test2