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