文档章节

java框架学习日志-7(静态代理和JDK代理)

 白话
发布于 2018/12/17 00:39
字数 2642
阅读 43
收藏 7

静态代理

我们平时去餐厅吃饭,不是直接告诉厨师做什么菜的,而是先告诉服务员点什么菜,然后由服务员传到给厨师,相当于服务员是厨师的代理,我们通过代理让厨师炒菜,这就是代理模式。代理模式需要三个东西:
真实对象——厨师(chef),用户真正需要去用到的对象。
调用者——客人(Client),需要用到真实对象的对象。
代理对象——服务员(waiter),代理真实对象与调用者沟通的对象。
厨师需要有个炒菜的方法,服务员负责代理厨师,所以也需要有一个炒菜方法,为了保证两个对象的炒菜方法一致,需要一个接口。

public interface Cook {
    public void cook(String name);//传入菜品名字
}

然后chef实现这个接口

public class Chef implements Cook{
    @Override
    public void cook(String name) {
        System.out.println("正在炒"+name);
    }
}

waiter也需要实现这个接口

public class Waiter implements Cook{
    Chef chef;

    public Waiter(Chef chef) { //通过构造函数与获取chef的实例对象
        this.chef = chef;
    }

    @Override
    public void cook(String name) { //自己不实现做菜,而是调用chef的方法
        chef.cook("鱼香肉丝");
    }
}

设置好后,客人就来点餐了

public class Client {

    public static void main(String[] args) {

        Chef chef=new Chef();
        Waiter waiter=new Waiter(chef);
        waiter.cook("鱼香肉丝");
    }
}


这里client没有直接调用chef的cook方法,而是通过waiter调用的cook方法。这样waiter就起到了代理的作用。代理的优点是可以让真实对象处理的业务更加纯粹,不再去关注一些公共的事情,公共的业务由代理来完成。
比如这个点餐的例子,可以让厨师专心做饭,不用去管点餐的事情,如果还要增加收钱功能,就可以让服务员去完成。

public class Waiter implements Cook{
    Chef chef;

    public Waiter(Chef chef) { //通过构造函数与获取chef的实例对象
        this.chef = chef;
    }

    @Override
    public void cook(String name) { //自己不实现做菜,而是调用chef的方法
        chef.cook("鱼香肉丝");
    }
    public void pay(){
        System.out.println("结账");
    }
}

动态代理

** 动态代理是spring的难点和重点。务必好好掌握。**

但是静态代理的缺点也很明显,多了代理类,工作量变大,开发效率降低。为了弥补这些缺点,就可以使用动态代理。动态代理又有几种实现
基于接口动态代理——jdk动态代理
基于类动态代理——cglib动态代理
还有用javasist来生成动态代理

先说jdk动态代理,动态代理只需要一个代理类就可以代理所有真实对象。静态代理是这么怎么做的呢?上面例子中,静态代理类(Waiter)代理了真实对象(Chef)。为了保证真实对象的方法(cook)与代理类的方法(cook)一致,所以需要都实现同一个接口(Cook),那如果还需要代理其他类呢?比如代理老板收钱,又需要实现pay接口,代理会计算账,又需要实现reckon接口。就会很麻烦。

动态代理只需要代理类实现一个InvocationHandler接口。就可以在代理类里面动态设置代理对象。API里InvocationHandLer是这样描述的



InvocationHandler是由代理实例的调用处理程序实现的接口 。 在这个例子中,代理实例就是指服务员(waiter),调用处理程序指调用厨师(chef)的做菜(cook),就是chef.cook("鱼香肉丝")。这里还提到了invoke方法



处理代理实例上的方法调用并返回结果。 当在与之关联的代理实例上调用方法时,将在调用处理程序中调用此方法。结合例子,当你想点餐(处理代理实例上的方法)时,就可以调用这个方法。然后结果点餐结果也会告诉你(并返回结果),因为这个方法可以代理很多真实对象,所以返回的Object。
还有一个需要了解的Proxy类,API中的描述



注意这里断句,Proxy提供了创建动态代理类和实例,的静态方法。这里的静态方法就是



第一个参数——类加载器,java去执行某一个类的时候,需要将这个.clss文件加载到java虚拟机里面去,这个加载的过程就需要类加载器去加载。 第二个参数——要实现接口的列表,接口也有class。所以是class<?>[]。
第三个参数——这个InvocationHandler就和之前的联系上了。
大概了解后可以配合代码理解了。我们在上面点餐的例子上更改。动态代理指动态生成的代理类,因此接口(Cook)依然存在,真实对象(Chef)也存在。然后是代理类(waiter),代理类需要去实现InvocationHandler接口,因为可以实现动态代理了,稍微改个名字Waiter2。

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

public class Waiter2 implements InvocationHandler{
    private Cook cook;//真实对象,因为chef实现了Cook,所以创建cook就可以了

    public void setCook(Cook cook) {
        this.cook = cook;
    }


    /**
     *代理方法逻辑
     * @param proxy 代理对象(chef)
     * @param method 当前调度方法,代理对象的调用处理程序方法的对象(cook())
     * @param args 当前方法的参数 (菜品名)
     * @return 返回代理结果
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object obj=method.invoke(cook,args);//第一个参数是真实对象
        return obj;
    }

    /**
     * 生成代理类
     * @return 返回代理类
     */
    public Object getProxy(){
        return  Proxy.newProxyInstance(this.getClass().getClassLoader(),cook.getClass().getInterfaces(),this);
    }
}

详细解释

private Cook cook;//真实对象,因为chef实现了Cook,所以创建cook就可以了

    public void setCook(Cook cook) {
        this.cook = cook;
    }

代理对象依然因为需要代理真实对象,所以还是需要先创建真实对象,真实对象厨师Chef继承了接口Cook的烹饪方法cook,所以创建接口Cook就可以了。然后通过set方法来设置真实对象。

/**
     *代理方法逻辑
     * @param proxy 代理对象(chef)
     * @param method 当前调度方法,代理对象的调用处理程序方法的对象(cook())
     * @param args 当前方法的参数 (菜品名)
     * @return 返回代理结果
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object obj=method.invoke(cook,args);//第一个参数是真实对象
        return obj;
    }

之前提到过,invoke是处理代理实例上的方法调用并返回结果,可以理解成让代理对象与真实对象建立逻辑关系。
这里的method.invoke()是通过反射去调用真实对象,第一个参数cook就是真实对象,第二个参数就是传的参数值。 然后返回代理结果就可以了。
之前还提到过Proxy有一个静态方法可以生成代理类。

/**
     * 生成代理类
     * @return 返回代理类
     */
    public Object getProxy(){
        return  Proxy.newProxyInstance(this.getClass().getClassLoader(),cook.getClass().getInterfaces(),this);
    }

newProxyInstance的第一个参数是类加载器,直接传入当前类的类加载器。
第二参数是接口,这里传入cook的接口。 第三个参数是InvocationHandler,本身这个类就实现了InvocationHandler接口,所以传入当前类。
然后是客户端

public class Client {
    public static void main(String[] args) {
        Chef chef=new Chef();  //获取真实对象的实例
        Waiter2 waiter2=new Waiter2();//获取代理的实例
        waiter2.setCook(chef); //设置代理去代理哪个真实实例
        Cook proxy=(Cook) waiter2.getProxy();//创建代理,getProxy返回的是object,所以转换一下。
        proxy.cook("宫保鸡丁");//通过代理去控制真实对象。
    }
}

Cook proxy=(Cook) waiter2.getProxy();这一行是用来创建代理的,proxy就相当于之前的服务员。创建为Cook类型是为了调用cook方法。之前的静态代理中,用的是Waiter waiter去创建代理,因为Waiter也实现了Cook接口。所以可以用Waiter类型。
运行结果

如果要添加公共方法到代理对象中

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("来了,老弟");
        Object obj=method.invoke(cook,args);//第一个参数是真实对象
        return obj;
    }


之前说静态代理有个坏处,有很多类,动态代理可以解决这个问题。现在好像还看不出来。我们稍微改一下,让动态代理类可以代理所有类。
这个例子中代理的是厨师,也可以加入收钱功能,或者打扫卫生功能。真实对象怎么变,我们的目的不变,代理真实对象,那么就可以把Waiter中的真实对象Cook,换成Object。Waiter2代码如下

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

public class Waiter2 implements InvocationHandler{
    private Object target;//真实对象

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

    /**
     *代理方法逻辑
     * @param proxy 代理对象(chef)
     * @param method 当前调度方法,代理对象的调用处理程序方法的对象(cook())
     * @param args 当前方法的参数 (菜品名)
     * @return 返回代理结果
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("服务员,麻烦你");//公共方法
        Object obj=method.invoke(target,args);//第一个参数是真实对象
        return obj;
    }

    /**
     * 生成代理类
     * @return 返回代理类
     */
    public Object getProxy(){
        return  Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
}

新增两个接口,打扫和付钱,和它们的接口实现类

public interface Clean {
    public  void clean();
}

public class CleanImpl implements Clean{
    public void clean(){
        System.out.println("打扫");
    }
}

public interface Pay {
    public void paymoney(int i);
    public void free();
}


public class PayImpl implements Pay{
    public void paymoney(int i){
        System.out.println("付钱诶!一共"+i);
    }
    public void free(){
        System.out.println("能不能免单");
    }
}

然后再客户端测试一下

public class Client {
    public static void main(String[] args) {
        Chef chef=new Chef();  //获取真实对象的实例
        Pay pay=new PayImpl();
        Clean clean=new CleanImpl();
        Waiter2 waiter2=new Waiter2();//获取代理的实例

        waiter2.setTarget(chef); //设置代理去代理哪个真实实例
        Cook cookproxy=(Cook) waiter2.getProxy();//创建代理,getProxy返回的是object,所以转换一下。
        cookproxy.cook("宫保鸡丁");//通过代理去控制真实对象。

        waiter2.setTarget(pay);
        Pay payproxy=(Pay) waiter2.getProxy();
        payproxy.paymoney(50);
        payproxy.free();

        waiter2.setTarget(clean);
        Clean cleanproxy=(Clean) waiter2.getProxy();
        cleanproxy.clean();
    }
}


这样这个代理类就可以代理各种各样的类。注意被代理的类必须实现接口,因为在newProxyInstance方法里面第二个参数就是传入真实对象的接口。

© 著作权归作者所有

共有 人打赏支持
粉丝 7
博文 19
码字总数 21678
作品 0
梁平
私信 提问
java框架学习日志-8(AOP简介)

小王被委托开发一款游戏,程序分为启动页面,登陆页面,战斗页面等。小王就采用了面向对象编程思想(OOP),把整个程序分解成下图 这种就是传统的自上而下的编程,或者说纵向的编程,负责启动...

白话
2018/12/19
0
0
Java面试基础篇——第十五篇:代理模式

什么是代理? 通过代理控制对象的访问,可以详细访问某个对象的方法,在这个方法调用处理,或调用后处理。 代理应用场景 安全代理 可以屏蔽真实角色远程代理 远程调用代理类RMI延迟加载 先加载...

developlee的潇洒人生
2018/08/02
0
0
3.2 Spring AOP的设计与实现

JVM的动态代理特性 在Spring AOP实现中,使用的核心技术是动态代理,这实际上是JDK的一个特性(JDK1.3以上的版本有这个特性)。通过JDK的动态代理特性,可以为任意Java对象创建代理对象,对于...

edwardGe
2018/05/30
0
0
JDK动态代理与Cglib动态代理(转载)

spring容器通过动态代理再结合java反射思想可以使得方法调用更加简洁 一、动态代理概述: 与静态代理对照(关于静态代理的介绍 可以阅读上一篇:JAVA设计模式之 代理模式【Proxy Pattern】(...

思悟修
2015/08/14
0
0
以此之长,补彼之短----AOP(代理模式)

上文中提到代理分为静态代理和动态代理,采用代理是为了通过不修改源代码的情况下给程序动态统一添加功能,利用代理技术可以将业务逻辑中一些非业务逻辑的代码分离出来,把他们独立到业务逻辑...

Kerry_Han
2013/08/30
0
0

没有更多内容

加载失败,请刷新页面

加载更多

嵌入式应用选择合适的微控制器

准备所需硬件接口列表 使用微控制器的基本硬件框图,准备一份微控制器需要支持的所有外设接口的列表。微控制器中有两种常见的接口类型需要列出。第一种是通信接口,这些是外围设备,如USB,S...

linuxCool
14分钟前
2
0
Group by使用

概述 GROUP BY我们可以先从字面上来理解,GROUP表示分组,BY后面写字段名,就表示根据哪个字段进行分组,如果有用Excel比较多的话,GROUP BY比较类似Excel里面的透视表。 GROUP BY必须得配合...

小橙子的曼曼
25分钟前
3
0
机械臂写中文

Make Me a Hanzi https://www.skishore.me/makemeahanzi/ 使用uArm Swift Pro机械臂写中文-毛笔字 https://github.com/makelove/Robot_Arm_Write_Chinese...

itfanr
37分钟前
4
0
OSChina 周三乱弹 —— 孤独到都和病毒发生了感情了

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @-冰冰棒- :#今日歌曲推荐# 逃跑计划《一万次悲伤 (Live)》 《一万次悲伤 (Live)》- 逃跑计划 手机党少年们想听歌,请使劲儿戳(这里) 现在...

小小编辑
今天
1K
14
test

//// main.c// Test//// Created by 吕颖 on 2019/1/16.// Copyright © 2019年 carmen. All rights reserved.//#include <stdio.h>#include <stdlib.h>#include <t......

carmen-ly
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部