文档章节

设计模式(二)代理模式

centrald
 centrald
发布于 2017/08/31 17:53
字数 1906
阅读 33
收藏 0
点赞 0
评论 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
粉丝 11
博文 68
码字总数 114870
作品 0
杭州
程序员
JavaScript设计模式之观察者模式

前言 准备研究一下MVVM的一些东西,由于MVVM运用了观察者模式的思想,因此翻开了《JavaScript设计模式与开发实践》一书,将观察者模式学习了一遍,顺便有对一些常用的设计模式进行一些了解,...

Srtian ⋅ 05/22 ⋅ 0

代理模式(Proxy Pattern):动态代理 - 最易懂的设计模式解析

前言 今天我来全面总结开发中最常用的设计模式 - 代理模式中的动态代理模式 其他设计模式介绍 1分钟全面了解“设计模式” 单例模式(Singleton) - 最易懂的设计模式解析 简单工厂模式(Sim...

Carson_Ho ⋅ 04/09 ⋅ 0

系统架构技能之设计模式-单件模式

一、开篇 其实我本来不是打算把系统架构中的一些设计模式单独抽出来讲解的,因为很多的好朋友也比较关注这方面的内容,所以我想通过我理解及平时项目中应用到的一 些常见的设计模式,拿出来给...

wbf961127 ⋅ 2017/11/12 ⋅ 0

JavaScript 中常见设计模式整理

开发中,我们或多或少地接触了设计模式,但是很多时候不知道自己使用了哪种设计模式或者说该使用何种设计模式。本文意在梳理常见设计模式的特点,从而对它们有比较清晰的认知。 JavaScript 中...

牧云云 ⋅ 05/18 ⋅ 0

设计模式梳理(一)

设计模式梳理(一) 总体来说设计模式分为三大类: @案例源码地址:https://gitlab.com/lxqxsyu/DisgnPattern 创建型模式 简单工厂模式 工厂类是整个模式的关键。它包含必要的判断逻辑,能够...

lxq_xsyu ⋅ 2017/11/02 ⋅ 0

C#设计模式(2)——简单工厂模式

一、引言   这个系列也是自己对设计模式的一些学习笔记,希望对一些初学设计模式的人有所帮助的,在上一个专题中介绍了单例模式,在这个专题中继续为大家介绍一个比较容易理解的模式——简单工...

技术小胖子 ⋅ 2017/11/08 ⋅ 0

Java经典设计模式-结构型模式-适配器模式(Adapter)

适配器模式 适配器模式主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。 适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的...

Idea ⋅ 01/20 ⋅ 0

设计模式.策略模式

策略模式跟抽象工厂非常相似,基本逻辑是根据需要实例化出需要用的类。不同的是策略模式需要调用者非常清晰的知道有哪些策略,各个策略的调用规则,而抽象工厂的话,需要知道有哪些类,找到调...

技术小胖子 ⋅ 2017/11/08 ⋅ 0

[设计模式]简单工厂模式

简介 简单工厂模式 (Simple Factory) 又叫静态工厂方法(Static Factory Method)模式。 简单工厂模式通常是定义一个工厂类,这个类可以根据不同变量返回不同类的产品实例。 简单工厂模式是一...

静默虚空 ⋅ 2015/06/03 ⋅ 0

一天一篇设计模式之--工厂模式与抽象工厂模式(创建型模式)

工厂模式与抽象工厂模式主要解决接口选择问题。 一、工厂模式 当明确计划需要在不同条件下创建不同实例时,可以使用工厂模式。 实例:发送邮件或者短信 1、创建发送信息的接口 2、创建两个实...

Timor_张先生 ⋅ 04/10 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

懒惰根本就不存在

简评:芝加哥大学心理学教授,懒惰根本就不存在。(本文表面讲行为心理学实则讲教育) 金句:以好奇而不是判断来回应一个人的无效行为,是非常有帮助的。 本文「我」代表原作者 E Price。 自...

极光推送 ⋅ 26分钟前 ⋅ 0

Excel提取单元格中最后一个“.”后面的数据

java.lang.String ----- String =TRIM((MID(SUBSTITUTE(B2,".",REPT(" ",99)),(LEN(B2)-LEN(SUBSTITUTE(B2,".","")))*99,99)))...

klog ⋅ 28分钟前 ⋅ 0

mac远程桌面

下载安装remote-desktop-mac Mac beta 客户端 mac通过远程桌面访问windows服务器。

亚林瓜子 ⋅ 33分钟前 ⋅ 0

firrtl

动手---sbt(2)之后,再回头看 chisel第一个实验,根据 https://github.com/freechipsproject/firrtl 发现firrtl没有执行sbt assembly命令,重新执行这个命令,结果成功。如下图: joe@joe-As...

whoisliang ⋅ 37分钟前 ⋅ 0

NIO

一、通道(Channel):用于源节点与目标节点的连接。在 Java NIO 中负责缓冲区中数据的传输。Channel 本身不存储数据,因此需要配合缓冲区进行传输。 二、通道的主要实现类 java.nio.channel...

stars永恒 ⋅ 37分钟前 ⋅ 0

Android悬浮窗的实现

0. 前言   现在很多应用都使用到悬浮窗,例如微信在视频的时候,点击Home键,视频小窗口仍然会在屏幕上显示。这个功能在很多情况下都非常有用。那么今天我们就来实现一下Android悬浮窗,以...

猴亮屏 ⋅ 37分钟前 ⋅ 0

日志采集中的关键技术分析

概述 日志从最初面向人类演变到现在的面向机器发生了巨大的变化。最初的日志主要的消费者是软件工程师,他们通过读取日志来排查问题,如今,大量机器日夜处理日志数据以生成可读性的报告以此...

tqyin ⋅ 39分钟前 ⋅ 0

使用Navicat将数据导出为text文本 然后再导入

将数据导出为text文本效率很高 1. 准备工作 1.1 准备表结构 1.2 目标库 执行生成表结构sql 2.将表数据导出为text文本 生成的text文本 3. 目标库 导入text 4.效果...

Lucky_Me ⋅ 44分钟前 ⋅ 0

IntelliJ IDEA 乱码解决方案 (项目代码、控制台等)

文章介绍了idea下,项目乱码、控制台乱码及运行tomcat控制台乱码的解决方案,文章链接:https://www.cnblogs.com/vhua/p/idea_1.html

Funcy1122 ⋅ 48分钟前 ⋅ 0

IDEA使用sonarLint

一、IDEA如何安装SonarLint插件 1.打开 Idea 2.点击【File】 3.点击【Settings】 4.点击【Plugins】 5.在搜索栏中输入“sonarlint”关键字 6.点击【Install】进行安装 7.重启Idea 二、IDEA如...

开源中国成都区源花 ⋅ 52分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部