Java动态代理总结
动态代理技术在Java中有着非常广泛的应用,我再阅读Spring源码的时候多次碰到对这种技术的使用,在这篇文章做个总结。

代理模式

首先介绍下什么是代理模式:代理模式给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。代理模式是一种结构型设计模式。

在代理模式中,我们把角色分为 3 种:

Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;
RealSubject(真实主题角色):真正实现业务逻辑的类;
Proxy(代理主题角色):用来代理和封装真实主题;

后面在编写Java动态代理的代码时,我们也会看到代码与这三个角色的对应关系。

如果根据字节码的创建时机来分类,可以将代理模式分为静态代理和动态代理:

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

静态代理

我们先通过实例来了解下静态代理,理解静态代理的缺点,再来了解动态代理

编写一个接口 Run,以及该接口的一个实现类 Runner。

public interface Run {
	public void run();
}
public class Runner implements Run {
	@Override
	public void run() {
		System.out.println("I am running");
	}
}

然后我们使用静态代理对 Runner 进行功能增强,在调用run之前记录一些日志。写一个代理类 RunnerProxy ,代理类需要实现 Run

public class RunnerProxy implements Run {
	private Run run;

	public RunnerProxy(Run run) {
		this.run = run;
	}

	@Override
	public void run() {
		System.out.println("before run");
		run.run();
		System.out.println("after run");
	}
}

具体使用方式如下:

public class ProxyTest {
	public static void main(String[] args) {
		Run run = new Runner();
		RunnerProxy runnerProxy = new RunnerProxy(run);
		runnerProxy.run();
	}
}

打印结果如下:

before run
I am running
after run

通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码,这是静态代理的一个优点。

静态代理的缺点
虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。

1、 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:

  • 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
  • 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类

2、 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。

动态代理

动态代理就是想办法,根据接口或目标对象,计算出代理类的字节码,然后再加载到JVM中使用,在这里我们借助JDK自带的动态代理和CGLIB动态代理两种技术来实现。

JDK动态代理

JDK动态代理主要涉及两个类:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler

我们依照上面代理的三个角色分别来写编码。

Subject(抽象主题角色),对应Run接口:

public interface Run {
	public void run();
}

RealSubject(真实主题角色),对应Runner类:

public class Runner implements Run {
	@Override
	public void run() {
		System.out.println("I am running");
	}
}


Proxy(代理主题角色),对应ProxyHandler:

public class ProxyHandler implements InvocationHandler {
	private Object object;

	public ProxyHandler(Object object) {
		this.object = object;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("before " + method.getName());
		Object object = method.invoke(this.object, args);
		System.out.println("after " + method.getName());
		System.out.println("--------------------------------");
		return object;
	}
}

具体使用方式如下:

import java.lang.reflect.Proxy;

public class ProxyTest {
	public static void main(String[] args) {
		Run run = new Runner();
		Jump jump = new Jumper();
		ProxyHandler handler = new ProxyHandler(run);
		Run proxyRunner = (Run) Proxy.newProxyInstance(run.getClass().getClassLoader(), run.getClass().getInterfaces(), handler);
		proxyRunner.run();
	}
}

运行结果:

before run
I am running
after run

CGLIB动态代理

首先maven引入CGLIB包。

正常编写类Jumper,不需要实现接口

public class Jumper {
	public void jump() {
		System.out.println("I am jumping");
	}
}

编写一个JumperLogger,继承MethodInterceptor,用于方法的拦截回调:

public class JumperLogger implements MethodInterceptor {
    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(object, objects);  
        after();
        return result;
    }
    private void before() {
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    private void after() {
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}

具体使用方式如下:

public class ProxyTest {
	public static void main(String[] args) {
		JumperLogger jumperLogger = new JumperLogger();
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(Jumper.class);  
		enhancer.setCallbacks(new Callback[]{jumperLogger});  
		Jumper proxy = (Jumper) enhancer.create(); 
		proxy.jump();
	}
}

运行结果如下:

log start time [Fri Apr 14 17:55:33 CST 2023] 
I am jumping
log end time [Fri Apr 14 17:55:33 CST 2023] 

CGLIB 创建动态代理类的模式是:

  1. 查找目标类上的所有非final 的public类型的方法定义;
  2. 将这些方法的定义转换成字节码;
  3. 将组成的字节码转换成相应的代理的class对象;
  4. 实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求

JDK动态代理与CGLIB动态代理对比

JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。

cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。

JDK Proxy 的优势:

  • 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
  • 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
  • 代码实现简单。

基于类似 cglib 框架的优势:

  • 无需实现接口,达到代理类无侵入
  • 只操作我们关心的类,而不必为其他相关类增加工作量。
  • 高性能
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇