Java 代理模式
代理模式
什么是代理模式?(简单举例几个例子)
1、打王者段位一直打不上王者段位怎么办?请游戏代练。
2、过年回家自已抢不到高铁票怎么办?找黄牛帮我们抢票。
3、.......(生活中处处可见。)
由上可知,代理模式 就是:
给某一个对象提供一个代理对象,并由代理对象控制对 原对象的引用。
通俗滴讲就是我们常说的:中介。
有哪几种代理模式(它们的区别与优缺点)?
一般分为两种:
1、静态代理 (代理类与被代理类的关系在程序运行前就已经确定好了。)
2、动态代理 (代理类与被代理类的关系在程序运行时才确定。)
- jdk动态代理
- cglib动态代理
什么是静态代理?
在程序运行前就已经存在的代理类的字节码文件。
代理类和委托类的关系在运行前就确定了。
什么是动态代理?
在程序运行时,通过反射机制动态创建的。
静态代理 与 动态代理 优缺点
优点
实现在不改变目标对象源码情况下,对目标对象进行功能扩展。
静态代理缺点
假设项目中有多个类,则需要编写多个代理类,工作量大,不好修改,不好维护,不能应对变化。
JDK动态代理缺点
被代理的类必须实现接口,未实现接口则没办法完成动态代理。
CGLIB动态代理缺点
被代理类必须不是 final 类。
代码实现代理
代码背景:某人(王五)吃苹果。
简单的代理(耦合性太强,采用继承的方式)不推荐使用
没有代理的情况下:
/**
* 王五
* @author oukele
*/
public class WangWu {
/**
* 吃苹果
*/
public void eatApple(){
System.out.println("王五吃苹果!");
}
}
运行结果:
public static void main(String[] args) {
WangWu wangWu = new WangWu();
wangWu.eatApple();
}
王五吃苹果!
有代理的情况下:
(张三将削好的苹果递给王五,然后王五吃苹果!)
/**
* 张三
* @author oukele
*/
public class ZhangSan extends WangWu {
@Override
public void eatApple() {
System.out.println("张三从众多苹果中挑了一个苹果。");
System.out.println("1. 然后拿去洗了洗...。");
System.out.println("2. 将苹果鲜红的外衣悄悄的脱掉...。");
System.out.println("N. N步操作在这里省略...。");
System.out.println("将处理好的苹果递给王五。");
// 王五吃苹果
super.eatApple();
}
}
运行结果:
public static void main(String[] args) {
ZhangSan zhangSan = new ZhangSan();
zhangSan.eatApple();
}
张三从众多苹果中挑了一个苹果。
- 然后拿去洗了洗...。
- 将苹果鲜红的外衣悄悄的脱掉...。
N. N步操作在这里省略...。
将处理好的苹果递给王五。
王五吃苹果!
上面的这些例子就是一个简单的代理行为。这个简单代理,耦合性太强了。作为演示就好了。
1、静态代理
代码背景:还是上面的例子,某人(王五)吃苹果。
第一步:创建 服务接口
/**
* 吃的服务行为接口
* @author oukele
*/
public interface Eat {
/**
* 吃的行为
*/
void eat();
}
第二步:实现服务接口
/**
* 王五(被代理类)
* @author oukele
*/
public class WangWu implements Eat {
@Override
public void eat() {
System.out.println("王五吃苹果!");
}
}
第三步:创建代理类(让张三来帮忙削皮)
/**
* 张三(代理类)
*
* @author oukele
*/
public class ZhangSan implements Eat {
/**
* 被代理类(王五)
*/
private Eat eat = null;
public ZhangSan(Eat eat){
this.eat = eat;
}
@Override
public void eat() {
System.out.println("0.-----张三的操作-----");
System.out.println("1.张三帮王五削好了苹果。");
// 王五吃苹果
eat.eat();
System.out.println("3.王五吃完了,张三顺便帮忙收拾好。");
}
}
第四步:进行测试
public static void main(String[] args) {
System.out.println("-------------------没有代理----------------------");
// 被代理类
WangWu wangWu = new WangWu();
wangWu.eat();
System.out.println("-------------------没有代理----------------------");
System.out.println();
System.out.println("-------------------有代理----------------------");
// 代理类
ZhangSan zhangSan = new ZhangSan(wangWu);
zhangSan.eat();
System.out.println("-------------------有代理----------------------");
}
运行结果:
-------------------没有代理----------------------
王五吃苹果!
-------------------没有代理-----------------------------------------有代理----------------------
0.-----张三的操作-----
1.张三帮王五削好了苹果。
王五吃苹果!
3.王五吃完了,张三顺便帮忙收拾好。
-------------------有代理----------------------
2、JDK动态代理
代码背景:还是上面的例子,某人(王五)吃苹果。
第一步:创建 服务接口
/**
* 吃的服务行为接口
* @author oukele
*/
public interface Eat {
/**
* 吃的行为
*/
void eat();
}
第二步:实现服务接口
/**
* 王五(被代理类)
* @author oukele
*/
public class WangWu implements Eat {
@Override
public void eat() {
System.out.println("王五吃苹果!");
}
}
第三步:创建代理类并实现 InvocationHandler 接口,这次就不叫 张三来帮忙了
/**
* 使用JDK内置的Proxy实现动态代理(代理类)
* @author oukele
*/
public class JdkProxy implements InvocationHandler {
/**
* 被代理类
*/
private Object object = null;
private JdkProxy(){};
public JdkProxy(Object object){
this.object = object;
}
/**
* 用户调用代理对象的什么方法,都是在调用处理器的invoke方法。【被拦截】
* @param proxy 被代理后的对象
* @param method 将要被执行的方法
* @param args 调用方法时需要的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("某某人....帮王五削好苹果。");
Object invoke = null;
try {
invoke = method.invoke(object, args);
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("出现异常:" + e.getMessage());
}
System.out.println("某某人....帮忙收拾好现场。");
return invoke;
}
}
第四步:进行测试
public static void main(String[] args) {
Eat eat = (Eat) Proxy.newProxyInstance(
WangWu.class.getClassLoader(),
WangWu.class.getInterfaces(),
new JdkProxy(new WangWu())
);
eat.eat();
}
注意 Proxy.newProxyInstance() 方法接受三个参数
1. ClassLoader loader: 指定当前目标对象使用的类加载器,获取加载器的方法是固定的。A
2. Class<?>[] interfaces: 指定目标对象实现的接口类型。 B
3. InvocationHandler: 指定动态处理器,执行目标对象的方法时,触发事件处理器的方法。C
大白话:
A: 生成代理对象使用哪个类装载器【一般我们使用的是被代理类的装载器】
B: 生成哪个对象的代理对象,通过接口指定【指定要被代理类的接口】
C: 生成的代理对象的方法里干什么事【实现InvocationHandler接口,我们想怎么增强原有对象的功能就随意怎么增强。】
运行结果:
某某人....帮王五削好苹果。
王五吃苹果!
某某人....帮忙收拾好现场。
3、CGLIB动态代理
其原理:通过字节码技术为一个类创建子类。
代码背景:还是上面的例子,某人(王五)吃苹果。
注意:需要 cglib 的依赖;
此处使用的版本为
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
1、第一步:创建一个 王五 类
/**
* 王五
* @author oukele
*/
public class WangWu {
/**
* 吃苹果
*/
public void eatApple(){
System.out.println("王五吃苹果!");
}
}
2、第二步:创建 CGLIB 代理类 并实现 MethodInterceptor 接口
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Cglib 动态代理类
* @author oukele
*/
public class CglibProxy implements MethodInterceptor {
/**
*
* @param o 由CGLib动态生成的代理类实例
* @param method 上文中实体类所调用的被代理的方法引用
* @param objects 参数值列表
* @param methodProxy 生成的代理类对方法的代理引用
* @return 代理实例的方法调用返回的值
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("某某人....帮王五削好苹果。");
Object invoke = null;
try {
invoke = method.invoke(o, objects);
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("出现异常:" + e.getMessage());
}
System.out.println("某某人....帮忙收拾好现场。");
return invoke;
}
}
3、第三步:进行测试
public static void main(String[] args) {
// 增强器,动态代码生成器
Enhancer enhancer = new Enhancer();
// 设置 被代理类
enhancer.setSuperclass(WangWu.class);
// 放置 代理类
enhancer.setCallback(new CglibProxy());
// 动态生成字节码并返回代理对象
WangWu wangWu = (WangWu) enhancer.create();
wangWu.eat();
// 简写
WangWu wangWu1 = (WangWu)Enhancer.create(
WangWu.class,
new CglibProxy()
);
wangWu1.eat();
}
运行结果:
某某人....帮王五削好苹果。
王五吃苹果!
某某人....帮忙收拾好现场。
小结:
JDK 动态代理 和 CGLIB 字节码生成的区别?
JDK动态代理
只能对实现了接口的类生成代理
CGLIB动态代理
针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,
并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final.
JDK 代理 与 CGLIB 代理 中如何选择?
目标对象是否实现了接口
是
采用JDK的动态代理
否
采用CGLIB动态代理
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架