动态代理由浅入深, 源码详解

实际生活中,我们经常听说中介、代理商、加盟等关键名词。试想一下,为什么需要这些?
就说说中介:就我本人身在上海来说,如果我要卖房子,我想找中介。原因:我每天工作很忙,卖房子也不是一天两天能找到买家,除了我的工作外,我没有时间浪费在上面,给我减轻负担。
再说一些品牌商,他们的核心就是打响了品牌,使用技术早就了这些好的商品,但是这些商品需要卖出去,自己去卖?人力、精力都不够用。所以他们就需要找代理商帮他去卖。
基于上述的问题,其实体现了专业分工的思想。实际开发中,一个类中,方法除了当前需要的核心操作,其他次要的可以丢给代理类去做。这样分工明确,业务分明。这就是我们接下来要说的动态代理。

动态代理的作用:可以在不改变原有类的基础上对方法进行增强。
什么是动态代理?这里2个关键字:动态和代理。
首先说下代理:这里涉及到一种设计模式–代理模式。我先不解释代理模式,我先说个词语:代理商。那么这个词语相信大家都听过,这个代理商和我们这里的代理是一样的意思。
所以动态代理,就是java虚拟就动态的生成了这个代理,而不是我们开发者手写java代码生成的。

一:快速入门-动态代理

需求:卖的奶茶和制造的奶茶需要加上品牌名“一点点”
首先我们直接先看动态代理的实现:只要记住动态代理的实现就是一种格式,固定步骤。
首先需要一个接口:奶茶

public interface Milk {
	//奶茶接口,有卖奶茶的方法
	public void sell(String milkName);
	//制造奶茶
	public void make();
}

然后一个品牌商:一点点

public class YiDianDian implements Milk {

	@Override
	public void sell(String milkName) {
		System.out.println("卖奶茶");
	}

	@Override
	public void make() {
		System.out.println("制造奶茶");
	}
}

动态代理生成 一点点 的代理商

public class Test {

	public static void main(String[] args) {
		System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
		//这是一点点 奶茶
		final Milk yiDianDian = new YiDianDian();
		//获取一点点的类加载器
		ClassLoader classLoader = yiDianDian.getClass().getClassLoader();
		//获取一点点实现的接口
		Class<?>[] interfaces = yiDianDian.getClass().getInterfaces();
		/*
		 * 动态代理创建一点点 的代理商
		 * 我们需要记住这种创建代理对象的格式:
		 * 	第一个参数:类加载器,一般写被代理对象的类加载器
		 * 	第二个参数:被代理对象实现的接口
		 * 	第三个参数:处理器     InvocationHandler接口的实现类。实现invoke方法,用来指定代理做的事情。
		 * 
		 */
		Milk proxyYiDianDian =(Milk)Proxy.newProxyInstance(classLoader, interfaces,new InvocationHandler() {	
			/*
			 * 
			 * 代理商做的事情就在这个invoke方法里书写,
			 * 这个方法3个参数:
			 * 第一个参数:代理商本身,invoke方法中不能使用,使用就报错。原因后面介绍。
			 * 第二个参数:正在执行的方法。这里可以理解成一点点这个品牌需要执行的方法。
			 * 第三个参数:正在执行的方法需要的参数。
			*/
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				//输出执行的方法的名称
				System.out.println("一点点的代理商代替 一点点品牌执行:"+method.getName()+"方法");
				//输出执行的方法下需要的参数
				System.out.println("一点点品牌传递过来的的参数:"+Arrays.toString(args));
				//执行被代理对象本身方法原有的功能。
				System.out.println("**********一点点品牌**********");
				method.invoke(yiDianDian, args);
				return null;
			}
		});
		
		proxyYiDianDian.sell("红茶");
		proxyYiDianDian.make();
	}
}

结果:
这里写图片描述

二:代理模式-静态代理

代理模式:创建一个对象的代理,以控制对这个对象的访问。通俗来讲,就是创建了一个中介(代理)。

首先需要一个接口:奶茶

public interface Milk {
	//奶茶接口,有卖奶茶的方法
	public void sell(String milkName);
	//制造奶茶
	public void make();
}

然后一个品牌商:一点点

public class YiDianDian implements Milk {

	@Override
	public void sell(String milkName) {
		System.out.println("卖奶茶");
	}

	@Override
	public void make() {
		System.out.println("制造奶茶");
	}
}

代理类

public class ProxyYiDianDian implements Milk {
	//代理商  拥有   被代理商的引用
	private Milk yiDianDain;
	public ProxyYiDianDian(Milk milk) {
		this.yiDianDain = milk;
	}
	@Override
	public void sell(String milkName) {
		System.out.println("***********一点点品牌*****************");
		yiDianDain.sell(milkName);
	}
	@Override
	public void make() {
		System.out.println("***********一点点品牌*****************");
		yiDianDain.make();	
	}
}

测试:

public class Test2 {

	public static void main(String[] args) {
		//被代理对象   一点点品牌
		Milk yiDianDian = new YiDianDian();
		
		//代理对象  代理商
		Milk proxyYiDianDian = new ProxyYiDianDian(yiDianDian);
		
		//执行sell方法
		proxyYiDianDian.sell("红茶");
		//执行make方法
		proxyYiDianDian.make();
		
	}
}

结果:
这里写图片描述

为什么需要动态代理:

就需求而言,制造和售出方法都添加打印品牌的名字,代理的原因就是因为我们所关注的售卖和制造的主要代码保持不变,创建代理类来在原有的功能上添加核心操作之外的步骤:打印一点点的品牌名。这样实现了每个方法的核心操作和打印品牌的业务分离。代码业务清晰。但是这个方法存在很明显缺点,当我们需要添加打印品牌的方法很多的时候,我们需要每一个方法都要添加这个输出语句,很是麻烦。而且后期一点点品牌发生变化,比如将一点点改成两点点,这是候每个方法都要修改,作为一个程序员真是心中十万只羊驼飘过。所以为了维护方便,我们需要使用动态代理。
其实这也就是说静态代理的缺点:
1.如果需要代理的方法很多的话,那么我们需要对每一个方法做实现,非常的麻烦。
2.后期如果接口增加新的方法,我们需要对java文件做改写。维护麻烦。
而动态代理完全避免了这些。因为这是java虚拟机做的事情。

动态代理和静态代理的区别:
通过上面的案例,我们发现,我们静态代理是需要我们手动的书写一个java文件,而动态代理是不需要手动书写一个java文件的。
我们学习java的都知道,我们创建一个对象,首先需要书写一个java文件,然后将java文件编译成class文件,类加载器将class文件加载到内存中。然后我们可以创建此对象的实例。而上述的代理商proxyYiDianDain这个对象是没有java文件的,也没有class文件,是java虚拟机直接在内存中生成了class对象.从而创建这个class对象的实例,当然这个实例可以按照我们开发者的需求做任何功能的改写,代替原有的对象。这就是动态代理含义的本质。
这里写图片描述

动态代理引用场景介绍:

通过代理模式,我们发现我们使用代理模式的目的是为了实现专业分工。所以在开发中当我们除了原始核心类以外的其他功能,比如说事务的管理,权限等,业务逻辑方法每个都要添加事务的管控和权限的判断是非常麻烦的,而这些东西,和我们方法的核心操作关系不大,所以我们可以将这些操作用专门的动态代理来处理。

三:动态代理深入理解-源码解析

1.Proxy.newProxyInstance静态方法解析

 public static Object newProxyInstance(ClassLoader loader,
             Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{
		if (h == null) {
				throw new NullPointerException();
		}
		
		/*
		* 1.核心操作,java虚拟机根据接口和类加载器,在内存中生成代理类的class对象。
		*/
		Class<?> cl = getProxyClass0(loader, interfaces); 
		
		try {
			//2.获取代理类的构造方法
			final Constructor<?> cons = cl.getConstructor(constructorParams);
			//3.获取我们调用这个方法传递过来的处理器。
			final InvocationHandler ih = h;
			SecurityManager sm = System.getSecurityManager();
			if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
				return AccessController.doPrivileged(new PrivilegedAction<Object>() {
				public Object run() {
					return newInstance(cons, ih);
					}
				});
			} else {
			//4.这里可以发现,通过代理类的构造方法和处理器来创建这个代理类的实例。也就是创建动态代理对象
			return newInstance(cons, ih);
			}
		} catch (NoSuchMethodException e) {
			throw new InternalError(e.toString());
		}
	 }

通过上面的4步我们可以发现,其实动态代理,就是java虚拟机动态生成了一个class对象,然后创建了一个对象。
所以接下来我们看看java虚拟机动态生成的这个class对象的字节码文件

2.动态代理类的字节码文件
在测试类的最上面加上如下的一句代码,让java虚拟机生成的字节码文件输出到硬盘。
System.setProperty(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);
然后再项目下添加如下目录:com/sun/proxy
这里写图片描述

执行代码:

public static void main(String[] args) {
		System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
		//这是一点点 奶茶
		final Milk yiDianDian = new YiDianDian();
		//获取一点点的类加载器
		ClassLoader classLoader = yiDianDian.getClass().getClassLoader();
		//获取一点点实现的接口
		Class<?>[] interfaces = yiDianDian.getClass().getInterfaces();
		/*
		 * 动态代理创建一点点 的代理商
		 * 我们需要记住这种创建代理对象的格式:
		 * 	第一个参数:类加载器,一般写被代理对象的类加载器
		 * 	第二个参数:被代理对象实现的接口
		 * 	第三个参数:处理器     InvocationHandler接口的实现类。实现invoke方法,用来指定代理做的事情。
		 * 
		 */
		Milk proxyYiDianDian =(Milk)Proxy.newProxyInstance(classLoader, interfaces,new InvocationHandler() {	
			/*
			 * 
			 * 代理商做的事情就在这个invoke方法里书写,
			 * 这个方法3个参数:
			 * 第一个参数:代理商本身,invoke方法中不能使用,使用就报错。原因后面介绍。
			 * 第二个参数:被代理对象本身所执行的方法。这里可以理解成一点点这个品牌需要执行的方法。
			 * 第三个参数:被代理对象本身所执行的方法需要的参数。
			*/
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				//输出执行的方法的名称
				System.out.println("一点点的代理商代替 一点点品牌执行:"+method.getName()+"方法");
				//输出执行的方法下需要的参数
				System.out.println("一点点品牌传递过来的的参数:"+Arrays.toString(args));
				//执行被代理对象本身方法原有的功能。
				System.out.println("**********一点点品牌**********");
				method.invoke(yiDianDian, args);
				return null;
			}
		});
		proxyYiDianDian.sell("红茶");
	}

可以发现,com/sun/proxy目录下生成了一个class文件,然后使用反编译工具打开,生成的代理类源码如下:

package com.sun.proxy;

import com.itheima.proxy.Milk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy
  implements Milk
{
  private static Method m1;
  private static Method m3;
  private static Method m0;
  private static Method m2;

  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }

  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  /**
   * 可以发现,代理类的所有的方法都执行了this.h.invoke方法
   */
  public final void sell(String paramString)
    throws 
  {
    try
    {
      this.h.invoke(this, m3, new Object[] { paramString });
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("com.xu.proxy.Milk").getMethod("sell", new Class[] { Class.forName("java.lang.String") });
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

通过上述的源码我们可以发现,代理类的所有的方法都执行了**this.h.invoke()**方法
我们将sell方法抽出来理解一下:

public final void sell(String paramString)
    throws 
  {
    try
    {
      /*
       * sell方法的核心操作就是这个
       * this代表当前对象,
       * h 代表我们传递过来的 invacationHandler的实现类对象
       * invoke就是处理器 的invoke方法
       * 
       * 也就是说,最终的实现全部丢给了invacationHandler的invoke()方法处理了。
       * 再来看看调用invoke()方法传递的参数,
       * 	this:代表当前对象,当前类就是代理类,所以是代理对象本身
       * 	m3: m3 = Class.forName("com.itheima.proxy.Milk").getMethod("sell", new Class[] 						 																		{ Class.forName("java.lang.String") });
       * 		最下面有如上代码,可以发现m3就是sell方法,执行的方法
       *   new Object[] { paramString }:就是调用sell方法时传递的参数。
      */
      this.h.invoke(this, m3, new Object[] { paramString });
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

结合上面生成的动态代理类的sell方法的源码,结合我们一开始实现动态代理类的代码来看一下

Milk proxyYiDianDian =(Milk)Proxy.newProxyInstance(classLoader, interfaces,new InvocationHandler() {	
			/*
		    	第一个参数:代理商本身,invoke方法中不能使用,使用就报错。原因后面介绍。
			 * 第二个参数:当前正在执行的方法。这里可以理解成一点点这个品牌需要执行的方法。
			 * 第三个参数:当前正在执行的方法需要的参数。
		 */
@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				//输出执行的方法的名称
				System.out.println("一点点的代理商代替 一点点品牌执行:"+method.getName()+"方法");
				//输出执行的方法下需要的参数
				System.out.println("一点点品牌传递过来的的参数:"+Arrays.toString(args));
				//执行被代理对象本身方法原有的功能。
				method.invoke(yiDianDian, args);
				return null;
			}
		});

所以,执行代理对象的任何方法,就需要执行invocationHandler的invoke方法。所以在invoke方法中调用proxy对象的方法,就会调用代理对象本身的方法,而又回到了invoke()方法中。所以陷入死循环。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值