目录
一、Java反射机制概述
补充:动态语言 vs 静态语言
1、Java反射机制研究及应用
2、反射相关的主要API
二、理解Class类并获取Class的实例
1、Class 类
对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
- Class本身也是一个类
- Class 对象只能由系统建立对象
- 一个加载的类在 JVM 中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪个 Class 实例所生成
- 通过Class可以完整地得到一个类中的所有被加载的结构
- Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象
2、Class类的常用方法
1、 获取Class类的实例(四种方法)
哪些类型可以有Class对象?
代码示例:
Man类:
public class Man {
private String name;
public int age;
public Man() {
}
public Man(String name, int age) {
this.name = name;
this.age = age;
}
private Man(String name) {
this.name = name;
}
@Override
public String toString() {
return "Man{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void show(){
System.out.println("你好,我是一个人");
}
private String showNation(String nation) {
System.out.println("我国籍是:" + nation);
return nation;
}
}
ReflectionTest类:
public class ReflectionTest {
// 反射之前,对于Man的操作
@Test
public void test(){
// 1、创建Man类的对象
Man man = new Man("jack",10);
// 2、通过对象,调用其内部的属性,方法
man.age=12;
System.out.println(man.toString());
man.show();
//在Man类外部,不可以通过Man类对象调用其内部私有结果
}
// 反射之后,对于Man的操作
@Test
public void test1() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Class clazz = Man.class;
// 1、通过反射,创建Man类的对象
Constructor cons = clazz.getConstructor(String.class, int.class);
Object obj = cons.newInstance("jack",10);
Man m = (Man) obj;
System.out.println(obj.toString());
// 2、通过反射,调用对象指定的属性,方法
Field age = clazz.getDeclaredField("age");
age.set(m,19);
System.out.println(m.toString());
// 调用方法
Method show = clazz.getDeclaredMethod("show");
show.invoke(m);
// 通过反射,可以调用Man类的私有结构的,比如:私有的构造器,方法,属性
// 调用私有构造器
Constructor cons1 = clazz.getDeclaredConstructor(String.class);
cons1.setAccessible(true);
Man m1 =(Man) cons1.newInstance("Jerry");
System.out.println(m1);
// 调用私有的属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(m1,"xiaohua");
System.out.println(m1);
// 调用私有的方法
Method showNation = clazz.getDeclaredMethod("showNation", String.class);
showNation.setAccessible(true);
String nation= (String) showNation.invoke(m1,"中国"); //相当于String nation=m1.showBation()
System.out.println(nation);
}
@Test
public void test3() throws ClassNotFoundException {
// 方式一:调用运行时的属性:class
Class clazz1= Man.class;
System.out.println(clazz1); //输出class ex.helllo.er.day29.Man
// 方式二:通过运行时类的对象
Man m1 = new Man();
Class clazz2 = m1.getClass();
System.out.println(clazz2); //输出class ex.helllo.er.day29.Man
// 方式三:调用Class的静态方法:forName(String classPath) (用的多)
Class clazz3 = Class.forName("ex.helllo.er.day29.Man");
// clazz3 = Class.forName("java.lang.String");
System.out.println(clazz3);
System.out.println(clazz1==clazz2);
System.out.println(clazz1==clazz3);
System.out.println(clazz2==clazz3);
// 方式四:使用类的加载器:ClassLoadern (了解)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("ex.helllo.er.day29.Man");
System.out.println(clazz4);
System.out.println(clazz1==clazz4);
}
}
3、类的加载与ClassLoader的理解
a、类的加载过程(了解)
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化:
- 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。
- 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
- 验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
- 初始化:
- 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
类的加载与ClassLoader的理解
public class ClassLoadingTest {
public static void main(String[] args) {
System.out.println(A.m);
}
}
class A {
static {
m = 300;
}
static int m = 100;
}
//第二步:链接结束后m=0
//第三步:初始化后,m的值由<clinit>()方法执行决定
// 这个A的类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按照顺序合并
产生,类似于
// <clinit>(){
// m = 300;
// m = 100;
// }
什么时候会发生类初始化?
类加载器的作用:
- 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
- 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
b、ClassLoader
类加载器作用是用来把类(class)装载进内存的。JVM 规范定义了如下类型的类的加载器。
三、 创建运行时类的对象
/**
* @Description: 通过反射创建对应的运行时类的对象$
* @Author: dyq
* @Date: 2021-07-28$
*/
public class NewInstanceTest {
@Test
public void test() throws IllegalAccessException, InstantiationException {
Class<Man> clazz = Man.class;
// newIntance():调用此方法,创建对应的运行时类的对象,内部调用了运行时的空参构造器
// 要想此方法正常的创建运行时类的对象,要求:
// 1、运行时类必须提供参的构造器
// 2、空参的构造器的访问权限得够。通常,设置为public。
// 在javabean中要求提供一个public的空参构造器,原因:
// 1、便于通过反射,创建运行时类的对象
// 2、便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
Man object= clazz.newInstance();
System.out.println(object);
}
}
四、 获取运行时类的完整结构
1、通过反射获取运行时类的完整结构
Field、Method、Constructor、Superclass、Interface、Annotation
- 实现的全部接口
- 所继承的父类
- 全部的构造器
- 全部的方法
- 全部的Field
五、 调用运行时类的指定结构
六、反射的应用:动态代理
动态代理与AOP(Aspect Orient Programming)
静态代理
/**
* @Description: $静态代理
* @Author: dyq
* @Date: $
*/
interface ClothFactory{
void produceCloth();
}
//代理类
class ProxyClothFactory implements ClothFactory {
private ClothFactory factory; //用被代理类对象进行实例化
public ProxyClothFactory(ClothFactory factory){
this.factory =factory;
}
@Override
public void produceCloth(){
System.out.println("代理工厂做一些准备工作");
factory.produceCloth();
System.out.println("代理工厂做一些后续的收尾工作");
}
}
//被代理类
class NikeClothFactory implements ClothFactory{
@Override
public void produceCloth() {
System.out.println("Nike工厂生产一批运动服");
}
}
public class StaticProxyTest{
public static void main(String[] args) {
// 创建被代理类对象
NikeClothFactory nike = new NikeClothFactory();
// 创建代理类的对象
ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nike);
proxyClothFactory.produceCloth();
}
}
动态代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @Description: $动态代理的举例
* @Author: dyq
* @Date: $
*/
interface Human{
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly!";
}
@Override
public void eat(String food) {
System.out.println("我喜欢吃"+food);
}
}
/*要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象
问题二:当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法。
* */
class ProxyFactory{
// 调用此方法,返回一个代理类对象,解决问题一
public static Object getProxyYInstance(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时,就会自动调用如下的方法:incoke()
// 将被代理类要执行的方法a的功能就声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
// object:被代理类的对象
Object returnValue = method.invoke(obj,args);
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superman = new SuperMan();
// ProxyYInstance:代理类的对象
Human proxyYInstance = (Human) ProxyFactory.getProxyYInstance(superman);
// 当通过代理类对象调用的方法时,会自动的调用被代理类中同名的方法
proxyYInstance.getBelief();
proxyYInstance.eat("炸鱼");
System.out.println("**********************************");
NikeClothFactory nikeClothFactory = new NikeClothFactory();
ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyYInstance(nikeClothFactory);
proxyClothFactory.produceCloth();
}
}