类加载机制与反射

44 阅读8分钟

JVM

当调用java命令运行某个java程序时,该命令会启动一个java虚拟机进程,不管该java程序有多么复杂,该程序启动了多少个线程,他们都处于该java虚拟机进程里。同一个jvm的所有线程、所有变量都处于同一个进程里,它们都使用该jvm进程的内存区。

jvm进程被终止的4中情况:

1、程序运行到最后正常结束

2、程序运行到使用System.exit() 或 Runtime.getRuntime().exit() 代码处结束程序

3、程序执行过程中遇到未捕获的异常或错误而结束

4、程序所在平台强制结束了jvm进程

类的加载

当程序使用某个类时,如果该类还未被加载到内存中,则jvm会通过加载、连接、初始化三个步骤来对该类进行初始化。jvm将会连续完成这三个步骤,所以这三个步骤统称为类加载或类初始化。

**类加载:**指将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,jvm都会为之建立一个java.lang.Class对象。jvm中所有的类实际上都是java.lang.Class的实例。

**类加载器:**类的加载由类加载器完成,类加载器通常由jvm提供,jvm提供的类加载器通常被称为系统类加载器,除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。

类加载器的几种方式:

1、从本地文件系统加载class文件。

2、从jar包加载class文件,这种方式很常见,jvm可以从jar文件中直接加载该class文件。

3、通过网络家族class文件

4、把一个java源文件动态编译,并执行加载。

类的连接

当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接可以分为三个阶段

1、验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。

2、准备:类准备阶段则负责为类的类变量分配内存,并设置默认初始值。

3、解析:将类的二进制数据中的符号引用替换成直接引用。

类的初始化

在java类变量指定初始化有两种方式:

1、声明类变量时指定初始值

2、使用静态初始化块为类变量指定初始值

JVM初始化类包含几个步骤:

1、假如这个类还没有被加载和连接,则程序先加载并连接该类

2、假如该类的直接父类还没有被初始化,则先初始化其直接父类

3、假如类中有初始化语句,则系统依次执行这些初始化语句

类加载器

当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构:

**Bootstrap ClassLoader:**根类加载器,负责加载java的核心类

import sun.misc.Launcher;

import java.net.URL;

public class BootsTest {
    public static void main(String[] args) {
        //获取javase运行的核心类
        URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
        for (int i = 0; i < urLs.length; i++) {
            System.out.println(urLs[i].toExternalForm());
        }
    }
}

//file:/D:/dev/Java/jdk1.8.0_201/jre/lib/resources.jar
//file:/D:/dev/Java/jdk1.8.0_201/jre/lib/rt.jar
//file:/D:/dev/Java/jdk1.8.0_201/jre/lib/sunrsasign.jar
//file:/D:/dev/Java/jdk1.8.0_201/jre/lib/jsse.jar
//file:/D:/dev/Java/jdk1.8.0_201/jre/lib/jce.jar
//file:/D:/dev/Java/jdk1.8.0_201/jre/lib/charsets.jar
//file:/D:/dev/Java/jdk1.8.0_201/jre/lib/jfr.jar
//file:/D:/dev/Java/jdk1.8.0_201/jre/classes

**Extension ClassLoader:**扩展类加载器,负责加载JRE的扩展目录(%JAVA_HOME%/jre/lib/ext或者java.ext.dirs系统属性指定的目录)中JAR包类。

通过这种方式,就可以为java扩展核心类以外的新功能,只要把自己开发的类打包成JAR文件,让后放入%JAVA_HOME%/jre/lib/ext路径即可。

**System ClassLoader:**系统类(也称为应用)加载器,它负责在JVM启动时加载来自java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。

程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器,如果没有特别指定,则用户自定义的类加载器都以类加载器作为父类加载器。

类加载机制

JVM的类加载机制主要有三种:

1、全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入,

2、父类委托,则是先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。

3、缓存机制,缓存机制将会保证所有加载过的类都会被缓存,当程序中需要使用某个类时,类加载器先从缓存区中搜寻该类,只有当缓存区中不存在该类对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区中,这就是为什么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。

注:类加载器之间的父子关系并不是类继承上的父子关系,这里的父子关系是类加载器实例之间的关系。

用户类加载器——>系统类加载器——>扩展类加载器——>根类加载器

类加载器加载Class的8个步骤:

1、检测此类是否载入过(即在缓存区中是否有此class),如果有则直接进入第8步,否则接着执行2步。

2、如果父类加载器不存在(如果没有父类加载器,则要么parent一定是根类加载器,要么本身就是根类加载器),则跳到第4步,如果父类加载器存在,执行下一步。

3、请求使用父类加载器去载入目标类,如果成功载入则跳到8步,否则执行5步

4、请求使用根类加载器来载入目录类,如果成功载入则跳到8步,否则跳到7步

5、当前类加载器尝试寻找class文件(从与此ClassLoader相关的类路径中寻找),如果找到则执行6步,如果找不到则7步

6、从文件中载入Class,成功载入后跳到8步

7、抛出ClassNotFoundException异常。

8、返回对应的java.lang.Class对象。

注:其中第5、6步允许ClassLoader的findClass()方法来实现自己的载入策略,甚至重写loadClass()方法来实现自己的载入过程。

Class对象

获得Class对象的三种方式:

1、使用Class类的 forName(String clazzName) 静态方法,该方法需要传入某个类的全限定类名(必须添加完整包名)。多用于配置文件

2、调用某个类的class属性来获取该类对应的Class对象。例如:Person.class将会返回Person类对应Class对象。

多用于参数传递

3、调用某个对象的getClass()方法,该方法是java.lang.Object类中的一个方法,所以所有的java对象都可以调用该方法,该方法将会返回该对象所属类对象的Class对象。多用于对象的获取字节码方式

反射

通过反射创建对象两种方式:

1、使用Class对象的newInstance()方法来创建该Class对象对应类的实例,这种方式要求该Class对象的对应类有默认构造器,而执行newInstance()方法时实际上是利用默认构造器来创建该类的实例。

2、先使用Class对象获取指定的Constructor对象,在调用Constructor对象的newInstance()方法来创建Class对象对应类的实例。通过这种方式可以选择使用指定的构造器来创建实例。

动态代理

Proxy提供了两个方法来创建动态代理类和动态代理实例

example

public class ReflexDemo1 {
  public static void main(String[] args) throws Exception{
    //1.创建properties对象
    Properties properties = new Properties();
    //获取类加载器
    ClassLoader loader = ReflexDemo1.class.getClassLoader();
    //通过类加载器读取配置文件
    InputStream is = loader.getResourceAsStream("pro.Properties");
    properties.load(is);

    //获取配置文件中定义的类名全路径
    String className = properties.getProperty("className");
    //获取配置文件中定义的方法名
    String methodName = properties.getProperty("methodName");

    //加载该类进内存
    Class cls = Class.forName(className);
    //创建对象
    Object obj = cls.newInstance();
    //获取方法对象
    Method method = cls.getMethod(methodName);
    //执行方法
    method.invoke(obj);
  }
}

动态代理

//返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 
  参数:
  	ClassLoader loader -类加载器:真实对象.getClass().getClassLoader()
  	Class<?>[] interfaces -接口数组:真实对象.getClass().getInterfaces()
  	InvocationHandler h -处理器:new InvocationHandler()
  
  
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {}
	参数:
		Object proxy -代理对象
  	Method method -代理对象调用的方法,被封装为的对象
  	Object[] args -代理对象调用的方法时,传递的实际参数

example

//接口
public interface SaleComputer {
    public String sale(double money);
    public void show();
}

//真实类
public class Lenovo implements SaleComputer {
    @Override
    public String sale(double money) {
        System.out.println("花了"+money+"元买了一台联想电脑...");
        return "联想电脑";
    }
    @Override
    public void show() {
        System.out.println("展示电脑....");
    }
}

//动态代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyTest {
    public static void main(String[] args) {
        //1.创建真实对象
        Lenovo lenovo = new Lenovo();
        //2.动态代理增强lenovo对象
        /*
            三个参数:
                1. 类加载器:真实对象.getClass().getClassLoader()
                2. 接口数组:真实对象.getClass().getInterfaces()
                3. 处理器:new InvocationHandler()
         */
        SaleComputer proxy_lenovo = (SaleComputer) Proxy.newProxyInstance(lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), new InvocationHandler() {
            /*
                代理逻辑编写的方法:代理对象调用的所有方法都会触发该方法执行
                    参数:
                        1. proxy:代理对象
                        2. method:代理对象调用的方法,被封装为的对象
                        3. args:代理对象调用的方法时,传递的实际参数
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /*System.out.println("该方法执行了....");
                System.out.println(method.getName());
                System.out.println(args[0]);
								*/
                //判断是否是sale方法
                if(method.getName().equals("sale")){
                    //1.增强参数
                    double money = (double) args[0];
                    money = money * 0.85;
                    System.out.println("专车接你....");
                    //使用真实对象调用该方法
                    String obj = (String) method.invoke(lenovo, money);
                    System.out.println("免费送货...");
                    //2.增强返回值
                    return obj+"_鼠标垫";
                }else{
                    Object obj = method.invoke(lenovo, args);
                    return obj;
                }
            }
        });

        //3.调用方法

       /* String computer = proxy_lenovo.sale(8000);
        System.out.println(computer);*/
        proxy_lenovo.show();
    }
}