一文入门Java之【反射机制】

一、什么是Java反射机制(Reflection)

  • Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
  • 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
    在这里插入图片描述
    Java反射机制提供的功能
  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理

二、 Class类与创建Class的实例

1. Class 类

在Class类中有public final native Class<?> getClass();。可以通过对象反射求出类的名称。

Java源文件在这里插入图片描述
Class 类
一个Class 对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息

  • Class本身也是一个类
  • Class 对象只能由系统建立对象
  • 一个加载的类在JVM 中只会有一个Class实例
  • 一个Class对象对应的是一个加载到JVM中的一个.class文件
  • 每个类的实例都会记得自己是由哪个Class 实例所生成
  • 通过Class可以完整地得到一个类中的所有被加载的结构
  • Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象

Class类的常用方法
在这里插入图片描述

2.获取Class类的实例

共四种方式

@Test public void test() throws ClassNotFoundException {
        //1. 调用运行时类的属性:.class
        Class c1 = String.class;
        System.out.println("c1:" + c1);
        //2. 通过运行时类的对象,调用getClass()
        String s1 = "";
        Class c2 = s1.getClass();
        System.out.println("c2:" + c2);
        //3. 调用Class的静态方法:forName(String classPath)
        Class c3 = Class.forName("java.lang.Integer");
        System.out.println("c3:" + c3);
        //4. 使用类加载器 ClassLoader
        ClassLoader classLoader = reflectDemo.class.getClassLoader();
        Class c4 = classLoader.loadClass("java.lang.Object");
        System.out.println("c4:" + c4);
    }

哪些类型可以有Class对象?
在这里插入图片描述

三、 类的加载与ClassLoader的理解

1. 类加载的三个步骤

在这里插入图片描述
加载:

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。

链接:

将Java类的二进制代码合并到JVM的运行状态之中的过程。
验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

初始化:

执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。

在这里插入图片描述
类加载器的作用:

  • 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
  • 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象

2. ClassLoader

类加载器作用是用来把类(class)装载进内存的。JVM 规范定义了如下类型的类的加载器。
在这里插入图片描述

  • 引导类加载器:用C++编写的,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取
  • 扩展类加载器:负责jre/lib/ext目录下的jar包或–D java.ext.dirs指定目录下的jar包装入工作库
  • 系统类加载器:负责java –classpath或–D java.class.path所指的目录下的类与jar包装入工作,是最常用的加载器

获取加载器

  @Test
    public void getClassLoader() throws ClassNotFoundException {
        // 1.获取一个系统类加载器
        ClassLoader classloader= ClassLoader.getSystemClassLoader();
        System.out.println(classloader);

        // 2. 获取系统类加载器的父类加载器,即扩展类加载器
        classloader= classloader.getParent();
        System.out.println(classloader);

        // 3. 获取扩展类加载器的父类加载器,即引导类加载器
        classloader= classloader.getParent();
        System.out.println(classloader);
        // 4. 测试当前类由哪个类加载器进行加载
        classloader= Class.forName("com.aaa.reflect.reflectDemo").getClassLoader();
        System.out.println(classloader);

        // 5. 测试JDK提供的Object类由哪个类加载器加载
        classloader= Class.forName("java.lang.Object").getClassLoader();
        System.out.println(classloader);

        //6. 关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路径下的指定文件的输入流
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("jdbc.properties");
        System.out.println(inputStream);

    }

四、创建运行时类的对象

调用Class对象的newInstance()方法

  1. 类必须有一个无参数的构造器。
  2. 类的构造器的访问权限需要足够。

没有无参的构造器也可以创建对象
在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。

  • . 步骤如下:
  1. 通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器
  2. 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
  3. 通过Constructor实例化对象。

创建步骤

  1. 根据全类名获取对应的Class对象
  2. 调用指定参数结构的构造器,生成Constructor的实例
  3. 通过Constructor的实例创建对应类的对象,并初始化类属性

代码实现

@Test 
    public void test3() throws Exception {
        //1. 根据全类名获取对应的Class对象
        Class clazz = Class.forName("java.lang.String");
        //2. 调用指定参数结构的构造器,生成Constructor的实例
        Constructor constructor = clazz.getConstructor(String.class);
        //3. 通过Constructor的实例创建对应类的对象,并初始化类属性
        String str = (String)constructor.newInstance("string");
        System.out.println(str);
    }

五、获取运行时类的完整结构

使用反射获取的内容

  1. 实现的全部接口public Class<?>[] getInterfaces(),确定此对象所表示的类或接口实现的接口。
  2. 所继承的父类
    public Class<? Super T> getSuperclass(),返回表示此Class 所表示的实体(类、接口、基本类型)的父类的Class。
  3. 全部的构造器
    public Constructor<T>[] getConstructors()返回此Class 对象所表示的类的所有public构造方法。
    public Constructor<T>[] getDeclaredConstructors()返回此Class 对象表示的类声明的所有构造方法。

Constructor类中:

  • 取得修饰符:public intgetModifiers();
  • 取得方法名称: public String getName();
  • 取得参数的类型:public Class<?>[] getParameterTypes();
  1. 全部的方法
    public Method[] getDeclaredMethods()返回此Class对象所表示的类或接口的全部方法
    public Method[] getMethods() 返回此Class对象所表示的类或接口的public的方法

Method类中:

  • public Class<?> getReturnType()取得全部的返回值
  • public Class<?>[] getParameterTypes()取得全部的参数
  • public int getModifiers()取得修饰符
  • public Class<?>[] getExceptionTypes()取得异常信息
  1. 全部的Field
    public Field[] getFields() 返回此Class对象所表示的类或接口的public的Field。
    public Field[] getDeclaredFields() 返回此Class对象所表示的类或接口的全部Field。

Field方法中:

  • public intgetModifiers() 以整数形式返回此Field的修饰符
  • public Class<?> getType() 得到Field的属性类型
  • public String getName() 返回Field的名称。
  1. Annotation相关
    getAnnotation(Class<T> annotationClass) 获取Annocation
    getDeclaredAnnotations()
  2. 泛型相关
    Type getGenericSuperclass() 获取父类泛型类型
    ParameterizedType 泛型类型
    getActualTypeArguments()获取实际的泛型类型参数数组
  3. 类所在的包 Package getPackage()

代码实现

@Test public void test4() throws Exception {
        Class clazz = Class.forName("java.lang.String");

        System.out.println("全部接口:" + Arrays.toString(clazz.getInterfaces()));
        System.out.println("所继承的父类:" + clazz.getSuperclass());
        System.out.println("全部的构造器:" + Arrays.toString(clazz.getConstructors()));
        System.out.println("全部的方法:" + Arrays.toString(clazz.getMethods()));
        System.out.println("全部的Field:"+ Arrays.toString(clazz.getFields()));
        System.out.println("Annotation:"+ clazz.getDeclaredAnnotations());
        System.out.println("父类泛型:" + clazz.getGenericSuperclass());
        System.out.println("类所在的包:" + clazz.getPackage());
    }

六、调用运行时类的指定结构

1. 调用指定方法

通过反射,调用类中的方法,通过Method类完成。步骤:

  1. 通过Class类的getMethod(String name,Class…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
  2. 使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。
    在这里插入图片描述
Object invoke(Object obj, Object … args)
  1. Object 对应原方法的返回值,若原方法无返回值,此时返回null
  2. 若原方法若为静态方法,此时形参Object obj可为null
  3. 若原方法形参列表为空,则Object[] args为null
  4. 若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。

2. 调用指定属性

Field类

在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作。

  • 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,Objectvalue) 设置指定对象obj上此Field的属性内容

代码示例

import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionTest {
    private int num = 100;

    private int tt(String str){
        System.out.println(str);
        return 99;
    }

    private static void ss(){
        System.out.println("hello static");
    }


    /**
     * 如何操作运行时类中的指定的属性
     */
    @Test
    public void testField1() throws Exception {
        Class clazz = ReflectionTest.class;

        //创建运行时类的对象
        ReflectionTest s = (ReflectionTest) clazz.newInstance();


        System.out.println("-------------------属性--------------------");
        //1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
        Field value = clazz.getDeclaredField("num");

        //2.保证当前属性是可访问的
        value.setAccessible(true);
        //3.获取、设置指定对象的此属性值
        value.set(s,1);

        System.out.println(value.get(s));
        System.out.println("-------------------方法--------------------");
        // 1. 获取方法  getDeclaredMethod():参数1 :指明获取的方法的名称  参数2:指明获取的方法的形参列表
        Method tt = clazz.getDeclaredMethod("tt",String.class);
        //2. 保证当前方法是可访问的
        tt.setAccessible(true);
        /** 3. 调用方法的invoke():参数1:方法的调用者  参数2:给方法形参赋值的实参
        invoke()的返回值即为对应类中调用的方法的返回值。
         */
        Object returnValue = tt.invoke(s,"hello tt! ");
        System.out.println(returnValue);
        System.out.println("-------------------静态方法--------------------");

        //如何调用静态方法
        Method ss = clazz.getDeclaredMethod("ss");
        ss.setAccessible(true);
        Object result = ss.invoke(s);
        System.out.println(result);

        System.out.println("-------------------构造方法--------------------");
        Class clazz1 = Integer.class;
        //创建运行时类的对象
        //1. 获取指定的构造器
        Constructor constructor = clazz1.getDeclaredConstructor(String.class);
        //2.保证此构造器是可访问的
        constructor.setAccessible(true);
        //3. 调用此构造器创建运行时类的对象
        Integer results = (Integer) constructor.newInstance("50");
        System.out.println(results);
    }

}

七、反射的应用:动态代理

1.代理设计模式的原理:

使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

2. Java动态代理相关API Proxy

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

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

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

3. 动态代理与AOP(Aspect Orient Programming)

在这里插入图片描述

  • 使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象生成动态代理
  • 这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象,AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理

4. 动态代理步骤

  1. 创建一个实现接口InvocationHandler的类,它必须实现invoke方法,以完成代理的具体操作。
  2. 创建被代理的类以及接口
  3. 通过Proxy的静态方法创建一个Subject接口代理
  4. 通过Subject代理调用RealSubject实现类的方法

5. 代码实现

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

public class ProxyTest {
    public static void main(String[] args) {
        Porsche porsche = new Porsche();
        //proxyInstance:代理类的对象
        Car proxyInstance = (Car)ProxyFactory.getProxyInstance(porsche);
        //当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
        String belief = proxyInstance.getName();
        System.out.println(belief);
        proxyInstance.buy(2000000);
    }
}

interface Car {

    String getName();

    void buy(int money);

}

//被代理类
class Porsche implements Car {

    @Override public String getName() {
        return "保时捷";
    }

    @Override public void buy(int money) {
        System.out.println("买车花费:" + money);
    }
}

class CarUtil {

    public void method1() {
        System.out.println("--------------------通用方法1--------------------");
    }

    public void method2() {
        System.out.println("--------------------通用方法2--------------------");
    }

}

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

        handler.bind(obj);

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

}

class MyInvocationHandler implements InvocationHandler {

    private Object obj;//需要使用被代理类的对象进行赋值

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

    //当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
    //将被代理类要执行的方法a的功能就声明在invoke()中
    @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        CarUtil util = new CarUtil();
        util.method1();

        //method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
        //obj:被代理类的对象
        Object returnValue = method.invoke(obj, args);

        util.method2();

        //上述方法的返回值就作为当前类中的invoke()的返回值。
        return returnValue;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值