代理模式

代理模式:用来解决直接访问带来的问题(比如对象创建开销很大,或某些操作需要安全控制,或者需要进程外的访问),增加中间层,实现与被代理类组合。

应用实例: 1、Windows 里面的快捷方式。2、如今风靡的代购。3、spring aop(经典)

优点: 1、职责清晰。 2、高扩展性。 3、智能化。

缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

 

代码演示:ProxyImage 是一个代理类,减少 RealImage 对象加载的内存占用。

ProxyPatternDemo是测试类


接口:

public interface Image {
   void display();
}

 

实体类:需要被代理的类(委托类)

public class RealImage implements Image {

   private String fileName;
   public RealImage(String fileName){
      this.fileName = fileName;
      loadFromDisk(fileName);
   }

   @Override
   public void display() {
      System.out.println("Displaying " + fileName);
   }

   private void loadFromDisk(String fileName){
      System.out.println("Loading " + fileName);
   }

}


代理类:

public class ProxyImage implements Image{

   private RealImage realImage;
   private String fileName;

   public ProxyImage(String fileName){
      this.fileName = fileName;
   }

   @Override
   public void display() {
      if(realImage == null){
         realImage = new RealImage(fileName);
      }
      realImage.display();
   }
}
----具体的都是在代理类中注入委托类,然后调用委托类的对应方法
 

测试类:

public class ProxyPatternDemo {

   public static void main(String[] args) {
      Image image = new ProxyImage("test_10mb.jpg");
      //图像将从磁盘加载
      image.display();
      System.out.println("");
      //图像将无法从磁盘加载
      image.display();     
   }
}

上述代码就是静态代理:

优点

代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合)

 

缺点

1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度

2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。

 ---------------------------------------------------------完美的分割线----------------------------------------------------------

动态代理:

    每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类,所以我们就会想办法可以通过一个代理类完成全部的代理功能,那么我们就需要用动态代理

Java中要想实现动态代理机制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 类的支持


java.lang.reflect.InvocationHandler接口

//Object proxy:被代理的对象  
//Method method:要调用的方法  
//Object[] args:方法调用时所需要参数  

public interface InvocationHandler {  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;  
}  

API给出了这样的定义:           

     InvocationHandler是代理实例的调用处理程序实现的接口。

     每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,

     将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法

 

java.lang.reflect.Proxy类的定义:

//CLassLoader loader:类的加载器  
//Class<?> interfaces:得到全部的接口  
//InvocationHandler h:得到InvocationHandler接口的子类的实例  

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

提供用于创建动态代理类和实例的静态方法,它还是由newProxyInstance方法创建的所有动态代理类的超类。

API:

      A method invocation on a proxy instance through one of its proxy interfaces will be dispatched to the invoke method of the instance's invocation handler, passing the proxy instance,a java.lang.reflect.Method object identifying the method that was invoked, and an array of type Object containing the arguments.  

   一个方法调用一个代理实例通过其代理接口将被派遣到调用方法的实例的调用处理程序,通过代理实例,一个java.lang.reflect.method对象被调用的方法识别,和类型的对象包含的参数数组。

 

 

代码讲解:

接口: 实体类需要实现的接口

public interface Hello {
  void say(String name);
  void say2(String name);
}

 

实体类: 需要被代理的对象

public class HelloImpl implements Hello {

  @Override
  public void say(String name) {
    System.out.println("Hello!"+name);
  }

  @Override
  public void say2(String name) {
     System.out.println("HEllo!"+name+"用来测试的");
  }
}

代理类:

public class DynamicProxy implements InvocationHandler {

 private Object target;  
 public DynamicProxy(Object target){  //这里就传入了一个动态代理对象
        this.target=target;  
 }  


 @SuppressWarnings("unchecked")  
 public <T> T getProxy(){  
        return (T) Proxy.newProxyInstance(  
                target.getClass().getClassLoader(),  
                target.getClass().getInterfaces(),  
                this  
        );  
    }  

  
    @Override  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        before();  
        Object result =method.invoke(target,args);  
        after();  
        return result;  
    }  

    private void before(){  
        System.out.println("Before代理");  
    }  
    private void after(){  
        System.out.println("After代理");  
    }  

}

测试方法:

    public static void main(String[] args){  

//        Hello hello =new HelloImpl();  
//        DynamicProxy dynamicProxy = new DynamicProxy(hello);  
//        Hello helloProxy=(Hello) Proxy.newProxyInstance(  
//                hello.getClass().getClassLoader(),  
//                hello.getClass().getInterfaces(),  
//                dynamicProxy  
//        );  
//        helloProxy.say("Jack");  

    	   DynamicProxy dynamicProxy=new DynamicProxy(new HelloImpl());  
           Hello helloProxy =dynamicProxy.getProxy();  //f返回一个Proxy的一个实例对象,这里可以debug去追溯追溯
           System.out.println(helloProxy instanceof Proxy);//判断是不是Proxy的一个实例  
           //这里可以看出helloProxy的Class类是$Proxy0,这个$Proxy0类继承了Proxy,实现了Hello接口  
           System.out.println("helloProxy的Class类是:"+subject.getClass().toString());  
           System.out.println("\n"+"helloProxy的父类是:"+helloProxy.getClass().getSuperclass());  
           System.out.print("\n"+"helloProxy实现的接口是:");  

         
           Class<?>[] interfaces=subject.getClass().getInterfaces();

            for(Class<?> i:interfaces){  
              System.out.print(i.getName()+", ");  
            }  

             helloProxy.say("Jack");  
             helloProxy.say2("Wade");

}  

然后我们深入的去看$Proxy0的源码:

      先从newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)  方法进去, Class cl = getProxyClass(loader, interfaces); 这句 

     1)根据参数loader和interfaces调用方法 getProxyClass(loader, interfaces)创建代理类$Proxy0.$Proxy0类 实现了interfaces的接口,并继承了Proxy类.

       2)实例化$Proxy0并在构造方法中把DynamicProxy传过去,接着$Proxy0调用父类Proxy的构造器,为h赋值,如下:class Proxy{  

    InvocationHandler h=null;  

    protected Proxy(InvocationHandler h) {  

        this.h = h;  

    }  

    ...  

}  

类名:“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率

最后细看$Proxy0的源码:

public final class $Proxy0 extends Proxy implements Subject {  

    private static Method m1;  
    private static Method m0;  
    private static Method m3;  
    private static Method m2;  
  
    static {  
        try {  
            m1 = Class.forName("java.lang.Object").getMethod("equals",  
                    new Class[] { Class.forName("java.lang.Object") });  
 
            m0 = Class.forName("java.lang.Object").getMethod("hashCode",  
                    new Class[0]);  
  
            m3 = Class.forName("***.HelloImpl").getMethod("say",  
                    new Class[0]);  
  
            m2 = Class.forName("java.lang.Object").getMethod("toString",  
                    new Class[0]);  

        } catch (NoSuchMethodException nosuchmethodexception) {  
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());  
        } catch (ClassNotFoundException classnotfoundexception) {  
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());  
        }  
    } //static  
  
    public $Proxy0(InvocationHandler invocationhandler) {  
        super(invocationhandler);  
    }  
  

    @Override  
    public final boolean equals(Object obj) {  
        try {  
            return ((Boolean) super.h.invoke(this, m1, new Object[] { obj })) .booleanValue();  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  

    @Override  
    public final int hashCode() {  
        try {  
            return ((Integer) super.h.invoke(this, m0, null)).intValue();  
        } catch (Throwable throwable) {  
           throw new UndeclaredThrowableException(throwable);  
        }  
    }  

    public final void say() {  
        try {  
            super.h.invoke(this, m3, null);  
            return;  
        } catch (Error e) {  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  

    @Override  
    public final String toString() {  
        try {  
            return (String) super.h.invoke(this, m2, null);  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
}  

   这里显示调用invoke方法的有四个方法,一个是我们自己要调用的say方法,另外三个是Object类的toString(),equals,hashCode, 接着把得到的$Proxy0实例强制转换成Hello,并将引用赋给helloProxy。当执行say()方法时,就调用了$Proxy0类中的say()方法,进而调用父类Proxy中的h的invoke()方法.即InvocationHandler.invoke()

       最终对上面api的那段翻译的理解: 当我们显示的去调用那个方法时,我们是通过newProxyInstance(对象)方法,将返回一个继承了Proxy,实现接口的对象返回,然后按照上述的源代码,最终调用了invoke方法,最终方法还是从代理类转到委托类的调用

这张图能清楚的了解到,$ProxyNProxy的子类,并且实现委托类的所有接口

 

     异常处理方面的特点:从调用处理器接口声明的方法中可以看到理论上它能够抛出任何类型的异常,因为所有的异常都继承于 Throwable 接口,但事实是否如此呢?答案是否定的,原因是我们必须遵守一个继承原则即子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。所以虽然调用处理器理论上讲能够,但实际上往往受限制,除非父接口中的方法支持抛 Throwable 异常。那么如果在 invoke 方法中的确产生了接口方法声明中不支持的异常,那将如何呢?放心,Java 动态代理类已经为我们设计好了解决方法:它将会抛出 UndeclaredThrowableException 异常。这个异常是一个 RuntimeException 类型,所以不会引起编译错误。通过该异常的 getCause 方法,还可以获得原来那个不受支持的异常对象,以便于错误诊断

      Java内置的动态代理的缺陷是:只能实现是接口的代理类,更完美的得看CGLIB的动态代理,原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理.

 

//没有实现接口的类

public class BookFacadeImpl1 {  
    public void addBook() {  
        System.out.println("增加图书的普通方法...");  
    }  
}  
/**
 * 使用cglib动态代理
 */  
public class BookFacadeCglib implements MethodInterceptor {  
    private Object target;  

    /**
     * 创建代理对象
     * @param target
     * @return
     */  
    public Object getInstance(Object target) {  
        this.target = target;  
        Enhancer enhancer = new Enhancer();  
        enhancer.setSuperclass(this.target.getClass());  //方法通过输入参数即父类的字节码,通过扩展父类的class来创建代理对象
        // 回调方法  
        enhancer.setCallback(this);  //调用intercept方法
        // 创建代理对象  
        return enhancer.create();  
}  

 

1.创建Enhancer实例

2.通过setSuperclass方法来设置目标类

3.通过setCallback 方法来设置拦截对象

4.create方法生成Target的代理类,并返回代理类的实例

 

    @Override  
    // 回调方法  ,这个方法会拦截目标类的所有方法

    public Object intercept(Object obj, Method method, Object[] args,  
            MethodProxy proxy) throws Throwable {  

        System.out.println("事物开始");  

        proxy.invokeSuper(obj, args);  

        System.out.println("事物结束");  

        return null;  

  // obj表示目标类的实例,method为目标类方法的反射对象,args为方法的动态入参,proxy为代理类实例。proxy.invokeSuper(obj, args)通过代理类调用父类中的方法。
    }  
}  

 

测试方法:

  public static void main(String[] args) {  
        BookFacadeCglib cglib=new BookFacadeCglib();  
        BookFacadeImpl1 bookCglib=(BookFacadeImpl1)cglib.getInstance(new BookFacadeImpl1());  
        bookCglib.addBook();  
}  

       CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

       CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理

 

我们根据生成的字节码文件,去反编译,看源码分析会发现会把目标类的所有类生成,如果代理对象存在,都会去调用intercept方法

具体的可以看这 https://www.cnblogs.com/cruze/p/3865180.html

 

      Jdk动态代理中拦截对象是在实例化代理类时由构造函数传入的,在cglib中是调用Enhancer的firstInstance方法来生成代理类实例并设置拦截对象的

 

另外:为什么不直接反射调用代理类生成的(CGLIB$g$0)来间接调用目标类的被拦截方法而使用proxy的invokeSuper方法呢?这里就涉及到了另外一个点— FastClass:

       Jdk动态代理的拦截对象是通过反射的机制来调用被拦截方法的,反射的效率比较低,所以cglib采用了FastClass的机制来实现对被拦截方法的调用。FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法

       它对每个方法建立了索引,通过传入的方法名+方法描述符,来获取相应的索引值,根据switch,case,不同索引值对应不同的逻辑处理(这里有对相应方法的引用,这里是直接调用了目标类的方法),Invoke根据指定的索引,以ol为入参调用对象O的方法。这样就避免了反射调用,提高了效率


更加想深入的同学可以看这个文章: 

https://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值