文档章节

设计模式(二)代理模式

centrald
 centrald
发布于 2017/08/31 17:53
字数 1906
阅读 34
收藏 0

静态代理:

大家经常会使用代理服务器去翻墙,这就是一个常见的正向代理过程。先以翻墙的过程为例,给出一个java例子。

(1) AccessInterface.java

public interface AccessInterface {

    void accessGoogle();


}

(2)PersonalComputer.java

/**
* 被代理类
*/
public class PersonalComputer implements AccessInterface {
    @Override
    public void accessGoogle() {
        System.out.println("access google.com");
    }
}

(3)HongkongService.java

public class HongkongService implements AccessInterface {

    private AccessInterface accessInterface;

    public HongkongService(AccessInterface accessInterfase) {
        this.accessInterface = accessInterfase;
    }

    public void accessGoogle(){

        //控制真实行为
        accessInterface.accessGoogle();
    }

}

(4)GoogleService.java

public class GoogleService {

    public static void main(String[] args) {
        AccessInterface myComputer = new PersonalComputer();

        HongkongService hongkongService = new HongkongService(myComputer);
        //通过代理对象去访问google
        hongkongService.accessGoogle();
    }
}

结果:access google.com

由以上demo结果可以看出模拟出了通过香港的服务器去访问google,实际上自己的个人电脑也访问到了google.   
    通过代理的小例子可以总结出java中实现代理的特点:

  • 代理类与被代理类要实现同一个接口.
  • 代理类中需持有被代理对象.
  • 在代理类中调用被代理的行为。

        通过以上几点总结可以看出代理方式也是有局限性的。比如现在我的个人电脑需要去访问某个学校的内网,这个时候就需要能访问学校内网的服务器去做代理了(不使用vpn的方式)。按照以上总结出的实现代理的特点,这个时候就需要再去写一个schoolservice的代理类,并且在访问时需要将myCompute作为构造方法的参数传入等。假如需要多个代理类,通过java的代理方式去操作,是极为繁琐与重复的。
      而动态代理恰好可以解决这个问题,它不再是对单一的类型进行代理, 而是可以对任意的一个实现了接口的类的对象做代理。实际上实现aop的动态代理方式有两种,JDK提供的动态代理技术 和 CGLIB(动态字节码增强技术)。

JDK动态代理:

先继续看一个实现demo:

(1)Computer.java

public interface Computer {

    void accessSchool();

    void accessGoogle();
}

(2)StudentComputer.java

public class StudentComputer implements Computer {
    @Override
    public void accessSchool() {
        System.out.println("access school");
    }

    @Override
    public void accessGoogle() {

        System.out.println("access google");
    }
}

(3)AccessUtil.java

public class AccessUtil {

    public void method1() {
        System.out.println("===拦截代理1===");
    }

    public void method2() {
        System.out.println("===拦截代理2==");
    }
}

(4)MyInvocationHandler.java

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {

    //需要被代理的对象
    private Object target;

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

    //执行动态代理类的所有方法时,都会被替换成执行如下的invoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        AccessUtil accessUtil = new AccessUtil();

        accessUtil.method1();

        //以target作为主调执行method方法
        Object result =  method.invoke(target, args);

        accessUtil.method2();

        return result;
    }
}

(5)MyProxyFactory.java

public class MyProxyFactory {

    //为指定的traget生成动态代理对象
    public static Object getProxy(Object target) {

        MyInvocationHandler handler = new MyInvocationHandler();

        handler.setTarget(target);

        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                handler);

    }
}

(6)Client.java

public class Client {

    public static void main(String[] args) {

        Computer target = new StudentComputer();

        Computer proxy = (Computer) MyProxyFactory.getProxy(target);

        proxy.accessSchool();
        proxy.accessGoogle();

    }
}
/**
 * ===拦截代理1===
 access school
 ===拦截代理2==
 ===拦截代理1===
 access google
 ===拦截代理2==
 */

    由以上demo可以看出,通过动态代理的方式,不需要单独去实现一个代理类,通过动态代理的方式可以去代理任意符合一定规范的被代理类。当然实现机制是通过反射的方式去实现的。
总结动态代理最重要的要求是:被代理类必须实现接口。
简单分析一下实现动态代理方式的Proxy类。

Proxy类中有一个方法newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h);
loader: 要求,传递的是被代理类的类加载器ClassLoader
类加载器怎样获取:通过反射机制得到其Class对象。在Class类中提供一个方法  getClassLoader();
interfaces:
要求:得到被代理对象所实现的接口的所有Class对象。
所有Class对象获得方法:得到其Class对象,在Class类中提供一个方法  getInterfaces();
它返回的是Class[],就代表所实现接口的所有Class对象。
h:
它的类型是InvocationHandler,这是一个接口。
InvocationHandler 是代理实例的调用处理程序 实现的接口。
InvocationHandler接口中有一个方法invoke;
public Object invoke(Object proxy, Method method, Object[] args);
// 参数 proxy就是代理对象
// 参数method就是调用方法
// 参数args就是调用的方法的参数
// 返回值,就是真实行为执行后返回的结果,会传递给代理对象调用的方法.

以上就是JDK提供的动态代理技术。

cglib动态代理:

cglib动态代理用到了第三方类库,需要在项目中引入两个jar包:

asm-6.0.jar

cglib-3.2.5.jar

1.StudentComputer.java(同上)

2.AccessUtil.java(同上)

3.CglibProxy.java

/**
 * 拦截器
 */
public class CglibProxy implements MethodInterceptor {

    /**
     * 重写方法拦截,在方法前和方法后加入业务
     * @param o 目标对象
     * @param method 目标方法
     * @param objects 参数
     * @param methodProxy  代理对象
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        AccessUtil accessUtil = new AccessUtil();

        accessUtil.method1();

        //调用代理类实例上的proxy方法的父类方法(即实体类TargetObject中对应的方法)
        methodProxy.invokeSuper(o, objects);

        accessUtil.method2();

        return null;
    }
}

4.MyProxyFactory.java

public class MyProxyFactory {

    //为指定的traget生成动态代理对象
    public static StudentComputer getProxy(CglibProxy cglibProxy) {

        //Enhancer类是CGLib中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展
        Enhancer enhancer = new Enhancer();

        //首先将被代理类TargetObject设置成父类
        enhancer.setSuperclass(StudentComputer.class);
        //然后设置拦截器TargetInterceptor
        enhancer.setCallback(cglibProxy);

        //最后执行enhancer.create()动态生成一个代理类,并从Object强制转型成父类型TargetObject
        StudentComputer studentComputer = (StudentComputer) enhancer.create();

        return studentComputer;
    }
}

5.client.java

public class Client {

    public static void main(String[] args) {

        CglibProxy proxy = new CglibProxy();

        StudentComputer studentComputer = MyProxyFactory.getProxy(proxy);

        studentComputer.accessGoogle();
        studentComputer.accessSchool();
    }
}

/**

 ===拦截代理1===
 access google
 ===拦截代理2==
 ===拦截代理1===
 access school
 ===拦截代理2==

 */

优缺点:   

  1. 若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
    优点:因为有接口,所以使系统更加松耦合
    缺点:为每一个目标类创建接口
  2. 若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
    优点:因为代理类与目标类是继承关系,所以不需要有接口的存在。
    缺点:因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好。

     AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。

    jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。

    还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。

 

© 著作权归作者所有

共有 人打赏支持
centrald
粉丝 13
博文 112
码字总数 120823
作品 0
杭州
程序员
私信 提问
《PHP设计模式大全》系列分享专栏

《PHP设计模式大全》已整理成PDF文档,点击可直接下载至本地查阅 https://www.webfalse.com/read/201739.html 文章 php设计模式介绍之编程惯用法第1/3页 php设计模式介绍之值对象模式第1/5页...

kaixin_code
11/06
0
0
【设计模式笔记】(十六)- 代理模式

一、简述 代理模式(Proxy Pattern),为其他对象提供一个代理,并由代理对象控制原有对象的引用;也称为委托模式。 其实代理模式无论是在日常开发还是设计模式中,基本随处可见,中介者模式中...

MrTrying
06/24
0
0
编程中的那些套路——关于策略模式

该文章属于《编程中的那些经典套路——设计模式汇总》系列,并且以下内容基于语言PHP 今天讲讲策略模式,策略模式 和工厂模式十分相像(或者说在代码逻辑层面,他们是一样的)。 但策略模式与...

gzchen
08/27
0
0
(目录)设计模式(可复用面向对象软件的基础)

本系列“设计模式”博客使用Golang语言实现算法。所谓算法是指解决一个问题的步骤,个人觉得不在于语言。小弟只是最近学习Golang,所以顺带熟练一下语法知识,别无它意。 本系列博客主要介绍...

chapin
2015/01/13
0
0
javascript 设计模式之工厂(Factory)模式

工厂模式介绍 工厂模式是一个创建型的模式,主要就是创建对象。其中工厂模式又分为简单工厂模式和抽象工厂模式。简单工厂模式是通过工厂方法确定创建 对应类型的对象。抽象工厂模式是通过子类...

hlxiong
2014/04/14
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Confluence 6 教程:在 Confluence 中导航

当你对 Confluence 有所了解后,你会发现 Confluence 使用起来非常简单。这个教程主要是针对你使用的 Confluence 界面进行一些说明,同时向你展示在那里可以进行一些通用的任务和操作。 空间...

honeymose
今天
2
0
sed, awk 练习

1. sed打印某行到某行之间的内容 2. sed 转换大小写 将单词首字母转化大写 将所有小写转化大写 3. sed 在某一行最后面添加一个数字 4. 删除某行到最后一行 解析: {:a;N;$!ba;d} :a : 是...

Fc丶
今天
2
0
babel6升级到7,jest-babel报错:Requires Babel "^7.0.0-0", but was loaded with "6.26.3".

自从将前端环境更新到babel7,jest-babel之前是基于babel6的,执行时候就会报:Requires Babel "^7.0.0-0", but was loaded with "6.26.3". 很烦,因为连续帮好几台电脑修复这个问题,所以记...

曾建凯
今天
1
0
探索802.11ax

802.11ax承诺在真实条件下改善峰值性能和最差情况。 如何改善今天的Wi-Fi? 在决定如何改进当前版本以外的Wi-Fi时,802.11ac,IEEE和Wi-Fi联盟调查了Wi-Fi部署和行为,以确定更广泛使用的障碍...

linuxprobe16
今天
2
0
使用linux将64G的SDCARD格式化为FAT32

一、命令如下: sudo fdisk -lsudo mkfs.vfat /dev/sda -Isudo fdisk /dev/sda Welcome to fdisk (util-linux 2.29.2). Changes will remain in memory only, until you decide to wri......

mbzhong
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部