java中的3种代理模式(静态代理、JDK动态代理、cglib动态代理)

java中的3种代理模式

前言: 代理模式,是属于23种设计模式中的一种,即,代理模式是一种设计模式。

一、代理模式 (Proxy Pattern) 是什么?

代理模式是一种设计模式,简单说就是在不改变源码的情况下,实现对目标对象的功能扩展。

比如有个歌手对象叫Singer,这个对象有一个唱歌方法叫sing()

1 public class Singer{

2     public void sing(){
3         System.out.println("唱一首歌");
4     }  

5 }

假如你希望,通过你的某种方式生产出来的歌手对象,在唱歌前后还要想观众问好和答谢,也即对目标对象Singer的sing方法进行功能扩展。

1 public void sing(){
2     System.out.println("向观众问好"); //不修改源代码,实现新增这个
3     System.out.println("唱一首歌");
4     System.out.println("谢谢大家");   //不修改源代码,实现新增这个
5 }  

但是往往你又不能直接对源代码进行修改,可能是你希望原来的对象还保持原来的样子,又或许你提供的只是一个可插拔的插件,甚至你有可能都不知道你要对哪个目标对象进行扩展。这时就需要用到java的代理模式了。网上好多用生活中的经理人的例子来解释“代理”,看似通俗易懂,但我觉得不适合程序员去理解。程序员应该从代码的本质入手。

二、Java的三种代理模式

Java的三种代理模式 :
静态代理JDK动态代理Cglib动态代理

想要实现以上例子中歌手的需求有三种方式,这一部分我们只看三种模式的代码怎么写,先不涉及实现原理的部分。

  1. 静态代理
 /**
  *  目标类要实现的接口
  */
 public interface ISinger {
     void sing();
 } 
 
 /**
  *  目标类
  */
  public class Singer implements ISinger{
      public void sing(){
          System.out.println("唱一首歌");
     }  
  }
/**
 *  【静态代理模式】   代理类和目标类实现相同的接口
 */
public class SingerProxy implements ISinger{
    private ISinger target;                //注入目标对象,以便调用sing方法
    public SingerProxy(ISinger target){    //代理类的构造方法,传入目标对象
        this.target=target;
    }
    // 对目标对象的sing方法进行功能扩展
    public void sing() {
        System.out.println("向观众问好");    //静态代理前置内容
        target.sing();
        System.out.println("谢谢大家");		 //静态代理后置内容
    }
}
 /**
  *  【静态代理模式】   测试类
  */
  public class Test {
      public static void main(String[] args) {
          //目标对象
          ISinger target = new Singer();
          //代理对象
          ISinger proxy = new SingerProxy(target);  //传入目标对象
          
         //执行的是代理的方法
         proxy.sing();
     }
 }

	运行测试类的结果:
	向观众问好
	唱一首歌
	谢谢大家
总结:
静态代理缺点:代理对象必须提前写出,如果接口层发生了变化,代理对象的代码也要进行维护。
  如果能在运行时动态地写出代理对象,不但减少了一大批代理类的代码,也少了不断维护的烦恼,
  不过运行时的效率必定受到影响。这种方式就是接下来的动态代理。
  1. JDK动态代理 【方法一】
  /**
  *  目标类要实现的接口
  */
 public interface ISinger {
     void sing();
 }
 
 /**
  *  目标类
  */
  public class Singer implements ISinger{
      public void sing(){
          System.out.println("唱一首歌");
     }  
  }
/**
 *        【动态代理模式】          代理类
 * */
public class SingerDynamicProxy implements InvocationHandler {
	private Ising target;                    //注入目标对象	
	public SingerDynamicProxy(Ising target) {//代理类的构造方法,传入目标对象
		this.target = target;
	}

	//	每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,
	//	当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
	//	InvocationHandler这个接口的唯一一个方法 invoke 方法:
	//	该方法接收3个参数:
	//	proxy:  指代我们所代理的那个真实对象
	//	method: 指代的是我们所要调用真实对象的某个方法的Method对象
	//	args:  指代的是调用真实对象某个方法时接受的参数
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("向观众问好 ...静态代理前置内容 ");// 在代理真实对象前我们可以添加一些自己的操作
		System.out.println("Method:" + method);	
		
		Object obj = method.invoke(target, args); // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
		
		System.out.println("方法参数: "+ Arrays.toString(args));			
		System.out.println("谢谢大家 ...静态代理后置内容 ");// 在代理真实对象后我们也可以添加一些自己的操作
		return obj;	
	}

}
public class Test2{
	public static void main(String[] args) {
	    //目标对象
		Ising target=new Singer();
		//代理对象
		// 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
		InvocationHandler proxy = new SingerDynamicProxy(target);
		
		/*
		 * 通过Proxy的newProxyInstance方法来创建我们的代理对象,三个参数:
		 * 第一个参数 :加载器用于定义代理类的类加载器 ,handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
		 * 第二个参数: 被代理的对象的类的接口列表实现 ,realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
		 * 第三个参数: 设置调用处理程序,将方法调用分派给的代理对象, handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
         */
		Ising ising = (Ising) Proxy.newProxyInstance(
									proxy.getClass().getClassLoader(),
									target.getClass().getInterfaces(),
								    proxy
								    				);	
		
		ising.sing();			
	}
	
}

	运行测试类的结果:
	向观众问好 ...静态代理前置内容 
    Method:public abstract void com.sjq.test.proxy.Ising.sing()
    唱一首歌
    方法参数: null
    谢谢大家 ...静态代理后置内容 
总结:
动态代理缺点:可以看出静态代理和JDK代理有一个共同的缺点,
就是目标对象必须实现一个或多个接口,如果没有,则可以使用Cglib代理。
  1. JDK动态代理 【方法二】
    (后期补充。)

  2. Cglib动态代理

前提条件:
需要引入cglib的jar文件,由于Spring的核心包中已经包括了Cglib功能,所以也可以直接引入spring-core-3.2.5.jar
目标类不能为final
目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法

三、总结:

三种代理模式各有优缺点和相应的适用范围,主要看目标对象是否实现了接口。以Spring框架所选择的代理模式举例。

在Spring的AOP编程中:
如果加入容器的目标对象有实现接口,用JDK动态代理 (不建议用静态代理,几乎被淘汰,缺点多);
如果目标对象没有实现接口,用Cglib动态代理;

四、备注:

学习链接1:https://www.cnblogs.com/boboxing/p/8126046.html
学习链接2:https://www.cnblogs.com/maohuidong/p/7992894.html
学习链接3:https://zhuanlan.zhihu.com/p/50777496

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值