类的加载

类的加载过程主要分为三个步骤:加载、连接、初始化。

主要涉及下面两个过程:

  • 将类的 class 文件读入内存,并为之创建一个 java.lang.Class 对象。系统中所有的类实际上也是实例,它们都是 java.lang.Class 的实例
  • 为类变量进行初始化操作

反射

每个类被加载完之后,系统会为该类生成一个 Class 对象。通过该 Class 对象可以访问到 JVM 中的这个类。

获取 Class 的方式

  • Class.forName(String clazzName)
  • 类名.class
  • 对象.getClass()

从 Class 中获取信息

Class 类提供了大量的实例方法来获取该 Class 对象所对应的类的详细信息。

1
2
// 第一个参数指定方法名,后面个数可变的 Class 参数指定形参类型列表
clazz.getMethod("info", String.class, Integer.class)

使用反射生成并操作对象

可以通过 Method 对象执行相应的方法,Constructor 对象调用对应的构造器创建实例,通过 Field 对象直接访问并修改对象的成员变量值。

创建对象

默认构造器方式。使用 Class 对象的 newInstance() 方法创建该 Class 对象对应类的实例。

指定构造器方式。使用 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance() 方法。

调用方法

每个 Method 对象对应一个方法,通过 Method 对象的 invoke 方法来调用它对应的方法。

1
2
Method mtd = targetClass.getMethod(mtdName, String.class);
mtd.invoke(target, "hello");

当使用 invoke() 方法时,会检查该方法的权限,如果需要调用某个对象的 private 方法,需要先调用 setAccessible(true) 方法,取消访问权限检查。

访问成员变量值

通过 Class 对象的 getFields()、getField() 或 getDeclaredFiled() 方法获取全员变量或指定变量。

Field 提供了如下两组方法来读取或设置成员变量值:

  • getXxx(Object obj):获取 obj 对象的该成员变量的值。,此处的 Xxx 对应 8 种基本类型,如果该成员变量的类型是引用类型,则取消 get 后面的 Xxx。
  • setXxx(Object obj,Xxx val):将 obj 对象的该成员变量设置成 val 值。此处的 Xxx 对应 8 种基本类型,如果该成员变量的类型是引用类型,则取消 set 后面的 Xxx。

使用这两个方法可以随意地访问指定对象的所有成员变量,包括 private 修饰的成员变量。

1
2
3
4
5
Person p = new Person();
Class<Person> personClazz m = Person.class;
Field nameField = personClazz.getDeclaredField("name"); 
nameField.setAccessible(true);
nameField.set(p ,"Yeeku.H.Lee");

此处使用 getDeclaredField 方法因为 getField 方法只能获取 public 访问控制的成员变量。

反射中使用泛型

在反射中使用泛型可以避免反射生成的对象需要进行强制转换。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class CrazyitObjectFactory {  
public static Object getInstance(String clsName) {
        try {
            Class cls = Class.forName(clsName);
	    // 对象的类型是 Object
            return cls.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

当需要使用 CrazyitObjectFactory 的 getInstance() 方法来创建对象时,需要进行强制类型转换

1
2
// 获取实例后需要强制类型转换
Date d = (Date)Crazyit.getInstance("java.util.Date");

如果将上面的 CrazyitObjectFactory 工厂类改写成使用泛型后的 Class,就可以避免这种情况。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class CrazyitObjectFactory2 {
    public static <T> T getInstance(Class<T> cls) {
        try {
            return cls.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        // 获取实例后无须类型转换
        Date d = CrazyitObjectFactory2.getInstance(Date.class);
    }
}

另一个应用,使用反射来获取泛型的类型信息。

我们知道,可以通过如下代码获得指定普通成员变量的类型。

1
2
// 获取成员变量 f 的类型
Class<?> a = f.getType();

如果该成员变量的类型是有泛型类型的类型,如 Map<String, Integer> 类型,则不能准确地得到该成员变量的泛型参数。则需要使用如下方法来获取该成员变量的泛型类型。

1
2
// 获得成员变量 f 的泛型类型
Type gType = f.getGenericType();

然后将 Type 对象强制类型转换为 ParameterizedType 对象。ParameterizedType 代表被参数化的类型,也就是增加了泛型限制的类型。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
27
28
29
30
31
32
33
34
35
public class GenericTest {
    private Map<String, Integer> score;

    public static void main(String[] args) throws Exception {
        Class<GenericTest> clazz = GenericTest.class;
        Field f = clazz.getDeclaredField("score");
        // 直接使用 getType() 取出的类型只对普通类型的成员变量有效
        Class<?> a = f.getType();
        // 下面将看到仅输出java.util.Map
        System.out.println("score的类型是:" + a);
        // 获得成员变量f的泛型类型
        Type gType = f.getGenericType();
        // 如果gType类型是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("获取泛型类型出错!");
        }
    }
}
// score的类型是:interface java.util.Map
// 原始类型是:interface java.util.Map
// 泛型信息是:
// 第0个泛型类型是:class java.lang.String
// 第1个泛型类型是:class java.lang.Integer

拓展:Type 也是 java.lang.reflect 包下的一个接口,该接口代表所有类型的公共高级接口,Class 是 Type 接口的实现类。Type 包括原始类型、参数化类型、数组类型、类型变量和基本类型等。

动态代理 & AOP

JDK 动态代理的实现

JDK 动态代理只能为接口创建动态代理。

Proxy 类提供了用于创建动态代理对象的静态方法:

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

创建一个动态代理对象,该代理对象的实现类实现了 interfaces 指定的系列接口,执行代理对象的每个方法时都会被替换执行 InvocationHandler 对象的 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package cc.tianny.concurrence.j2se.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxy {
    public static void main(String[] args) {
	// 创建动态代理对象的三板斧
        InvocationHandler handler = new MyInvocationHandler();
        Person p = (Person)Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, handler);
        p.walk();
        p.sayHello();
    }
}

class MyInvocationHandler implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        /*
        执行动态代理对象的所有方法时,都会替换成执行如下的 invoke 方法
        proxy:代表动态代理的对象
        method:代表正在执行的方法
        args:代表调用目标方法时传入的参数
         */
        System.out.println("正在执行的方法: " + method);
        if (args != null) {
            System.out.println("下面是执行该方法传入的实参为: ");
            for (Object val : args) {
                System.out.println(val);
            }
        } else {
            System.out.println("调用该方法没有实参");
        }
        return null;
    }
}

interface Person {
    void walk();
    void sayHello();
}

AOP

假设我们封装好了一个 log() 方法,以便在程序中的其他方法中调用该方法来打印日志信息。常见的做法是在每个方法中直接调用该方法,但这种方式的弊端在于每个方法中都显示调用了 log() 方法,即以硬编码的方式进行了调用。那么如何避免使用硬编码的方式调用 log() 方法呢?这是可以通过动态代理来实现。

由于 JDK 动态代理只能为接口创建动态代理。下面提供一个接口:

1
2
3
4
interface Dog {
    void info();
    void run();
}

如果直接使用 Proxy 为该接口创建动态代理对象,则动态代理对象的所有方法的执行效果又将完全一样。通常情况下,日常业务开发中会为 Dog 接口提供一个或多个实现类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class GunDog implements Dog {

    @Override
    public void info() {
        System.out.println("我是一只猎狗");
    }

    @Override
    public void run() {
        System.out.println("我奔跑迅速");
    }
}

现在开始看我们需要实现的功能。要求执行 info()run() 方法能调用某个通用方法,但又要避免以硬编码的方式。

下面提供一个 DogUtil 类,该类里包含两个要被调用的通用方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class DogUtil {
    // 第一个拦截器
    public void method1() {
        System.out.println("----模拟第一个通用方法----");
    }

    // 第二个拦截器
    public void method2() {
        System.out.println("----模拟第二个通用方法----");
    }
}

借助于 Proxy 和 InvocationHandler 就可以实现自动将 method1 和 method2 两个通用方法插入 info()run() 方法中执行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class MyInvocationHandler1 implements InvocationHandler {
    // 需要被代理的对象
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    // 执行动态代理对象的所有方法都将被替换成执行如下的 invoke 方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        DogUtil dogUtil = new DogUtil();
        // 执行通用方法 method1
        dogUtil.method1();
        // 以 target 作为主调来执行 method 方法
        Object result = method.invoke(target, args);
        // 执行通用方法 method2
        dogUtil.method2();
        return result;
    }
}

下面再为程序提供一个 MyProxyFactory 类,用来为指定的 target 类生成动态代理对象。

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

上面的代理工厂类为 target 对象生成了一个动态代理对象,这个动态代理对象与 target 实现了相同的接口,从这个意义上看,动态代理对象可以当成 target 对象使用。当程序调用动态代理对象的 info() 方法时,实际就变为执行 MyInvocationHandler1 对象的 invoke() 方法。具体执行流程如下:

  • 创建 DogUtil 实例
  • 执行 DogUtil 实例的 method1 方法
  • 使用反射以 target 作为调用者执行 info() 方法
  • 执行 DotUtil 实例的 method2() 方法

下面用一个主程序进行测试

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

上面的 proxyDog 对象实际上是动态代理对象,因为动态代理对象也实现了 Dog 接口,所以也可以当成 Dog 对象使用。

在使用 Proxy 生成一个动态代理时,一般不会凭空生成一个动态代理,因为这样不会有太大的意义。通常是都是为指定的目标对象生成动态代理。

这种动态代理在 AOP 中被称为 AOP 代理,AOP 代理可代替目标对象,AOP 代理包含了目标对象的全部方法,同时在 AOP 代理的方法中可以在执行目标方法之前、之前插入一些通用处理方法。