Java学废之路08——反射与代理

八、反射与代理

8.1 反射机制

当使用字符串的形式给出一个类名时,依靠什么机制来获取他的类型呢?

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用对象的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

8.1.1 简介

在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时就可以利用Java的反射机制来获取所需的成员变量或是方法(public或private都可被访问到 )。

Reflection反射是被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性与方法。

在JVM加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只能由一个Class对象),这个对象包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。

image-20210415152446246

  • 动态语言:在运行时代码可以根据某些条件改变自身结构,已有的函数可以被删除或是其他结构上的变化。主要的动态语言: C#、 JavaScript、 PHP、 Python、 Erlang。
  • 静态语言:运行时结构不可变的语言就是静态语言。如Java、 C、C++

注:Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程变得更加灵活。

(1)反射的应用

Java反射机制的应用:

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法【public与private都可以】
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理
(2)反射与封装

反射机制可以使得“外部类“调用类内部的private属性与方法,这看似与封装机制产生了冲突。但是实际上是不矛盾的。

  • 封装是在编码阶段:使用public与private来区分哪些属性与方法可以被外部类所调用。将public部分的功能暴露给外部,private的内部实现细节隐藏起来。
  • 反射是在运行阶段:首先使用反射的结果是返回一个完整的Class对象,该对象具有类的全部内容,其中就包含private的内容。
(3)反射的相关类

image-20200804101445855

8.1.2 反射源头类Class

所谓反射,是指在运行时状态中,获取类中的属性和方法,以及调用其中的方法的一种机制。这种机制的作用在于获取运行时才知道的类(Class)及其中的属性(Field)、方法(Method)以及调用其中的方法,也可以设置其中的属性值。

在Java中实现反射最重要的一步,也是第一步就是获取Class对象,得到Class对象后可以通过该对象调用相应的方法来获取该类中的属性、方法以及调用该类中的方法。

(1)简介

Class类是Java的一个基础类,每装载一个新类的时候,jvm就会在Java堆中创建一个Class实例,这个实例就代表整个类实体。

以下类型可以有Class对象:

  • class类对象:外部类、成员内部类、局部内部类、匿名内部类
  • interface接口
  • 数组
  • enum枚举
  • annotation注解
  • 基本数据类型【int.class】
  • void

image-20210417201538186

Class的一个实例就对应着一个运行时类【经过类加载过程后,被加载到内存中的类】。一个加载的类在 JVM 中只会有一个Class实例。

  • Class本身也是一个类
  • Class 对象只能由系统建立对象
  • 一个加载的类在 JVM 中只会有一个Class实例
  • 一个Class对象对应的是一个加载到 JVM 中的一个.class文件
  • 每个类的实例都会记得自己是由哪个 Class 实例所生成【newInstance()】
  • 通过Class可以完整地得到一个类中的所有被加载的结构
(2)获取Class的方式

Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象。获取Class类反射源头的方式有以下四种:

  • 方式一:调用运行时类的方法 .class。若已知具体的类,通过类的class属性获取,该方法最为安全可靠,性能最高。
  • 方式二:通过运行时类的对象,调用 getClass()。已知某个类的实例,调用该实例的getClass()方法获取Class对象。
  • 方式三:调用 Class 的静态方法 forName(String classpath)。已知一个类的全类名,且该类在类路径下, 可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException。
  • 方式四:使用当前类的加载器 ClassLoader。在当前类内部使用当前类的类加载器,去加载一个其他类的Class对象。
// Class的获取方式
@Test
public void test3() throws ClassNotFoundException {
    //方式1
    Class<Person> clazz1 = Person.class;
    System.out.println(clazz1);

    // 方式2
    Person person = new Person();
    Class<? extends Person> clazz2 = person.getClass();
    System.out.println(clazz2);

    // 方式3【常用】
    Class<?> clazz3 = Class.forName("com.zdp.learn.studyReflection.Person");
    System.out.println(clazz3);

    // 方式4
    ClassLoader classLoader = this.getClass().getClassLoader();
    Class<?> clazz4 = classLoader.loadClass("com.zdp.learn.studyReflection.Person");
    System.out.println(clazz4);

    // 一个加载的类在 JVM 中只会有一个Class实例
    System.out.println(clazz1 == clazz2);	// true
    System.out.println(clazz1 == clazz3);	// true
    System.out.println(clazz1 == clazz4);	// true
}
8.1.3 反射的使用

Java.lang.reflect包中有三个类Field\Method\Constructor分别用于描述类的域、方法和构造器。这三个类都有一个getName方法,用来返回项目的名称。

  • Class类的getFields\getMethods\getConstructor方法将返回类提供的public域、public方法和public构造器,其中包括超类的公有成员;
  • Class类的getDeclaredFields\getDeclaredMethods\getDeclaredConstructor方法将返回类提供的全部域、全部方法和全部构造器,其中包括私有成员域受保护成员,但不包括超类的成员。
(1)获取类中的域

Declared代表获取私有的域对象,与setAccessible(true)配合使用:

image-20200804102930191

(2)获取类中方法

Declared代表获取私有的方法,与setAccessible(true)配合使用:

image-20200804103027474

image-20200804160841693

(3)获取类中构造器

Declared代表获取私有的构造器,与setAccessible(true)配合使用:

image-20200804103039150

(4)调用运行时类的指定结构

指定结构主要指的是属性、方法和构造器。详见实例。

(5)总结与实例

image-20210415182753998

  • 首先通过反射获取对象的类Class【Class是反射的源头,该类对象是后续操作的基础】
  • 通过Class获取类中的public内容与private内容
    • 构造器【通过构造器实例化类对象】
    • 属性【若要使用set()来改变某一属性的值,需要绑定该属性所对应类的实例化对象】
    • 方法【若要执行invoke()相应的方法,也需要与相应的类实例对象绑定】
// 实体类
public class Person {
    // 私有属性
    private String name;
    private int age;
    // 公有属性
    public int sex;
    
    //1 公有方法
    // 公有构造器
    public Person() {
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Person(String name, int age, int sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    // 成员方法
    public void show() {
        System.out.println("hello"+this.name);
    }

    //2 私有方法
    // 私有构造器
    private Person(String name) {
        this.name = name;
    }
    // 私有方法
    private void myName(String name){
        this.name = name;
    }
    
    /*getter/setter方法*/
    
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                '}';
    }
}
@Test
public void test2() throws Exception {
    // 通过反射获取类对象
    Class<Person> personClass = Person.class;
    //1 获取类对象的public内容
    // 获取类对象的公有构造器
    Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
    // 利用构造器创建类对象
    Person zdp = constructor.newInstance("zdp", 100);
    // 获取类公有属性
    Field sex = personClass.getDeclaredField("sex");
    sex.set(zdp, 123);
    // 获取类公有方法
    Method show = personClass.getMethod("show");
    show.invoke(zdp);
    System.out.println(zdp.toString());

    //2 获取类对象的private内容
    // 获取类的私有构造器
    Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor(String.class);
    declaredConstructor.setAccessible(true);
    // 使用私有构造器创建实例化对象
    Person zjx = declaredConstructor.newInstance("zjx");

    // 获取类的私有属性
    Field name = personClass.getDeclaredField("age");
    name.setAccessible(true);
    name.set(zjx, 555);

    // 获取类的私有方法
    Method myName = personClass.getDeclaredMethod("myName", String.class);
    myName.setAccessible(true);
    Object zjxzjx = myName.invoke(zjx, "zjxzjx");
    System.out.println("返回值:" + zjxzjx);

    System.out.println(zjx.toString());
}
8.1.4 类封装性的破坏

封装、继承、多态是Java的三大特性,但是,通过反射,我们可以轻而易举的获取对私有成员的操作权利。那么这就是所谓的封装性遭受到破坏了吗?Java这样做岂不是搬起石头砸自己的脚吗?

先来回想一下我们自己在使用Java撸代码的时候,声明私有方法时的出发点是什么?是不是大多数的情况下,这样做是为了支撑某一个我们对外提供的方法?并且,很多时候,我们为了代码的可读性会将一整个public方法拆分成很多个言简意赅、易读性更强的private方法和一个组合这些private方法的public方法。对于私有的成员变量,也是同理,使用private修饰变量,也是为了更好的服务于我们编写的某个对外提供的功能,而这些私有的成员变量和成员方法,仅在本类内有意义,如果对外开发的话,不仅毫无意义,甚至会严重影响到我们打算对外提供的功能。

用户通过反射机制获取了所使用的类中私有成员的存取权限,这是毫无意义的,因为大多数的这些 private 成员是依附于其它 public 方法而存在的,基本上都是为了服务这些 public 成员的。

同时,要想获取私有成员还需要设置 setAccessible(true); // 设置访问权限不受控制

8.2 代理机制

代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。

image-20200804162213619

代理模式有Subject角色,RealSubject角色,Proxy角色:

  • Subject角色负责定义RealSubject和Proxy角色应该实现的接口;
  • RealSubject角色用来真正完成业务服务功能;
  • Proxy角色负责将自身的Request请求,调用RealSubject对应的request功能来实现业务功能以及实现扩展

  • 用户client只关心接口功能,而不在乎谁提供了功能。上图中接口是 Subject。
  • 接口真正实现者是上图的 RealSubject,但是它不与用户直接接触,而是通过代理ProxySubject。
  • 代理就是上图中的 ProxySubject,由于它实现了 Subject 接口,所以它能够直接与用户接触。
  • 用户调用 ProxySubject的时候,ProxySubject内部调用了 RealSubject。所以,Proxy 是中介者,它可以增强 RealSubject 操作。

8.2.1 静态代理

image-20200804162655461

image-20200804162936477

静态代理实质就是在代理类中定义了与目标类(真实类)相同的方法,然后通过修改、执行代理类的方法来达到在原来的方法基础上进行扩充的目的,不止执行了目标对象的目标方法,同时还可以自己添加其他的功能。

image-20200804163413962

8.2.2 动态代理

动态代理是指在程序运行中根据代码利用反射机制动态生成代理,主要有代理类和委托类(真实类)

动态代理重要的应用是Spring AOP的实现,可以使用JDK(基于接口的)和CGLIB(基于类的)来产生proxy代理对象。

(1)特点
  • 代理类与被代理类实现的抽象接口相同,代理类主要负责为被代理类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。我们在访问实际对象/被代理类对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种操作。
  • 必须实现InvocationHandler 接口。当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为 InvocationHandler 这个接口的 invoke() 方法来进行调用。
  • 对于同一个抽象角色而言,可能会有很多个真实角色去实现它,但是只需要一个代理Proxy.newProxyInstance()方法即可获得不同的角色代理【因为该方法的前两个参数保证了代理角色与真实角色所实现接口的一致性】
(2)代码演示

/**
 * 动态代理公有接口/抽象接口
 */
public interface Human {
    String getName();
    void eat(String food);
}
/**
 * 被代理类
 */
public class Superman implements Human {
    @Override
    public String getName() {
        System.out.println("超人...");
        return "超人";
    }

    @Override
    public void eat(String food) {
        System.out.println("超人吃:" + food);
    }
}
/**
 * 代理类:动态代理的目的是动态地创建一个代理类对象【使用java.lang.reflect.Proxy】,而不需要提前写死
 * 要想实现动态代理,需要解决:
 * 1、如何根据加载到内存中的被代理类,动态地创建一个代理类对象,通过该代理类对象实现对被代理类的额外操作
 * 2、当通过代理类的对象调用方法时,如何动态地调用被代理类中的同名方法
 */
public class ProxyHuman {
    /**
     * 调用此方法动态地返回一个代理类的对象。解决问题1
     *
     * @param obj 被代理类对象
     * @return 代理类对象实例
     */
    public static Object getProxyInstance(Object obj) {
        // 核心接口:内部只有一个方法invoke(),调用被代理类中的同名方法,同时可添加附加操作。
        // 解决问题2
        InvocationHandler handler = new MyInvocationHandler(obj);
        // 运用反射,通过参数类的加载器、接口【被代理类所实现的接口,即公有接口】、处理器【当调用返回的代理类中的方法时,将会自动执行该处理器内的invoke()方法】,返回一个代理类对象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
    }
}
/**
 * 通用方法
 */
public class HumanUtils {
    public void method1() {
        System.out.println("通用方法:预处理...");
    }

    public void method2() {
        System.out.println("通用方法:后处理...");
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
// 处理器
public class MyInvocationHandler implements InvocationHandler {

    // 被代理类对象【类型不要写死】
    private Object object;

    public MyInvocationHandler(Object obj) {
        this.object = obj;
    }

    /**
     * 动态代理实现的核心
     * 当我们调用代理类对象中的方法a时,该invoke就会被自动调用
     * @param proxy     代理类对象
     * @param method    要执行的同名方法
     * @param args      方法中所需的参数
     * @return          执行方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        HumanUtils humanUtils = new HumanUtils();
        humanUtils.method1();
        // 通过反射【调用类中方法】,执行被代理类中的方法
        Object returnValue = method.invoke(object, args);   // 反射:将method与object被代理类对象绑定
        humanUtils.method2();
        return returnValue;
    }
}
/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        System.out.println("===========动态代理===========");
        // 被代理类对象
        Superman superman = new Superman();
        // 代理类对象【动态生成】
        Human proxyInstance = (Human) ProxyHuman.getProxyInstance(superman);
        proxyInstance.getName();
        proxyInstance.eat("麻辣烫");
    }
}

image-20210418110617226

(3)语法解析
a> InvocationHandler

InvocationHandler是一个接口,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。

public interface InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
        
}
/*
InvocationHandler 内部只是一个 invoke() 方法,正是这个方法决定了怎么样处理代理传递过来的方法调用。
1.proxy 代理对象
2.method 利用反射,调用被代理类中的同名方法【需要与被代理类对象进行绑定】
3.args 调用的方法中的参数
*/

仔细思考代理模式中的代理Proxy角色。Proxy角色在执行代理业务的时候,无非是在调用真正业务之前或者之后做一些“额外”业务。

image-20200804182519768

由上图可以看出,代理类处理的逻辑很简单:在调用某个方法前及方法后做一些额外的业务。换一种思路就是:在触发(invoke)真实角色的方法之前或者之后做一些额外的业务。那么,为了构造出具有通用性和简单性的代理类,可以将所有的触发真实角色动作交给一个触发的管理器,让这个管理器统一地管理触发。这种管理器就是InvocationHandler(调节处理器)。

动态代理工作的基本模式就是将自己的方法功能的实现交给 InvocationHandler角色,外界对Proxy角色中的每一个方法的调用,Proxy角色都会交给InvocationHandler来处理,而InvocationHandler则使用内部的invoke()方法来调用具体对象角色的方法。

当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。

image-20200804184522028

b> Proxy.newProxyInstance()
//该方法动态地返回一个代理类的实例
public static Object newProxyInstance(ClassLoader loader,		//类加载器
                                      Class<?>[] interfaces, 	//要用来代理的接口(抽象)
                                      InvocationHandler h		//InvocationHandler对象
                                     )
  • loader与interfaces 参数。该方法可以根据传入接口的不同,动态的产生不同的接口【抽象接口/公有接口】实例。因为代理模式的UML就是代理类与被代理类实现的同一个抽象接口,所以在使用 Proxy.newProxyInstance() 创建一个代理类对象时,就需要将被代理类所实现的接口传入,以保证生成的代理类对象与被代理类实现的是同一个抽象接口。
  • InvocationHandler 参数。代理类中还有一个关键部分就是对真实对象/被代理类对象中的方法进行调用。在动态代理中,该部分使用了 InvocationHandler 接口来统一处理。该接口内部有一个 invoke() 方法,当在 client 中调用代理类对象中的方法时,将会自动地执行该接口,进入 invoke() 的内部处理逻辑,该部分包含有对被代理类对象同名方法的调用,以及额外操作。
(4)总结

image-20210418150658116

  • 代理分为静态代理和动态代理两种。
  • 静态代理,代理类需要自己编写代码写成。
  • 动态代理,代理类通过 Proxy.newInstance() 方法生成。
  • 不管是静态代理还是动态代理,代理与被代理者都要实现两样接口,它们的实质是面向接口编程。
  • 静态代理和动态代理的区别是在于要不要开发者自己定义 Proxy 类。
  • 动态代理通过 Proxy 动态生成代理类,但是它也指定了一个 InvocationHandler 的实现类。
  • 代理模式本质上的目的是为了增强现有代码的功能。
(5)CGLIB与Proxy的区别

JDK的动态代理只能针对实现了接口的类生成代理。而cglib的动态代理是针对类实现代理,这两种代理我们可以灵活使用。


  • Proxy方式
// Car 接口【抽象对象】
public interface Car {
    public void run();
}
// Car 实现类【被代理对象——真实对象】
public class CarImpl implements Car{
    public void run() {
        System.out.println("car running");
    }
}
// 动态代理处理器类
//1、聚合真实对象,并通过反射执行该真实类对象中的方法
//2、在重写的invoke()方法中附加增强操作
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//JDK动态代理代理类 
public class CarHandler implements InvocationHandler{
    // 聚合的真实类【被代理类】对象
    private Object car;
    // 构造方法赋值给真实的类
    public CarHandler(Object obj){
        this.car = obj;
    }
	// 代理类执行方法时,调用的是这个方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置增强处理
        System.out.println("before");
        
        // 反射在动态代理中的应用:通过反射执行invoke()被代理对象【真实对象】中的方法
        // 被代理类【真实对象】,即为抽象对象的实现类
        Object res = method.invoke(car, args);
        
        // 后置增强处理
        System.out.println("after");
        return res;
    }
}
// main 方法
import java.lang.reflect.Proxy;

public class main {
    public static void main(String[] args) {
        // 被代理对象【是一个接口的实现类】
        CarImpl carImpl = new CarImpl();
        // 创建动态代理处理器对象
        CarHandler carHandler = new CarHandler(carImpl);
        // 获取代理对象
        Car proxy = (Car)Proxy.newProxyInstance(
                carImpl.class.getClassLoader(), // 第一个参数,获取ClassLoader
                carImpl.getClass().getInterfaces(), // 第二个参数,获取被代理类的接口
                carHandler);// 第三个参数,一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
        proxy.run();
    }
}

JDK的动态代理依靠接口实现,入参必须有被代理类的接口,也就是carImpl.getClass().getInterfaces()。如果有些类并没有实现接口,则不能使用JDK代理,这就要使用cglib动态代理了。


  • CGLIB方式
// 未实现接口的类
public class CarNoInterface {

    public void run() {
        System.out.println("car running");
    }
}
// cglib代理类
import java.lang.reflect.Method;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor{

    private Object car;
    
    /** 
     * 创建代理对象 
     *  
     * @param target 
     * @return 
     */  
    public Object getInstance(Object object) {  
        this.car = object;  
        Enhancer enhancer = new Enhancer();  
        enhancer.setSuperclass(this.car.getClass());  
        // 回调方法  
        enhancer.setCallback(this);  
        // 创建代理对象  
        return enhancer.create();  
    }  
    
    @Override
    public Object intercept(Object obj, Method method, Object[] args,MethodProxy proxy) throws Throwable {
        System.out.println("事物开始");  
        proxy.invokeSuper(obj, args);  
        System.out.println("事物结束");  
        return null;  
    }

}
// main方法
import java.lang.reflect.Proxy;

public class main {

    public static void main(String[] args) {    
        CglibProxy cglibProxy = new CglibProxy();
        CarNoInterface carNoInterface = (CarNoInterface)cglibProxy.getInstance(new CarNoInterface());
        carNoInterface.run();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我姓弓长那个张

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值