背景:以前都是零零散散的去学习反射,对于用法很模糊、还有原理也是只知一二,最近在学习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提供了大量能使我们快速便捷地处理数据的函数和方法。
    
   
 
