一、什么是代理
Java代理(Proxy)是Java编程语言中一个非常重要的概念,它主要用于在不修改原有目标类代码的前提下,增加或修改目标类的行为。代理模式属于设计模式中的结构型模式,它通过引入一个代理类来间接访问目标对象,从而实现增强原有对象功能的目的。
代理本质上是一种设计模式。经常应用于Java训练当中。
例:以银行转账系统为例(包含验证、转账、服务三个方法)
①不使用代理
如果正常写方法,不做任何强调,可能会将三个方法写到一起,混写成一个大的方法。如下图所示。
直接混写成一个大的方法,其实并不好,因为完成验证、完成转账、和事后服务、都是及其复杂的流程,如果都写在一起的话,会使代码功能量太大。所以应该想方法降低功能量。
②使用代理
参照上文,为了降低代码的功能量,很多人的思路为将每个功能单独写一个方法,此时每个方法都能完成单独的功能,不会造成大量的冗余,是可行的。
但是还是没有考虑到扩容的问题。例如最开始只有银行转账,但现在要求开放对支付宝,微信的转账,即对其他公司开放转账的接口。以支付宝为例,将验证和服务交给支付宝,而核心功能即转账功能由银行负责,即代理模式。
不允许其他人接触核心业务,但可以将核心业务交给别人去代理,用户只能访问代理的部门、无法接触到核心业务。(可以理解为买车,大多数人买车只能去4s店去购买,而不能去工厂直接购买。)
二、代理模式的好处
①防止用户直接访问核心方法,带来一系列不必要的危机。
②能够对核心方法进行功能的增强
三、java代理
java代理分为静态代理(基于jdk实现的静态代理)和动态代理(基于jdk实现的动态代理、基于CGLB实现的动态代理)。
四、静态代理
1、什么是静态代理(定义、流程)
静态代理是指在程序运行前就已经存在代理类的字节码文件,代理类和目标类的关系在运行前就已确定,且代理类通常是手工编写的。
其模式为由核心类(目标类)生成核心对象(目标对象),由代理类生成代理对象,在由代理对象代理核心对象,而核心类和代理类都会实现同一个接口。
接口起到了一个沟通(通知)的作用,即通知代理类所要代理的核心是什么。
2、静态代理的使用(以银行转账系统为例)
首先实现一个接口(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的转账方法在这个过程中也完成了业务的增强。
3、静态代理的优点
①代理对象和目标对象在编译时就确定了关系,所以这种代理方式的安全性较高。
②代理类可以拥有额外的功能,比如可以在调用目标方法前后增加一些额外的操作。
4、静态代理的缺点
①用一个代理类代理多个目标类是很难实现的,工作量巨大。
②一旦目标接口增加方法,代理类也需要修改,不符合开闭原则(对扩展开放,对修改关闭)
举例:衣服工厂和鞋子工厂
首先定义一个Clothes接口和一个核心方法ByClothes,在创建一个核心类ClothesFactory并实现核心方法ByClothes,接着创建代理类XSD,实现核心方法,首先第一步代理目标类,创建ClothesFactory对象,然后实现代理类的核心方法,调用核心类对象的核心方法。然后添加yige服务方法。在创建一个Test类测试。
接着定义一个Shoes接口和一个核心方法ByShoes,在创建一个核心类ShoeFactory并实现核心方法Byshoes。接着在代理类继承Shoes接口并 在代理类当中创建ShoeFactory对象,并实现Shoes接口当中的核心方法。
此时我们发现每增加一个目标类,就需要创建新的接口并在代理类当中不断进行扩充。每个代理对象代理的目标太多,模式图如下。
五、动态代理
1、什么是动态代理
动态代理是指在程序运行时动态地创建代理类,无需程序员手动编写代理类的源代码。动态代理主要依靠JDK的反射(Reflection)API来实现。Java中提供了两种动态代理机制:JDK动态代理和CGLIB动态代理。
基于上述问题就引出了动态代理。以上述问题为例(衣服工厂、鞋子工厂)。一个代理类可以实现多个的代理对象,每个代理对象只匹配一个目标对象,即代理类动态的生成一个代理对象去匹配一个目标对象。
而对于静态代理来说,哪怕如动态代理一样生成一个新的对象,新的对象依旧包含所有目标对象,无法单独匹配一个对象,比如在上述举例中Test类中创建两个XSD对象,其展示图和内存图如下。
2、动态代理的使用(以上述静态代理缺点当中的示例讲述,衣服工厂和鞋子工厂)
创建一个动态代理类即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+"的鞋子");
}
}
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("销售店进行一系列的服务");
}
}
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");
}
}
3、JDK动态代理
①基于接口的动态代理。
②JDK提供了一个Proxy类和InvocationHandler接口来生成动态代理类的对象。
③Proxy类提供了创建动态代理类和实例的静态方法。
④InvocationHandler接口的实现类负责自定义接口中方法的实现逻辑。
4、CGLIB动态代理
①基于类的动态代理,可以代理没有实现接口的类。
②CGLIB是一个强大的、高性能的代码生成库,被广泛应用于AOP(面向切面编程)框架中。
③通过继承目标类来创建代理对象,因此目标类不能为final类,且目标类中的final方法也不能被代理。
六、使用场景
①静态代理:
适用于代理关系明确且目标对象数量有限的情况。
②动态代理:
适用于代理关系不确定、目标对象数量多或者需要在运行时动态生成代理类的情况。特别是在AOP(面向切面编程)框架中,动态代理得到了广泛应用,如Spring AOP就是通过动态代理来实现的。
总之,Java代理是Java编程中一种非常有用的技术,通过代理模式,可以在不修改原有代码的基础上,为系统增加新的功能或修改原有功能,从而提高系统的灵活性和可扩展性。