代理设计模式之静态代理与动态代理(超..)详解

  在学习Spring框架的时候,有一个重要的思想就是AOP,面向切面编程,利用AOP的思想结合Spring的一些API可以实现核心业务与辅助业务的分离,即可以在执行核心业务时,将一些辅助的业务加进来,而辅助业务(如日志,权限控制等)一般是一些公共业务,这样就实现了两者的分离,使得核心业务的代码更加纯粹,而且辅助业务也能得到复用,这一篇笔记是当时学习spring的时候写的,使用springAPI以及自定义类 实现AOP的一个例子 ,.AOP底层就是通过动态代理来实现的,最近专门学习了一下代理模式,反射机制,(动态代理也是基于反射机制来实现的)对代理的理解又立体了一点点,下面通过例子来讲解一下代理.

为什么需要代理:

  打一个最简单的比方,我现在想要学习,那么就必须得把书包拿过来,把书掏出来,准备好纸笔,然后开始学习,等学完了我还得收拾书,把书塞回书包里,还得整理一下书包,这是一个完整的学习的过程,但是我很懒,不想动弹,只想学习,那可能就得让妈妈帮我把书包拿过来,把书打开,我只管学习就好了,学完以后,妈妈再帮我把书整理好放回去.(我这是打个什么破比方...),在这里,妈妈就代表了一个代理对象,要学习的人是我,而我只管学习,这样效率才最高,至于其他的交给代理对象(妈妈)做就好了,画一个丑陋的的图表示一下:

  再联想我们最开始接触jdbc操作数据库的时候,业务层每一个方法,都需要1.打开数据库连接,2.执行我们想要的操作3.关闭数据库连接.这样就使得业务层代码不够纯粹,我的功能是查询用户数据,打开和关闭数据库连接关我毛事?我干嘛要去干这件事?这就是传统开发中存在的一个问题,现在通过代码演示一下:

/**
 * 简单业务层接口,只有一个save方法
 */
interface UserService{
    public void saveUser();
}////////////////////////////////////////////////////////
/**
 * 业务层实现类,实现save方法
 */
class UserServiceImpl implements UserService{

    @Override
    public void saveUser() {
        System.out.println("1:打开数据库连接");
        System.out.println("2:保存用户信息");
        System.out.println("3:关闭数据库连接");
    }

}

我们可以看到其实这个方法的实现是有问题的,核心业务与辅助业务写在了一个方法中,不但业务冗余了不说,像开关数据库连接这样的公共操作也大量的重复,这时候就出现了代理模式的思想,我们可以使用代理模式的思想改写一下上面的代码:

package com.wang.proxy;
/**
 * 简单业务层接口,只有一个save方法
 */
interface UserService{
    public void saveUser();
}

/**
 * 代理类
 */
class UserServiceProxy implements UserService{

    private UserService userService;

    public UserServiceProxy(UserService userService) {
        super();
        this.userService = userService;
    }

    public void open(){
        System.out.println("1:打开数据库连接");
    }
    public void close(){
        System.out.println("3:关闭数据库连接");
    }
    @Override
    public void saveUser() {
        this.open();
        userService.saveUser();
        this.close();
    }

}

/**
 * 业务层实现类,实现save方法
 */
class UserServiceImpl implements UserService{

    @Override
    public void saveUser() {
        System.out.println("2:保存用户信息");
    }

}
/**
 * 测试类
 */
public class TestProxy {

    public static void main(String[] args) {
        UserService userService =new UserServiceProxy(new UserServiceImpl());
        userService.saveUser();
    }
}

  通过测试代码打印结果,和上面没有使用代理的代码是完全一样,但是通过修改可以清晰地看到,业务层代码变得很纯很纯的,只剩下最核心的保存用户信息的代码.通过代理模式,我们可以抽取出核心业务与辅助业务,但是问题随之而来了,我这里编写的UserServiceProxy是挺不错,可是它只能服务与UserService这个接口的对象啊,如果我有一千个业务,那岂不是要编写一千个代理类,毕竟一千个人心中就有一千个哈姆雷特啊,其实这种代理模式就是静态代理,它的缺点很明显,静态代理只能服务于一种类型的对象,不利于业务的扩展,那么我们就想了,能不能设计一个代理类可以服务于所有的业务对象呢?于是,这时候,动态代理就闪亮登场了.

  如果要实现动态代理,那么你要编写的那个代理类就需要实现一个InvocationHandle接口.这个接口所在位置是java.lang.reflect.InvocationHandler.看到reflect我们就能知道,动态代理肯定是通过反射来实现的了,这个接口中有一个方法:

    Object  invoke(Object proxy, Method method, Object[] args)    :在代理实例上处理方法调用并返回结果。

    invoke方法其实是反射里边的一个方法,在这个方法中有三个参数:

    Ojbect proxy:表示需要代理的对象

    Method method:表示要操作的方法

    Object[] args:method方法所需要传入的参数(可能没有为,null.也可能有多个)

  如果要想让代理设计真正可用,我们还必须有一个代理类对象产生,这有用到了另一个类:java.lang.reflect.Proxy.我的中文jdk文档对他的描述是:

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

  在这个类下面,我们找到了这样一个方法:

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

  该方法返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序.方法中有三个参数:

   参数:

     loader - 定义代理类的类加载器

     interfaces - 代理类要实现的接口列表

     h - 指派方法调用的调用处理程序

   返回:

    一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口

  ok,有了这些知识,我们就可以来编写一个万能的动态代理类了.

class ServiceProxy implements InvocationHandler {

    private Object target=null;//保存真实业务对象
    /**
     * 返回动态代理类的对象,这样用户才可以利用代理类对象去操作真实对象
     * @param obj  包含有真实业务实现的对象
     * @return   返回代理对象
     */
    public Object getProxy(Object obj) {
        this.target=obj;//保存真实业务对象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
                .getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result=method.invoke(target, args);//通过反射调用真实业务对象的业务方法,并且返回
        return result;
    }

}

现在来测试一下好不好使:

/**
 * 测试类
 */
public class TestProxy {

    public static void main(String[] args) {
            UserService service=(UserService) new ServiceProxy().getProxy(new UserServiceImpl());
            service.saveUser();
    }
}

//打印结果:
//        2.保存用户信息

  看!!!!完美,你说什么?为什么没有打开数据库连接,和关闭数据库连接呢?  简单,我们直接在ServiceProxy中加入两个方法,open()和close(),一个放到method.invoke()上面,一个放到下面,就可以了,注意,我们现在编写的是一个万能的动态代理类了,没有和任何的业务层接口关联,所以接下来你就可以为所欲为了.

  下面来总结一下:

  动态代理和静态代理相比较,最大的好处就是接口中声明的所有的方法都被转移到一个集中的方法中去处理,就是invocke()方法.这样在接口中声明的方法比较多的情况下我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

动态代理只能代理接口,代理类都需要实现InvocationHandler类,实现invoke方法。该invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法的返回值是被代理接口的一个实现类。

  那么有没有可能我们可以不依赖接口呢?这时候就需要CGLIB实现动态代理了,这个jar包可以让我们摆脱接口的烦恼,感兴趣的自己去查一下吧,反正我也没用,不会...

时间: 2024-09-30 14:27:39

代理设计模式之静态代理与动态代理(超..)详解的相关文章

(笔记)Linux下的静态库和动态库使用详解

库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行.库分静态库和动态库两种. 一.静态库和动态库的区别 1. 静态函数库 这类库的名字一般是libxxx.a:利用静态函数库编译成的文件比较大,因为整个 函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了.当 然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译. 2. 动态函数库 这类库的名字一般是libxxx.so;相对于

继承?静态代理?写一个自己的动态代理吧

[ 需求分析 ] 在我们实际开发中常常会遇到这样的问题:记录一个类的方法运行时间,以分析性能.一般我们的做法是先在类的开始记录一个开始时间,然后在类的结束记录一个结束时间,二者相减就可以获取我们想要的结果.但是很多时候这些类已经打了jar包,我们无法直接修改源码,这个时候我们应该怎么办呢? 下文使用Tank的移动需要统计时间.记录日志来模拟需求场景,假定Moveable.Tank类无法修改. interface:Moveable public interface Moveable { publi

设计模式(二)学习----动态代理

动态代理:动态代理是指在实现阶段不需要关心代理谁,而在运行阶段才指定代理哪一个对象.Spring AOP采用的核心思想就是动态代理设计模式.  下面看动态代理的UML类图: 下面思考问题:invocationHandler的invoke方法是由谁来调用的,代理对象是怎么生成的? 动态代理类: package com.lp.ecjtu.DynamicProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect

系统架构设计——设计模式之代理模式(二)CGLIB动态代理实现

像上一篇所说的代理模式其实是静态代理,在实际开发中其实应用不大,因为他需要事先知道被代理对象是谁,而且被代理对象和代理对象实现了公共的接口.实际情况往往并不能满足这些条件,我们往往在写代理模式的时候并不知道到时候被代理的对象是谁.解决办法就是--动态代理.以下我们将使用CGLIB实现动态代理. 一.动态代理概述 程序在运行期而不是编译器,生成被代理对象的代理对象,并且被代理对象并不需要和代理对象实现共同的接口.基于此,我们可以利用代理对象,提供一种以控制对被代理对象的访问. 1.1 动态代理的原

关于IPB帧与恒定比特率、动态比特率的详解

之所以写这篇文章是因为有朋友对IPB帧的设置比较感兴趣,回复中说得比较简单,因此在这里详细的写一下,虽然说一般情况下我们很少去设置这个IPB帧,不过,如果真的学好了,并且清楚的了解了这个IPB帧的概念和设置后,我们能够在渲染成品的时候更加好的掌控视频的质量与体积. 在说怎么设置之前,我们先了解一下IPB帧的概念,网络上其实蛮多的,我这里稍微做一点自己的改变和理解,这样更加方便我们理解. 首先说到IPB帧,就不得不说一下帧内压缩和帧间压缩:关于压缩,我想不需要做更多的解释,简单点,就是为了在保证我

设计模式 - 迭代器模式(iterator pattern) Java 迭代器(Iterator) 详解

迭代器模式(iterator pattern) Java 迭代器(Iterator) 详解 本文地址: http://blog.csdn.net/caroline_wendy 参考迭代器模式(iterator pattern): http://blog.csdn.net/caroline_wendy/article/details/35254643 Java的标准库(util)中包含迭代器接口(iterator interface), import java.util.Iterator; 继承(

设计模式 - 适配器模式(adapter pattern) 枚举器和迭代器 详解

适配器模式(adapter pattern) 枚举器和迭代器 详解 本文地址: http://blog.csdn.net/caroline_wendy 参考适配器模式(adapter pattern): http://blog.csdn.net/caroline_wendy/article/category/2281679 Java早期版本的枚举器(Enumeration)和现在的迭代器(Iterator) 可以使用适配器(adapter)进行转换. 适配器(adapter)代码: /** *

设计模式 - 模板方法模式(template method pattern) 排序(sort) 详解

模板方法模式(template method pattern) 排序(sort) 详解 本文地址: http://blog.csdn.net/caroline_wendy 参考模板方法模式(template method pattern): http://blog.csdn.net/caroline_wendy/article/details/32159455 模板方法模式的一个主要的应用是排序(sort)算法. 对象的排列方式并不是完全相同, 所以需要排序(sort)算法compareTo()

设计模式 - 命令模式(command pattern) 宏命令(macro command) 详解

命令模式(command pattern) 宏命令(macro command) 详解 本文地址: http://blog.csdn.net/caroline_wendy 参考: 命名模式(撤销): http://blog.csdn.net/caroline_wendy/article/details/31419101 命令模式可以执行宏命令(macro command), 即多个命令的组合操作. 具体方法:  1. 其余代码与命令(撤销)一致 2. 添加宏命令(macro command),

ant-design-pro 动态菜单-路由详解

ant-design-pro 动态菜单-路由详解最近利用ant-design-pro开发项目要实现之前的项目嵌入到新项目里来,并且根据和后台的接口返回的数据显示侧边栏菜单.既然是是利用别人的架构那当然是从文档中找实现的方法,终于不负苦心人在https://pro.ant.design/docs/router-and-nav-cn文档那里找到初步的解决方法 进入src/layouts/Basilayout.js在官网中直接复制该代码,将原文件替换.现在正式进入正题. 1,在src/layouts/