【Java必备】Java代理模式(静态代理、JDK/Cglib动态代理)

引言

相信作为Java开发人员,应该都有用过或见过代理设计模式,像Spring的AOP中,就是使用的动态代理模式,Mybatis中xxxMapper接口,也是使用的动态代理来生成相应的Mapper代理对象,可见,动态代理模式的重要性。

代理模式一般分为静态代理和动态代理,目标都是为了扩展增强原有方法功能。

静态代理

  • 抽象对象角色(AbstractObject):一般使用接口或抽象类,声明了目标对象和代理对象的共同接口,这样依赖在任何可以使用目标对象的地方都可以使用代理对象。
  • 目标对象角色(RealObject):被代理的真实角色。
  • 代理对象角色(ProxyObject):实现目标对象的相同接口,并含有目标对象的引用,可对目标方法进行自定义扩展增强。

定义一个订单接口,添加下单方法

public interface IOrderService {
    // 提交订单
    void submitOrder();
}

编写订单接口实现类

public class OrderServiceImpl implements IOrderService{

    // 提交订单测试
    @Override
    public void submitOrder() {
        System.out.println("-------保存订单-------");
    }
}

然后,假如当前的submitOrder()方法已经不能满足我们的需求,需要在此方法前后做一些特殊的业务处理,但我们又不希望在原有订单接口实现类上做修改(开闭原则),所以我们可以新增一个订单代理类,这个代理类定义了一个目标接口对象的成员属性,通过构造注入,从而可以操作目标对象,可以在目标对象方法前后添加自定义逻辑:

public class OrderStaticProxy implements IOrderService {

    private IOrderService orderService;

    // 构造注入
    public OrderStaticProxy(IOrderService orderService){
        this.orderService=orderService;
    }

    @Override
    public void submitOrder() {
        System.out.println("--------提交订单前,自定义逻辑");
        orderService.submitOrder();
        System.out.println("--------提交订单前,自定义逻辑");
    }
}

测试类:

public class Test {

    public static void main(String[] args) {
        IOrderService orderService = new OrderServiceImpl();
        OrderStaticProxy proxy = new OrderStaticProxy(orderService);
        proxy.submitOrder();
    }
}

输出如下:

分析:在不改变原有OrderServiceImpl实现类的情况下,扩展增强了原订单接口的功能

优点:

  • 可以做到在不修改目标对象的前提下,拓展目标对象的功能;
  • 公共的业务交由代理类进行处理,更加方便;
  • 符合开闭原则、单一原则。

缺点:

  • 代理类需要同目标对象实现相同的接口,所以每个类都需要写代理类时,造成类过多,维护不方便;
  • 假如接口中增加方法,目标实现类与代理类都需要进行修改。

因此,我们引出动态代理。

动态代理

动态代理的角色与静态代理一样。

静态代理会为每一个业务增强都提供一个代理类, 由代理类来创建代理对象;而动态代理并不存在代理类, 代理对象直接由代理生成工具动态生成。

动态代理的代理对象是运行时动态生成的,静态代理的代理类需要我们提前写好,即代理类在编译时期就已经确定了。

动态代理又分为:

  • 基于接口的动态代理(JDK动态代理)
  • 基于类的动态代理(cglib)

JDK动态代理

在JDK动态代理中,核心是InvocationHandler接口Proxy类,具体可以参考JDK帮助文档。

package java.lang.reflect;

public interface InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

其中,我们需要实现InvocationHandler#invoke()方法,参数为:

  • Object proxy:代理对象(基本没什么用)
  • Method method:代理对象调用的方法
  • Object[] args:被调用方法中的参数

通过Proxy#newProxyInstance()生成代理类对象

    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        //。。。。
    }

其中,该方法在Proxy类中是静态方法,参数为:

  • ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的。
  • Class<?>[] interfaces:目标对象实现的接口类型,使用泛型方式确认类型。
  • InvocationHandler h:事件处理器,执行目标对象的方法时,会触发事件处理器的invoke()方法,会把当前执行目标对象的方法作为参数传入。

代码实现

抽象对象角色与目标对象角色与静态代理一样(IOrderService-->OrderServiceImpl)。

自定义实现InvocationHandler#invoke(),如下

public class DynamicProxy implements InvocationHandler {


    private Object target;

    public DynamicProxy(Object target){
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("------动态代理,前置增强-----");
        Object invoke = method.invoke(target, args);
        System.out.println("------动态代理,后置增强-----");
        return invoke;
    }
    // 生成动态代理对象
    public static Object getProxy(Object target){
        DynamicProxy proxy = new DynamicProxy(target);
        return Proxy.newProxyInstance(proxy.getClass().getClassLoader(), target.getClass().getInterfaces(),proxy);
    }

}

测试类:

/**
 * @description: 测试
 * @author: stwen_gan
 * @date: 
 **/
public class Test {

    public static void main(String[] args) {

        // 为了在项目下生成代理类
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        
        // 静态代理
//        IOrderService orderService = new OrderServiceImpl();
//        OrderStaticProxy proxy = new OrderStaticProxy(orderService);
//        proxy.submitOrder();

        // 动态代理
        IOrderService orderService = new OrderServiceImpl();
        IOrderService dynamicProxy = (IOrderService) DynamicProxy.getProxy(orderService);
        dynamicProxy.submitOrder();


    }
}

输出:

其中,添加如下配置语句

//该设置用于输出cg1ib动态代理产生的类

// System.setProperty(Debuggingclasswriter.DEBUG_LOCATION_PROPERTY,"D:\\class");

//该设置用于输出jdk动态代理产生的类

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

就会在我们的当前目录下,JDK默认会生成了一个代理类,名字默认$Proxy0,继承了Proxy

核心:一个动态代理,一般代理某一类业务,可代理多个类,代理的是接口。

从上面的代码可以看出,动态代理对象不需要实现目标对象接口,但是目标对象一定要实现接口,否则不能使用JDK动态代理。

改进:返回对象类型为泛型,抽象接口可以没有实现类,类似Mybatis中的xxxMapper接口,底层也是通过动态代理生成mapper代理对象,去拼接执行sql,我们无需编写mapper实现类

Cglib代理

上面的静态代理和JDK动态代理模式都需要目标对象去实现一个接口,但是有的时候,目标对象可能只是一个独立的类,并没有实现任何的接口,这个时候,我们就可以使用目标对象子类的方式实现代理,这种代理方式就是:Cglib代理,使用得相对少一些。(引用

Cglib动态代理,原理是生成目标类的子类(即这个子类对象就是代理对象),它是在内存中构件一个子类对象,从而实现对目标对象的功能拓展

注意: 不管有没有实现接口都可以使用Cglib动态代理, 而不是只有在无接口的情况下才能使用。

  • JDK的动态代理有个限制,就是使用动态代理的目标对象必须实现至少一个接口,由此,没有实现接口但是想要使用代理的目标对象,就可以使用Cglib代理。
  • Cglib是强大的高性能的代码生成包,它可以在运行期间拓展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)。
  • Cglib包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类,不鼓励直接使用ASM,因为它要求你必须对JVM内部结构,包括class文件的格式和指令集都很熟悉。

MethodInterceptor

public interface MethodInterceptor extends Callback {
    Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}

Cglib工具类,我们只需要实现MethodInterceptor#intercept(),对目标方法进行增强

public class CglibProxy implements MethodInterceptor {

    //维护目标对象
    private Object target;
    public CglibProxy(Object target) {
        this.target = target;
    }

//    //获取目标对象的代理对象
//    public Object getProxy() {
//        //1. 实例化工具类
//        Enhancer en = new Enhancer();
//        //2. 设置父类对象
//        en.setSuperclass(this.target.getClass());
//        //3. 设置回调函数
//        en.setCallback(this);
//        //4. 创建子类,也就是代理对象
//        return en.create();
//    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("-------动态代理,前置增强-----");
        //执行目标对象的方法
        Object object = method.invoke(target, objects);
        System.out.println("-------动态代理,后置增强-----");
        return object;
    }
}

其中,注释掉的那个方法,是为了获取目标类的代理对象,我们可以把它单独抽取出来,新增一个cglib代理工厂:

/**
 * @description: cglib代理工厂
 * @author: stwen_gan
 * @date: 
 **/
public class CglibProxyFactory {
    //获取目标类的代理对象
    public static <T> T createProxy(final Class<?> targetClass, final MethodInterceptor methodInterceptor) {
        return (T) Enhancer.create(targetClass,methodInterceptor);
    }
}

新增一个OrderDao类,无需实现任何接口

public class OrderDao {
    
    void submitOrder(){
        System.out.println("--------保存订单-------");
    }
}

测试类:

输出:

总结

JDK动态代理 相比较 静态代理 相同点在于都是面向接口编程,在不修改原有代码情况下增强目标方法功能,而且动态代理无需像静态代理一样,针对目标对象需要编写一一对应的代理类。使用代理模式,符合“开闭原则”,功能职责划分清晰,目标都是为了扩展增强原有方法功能,更易于后期的扩展与维护。

史上最强Tomcat8性能优化

阿里巴巴为什么能抗住90秒100亿?--服务端高并发分布式架构演进之路

B2B电商平台--ChinaPay银联电子支付功能

学会Zookeeper分布式锁,让面试官对你刮目相看

SpringCloud电商秒杀微服务-Redisson分布式锁方案

查看更多好文,进入公众号--撩我--往期精彩

一只 有深度 有灵魂 的公众号0.0

猜你喜欢

转载自blog.csdn.net/a1036645146/article/details/111881599
今日推荐