GoF之代理模式

在java程序中的代理模式的作用:
第一个作用: 当一个对象需要受到保护的时候,可以考虑使用代理对象去完成某个行为。
第二个作用: 需要给某个对象的功能进行功能增强的时候,可以考虑找一个代理进行增强。
第三个作用: A对象无法和B对象直接交互时,也可以使用代理模式来解决。

代理模式的类图:

拓展: UML中常见的几种关联关系(依赖、泛化、实现、关联、组合、聚合)

什么是静态代理?

一:创建一个代理对象,这个代理对象要实现目标对象所实现的接口。也就是说,目标对象实现了哪些接口,这个目标对象的代理对象就要实现这些接口。

二:将目标对象传递给代理对象(如下代码)

    // 目标对象
    private OrderService orderService;

    // 通过构造方法将目标对象传递给代理对象
    public OrderServiceProxy(OrderService orderService) {
        this.orderService = orderService;
    }

如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。怎么解决这个问题?动态代理可以解决。因为在动态代理中可以在内存中动态的为我们生成代理类的字节码。代理类不需要我们写了。类爆炸解决了,而且代码只需要写一次,代码也会得到复用。

什么是动态代理?

在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。

在内存当中动态生成类的技术常见的包括:

  • JDK动态代理技术:只能代理接口。

  • CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)

  • Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。

(spring框架主要用的是前两种代理技术)

JDK动态代理:

OrderService接口:

public interface OrderService { //代理对象和目标对象的公共接口

    //生成订单
    void generate();

    //修改订单信息
    void modify();

    //查看订单详情
    void detail();

}

OrderService接口的实现类:

public class OrderServiceImpl implements OrderService{  //目标对象
    @Override
    public void generate() {    //目标方法
        try {
            Thread.sleep(1234);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成");
    }

    @Override
    public void detail() {  //目标方法
        try {
            Thread.sleep(2541);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单信息如下:******");
    }

    @Override
    public void modify() {  //目标方法
        try {
            Thread.sleep(1010);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改");
    }
}

我们在静态代理的时候,除了以上一个接口和一个实现类之外,是不是要写一个代理类UserServiceProxy呀!在动态代理中UserServiceProxy代理类是可以动态生成的。这个类不需要写。我们直接写客户端程序即可:

public class Client {   //客户端程序
    public static void main(String[] args) {

        //创建目标对象
        OrderService target = new OrderServiceImpl();

        //创建代理对象
        /*
        1. newProxyInstance 翻译为:新建代理对象
               也就是说,通过调用这个方法可以创建代理对象。
               本质上,这个Proxy.newProxyInstance()方法的执行,做了两件事:
                    第一件事:在内存中动态的生成了一个代理类的字节码class。
                    第二件事: new对象了。通过内存中生成的代理类这个代码,实例化了代理对象。
        2.关于newProxyInstance()方法的三个重要的参数,每一个什么含义,有什么用?
                第一个参数:ClassLoader loader
                    类加载器。这个类加载器有什么用呢?
                        在内存当中生成的字节码也是class文件,要执行也得先加载到内存当中。加载类就需要类加载器。
                        所以这里需要指定类加载器。并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个。

                第二个参数: Class<?>[] interfaces
                    代理类和目标类要实现同一个接口或同一些接口。
                    在内存中生成代理类的时候,这个代理类是需要你告诉它实现哪些接口的。

               第三个参数: InvocationHandler h
                    InvocationHandler被翻译为:调用处理器。是一个接口。
                    在调用处理器接口中编写的就是:增强代码。
                    因为具体要增强什么代码,JDK动态代理技术它是猜不到的。没有那么神。
                    既然是接口,就要写接口的实现类。

                    可能会有疑问?
                        自己还要动手写调用处理器接口的实现类,这不会类爆炸吗?不会。
                        因为这种调用处理器写一次就好。

                注意:代理对象和目标对象实现的接口一样,所以可以向下转型。
        */

         OrderService proxyObj = (OrderService)Proxy.newProxyInstance(
                                                    target.getClass().getClassLoader(),
                                                    target.getClass().getInterfaces(),
                                                    new TimerInvocationHandler(target)
                                                     );
        //代理对象调用代理方法
        proxyObj.generate();
        /*proxyObj.modify();
        proxyObj.detail();*/

    }
}
/**
 *专门负责计时的一个调用处理器对象。
 * 在这个调用处理器当中编写计时相关的增强代码。
 * 这个调用处理器只需要写一个就行了。
 */
public class TimerInvocationHandler implements InvocationHandler {

    private Object target;

    public TimerInvocationHandler(Object target) {
        this.target = target;
    }

    /*
      1.为什么强行要求你必须实现InvocationHandler接口?
            因为一个类实现接口就必须实现接口中的方法。
            以下这个方法必须是invoke(),因为JDK在底层调用invoke()方法的程序已经提前写好了。
            注意: invoke方法不是我们程序员负责调用的,是JDK负责调用的。
      2. invoke方法什么时候被调用呢?
            当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法被调用。
      3. invoke方法的三个参数:
            invoke方法是JDK负责调用的,所以JDK调用这个方法的时候会自动给我们传过来这三个参数。
            我们可以在invoke方法的大括号中直接使用。
            第一个参数:Object proxy 代理对象的引用。这个参数使用较少。
            第二个参数:Method method目标对象上的目标方法。(要执行的目标方法就是它。)
            第三个参数:Object[ ] args目标方法上的实参。
    */

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        long begin = System.currentTimeMillis();

        //System.out.println("增强1");

        //调用目标对象的目标方法
        Object retValue = method.invoke(target, args);

        //System.out.println("增强2");
        long over = System.currentTimeMillis();

        System.out.println("程序所用时间:"+(over-begin));

        //注意这个invoke方法的返回值,如果代理对象调用代理方法之后,需要返回结果的话,invoke方法必须将目标对象的目标方法执行结果继续返回。
        return retValue;
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值