黑马程序员-java加强-代理

------- android培训java培训、期待与您交流! ----------

代理的概念和作用:

要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如:异常处理、日志、计算方法的运行时间、事务处理等等.你准备如何做?

编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用的目标类的方法周围加上系统功能代码。

在客户端用接口来引用:如果想用代理,就传递代理的对象,如果想用目标,就传递目标的对象。

如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端的程序,在配置文件中配置是使用目标类还是使用代理类。这样以后很容易切换。

譬如:想要日志功能就配置代理类,否则配置目标类。这样增加系统功能很容易,以后去掉系统功能也很容易。

Interface   InterfaceA{

        void     dosomething();

      }

      X   implements      InterfaceA{

        void     dosomething(){

      }

}

XProxy  implements  InterfaceA{

         void     dosomething(){

         beforetime…

         new  X().dosomething();

         endtime;

      }

}

   class    Client{

          public    static   void  main(String[] args){

InterfaceA obj=Class.forName(“xxxxxxxxxxx”).newinstance;

          }

   }

这样客户端的代码不用修改,只用修改配置文件就可以了。配置文件中配置到底调用的是类名。

什么是交叉业务:

系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:

                              安全       事务         日志

StudentService  ------|----------|------------|-------------

CourseService   ------|----------|------------|-------------

MiscService       ------|----------|------------|-------------

用具体的程序代码描述交叉业务:

  method1         method2          method3

   {                      {                       {

   ------------------------------------------------------切面

    ....            ....              ......

   ------------------------------------------------------切面

   }                       }                       }

  交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与 直接在方法中编写切面代码的运行效果是一样的,如下所示:

------------------------------------------------------切面

func1         func2            func3

{             {                {

....            ....              ......

}             }                }

------------------------------------------------------切面

使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
动态代理技术:
要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情。
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理,即动态代理类。
JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
用Collection接口生成的代理类,目标可以是ArrayList,也可以是Hashset。因为它们有相同的接口。
生成的动态类要和目标类具有相同的方法(此处说的不太严谨,因为目标类中的某些方法在接口中并没有定义,而是目标类
自己的方法),因为继承了接口,所以动态生成类的时候,就知道需要生成什么方法。
如果一个类没有实现任何接口,怎样为该类动态生成一个代理呢?
CGLIB库可以动态生成一个类的子类,一个类的子类也可以作为该类的代理。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置
加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
总之,代理类要有和目标类中所有方法签名相同的方法。
不过用接口方式生成动态类的话,生成的动态类中的方法仅仅是接口中的所有方法,另外因为生成的是类,所以会有Object
中的所有方法,目标类中的个别方法是自身特有的。在接口中没有定义,所以这些特有的方法在动态类中没有。
而用CGLIb库的话,因为是继承,生成的动态类会有父类所有的方法。

java.lang.reflect
Proxy 中的方法

static   Class<?>

getProxyClass(ClassLoaderloader,Class<?>...interfaces)
 
返回代理类的java.lang.Class对象,并向其提供类加载器和接口数组。

这个方法就是在内存中造出一份字节码。在造出字节码过程中,要指定该类实现了什么接口。因为该字节码是直接在内存中生成的,所以没有用类加载器加载。但是为了统一,所有的字节码都关联的类加载器。所以此处指定一个类加载器即可,可以随意指定,但是一般情况下指定为所实现的某个接口关联的类加载器。

JVM创建动态类及其实例对象,需要给他提供哪些信息?

1.      生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知。

2.      产生的类字节码必须有个关联的类加载器对象。

3.      生成的类中的方法的代码是怎样的,也是由我们提供。把我们的代码写在一个约定好的接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码,提供执行代码的那个对象就是那个InvocationHandler对象。它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用了。

创建动态代理的几种方式:

   public   class    ProxyTest {
                   @Test
                    public   void   test1()   throws Exception {
                          // 先在内存中创建一个实现某接口的动态类字节码
                          Class clazzProxy = Proxy.getProxyClass(
                                                                              Collection.class.getClassLoader(), Collection.class);
                          // 获取该字节码的构造方法的对象。
                          Constructor constructor = clazzProxy
                                                                         .getConstructor(InvocationHandler.class);
                          // 实现InvocationHandler接口的子类。
                          class MyInvocationHandler implements InvocationHandler {

                         @Override
                         public Object invoke(Object proxy, Method method, Object[] args)
                         throws Throwable {
                                                                  return null;
                            }
                   }
                       // 调用构造方法对象,传入InvocationHandler的子类对象,创建出动态类的对象。
                     Collection proxy = (Collection) constructor
                                                                         .newInstance(new MyInvocationHandler());
                      // Collection的clear方法返回值为void,运行不会出错。
                      proxy.clear();
                      // Collection的size方法有返回值,运行出错了,这是因为返回的null,无法转变成为int类型的数据。
                      // System.out.println(proxy.size());
              }
   @Test
   public   void     test5() throws Exception {
                     Class clazzProxy = Proxy.getProxyClass(
                                                                  Collection.class.getClassLoader(), Collection.class);
                     Constructor constructor = clazzProxy
                                                                    .getConstructor(InvocationHandler.class);
                     // 此处与test4的不同之处就在于用匿名内部类的形式实现了InvocationHandler接口。
                     Collection proxy = (Collection) constructor
                                                            .newInstance(new InvocationHandler() {
             @Override
              public Object invoke(Object proxy, Method method,
                                                                              Object[] args) throws Throwable {
                     return null;
              }
          });
                      System.out.println(proxy);
      }


@Test
        // 将分步合为一步来完成。
       public   void    test6() throws Exception {
                Collection proxy = (Collection) Proxy.newProxyInstance(
                                                                     Collection.class.getClassLoader(),
                                                                                       new Class[] { Collection.class }, new InvocationHandler() {
                @Override
                 public Object invoke(Object proxy, Method method,
                                                                                     Object[] args) throws Throwable {
                 return null;
                       }
                 });
               System.out.println(proxy);
        }


      @Test
      // 加入了调用目标类对象的方法的代码。
       public   void   test7() throws Exception {
                     Collection proxy = (Collection) Proxy.newProxyInstance(
                                                                               Collection.class.getClassLoader(),
                                                                                          new Class[] { Collection.class }, new InvocationHandler() {
                     ArrayList target = new ArrayList();
                     @Override
                     public   Object     invoke(Object proxy, Method method,
                                                                                             Object[] args) throws Throwable {
                     /*
                      *  这个method是proxy.getClass.getMethod("methodname")得来的,而不是
                      *  target.getClass.getMethod("methodname");我想应该都是一样的吧。可
                      * 作用于目标对象。
                      */
                    Object retVal = method.invoke(target, args);
                    // 返回方法执行后的结果。
                     return retVal;
                   }
             });
                  proxy.add("zxx");
                  proxy.add("lhm");
                 // 此时调用了有返回值的方法不会出错。
                  System.out.println(proxy.size());
      }


@Test
// 加入了功能代码
public void test8() throws Exception {
                       Collection proxy = (Collection) Proxy.newProxyInstance(
                                                                           Collection.class.getClassLoader(),
                                                                                                        new Class[] { Collection.class }, new InvocationHandler() {
                       ArrayList target = new ArrayList();
                       @Override
                       public   Object    invoke(Object proxy, Method method,
                                                                                        Object[] args) throws Throwable {
                       long begintime = System.currentTimeMillis();
                       Object retVal = method.invoke(target, args);
                       long endtime = System.currentTimeMillis();
                       System.out.println(method.getName() + "  run time is: "
                                                                                                    + (endtime - begintime));
                      return retVal;
                   }
                });
                  proxy.add("zxx");
                  proxy.add("lhm");
                  System.out.println(proxy.size());
              }
      }
动态类内部的代码猜想:

动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接受InvocationHandler参数的构造方法。

  $Proxy0   implements   Collection
  {
         InvocationHandler handler;
         public $Proxy0(InvocationHandler handler)
        {
             this.handler = handler;
        }
        //生成的Collection接口中的方法的运行原理
        int size()
        {
            return handler.invoke(this,this.getClass().getMethod("size"),null);
        }
        void    clear(){
            handler.invoke(this,this.getClass().getMethod("clear"),null);
        }
         boolean     add(Object obj){
           handler.invoke(this,this.getClass().getMethod("add"),obj);
        }
  }
InvocationHandler  接口中定义的invoke方法接收的三个参数是什么意思呢?
Client程序调用
proxy.add(“abc”)(注意这里是代理对象在调用代理的add方法)方法时候,涉及到三个要素:
proxy对象,代理的add方法,add方法的参数值。


Class $ Proxy0{
      add(Object object) {
      return handler.invoke(Object proxy, Method method, Object[] args);
   }
}
因为在调用代理的add方法的时候,add方法中就交给handler对象的invoke方法进行处理。
第一个参数的意思是:处理哪个代理类对象。
第二个参数的意思是:处理代理类对象的哪个方法。
第三个参数的意思是:该方法需要接受的参数值。
为什么动态类的实例对象的getClass()方法返回了正确结果呢?
调用代理对象从Object类继承的hashCode, equals, 或toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于其他方法,则不转发调用请求。所以打印的结果是:$Proxy数字 这个名称。

为了扩展性,也可以说将此做成框架的时候.会出现两点问题:

系统功能如果硬编码在InvocationHanlderinvoke方法中,灵活性太差。而应该是用户需要什么功能,自己编写。

如果将一段代码传递到invoke方法内去执行。

此处和线程的处理方式很类似:可以将功能代码封装在一个类的方法中,将该类的对象传递给InvocationHandler,在invoke方法中,调用该类对象的方法也就是执行了功能代码。

另外,目标对象也不能硬编码在InvocationHandler对象中,而是应该传递进去。如果硬编码的话,该代理框架只能用于某一个目标。

如下所示的就是一个不好的示例:

功能代码硬编码在invoke方法中,目标对象硬编码在InvocationHandler对象中。

    publicvoid     test8() {

               Collection    proxy =    (Collection) Proxy.newProxyInstance(

                                                                           Collection.class.getClassLoader(),

                                                                                                  new Class[] { Collection.class },new InvocationHandler() {

                     ArrayList target =newArrayList();

                    @Override

                     public Object invoke(Object proxy, Method method,

                                                                            Object[] args)throws Throwable {

                     long begintime = System.currentTimeMillis();

                     Object retVal = method.invoke(target, args);

                     long endtime = System.currentTimeMillis();

                     System.out.println(method.getName()+ "  run time is: "

                                                                                        +(endtime - begintime));

                     return retVal;

                  }

              });

       proxy.add("zxx");

       proxy.add("lhm");

       System.out.println(proxy.size());

    }

之前的示例都是直接用接口来生成代理对象,而真实的用法是用传入的目标对象来获与配置取该目标类所实现的接口。

用于为某个对象生成和返回其代理对象,源对象必须实现接口,生成的代理对象会实现与源对象相同的接口。

以下是比较好的处理方式:

1.

/*
* 要说明的是,Advice接口中的方法可以接收InvocationHandler
* 的invoke方法的参数来用。
* 就比如aftermethod就定义了method参数,该方法可以在内部用
* 从invoke传递来的method。
*/
     public     interface      Advice {
             void beforeMethod();

             void afterMethod(Method method);
   }
2.

public    class    MyAdvice     implements Advice {
             long beforetime = 0;

             @Override
              public   void    beforeMethod() {
                     beforetime = System.currentTimeMillis();
              }

             @Override
             public  void    afterMethod(Method method) {
             long endtime = System.currentTimeMillis();
             System.out.println(method.getName() + " run time is  :"
                                                                             + (endtime - beforetime));
             }
     }

3.

     public   class     ProxyTest {
           public   static    void     main(String[] args) {
                 // 这样就将getProxy做成了一个小框架,以后用户只需要做的就是配置目标对象
                 // 以及编写系统功能代码就可以了。生成代理操作就不用再由用户来写了。
                 final ArrayList target = new ArrayList();
                 final Advice advice = new MyAdvice();
                 Collection proxy = (Collection) getProxy(target, advice);
                 proxy.add("hhh");
                 proxy.add("lim");
                 proxy.add("zxx");
                 System.out.println(proxy.size());
          }

    private   static Object getProxy(final Object target, final Advice advice) {
                 Object proxy = Proxy.newProxyInstance(target.getClass()
    .                                                                                     getClassLoader(), target.getClass().getInterfaces(),
                 new InvocationHandler() {
                 @Override
                 public Object invoke(Object proxy, Method method,
                                                                                         Object[] args) throws Throwable {
                 advice.beforeMethod();
                 Object retVal = method.invoke(target, args);
                 advice.afterMethod(method);
                 return retVal;
                   }
                });
              return proxy;
           }
     }
实现AOP功能的封装与配置

具体步骤如下:

1. 工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。
    其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中
    对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该类实例
    对象的getProxy方法返回的对象。
2. BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:

        #xxx=java.util.ArrayList

        xxx=cn.itcast.ProxyFactoryBean

        xxx.target=java.util.ArrayList

        xxx.advice=cn.itcast.MyAdvice

3. ProxyFacotryBean充当封装生成动态代理的工厂,需要为工厂类提供哪些配置参数信息?
     (1)目标
     (2)通知
4. 编写客户端应用:
     (1)编写实现Advice接口的类和在配置文件中进行配置
       (2)调用BeanFactory获取对象
具体实现代码如下:
   1.
   public    class     BeanFactory {
             Properties props = new Properties();
             public BeanFactory(InputStream ips){
             try {
                       props.load(ips);
                  } catch (IOException e) {
           e.printStackTrace();
          }
      }

    public    Object     getBean(String name){
              String className = props.getProperty(name);
              Object bean = null;
            try {
                     Class clazz = Class.forName(className);
                     bean = clazz.newInstance();
                          } catch (Exception e) {
                       e.printStackTrace();
                 }
         if(bean instanceof ProxyFactoryBean){
         Object proxy = null;
         ProxyFactoryBean      proxyFactoryBean = (ProxyFactoryBean)bean;
        try {
                   Advice advice = (Advice)Class.forName(props.getProperty(name + ".advice")).newInstance();
                   Object target = Class.forName(props.getProperty(name + ".target")).newInstance();
                   proxyFactoryBean.setAdvice(advice);
                   proxyFactoryBean.setTarget(target);
                   proxy = proxyFactoryBean.getProxy();
             } catch (Exception e) {
    // TODO Auto-generated catch block
                e.printStackTrace();
       }
            return proxy;
       }
      return bean;
     }
   }
2
  public      class   ProxyFactoryBean {

                  private Advice advice;
                  private Object target;

                  public Advice getAdvice() {
                  return advice;
      }

       public void setAdvice(Advice advice) {
                  this.advice = advice;
      }

       public  Object    getTarget() {
                  return target;
      }

       public   void   setTarget(Object target) {
                  this.target = target;
      }

      public     Object   getProxy() {
          Object    proxy3 = Proxy.newProxyInstance(
                      target.getClass().getClassLoader(),
                      /*new Class[]{Collection.class},*/
                      target.getClass().getInterfaces(),
                      new InvocationHandler(){
                      public Object invoke(Object proxy, Method method, Object[] args)
                                                                                                              throws Throwable {

                     /*long beginTime = System.currentTimeMillis();
                     Object retVal = method.invoke(target, args);
                     long endTime = System.currentTimeMillis();
                     System.out.println(method.getName() + " running time of " + (endTime - beginTime));
                     return retVal;*/
                     advice.beforeMethod(method);
                     Object retVal = method.invoke(target, args);
                     advice.afterMethod(method);
                     return retVal;     
                }
            }
         );
                     return proxy3;
          }

     }

3.
    public     class     AopFrameworkTest {
                  public static void main(String[] args) throws Exception {
                  // TODO Auto-generated method stub
                   InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");
                  Object bean = new BeanFactory(ips).getBean("xxx");
                  System.out.println(bean.getClass().getName());
                  ((Collection)bean).clear();
             }

      }

-------android培训java培训、期待与您交流! ----------

详细请查看:http://edu.csdn.net/heima

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值