六、代理模式

六、动态代理模式

1、模式结构和结构图

  1. 1 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。

    1.2 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。

    1.3 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能
    在这里插入图片描述

2、代码实战

需求背景:我们通过代理模式,来实现对某个方法的执行时间统计。

公共方法:

// 时间类
package proxy;

/**
 * 时间工具类
 *
 * @author 刀刀和阳
 */
public class DateTimeUtils {

    /**
     * 开始计时
     *
     * @return 当前时间
     */
    public static long start() {
        System.out.println("开始计时!");
        return System.currentTimeMillis();
    }

    /**
     * 结束计时
     *
     * @return 当前时间
     */
    public static long end() {
        System.out.println("结束计时!");
        return System.currentTimeMillis();
    }
}

公共方法接口

package proxy;

/**
 * 公共方法接口
 *
 * @author 刀刀和阳
 */
public interface Method {
    void doSomething();
}

具体的方法实现类:

package proxy;

/**
 * @author 刀刀和阳
 */
public class RealMethod implements Method {
    
    @Override
    public void doSomething() {
        System.out.println("实际的方法调用!");
    }
}

2.1 静态代理模式:

静态代理方法实现类:

package proxy.staticproxy;

import proxy.DateTimeUtils;
import proxy.Method;

/**
 * 静态代理方法实现类
 * 
 * @author 刀刀和阳
 */
public class ProxyMethod implements Method {
    private Method method;

    public ProxyMethod(Method method) {
        this.method = method;
    }

    @Override
    public void doSomething() {
        long start = DateTimeUtils.start();
        method.doSomething();
        long end = DateTimeUtils.end();
        System.out.println("总计耗时:" + (end - start) + "ms;");
    }
}

测试:

package proxy.staticproxy;

import proxy.Method;
import proxy.RealMethod;

/**
 * 实现某方法的调用时间统计
 *
 * @author 刀刀和阳
 */
public class Client {
    public static void main(String[] args) {
        Method realMethod = new RealMethod();
        ProxyMethod proxyMethod = new ProxyMethod(realMethod);
        proxyMethod.doSomething();
    }
}

执行结果:

开始计时!
实际的方法调用!
结束计时!
总计耗时:0ms;

2.2 动态代理模式

代理方法实现类

package proxy.dynamicproxy;

import proxy.DateTimeUtils;

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

/**
 * 动态代理方法实现类
 * 
 * @author 刀刀和阳
 */
public class ProxyMethod implements InvocationHandler {
    private Object object;

    public ProxyMethod(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = DateTimeUtils.start();
        method.invoke(object, args);
        long end = DateTimeUtils.end();
        System.out.println("总计耗时:" + (end - start) + "ms;");
        return object;
    }
}

测试:

package proxy.dynamicproxy;

import proxy.Method;
import proxy.RealMethod;

import java.lang.reflect.Proxy;

/**
 * 动态代理测试方法
 * 
 * @author 刀刀和阳
 */
public class Client {
    public static void main(String[] args) {
        Method realMethod = new RealMethod();
        Method proxyMethod = (Method) Proxy.newProxyInstance(
                Method.class.getClassLoader(),
                new Class[]{Method.class},
                new ProxyMethod(realMethod));
        proxyMethod.doSomething();
    }
}

测试结果:

开始计时!
实际的方法调用!
结束计时!
总计耗时:0m=s;

3、总结

上述代码可以总结看出,静态代理和动态代理的区别在于:静态代理在代码运行前就已经确定需要代理的具体类是什么。而动态代理则是在运行时确定具体的代理对象。

其次,如果静态代理的公共接口发生了变化,则具体的实现接口和代理接口都要做相应的改变。违反了开闭原则。可以在上述案列中Method接口新增一个方法doOtherThing()方法即可感受到差别。

最后,上述案例中的动态代理通过JDK代理的方式,此外,还有通过cglib的方式来实现动态代理。我们在进阶中会具体讲述。

4、进阶

1、 jdk代理和cglib代理的区别

4.1 原理区别:

java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。核心是实现InvocationHandler接口,使用invoke()方法进行面向切面的处理,调用相应的通知。

而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。核心是实现MethodInterceptor接口,使用intercept()方法进行面向切面的处理,调用相应的通知。

1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP

2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP

3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

4.2 性能区别:

1、CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。

2、在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理。

4.3 各自局限:

1、JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理。

唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。

2、在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理。

4.3 各自局限:

1、JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理。

2、cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值