代理
什么是代理
代理模式是 Java 中的一种重要设计模式,属于结构型设计模式。其主要作用是通过引入代理对象,在不修改目标类的情况下,扩展或修改目标类的行为。代理模式通常用于控制访问或增强功能。
常见场景:在银行转账系统中,可以通过代理模式实现对验证、转账、服务等功能的分离,避免功能混杂,提升代码可维护性和扩展性。
不使用代理
如果正常写方法,不做任何强调,可能会将三个方法写到一起,混写成一个大的方法。如下图所示。
直接混写成一个大的方法,其实并不好,因为完成验证、完成转账、和事后服务、都是及其复杂的流程,如果都写在一起的话,会使代码功能量太大。所以应该想方法降低功能量。
使用代理
参照上文,为了降低代码的功能量,很多人的思路为将每个功能单独写一个方法,此时每个方法都能完成单独的功能,不会造成大量的冗余,是可行的。
但是还是没有考虑到扩容的问题。例如最开始只有银行转账,但现在要求开放对支付宝,微信的转账,即对其他公司开放转账的接口。以支付宝为例,将验证和服务交给支付宝,而核心功能即转账功能由银行负责,即代理模式。
不允许其他人接触核心业务,但可以将核心业务交给别人去代理,用户只能访问代理的部门、无法接触到核心业务。(可以理解为买车,大多数人买车只能去4s店去购买,而不能去工厂直接购买。)
代理模式的好处
防止用户直接访问核心业务,避免潜在的安全问题。
扩展核心业务功能,如添加日志、权限验证等。
java代理
Java 代理分为静态代理和动态代理两种实现方式:
静态代理:在编译期间确定代理类,代理类和目标类的关系在编译期就确定下来。
动态代理:在运行时动态生成代理类,不需要程序员手动编写代理类。
静态代理
什么是静态代理(定义、流程)
静态代理是在编译期间确定代理类,代理类实现与目标类相同的接口。通过代理对象代理核心对象,实现对核心业务的扩展。
静态代理的使用(以银行转账系统为例)
首先实现一个接口(ZhuanZhang)并定义一个核心方法zhuanzhang(),在创建一个YinHang类(核心类)去继承该接口,并实现核心方法。在创建一个ZhiFuBao类(代理类)继承接口并实现核心方法(先实现但不填写内容)。
在代理类中,首先定义好一个被代理的类(核心类即YinHang类),然后在核心方法中调用该对象的zhuanzhang()方法即核心类的核心方法。然后实现功能的增强添加一个身份验证、金额验证和事后服务功能。最后在创建一个Test类本身无任何实际意义仅用来测试。代码如下。
接口:
public interface ZhuanZhang {
public void zhuanzhang(String A,String B,Double money);
}
核心类(YinHang):
public class YinHang implements ZhuanZhang{
public void zhuanzhang(String A, String B, Double money) {
System.out.println(A+"给"+B+"转账了"+money);
}
}
代理类(ZhiFuBao):
public class ZhiFuBao implements ZhuanZhang{
private YinHang yinHang =new YinHang();
private void yanzheng(String A,String B,Double money){
System.out.println("对A进行了身份验证");
System.out.println("对B进行了身份验证");
System.out.println("对转账金额进行了身份验证");
}
private void fuwu(){
System.out.println("转账完成后进行服务");
}
@Override
public void zhuanzhang(String A, String B, Double money) {
yanzheng(A,B,money);
yinHang.zhuanzhang(A,B,money);
fuwu();
}
}
Test类:
public class Test {
public static void main(String[] args) {
ZhiFuBao zhiFuBao=new ZhiFuBao();
zhiFuBao.zhuanzhang("张三","李四",100.00);
}
}
结果:
流程图:
首先main()方法入栈,创建zhifubao对象,其中包括转账的方法、验证和服务方法。还会创建yinhang对象。该对象中也有转账方法。首先是zhifubao的转账方法入栈,然后调用yinhang的转账方法在这个过程中也完成了业务的增强。
静态代理的优点
编译期间确定代理对象,具有较高的安全性。
可以对目标方法进行增强,如添加日志、权限验证等功能。
静态代理的缺点
每个目标类都需要对应一个代理类,维护成本高。
如果目标接口修改,代理类也需要同步修改,违反了开闭原则。
举例:衣服工厂和鞋子工厂
首先定义一个Clothes接口和一个核心方法ByClothes,在创建一个核心类ClothesFactory并实现核心方法ByClothes,接着创建代理类XSD,实现核心方法,首先第一步代理目标类,创建ClothesFactory对象,然后实现代理类的核心方法,调用核心类对象的核心方法。然后添加yige服务方法。在创建一个Test类测试。
接着定义一个Shoes接口和一个核心方法ByShoes,在创建一个核心类ShoeFactory并实现核心方法Byshoes。接着在代理类继承Shoes接口并 在代理类当中创建ShoeFactory对象,并实现Shoes接口当中的核心方法。
此时我们发现每增加一个目标类,就需要创建新的接口并在代理类当中不断进行扩充。每个代理对象代理的目标太多,模式图如下。
动态代理
什么是动态代理
动态代理是指在运行时动态生成代理类,不需要手动编写代理类的代码。Java 中常见的动态代理有两种实现方式:
基于 JDK 的动态代理:只能代理实现了接口的类。
基于 CGLIB 的动态代理:可以代理未实现接口的类,通过生成子类实现代理。
基于上述问题就引出了动态代理。以上述问题为例(衣服工厂、鞋子工厂)。一个代理类可以实现多个的代理对象,每个代理对象只匹配一个目标对象,即代理类动态的生成一个代理对象去匹配一个目标对象。
而对于静态代理来说,哪怕如动态代理一样生成一个新的对象,新的对象依旧包含所有目标对象,无法单独匹配一个对象,比如在上述举例中Test类中创建两个XSD对象,其展示图和内存图如下。
动态代理的使用(以上述静态代理缺点当中的示例讲述,衣服工厂和鞋子工厂)
创建一个动态代理类即DTXSD类,相较于静态代理类XSD来说,之所以每次在生成静态代理类时都会生成一系列目标类的对象是因为静态代理类当中代理了多个目标类,即创建了多个不同目标类的对象。而动态代理类当中则是创建一个Object类型的参数object(Object是所有类型的父类)和一个构造器参数为Object类型的参数,在该构造器中将所传参数的值赋给object(java只有值传递)。可以参考下述流程图。
流程图:
现在只是实现了对象的代理,下面讲述如何实现代理的相关工作,在静态代理中则是继承实现相关的接口,在动态代理中实现相关接口则是写一个getProxyInstance方法,代码如下。其中object为目标类,object.getClass() 获取目标类的类对象,this代表当前对象object.getClass().getInterfaces()获取当前目标类的接口。
public Object getProxyInstance(){ //object.getClass() 获取目标类的类对象
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(),this);
}
然后在如果要调用相关核心方法需要继承一个InvocationHandler接口,实现invoke方法代码如下其三个参数讲解如下
Object:jdk创建的代理类,无需赋值
Method:目标类当中的方法,jdk提供,无需赋值
Object[]:目标类当中的方法的参数,jdk提供,无需赋值
//三个参数的讲解
//1.Object:jdk创建的代理类,无需赋值
//2.Method:目标类当中的方法,jdk提供,无需赋值
//3.Object[]:目标类当中的方法的参数,jdk提供,无需赋值
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(object,args);
fuwu();
return null;
}
在调用相关核心方法时,必须用相关接口进行接收(想调用相关方法可以用接口或者目标类调用,但不能使用目标类调用。)
ClothesFactory clothesFactory = new ClothesFactory();
DTXSD dtxsd1 = new DTXSD(clothesFactory);
//已经知道了目标类当中的核心方法是什么
Clothes clothes = (Clothes) dtxsd1.getProxyInstance();
clothes.ByClothes("XXXL");
如果不用接口接收,无法调用核心方法,如果用目标类直接调用只能调用核心方法,无法调用其他方法,没有使用任何代理方式,而是直接使用的对象调用方法,代码和流程图如下。
具体代码如下
Clothes接口:
public interface Clothes {
public void ByClothes(String size);
}
ClothesFactory:
public class ClothesFactory implements Clothes {
@Override
public void ByClothes(String size) {
System.out.println("定制一件大小为"+size+"的衣服");
}
}
Shoes接口:
public interface Shoes {
public void ByShoes(String size);
}
ShoeFactory:
public class ShoeFactory implements Shoes{
@Override
public void ByShoes(String size) {
System.out.println("定制一款大小为"+size+"的鞋子");
}
}
Test类:
public class Test {
public static void main(String[] args) {
//代理
ClothesFactory clothesFactory = new ClothesFactory();
DTXSD dtxsd1 = new DTXSD(clothesFactory);
//已经知道了目标类当中的核心方法是什么
Clothes clothes = (Clothes) dtxsd1.getProxyInstance();
clothes.ByClothes("XXXL");
}
}
DTXSD类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DTXSD implements InvocationHandler{
private Object object; //目标类
public DTXSD(Object o) {
object = o;
}
/**
* 动态代理实现相关接口
* 调用该方法你就知道了目标类当中的核心方法是什么
* @return
*/
public Object getProxyInstance(){ //object.getClass() 获取目标类的类对象
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(),this);
}
//三个参数的讲解
//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);
fuwu();
return null;
}
private void fuwu(){
System.out.println("销售店进行一系列的服务");
}
}
JDK动态代理
基于接口的动态代理。
JDK提供了一个Proxy类和InvocationHandler接口来生成动态代理类的对象。
Proxy类提供了创建动态代理类和实例的静态方法。
InvocationHandler接口的实现类负责自定义接口中方法的实现逻辑。
步骤:
实现 InvocationHandler 接口,重写 invoke() 方法。
通过 Proxy.newProxyInstance() 方法生成代理对象。