Java基础-反射

反射

  • 反射的理解
  • Class类的特点
  • 通过反射创建Class类的对象
  • 通过反射解析对应类的结构,获取信息
  • 通过反射创建对应类的对象
  • 通过反射调用类的成员(属性、方法、构造)

关键字:

  • 反射机制
  • 动态语言

一.Java反射机制概述


1.关于类的加载

1.类的加载过程:

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化:

  • 1)类的装载:将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成
  • 2)类的连接:将类的二进制数据合并到JRE中
  • 3)类的初始化:JVM负责对类进行初始化

2.ClassLoader 类加载器:

  • 类加载的作用: 将class文件字节码内容加载到内存中, 并将这些静态数据转换成方法区的运行时数据结构, 然后在堆中生成一个代表这个类的java.lang.Class对象, 作为方法区中类数据的访问入口。

  • 类缓存: 标准的JavaSE类加载器可以按要求查找类, 但一旦某个类被加载到类加载器中, 它将维持加载(缓存) 一段时间。 不过JVM垃圾回收机制可以回收这些Class对象。

  • 类加载器是用来把类(class)装载进内存的。

JVM 规范定义了两种类型的类加载器:启动类加载器(bootstrap)和用户自定义加载器(user-defined class loader)。

JVM在运行时会产生3个类加载器组成的初始化加载器层次结构 ,如下图所示:

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

3.类的加载时期:

  • 编译期加载:又称为静态加载,编译时则加载所有用到的类,如果该类不存在,则直接报编译错误,依赖性太强
  • 运行期加载:又称为动态加载,运行时加载用到的类,如果该类(字节码文件)不存在,则报运行错误,降低了依赖性,提高了维护性

4.类的加载时机:

  • new对象
  • 调用类的静态成员
  • 加载子类
  • 反射

5.类加载器的使用案例:

//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("exer2.ClassloaderDemo").getClassLoader();
System.out.println(classloader);
//5.测试JDK提供的Object类由哪个类加载器加载
classloader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classloader);
//*6.关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路径下的指定文件的输入流
InputStream in = null;
in = this.getClass().getClassLoader().getResourceAsStream("exer2\test.properties");
System.out.println(in);
View Code

2.什么是:Reflection

1.什么是反射:

反射,属于java实现动态语言的关键,可以获取运行期间的类的信息

Reflection(反射)是被视为动态语言的关键。

反射机制:允许程序在运行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

2.Java反射机制提供的功能:

  • 在运行时,判断任意一个对象所属的类
  • 在运行时,构造任意一个类的对象
  • 在运行时,判断任意一个类所具有的成员变量和方法
  • 在运行时,调用任意一个对象的成员变量和方法
  • 生成动态代理

3.反射相关的主要API:

  • java.lang.Class:代表一个类
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Constructor:代表类的构造方法

3.反射的好处

java实现动态语言的关键,可以动态获取一个类的结构信息,并调用成员

1)Java中的软编码:维护性高

//配置文件信息
<class-name>com.atguigu.reflect.Student</class-name>
//拿到类名
String clazzName = xxxx;
//使用反射,创建类的对象、调用方法等等
......
View Code

2)硬编码:维护性差

Person per = new Person();

二.Class类的理解

There is a class named Class.

  • 1 Class本身也是一个类
  • 2 Class 对象只能由系统建立对象,但我们可以通过一些方式获取该对象的引用
  • 3 一个类在 JVM 中只会有一个Class实例 ,因为一个类只可能加载一次
  • 4 一个Class对象对应的是一个加载到JVM中的一个.class文件
  • 5 每个类的对象,都能找到它所属的类型,也就是可以获取对应的Class类的对象
    Class clazz = 对象.getClass();
  • 6 通过Class类对象的引用,可以解剖类的结构,可以获取类的所有结构信息,并且调用方法、属性、构造等

Class 也是一个类:其对象是方法区中对应.class文件的加载

所谓反射,从程序的运行结果来看也很好理解,即:可以通过对象反射出类,以及对类的各种操作。

通过反射:可以解剖使用到的这个类

因为:通过Class可以完整地得到一个类中的完整结构

问题:如何获得Class对象(对应加载到JVM中的.class文件),以及通过Class对象,解剖对应的类?

三.反射的使用

1.通过反射,获取Class类的对象:

问题:如何获得一个Class对象?

获取Class对象的四种方法:

    • 通过全类名字符串,获取Class类的对象:Class.forName()

    • 通过某个具体类的对象获取Class类的对象:对象名.getClass()

    • 通过类名获取Class类的对象,比较适合传参:String.class

    • 通过类加载器获取Class类的对象,用的较少

//方式1:通过全类名字符串获取Class类的对象
@Test
public void test1() throws ClassNotFoundException {
    Class<?> clazz = Class.forName("java.lang.String");
    System.out.println(clazz.getSimpleName());
    System.out.println(clazz.getPackage().getName());
}
//方式2:通过某个具体类的对象获取Class类的对象
@Test
public void test2() {
    String s = "令狐冲";
    //可以调用 对象.getClass()方法,返回对应Class对象
    Class clazz = s.getClass();
    method("java");
}
public <T> void method(T t){
    System.out.println(t.getClass().getSimpleName());    //String
}
//方式3:通过类名获取Class类的对象:比较适合传参
@Test
public void test3() {
    //每个类有对应的Class类型的class成员属性
    Class clazz = String.class;
}
//方式4:通过类加载器获取Class类的对象,用的较少
@Test
public void test4() throws ClassNotFoundException {
    //1)首先,使用当前对象的Class对象获取,获取一个类加载器
    ClassLoader loader = this.getClass().getClassLoader();
    //2)使用类加载器,通过全类名,加载获取对应的Class对象
    Class clazz = loader.loadClass("java.lang.String");
}
View Code

2.通过反射,获取类的结构信息

问题:通过反射获得了Class对象,Class对象又能做什么?

1)通过Class对象,获取类的成员

包括类的全部属性、方法、构造器、父类、接口、包、泛型、注解

java.lang.Class 类中的方法:

1)获取类的属性

  • getFields():获取本类以及从父类继承来的所有public修饰的属性,不限于直接父类
  • getDeclaredFields():获取本类中定义的所有属性,不问修饰符
  • getField(String name) : 返回一个 Field 对象

2)获取类的方法们

  • getMethods():获取本类以及从父类继承来的所有public修饰的方法,不限于直接父类
  • getDeclaredMethods():获取本类中定义的所有方法,不问修饰符
  • getMethod(String name, Class<?>… parameterTypes) :返回对应方法名,参数列表的Method

3)获取类的构造器们

  • getConstructors():获取本类以及从父类继承来的所有public修饰的构造器,不限于直接父类
  • getDeclaredConstructors():获取本类中定义的所有构造器,不问修饰符
  • getConstructor(Class<?>… parameterTypes)

4)获取类的父类

  • getSuperClass():以Class类型返回父类对象

5)获取类的接口

  • getInterfaces():以Class[]类型返回接口对象

6)获取类的注解

  • getAnnotations():返回此元素上存在的所有注释。返回 Annotation[]
  • getDeclaredAnnotations()
  • getAnnotation(Class annotationClass) :如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。

**注意:**只能获取保留策略为RetentionPolicy.RUNTIME的注解

7)获取父类或接口的泛型

  • getGenericSuperClass():获取父类的泛型(父类类型+泛型)
  • getGenericInterfaces():获取接口的泛型(接口类型+泛型)
  • 泛型类型:ParameterizedType

ParameterizeType pt = (ParameterizeType)type;
pt.getActualTypeArguments();

2.其次:返回值是对应类的数组,根据对应类的方法,获取类的结构信息

1)java.lang.reflect.Field类:

  • getModifiers() 修饰符:public static
  • getType()
  • getName()

2)java.lang.reflect.Method:

  • getModifiers()
  • getReturnType()
  • getName()
  • getParameterTypes()

3)java.lang.reflect.Constructor:

  • getModifiers()
  • getName()
  • getParameterTypes()

4)java.lang.reflect.Annotation:

  • annotationType()

**注意:**并不是所有的注解都有返回的资格

  • 注解的 @Retention 保留策略 --> 会使通过反射获取该类的注解时,获取不到

注解的作用机制:编译器通过反射判断你的注解是否正确;

3.通过反射,访问类的成员


问题:能不能通过Class对象,访问/设置类或对象对应单个的具体成员?

1.首先,通过Class获取具体的类成员,并返回该类型的对象

Class类的API:获取对应成员

  • getField(属性名):获取本类以及从父类继承来的,公共的属性
  • getDeclaredField(属性名):获取本类定义的属性,不问修饰符
  • getMethod(方法名,形参列表):获取本类以及从父类继承来的,公共的方法
  • getDeclaredMethod(方法名,形参列表):获取本类定义的方法,不问修饰符

2.通过具体的对象,实现类成员的访问和操作

Field类的API:

  • set(对象,属性值):为指定对象的属性赋值,如果该属性为静态属性,则对象可以使用null
  • get(对象):获取指定对象的该属性值,如果该属性为静态属性,则对象可以使用null
  • setAccessible(true):暴破

Method类的API

  • invoke(对象,实参列表):调用指定对象的方法,如果该方法为静态方法,则对象可以使用null
  • setAccessible(true):暴破

1)访问属性

//步骤1:获取Class类对象
Class clazz = Class.forName("全类名");

//步骤2:获取name属性对象
Field field = clazz.getDeclaredField("属性名");

//步骤3:暴破(属性为私有时,需要爆破)
field.setAccessible(true);

//步骤4:访问
Object object = clazz.newInstance();//确保类中有public修饰的无参构造器
//设置object对象的field字段值为“属性值”
field.set(object,"属性值");
//获取该对象的filed值
field.get(object);
View Code

2)访问方法

//步骤1:获取Class类对象
Class clazz = Class.forName("全类名");

//步骤2:获取方法对象
Method method = clazz.getDeclaredMethod(方法名,参数列表);

//步骤3:暴破
method.setAccessible(true);

//步骤4:访问
Object object = clazz.newInstance();//确保类中有public修饰的无参构造器
//调用object对象的method方法,并传入实参
Object value = method.invoke(object,实参列表);
View Code

3)获取接口的泛型/父类的泛型

@Test
public void testGetGeneric() {
    //获取接口的类型Type[]:Class类实现了Type接口
    Type[] types = clazz.getGenericInterfaces();    //Fly,Swim<String> 实现的 接口类型+泛型
    for (Type type : types) {
        //判断接口类型 Type 是否为参数化类型:ParameterizedType(Type接口的子类),如 Collection<String>
        if (!(type instanceof ParameterizedType)) {
            continue;
        }
        //若是参数化类型(即该类型有参数,也就是有泛型),则向下转型
        ParameterizedType pt = (ParameterizedType) type;
        //调用ParameterizedType接口的getActualTypeArguments()方法:返回表示此类型实际类型参数的 Type 对象的数组
        Type[] actualTypeArguments = pt.getActualTypeArguments();
        //向下转型为Class,获取其简单类名
        System.out.println(((Class)actualTypeArguments[0]).getSimpleName());
    }
}
View Code

4)获取注解

@Test
public void testAnnotation() {
    //获取Class对象的注解们
    Annotation[] annotations = clazz.getAnnotations();
    for (Annotation annotation : annotations) {
        //获取该注解的Class对象
        Class<? extends Annotation> annotationType = annotation.annotationType();
        System.out.println(annotationType.getSimpleName());
    }
}
View Code

4.通过反射,创建运行时类的对象


问题:有了Class对象,对应类也就加载到了内存,如何通过Class对象创建该类的对象呢?

1)使用无参构造器

调用Class对象的newInstance()方法

Object object = clazz.newInstance();

要求:

  • 1)类必须有一个无参数的构造器(或默认有)
  • 2)类的构造器的访问权限需要足够。

难道没有无参的构造器就不能创建对象了吗?

2)使用有参构造器

Object object = clazz.getDeclaredCosntructor(参数列表).newInstance(实参列表);

步骤如下:

  • 1)通过Class类的getDeclaredConstructor(Class … parameterTypes):取得本类的指定形参类型的构造器
  • 2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
//1.根据全类名获取对应的Class对象
String name = “com.mytest.Person";
Class clazz = Class.forName(name);
//2.获取指定参数结构的构造器 Constructor 的实例
Constructor con = clazz.getConstructor(String.class,Integer.class);
//3.通过Constructor的实例,创建对应类的对象,并初始化类属性
Person p2 = (Person)con.newInstance("Peter",20);
System.out.println(p2);
View Code

泛型擦除


泛型擦除:

  • 泛型只保留在编译期间,运行期间不存在泛型

面试题1:通过反射调用list2对象的add方法

List list1 = new ArrayList();
list1.add("");
list1.add(123);
//使用泛型

List<String> list2 = new ArrayList<>();
list2.add("");
list2.add(123);//错误

//泛型擦除,测试:
Class clazz = list2.getClass();
//Method method =clazz.getMethod("add",String.class);//找不到该方法
Method method =clazz.getMethod("add",Object.class);//
method.invoke(list2,"john");
View Code

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

1.什么是动态代理

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

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

2)动态代理是指:

  • 客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。

3)动态代理使用场合:

  • 调试
  • 远程方法调用

4)静态代理,特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代
理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。 最好可以通过一个代理类完成全部的代理功能。

动态代理相比于静态代理的优点:

  • 抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。

2.Java动态代理相关API

**1.java.lang.reflect.Proxy **:

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

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

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

参数含义:

  • ClassLoader loader:类加载器
  • Class<?>[] interfaces:得到被代理类实现的全部接口
  • InvocationHandler h:得到InvocationHandler接口的实现类实例

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

前面介绍的Proxy和InvocationHandler,很难看出这种动态代理的优势。

在这里插入图片描述
代码段1,2,3中都存在有相同的代码段,按照一般思路可以将相同的代码段封装成公共的方法,然后进行统一的调用。
在这里插入图片描述
改进后的说明:代码段1、代码段2、代码段3和深色代码段分离开了,但代码段1、 2、 3又和一个特定的方法A耦合了!

最理想的效果是:代码块1、 2、 3既可以执行方法A,又无须在程序中以硬编码的方式直接调用深色代码的方法

AOP(Aspect Orient Programming)面向切面编程:

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

  • AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理

在这里插入图片描述

 
/**
 * 动态代理与AOP的举例
 * @author zxy
 */
//1.被代理类实现的接口
interface Human{
    String getBelief();
    void eat(String food);
}
//2.被代理类
class SuperMan implements Human{
    @Override
    public String getBelief() {
        return "I believe I can fly!";
    }
    @Override
    public void eat(String food) {
        System.out.println("我喜欢吃" + food);
    }
}
//AOP代理演示
class HumanUtil{
    public void method1(){
        System.out.println("====================通用方法一====================");

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

/*
要想实现动态代理,需要解决的问题?
    问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
    问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
 */
 //3.代理类
class ProxyFactory{
    //调用此方法,传入一个被代理类的实例,返回一个代理类的对象:用于解决问题一
    public static Object getProxyInstance(Object obj){//obj:被代理类的对象
        //指派方法调用的调用处理程序 :用于解决问题二(详见4.)
        MyInvocationHandler handler = new MyInvocationHandler();
        handler.bind(obj);
        
        //调用Proxy.newProxyInstance():返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
    }
}

//4.java.lang.reflect.InvocationHandler 是代理实例的调用处理程序 实现的接口
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 {
        //演示AOP代理增加的通用方法
        HumanUtil util = new HumanUtil();
        //通用方法1
        util.method1();

        //method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
        //obj:被代理类的对象
        Object returnValue = method.invoke(obj,args);
        
        //通用方法2
        util.method2();
        
        //上述方法的返回值就作为当前类中的invoke()的返回值。
        return returnValue;
    }
}
//测试类
public class ProxyTest {

    public static void main(String[] args) {
        //创建一个被代理类的实例
        SuperMan superMan = new SuperMan();
        //proxyInstance:代理类的对象
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        //当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
        String belief = proxyInstance.getBelief();
        System.out.println(belief);
        proxyInstance.eat("四川麻辣烫");

        System.out.println("*****************************");

        NikeClothFactory nikeClothFactory = new NikeClothFactory();

        ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);

        proxyClothFactory.produceCloth();
    }
}
View Code

对比静态代理:代理类和被代理类在编译期间,就确定下来了

/*
 * 静态代理举例:
 * 特点:代理类和被代理类在编译期间,就确定下来了。
 */
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) {
        //创建被代理类的对象
        ClothFactory nike = new NikeClothFactory();
        //创建代理类的对象
        ClothFactory proxyClothFactory = new ProxyClothFactory(nike);
        proxyClothFactory.produceCloth();
    }
}
View Code

转自https://blog.csdn.net/select_alter_drop/article/details/98882949

原文地址:https://www.cnblogs.com/Ke-Me/p/14266162.html