上帝视角学JAVA- 基础19-反射、动态代理【2021-09-10】

1、反射

反射是 JAVA 神奇的关键。

反射机制 允许程序在执行期间借助与 Reflection API 获取任意类的内部信息,并能直接操作任意对象的内部属性以及方法。

加载完类之后,在堆内存的方法区就产出了一个Class类型的对象。我们定义的所有的类都是 Class类型的对象。

一个类只有一个Class对象。我们定义的类的对象,属于Class对象的对象。

这个Class对象包含了完整的类的结果信息。可以通过获取这个对象,来获取类的结构。这个对象就像一面镜子,通过这个镜子看到类的结构,称为反射。

通过反射获取类的结构过程:

实例化对象->getClass方法获取字节码文件->获取完成的结构

还记得 最开始讲的 java运行过程吗?

 

通过javac.exe 编译.java文件得到 .class文件。

getClass() 方法就是获取这个 .class 文件。这是一个什么文件呢?见下面,下面是一开始我们常用的一个类 Animal.java 通过javac.exe生成的Animal.class 文件。

  

可以看到这是一个二进制文件。这是使用 sublim 这个编辑器打开的。这些二进制我们根本就看不懂啊。但是没关系,程序看的懂。这个文件就包含了Animal类的全部信息。

有了反射能干嘛?

在运行时,可以干下面的事,包括且不限于:

  • 判断任意一个对象所属于的类

  • 构造任意一个类的对象

  • 判断任意一个类所具有的成员变量和方法

  • 获取泛型信息

  • 调用任意一个对象的成员变量以及方法

  • 处理注解

  • 生成动态代理

1.1 反射主要的API

java.lang.Class类

这是一个类,是首字母大写的,关键字是首字母小写的。java是严格区分大小写的。

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement 

Class类是用来描述一个通用的类。上面是Class类的头。可以看到这个类实现了4个接口。

java.lang.reflect.Method 类:描述类的方法的类

public final class Method extends Executable {}

java.lang.reflect.Field类 描述类成员变量的类

public final
class Field extends AccessibleObject implements Member {}

java.lang.reflect.Constructor类 描述类的构造器的类

public final class Constructor<T> extends Executable {}

等等。

除了Class类在lang包下,其他都在lang包的子包reflect包下。这些类基本都是定义为final。即不可被继承。

public class Person {
     // 私有属性
    private String name;
    public int age;
​
    public void show(){
        System.out.println("公有方法:这是Person类的show方法打印的");
    }
     // 私有方法
    private String showNational(String str){
        System.out.println("私有方法:这是Person类的 showNational 方法打印的:" + str);
        return str;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
​
    public void setAge(int age) {
        this.age = age;
    }
    public Person() {
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // 私有构造器
    private Person(String name) {
        this.name = name;
    }
}

有上面这样一个Person类。我们来试试反射怎么用

// 不用反射,我们可以做这些事情
// 1、创建Person类的对象
Person p1 = new Person("Tom", 12);
// 2、通过对象调用公有的属性和方法。
p1.age = 10;
System.out.println(p1);
p1.show();
// 在Person 类的外部,是不可以通过对象调用私有结构的。包括私有属性、方法、构造器。

使用反射来实现和上面一样的事。

// 使用反射 来试试
// Person 类有一个属性class,就是Class类的一个对象。
Class clazz = Person.class;
// 获取构造器
Constructor cons = clazz.getConstructor(String.class, int.class);
// 创建对象
Object tom = cons.newInstance("Tom", 12);
// 强转
Person p = (Person) tom;
System.out.println(tom.toString());
// 通过反射来调用 属性、方法
// 调用属性
Field age = clazz.getDeclaredField("age");
age.set(p, 10);
// 调用show方法
Method show = clazz.getDeclaredMethod("show");
show.invoke(p);

反射除了实现上面的功能之外,反射还可以调用 普通方式不能调用的私有属性、方法、构造器。

要是反射能干的事只能和普通方式一样,反射也就没有啥存在的必要了。

// Person 类有一个属性class,就是Class类的一个对象。
Class clazz = Person.class;
​
// 使用clazz对象调用私有构造器
Constructor cons1 = clazz.getDeclaredConstructor(String.class);
// 设置可以访问私有的
cons1.setAccessible(true);
// 创建对象
Object obj = cons1.newInstance("Jerry");
// 强转回 Person 对象
Person jerry = (Person) obj;
// 调用私有属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(jerry, "jerry_big");
System.out.println(jerry);
// 调用 私有方法
Method showNational = clazz.getDeclaredMethod("showNational", String.class);
showNational.setAccessible(true);
String str = (String) showNational.invoke(jerry, "反射调用私有 哈哈哈");
System.out.println(str); 
​
// 输出
// Person{name='jerry_big', age=0}
// 私有方法:这是Person类的 showNational 方法打印的:反射调用私有 哈哈哈
// 反射调用私有 哈哈哈

上面代码可以看出,主要都是通过 类名点class 获取。不管是私有还是共有的,都可以获取。只不过如果是私有的,都要调用setAccessible 方法获取权限。 当调用有返回值的方法是,invoke方法的返回值就是原方法的返回值。

1.2 思考

1、反射可以调用私有的东西。那么面向对象中,说的类的封装性,即权限。是否就失效了呢?这会不会矛盾呢?

不矛盾,这两种需求都是我们实际开发中需要的。私有是告诉你不要用。共有是告诉你这是提供给你用的。但是一定要使用,也是可以的。

2、反射可以和普通调用一样做一样的事情,是否就没必要用普通调用方式了呢?

从代码量来讲,明显普通调用更少,逻辑更清晰。也没有强制的麻烦。反射更多的是提供一种动态性。

当普通调用不能满足需求时,比如运行时才知道调什么。那就需要用反射。

1.3 Class 类

在1.1的代码中,使用反射基本都是通过 Class 类的对象进行操作的。

有必要研究一下这个Class类。

当一个类被编译为 .class字节码文件时。使用java.exe 对字节码文件进行解释运行。相当于将这个字节码文件 加载到内存中。这个过程就是类的加载。

加载到内存中的类,叫做运行时类。这个运行时类就是 Class类 的对象。

Class clazz = Person.class;

我们是通过 类名.class 方式得到Class类的对象。其实类名点class 就是表示这个类本身。但是不能直接写类名,不然就是代码一种类型了。为了区别于类型,就给每一个类都增加一个属性 class,使用类名点class方式来表示 字节码文件。当加载到内存,就变成 Class 对象。

获取Class 类的对象的方式其实不止上面这一种。而且Class类是有泛型的

 Class<Person> clazz = Person.class;
// 方式2:通过运行时类的对象,调用getClass方法
Person p1 = new Person();
Class aClass = p1.getClass();

方式3:通过Class类的静态方法forName。需要传入全类名,防止名称冲突

Class clazz = Class.forName("com.xgzit.JavaBaseStudy.reflectStudy.Person");

方式4: 使用类的加载器

// 先通过当前类获取 ClassLoader类的对象 
// 再使用这个对象加载 需要获取的类,就会得到Class的对象。
ClassLoader classLoader = TestMain.class.getClassLoader();
Class clazz = classLoader.loadClass("com.xgzit.JavaBaseStudy.reflectStudy.Person");

加载到内存中的运行时类,会缓存一定的时间,不管是方式几,获取的都是同一个Class类的对象。

就像北京只有1个,你可以通过不同的交通方式到达北京。.

上面4种方式,方式3用的最多。因为方式1是写死的。如果是当前不存在的类,编译就会报错,不够灵活。

方式2都有对象了,一般就不用反射了。方式3比较灵活,即使全类名当前不存在,也不会报错,只有运行时真的不存在才会报错。

java可不只是有类,还有接口、数组、枚举、注解、基本数据类型等,这些东西加载后算Class类的对象吗?

实时上都算。void其实也算。

 

Class c1 =  Object.class;
Class c2 =  Comparable.class;
Class c3 =  String[].class;
Class c4 =  int[][].class;
Class c5 =  ElementType.class;
Class c6 =  Override.class;
Class c7 =  int.class;
Class c8 =  void.class;
Class c9 =  Class.class;

以上9个例子都是正确的。

int[] a = new int[10];
int[] b = new int[100];
Class c1 = a.getClass();
Class c2 = b.getClass();
System.out.println(c1 == c2); // true

上面的代码说明 只要数组的维度与类型都一致,比如都是int型的1维数组,那么它们的Class 对象就是同一个。

类的一个加载过程:

当程序主动使用某个类时,如果这个类还没有加载到内存中,则系统会通过如下三个步骤对类进行初始化。

 

类的加载与ClassLoader

 

类加载的作用:将class字节码文件内容加载到内存中,将这些数据转换为方法区的运行时数据结构,然后在堆中生成一个代表这个类的Class对象,作为方法区中类数据的访问入口。

类缓存:标准的javaSe类加载器可以按照要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间,不过JVM垃圾回收机制可以回收这些Class对象。

类加载器分为4种。

 

其中引导类是负责核心库,扩展类是加载引入的jar包,系统类是加载我们自己写的类。自定义的这里就不介绍了。

// 获取自定义类的类加载器 是系统类加载器
ClassLoader classLoader = TestMain.class.getClassLoader();
System.out.println(classLoader);
// 获取系统类加载器的 父加载器 是 扩展类加载器
ClassLoader parent = classLoader.getParent();
System.out.println(parent);
// 获取 扩展类的父加载器是 引导类加载器,无法直接获取!!!
ClassLoader parentParent = parent.getParent();
System.out.println(parentParent);
// 输出
// sun.misc.Launcher$AppClassLoader@18b4aac2
// sun.misc.Launcher$ExtClassLoader@4fca772d
// null

String类是核心库,由引导类加载器进行加载。也不能直接获取String类的类加载器。

1.4 加载配置文件

// properties 的特点是 key-value都是String
Properties pros = new Properties();
File file = new File("jdbc.properties");
// idea 中文件默认在 当前Module下
FileInputStream fis = new FileInputStream(file);
pros.load(fis);
​
String name = pros.getProperty("name");
String password = pros.getProperty("password");
System.out.println(name);

上面的代码是以前讲的加载配置文件的方式。

使用ClassLoader也可以用于加载配置文件

// properties 的特点是 key-value都是String
ClassLoader loader = TestMain.class.getClassLoader();
InputStream stream = loader.getResourceAsStream("jdbc1.properties");
Properties properties = new Properties();
properties.load(stream);
​
String name = properties.getProperty("name");
System.out.println(name);

classLoader 的对象调用getResourceAsStream方法,默认的文件在 当前module的src目录下。但是我测试失败啊。不知道什么鬼

1.5 创建运行时类的对象

加载到内存中的类就是运行时类。

Class<Person> clazz = Person.class;
// 创建对应的运行时类的对象
Person o = clazz.newInstance();
System.out.println(o); // Person{name='null', age=0}

首先需要拿到对应类的Class对象。通过Class对象调用newInstance方法。创建对象。

newInstance 实际上还是调用的空参构造器。因此必须提供一个空参构造器。还有一个问题就是权限。

如果空参构造器权限是私有的,那么也不能调用,会报非法访问异常。通常权限是public

  • 创建运行时类的对象需要满足 这个类提供一个空参构造器

  • 这个空参构造器权限一般是public

为了写通用的代码,需要提供空参构造器。其实是可以通过 getDeclaredConstructor 获取有参构造器来创建对象的。

但是每个类的有参构造器可能参数都不一样,很难写出通用的代码,而空参构造器都一样,没有参数。如果有属性,后面再通过属性赋值的形式加上就好了。

   
 public static void main(String[] args) throws Exception{
      int num = new Random().nextInt(3); // 0 1 2
        String classPath = "";
        switch (num){
            case 0:
                classPath = "java.util.Date";
                break;
            case 1:
                classPath = "java.lang.Object";
                break;
            case 2:
                classPath = "com.xgzit.JavaBaseStudy.reflectStudy.Person";
                break;
            default:
                break;
        }
        Object instance = getInstance(classPath);
        System.out.println(instance);
    }
    // 获取 对象
    public static Object getInstance(String classPath) throws Exception{
        Class<?> aClass = Class.forName(classPath);
        return aClass.newInstance();
    }

上面的代码就体现了反射的动态性。编译时不知道要什么对象。运行时才知道。

getInstance 方法是根据 传入的全类名进行创建对象的。传入的全类名不同,创建的对象也就不同。

上面使用了随机数+switch结构来控制 classPath的不同,从而实现了动态的创建不同的对象。

1.6 获取类的完整结构

为了更清晰,需要一些准备工作。

首先是注解: 以前提到注解主要就是反射时用的,现在就来用一下。下面是一个自定义的注解

@Repeatable(value = MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD, ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface MyAnnotation {
​
    // 定义属性  有点像方法,实际是属性。 返回值是属性类型, 如果需要接收多个属性值,就可以定义为数组
    String[] value() default "哈哈哈";
}

一个自定义接口:

public interface MyInterface {
​
    void info();
}
​

一个父类,带有泛型

public class Creature<T> implements Serializable {
    private static final long serialVersionUID = 1L;
​
    private char gender;
    public double weight;
​
    private void breath() {
        System.out.println("生物呼吸");
    }
    public void eat() {
        System.out.println("生物吃东西");
    }
}

一个具体的类:

@MyAnnotation(value = "hi")
public class FullPerson extends Creature<String> implements Comparable<String>, MyInterface {
​
    private String name;
    int age;
    public int id;
​
    public FullPerson() {}
    
    private static int staticMethodShow(int num){
            System.out.println("私有静态方法" + num);
            return num;
        }
    
    @MyAnnotation(value = "私有有参构造")
    private FullPerson(String name){
        this.name = name;
    }
    FullPerson(String name, int age){
        this.name = name;
        this.age = age;
    }
    @MyAnnotation()
    private String show(String str) {
        System.out.println("我的国籍是"+str);
        return str;
    }
    public String display(String interest) throws NullPointerException, ClassCastException{
        System.out.println("我的兴趣是"+interest);
        return interest;
    }
​
    @Override
    public int compareTo(String o) {
        return 0;
    }
​
    @Override
    public void info() {
        System.out.println("我是一个人");
    }
    @Override
    public String toString() {
        return "FullPerson{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                '}';
    }
}

FullPerson 类加上了注解,也有泛型。继承了Creature类,并实现了2个接口。mub

目标是获取运行时的 FullPerson 类的 结构。

  • Class类的 getFields 方法

Class<FullPerson> clazz = FullPerson.class;
// 获取属性
Field[] fields = clazz.getFields();
for (Field field : fields) {
    System.out.println(field);
}
// 输出
// public int com.xgzit.JavaBaseStudy.reflectStudy.FullPerson.id
// public double com.xgzit.JavaBaseStudy.reflectStudy.Creature.weight

首先都是要有运行时类的Class对象。使用getFields方法可以获取全部的公共属性。这里涉及到权限问题。直接获取是拿不到没有权限的属性的。这里的全部公共属性,不仅仅包括自己的,还包括其所有父类的公共属性。

1.6.1 属性

  • Class类的 getDeclaredFields 方法

Class<FullPerson> clazz = FullPerson.class;
// 获取属性
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
    System.out.println(field);
}
// 输出
// private java.lang.String com.xgzit.JavaBaseStudy.reflectStudy.FullPerson.name
// int com.xgzit.JavaBaseStudy.reflectStudy.FullPerson.age
// public int com.xgzit.JavaBaseStudy.reflectStudy.FullPerson.id

上面的代码是调用 getDeclaredFields 方法,这个方法是获取当前类定义是所有属性,是不包括其父类的,而且获取的属性忽略权限。

  • getModifiers 方法 获取属性的修饰符,返回值是int型。 即一个数,这个数表示了一种权限名,这个数与权限的对应关系在 Modifier 这个类里面定义了。使用Modifier的静态方法toString可以将 权限的修饰符翻译回来。

Class<FullPerson> clazz = FullPerson.class;
        // 获取属性
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            // 1、 获取权限修饰符
            int modifier = field.getModifiers();
            System.out.println(modifier);
            // Modifier 的 toString 方法可以将int型的数翻译成字符串
            System.out.println(Modifier.toString(modifier));
        }
// 输出
2
private
0
​
1
public

可以看到 2 是private; 0 是默认,默认就是不写。 1是public。

  • Field 类的 public Class<?> getType() 方法 获取属性的类型

Class<FullPerson> clazz = FullPerson.class;
// 获取属性
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
    // 获取类型
    Class<?> type = field.getType();
    System.out.println(type);
​
}
// 输出
class java.lang.String
int
int

可以看到 获取了属性的类型。

  • Field 类的 String getName() 方法: 获取变量名

Class<FullPerson> clazz = FullPerson.class;
// 获取属性
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
    // 获取变量名
    String name = field.getName();
    System.out.println(name);
}
// 输出
name
age
id

1.6.2 方法

  • Class类的 getMethods方法: 获取所有公共的方法,包括父类的

Class<FullPerson> clazz = FullPerson.class;
// 获取方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
    System.out.println(method);
}
// 输出
public int com.xgzit.JavaBaseStudy.reflectStudy.FullPerson.compareTo(java.lang.String)
public int com.xgzit.JavaBaseStudy.reflectStudy.FullPerson.compareTo(java.lang.Object)
public void com.xgzit.JavaBaseStudy.reflectStudy.FullPerson.info()
public java.lang.String com.xgzit.JavaBaseStudy.reflectStudy.FullPerson.display(java.lang.String)
public void com.xgzit.JavaBaseStudy.reflectStudy.Creature.eat()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
  • Class类的 getDeclaredMethods 方法:当前类的所有方法,忽略权限,不包括父类的

Class<FullPerson> clazz = FullPerson.class;
// 获取方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
    System.out.println(method);
}
// 输出
public int com.xgzit.JavaBaseStudy.reflectStudy.FullPerson.compareTo(java.lang.String)
public int com.xgzit.JavaBaseStudy.reflectStudy.FullPerson.compareTo(java.lang.Object)
public void com.xgzit.JavaBaseStudy.reflectStudy.FullPerson.info()
private java.lang.String com.xgzit.JavaBaseStudy.reflectStudy.FullPerson.show(java.lang.String)
public java.lang.String com.xgzit.JavaBaseStudy.reflectStudy.FullPerson.display(java.lang.String)
  • Method 类的 Annotation[] getAnnotations() 方法,获取方法的所有注解

注意,这样只能是获取 注解生命周期@Retention为RetentionPolicy.RUNTIME 的。并且,该注解要可以作用在方法上。

Class<FullPerson> clazz = FullPerson.class;
// 获取方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
    Annotation[] annos = method.getAnnotations();
    for (Annotation anno : annos) {
        System.out.println(anno);
    }
}
// 输出
@com.xgzit.JavaBaseStudy.AnnotationStudy.MyAnnotation(value=[哈哈哈])
  • Method 类的 int getModifiers() 获取方法的 修饰符。和属性的修饰符获取几乎一致

Class<FullPerson> clazz = FullPerson.class;
// 获取方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
    int mod = method.getModifiers();
    System.out.println(mod);
    System.out.println(Modifier.toString(mod));
}
// 输出
1
public
4161
public volatile
1
public
1
public
2
private

1代表 public 、4161 代表public volatile、2代表 private

  • Method 类的 Class<?> getReturnType() 获取方法的返回值类型

Class<FullPerson> clazz = FullPerson.class;
        // 获取方法
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            Class<?> returnType = method.getReturnType();
            System.out.println(returnType);
        }
// 输出
int
int
void
class java.lang.String
class java.lang.String
  • Method类的 String getName() 获取方法名

Class<FullPerson> clazz = FullPerson.class;
// 获取方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
    String name = method.getName();
    System.out.println(name);
}
// 输出
compareTo
compareTo
info
show
display
  • Method类的 Class<?>[] getParameterTypes 获取形参类型

Class<FullPerson> clazz = FullPerson.class;
// 获取方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
    String name = method.getName();
    System.out.print(name + "(");
    Class<?>[] parameterTypes = method.getParameterTypes();
    if (parameterTypes.length != 0){
        for (Class<?> parameterType : parameterTypes) {
            System.out.print(parameterType);
        }
    }
    System.out.println(")");
}
//
compareTo(class java.lang.String)
compareTo(class java.lang.Object)
info()
display(class java.lang.String)
show(class java.lang.String)
  • Method类的 Class<?>[] getExceptionTypes() 方法 获取方法抛出的异常

 Class<FullPerson> clazz = FullPerson.class;
// 获取方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
    String name = method.getName();
    System.out.print(name +"\t");
    Class<?>[] exceptionTypes = method.getExceptionTypes();
    if (exceptionTypes.length != 0){
        System.out.print("throws");
        for (Class<?> exceptionType : exceptionTypes) {
            System.out.print(exceptionType);
        }
    }
    System.out.println();
}
//输出
compareTo   
compareTo   
info    
display throwsclass java.lang.NullPointerExceptionclass java.lang.ClassCastException
show

1.6.3 构造器

  • Class类的 Constructor<?>[] getConstructors() 方法,获取运行时类的构造器。

    注意:这里的不能获取父类的构造器,只能获取自己的公有权限构造器

Class<FullPerson> clazz = FullPerson.class;
// 获取构造器
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor : constructors) {
    System.out.println(constructor);
}
// 输出
public com.xgzit.JavaBaseStudy.reflectStudy.FullPerson()
  • Class类的 Constructor<?>[] getDeclaredConstructors() 方法 获取运行时类自己的所有构造器,忽略权限

Class<FullPerson> clazz = FullPerson.class;
// 获取构造器
Constructor[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
    System.out.println(constructor);
}
// 输出
com.xgzit.JavaBaseStudy.reflectStudy.FullPerson(java.lang.String,int)
private com.xgzit.JavaBaseStudy.reflectStudy.FullPerson(java.lang.String)
public com.xgzit.JavaBaseStudy.reflectStudy.FullPerson()

其他的获取构造器的参数列表,修饰符,注解 什么的也都可以。和方法的获取方式类似。

1.6.4 父类

  • Class类的 Class<? super T> getSuperclass() 方法 获取父类

Class<FullPerson> clazz = FullPerson.class;
// 获取父类
Class<? super FullPerson> superclass = clazz.getSuperclass();
System.out.println(superclass);
// class com.xgzit.JavaBaseStudy.reflectStudy.Creature

获取的父类也是一个Class 类的对象。

  • Class类的 Type getGenericSuperclass() 方法,获取带泛型的父类。返回值是Type类型。Type是一个接口

Class<FullPerson> clazz = FullPerson.class;
// 获取带泛型的父类
Type superclass = clazz.getGenericSuperclass();
System.out.println(superclass);
// 输出
com.xgzit.JavaBaseStudy.reflectStudy.Creature<java.lang.String>
  • 获取父类的泛型

Class<FullPerson> clazz = FullPerson.class;
// 获取带泛型的父类
Type superclass = clazz.getGenericSuperclass();
System.out.println(superclass.getClass()); // class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
// 强转
ParameterizedType para = (ParameterizedType) superclass;
Type[] arguments = para.getActualTypeArguments();
for (Type argument : arguments) {
    System.out.println(argument.getTypeName()); // java.lang.String
}

为什么可以强转?因为superclass 本来就是这种类型的。可以使用superclass.getClass() 获取一下运行时类的类型。就知道可以强转为什么了。

1.6.5 接口、所在包、注解

  • Class<?>[] getInterfaces() 获取运行时类实现的接口。 这个不会获取父类里面实现的接口。

Class<FullPerson> clazz = FullPerson.class;
// 获取实现的接口
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> anInterface : interfaces) {
    System.out.println(anInterface);
}
// 输出
interface java.lang.Comparable
interface com.xgzit.JavaBaseStudy.interfacepack.MyInterface
  • Package getPackage() 获取运行时类所在的包

Class<FullPerson> clazz = FullPerson.class;
// 获取所在包
Package aPackage = clazz.getPackage();
System.out.println(aPackage);
// 输出
package com.xgzit.JavaBaseStudy.reflectStudy
  • Annotation[] getAnnotations() 获取运行时类上面的注解

Class<FullPerson> clazz = FullPerson.class;
// 获取类上面的注解
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
    System.out.println(annotation);
}
// 输出
@com.xgzit.JavaBaseStudy.AnnotationStudy.MyAnnotation(value=[hi])

这一节获取类的完整结构,其实就是 Class 类的里面的方法的使用。还涉及到Field类、Method类、Constructor类、Package类、Annotation类。即 类里面的每一个结构都有一个类与之对应。调用这些类的方法就可以干相应的活。本质上还是 类、对象 这些知识。

1.7 调用运行时类的指定属性、方法

1.7.1 指定属性

  • Class类的 Field getField(String name) 方法:获取指定的属性

    这个方法还是只能获取 公共权限的属性

Class<FullPerson> clazz = FullPerson.class;
// 创建运行时类的对象
FullPerson person = clazz.newInstance();
// 获取指定属性
Field id = clazz.getField("id");
// 设置当前属性的值 参数1是对象,参数2是值
id.set(person, 1001);
// 获取对象的属性值
int o = (int) id.get(person);
System.out.println(o); // 1001
  • Class类的 Field getDeclaredField(String name) 方法:获取指定的属性

    这个方法就可以获取运行时类的所有属性,忽略权限

Class<FullPerson> clazz = FullPerson.class;
// 创建运行时类的对象
FullPerson person = clazz.newInstance();
// 获取指定属性
Field name = clazz.getDeclaredField("name");
// 设置可以获取私有的权限
name.setAccessible(true);
// 设置当前属性的值 参数1是对象,参数2是值
name.set(person, "Tom");
// 获取对象的属性值
String o = (String) name.get(person);
System.out.println(o);

注意,访问私有属性时,必须设置setAccessible(true) 才可以。否则会报错。

1.7.2 指定方法

  • Class类的Method getMethod(String name, Class<?>... parameterTypes) 方法:获取指定的方法

    getMethod 也只能获取 公共权限的方法

Class<FullPerson> clazz = FullPerson.class;
// 创建运行时类的对象
FullPerson person = clazz.newInstance();
// 获取指定方法  第一个参数是方法名称, 后面的参数是 方法的形参类型。
Method display = clazz.getMethod("display", String.class);
System.out.println(display);
// 输出
public java.lang.String com.xgzit.JavaBaseStudy.reflectStudy.FullPerson.display(java.lang.String) throws java.lang.NullPointerException,java.lang.ClassCastException
  • Method getDeclaredMethod(String name, Class<?>... parameterTypes) 获取指定的方法

    这个方法可以获取所有权限的方法。

Class<FullPerson> clazz = FullPerson.class;
// 创建运行时类的对象
FullPerson person = clazz.newInstance();
// 获取指定方法
Method show = clazz.getDeclaredMethod("show", String.class);
System.out.println(show);
// 设置可以获取私有的权限
show.setAccessible(true);
show.invoke(person, "haha");
// 输出
private java.lang.String com.xgzit.JavaBaseStudy.reflectStudy.FullPerson.show(java.lang.String)
我的国籍是haha

同样的,必须调用setAccessible(true)方法才可以使用私有权限的方法。

调用方法是 方法对象show 调用invoke 方法。

Object invoke(Object obj, Object... args)

invoke方法 第一个参数 调用方法的对象,后面是原方法的参数。返回值是原方法的返回值。

下面是静态方法的例子:

Class<FullPerson> clazz = FullPerson.class;
// 创建运行时类的对象
FullPerson person = clazz.newInstance();
// 获取指定方法
Method show = clazz.getDeclaredMethod("staticMethodShow", int.class);
System.out.println(show); 
// 设置可以获取私有的权限
show.setAccessible(true);
show.invoke(person, 5); // 私有静态方法5
int invoke = (int) show.invoke(clazz, 5);//私有静态方法5
System.out.println(invoke);  // 5
​
// 输出
private static int com.xgzit.JavaBaseStudy.reflectStudy.FullPerson.staticMethodShow(int)
私有静态方法5
私有静态方法5
5

show.invoke(person, 5); 是通过对象调用静态方法。show.invoke(clazz, 5)是通过类去调用。

建议使用第二种方式。

事实上,这样调用也可以。直接写null。因为这个是静态方法,不需要对象去调用。show是通过clazz获取了,本身就知道了是通过类对象调用的。

show.invoke(null, 5); //私有静态方法5

1.7.3 指定构造器

  • getConstructor 方法 获取指定的共有权限构造器。

Class<FullPerson> clazz = FullPerson.class;
Constructor<FullPerson> constructor = clazz.getConstructor();
System.out.println(constructor); // public com.xgzit.JavaBaseStudy.reflectStudy.FullPerson()
  • getDeclaredConstructor 获取指定的构造器,忽略权限。

Class<FullPerson> clazz = FullPerson.class;
Constructor<FullPerson> constructor = clazz.getDeclaredConstructor(String.class);
System.out.println(constructor); // private com.xgzit.JavaBaseStudy.reflectStudy.FullPerson(java.lang.String)

如果要使用私有的构造器,也是需要调用setAccessible(true) 方法

Class<FullPerson> clazz = FullPerson.class;
Constructor<FullPerson> constructor = clazz.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
FullPerson xxxx = constructor.newInstance("xxxx");
System.out.println(xxxx);
// 输出
FullPerson{name='xxxx', age=0, id=0}

我们更多的时候用的是 Class对象直接调用 newInstance 调用无参构造器。这样比较通用。

2、动态代理

代理模式前面已经讲过。

主要是静态代理,特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的拓展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。

我们更希望的是使用一个代理类就可以完成全部的代理功能。

那就是动态代理了。在程序运行时根据需要动态的创建目标类的代理对象。

2.1 静态代理

回顾静态代理。

需要一个接口。

public interface ClothFactory {
​
    void produceCloth();
}

一个静态代理类。

public class ProxyClothFactory implements ClothFactory{
​
    private ClothFactory factory;
​
    @Override
    public void produceCloth() {
        System.out.println("代理工厂做前置工作");
        // 被代理类自己干核心工作
        factory.produceCloth();
        System.out.println("代理工厂做收尾工作");
    }
​
    public ProxyClothFactory(ClothFactory clothFactory){
        this.factory = clothFactory;
    }
}

一个被代理类

public class QdClothFactory implements ClothFactory{
​
    @Override
    public void produceCloth() {
        System.out.println("乔丹工厂生产一批运动鞋");
    }
}

使用:

    @Test
    public void test1() {
        QdClothFactory qd = new QdClothFactory();
        ProxyClothFactory proxyClothFactory = new ProxyClothFactory(qd);
        proxyClothFactory.produceCloth();
    }

代理类与被代理类都必须实现同一个接口。

2.2 动态代理

一个接口

public interface Human {
    /** 获取信仰 **/
    String getBelief();
    
    /** 吃饭 **/
    void eat(String food);
}

一个被代理类

public class SuperMan implements Human{
​
    @Override
    public String getBelief() {
        return "超人拯救地球";
    }
​
    @Override
    public void eat(String food) {
        System.out.println("超人喜欢吃"+food);
    }
}
 

一个代理类:

思考如何根据加载到内存的被代理类,动态创建一个代理类?

当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法?

public class ProxyFactory {
​
    /** 调用此方法,返回一个代理类对象 **/
    public static Object getProxyInstance(Object obj) {
        MyInvocationHandler handler = new MyInvocationHandler();
        handler.bind(obj);
        // 使用 Proxy 的 静态方法 newProxyInstance 得到代理类对象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(), handler);
    }
}

上面的代理是一个通用的获取代理类对象的方法。需要接收一个 被代理类的对象。然后调用的是Proxy 类的静态方法 newProxyInstance 获取代理对象。这个newProxyInstance 是JDK为我们提供的API 。来看一下。

   
 @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {   
        //检查 通过的 InvocationHandler 实现类对象不能是 null,否则抛异常
        Objects.requireNonNull(h);
        // 得到所有的 接口
        final Class<?>[] intfs = interfaces.clone();
        // 安全检查
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
​
        // 使用 提供的类加载器 以及 接口,得到一个 相同的类对象
        Class<?> cl = getProxyClass0(loader, intfs);
​
        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            // 获取构造器
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        // 接口数量最大不能超过65535
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
        //如果给定加载程序定义的代理类
        //如果给定的接口存在,这将只返回缓存的副本;
        //否则,它将通过ProxyClassFactory创建代理类
        return proxyClassCache.get(loader, interfaces);
    }

这个方法 newProxyInstance 最终根据 我们提供的 被代理类的类加载器、被代理类实现的接口、以及一个 InvocationHandler 实现类对象为我们动态创建一个代理类对象。

看一下这个 InvocationHandler 接口

public interface InvocationHandler {
​
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

这个接口比较简单,只有一个 invoke 方法。接收3个参数。一个是 对象,第二个是 Method 类对象,第三个是Object数组类型的 参数数组。

当我们使用获取的 代理类对象调用 方法时,就会调用这个接口实现类的invoke方法。

我们需要提供一个这样的 实现类对象。

public class MyInvocationHandler implements InvocationHandler {
​
    /** 保存被代理类对象 **/
    private Object obj;
​
    /** 构造器来接收被代理对象 **/
    public MyInvocationHandler(Object obj){
        this.obj = obj;
    }
    // 当我们通过代理类的对象,调用方法a时,就会自动的来调用这里的invoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
​
        System.out.println("代理类做前置工作");
        // method.invoke 是被代理类自己干核心的活
        Object returnVal = method.invoke(obj, args);
        System.out.println("代理类做收尾工作");
        return returnVal;
    }
}

这个实现类 需要接收 被代理类的对象。因为这里面是需要这个对象调用自己的方法的。

在invoke方法里面,和 静态代理一样,也是可以做一些前置、后置的工作。但是中间一定是 被代理对象自己干活。

Object returnVal = method.invoke(obj, args);

测试一下:

// 代理 superMan
SuperMan superMan = new SuperMan();
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
proxyInstance.eat("美女");
// 代理 qdClothFactory
QdClothFactory qdClothFactory = new QdClothFactory();
ClothFactory instance = (ClothFactory) ProxyFactory.getProxyInstance(qdClothFactory);
instance.produceCloth();

可以看到,通用的 获取代理对象方法getProxyInstance 不仅可以代理 superMan 也可以代理 qdClothFactory

使用动态代理就无需创建多个代理类了。

实时上,我们发现 在 ProxyFactory 类中, 我们创建的 InvocationHandler 实现类对象 只用了一次。是不是可以使用接口的匿名实现类呢?是可以的。进行代码改造

public class ProxyFactory {
​
    /** 调用此方法,返回一个代理类对象 **/
    public static Object getProxyInstance(Object obj) {
        // 使用 Proxy 的 静态方法 newProxyInstance 得到代理类对象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(), new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        return method.invoke(obj, args);
                    }
                });
    }
}

这就完了吗? 接下来我们会了解 JDK8的新特性。使用 lambda表达式。可以再次进行改造。

public class ProxyFactory {
​
    /** 调用此方法,返回一个代理类对象 **/
    public static Object getProxyInstance(Object obj) {
        // 使用 Proxy 的 静态方法 newProxyInstance 得到代理类对象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(), (proxy, method, args) -> method.invoke(obj, args));
    }
}

lambda 表达式 的详情见下一节内容。

2.3 AOP 与动态代理 简述

前面介绍的 Proxy 和 InvocationHandler 很难看出这种动态代理的优势,下面我们来看看Spring中的动态代理。

情形:

假设在 3个地方都出现了相同的代码段,我们一般想到的是通过抽取相同的代码变成一个方法A,然后在三个地方都调用这个方法。

这是我们最常见的处理相同代码的方式。但是这样 三个代码段又与抽取的方法A进行耦合了。

最理想的效果是 在这三个代码段中都可以执行方法A,又不需要在程序中以硬编码的方式直接调用。

 

例子:

写一个通用方法类

public class GeneralMethod {
​
    public void method1(){
        System.out.println("通用方法1");
    }
​
    public void method2(){
        System.out.println("通用方法2");
    }
}
​

改造一下 ProxyFactory 类。

public class ProxyFactory {
​
    /** 调用此方法,返回一个代理类对象 **/
    public static Object getProxyInstance(Object obj) {
        // 使用 Proxy 的 静态方法 newProxyInstance 得到代理类对象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(), (proxy, method, args) -> {
                    GeneralMethod humanUtil = new GeneralMethod();
                    humanUtil.method1();
                    Object invoke = method.invoke(obj, args);
                    humanUtil.method2();
                    return invoke;
                });
    }
}

其实没什么区别,就是在 被代理类的前面后面干了一点通用的事。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值