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