java代理Proxy以及实际PRC场景中的使用

本文介绍了Java中的代理模式,包括静态代理、基于JDK的动态代理和CGLIB动态代理。静态代理通过接口实现扩展功能,但类数量多且不易维护。JDK动态代理在运行时生成代理对象,适用于目标对象实现接口的情况。CGLIB代理则通过继承目标类实现,适用于无接口的情况。动态代理在实际项目中广泛应用,如MyBatis和Spring Cloud的接口调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

代理(Proxy)

代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.

这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法

定义:给目标对象提供一个代理对象,并且由代理对象控制对目标对象的引用

目的

    ①:通过代理对象的方式间接的访问目标对象,防止直接访问目标对象给系统带来不必要的复杂性
    ②:通过代理业务对原有业务进行增强

java当中有三种方式来创建代理对象:

  • 静态代理
  • 基于jdk(接口)的动态代理
  • 基于CGLLIB(父类)的动态代理。

静态代理

创建一个 Image 接口和实现了 Image 接口的实现类。ProxyImage 是一个代理类,屏蔽具体的实现过程并且提供比真实实现过程更多的前后增强操作。

ProxyPatternDemo 类使用 ProxyImage 来获取要加载的 Image 对象,并按照需求进行显示

image.png

public interface Image { 
    void display(); 
}
public class RealImage implements Image { 
    private String fileName; 
    
    public RealImage(String fileName){ 
    this.fileName = fileName; loadFromDisk(fileName); 
    } 
    
    @Override public void display() {
    System.out.println("Displaying " + fileName);
    }
      private void loadFromDisk(String fileName){
    System.out.println("Loading " + fileName);
    } 
 }
 
 
 /**proxy**/
 public class ProxyImage implements Image{ 
 private RealImage factory; 
 private String fileName; 
 
	public proxy(String fileName) {
		// TODO Auto-generated constructor stub
                if(realImage == null){
                 factory = new RealImage(fileName); 
                 } 
		this.fileName = fileName;
	}

 
         @Override public void display() { 
             //before
             System.out.println("image before.... ");
             realImage.display(); 
             System.out.println("image after.... ");
             //after
             
         } 
 
 }
 
 public class ProxyTestDemo { 
     public static void main(String[] args) { 
     Image image = new ProxyImage("test_10mb.jpg"); // 图像将从磁盘加载 image.display();  
     // 图像不需要从磁盘加载 
     image.display(); 
     } 
 }
 
 

细节:我们的真实实现对象必须实现我们的接口,同时代理对象也必须实现这一接口

静态代理存在哪些问题?

静态代理类优缺点:

优点:可以在不修改目标代码的情况下,扩展额外功能

缺点:因为代理类和目标类必须实现相同的接口,所以会有很多代理类,类太多,同时,一旦接口增加方法,目标对象很代理对象都需要维护

违反了开闭原则:程序对访问开放,对修改关闭,换句话来说,当需求发生变化时,我们可以增加新模块来解决新需求,而不是通过改变原来的代码来解决我们的新需求,这里我们的新需求都是通过ProxyImage中添加代码实现的。

假设image是一个定义的播放工具,如同数据库工具定义的规范。现在出了播放图片,又要新增一个播放视频的功能vedioImage,当前ProxyImage里面定义的RealImage是写死的。难道要新增一个vedioImage变量么,类似的在数据库驱动程序中也存在类似现象,javajdk只会定义广义统一的流程接口规范,每个数据库厂商根据该jdk接口自己去实现自家数据库的链接驱动。因此调用驱动时底层实现链接逻辑并不指定具体的实现类型,而是由上层启动类在外部指定,从而动态的加载相应的驱动类,并执行对应的方法流程。

JDK动态代理

动态代理的主要特点就是能够在程序运行时JVM才为目标对象生成代理对象。

常说的动态代理也叫做JDK 代理也是一种接口代理,之所以叫做接口代理,是因为被代理的对象也就是目标对象必须实现至少一个接口,没有实现接口不能使用这种方式生成代理对象,JDK中生成代理对象的代理类就是Proxy,所在包是java.lang.reflect.

import java.lang.annotation.Target;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
 
/**
*动态代理工厂:不具体指定代理对象,代理类也不用实现代理接口,由底层JDK根据代理接口类型自动生成代理对象
 * @author bamboo
 * @description:
 * @date 2020/12/14
 */
public class ProxyFactory implements InvocationHandler {
 
    private  Object target;
    public StarInvocationHandler(Object target) {
        this.target = target;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 
        System.out.println("展示前工作");
        //调用目标对象的目标方法
        method.invoke(target,args);
        System.out.println("展示后工作");
        return null;
    }
    
    //代理对象初始化:利用反射创建代理对象
    public Object getProxyInstance() {
		// TODO Auto-generated method stub
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
	}
}



public class JdkTest {
	public static void main(String[] args) {
                //由调用层决定具体由谁来执行
                RealImage realImage = new RealImage();//1
		Image image =(Image) new ProxyFactory(realImage).getProxyInstance();
		image.display("d://image.jpg");
	}
}

JDK代理方式不需要代理对象实现接口,但是实现对象一定要实现接口,但是我们在项目中有很多需要代理的类并没有实现接口,所以这也算是这种代理方式的一种缺陷

优点:代理类和实现类脱离了依赖关联,不用强绑定,完全由调用方使用时动态反射出具体的实现类
缺点:代理的对象必须有实现类并且重写了该接口,否则无法实现代理。

比如,我们在JdkTest中//1行并不知道具体实现类,更确切说压根就没有实现类,那么通用驱动就不用写了吗?

cglib动态代理

如果我们 需要给没有是实现任何接口的目标类生成代理对象,JDK方式是做不到的。这是就可以使用继承目标类以目标对象子类的方式实现代理,这种方法就叫做Cglib代理,也叫做子类代理,他是在内存中构建一个子类对象从而 实现对目标对象功能的扩展。

Cglib是一个强大的高性能代码生成包,他可以在运行期扩展java类和扩展java接口。它广泛的被许多AOP框架使用,例如Sring AOP和synaop,为他们提供方法的intercepion(拦截)

Cglib包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类。

Cglib子类代理实现方法:

1.需要引入cglib的jar文件 cglib-2.2.2.jar asm-3.3.1.jar,但是Spring的核心包中已经包括了Cglib功能,所以直接引入spring-core-3.2.5.jar即可.
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为final,否则报错
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;



import java.lang.reflect.Method;

/**
*cglib代理:不需要实现类,只需要提供普通类,剩下的工作有底层完成
* @author bamboo
* @description:
* @date 2020/12/14
*/
public class CglibProxyFactory implements MethodInterceptor {

   //维护目标对象
   private Object target;

   public CglibProxyFactory(Object target) {
       this.target = target;
   }

   /**
    * @Description:获得目标类的代理对象
   */
   public Object getProxyObject(){
       //1、工具类
       Enhancer enhancer = new Enhancer();
       //2、设置父类
       enhancer.setSuperclass(target.getClass());
       //3、设置回调
       enhancer.setCallback(this);
       //4、创建代理类
       Object proxyObject = enhancer.create();
       return proxyObject;

   }

   @Override
   public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

       System.out.println("工作前");
       //执行目标对象的目标方法
       method.invoke(target,objects);
       System.out.println("工作后");
       return null;
   }

}

//普通类
class Image {
   void display() {
       System.out.println("display");
   }
}



public class CglibTest {

   public static void main(String[] args) {
       Image image = new Image();
       Image proxyObject = (Image)new CglibProxyFactory(image).getProxyObject();
       proxyObject.sing();
   }
}

总结:其实cglib可jdk的动态代理很类似,最终都是通过被代理类执行原始方法,只是jdk使用的是反射,cglib使用的fastcalss机制

到这三种代理方式我们都介绍完了,下面总结一下:

1、如果目标对象实现了接口,我们就采用JDK方式实现动态代理

2、如果目标对象没有实现接口,我们就需要采用cglib方式实现动态代理;

扩展在实际项目中使用样例:接口+注解的动态代理

mybatis中Map接口定义了方法,不用具体实现接口和xml的sql语句映射,代理类自动完成这些工作。
springCloud中对于feign调用微服务时只依赖了服务方提供的interface方法包,调用方这边并没有imp相关的接口实现类,这个过程也是由代理类完成,从而实现http访问服务方的接口返回结果给调用方

这里以微服务client端为例


/**
 * @author bamboo
 * @description: 接口类
 * @date 2020/12/14
 */
public @interface UserService {

    String  getById(Integer id) ;
}



/**
 * @author bamboo
 * @description: 接口方法执行拦截器:执行接口方法时做增强,使用http调用远程服务并返回值
 * @date 2020/12/14
 *
public class CustomerInvocationHandler implements InvocationHandler {
    //被代理类
    private final Class<?> serviceClass;

    public CustomerInvocationHandler(Class<?> serviceClass) {
        this.serviceClass = serviceClass;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy = " + serviceClass.getName() + ", method = " + method.getName() + ", args = " + Arrays.deepToString(args));
        Socket socket = new Socket("127.0.0.1", 8889);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
        //数据打包
        RpcRequest rpcRequest = new RpcRequest(serviceClass.getName(), method.getName(), method.getParameterTypes(), args);
        //通过socket发送数据
        objectOutputStream.writeObject(rpcRequest);
        //接收返回数据
        ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
        return inputStream.readObject();
    }
}




import java.lang.reflect.Proxy;
 
/**
 * @author bamboo
 * @description: 代理工厂
 * @date 2020/12/1411:47
 */
public class  ProxyFactory {
    public static Object getProxyObject( ClassLoader target,
                                         Class<?>[] interfaces,
                                         InvocationHandler h){
        return Proxy.newProxyInstance(target,interfaces,h);
    }
}

通过RPC远程调用

  • 创建代理类的InvocationHandler,相当于AOP拦截器,使得调用代理类的方法都会转入invoke中执行。
  • 创建代理类(调用方法已经在invoke中完成)。
  • 调用相应的方法。
public class TestRpc {

    public static void main(String[] args) {
        //创建代理类的InvocationHandler拦截器
        CustomerInvocationHandler customerInvocationHandler=new CustomerInvocationHandler(UserService.class);
        //创建代理类
        //代理类会声明被代理类的接口
        UserService userService=(UserService) ProxyFactory.getProxyObject(UserService.class.getClassLoader(),new Class<?>[]{UserService.class},customerInvocationHandler);
        //调用
        String a=userService.getById(1);
        System.out.println("RPC CONNECTED!"+a);
    }
}



-------------------------------------------------------------------------------------
proxy = com.bamboo.demo.rpc.UserService, method = getById, args = [1]
RPC CONNECTED!bamboo

以上过程可以看出代理在我们常用场景中的重要性,不过这种使用也屏蔽了底层具体实现的细节,比如我们使用Mybatis如果不去看源码不关注底层其实直接一个接口就可以完成工作。同样的微服务中,我们只是使用其实只用给调用方一个接口它直接拿着用就可以了,连TestRpc注释调用之前的代码都不需要关注,是我们更加关注业务本身,从而抽繁化简,这才是编程工具使用称心的本心和初衷。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值