java中的代理模式
代理模式的原理
使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
简单例子:
小强意外成为了一个网红明星,此时他请了一个专业的经纪人进行各种事务的管理,小强的演出活动啊都得经过经纪人的同意。此时小强是被代理对象,经纪人是代理对象。
此时快乐大本营想请小强去参加节目,需要找到小强的经纪人,小强的经纪人然后进行排挡,找小强去完成唱歌,才艺展示等动作。
静态代理
代理对象和被代理对象(目标对象)实现同一接口。优点是不用修改目标对象的源码,扩展其功能。
特点
- 在编译期间,代理类和被代理对象(目标对象)的类都确定下来了,不利于程序的扩展
- 每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。
举例
创建一个接口,这个接口是代理工厂(代理类)和Nike工厂(被代理类)的公共接口,
通过代理工厂的方法调用同时也调用了Nike工厂的同名方法,完成不修改被代理类的源码而扩展其功能。
共同接口
interface ClothFactory{
void produceCloth();
}
代理类
// 代理类
class ProxyClothFactory implements ClothFactory {
private ClothFactory factory;//用被代理类对象进行实例化
public ProxyClothFactory(ClothFactory factory) {
this.factory = factory;
}
@Override
public void produceCloth() {
System.out.println("代理工厂做一些准备工作");
factory.produceCloth();
System.out.println("代理工厂做一些后续的工作");
}
}
被代理类
// 被代理类
class NikeClothFactory implements ClothFactory{
@Override
public void produceCloth() {
System.out.println("nike工厂生产运动服");
}
}
测试代码
public class StaticProxyTest {
public static void main(String[] args) {
// 创建被代理类对象
NikeClothFactory nikeClothFactory = new NikeClothFactory();
// 创建代理类对象
ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nikeClothFactory);
proxyClothFactory.produceCloth();
}
}
输出结果
代理工厂做一些准备工作
nike工厂生产运动服
代理工厂做一些后续的工作
动态代理
动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。
特点
代理对象不需要实现接口,但是要求被代理对象(目标对象)必须实现接口,否则不能使用动态代理。
相关API
Proxy
:专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
直接创建一个动态代理对象
参数详解:
ClassLoader loader
:和被代理对象使用相同的类加载器Class<?>[] interfaces
:和被代理对象具有相同的行为,实现相同的接口InvocationHandler h
:得到InvocationHandler接口的实现类实例
实现动态代理
实现动态代理,要解决的问题
- 如何根据加载到内存中的被代理类,动态创建一个代理类及其对象;
- 当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法。
具体步骤如下:
-
创建被代理类和接口
接口:Human接口
//共同接口 interface Human{ String getBelief(); void eat(String food); }
被代理类:SuperMan类
// 被代理类 class SuperMan implements Human{ @Override public String getBelief(){ return "我能飞"; } @Override public void eat(String food){ System.out.println("我喜欢吃"+food); } }
-
创建一个用来生产代理类的工厂,该工厂有一个静态方法
getProxyInstance
可以返回一个代理类的对象,该方法需传入被代理类对象。class ProxyFactory{ // 调用此方法,返回一个代理类的对象。需要传入被代理类对象 public static Object getProxyInstance(Object obj){ MyInvocationHandler handler = new MyInvocationHandler(); handler.bind(obj); // 哪个类加载的?被代理类 // 被代理类实现了哪个接口?得到被代理类实现的全部接口 // 得到InvocationHandler接口的实现类实例 return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler); } }
-
创建一个实现接口
InvocationHandler
的类,实现inoke方法,以完成代理的具体操作。当我们通过代理类的对象调用方法a时,就会自动的调用如下方法invoke(),将被代理类要执行的方法a的功能声明在invoke()中。class MyInvocationHandler implements InvocationHandler{ private Object obj;//需要使用被代理类的对象进行赋值 public void bind(Object obj){ this.obj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // obj:被代理类对象 // method:即为代理类对象调用的方法,此方法作为被代理类对象要调用的方法 // args: 方法调用时所需要的参数 Object returnValue = method.invoke(obj,args); //该返回值作为当前类中的invoke()的返回值 return returnValue; } }
-
测试类
public class ProxyTest { public static void main(String[] args) { SuperMan superMan = new SuperMan();//创建一个被代理类对象 // 动态创建代理类 Human proxyInstance = (Human)ProxyFactory.getProxyInstance(superMan); String belief = proxyInstance.getBelief(); System.out.println(belief); proxyInstance.eat("肉夹馍"); } }
-
输出结果
我们发现,只需要知道接口和被代理类,就能实现创建一个代理类。把上面静态代理部分的例子拿过来
NikeClothFactory nike = new NikeClothFactory();
ClothFactory proxyInstance1 = (ClothFactory) ProxyFactory.getProxyInstance(nike);
proxyInstance1.produceCloth(); //nike工厂生产运动服
动态代理实现两种方式
动态代理
特点:字节码随用随创建,随用随加载;
作用:不修改源码的基础上对方法增强。
基于接口的动态代理
提供者: JDK 官方的 Proxy 类。
要求:被代理类最少实现一个接口。
实现步骤
-
创建一个接口,该接口可以理解成对生产厂家的规范
/** * 对生产厂家要求的接口 */ public interface IProducer { /** * 销售 * @param money */ public void saleProduct(float money); /** * 售后 * @param money */ public void afterService(float money); }
代理类:是一个生产者,它实现了接口,具有接口中的方法,也就是说符合生产厂家的要求,产品销售和售后是ok的。
/** * 一个生产者 */ public class Producer implements IProducer{ /** * 销售 * @param money */ public void saleProduct(float money){ System.out.println("销售产品,并拿到钱:"+money); } /** * 售后 * @param money */ public void afterService(float money){ System.out.println("提供售后服务,并拿到钱:"+money); } }
-
模拟一个消费者,如果该消费者如果没有通过代理商去买电脑,而是直接去到厂家处购买。但现实中,很少有人这样购买。一般都是厂家把产品交给代理商进行销售以及提供售后服务,顾客只需跟代理商进行交易,而不直接与厂家进行联系。
没有代理之前
Producer producer1 = new Producer(); producer.saleProduct(10000f);//销售产品,并拿到钱:10000.0
有了代理,代理要从中提取20%的手续费。那么生产者只能得到80%的钱
public class Client { public static void main(String[] args) { final Producer producer = new Producer(); IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //提供增强的代码 Object returnValue = null; //1.获取方法执行的参数 Float money = (Float)args[0]; //2.判断当前方法是不是销售 if("saleProduct".equals(method.getName())) { returnValue = method.invoke(producer, money*0.8f); } return returnValue; } }); proxyProducer.saleProduct(10000f); } }
涉及的参数:
Proxy类
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) ClassLoader:类加载器 代理谁写谁的类加载器,它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。 Class[]:字节码数组,它是用于让代理对象和被代理对象有相同方法。固定写法。 InvocationHandler:用于提供增强的代码 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。此接口的实现类都是谁用谁写。
InvocationHandler接口
public Object invoke(Object proxy, Method method, Object[] args) 作用:执行被代理对象的任何接口方法都会经过该方法 方法参数的含义 proxy 代理对象的引用 method 当前执行的方法 args 当前执行方法所需的参数
基于子类的动态代理
提供者:第三方的 cglib。
要求:被代理类不能是最终类(用 final 修饰的类)。
涉及的类:Enhancer
如何创建代理对象:使用Enhancer类中的create(Class,Callback)方法
create方法的参数:
Class:字节码,它是用于指定被代理对象的字节码。
Callback:如何代理,即就是用于提供增强的代码。我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。此接口的实现类都是谁用谁写。我们一般写的都是该接口的子接口实现类:MethodInterceptor
还是消费者买产品的例子,只不过不让它实现接口。
举例:
生产者
/**
* 一个生产者
*/
public class Producer {
/**
* 销售
* @param money
*/
public void saleProduct(float money){
System.out.println("销售产品,并拿到钱:"+money);
}
/**
* 售后
* @param money
*/
public void afterService(float money){
System.out.println("提供售后服务,并拿到钱:"+money);
}
}
消费者
/**
* 模拟一个消费者
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
* @param methodProxy :当前执行方法的代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(12000f);
}
}
参考链接:
本文作者:benjieqiang
本文链接:https://www.cnblogs.com/benjieqiang/p/11309733.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步