zoukankan      html  css  js  c++  java
  • Java设计模式:(一)动态代理分析 (含静态代理)

    代理模式:为其他对象提供一种代理以控制某个对象的访问。用在:在某些情况下,一个客户不想或者不能直接访问另一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用,代理对象还可以完成它附加的操作。

    例子:就像房东、租客、中介的关系。中介(代理对象)为房东(真实对象)出租房子,租客(客户)通过中介(代理对象)来找房子租房子,中介完成了租房以后可以收取中介费(附加操作)。

    先看看静态代理模式,通过上面对代理模式的理解,可以了解到代理模式:即不直接通过new一个真实对象来调用方法,而是通过代理对象来调用一个方法,所以代理对象包含真实对象的引用。下面看一下代码

    接口:Subject包含一个方法

    1 package com.example.designpattern.proxy;
    2 
    3 public interface Subject {
    4 void request();
    5 }

    RealSubject类,实现了Subject接口,为了简单起见,方法简单的输出一句话:

    1 package com.example.designpattern.proxy;
    2 
    3 public class RealSubject implements Subject {
    4 //真是角色实现了
    5 public void request() {
    6 System.out.println("From real subject");
    7 }
    8 }

    代理类ProxySubject,也要实现Subject接口,实现Subject里面的方法,但是在这里里面是通过调用真实对象来实现的。

     1 package com.example.designpattern.proxy;
     2 
     3 public class ProxySubject implements Subject {
     4 
     5 private RealSubject realSubject; //代理角色内部引用了真实角色
     6 
     7 //代理角色实现目标动作
     8 public void request() {
     9 
    10 this.preRequest(); //在真实角色操作之前所附加的操作
    11 if (realSubject == null){
    12 realSubject = new RealSubject();
    13 }
    14 realSubject.request(); // 真实角色所完成的事情
    15 this.afterRequet(); //在真实角色操作之后附加的操作
    16 }
    17 //代理角色之前完成的动作
    18 private void preRequest(){
    19 System.out.println("pre request");
    20 }
    21 //代理角色之后完成的动作
    22 private void afterRequet(){
    23 System.out.println("after request");
    24 }
    25 }

    客户调用者

    package com.example.designpattern.proxy;
    
    public class Client {
    public static void main(String[] args) {
    ProxySubject proxy = new ProxySubject();
    //通过代理对象来调用方法
    proxy.request(); 
    }
    }

    静态代理:
    可以运行一下这些代码哦, 可以在Client类中看到,是通过代理ProxySubject的对象proxy来调用方法的,在代理类ProxySubject中,有一个真实对象的引用,在代理对象的中request()方法调用了真实对象的方法。这样的模式叫做代理模式。

    优点是:
    1. 代理模式能将代理对象与真实对象被调用的目标对象分离。
    2. 一定程度上降低了系统的耦合度,扩展性好。

    代理类中包含了对真实主题的引用,这样做也有缺点
    1. 真实对象与代理类一一对应,增加真实类也要增加代理类,这样做会快速的增加类的数量,使得系统变得复杂。
    2. 设计代理以前真实主题必须事先存在,不太灵活。


    采用动态代理可以解决以上问题,动态代理是相对于静态代理来说的。

    可能你也会说怎么样实现动态创建实例,以前我们创建实例不都是通过new 的方式来实现的吗?

    //如下
    Hello hi = new Hello();

    那么动态创建实例是由Java提供的功能,就不需要我们去new 对象,他已经定义好了静态方法Proxy.newProxyInstance(),只要传入参数调用就可以。Java文档里面有哦,如图:

     

    Java标准库提供了一种动态代理(DynamicProxy)的机制:可以在运行期动态创建某个interface的实例。
    参数解释:

    1 Proxy.newProxyInstance(
    2 ClassLoader loader, // 传入ClassLoader
    3 Class<?>[] interfaces, // 传入要调用的接口的方法数组
    4 InvocationHandler h); //传入InvocationHandler 的实例


    下面看一下动态代理例子代码:
    Subject 接口

    package design.dynamicproxy;
    
    public interface Subject {
    void request(String str);
    }

    RealSubject类 实现 Subject 接口

    1 package design.dynamicproxy;
    2 
    3 public class RealSubject implements Subject {
    4 @Override
    5 public void request(String str) {
    6 System.out.println("From Real Subject!" + " args:" + str );
    7 }
    8 }

    动态代理类DynamicSubject 实现了InvocationHandler,重写invoke()方法

     1 package design.dynamicproxy;
     2 
     3 import java.lang.reflect.InvocationHandler;
     4 import java.lang.reflect.Method;
     5 
     6 /**
     7 * 该代理类的内部属性时Object类型,实际使用时,使用该类的构造方法传递一个对象
     8 * 此外该类还实现了invoke() 方法,该方法中的method.invoke() 其实就是要调用被代理对象的要执行的方法
     9 * 方法参数是object,表示该方法从属于object对象,通过动态代理类,我们可以在执行真是对象的
    10 * 方法前后可以加入一些额外的方法
    11 */
    12 public class DynamicSubject implements InvocationHandler {
    13 
    14 //引入的类型是Object的,可以随便传入任何一个对象
    15 private Object object;
    16 
    17 public DynamicSubject(Object object){
    18 this.object = object;
    19 }
    20 
    21 @Override
    22 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    23 System.out.println("before calling:" + method);
    24 //等价于realSubject的request() 方法,如果这里不调用的话,不会调用Method 对象中的方法
    25 method.invoke(object, args);
    26 System.out.println("after calling:" + method);
    27 return null;
    28 }
    29 }

    Client类

     1 package design.dynamicproxy;
     2 
     3 import java.lang.reflect.InvocationHandler;
     4 import java.lang.reflect.Proxy;
     5 
     6 public class Client {
     7 
     8 public static void main(String[] args) {
     9 RealSubject realSubject = new RealSubject();
    10 InvocationHandler handler = new DynamicSubject(realSubject);
    11 Class<?> classType = handler.getClass();
    12 //下面的代码一次性生成代理
    13 // 动态生成了class com.sun.proxy.$Proxy0 的实例,
    14 Subject subject = (Subject) Proxy.newProxyInstance(classType.getClassLoader(), realSubject.getClass().getInterfaces(),handler);
    15 subject.request("eather");
    16 System.out.println(subject.getClass()); //输出class com.sun.proxy.$Proxy0,可以看到Proxy.newProxyInstance() 是系统自动生成的实例
    17 }
    18 }

    在Client中可以看到,我们这里调用方法的是 subject.request("eather"); 这个对象subject 不是通过new DynamicSubject()生成的,而是Java内部写好的方法在运行时动态生成对象;可能有人说

    InvocationHandler handler = new DynamicSubject(realSubject);


    这里不是通过new new DynamicSubject(realSubject); 生成了一个对象吗? 是的,但是它是InvocationHandler 类型的,主要是传递一个InvocationHandler类型参数给Proxy.newProxyInstance(); 即最后一个参数。通过Client类的最后一句输出可以看到它是 class com.sun.proxy.$Proxy0 ,这是Java运行时生成的。

    解决了静态代理的难题:1. 真实对象与代理类一一对应,增加真实类也要增加代理类,这样做会快速的增加类的数量,使得系统变得复杂。 为什么这么说呢, 因为代理类引用的类型是Object的,可以随便传入任何一个对象,当真实类增加时,代理类不用增加,new DynamicSubject(object); new的时候把要传入的对象传进去即可。

    下面是Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h); 这个方法的源码啦,可以看看,深入了解一下

     1 public static Object newProxyInstance(ClassLoader loader,
     2 Class<?>[] interfaces,
     3 InvocationHandler h)
     4 throws IllegalArgumentException
     5 {
     6 Objects.requireNonNull(h);
     7 
     8 final Class<?>[] intfs = interfaces.clone();
     9 final SecurityManager sm = System.getSecurityManager();
    10 if (sm != null) {
    11 checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    12 }
    13 
    14 /*
    15 * Look up or generate the designated proxy class.
    16 生成一个代理类对象
    17 */
    18 Class<?> cl = getProxyClass0(loader, intfs);
    19 
    20 /*
    21 * Invoke its constructor with the designated invocation handler.
    22 使用指定的调用处理程序调用其构造函数。就是使用InvocationHandler 实例调用【要调用方法的那个类】的构造方法
    23 */
    24 try {
    25 if (sm != null) {
    26 checkNewProxyPermission(Reflection.getCallerClass(), cl);
    27 }
    28 
    29 final Constructor<?> cons = cl.getConstructor(constructorParams);
    30 final InvocationHandler ih = h;
    31 if (!Modifier.isPublic(cl.getModifiers())) {
    32 AccessController.doPrivileged(new PrivilegedAction<Void>() {
    33 public Void run() {
    34 cons.setAccessible(true);
    35 return null;
    36 }
    37 });
    38 }
    39 return cons.newInstance(new Object[]{h});
    40 } catch (IllegalAccessException|InstantiationException e) {
    41 throw new InternalError(e.toString(), e);
    42 } catch (InvocationTargetException e) {
    43 Throwable t = e.getCause();
    44 if (t instanceof RuntimeException) {
    45 throw (RuntimeException) t;
    46 } else {
    47 throw new InternalError(t.toString(), t);
    48 }
    49 } catch (NoSuchMethodException e) {
    50 throw new InternalError(e.toString(), e);
    51 }
    52 }
  • 相关阅读:
    ios9没事,ios7tableviewcell报约束冲突问题
    在导航控制器的rootviewcontroller中push一个控制器 出现view重叠现象
    用mansard对cell的子控件设置约束,并且自动计算cell高度的问题,ios7警告
    textview第一次出现不可滚动文本,但是点击出现键盘,键盘落下,就可以滚动问题
    animateWithDuration 这样的方法,在第二次应用到某个view上动画时间无效问题
    UItoolBar 设置背景颜色
    implicitly declaring library function 'objc_msgSend'with type '(id,SEL,...)' 警告
    侧滑手势移除控制器,pop
    textfield光标消失和故意隐藏需求
    【Debug】IAR右键无法跳转到定义的解决方法
  • 原文地址:https://www.cnblogs.com/eathertan/p/12457862.html
Copyright © 2011-2022 走看看