【Java基础】代理
文章目录
- 代理
- 代理模式的优点
- 代理模式类型
- 基于JDK的静态代理
- 基于JDK的动态代理
代理
一种设计模式,不允许用户直接访问核心功能,而是通过代理来访问核心类的核心功能
举个例子,如果我们现在需要一个银行转账系统,我们需要在一个Java类中实现:
- 转账方法(核心)
- 验证用户身份、验证金额
- 一些后续服务等
初学者很容易将这些方法都写在同一个Java类中,然而以上三个方法其实每一个都是一个很复杂的流程,写在一起会导致当前类的功能量太大,所以我们想到了一个办法:将这些方法分开写,不再写到同一个方法中,将最核心的业务,也就是转账业务,由支付宝或者微信代理完成,而其他的服务由微信或者支付宝自身完成,用户通过访问支付宝代理来完成最核心的转账业务。——也就是代理模式,用户只能访问代理,而不能访问核心功能
代理模式的优点
- 防止用户直接访问核心方法带来一些不必要的危机(代码冗余等)
- 能够对核心方法进行功能的增强
代理模式类型
代理模式分为三类:基于JDK的静态代理、基于JDK的动态代理、基于CGLB的动态代理,下面我们详细说一下每一种代理模式:
基于JDK的静态代理
静态代理中,我们需要实现核心类与核心对象,代理类与代理对象,以及一个定义核心类中核心方法的接口,代理类与核心类中都需要对接口中核心方法的实现,并在代理类中创建核心对象,进行代理,在用户访问时,通过创建代理对象来访问核心对象,来完成我们的代理流程
我们依然以上面的银行转账系统为例子,我们先定义一个Pay
接口,其中定义我们的转账方法(核心方法),但不实现
public interface Pay {
// 定义核心方法
public void pay(String A, String B, Double money);
}
再定义Bank
核心类,继承Pay
接口并实现接口中的核心方法Pay()
:
public class Bank implements Pay {
@Override
public void pay(String A, String B, Double money) {
System.out.println(A + "给" +B+ "转账了" +money+ "元");
}
}
定义代理类,我们假定为支付宝AliPay
,让代理类也继承接口并实现其中的核心方法,其中也包括对核心方法功能的增强,如验证功能以及后续服务功能:
public class AliPay implements Pay {
// 1. 定义一个好被代理的类--->核心类
private Bank bank = new Bank();
public void check(String A, String B,Double money){
System.out.println("对A进行了验证");
System.out.println("对B进行了验证");
System.out.println("对金额进行了验证");
}
private void service(){
System.out.println("转账完成后进行的事后服务");
}
@Override
public void pay(String A, String B, Double money) { // 核心方法的实现
check(A,B,money);
bank.pay(A,B,money);
service();
}
}
最后我们创建Test
测试类,来测试我们以上的代理流程:
public class Test {
public static void main(String[] args) {
AliPay aliPay = new AliPay();
aliPay.pay("zhangsan", "lisi", 100.12);
}
}
我们可以看到,在用户界面,也就是Test
类中,用户只能通过访问代理,也就是创建aliPay
对象来对Bank
核心类中的核心方法Pay()进行访问,而不是直接对核心类中的核心方法进行访问,这样就完成了代理,下面是内存图:
基于JDK的动态代理
先考虑一个问题,如果我们的代理需要同时代理很多个核心类的话,那么会出现什么情况?比如一个商店,对服装工厂、鞋子工厂、裤子工厂等等多个工厂同时进行代理,那么在这个代理类的代理对象中,我们就需要创建很多个核心对象,来完成我们的代理流程,并且每多代理一个工厂就要手动增加一个核心对象,这也是静态代理的一个缺陷:同一个代理类代理多个目标类是很难实现的
那么我们就需要用动态代理模式来完成这个功能。与静态代理不同的是,在动态代理中,每一个核心对象会由一个独立的代理对象进行代理,而不是全部都由同一个代理对象代理,也就是一对一模式,如下图
那我们如何实现这个一对一模式呢?
我们商店的例子中,商店作为动态代理,其中包含一个Object
对象,以及一个将核心对象传入代理对象的构造器:
public class Shop {
private Object object;
public Shop(Object o){ // 传入目标类的目标对象(代理类的代理对象)
object = o;
}
}
public class Test {
public static void main(String[] args) {
ClothesFactory clothesFactory = new ClothesFactory();
Shop shop = new Shop(clothesFactory);
}
}
这样在用户访问时,每创建一个代理对象shop
,就会将一个核心对象的地址传给代理对象,进行代理,也就是完成了上面说的一对一模式
静态代理中,我们一一实现了每一个核心类的核心方法,继承了相对应的接口,动态代理中同样需要实现方法,但是并不像静态代理那样分别进行继承和实现,这里我们定义一个返回值为Object
类型的方法getInstance()
,通过这个方法我们可以获取到核心类的类对象,从而完成对其中方法的实现
public Object getProxyInstance(){ // object.getClass() 获取目标类的类对象
// object.getClass().getInterfaces() 获取目标类所继承的接口,告知代理类所代理的核心功能是什么
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(),this);
} // 这里的返回值是一个对象
之后我们需要对核心类中的核心方法进行调用,在上一步我们已经知道了核心类的类对象信息,那么我们就可以通过反射来调用其中的方法,这里我们需要继承一个接口InvocationHandler
,来对其中的invoke()
方法进行重写,来调用核心类中的核心方法(暂时不做深究,和反射中的invoke()
方法是一个方法)
public class Shop implements InvocationHandler {
//三个参数的讲解
//1.Object:jdk创建的代理类,无需赋值
//2.Method:目标类当中的方法,jdk提供,无需赋值
//3.Object[]:目标类当中的方法的参数,jdk提供,无需赋值
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(object,args);
service();
return null;
}
}
那么我们在Test
类中获取到相对应的类信息用什么来接收呢?答案是需要用相关接口进行接收。观察整个流程,没有任何一个地方可以调用到其中的核心方法,如果我们直接创建子类ClothesFactory
对象对其中的核心方法进行调用的,这种方式并没有通过任何代理,如下:
public class Test {
public static void main(String[] args) {
ClothesFactory clothesFactory = new ClothesFactory(); // 这里利用反射创建对象也可以
clothesFactory.BuyClothes("XL");// 这种方式并没有走任何代理,只是直接调用了ClothesFactory中的BuyClothes()方法
}
}
所以只能通过接口进行调用,这里是一个多态,接口Clothes
为父类,创建父类引用,让他指向子类对象,然后在调用方法时,因为子类ClothesFactory
对方法进行了重写,那么只能访问重写方法,这样就完成了整个代理流程,访问到了我们的核心方法