动态代理和静态代理

动态代理和静态代理都是代理.那什么是代理模式呢?
代理模式是指,为其他对象提供一种代理以控制这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户类和目标对象之间起到中介的作用.
换句话说,使用代理对象,是为了在不修改目标对象的基础上,增强主业务逻辑(即代理对象可以在目标对象的基础上增加一些新的功能).
客户端真正想要访问的对象是目标对象,但是客户类真正可以访问的对象是代理对象,客户类对目标对象的访问是通过访问代理来实现的.当然,代理类和目标类要实现同一个接口.
例如: 有A、B、C三个类,A可以调用C类的方法,现在因为某种原因C类不允许A类调用其方法,但是B类可以调用C类的方法.A类通过B类调用C类的方法.这里B是C的代理.A通过代理B访问C.

介绍完了代理模式,那么使用代理模式的作用是什么呢?

  1. 目标类方法的调用
  2. 功能增强(主要作用): 在目标对象原有功能基础上,代理对象增加一些额外的功能.
  3. 控制访问: 代理类不让客户端访问目标

实现代理的方式
1. 静态代理

  • 代理类是自己手工实现的,即自己创建一个java类,表示代理类.
  • 要代理的目标类是确定的
/**
 * 卖U盘接口
 */
public interface UsbSell {
    /**
     * @param amount 表示卖U盘的数量
     * @return
     */
    float sell(int amount);
}

/**
 * 代理模式中的目标类:金士顿厂家,卖U盘,不接受用户的直接购买。
 */
public class UsbKingFactory implements UsbSell{
    /**
     * U盘的价格是85原,购买的数量是amount,返回总价格
     * @param amount 表示卖U盘的数量
     * @return
     */
    public float sell(int amount) {
        return 85f * amount;
    }
}

/**
 * 代理类: 京东是一个商家,代理金士顿U盘的销售
 */
public class Jingdong implements UsbSell{
    UsbSell factory = new UsbKingFactory();

    public float sell(int amount) {
        // 金士顿厂家销售U盘的价格
        float kingPrice = factory.sell(amount);
        // 京东作为一个卖家要加价,以获取利润。这部分即代理中的功能增强
        float profit = 10f * amount;
        // 返回京东的售价
        return kingPrice + profit;
    }
}

/**
 * 代理模式中的客户端
 */
public class Shopping {
    public static void main(String[] args) {
        UsbSell jingdong = new Jingdong();
        // 输出用户买10个U盘的价格
        System.out.println(jingdong.sell(10));
    }
}

静态代理的优缺点:
优点:
实现简单、容易理解
缺点:

  1. 当目标类增加了,代理类也需要成倍的增加,代理类数量过多.比如当增加一个目标类,并且有n个代理类代理该目标类时,那么代理类就要增加n个.
  2. 当接口中功能增加或修改了,所有实现该接口的目标类和代理类都需要修改.
    总结: 静态代理适合比较简单的环境中,接口功能简单、基本不需要变动,并且目标类和代理类数量少.

2. 动态代理
在静态代理中目标类很多时,可以使用动态代理,避免静态代理的缺点.在动态代理中即使目标类很多,1)代理类数量可以很少, 2)修改了接口中的方法时,不会影响代理类.
动态代理的定义
在程序执行过程中,使用jdk的反射机制,创建代理类对象,并动态的指定要代理的目标类.而且不用创建代理类文件.
实现代理的方式

  1. jdk动态代理
    使用java反射包中的类和接口实现动态代理的功能.
    反射包java.lang.reflect,里面有三个类: InvocationHandler、Method、Proxy.
    限制: 使用JDK的Proxy实现代理,要求目标类和代理类(不需要创建)实现相同的接口,若不存在接口,则无法使用该方式实现(可以使用cglib动态代理实现).
// 使用反射的示例
// 接口
public interface HelloService {
    void sayHello(String name);
}

// 目标类1
public class HelloServiceImpl implements HelloService{
    public void sayHello(String name) {
        System.out.println("你好," + name);
    }
}

// 目标类2
public class HelloServiceImpl2 implements HelloService{
    public void sayHello(String name) {
        System.out.println("hello," + name);
    }
}

// 代理模式中的客户端
public class TestApp {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
        HelloService helloService = new HelloServiceImpl();
//        即使增加目标类,代理类仍然不需要,通过反射即可解决
//        HelloService helloService = new HelloServiceImpl2();
//          获得HelloService的class对象的三种方式:类的实例对象.getClass、类名.class、Class.forName(类的全路径)
//        Class c = helloService.getClass();
//        Class c = HelloService.class;
        Class c = Class.forName("com.kenai.dongtai_proxy.HelloService");
        // 通过HelloService的class对象获得sayHello方法
        Method sayHello = c.getMethod("sayHello", String.class);
        // 执行helloService对象的sayHello方法,参数为"张三"
        sayHello.invoke(helloService, "张三");
    }
}

JDK动态代理的实现
依赖于反射包java.lang.reflect中的三个类: InvocationHandler、Method、Proxy.
InvocationHandler(调用处理器): 就一个方法invoke()
invoke(): 表示代理对象要执行的功能代码.你的代理类要完成的功能就写在invoke方法中.

// Obejct proxy: jdk创建的代理对象,无需赋值
// Method method: 目标类中的方法
// Object[] args: 目标类中的参数
public Object invoke(Object proxy, Method method, Object[] args);

Method类: 目标类中的方法
作用: 通过Method可以执行某个目标类中的方法.method.invoke().
Method method; method.invoke();
注: method.invoke()方法和InvocationHandler中的invoke()方法只是重名而已.

// 执行目标对象的method方法,参数为下面自己设置的方法参数
method.invoke("目标对象", "方法参数");

Proxy类:核心的对象,用于创建代理对象.之前创建对象都是new类的构造方法,现在使用proxy类的方法代替new的使用.
方法: 静态方法newProxyInstance()

// ClassLoader: 类加载器.负责将class二进制文件加载到JVM的方法区中.获取类加载器的方法可以参考上面反射代码示例中获取class对象的三种方法,在获取到class对象后,class对象.getClassLoader()即可获得.
// interfaces: 目标对象所实现的接口.也是通过反射获取的
// InvocationHandler: 自己写的代理类要完成的功能
// 返回值就是代理对象
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)

JDK动态代理实现的步骤:

  • 创建接口,定义目标类要完成的功能
  • 创建目标类,实现创建的接口
  • 创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能: 调用目标方法、功能增强
  • 使用Proxy类的静态方法,创建代理对象.并把返回值转为接口类型

动态代理代码

/**
 * 卖U盘接口
 */
public interface UsbSell {
    /**
     * @param amount 表示卖U盘的数量
     * @return
     */
    float sell(int amount);
}

/**
 * 代理模式中的目标类:金士顿厂家,卖U盘,不接受用户的直接购买。
 */
public class UsbKingFactory implements UsbSell {
    /**
     * U盘的价格是85原,购买的数量是amount,返回总价格
     * @param amount 表示卖U盘的数量
     * @return
     */
    public float sell(int amount) {
        return 85f * amount;
    }
}

public class MyShellHandler implements InvocationHandler {
    private Object target;
    public MyShellHandler(Object target){
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object res = method.invoke(target,args);
        if(method.getName().equals("sell") && res != null){
            Float price = (Float)res;
            price += (Integer)args[0] * 10;
            res = price;
        }
        return res;
    }
}

public class MainShopping {
    public static void main(String[] args) {
        // 1.创建目标对象
        UsbSell factory = new UsbKingFactory();
        // 2.创建InvocationHandler对象(方法的调用和功能增强)
        InvocationHandler handler = new MyShellHandler(factory);
        // 3.创建代理对象
        UsbSell proxy = (UsbSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(), factory.getClass().getInterfaces(), handler);
        // 4.通过代理执行方法
        // 这的sell方法对应InvocationHandler实现类MyShellHandler中的invoke方法中的method,sell方法的参数对应invoke方法中的args
        float price = proxy.sell(10);
        System.out.println("通过动态代理对象调用方法,总价格为:" + price);
    }
}

执行结果:
通过动态代理对象调用方法,总价格为:950.0
  1. cglib动态代理
    cglib是第三方的工具库,可以用来创建代理对象.
    原理: 通过继承目标类,创建它的子类,在子类中重写父类的方法,实现功能的修改增强.
    限制: 由于cglib是通过继承的方式重写方法,所以目标类不能是final的,方法也不能是final的.
    因为被final修饰类不能被继承,被final修饰的方法不能被重写,被final修饰的变量不能被修改.
    优点: cglib通过继承的方式重写方法,要求的目标类比较宽松(只要能继承能重写即可).对于无接口的类也能为其创建动态代理.并且代理效率比jdk动态代理的方式快.
    应用: cglib在很多框架中使用,比如spring aop、mybatis、Hibernate等.

代理在日常开发中的作用:
当调用其他部门或者公司的人写好的功能时,发现这个功能存在某些缺点,满足不了自己的需要,但是又由于时其他部门或者公司的人写的,不可能获得源代码,便可以通过代理的方式,增加自己的功能代码,而不用改原文件.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值