Proxy代理者模式(一)

摘要

本篇笔记针对Java设计模式中最难理解的代理者模式进行讲解,从静态代理、动态代理,及Java相关代理类的应用等几个方面讲解。

一、简介

主要内容:
1、由问题引出设计模式
2、静态代理的产生与实现
3、继承与聚合哪个好
4、动态代理的产生与实现
5、JDK proxy的实现及应用
6、总结与补充

二、问题引出

在实际应用中,我们经常要对一些类的的功能进行一些日志啊,权限验证等操作,事务控制等处理,而这些类很多都是打成jar包的,本就看不到源码、别说修改了。那我们怎么办?问题就来了:如何在不修改一个类的代码的情况下去实现我们想要的上述的功能?比如性能测试中我们想知道某个方法的执行时间是多少?

三、静态代理

1、简单静态代理

为了解决以上问题,我们首先想到的就是
1、自己写一个类继承其它被代理类。
2、在main方法中调用父类的被测试方法之前记录当前系统时间作为起始时间。
3、调用被测试方法、调用完之后记录结束时间、并输出时间差、这就是要测试的方法的执行时间。下面Java模仿实现代码
a)假设Car使我们看不到的源码类、它有一个run方法,我们想测试其运行时间

package com.mmb.proxy;
import java.util.Random;
public class Car {
public void running()
{
    System.out.println("Car is runing");
    try
    {
        Thread.sleep(new Random().nextInt(1000));
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
}
}

b) 我们自己定义个CarTimeProxy类、继承Car类、那么我们自己定义的类就拥有了使用Car类的running方法、那就可以实现测试执行时间。CarTimeProxy——代码:

package com.mmb.proxy;
public class CarTimeProxy extends Car {
@Override
public void running() {
    long startTime = System.currentTimeMillis();
    System.out.println("start time is " + startTime);
    super.running();
    long endTime = System.currentTimeMillis();
    System.out.println("end time is " + endTime);
    System.out.println("it takes " + (endTime-startTime) + " ms");
}
}

4、Client代码:package com.mmb.proxy;

public class Client {
public static void main(String[] args) {
    CarTimeProxy carTimeProxy = new CarTimeProxy();
    carTimeProxy.running();
}
}

5、测试结果

start time is 1433255852213
Car is runing
end time is 1433255853199
it takes 986 ms

2、静态代理的进一步实现

上面完美的解决了对一个类的一个方法的一种处理方式、看起来我们的RunTimeProxy是不是有点代理的样子了?但是明显上面有很大的局限性、甚至可以说是缺陷!那就是没有用到我们常常挂在嘴边的抽象、多态。当然这么讲可能觉得不理解、没有直观的印象。问题:当我想换一个会跑的对象(比如狗,马)来测试一下他跑的方法的执行时间的时候怎么办?是不是只能重新写一个我们自己的类来继承动物类、然后重复上边的步骤?很显然、这样的做法没有任何我们觉得可取的优点。只会将时间浪费在重复的coding代码中。
对与会飞的东西、我们第一想法应该是可以抽象出他们共同的特性——会跑、那么我们可不可以定义一个runAble接口、提供一个running方法呢、这样想具有跑的功能就实现这个接口就ok了。
到了上面一步、距离我们想要的结构就更近一步了、那就是使用聚合的形式将被代理类与代理类相结合!这样我们在一个代理类中就可以对任意实现了我们规定的接口的实现类进行我们想要的处理、可能这样讲有点迷惑、直接通过代码来、在上面的代码基础上修改即可:

1、添加一个RunAble接口、里面就一个running方法——RunAble代码:

 package com.mmb.proxy;
 public interface RunAble {
 public void running();
   }

2、让想要具有跑的功能的类都实现个这个接口。Dog代码:Horse代码:

package com.mmb.proxy;

import java.util.Random;
public class Horse extends RunAble {
@Override
public void running() {
    System.out.println("Horse is runing");
    try
    {
        Thread.sleep(new Random().nextInt(1000));
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
}
}

package com.mmb.proxy;

import java.util.Random;
public class Dog implements RunAble {
@Override
public void running() {
    System.out.println("Dog is running");
    try
    {
        Thread.sleep(new Random().nextInt(1000));
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
}
}

3、此时的代理类——RunAbleTimeProxy(此时再叫RunTimeProxy就不合适了、因为他现在是所有能飞的东西的时间代理类)代码:

package com.mmb.proxy;
public class RunAbleTimeProxy implements RunAble{
private RunAble runAbleObject;

public RunAbleTimeProxy(RunAble runAbleObject)
{
    super();
    this.runAbleObject = runAbleObject;
}

@Override
public void running() {
    long startTime = System.currentTimeMillis();
    System.out.println("start time is " + startTime);
    runAbleObject.running();
    long endTime = System.currentTimeMillis();
    System.out.println("end time is " + endTime);
    System.out.println("it takes " + (endTime-startTime) + " ms");
}
}

4、为了正规点我们另起一个Client测试代理类:

 package com.mmb.proxy;
 public class RunAbleClient {
public static void main(String[] args) {
    RunAbleTimeProxy runAbleTimeProxy = new RunAbleTimeProxy(new Dog());
    runAbleTimeProxy.running();
}
}

5、测试结果:

start time is 1433256898668
Dog is running
end time is 1433256899343
it takes 675 ms

3、静态代理的更进一步实现

上面的代码就显得有点能看了、最起码我们在里面能找到多态、接口这些东西的使用、同时也解决了一个代理可以代理具有同一特性的类(这里就是多态的强大与灵活)、但是设计是无止境的!一个问题的解决往往伴随这新的问题的出现:如果我想要对fly方法进行日志记录、怎么办?你脑海中的第一反应可能会是再写一个类RunAbleLogProxy、没错!这本身就是一种解决办法、更好的解决方法下面会有、这里不是重点、重点是——我既想记录时间、又想记录日志、怎么办?写第三个代理类:RunAbleLogAndTimeProxy。好吧、继续来问题:我想先记录时间、在记录日志?之后又想先记录日志再记录时间?之后我觉得还应该添加一个事务控制、然后他们三个的顺序我还想换换?然后我觉得还应该加个权限控制。。。。。。不要觉得我犯贱、想这想那。。。问题真的来临的时候你怎么解决?难道每出现一个新的变动就要写一个新的代理类?那要写多少?什么时候是个头?想解决问题、还是得找Java的多态、抽象。四个字说起来简单、真真正正的用的时候你才会体会到他的博大精深!解决方法:
我们有没有想过代理类也是类?也可以被其他的代理类所代理?条件无非就是和原始的被代理类实现同一个接口!然后可以根据不同的需求顺序来调整他们的代理顺序?比如我先使用记录时间的类来代理被代理类、然后使用记录日志的代理类来代理记录时间的代理类?这样是不是实现了先记录时间后记录日志的代理?如果有多个、我们完全可以按照这种方式去实现!还是以代码来说明:
1、 新添加一个能够记录日志的代理类(想来没有任何难度、注意实现了RunAble接口)——RunAbleLogProxy代码:

package com.mmb.proxy;
public class ComplexClient {
public static void main(String[] args) {
    RunAbleTimeProxy runAbleTimeProxy = new RunAbleTimeProxy(new Dog());
    RunAbleLogProxy runAbleLogProxy = new RunAbleLogProxy(new Dog());
    runAbleLogProxy.running();
    runAbleTimeProxy.running();

    System.out.println("---------------");

    RunAbleLogProxy runAbleLogProxy1 = new RunAbleLogProxy(new Dog());
    RunAbleTimeProxy runAbleTimeProxy1 = new RunAbleTimeProxy(runAbleLogProxy1);
    runAbleTimeProxy1.running();

    System.out.println("========================");

    RunAbleTimeProxy runAbleTimeProxy2 = new RunAbleTimeProxy(new Dog());
    RunAbleLogProxy runAbleLogProxy2 = new RunAbleLogProxy(runAbleTimeProxy2);

    runAbleLogProxy2.running();
}
}

结果:

log is start 
Dog is running
log is end 
start time is 1433257548692
Dog is running
end time is 1433257549486
it takes 794 ms
---------------
start time is 1433257549486
log is start 
Dog is running
log is end 
end time is 1433257549802
it takes 316 ms
========================
log is start 
start time is 1433257549802
Dog is running
end time is 1433257549980
it takes 178 ms
log is end 

4、继承与聚合

留心的可以发现、上面的静态代理是一步步从类的继承走向聚合的。首先是通过继承来实现单一类的代理、这样的代理、一个代理类只能代理一个类或者其父类。如果想代理别的类、或者对同一类实现不同的代理、那就要另造代理类、如果需要代理的类特别多、则随之衍生的代理类则无限的膨胀下去、简称——类爆炸。这样的话我们基本看不到他们的可取之处。设计的不合理可以定位与对Java多态的没有充分利用。
当我们使用聚合的时候、发现会灵活的太多、最起码进一步解决了继承所带来的类爆炸的问题(当然没有完全解决、对于不同的功能还是要我们去实现不同的代理类)、代理拥有被代理类的父类引用、这样代理可以代理代理类、这种随意的组合无疑让我们方便很多!这是继承所不能带给我们的优势。同时从这里可以看出一点:从接口出发的优势!所以我们在用许多框架的时候、他会强制要求我们提供接口、然后再提供他的实现类、就是为了更强的灵活性和可扩展性!说到底这就是一种抽象、多态的应用!

四、动态代理

1、动态代理的产生

通过静态处理之后、我们发现现实与理想更进一步了。最起码上面的看起来并不是需要写那么多的类了。但是还是要避免不了的去写不同的类的代理、不同功能的代理。那么有没有可能使用一个类来完成上面的所有功能的呢?
动态代理!动态代理可以对任意的对象、任意的接口的方法、实现任意的代理!我们不必关心、也关心不了代理内部的实现、只要按照代理类的要求来使用他、就能实现上面的功能。当然、孤木不成林、我们需要按照他的要求来实现具有自己指定功能的类、以及被代理类。

2、动态代理的实现

a)既然我们的目标是对任意对象、任意接口的方法、实现任意的代理、显然要使用的是一个高度抽象的接口、或者类来构建骨架。然后在使用时传入具体的实现类的对象。
b)开始的模型从简单的开始、就为一个Bird生成一个动态代理。保留Bird类和FlyAble接口、接下来就围绕如何使用动态代理来生成Bird的代理类。
c)既然是需要有任意性、那么就先定义一个所有代理类必须实现的接口InvocationHandler、使得代理类具有任意性、并且声明一个方法invoke(Object o, Method m)、用于调用实现类中的此方法来实现代理。InvocationHandler接口:

package com.mmb.designPattern;
import java.lang.reflect.Method;

public interface InvocationHandler {
//    此方法是在Proxy中真正被执行的,是核心
//    o是被带理的对象,m是被执行的方法

public void invoke(Object o ,Method m);
}

d)InvocationHandler的一个实现子类:TimeHandler(它不再是某一具体类的时间代理、而是Object):

package com.mmb.designPattern;

import java.lang.reflect.Method;

public class TimeHandler implements InvocationHandler {
//被代理对象
public Object target;
public TimeHandler(Object o)
{
    this.target = o;
}
/**
 * 细心的可以发现第一个参数Object、并没有使用。
 * 只是在这里没有使用、我们可以按照自己的需求、、
 * 传入一个需要借助的Object来实现特定的功能。
 * 此方法会在Proxy中生成的代理类$Proxy1被真正的执行、Object会传this、也就是$Proxy1!
 */
@Override
public void invoke(Object o, Method m) {
    long start = System.currentTimeMillis();
    System.out.println("start time is " + start);
    System.out.println(o.getClass().getName());
    try{
        m.invoke(target);
    }
    catch (Exception e)
    {
        e.printStackTrace();;
    }
    long end = System.currentTimeMillis();
    System.out.println("end time is " + end);
    System.out.println("it takes " + (end-start) +" ms");
}
}

、e)接下来就是核心的Proxy!Proxy只有一个功能——为我们产生代理类!当然需要条件、为谁产生代理类(包括他的所有方法)?产生什么样的代理类?这个类当我们完成之后、就不必再修改。就是因为他的任意性!当然这里面使用的点反射的东西、但是也没有多少、看懂不难。
f)主要实现思路:
i、将所有方法代码拼接成字符串、
ii、 将生成代理类的代码拼接成字符串(包含所有方法拼接成的字符串)、iii、将此字符串写入文件中、使用JavaComplier对齐进行编译、
v、load进内存供我们使用。返回代理实例。
Proxy代码:

package com.mmb.designPattern;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
  • 为所有的类生成指定的代理类。

  • 简单起见仅仅考虑没有返回值没有参数的方法的代理。

  • @author mmb

  • */
    public class Proxy {
    public static Object newInstance(Class interfaceA, InvocationHandler h) throws Exception {

     String methodStr = "";
     String rt = "\r\n";
     Method[] methods = interfaceA.getMethods();
     for (Method m : methods)
     {
         methodStr = "@Override" + rt +
                 "public void " + m.getName() + "(){" + rt +
                 "    try {" + rt +
                 "        Method md = " + interfaceA.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt +
                 "        h.invoke(this, md);" + rt +
                 "    }catch(Exception e) {e.printStackTrace();}" + rt +
                 "}";
     }
     String src =  "package com.mmb.designPattern;" + rt +
             "import java.lang.reflect.Method;" + rt +
             "public class $Proxy1 implements " + interfaceA.getName() + "{" + rt +
             "   com.mmb.designPattern.InvocationHandler h;" + rt +
             "   public $Proxy1(InvocationHandler h){" + rt +
             "       this.h = h;" + rt +
             "   }" + rt +
             "   "+methodStr +
             "}";
    
     String fileName = "C:\\WorkPlace\\DesignPattern\\src\\com\\mmb\\designPattern\\$Proxy1.java";
     File f = new File(fileName);
     FileWriter fw = new FileWriter(f);
     fw.write(src);
     fw.flush();
     fw.close();
    
     //compile the proxy class
     JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
     StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
     Iterable units = fileMgr.getJavaFileObjects(fileName);
     JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
     t.call();
     fileMgr.close();
    
     //load into memory and create an instance
     URL[] urls = new URL[]{new URL("file:/" + "C:\\WorkPlace\\DesignPattern\\src")};
     URLClassLoader ul = new URLClassLoader(urls);
     Class c = ul.loadClass("com.mmb.designPattern.$Proxy1");
     Constructor ctr = c.getConstructor(InvocationHandler.class);
     Object m = ctr.newInstance(h);
     return m;
    
      }
    
     public static void main(String[] args) throws  Exception{
    
     Proxy proxy = new Proxy();
     Car car = new Car();
     TimeHandler timeHandler = new TimeHandler(car);
         Object o = Proxy.newInstance(RunAble.class, timeHandler);
     System.out.println(o);
     }
    }
    

生成的代理类 $Proxy1

package com.mmb.designPattern;
import java.lang.reflect.Method;
public class $Proxy1 implements
com.mmb.designPattern.RunAble{
 com.mmb.designPattern.InvocationHandler h;
   public $Proxy1(InvocationHandler h){
   this.h = h;
   }
   @Override
 public void running(){
try {
    Method md = com.mmb.designPattern.RunAble.class.getMethod("running");
    h.invoke(this, md);
}catch(Exception e) {e.printStackTrace();}
}}

五:CGLIB

spring的AOP主要是由动态代理和CGLIB来完成代理。

1、CGLIB:是针对类生成代理,针对指定的类生成一个子类,覆盖里面的方法,所以指定的类不能是final包括方法。
2:如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
3:目标对象实现了接口,可以强制使用CGLIB实现AOP
4:如目标对象没有实现接口,必须采用CGLIB库,spirng会自动在JDK动态代理和CGLIB之间转换

六:总结补充

1、总结

代理到这里就告一段落了、从问题的产生、到一步步的解决、优化。从继承式的静态代理到聚合式的静态代理、从静态代理到动态代理、逐渐的揭示了代理的功能与实现方式。当一个代理类被创建好之后、我们就不必再关心他的信息、包括如何实现、具体在哪里等等、所要知道的就是如何使用即可。
我们完全可以通过配置文件来指定我们想要使用的代理类。这点是不是让你想到了spring的AOP?没错、spring的AOP是动态代理的一种应用、而不是动态代理是AOP的一种应用、别弄混了。
还有点题外话、spring控制的事务是在什么时候开启的?是在调用Dao层开启的还是在调用Service层还是Action层?答案:一个Service层有可能调用多个Dao、所以是在调用Service层方法开始、方法执行完之后结束、根据结果来判断是提交还是回滚。

以上内容:严重参考 http://blog.csdn.net/crave_shy/article/details/21000887
后面是我自己扩充的一些知识

七、JDK的Proxy类的使用方法

先发布了,这部分,等我学明白了再写

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容

  • title: Jdk动态代理原理解析 tags:代理 categories:笔记 date: 2017-06-14...
    行径行阅读 19,197评论 3 36
  • 整体Retrofit内容如下: 1、Retrofit解析1之前哨站——理解RESTful 2、Retrofit解析...
    隔壁老李头阅读 3,210评论 2 10
  • 生活中不断被人侵犯 却总是合理化对方也是为了我好 我怎么可以对她表达愤怒呢 事实是我真的受到伤害了 我有愤怒 因为...
    竺子阅读 247评论 0 0
  • 诗说三月扬州好,烟花天地杨花蕾。 可知孔明洗马处,长有梅子伴年岁。 祭日重回鸳鸯水,双树婆娑载风飞。 平洲小艇过江...
    步红尘阅读 444评论 1 3
  • 我才15岁,但是我觉得我遇到的这些事不算少。 我有一个朋友,上一次大晚上的他给我发信息说她好委屈。 她有个朋友的爸...
    宇宙最酷丁慧玲阅读 204评论 0 0