JAVA反射

背景:以前都是零零散散的去学习反射,对于用法很模糊、还有原理也是只知一二,最近在学习Sprint源码,IOC容器中创建对象都是使用反射进行创建对象的,还有动态代理也是使用了反射,足以证明java反射对于以后的高阶学习是一个极其重要的部分。所以这期博客用来整理JAVA反射相关的知识。


整理将用,1、JAVA反射原理;2、用法;3、使用场景 。这几个纬度进行整理!


为什么要用JAVA反射

在框架代码和工具代码中,这类项目往往对于灵活性要求很高,合理运用能在使用框架时优化出更好的性能

JAVA反射原理

  • 反射是Java中的一个重要的特性,使用反射可以在运行时动态生成对象--.Class、获取对象属性以及调用对象方法。
  • 与编译期的静态行为相对,所有的静态型操作都在编译期完成,而反射的所有行为基本都是在运行时进行的,这是一个很重要的特性。它让Java有了动态特性,可以让程序更加灵活强大。

反射运行流程

  • 准备阶段:在编译期装载所有的类,类的元信息保存在Class对象中,一个类对应一个Class对象。这也是访问这个类的入口。
  • 获取Class对象: 通过调用x.class/x.getClass()/Class.forName() 获取x的Class对象
  • 进行实际的反射:通过clz对象获取Field/Method/Constructor对象进行进一步操作

类加载过程

  • 补充:传统的JAVA创建对象的5个过程是: 加载–> 验证–> 创建–>解析–>初始化

在这里插入图片描述

  • 预备节点:.java文件经过.javac转为.class字节码

  • 加载:(官方解释)加载阶段是类加载过程的第一个阶段。有三个过程1、在这个阶段,通过一个类的全限定名获取此类的二进制字节流,2、将字节流所代表的静态存储结构,转化为方法区的运行时数据结构,3、在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问 入口。(书上说的内存,我理解为堆区内存)------ 入口 我的理解是,不能直接访问方法区对象,得通过堆区创建的Class对象必须访问方法区中这个模板的各种数据结构。 该过程简单来说,就是把代码数据加载到内存中
    在这里插入图片描述

  • 验证:确保java加载进内存的二进制文件符合JVM的加载规范,并且不会危害虚拟机的自身安全。

  • 准备:完成字节码检验后,JVM开始为类变量分配内存初始化。1、内存分配:java中有被static修饰的类变量、和未被static修饰的类成员变量。在准备阶段,JVM只为类变量进行内存分配,而类成员变量不会被分配内存;2、初始化:在准备阶段,类变量被初始化为“零值”。比如 public static int sector = 3;sector分配的是0而不是3。但如果一个变量是常量(被 static final 修饰)的话,那么会被赋值期望的值。public static final int number = 3; 被赋值为3。

    • 思考,为什么常量会被赋值?? 因为final修饰的变量代表不可改变,那么必须一开始就赋值其想要的值;未被final修饰的类变量可在初始化、运行阶段被改变值。
  • 解析:这个阶段的主要任务是将其在常量池中的符号引用替换成直接其在内存中的直接引用(地址引用)举个例子来说,现在调用方法hello(),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。

  • 初始化:JVM用赋值或者缺省值将静态变量进行初始化,并执行静态初始化程序(static块中的代码)—通过(),这是javac编译器的产物。初始化发生在执行main方法之前,但在指定的类初始化之前他的父类必须先初始化。且到了这个阶段java程序代码才真正开始执行。JVM会根据语句执行顺序对类对象进行初始化。

JAVA反射的基本使用

案例

public class Apple {
    private static int total = 20;
    private volatile int  price;

    public int getPrice() {
        return price;
    }

    public void setPrice(int pricel) {
        this.price = pricel;
    }

通过反射获取Class对象

Clazz clz= Class.forname("xxxx");//根据全限定名获取对象的Class对象

通过反射调用Class对象的方法

调用的话得通过Class对象生成对应的具体对象

 Constructor appConstructor = clz.getConstructor();// 获得公共的无参构造函数
 Object o = appConstructor.newInstance(); //使用newInstance实力化对象
 Method setPriceMethod=clz.getMethod("setPrice",int.class);//获取Class对象的方法 
 setPriceMethod.invok(o,3)//. invok对反射出来的对象o进行方法调用
 Method getPriceMethod = clz.getMethod("getPrice");
 System.out.println("Appple price" + getPriceMethod.invoke(o));
 buyAppleMethod.invoke(o,"唐经",4);
//两个参数的含义1、获取方法的名字,2、参数的类型


 

总结

从这个简单的例子可以看出,一般情况下我们使用反射获取一个对象的步骤:

  • 1、获取类的 Class 对象实例
    Class clz = Class.forName(“com.zhenai.api.Apple”);
  • 2、根据 Class 对象实例获取 Constructor 对象
    Constructor appleConstructor = clz.getConstructor();
  • 3、使用 Constructor 对象的 newInstance 方法获取反射类对象
    Object appleObj = appleConstructor.newInstance();

而如果要调用某一个方法,则需要经过下面的步骤:

  • 4、获取方法的 Method 对象
    Method setPriceMethod = clz.getMethod(“setPrice”, int.class);
  • 5、利用 invoke 方法调用方法
    setPriceMethod.invoke(appleObj, 14);

常用反射API

获取Class对象的三种方法 (Class.forName()/String.class/str.getClass();)

第一种,使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。

Class clz = Class.forName(“java.lang.String”);
第二种,使用 .class 方法。

这种方法只适合在编译前就知道操作的 Class。

Class clz = String.class;
第三种,使用类对象的 getClass() 方法。

String str = new String(“Hello”);
Class clz = str.getClass();

通过反射创建类对象(1、通过Class对象的newInstance()2、通过 Constructor 对象的 newInstance() 方法。)

第一种:通过 Class 对象的 newInstance() 方法。

Class clz = Apple.class;
Apple apple = (Apple)clz.newInstance();

第二种:通过 Constructor 对象的 newInstance() 方法

Class clz = Apple.class;
Constructor constructor = clz.getConstructor();
Apple apple = (Apple)constructor.newInstance();

通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。

Class clz = Apple.class;
Constructor constructor = clz.getConstructor(String.class, int.class);
Apple apple = (Apple)constructor.newInstance("红富士", 15);

通过反射获取类属性(Class 对象的 getFields() 方法可以获取 Class 类的属性,碰到私有属性是得用getDeclaredFields() )

我们通过 Class 对象的 getFields() 方法可以获取 Class 类的属性,但无法获取私有属性。

Class clz = Apple.class;
Field[] fields = clz.getFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

输出结果是:

price
而如果使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性:

Class clz = Apple.class;
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

输出结果是:

name
price

Spring 在JDK动态代理中的应用

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理控制对某个对象的访问

  • 代理的作用: 代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

  • 与静态代理相比:1、静态代理只能代理某一类型接口的实例,不能代理任意接口任意方法的操作(静态代理的局限性),动态代理可以任意代理,2、静态代理需要手动去编写,动态代理可以自动编写

  • JDK动态代理的应用场景:1、事务处理;2、权限管理;3、日志手机;4、AOP切面

动态代理的实现

1、提供一个接口,和该接口的实现类,

该接口定义了被代理类对象的类型

//提供一个接口,继承该接口的类都可以被代理
interface Subject {

    void test();
}
//提供一个实现类
class SubjectImpl implements Subject {

    @Override
    public void test() {
        System.out.println("This is test method");
    }
}

2、通过实现InvocationHandler接口,在invoke方法中实现代理逻辑

class SubjectInvocationHandler implements InvocationHandler {

    private Subject target;

    public SubjectInvocationHandler(Subject subject) {
        this.target = subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("before method!");

        Object result = method.invoke(target, args); // target对象 调用 Method方法

        System.out.println("after method!");

        return result;
    }
}

3、通过Proxy的newProxyInstance方法生成代理类,这里主要是根据被代理类的接口类型,通过反射创建代理类;

public class UseJDKProxyDemo  {
    public static void main(String args[]) {

        Subject subject = new SubjectImpl();
                                                        //实现类的类加载器                              被代理对象的接口                       InvocationHandler  拦截器类实例(增强处理)
        Subject proxy = (Subject) Proxy.newProxyInstance(SubjectImpl.class.getClassLoader(), SubjectImpl.class.getInterfaces(), new SubjectInvocationHandler(subject));
        proxy.test();

        System.out.println(proxy);
    }
}

源码(看了很久没看懂多少)

总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值