2019-04-21

平台无关性

1555814006393.png

Java源码首先被编译成字节码,再由不同平台的JVM进行解析,Java语言在不同平台上运行不需要重新编译,Java虚拟机在执行字节码的时候,把字节码转换为具体平台上的机器码。

反射

示例代码如下:

首先编写一个类:

public class ReflectTest {

    private String name;

    public String getName(){
        return name;
    }

    public void publicMethod(String val){
        System.out.println("enter publicMethod:"+val);
    }

    private void privateMethod(String val){
        System.out.println("enter privateMethod:"+val);
    }
}

里面定义了私有属性,私有方法和公有方法。下面通过反射机制来访问这些属性和方法。

// 1. 获取class对象
Class rc = Class.forName("com.example.demo.ReflectTest");

// 2. 创建反射类实例
ReflectTest reflectTest = (ReflectTest) rc.newInstance();

// 3. 获取类名
System.out.println("Class Name is :"+rc.getName());

// 4. getDeclaredMethod()可以获取除了父类和接口外的所有方法(只要是它自己定义的方法)
Method privateMethod = rc.getDeclaredMethod("privateMethod",String.class);
privateMethod.setAccessible(true);
// 5. 执行私有方法
privateMethod.invoke(reflectTest,"hello");

// 6.getMethod()只能获取有访问权限的方法,私有方法不行
Method publicMethod = rc.getMethod("publicMethod", String.class);
publicMethod.invoke(reflectTest,"world");

//7. 获取和修改私有属性
Field name = rc.getDeclaredField("name");
name.setAccessible(true);
name.set(reflectTest,"yun");

// 7.1 打印设置的属性
Method getNameMethod = rc.getMethod("getName");
System.out.println(getNameMethod.invoke(reflectTest));

getDeclaredMethod()方法和getMethod()方法的区别在于:

  • getDeclaredMethod()可以获取除了父类和接口定义的方法以外的所有方法
  • getMethod()只可以获取有访问权限的方法,如private方法不能获取

类的加载过程

1555822218454.png

ClassLoader的种类

  • BootStrapClassLoader::C++编写,加载核心库java.*

  • ExtClassLoader:Java编写,加载扩展库javax.*

  • AppClassLoader:Java编写,加载程序所在目录

  • 自定义ClassLoader:Java编写,定制化加载

BootStrapClassLoader

BootStrapClassLoader作为核心的类加载器,加载出核心库给应用使用,开发者一般调用不到这个类。

ExtClassLoader

Java编写,加载扩展库javax.*。从其源码可以看出,它只获取java.ext.dirs目录下的class文件:

private static File[] getExtDirs() {
    String var0 = System.getProperty("java.ext.dirs");
    File[] var1;
    ......

AppClassLoader

Java编写,加载程序所在目录,也就是我们项目中自己编写生成的class文件。从源码中可以看出,它会去加载
java.class.path目录下的class文件:

public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
    final String var1 = System.getProperty("java.class.path");
    ......

自定义ClassLoader

在某些特殊情况下,可能需要自定义类加载器,下面是一个示例:

首先创建一个类加载器:

public class MyClassLoader extends ClassLoader {
    private String path;
    private String classLoaderName;

    public MyClassLoader(String path,String classLoaderName){
        this.path = path;
        this.classLoaderName = classLoaderName;
    }

    @Override
    public Class findClass(String name){
        byte[] b = loadClassData(name);
        return defineClass(name,b,0,b.length);
    }

    private byte[] loadClassData(String name){
        name = path+name+".class";
        InputStream in = null;
        ByteArrayOutputStream out = null;
        try{
            in = new FileInputStream(new File(name));
            out = new ByteArrayOutputStream();

            int i=0;
            while((i=in.read()) != -1){
                out.write(i);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try{
                out.close();
                in.close();
            } catch (Exception e){
                e.printStackTrace();
            }
        }
        return out.toByteArray();
    }
}

自定义类加载器最主要的就是实现findClass()这个方法,里面的逻辑就是获取class文件,将其以字节数组的形式传给defineClass()方法去执行(这个方法父类已经实现,不需要我们再去实现)。

接下来我们在桌面上创建一个类:

public class Test {
    static{
        System.out.println("hello world");
    }
}

并将其编译出Test.class文件,准备加载。

最后编写测试类测试:

MyClassLoader myClassLoader = new MyClassLoader("C:\\Users\\Administrator\\Desktop\\","myClassLoader");
Class c = myClassLoader.loadClass("Test");
c.newInstance();

经过测试,我们自定义的类加载器已经可以正常加载class文件,并创建类的对象了,静态代码块的打印也正常显示了。

loadClassForName的区别

前面我们知道在反射的时候使用loadClass来加载类:

// 1. 获取class对象
Class rc = Class.forName("com.example.demo.ReflectTest");
// 2. 创建反射类实例
ReflectTest reflectTest = (ReflectTest) rc.newInstance();

而我们在使用自定义类加载器的时候使用ForName来加载:

MyClassLoader myClassLoader = new MyClassLoader("C:\\Users\\Administrator\\Desktop\\","myClassLoader");
Class c = myClassLoader.loadClass("Test");
c.newInstance();

那使用Class.forName()myClassLoader.loadClass()这两种方式加载的区别是什么呢?

  • 使用ClassLoader.loadClass()加载时,只对类进行加载操作,即类的加载第一阶段:将class文件加载到内存中,并未对类进行初始化等其他操作,所以类的static静态代码块中代码并未得到执行
  • 而使用Class.forName()加载时,对类的加载三个节点都会执行,即加载,链接,初始化。所以类定义的静态代码块也会执行。

类加载器的双亲委派机制

四种类加载器相互协作。判断一个Class是否被加载过的逻辑是:首先判断自定义加载器是否已经加载过,如果没有再委派给它的父加载器去查看,一直往上进行判断。

1555820192013.png

为什么使用双亲委派机制去加载类:

  • 避免多个类加载器重复加载,之前某个加载器加载过就不需要再重复加载了。

双亲委派机制体现在ClassLoader类的源码中:

    protected Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                }

                if (c == null) {
                    long t1 = System.nanoTime();
                    c = findClass(name);
                    ......
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

你可能感兴趣的:(2019-04-21)