java学习之路----静态代理---动态代理-----AOP的前奏(AOP也是动态代理)

原创 2013年12月05日 21:27:42
 什么叫代理? 什么是静态代理?什么是动态代理?

我觉得用代码可以解释这一切。。

     直接看代码:


1.现在我们来建立一个java项目,叫Proxy,建立一个类,叫Tank,继续建立一个接口,叫Moveable,我们用Tank 来实现Moveable(意思就是坦克实现移动)

public
interface Moveable {
   
void move();//移动接口
}

public
class Tank implements Moveable {

@Override
public void move() {
   System.out.println( "Tank moving....");//简单的输出一句话就代表移动了
    
     }

}

我们再建立一个测试的服务端  叫Client类

public class Client {
     public static void main(String[] args) {
         Moveable m=new Tank();
            m.move();
       
     }

}

运行一下,结果:

Tank moving....


证明我们第一步成功了

2.现在假设我们把这段代码提交到了一个地方,我们不可以去修改源码了,我们又想添加新的功能怎么办?

    一般有两种方法:继承和聚合

     来先看下继承:

          我们来建立一个Tank2类来继承Tank,并且增加一个新的功能,叫计时的功能,就是方法开始运行就记下当前时间,结束的时候也记下结束的时间,然后相减,就得到了运行时间

     public class Tank2 extends Tank{

     @Override
     public void move() {
           long start=System.currentTimeMillis();
           super.move();
           long end=System.currentTimeMillis();
          System. out.println((end-start));
     }

}

我们修改测试类来看一下:

public class Client {
     public static void main(String[] args) {
          Moveable m= new Tank2();
          m.move();

     }

}
结果:
Tank moving....
0

结果是0ms,这样看不出效果,那我们再加一个线程的睡眠


修改代码:
public class Tank implements Moveable {

     @Override
     public void move() {
   
     System.out.println( "Tank moving....");
     try {
          Thread. sleep(new Random().nextInt(10000));
     } catch (InterruptedException e) {
          
          e.printStackTrace();
     }
   
     }

}

运行测试类:

Tank moving....
2995

这样就看出了运行时间


这上面的功能就是代理功能


我们在来看聚合


我们要聚合,就要先建立一个TankTimeProxy类,并且实现moveable接口,这个就是来计算tank移动方法运行的时间

     
看代码:
     
public class TankTimeProxy implements Moveable{
     
     public TankTimeProxy(Tank t) {
           this.t = t;
     }
    Tank t;//这里给一个tank对象进来,这就聚合(在一个类中引入另一个类的对象)
     @Override
     public void move() {
           long start=System.currentTimeMillis();
           t.move();
           long end=System.currentTimeMillis();
          System. out.println((end-start));
     }
          
}
     
修改测试类:

public class Client {
     public static void main(String[] args) {
          Tank m= new Tank();
          TankTimeProxy time= new TankTimeProxy(m);
          
          time.move();
          
          

     }

}        

结果:
Tank moving....
923

3.这两个哪个好喃?

          如果现在我们还要增加一个日志的功能,如果是继承,我们还要写一个类来继承Tank2,但是用户又说,我要求先日志,在计算时间,那么是不是又要写个类来实现movaable接口,来修改喃,这样就会造成类的无限增长,这显然是不合理的,所以我们要用聚合。。聚合,无论你增加多少功能,我都可以互相交换


下面来看下聚合的代码:

     我们先增加一个加 TankLogProxy的类,同样也实现了moveable接口

public class TankLogProxy implements Moveable {
     public TankLogProxy(Tank t) {
           this.t = t;
     }

    Tank t;
     
     @Override
     public void move() {
          System. out.println("Tank start" );
           t.move();
          System. out.println("Tank end" );
          
     }

}

    如果我们要想互相交换,那我们还需要修改一下代码:

public class TankLogProxy implements Moveable {
     public TankLogProxy(Moveable t) {
           this.t = t;
     }

     Moveable t;
     
     @Override
     public void move() {
          System. out.println("Tank start" );
           t.move();
          System. out.println("Tank end" );
          
     }

}

public class TankTimeProxy implements Moveable {
     
     public TankTimeProxy( Moveable t) {
           this.t = t;
     }
     Moveable t ;
     @Override
     public void move() {
           long start=System.currentTimeMillis();
           t.move();
           long end=System.currentTimeMillis();
          System. out.println((end-start));
     }
          
}

我们都把我们要传进来的对象变成Moveable的对象,因为我们都是实现了moveable接口

          我们来写测试类
          先日志,在时间
          public class Client {
     public static void main(String[] args) {
     
          Tank t= new Tank();
          TankLogProxy log= new TankLogProxy(t);//这里传的是tank的对象。tank也是moveable的对象
          TankTimeProxy time= new TankTimeProxy(log);
          Moveable m=time;
          m.move();
     }

}
结果:
Tank start
Tank moving....
Tank end
3639


满足我们的要求,如果现在我们要先时间,再日志,我们只需要修改一下测试类就oK

     看代码:

          public class Client {
     public static void main(String[] args) {
     
          Tank t= new Tank();
          TankTimeProxy time= new TankTimeProxy(t);
          TankLogProxy log= new TankLogProxy(time);
          
          Moveable m=log;
          m.move();
     }

}

结果:

Tank start
Tank moving....
484
Tank end

是不是很方便的就修改了我们的代码


这上面的就可以叫静态代理


4.现在有出现了一个问题?如果我现在有多个类,那我是不是要去实现多个计时,多个日志,那不是和刚才的继承一样,造成了类的大量产生(重复),这样显然是不合理的,那我们带怎么办喃?
          我们现在就可以使用动态代理


     我们来自己写一个动态代理类,名字叫Proxy

源码:

public class Proxy {
     //这个类的作用就是用来产生新的代理类
     public static Object newProxyInstance(){
           /*把这个类当成一个string的字符串(源码)
          现在我们假设,我们能把这字符串编译,生成类,放在内存,来产生对象
          
          动态代理就是你看不到代理类,你只需要调用一个方法( Proxy的newProxyInstance()方法),
          会自动给你返回一个代理类对象,这个对象的产生是由内部动态的生成一段代码,编译完成的*/
          
        
          String  src=
           "package com.text;"+

           "public class TankTimeProxy implements Moveable{"+
              
               "public TankTestProxy(Moveable t) {" +
               "    this.t = t;" +
           "    }"+
           "    Moveable t;" +
           "    @Override" +
           "    public void move() {" +
               "    long start=System.currentTimeMillis();"+
               "    t.move();" +
               "    long end=System.currentTimeMillis();" +
               "    System.out.println((end-start));" +
           "    }"+
                   
           "}";
          
           return null ;
     }

}
     
上面的注释解释的很清楚了。。

   现在我们就来动态的编译这段代码

一般动态编译文件有这些方法(用JDK6的complier API(大于1.6都行,只是这个是1.6的新特性),CGlib,ASM(直接生成二进制的class文件))

     我们直接用JDK的 complier 


     我们要做的步骤:

  •     把字符串进行编译
  •     生成一个类
  •     写入内存
  •     生成对象
     下面我们就来一一实现

我们先写一个测试类,叫Test1.java

     源码:

第一步:

     public class Test1 {
     public static void main(String[] args)throws Exception {
           //来测试怎么编译这段代码
          String  src=
                    "package com.text;"+

                    "public class TankTimeProxy implements Moveable{"+
                        
                         "public TankTimeProxy(Moveable t) {"+
                         "    this.t = t;" +
                    "    }"+
                    "    Moveable t;" +
                    "    @Override" +
                    "    public void move() {" +
                         "    long start=System.currentTimeMillis();"+
                         "    t.move();" +
                         "    long end=System.currentTimeMillis();"+
                         "     System.out.println((end-start));" +
                    "    }"+
                             
                    "}";
           //获取当前系统目录(就是项目根目录)
          String fileName=System.getProperty("user.dir") ;
          
          System.out.println(fileName);//先输出我们获取的根目录
        
     }
}

第二步:
     我们输出了根目录之后,我们就来编译这段字符串

     修改
          String fileName=System.getProperty( "user.dir" )+"/src/com/text/TankTimeProxy.java";
          这样做的目的是为了让生成的代码就在我们的项目的文件里

     添加以下代码:

               File f=new File(fileName);
          FileWriter writer= new FileWriter(f);
          writer.write(src);
          writer.flush();
          writer.close();

    在做这一步之前,如果你的文件里有TankTimeProxy.java文件,你把它删除了,不需要了,因为我可以动态的来生成了。
          运行代码,完成之后,右键项目,刷新,你会看到出现了一个TankTimeProxy.java文件.OK,第二步完成

第三步:
          我们来生成一个类

          //这句话的作用就是获取系统当前默认的编译器(其实就 javac
          JavaCompiler compiler=ToolProvider.getSystemJavaCompiler(); //拿到java的编译器
          
          System. out.println(compiler.getClass().getName());
          
          StandardJavaFileManager filemag=compiler.getStandardFileManager(null, null, null);//文件的  管理器
          
           Iterable  untis= filemag.getJavaFileObjects(fileName);//找到文件,把文件放在 Iterable(数组)
          
          CompilationTask t=compiler.getTask( null, filemag, null, null , null, untis );//定好编译文件任务
          t.call(); //编译文件
          
          filemag.close();//关闭文件管理器

运行:

     编译之后,我们打开window show View 选择Navigator(这个可以看到类详细的变化,就是看得到class文件的产生)
就会看到多了一个TankTimeProxy.class 文件,第三步成功


 第四步:

     我们把文件加入内存(原本一般的做法是class.loader,就OK了,但是调用这个方法的前提就是,你的class文件目录必须在classpath的文件目录下),我们这里用一种通用的做法
          //这里使用url加载器
               URL [] urls=new URL[]{ new URL("file:/"+System.getProperty( "user.dir")+"/src" )};
          URLClassLoader ul= new URLClassLoader(urls);//这里需要一个数组地址
           Class c=ul.loadClass("com.text.TankTimeProxy" );//把类加到内存
          
          System. out.println(c);

          测试:输出c,OK,第四步完成

最后一步,生成对象

          //反射来创建对象
           Constructor ctr=c.getConstructor(Moveable.class) ;//获取构造方法
          
          Moveable m=(Moveable)ctr.newInstance( new Tank());
          m.move();

     运行: 

看下结果:     
          com.sun.tools.javac.api.JavacTool
class com.text.TankTimeProxy
Tank moving....
2004

         Ok,我们要求的功能全部实现了。

5.如果现在我们实现不是一个特定的接口(意思就是不是实现Moveable接口,而是实现的其他接口),那我们怎么办喃?

     那我们把接口也当参数传进来


          修改代码:
                                                                       //把接口也当做一个参数传进来
                    public static Object newProxyInstance(Class infc) throws Exception{//把接口也传进去
           
          String methodsString= "";//定义一个变量
          
          Method methods[]=infc.getMethods();//获取方法(反射)
          
           for(Method m: methods){
              
               methodsString=methodsString+"@Override"+
              "    public void "+m.getName()+"() {"+
                        
                        "    long start=System.currentTimeMillis();"+
                        "    t."+m.getName()+"();"+
                        "    long end=System.currentTimeMillis();"+
                        "     System.out.println((end-start));"+
                        
                   "    }";
            
          
           /*把这个类当成一个string的字符串(源码)
          现在我们假设,我们能把这字符串编译,生成类,放在内存,来产生对象
          
          动态代理就是你看不到代理类,你只需要调用一个方法( Proxy的newProxyInstance()方法),
          会自动给你返回一个代理类对象,这个对象的产生是由内部动态的生成一段代码,编译完成的*/
          
           //现在我们就来动态的编译这段代码(JDK6,complier API,CGlib,ASM(直接生成二进制的class文件))
          String  src=
           "package com.text;"+

           "public class TankTimeProxy implements "+infc.getName()+"{"+
              
               "public TankTimeProxy(Moveable   h) {"+
               "    this.h = h;" +
           "    }"+
           //"  Moveable t;"+
          
           "Moveable h ; " +
          
          
          methodsString
          +
                   
           "}";
          
          
           //获取当前系统目录(就是项目根目录)
          String fileName=System.getProperty("user.dir")+ "/src/com/text/TankTimeProxy.java" ;
          
           //System.out.println(fileName);
          File f= new File(fileName);
          FileWriter writer= new FileWriter(f);
          writer.write(src);
          writer.flush();
          writer.close();
          
           //看是否生成代码,右键项目,刷新就OK了
          
          
          
          
           /*****************编译********************/
          
           //这句话的作用就是获取系统当前默认的编译器(其实就 javac
          JavaCompiler compiler=ToolProvider.getSystemJavaCompiler(); //拿到java的编译器
          
          System. out.println(compiler.getClass().getName());
          
          StandardJavaFileManager filemag=compiler.getStandardFileManager(null, null, null);//文件的管理器
          
           Iterable  untis= filemag.getJavaFileObjects(fileName);//找到文件,把文件放在 Iterable
          
          CompilationTask t=compiler.getTask( null, filemag, null, null , null, untis );//编译文件任务
          t.call(); //编译文件
          
          filemag.close();
          
           //编译之后,我们打开window show View 选择Navigator(这个可以看到类详细的变化,就是看得到class文件的产生)
          
           /********************load 到内存,和创建对象*************************/
          
           //如果要使用class.loader,就必须保证这个class在 classpath的路径下
          
           //因此我们要用一个特殊的loader
          URL [] urls= new URL[]{new URL("file:/"+System.getProperty( "user.dir")+"/src" )};
          URLClassLoader ul= new URLClassLoader(urls);//这里需要一个数组地址
           Class c=ul.loadClass("com.text.TankTimeProxy" );//把类加到内存
          
          System. out.println(c);
          
           //反射来创建对象
           Constructor ctr=c.getConstructor(Moveable.class) ;//获取构造方法
          
          Object m=ctr.newInstance( h);
     
          
           return m;
     }

}

即使这样,我们还是遇到一个问题,那就是每个接口的方法不是一样的,有的多,有的少,这样有怎么办喃?


          那我们把方法也修改了,就是上面的那个段代码:
 for (Method m: methods){
              
               methodsString=methodsString+"@Override"+
              "    public void "+m.getName()+"() {"+
                        
                        "    long start=System.currentTimeMillis();"+
                        "    t."+m.getName()+"();"+
                        "    long end=System.currentTimeMillis();"+
                        "     System.out.println((end-start));"+
                        
                   "    }";

这样就能保证,即使方法不统一,我也可以让每个方法都执行计时功能哦



6.现在我们来解决下一个问题,我们每个接口都是来实现计时功能的?显然不是,那肯定还有其他功能三


          那我们怎么样来做,才可以是我们想实现什么功能,就实现什么功能喃?动态代理?

          我们按照一贯的做法,继续把功能也传经来。。


     看代码:

     public class Proxy {
     //这个类的作用就是用来产生新的代理类
     public static Object newProxyInstance(Class infc,InvocationHandler h)throws Exception{//把接口也传进去
          String methodsString= "";
          
          Method methods[]=Moveable. class.getMethods();
          
           for(Method m: methods){
              
               /*methodsString=methodsString+"@Override"+
              "    public void "+m.getName()+"() {"+
                        
                        "    long start=System.currentTimeMillis();"+
                        "    t."+m.getName()+"();"+
                        "    long end=System.currentTimeMillis();"+
                        "     System.out.println((end-start));"+
                        
                   "    }";*/
              methodsString=methodsString+ "@Override"+
                         "    public void "+m.getName()+"() {"+
                              "Method md="+infc.getName()+".class.getMethod(\""+m.getName()+ "\");"+
                              "h.invoke(this,md) ;"+
                              "    }";
              
          }
        
           /*把这个类当成一个string的字符串(源码)
          现在我们假设,我们能把这字符串编译,生成类,放在内存,来产生对象
          
          动态代理就是你看不到代理类,你只需要调用一个方法( Proxy的newProxyInstance()方法),
          会自动给你返回一个代理类对象,这个对象的产生是由内部动态的生成一段代码,编译完成的*/
          
           //现在我们就来动态的编译这段代码(JDK6,complier API,CGlib,ASM(直接生成二进制的class文件))
          String  src=
           "package com.text;"+

           "public class TankTimeProxy implements "+infc.getName()+"{"+
              
               "public TankTimeProxy(InvocationHandler h) {"+
               "    this.h = h;" +
           "    }"+
           //"  Moveable t;"+
          
           "com.text.InvocationHandler h ; " +
          
          
          methodsString
          +
                   
           "}";
          
          
           //获取当前系统目录(就是项目根目录)
          String fileName=System.getProperty("user.dir")+ "/src/com/text/TankTimeProxy.java" ;
          
           //System.out.println(fileName);
          File f= new File(fileName);
          FileWriter writer= new FileWriter(f);
          writer.write(src);
          writer.flush();
          writer.close();
          
           //看是否生成代码,右键项目,刷新就OK了
          
          
           /*****************编译********************/
          
           //这句话的作用就是获取系统当前默认的编译器(其实就 javac
          JavaCompiler compiler=ToolProvider.getSystemJavaCompiler(); //拿到java的编译器
          
          System. out.println(compiler.getClass().getName());
          
          StandardJavaFileManager filemag=compiler.getStandardFileManager(null, null, null);//文件的管理器
          
           Iterable  untis= filemag.getJavaFileObjects(fileName);//找到文件,把文件放在 Iterable
          
          CompilationTask t=compiler.getTask( null, filemag, null, null , null, untis );//编译文件任务
          t.call(); //编译文件
          
          filemag.close();
          
           //编译之后,我们打开window show View 选择Navigator(这个可以看到类详细的变化,就是看得到class文件的产生)
          
           /********************load 到内存,和创建对象*************************/
          
           //如果要使用class.loader,就必须保证这个class在 classpath的路径下
          
           //因此我们要用一个特殊的loader
          URL [] urls= new URL[]{new URL("file:/"+System.getProperty( "user.dir")+"/src" )};
          URLClassLoader ul= new URLClassLoader(urls);//这里需要一个数组地址
           Class c=ul.loadClass("com.text.TankTimeProxy" );//把类加到内存
          
          System. out.println(c);
          
           //反射来创建对象
           Constructor ctr=c.getConstructor(InvocationHandler.class) ;//获取构造方法
          
          Object m=ctr.newInstance(h);
     
          
           return m;
     }

}

我在代码中增加一个叫InvocationHandler 的类,它的功能就是告诉我,什么对象要来实现什么功能


          来理一下思路:     我要实现什么功能?(这里是我要?如果其他人喃?)

                                   那就变成:什么人要实现什么功能?(object o ,Method m)
                                       

public interface InvocationHandler {//指定方法(你需要时间,日志,还是其他)
           public void invoke(Object o,Method m) throws Exception;//告诉那个对象去执行这个方法
}


比如说我要实现计时的功能
          我传递的参数就是(I, time)

            我们来写一下实现类

     public class TimeHandler implements InvocationHandler{

     @Override
     public void invoke(Object o,Method m) throws Exception{
           long start=System.currentTimeMillis();
                         m.invoke(o, null);
                         long end=System.currentTimeMillis();
                        System. out.println((end-start));
          
     }
     

}          

我们运行下代码,发现出错了

这是因为我们不是代理  一个具体类来实现功能吗?但是现在却看到具体类的影子?

比如:我们要代理Tank 实现计时功能,就要传入Tank类  

我们来修改一下代码:

    public class TimeHandler implements InvocationHandler{
     
     private Object t;
     

     public TimeHandler(Object t) {
           super();
           this.t = t;
     }


     @Override
     public void invoke(Object o,Method m) throws Exception{
           long start=System.currentTimeMillis();
                        m.invoke( t);
                         long end=System.currentTimeMillis();
                        System. out.println((end-start));
          
     }
     

}

测试类:

public class Client {
     public static void main(String[] args) throws Exception{

          Tank t= new Tank();
         
          TimeHandler h= new TimeHandler(t);
          Moveable m=(Moveable)Proxy.newProxyInstance(Moveable. class,h);//这句话就是我的Moveable接口的实现类Tant 要实现计时功能
          m.move();
          
     }

}


运行:

结果:
com.sun.tools.javac.api.JavacTool
class com.text.TankTimeProxy
Tank moving....
6775


我们来整理一下我们的思路


          首先我们有一个Tank对象,它实现了一个moveable接口,它就具有了一个move移动的方法,我们想在move方法的前后加上一些逻辑,这些逻辑由我们来定,所以我们首先定义自己的逻辑,我们就建立了TimeHandler类,当调用这个类的时候,就会把Tank的代理类的实体传进来,我们就可以在move方法的前后加上一些逻辑。


这样做的好处就是可以对任意的对象,任意的接口,实现任意的代理

我们现在来写一个Person 类

public class Person implements Moveable{

     @Override
     public void move() {
          System. out.println("我正在走路" );
           try {
              Thread. sleep(new Random().nextInt(10000));
          } catch (InterruptedException e) {
               // TODO Auto-generated catch block
              e.printStackTrace();
          }
     }

}

我们也来实现计时

测试:
public class Client {
     public static void main(String[] args) throws Exception{
     

          Person person= new Person();
          TimeHandler h= new TimeHandler(person);
          
          Moveable m=(Moveable)Proxy.newProxyInstance(Moveable. class, h);
          m.move();
          
          
     }

}
     
结果:
com.sun.tools.javac.api.JavacTool
class com.text.TankTimeProxy
我正在走路
com.text.TankTimeProxy
8240

我想你发现代理类的好处了吧。。。。。就是只需要写一次,你就可以在任意的类中完成任意的代理


再来测试不同的接口

public interface Run {
           public void run();//跑
}

我们用Person来实现这个接口,并计时

public class Person implements Pao {

     @Override
     public void rrrrun() {
          System. out.println("我正在跑步" );
           try {
              Thread. sleep(new Random().nextInt(10000));
          } catch (InterruptedException e) {
               // TODO Auto-generated catch block
              e.printStackTrace();
          }
          
     }



     
}

测试:
public class Client {
     public static void main(String[] args) throws Exception{
     

          Person person= new Person();
          TimeHandler h= new TimeHandler(person);
          
          Pao m=(Pao)Proxy. newProxyInstance(Pao.class, h);
          
          m.rrrrun();
          
          
     }

}

结果:

com.text.Paollll
com.sun.tools.javac.api.JavacTool
class com.text.TankTimeProxy
public void com.text.TankTimeProxy.rrrrun()
我正在跑步
com.text.TankTimeProxy
8171




               


版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

Java动态代理模式jdk和cglib的2种实现以及二者的区别(AOP面向切面的前奏)

关于动态代理模式里面有两种实现,一种是jdk实现,一种是cglib来实现。 下面来整jdk来实现动态代理的Java实例。 jdk动态代理模式里面有个拦截器的概念,在jdk中,只要实现了Invocati...

java 动态代理实现AOP

  • 2012年05月17日 14:30
  • 139KB
  • 下载

Spring AOP 学习之java JDK动态代理

前面讲了静态代理,现在来总结动态代理,java动态代理有两种,一种是JDK动态代理,一种是cglib动态代理。JDK动态代理动态代理类只能代理接口,代理类都需要实现InvocationHandler类...

java动态代理与AOP实例

  • 2013年05月26日 17:06
  • 3KB
  • 下载

java动态代理实例aop

  • 2017年04月20日 21:22
  • 10.89MB
  • 下载

[Spring学习笔记 4 ] AOP 概念原理以及java动态代理

一、Spring IoC容器补充(1) Spring IoC容器,DI(依赖注入): 注入的方式:设值方法注入setter(属性注入)/构造子注入(构造函数传入依赖的对象)/字段注入Field(注解)...
  • lwj0310
  • lwj0310
  • 2014年04月25日 12:02
  • 443

Java动态代理在AOP中的应用

  • 2013年04月01日 14:31
  • 299KB
  • 下载

Spring 4 学习笔记4:Java动态代理(Spring AOP原理)

介绍Spring AOP原理,Java动态代理
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:java学习之路----静态代理---动态代理-----AOP的前奏(AOP也是动态代理)
举报原因:
原因补充:

(最多只允许输入30个字)