15. Java反射机制

Java反射机制概述

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

动态语言:
在运行时代码可以根据某些条件改变自身结构。
主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。

静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。

Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。

反射相关的主要API

  • java.lang.Class:代表一个类
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Constructor:代表类的构造器...

理解Class类并获取Class的实例

在Object类中定义了以下的方法,此方法
将被所有子类继承:
public final Class getClass()

Class 类

关于java.lang.Class类的理解

  • Class本身也是一个类
  • Class 对象只能由系统建立对象
  • 一个加载的类在 JVM 中只会有一个Class实例
  1. 类的加载过程:
    1. 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
    2. 接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。
    3. 加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。
  2. 换句话说,Class的实例就对应着一个运行时类。
  3. 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。

c303ae33c6776a91b6894ac8f8adf699.png

获取Class类的实例(四种方法)

方式一:调用运行时类的属性:.class

前提:若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高

Class clazz1 = Person.class;
System.out.println(clazz1);

方式二:通过运行时类的对象,调用getClass()

前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象

Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);

方式三:调用Class的静态方法:forName(String classPath)

前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException

Class clazz3 = Class.forName("com.atguigu.java.Person");
System.out.println(clazz3);

方式四:使用类的加载器:ClassLoader (了解)

ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
System.out.println(clazz1 == clazz2);//true
System.out.println(clazz1 == clazz3);//true
System.out.println(clazz1 == clazz4);//true

以下类型都有Class对象:
(1)class:
外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2)interface:接口
(3)[]:数组
(4)enum:枚举
(5)annotation:注解@interface
(6)primitive type:基本数据类型
(7)void

类的加载与ClassLoader的理解

5db3ad7f279b84dddcd2256411c7136d.png

575b7b02509593f848b896b1a636f0d7.png

类加载器的作用:

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

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

cb6f0988f977970e174b5bc1157ee2ff.png

public class ClassTest {
    public static void main(String[] args) {
        //获取一个系统类加载器
        ClassLoader cl=ClassTest.class.getClassLoader();
        System.out.println(cl);
        //获取系统类加载器的父类加载器,即扩展类加载器
        cl=cl.getParent();
        System.out.println(cl);
        //获取扩展类加载器的父类加载器,即引导类加载器
        cl=cl.getParent();
        System.out.println(cl);
        
        cl=String.class.getClassLoader();
        System.out.println(cl);
    }
}

利用Properties读取配置文件

public void test2() throws Exception {
    Properties pros = new Properties();
    //此时的文件默认在当前的module下。
    //读取配置文件的方式一:
//        FileInputStream fis = new FileInputStream("jdbc.properties");
//        FileInputStream fis = new FileInputStream("src\\jdbc1.properties");
//        pros.load(fis);

    //读取配置文件的方式二:使用ClassLoader
    //配置文件默认识别为:当前module的src下
    ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
    InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
    pros.load(is);

    String user = pros.getProperty("user");
    String password = pros.getProperty("password");
    System.out.println("user = " + user + ",password = " + password);
}

创建运行时类的对象

newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。

要想此方法正常的创建运行时类的对象,要求:

  1. 类必须有一个无参数的构造器。
  2. 类的构造器的访问权限需要足够。通常,设置为public。

在javabean中要求提供一个public的空参构造器。原因:
1.便于通过反射,创建运行时类的对象
2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器

Class<Person> clazz = Person.class;
Person p1 = clazz.newInstance();
System.out.println(p1);//Person{name='null', age=0}

根据全类名获取对应的Class对象:

Class clazz =  Class.forName(classPath);
Object a=clazz.newInstance()

反射的动态性示例:

public void Test() {
    String path = null;
    for (int i = 0; i < 3; i++) {
        switch (new Random().nextInt(3)) {
            case 0:
                path = "g_1.Person";
                break;
            case 1:
                path = "g_1.ClassTest";
                break;
            case 2:
                path = "java.util.Date";
                break;
        }
        try {
            System.out.println(getInstance(path));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public Object getInstance(String classPath) throws Exception {
    return Class.forName(classPath).newInstance();
}

获取运行时类的完整结构

使用反射可以取得:

  1. 实现的全部接口
  2. 所继承的父类
  3. 全部的构造器
  4. 全部的方法
  5. 全部的Field

操作运行时类中的指定的属性

  1. (通常不采用此方法)要求运行时类中属性声明为public
public void Test() throws Exception {
    Class<Person> clazz= Person.class;
    Person p=clazz.newInstance();
    Field id=clazz.getField("id");
    id.set(p,1001);
    System.out.println(id.get(p));//1001
}
  1. 此方法可以访问该类中声明的所有方法(无视权限修饰符),需要掌握
public void Test2() throws Exception {
    Class<Person> clazz = Person.class;
    Person p = clazz.newInstance();
    Field name = clazz.getDeclaredField("name");
    //保证当前属性是可访问的
    name.setAccessible(true);
    name.set(p, "Tom");
    System.out.println(name.get(p));
}

其余结构的获取都是这两类方法,以下不在赘述

操作运行时类中的指定的方法

Method country =clazz.getMethod("country",String.class);
country.invoke(p,"chinese");//I am chinese
System.out.println(country);//public java.lang.String g_1.Person.country(java.lang.String)
Method country = clazz.getDeclaredMethod("country", String.class);
country.setAccessible(true);
country.invoke(p, "chinese");
System.out.println(country);

调用静态方法也是这两种模式,只是在invoke()的使用有两种写法

showDesc.invoke(Person.class);//=showDesc.invoke(null);

调用运行时类中的指定的构造器

Constructor<Person> cs=clazz.getConstructor();
Person p1=cs.newInstance();
System.out.println(p1);//person{name='null', age=0, ID=0}
Constructor<Person> cs=clazz.getDeclaredConstructor(String.class,int.class,int.class);
Person p1=cs.newInstance("jom",20,1001);
System.out.println(p1);//person{name='jom', age=20, ID=1001}

获取其余结构方法:

1.实现的全部接口

  • public Class<?>[] getInterfaces()

确定此对象所表示的类或接口实现的接口。

2.所继承的父类

获取父类泛型类型:Type getGenericSuperclass()
泛型类型:ParameterizedType
获取实际的泛型类型参数数组:getActualTypeArguments()

获取运行时类的父类

  • public Class<? Super T> getSuperclass()

返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的Class。

Class<Person> clazz = Person.class;
Class<? super Person> clazz1 = clazz.getSuperclass();
System.out.println(clazz1);//class g_1.Animal

获取运行时类的带泛型的父类

获取父类泛型类型:Type getGenericSuperclass()
泛型类型:ParameterizedType
获取实际的泛型类型参数数组:getActualTypeArguments()

Type type=clazz.getGenericSuperclass();
System.out.println(type);//g_1.Animal<java.lang.String>

获取运行时类的带泛型的父类的泛型

ParameterizedType pt= (ParameterizedType) type;
Type[] types = pt.getActualTypeArguments();
System.out.println(types[0].getTypeName());//java.lang.String

3.全部的构造器

  • public Constructor[] getConstructors()
    返回此 Class 对象所表示的类的所有public构造方法。
  • public Constructor[] getDeclaredConstructors()
    返回此 Class 对象表示的类声明的所有构造方法。

Constructor类中:

  • 取得修饰符: public int getModifiers();
  • 取得方法名称: public String getName();
  • 取得参数的类型:public Class<?>[] getParameterTypes();

4. 全部的方法

  • public Method[] getDeclaredMethods()
    返回此Class对象所表示的类或接口的全部方法
  • public Method[] getMethods()
    返回此Class对象所表示的类或接口的public的方法

Method类中:

  • public Class<?> getReturnType()取得全部的返回值
  • public Class<?>[] getParameterTypes()取得全部的参数
  • public int getModifiers()取得修饰符
  • public Class<?>[] getExceptionTypes()取得异常信息

5.全部的Field

  • public Field[] getFields()
    返回此Class对象所表示的类或接口的public的Field。
  • public Field[] getDeclaredFields()
    返回此Class对象所表示的类或接口的全部Field。

Field类中:

  • public int getModifiers() 以整数形式返回此Field的修饰符
  • public Class<?> getType() 得到Field的属性类型
  • public String getName() 返回Field的名称。

6.Annotation(注解)相关

  • get Annotation(Class<T> annotationClass)
  • getDeclaredAnnotations()

7.类所在的包

  • Package getPackage()

调用运行时类的指定结构

1.调用指定方法

  1. getMethod(String name,Class…parameterTypes)
    取得一个Method对象,并设置此方法操作时所需要的参数类型。
  2. Object invoke(Object obj, Object[] args)
    向方法中传递要设置的obj对象的参数信息。

说明:
1.Object 对应原方法的返回值,若原方法无返回值,此时返回null
2.若原方法若为静态方法,此时形参Object obj可为null
3.若原方法形参列表为空,则Object[] args为null
4.若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。

具体举例见“操作运行时类中的指定的方法”

2.调用指定属性

  • public Field getField(String name) 返回此Class对象表示的类或接口的指定的public的Field。
  • public Field getDeclaredField(String name)返回此Class对象表示的类或接口的指定的Field。

在Field中:

  • public Object get(Object obj) 取得指定对象obj上此Field的属性内容
  • public void set(Object obj,Object value) 设置指定对象obj上此Field的属性内容

关于setAccessible方法的使用

  1. Method和Field、Constructor对象都有setAccessible()方法。
  2. setAccessible启动和禁用访问安全检查的开关。
  3. 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
    • 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
    • 使得原本无法访问的私有成员也可以访问
  4. 参数值为false则指示反射的对象应该实施Java语言访问检查。

反射的应用:动态代理

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

动态代理使用场合: 调试、远程方法调用

Java动态代理相关API

Proxy :专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。

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

  • static Class getProxyClass(ClassLoader loader, Class... interfaces) 创建一个动态代理类所对应的Class对象
  • static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 直接创建一个动态代理对象

ClassLoader loader:类加载器
Class<?>[] interfaces:得到被代理类实现的全部接口
InvocationHandler h:得到InvocationHandler接口的实现类实例

动态代理步骤

  1. 创建一个实现接口InvocationHandler的类,它必须实现invoke方法,以完成代理的具体操作。
  2. 创建被代理的类以及接口
  3. 通过Proxy的静态方法创建接口代理对象
  4. 通过 Subject代理调用RealSubject实现类的方法
public class ProxyTest {
    @Test
    public void Test() {
        SingStar ss = new SingStar();
		System.out.println(ss);//g_1.SingStar@4f8e5cde
        Star pit = (Star) ProxyAgent.getProxyInstance(ss);
        System.out.println(pit);//g_1.SingStar@4f8e5cde
        pit.perform();//歌星会唱歌
        People peo = new People();
        Creature pit1 = (Creature)ProxyAgent.getProxyInstance(peo);
        System.out.println(pit1);//g_1.People@504bae78
        pit1.breath();//人类需要呼吸
    }
}

/*
要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
 */
class MIHandler implements InvocationHandler {
    private Object obj;//需要使用被代理类的对象进行赋值

    public void connect(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
        //obj:被代理类的对象
        return method.invoke(obj, args);//执行方法并将地址返回
    }
}

class ProxyAgent {
    //调用此方法,返回一个代理类的对象。解决问题一
    public static Object getProxyInstance(Object obj) {//obj:被代理类的对象
        MIHandler handler = new MIHandler();

        handler.connect(obj);

        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
    }
}

interface Star {
    void perform();

}

//被代理类
class SingStar implements Star {
    @Override
    public void perform() {
        System.out.println("歌星会唱歌");
    }
}

interface Creature {
    void breath();
}

class People implements Creature {

    @Override
    public void breath() {
        System.out.println("人类需要呼吸");
    }
}

创建的动态代理对象调用的是 MIHandler 中重写的 invoke() 方法

动态代理与AOP(Aspect Orient Programming)

AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理

posted @ 2023-09-30 16:24  LemonPuer  阅读(16)  评论(0)    收藏  举报