Java动态代理机制

title: 动态代理
date: 2023-05-08 22:11:36
tags: [其它] 
categories: [其它]
comments: true

动态代理

个人Blog:dykang.top

动态代理的简介

Java动态代理机制的出现,使得Java开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类。代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程中,开发人员还可以按需调整委托类对象及其功能,这是一套非常灵活有弹性的代理框架。Java动态代理实际上通过反射技术,把代理对象和被代理对象(真实对象)的代理关系建立延迟到程序运行之后,动态创建新的代理类去完成对真实对象的代理操作(可以改变原来真实对象的方法行为),这一点成为了当前主流的AOP框架和延迟加载功能的基础。本文在查看和编写动态代理相关的代码使用的是JDK11,不过JDK动态代理相关的功能和接口已经相对稳定,不必担心JDK版本升级带来的兼容性问题,但是需要注意由于JDK9引入了模块概念,动态代理的源码也有不少的改动。下文先介绍设计模式中的代理模式,接着会分析JDK动态代理的核心类库、流程和机制,最后分析其底层源码级别实现。

动态代理问题

wt.png

  • 1.通过java提供的 Proxy 类帮我们创建代理对象。

  • 2.动态代理底层操作字节码,自动加载静态代码块,去生成代理类。

dtdl.png

  • 3.生成代理类的时候,根据传的Invocationhandler参数 会在代理类的实现接口的方法里面实现InvocationHandler的invoke()方法,也就是调用InvocationHandler实现类的invoke方法,然后在InvocationHandler实现类的InvocationHandler方法里加入业务逻辑,而且中间在再通过invoke方法调用被代理类的方法

ClassLoader中的类加载

类加载过程其实是一个很复杂的过程,主要包括下面的步骤:

1、加载过程:使用(自定义)类加载器去获取类文件字节码字节类的过程,Class实例在这一步生成,作为方法区的各种数据类型的访问入口。 2、验证过程:JVM验证字节码的合法性。 3、准备过程:为类变量分配内存并且设置初始值。 4、解析过程:JVM把常量池中的符号替换为直接引用。 5、初始化过程:执行类构造器<cinit>()方法,<cinit>()方法是编译器自动收集所有类变量的赋值动作和静态代码块中的语句合并生成,收集顺序由语句在源文件中出现的顺序决定,JVM保证在子类<cinit>()方法调用前父类的<cinit>()方法已经执行完毕。 ClassLoader#loadClass()方法就是用于控制类加载过程的第一步-加载过程,也就是控制字节码字节数组和类名生成Class实例的过程。

设计模式中的代理模式

代理模式是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

代理模式主要包括三种角色:
  • Subject抽象主题角色:一般定义为抽象类或者接口,是作为功能的定义,提供一系列抽象的功能方法。

  • RealSubject具体(真实)主题角色:一般称为被委托角色或者被代理角色,它是Subject的一个具体实现。

  • ProxySubject代理主题角色:一般称为委托角色或者代理角色,一般ProxySubject也实现(或者继承)Subject,接收一个具体的Subject实例RealSubject,在RealSubject处理前后做预定义或者后置操作,甚至可以直接忽略RealSubject原来的方法。 把上面的类图编写成代码如下:

public interface Subject {
 
void doSomething();
}
 
public class RealSubject implements Subject {
 
@Override
public void doSomething() {
    System.out.println("RealSubject doSomething...");
 }
}
 
public class ProxySubject implements Subject {
 
private final Subject subject;
 
public ProxySubject(Subject subject) {
    this.subject = subject;
}
 
@Override
public void doSomething() {
    subject.doSomething();
    doOtherThing();
}
 
private void doOtherThing() {
    System.out.println("ProxySubject doOtherThing...");
 }
}
 
public class Client {
 
public static void main(String[] args) throws Exception {
    Subject subject = new RealSubject();
    ProxySubject proxySubject = new ProxySubject(subject);
    proxySubject.doSomething();
  }
}

运行Client main()输出:

RealSubject doSomething...
ProxySubject doOtherThing...

代理模式在日常的场景中也经常碰到,比较常见的一个场景就是游戏代练,套进去上面的代码可以写个比较生动的例子:

public interface Player {
 
void playGame();
 }
 
public class I implements Player {
 
@Override
public void playGame() {
    System.out.println("操作Throwable游戏角色打怪升级");
  }
}
 
public class ProxyPlayer implements Player {
 
private final Player player;
 
public ProxyPlayer(Player player) {
    this.player = player;
}
 
@Override
public void playGame() {
    login();
    this.player.playGame();
    logout();
}
 
private void login() {
    System.out.println("登录Throwable游戏角色");
}
 
private void logout() {
    System.out.println("退出Throwable游戏角色");
 }
}

代理模式有几个比较大的优点:

职责清晰:也就是真实主题角色只需要实现具体的逻辑,不需关注代理类的职责,而代理类也只需要处理预处理和后置的逻辑,类的职责分明。 高扩展性:由于职责分明,也就是真实主题角色可以随时修改实现,这样就能通过更新或者替换真实主题的实现并且不改变代理主题角色的情况下改变具体功能。 高灵活性:主要体现在后面提到的动态代理。

注意
  • 接口数组中所有接口元素的类修饰符最好一致为public。如果接口数组中存在非default修饰的接口元素,那么接口数组中的所有接口类都要放在同一个包下,并且都要使用default修饰。

  • 很少情况下我们修改接口的修饰符,默认为public,那么所有代理类的包路径都是com.sun.proxy,全类名是:com.sun.proxy.$ProxyN。

代理类的代码,有如下几个特点:
  • 1、代理类继承于java.lang.reflect.Proxy,实现了接口数组中的接口元素类,构造函数只有一个InvocationHandler类型的参数。

  • 2、接口中的所有被代理方法包括equals、toString、hashCode都建立了一个对应的Method私有静态实例,在最后面的静态代码块中实例化。

  • 3、所有代理方法都是用public final修饰,也就是代理类中的代理方法是不能覆盖的。

  • 4、所有代理方法都是通过InvocationHandler实例的invoke方法进行调用的,记得第一个参数是代理类实例本身,如果用了在InvocationHandler#invoke()方法实现过程中使用了这个参数有可能造成死循环。

  • 11
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值