类的加载机制与反射机制

JVM和类

Java命令运行某一个Java程序的时候,该命令将会启动一个Java虚拟机进程,不管该Java程序多么复杂,都处于JVM虚拟机里面,所有的线程、所有的变量都处于同一个进程里面。

JVM终止的条件:

  • 程序正常运行结束
  • 遇到未捕获的异常或错误而结束
  • 平台强制结束了JVM
  • 运行的时候使用了System.exit()和Runtime.getRuntime().exit()代码 处结束程序

类的加载或初始化

如果该类还没有被加载到内存中,则系统会通过加载、连接、初始化三个步骤来对该程序进行初始化。

类加载

类的加载是指类加载器将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说,当程序使用任何类的时候,系统都会为之创建一个java.lang.Class对象。

class是作为类的二进制数据,通常有以下几种来源:

  • 本地系统加载class文件
  • 从jar包 加载class 文件
  • 通过网络加载class文件
  • 把一个java源文件动态编译,并执行加载
    类加载器通常无须等到使用的时候才加载,虚拟机规范允许系统预先加载某些类

类的连接

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

  • 验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
  • 准备: 类准备阶段则负责为类的静态Field分配内存,并设置默认初始值
  • 解析:将类的二进制数据中的符号引用替换成直接引用。

类的初始化

类的初始化阶段,虚拟机负责对类进行初始化,主要就是对静态Field进行初始化。在Java类中对静态Field指定初始值有两种方式:

  • 声明静态Field时指定初始值
  • 使用静态初始化块为静态Field指定初始值
    静态初始化块都将被当成类的初始化语句
    类的初始化的步骤:
  1. 先判断一下某一个类是不是加载和连接,先进行加载和连接操作
  2. 判断该类的直接父类有没有被初始化,先初始化其父类。每次先初始化的都是Object类
  3. 假如类中有初始化语句,则系统依次执行这些初始化语句

    类初始化的时机

  • 创建类的实例(new,反射,反序列化)
  • 调用某个类的静态方法
  • 使用某个类或者接口的静态Field ,或者为该Field进行赋值
  • 使用反射方式来强制创建某个类的或者接口的对应java.lang.Class 对象。
  • 初始化某个类的子类
  • 直接使用java.exe 命令来运行某个主类

    类加载器

    类加载器负责将class文件加载到内存中,并且为之生成对应的java.lang.Class对象。开发中很少用到类加载机制,但是了解这个机制,能更好的满足我们的需求。
    一个类只要加载进JVM过一次,就不会在加载。同一个类的定义为(包名-类名)作为标识是一样的。一个对象的唯一标识使用的,同一个类+ 类加载器的编号。只有这三个全部相同了才能属于同一个类。
    当JVM启动的时候,会形成3个类加载器组成的初始类加载器层次结构。
  • Bootstrap ClassLoader:根类加载器
    负责加载java的核心类。
  • Extension ClassLoader: 扩展类加载器
    扩展类加载器负责加载JRE的扩展项目中JAR包的类,通过这种方式,就可以为JAVA扩展核心类以外的新功能,只要我们将自己开发的类打包成JAR包,然后放入JAVA_HOME/jre/lib/ext 路径即可。
  • System ClassLoader:系统类加载器
    他负责在JVM启动的时候加载来自java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH环境变量所指定的JAR包和类的路径。

    类加载机制

    JVM的类加载机制主要有如下3种:
  • 全盘负责
    就是当一个类加载器负责加载某个Class的时候,该Class所依赖的和引用的其他Class 也将由该类负责载入。
  • 父类委托
    先让parent(父)类加载器试图加载该Class,只有在父类加载器无法加载该类的时候才尝试从自己的类路径中加载该类。
  • 缓存机制
    缓存机制将会保证所有加载过的Class 都会被缓存,当程序中需要使用某个Class的时候,类加载器先从缓存中搜寻该Class,只有当缓存区不存在该class对象的时候,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。

    自定义类加载器

    开发者通过扩展ClassLoader的子类,并重写该ClassLoader所包含的方法来实现自定义的类加载器。可以实现如下功能:
  • 执行代码之前自动验证数字签名
  • 根据用户提供的密码解密代码,从而实现代码混淆来避免反编译Class文件
  • 根据用户需求来动态的加载类
  • 根据应用需求把其他数据以字节码的形式加载到应用中

    URLClassLoader类

    通过该类既可以从本地文件系统获取二进制文件来加载该类,也可以从远程主机获取二进制文件来加载该类。
    主要提供了两构造器:
  • URLClassLoader(URL[] urls):使用默认的父类加载器创建一个ClassLoader对象,该对象将从urls所指定的系列路径来查询并加载类
  • URLClassLoader(URL[] urls ,ClassLoader parent) 使用指定的父类加载器创建一个ClassLoader对象
    • URL file前缀表示从本地文件系统加载
    • http 前缀表示从互联网通过HTTP 加载访问来加载
    • ftp 为前缀 表明从互联网通过ftp 访问加载

      通过反射查看类的信息

      程序在运行时会出现两种类型,运行时类型与编译时类型。程序在运行的时候接收到外部传入的一个对象,该对象的编译时类型时Object,但是程序又需要调用该对象运行时类型的方法,为了在运行的时候发现对象和类的真实信息,可以通过下面两种方法:
  • 假设在编译时和运行时完全知道类型的具体信息,这种情况下可以通过使用instanceof运算符进行判断,在进行强制类型转换。
  • 在编译的时候根本无法知道该对象和类属于哪些类,程序只能通过运行时信息来发现该对象和类的真实信息,就必须使用反射。

    获取Class 对象

  • 使用Class 类的forName(String clazzName) 静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名
  • 调用某个类的class属性来获取该类对应的Class 对象例如:Person.class
  • 调用某个对象的getClass()方法。该方法是java.lang.Object 类的一个方法
  • 调用某个对象的getClass()方法,该方法是java.lang.Object类中的一个方法,所以所有的java对象都可以调用该方法。
    前两种都是根据类来获取指定类的Class对象,第二种方法有如下优点:
  • 安全性。程序在编译阶段就可以检查Class对象是不是存在
  • 程序的性能更好。因为无需调用方法,因此性能更好

    从class中获取信息

    Class类提供了大量的实例方法来获取该Class对象所对应的详细信息,主要使用方式查询API。
    获取构造器的时候无须传入构造器名-同一个类的所有构造器的名字都是相同,所以要确定一个构造器只要指定形参列表即可。

    使用反射生成并操作对象

    创建对象

    通过反射生成对象有两种方式:
  • 使用Class对象的newInstance()方法来创建该Class对象对应类的实例,这种方式要求该Class必须要实现默认的构造器

根据配置文件创建 对象池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package Tool;

import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class test {
//定义一个对象池,前面是对象名,后面是实际的对象
private Map<String, Object> objectPool = new HashMap<String, Object>();
//定一个创建对象的方法
//该对象只要传入一个字符串类名,程序可以根据该类名生成Java对象
private Object createObject (String clazzName)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class<?> clazz = Class.forName(clazzName);
//使用clazz 默认的构造器创建实例
return clazz.newInstance();
}
//该方法根据指定文件来初始化对象池
//根据配置文件来创建对象
public void initPool(String fileName) {
try {
FileInputStream fis = new FileInputStream(fileName);
Properties pr = new Properties();
pr.load(fis);
for(String name : pr.stringPropertyNames()) {
System.out.println(name);
//每取出一对 key-value对就根据value创建一个对象,根据对象值 填充到对象池中
objectPool.put(name,createObject(pr.getProperty(name)));
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("读取"+fileName+"异常");
}

}
public Object getObject(String name) {
return objectPool.get(name);

}
public static void main(String[] args) {
test pf = new test();
pf.initPool("./src/instance.properties");
Object x1 = pf.getObject("a");
Object x2 = pf.getObject("b");
System.out.println(x1.equals(x2));
//System.out.println(pf.getObject("b"));
}
}

  • 先使用Class对象获取指定的Constructor对象,在调用Constructor对象的newInstance()方法来创建该对象对应的实例。
    如果不想利用默认构造器来创建java对象,而是使用指定的构造器来创建java对象,则需要利用Constructor对象,每一个Constructor对应一个构造器。
    这个的使用步骤为:
    • 首先获得一个类的Class
    • 通过Class获取该类对应的constructor
    • 调用Constructor的newInstance()方法来创建Java对象。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      //获取class对象
      try {
      Class<?> cla = Class.forName("javax.swing.JFrame");
      Constructor con = cla.getConstructor(String.class);
      //调用方法创建对象
      Object obj = con.newInstance("ceshi");
      System.out.println(obj);
      } catch (Exception e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
      }

第一种方式来创建对象是比较常见的方式,因为在java中很多都是通过读取配置文件信息来创建java对象的。

调用方法

当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods()方法或者getMethod()方法来获取全部方法或者指定方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package Tool;

import java.io.FileInputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class test {
//定义一个对象池,前面是对象名,后面是实际的对象
private Map<String, Object> objectPool = new HashMap<String, Object>();
private Properties pr = new Properties();
//定一个创建对象的方法
//该对象只要传入一个字符串类名,程序可以根据该类名生成Java对象
private Object createObject (String clazzName)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class<?> clazz = Class.forName(clazzName);
//使用clazz 默认的构造器创建实例
return clazz.newInstance();
}
//该方法根据指定文件来初始化对象池
//根据配置文件来创建对象
public void initPool(String fileName) {
try {
FileInputStream fis = new FileInputStream(fileName);

pr.load(fis);
for(String name : pr.stringPropertyNames()) {
System.out.println(name);
//每取出一对 key-value对就根据value创建一个对象,根据对象值 填充到对象池中
if(!name.contains("%")) {
objectPool.put(name,createObject(pr.getProperty(name)));
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("读取"+fileName+"异常");
}
}
public void initProperty() {
for(String name : pr.stringPropertyNames()) {
// 每次取出来一个key-value 对,如果key 中包含百分号(%)
//即可认为该key 是用来为对象的Field赋值的
/*
* %的前半为对象名字,后半为Field设置值
* 程序将会调用相对应的setter来为Field设置值
*/
if(name.contains("%")) {
//将配置文件按照%进行分割
String [] objAndProp = name.split("%");
//取出需要设置的Field值的目标对象
Object target = getObject(objAndProp[0]);
//该Field对应的setter方法名为
String mtdName = "set"+objAndProp[1].substring(0,1).toUpperCase()+objAndProp[1].substring(1);
Class<?> targetClass = target.getClass();
//获取该属性对应的setter方法
try {
Method mtd = targetClass.getMethod(mtdName, String.class);
//通过Method 的invoke 方法执行setter 方法
//将config.getProperty(name) 的属性值作为调用setter的方法实参
mtd.invoke(target,pr.getProperty(name));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

public Object getObject(String name) {
return objectPool.get(name);
}
public static void main(String[] args) {
test test = new test();
test.initPool("./src/instance.properties");
test.initProperty();
System.out.println(test.getObject("a"));
}
}

使用Method的invoke()方法来调用对应的方法的时候,Java程序会要求必须有调用该方法的权限,如果某个程序想要调用某个对象的private方法, 那么可以先调用Method方法的如下方法:

  • setAccessible(bool flag) 值为true的时候,说明该Method方法在使用的时候要取消java语言的访问呢检查,值为falser的时候,则要进行权限的检查。该方法不只是属于method ,对于constructor、field都是可以调用的

    访问属性值

  • getField(String name)
  • getFields()
  • getDeclaredField(String name)
  • getDeclaredFields()
  • setXxx(Object obj,Xxx val) 将obj对象的field设置成val值,Xxx对应八种基本类型,val是要设置的对象的值。如果属性类型是引用类型,则取消set后面的Xxx
  • getXxx()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class Person1 {
    private String name;
    private int age;
    public String toString() {
    return "Person[name:"+name+",age:"+age+"]";
    }
    }

    public void test5() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
    Person1 p = new Person1();
    Class<Person1> clazz = Person1.class;
    Field nameField = clazz.getDeclaredField("name");
    nameField.setAccessible(true);
    nameField.set(p, "gorge");
    Field ageField = clazz.getDeclaredField("age");
    ageField.setAccessible(true);
    ageField.setInt(p, 12);
    System.out.println(p);
    }

操作数组

在java.lang.reflect包下还提供了一个Arrays类,Arrays对象可以代表所有数组,程序可以通过使用Arrays来动态的创建数组。

  • static Object newInstance(class<?> type, int len) 创建一个类型为type,长度为len的数组
  • static xxx getXxx(Obj array, int index) 返回array数组中第index个元素。其中x x x是各种基本数据类型
  • static void setXxx(Obj array, int index,xxx val)将arrays数组中第index个元素的值设置为val,如果是引用类型则直接使用set()
1
2
3
4
5
6
7
Object arr = Array.newInstance(String.class,4);
//为数组赋值
Array.set(arr,3,"java");
Array.set(arr, 2,"java ee");
Object one = Array.get(arr,2);
Object two = Array.get(arr,3);
System.out.println(one+":"+two);

使用反射生成JDK动态代理

在java的java.lang.reflect 包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口可以生成JDK动态代理类或动态代理对象。
如果在程序中为一个或多个接口动态的生成实现类,就可以使用proxy来创建动态代理类;如果需要为一个或多个接口动态的创建实例,也可以使用Proxy来创建动态代理实例。

  • static Class<?> getProxyClass(ClassLoader loader,Class <?> … interfaces): 创建一个动态代理类所对应的class对象,该代理类将实现interface 所指定的多个接口。第一个ClassLoade参数指定生成动态代理类的类加载器。
  • static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h):直接创建一个动态代理对象,该代理对象的实现类实现了interface指定的系列接口,执行代理对象的每个方法时都会被替换执行InvocationHandler对象的invoke方法。

    通过动态代理类生成动态代理对象

    先生成一个动态代理类,然后在通过动态代理类,来创建动态代理对象
1
2
3
4
5
6
7
//创建一个InvokationHandler对象
InvocationHandler handler = new MyInvokationHandler();
//通过代理类来创建代理对象
Class proxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class[] {Person.class});
Constructor ctor = proxyClass.getConstructor(new Class[] {InvocationHandler.class});
Person p2 = (Person)ctor.newInstance(new Object[] {handler});
p2.sayHello("nnd");

直接生成动态代理对象

  • 首先,我们要先创建一个接口
1
2
3
4
public interface Person {
void walk();
void sayHello(String name);
}
  • 然后实现InvocationHandle接口,因为在执行方法的时候,都会被替换成调用该invoke()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//生成动态代理对象
public class MyInvokationHandler implements InvocationHandler {
/*
* 执行动态代理对象的所有方法的时候,都会被替换成执行如下的invoke方法
* 其中:
* proxy:代表动态代理对象 例如:Person.class.getClassLoader()
* method:代表正在执行的方法
* args:代表调用目标方法的时候传入的实参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
System.out.println("---正在执行的方法:"+method);
//这里可以判断一下执行的是什么方法,然后对该方法进行 重新编辑
if(method.equals(Person.class.getMethod("walk", null))) {
System.out.println("正在执行walk方法");
}
if(args!=null) {
System.out.println("下面是执行该方法的时候传入的参数:");
for(Object obj:args) {
System.out.println(obj);
}
}else {
System.out.println("该方法没有实际的参数");
}
return null;
}
  • 创建一个动态代理对象
1
2
3
4
5
6
7
8
public static void main(String [] args) {
//创建一个InvokationHandler对象
InvocationHandler handler = new MyInvokationHandler();
//使用指定的InvocationHandler 来生成一个动态代理对象
Person p = (Person)Proxy.newProxyInstance(Person.class.getClassLoader(),new Class[] {Person.class} , handler);
p.walk();
p.sayHello("HELLO");
}

一般我们通过第二种方式创建类的对象就可以了,在编写框架或者底层基础代码的时候,动态代理的作用就非常大了。

动态代理和AOP

在开发一个实际的应用软件系统的时候,总会存在相同的代码段重复出现的情况,这种情况下,如果通过复制粘贴的方式,则对于后期的维护是非常困难的,如果通过将这个方法写出一个方法,然后调用的方式执行,则会使得程序的耦合性提高。因此,我们就可以通过动态代理的方式进行。
具体方式如下:

  • 创建一个对象接口
1
2
3
4
public interface Dog {
void info();
void run();
}
  • 创建接口的实现类
1
2
3
4
5
6
7
8
9
10
public class GunDog implements Dog {
@Override
public void info() {
System.out.println("我是一只猎狗");
}
@Override
public void run() {
System.out.println("我奔跑很快");
}
}
  • 将重复的方法写到一个工具类中
1
2
3
4
5
6
7
8
class DogUtil {
public void method() {
System.out.println("------通用方法一");
}
public void method2() {
System.out.println("------通用方法二");
}
}
  • 实现InvocationHandler方法,并设置主调对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyInvokationHandler implements InvocationHandler {
//需要被代理的对象
private Object target;
public void setTarget(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
DogUtil du = new DogUtil();
//执行DogUtil对象中的method方法
du.method();
Object result = method.invoke(target, args);
//执行DogUtil 对象中的method2方法
du.method2();
return result;
}
}
  • 创建代理工厂为指定的target 生成动态代理对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class MyProxyFactory {
    //为指定的target生成动态代理对象
    public static Object getProxy(Object target) {
    MyInvokationHandler handler = new MyInvokationHandler();
    //为handler设置target对象
    handler.setTarget(target);
    //创建并返回一个动态代理
    return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), handler);
    }
    }
  • 测试

1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String [] args) {
Dog target = new GunDog();
//以指定target 来创建动态代理对象
Dog dog = (Dog)MyProxyFactory.getProxy(target);
dog.info();
dog.run();
}
}

当我们使用proxy生成一个动态代理的时候,往往并不会凭空产生一个动态代理,通常都是为指定的目标对象生成动态代理。
这种动态的代理在AOP(面向切面的编程)中被称为AOP代理,AOP代理可以代替目标对象,AOP代理包含了目标对象的全部方法,但是AOP代理中的方法与目标对象的方法存在差异,AOP代理的方法可以在执行目标方法之前或者之后插入一些有用的处理。

反射和泛型

如果Class对应的类暂时未知,则使用Class<?>。通过在反射中使用泛型,可以避免使用反射生成的对象需要强制类型转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static <T> T getInstance(Class<T> cls) {
try {
return cls.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
public static void main(String [] args) {
//获取实例无需进行类型转换
Date d = getInstance(Date.class);
JFrame f = getInstance(JFrame.class);
}

使用反射来获取泛型信息

对于基本的类型,可以通过泛型,直接查看

1
2
3
4
public int dfsdf ;
Field t = Test.class.getField("dfsdf");
Class<?> TYPE = t.getType();
System.out.println(TYPE);

但是如果该Field是有泛型类型的类型,如Map<String,Integer>类型,则不能准确的得到这个类型。
要想获取泛型类型,则需要先使用

  • getGenericType() 获得实例的泛型类型
    然后在将Type 对象强制类型转换为ParameterizedType 对象,该对象代表被参数化的类型,也就是增加了泛型限制的类型。
  • getRawType(): 返回没有泛型信息的原始类型
  • getActualTypeArguments() 返回泛型参数类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private Map<String,Integer> score;
public static void main(String [] args) throws NoSuchFieldException, SecurityException {
Class<?> clazz = Test.class;
Field f = clazz.getDeclaredField("score");
Class<?> a = f.getType();
//下面将看到输出Map类型,但是map的泛型类型,则获取不到
System.out.println(a);
//获得Field实例f的泛型类型
Type gType = f.getGenericType();
//如果这个类型是ParameterizedType 对象
if(gType instanceof ParameterizedType) {
//强制类型转换
ParameterizedType ptype = (ParameterizedType)gType;
//获取原始类型
Type rType = ptype.getRawType();
System.out.println("原始类型为:"+rType);
//获取泛型类型的泛型参数
Type[] tArgs = ptype.getActualTypeArguments();
System.out.println("泛型类型是:");
for(int i=0;i<tArgs.length;i++) {
System.out.println("第"+i+"个泛型类型是"+tArgs[i]);
}
}else {
System.out.println("获取类型错误");
}
}

注意上面说有的类型都是来自java.lang.reflect包下的。