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

 什么叫代理? 什么是静态代理?什么是动态代理?

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

     直接看代码:


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




               


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值