Java中如何使用反射实现动态代理?

什么是反射?

        了解反射之前,先来了解Java中的两个概念:编译期和运行期。

        编译期:是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作。比如:检查语法错误。

         运行期:是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。

        反射(Reflection)是Java程序开发语言的特征之一,它允许运行中的Java程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性和方法。

        Java反射机制主要提供以下功能

       1.在运行时获取任意一个对象所属的类。

       2.在运行时构造任意一个类的对象。

       3.在运行时获取任意一个类所具有的成员变量和方法。

       4.在运行时调用任意一个类对象的方法。

       5.生成动态代理。

Java中如何实现反射?

        在JDK中,主要由以下类来实现Java反射机制:

        Field类:提供有关类的构造方法的信息,以及对它的动态访问权限。它是一个封装反射类的属性的类。

        Constructor类:提供有关类的构造方法信息,以及对它的动态访问权限。它是一个封装反射类的构造方法的类。

        Method类:提供关于类的方法的信息,包括抽象方法。它是用来封装反射类方法的类。

        Class类:表示正在运行的Java应用程序中的Class类型信息的实例

        Object类:Object是所有Java类的父类。所有对象都默认继承Object类。

获取Class对象的三种方式?

        第一种方式:Class.forName(类名),是通过类的完全限定名字符串获取Class对象,这也是平时最常用的反射获取Class对象的方法。

try {
		Class cls = Class.forName("com.fulian.demo.Student");
		
    } catch (ClassNotFoundException e) {
		e.printStackTrace();
	}

        第二种方式:类名.class,通过类名的class属性。

Class cls = String.class;

        第三种方式:对象.getClass(),通过对象的getClass()方法,该方法由Object提供。

	String s = "just do it";
	Class cls = s.getClass();

Class类中的常用方法?

类型

访问方法

返回值类型

说明

包路径

getPackage()

Package 对象

获取该类的存放路径

类名称

getName()

String 对象

获取该类的名称

继承类

getSuperclass()

Class 对象

获取该类继承的类

实现接口

getlnterfaces()

Class 型数组

获取该类实现的所有接口

构造方法

getConstructors()

Constructor 型数组

获取所有权限为 public 的构造方法

getDeclaredContruectors()

Constructor 型数组

获取当前对象的所有构造方法

newInstance() Object对象 调用当前对象的public无参构造方法

方法

getMethods()

Methods 型数组

获取所有权限为 public 的方法

getDeclaredMethods()

Methods 型数组

获取当前对象的所有方法

invoke(对象名,参数) Object对象 调用当前对象的方法

成员变量

getFields()

Field 型数组

获取所有权限为 public 的成员变量

getDeclareFileds()

Field 型数组

获取当前对象的所有成员变量

get(对象名) Object对象 获取当前成员变量的值
set(对象名,参数) 设置当前成员变量的值

反射机制中的优点与缺点?

优点:运行期类型的判断,动态加载类,提高代码灵活度。

缺点:

        1.性能瓶颈:反射相当于一系列的解释操作,通知JVM要做的事情,性能比直接的java代码要慢很多。

        2.安全问题,通过反射可以动态操作私有属性或方法,破坏封装,同时也增加了类的安全隐患。

反射的使用场景?

        在平时的项目开发过程中,基本很少直接使用到反射机制;

        反射是框架设计的灵魂,Spring/Mybatis等框架底层,大量使用到反射机制进行框架设计;

举例:

        1.使用JDBC连接数据库时,使用Class.forName(驱动类名),通过反射加载数据库的驱动类;

        2.Spring框架的IOC(动态加载管理Bean)创建对象以及AOP(动态代理)使用反射进行底层实现;

        3.MyBatis框架的Mapper接口代理对象的创建,也是通过反射实现。

如何实现动态代理?

        动态代理(Dynamic Proxy): 可以在运行期,动态创建某个interface的实例,实际上是JVM在运行期动态创建class字节码并加载的过程。动态代理在Java中有着广泛的应用,比如Spring AOP、Hibernate数据查询、测试框架的后端mock、RPC远程调用、Java注解对象获取、日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等。

代理模式       

        代理模式:给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。代理模式是一种结构型设计模式。
代理模式角色分为 3 种:
        ●Subject(抽象主题角色):定义代理类和真实主题的公共对外方法。通常被设计成接口
        ●RealSubject(真实主题角色):真正实现业务逻辑的类;
        ●Proxy(代理主题角色):用来代理和封装真实主题;
        代理模式的结构比较简单,其核心是代理类,为了让客户端能够一致性地对待真实对象和代理对象,在代理模式中引入了抽象层。

Java中如何使用反射实现动态代理?_第1张图片


        如果根据字节码的创建时机来分类,可以分为静态代理和动态代理:
        ●所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。
        ●而动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件。

静态代理

Subject接口

//代理模式中的“subject”
//定义公共的行为方法
public interface Subject {
	void request();
}

  RealSubject实现类

public class RealSubject implements Subject{

	@Override
	public void request() {
		System.out.println("RealSubject类中的request()方法");
		
	}

}

Proxy代理类:增强日志功能

import java.time.LocalDateTime;

public class Proxy implements Subject{
	// 每个代理对象一定“持有”一个“RealSubject”目标对象
	private RealSubject target = new RealSubject();

	@Override
	public void request() {
		before("request()"); // 附加逻辑
		target.request();; // 核心调用
		after("request"); // 附加逻辑
	}
	
	public void before(String methodName) {
		System.out.println("方法" + methodName + "开始执行。。。");
	}
	
	public void after(String methodName) {
		System.out.println("方法" + methodName + "结束执行。。。" + LocalDateTime.now());
	}
}

客户端

public class Client {
	public static void main(String[] args) {
		// 通过代理对象,进行方法的调用
		Subject sub = new Proxy();
		sub.request();
	}
}

// 运行结果
方法request()开始执行。。。
RealSubject类中的request()方法
方法request结束执行。。。2022-07-27T21:14:33.051

        通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码,这是静态代理的一个优点。
        虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。
1、 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
        ●只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
        ●新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
2、 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。

动态代理

        Java中两种常见的动态代理方式:JDK原生动态代理和CGLIB动态代理(第三方开源类库)。

        JDK动态代理 JDK动态代理主要涉及两个类:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler。我们通过编写一个调用逻辑处理器 LogHandler 类来提供日志增强功能,并实现 InvocationHandler 接口;在 LogHandler 中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke()方法中编写方法调用的逻辑处理。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.time.LocalDateTime;

// 日志组件
// 作用 : 实现日志逻辑
public class LogHandler implements InvocationHandler{
	private Object target;
	
	public LogHandler(Object obj) {
		this.target = obj;
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

		System.out.printf("%s方法开始执行。。。\n",method.getName());
		
		// 调用目标方法
		Object result = method.invoke(target, args);
		
		System.out.printf("%s方法结束执行[%s]\n",method.getName(),LocalDateTime.now());
		
		return result;
	}

}
import java.lang.reflect.Proxy;

public class Client {
	public static void main(String[] args) {
		// 动态产生UserService接口的实现类(Proxy动态代理)
		// 类加载器
		ClassLoader loader = Subject.class.getClassLoader();
		
		// 接口列表
		Class[] interfaces = {Subject.class};
		
		// 日志
		// 传入目标对象
		LogHandler handler = new LogHandler(new RealSubject());
		
		// 通过Proxy的newProxyInstance()方法
		// 动态产生代理类 : com.sun.proxy.$Proxy0
		// 创建代理类对象
		Subject sub = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
		sub.request();

	}
}

// 运行结果
request方法开始执行。。。
RealSubject类中的request()方法
request方法结束执行[2022-07-27T21:19:28.678]

你可能感兴趣的:(java,servlet,jvm,开发语言,后端)