【Java】一步一步理解「动态代理」

  • Post author:
  • Post category:java



前言:本篇博客依次介绍了静态代理、JDK动态代理和CGLIB动态代理,从静态代理开始一步一步理解这三种代理的实现方式。文中有一些代码的例子,希望可以耐心读完,都是些逻辑很简单的代码,但是如果不看代码,可能就不太好理解。


一、代理模式


简单理解代理模式:简单来说就是我们使用①代理对象来代替对真实对象(real object)的访问,这样就②可以在不修改原目标对象的前提下③提供额外的功能操作,扩展目标对象的功能。

上面用序号标出来的3点就是代理模式的关键:

①通过代理对象去访问真正要访问的对象。

举个例子:小明要跟总裁小红进行沟通,但是小明不能直接跟小红面对面讲话,而是需要通过小红的秘书来跟小红沟通;这个例子中,小红的秘书就是一个代理对象。

②后面两点是我们使用代理模式的目的,就是为了增强对象的方法(即提供额外操作),同时不改变原先写好的代码。

举个例子:现在写好了一个类myeg,这个类里面有一个sayHi()方法,我们要使sayHi()的功能更丰富,同时不对类myeg进行修改,那么我们就可以考虑使用动态代理。

那么问题来了,代理要怎么实现呢?在Java中,代理有两种实现方式,分别是静态代理和动态代理。接下来我们就从静态代理开始理解,一步一步来认识Java中的代理。


参考资料:https://javaguide.cn/java/basis/proxy.html


二、静态代理


静态代理的实现方法十分简单粗暴,我们直接从代码中来认识它:

①首先,我们先实现一个类及其接口,这个类就是我们接下来要增强的类,暂且把它叫做「目标类」。

接口StaticService:

public interface StaticService {
    void sayHi();
}

接口的实现类(待会要增强它!)

public class StaticServiceImpl implements StaticService {
    @Override
    public void sayHi() {
        System.out.println("Hi~");
    }
}

②我们实现一个代理类,这个代理类也实现了上文中定义的接口,同时实例化了一个「目标类」对象(这是要干什么捏,别急,且看代码)。

public class StaticProxy implements StaticService {
    private StaticService myProxy;
    public StaticProxy(StaticService staticService){
        myProxy = staticService;
    }
    
    @Override
    public void sayHi() {
        System.out.println("手动增强中biu - biu - biu - ");
        myProxy.sayHi();
        System.out.println("成功!");
    }
}

看了代码,我们应该就清楚代理类是怎么增强「目标类」的了,其实它就是手动调用了「目标类」的方法,这样就保证代理类百分之百保留了「目标类」的东西;同时,我们可以在调用前后编写代码来实现增强的效果(如这里的两句输出语句)。

③实际使用。

public class TestProxy {
    @Test
    public void testStaticProxy(){
        StaticService myServie = new StaticServiceImpl();
        StaticProxy myProxy = new StaticProxy(myServie);
        myProxy.sayHi();
    }
}

总结一下:

在这里插入图片描述


三、动态代理之JDK动态代理


首先,我们先了解下为什么需要动态代理。【静态代理:你礼貌吗?】

因为静态代理有以下缺点:①静态代理我们要针对每一个目标类都单独创建一个代理类。②一旦目标类实现的接口多加另一个方法,那我们不仅要修改目标类,还要修改代理类。


总结:

静态代理类不够灵活,所以我们需要动态代理。动态代理主要有两种,分别是JDK动态代理和CGLIB动态代理,这一节我们先介绍JDK动态代理。

—分割线—

JDK动态代理中,我们需要先认识一个接口InvocationHandler,这个接口中有一个方法invoke(),待会我们需要去实现实现这个接口并实现这个invoke()方法。此外,还有一个类需要我们认识,这个类叫Proxy,它有一个方法叫newProxyInstance(),这个方法是用来帮我们生成代理对象的。

我们依旧从代码出发:

①首先,依旧是先实现「目标类」及其接口:

还是刚才那两个。【虽然它们名字带了Static,但现在是动态代理的例子了!】

接口:

public interface StaticService {
    void sayHi();
}

接口的实现类(待会要增强它!)

public class StaticServiceImpl implements StaticService {
    @Override
    public void sayHi() {
        System.out.println("Hi~");
    }
}

②写一个类实现InvocationHandler接口及其invoke()方法。

之所以要实现这个接口和这个invoke()方法,是因为我们待会是通过这个invoke()方法来调用「目标类」中的方法的;而在调用的前后,我们同样可以写代码来达到增强方法的目的。

这个invoke()方法有3个参数:

  • proxy:动态生成的代理类(动态生成代理类的代码在后面,这里先知道参数的意义即可)。
  • method:与代理类对象调用的方法相对应。
  • args:调用method对应的方法中所需要的参数。

    也就是说:我们在通过代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。

    接下来我们看看实现InvocationHandler接口及其invoke()方法的代码:
public class jdkProxyHandler implements InvocationHandler {
    private Object target;
    public  jdkProxyHandler(Object target){
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK动态代理增强中biu - biu - biu - ");
        method.invoke(target, args);
        System.out.println("成功!");
        return null;
    }
}

注意在method.invoke()中,第一个参数是类中的成员变量target,如果错写成proxy,就会陷入到无限循环中。

③动态生成代理类对象。

这里需要用到前面提到的Proxy类及其newProxyInstance()方法。前面已经提到,这个方法是用来生成代理对象的,它有3个参数:

  • loader:「目标类」的类加载器,用于加载代理对象。
  • interfaces:「目标类」实现的接口。
  • h:实现了InvoctionHandler接口的类的对象,也就是我们在上一步中实现的类的对象。

由于newProxyInstance()方法是一个静态方法,因此可以直接通过类来调用。

代码中的getProxy()需要我们传入一个「目标类」对象。

public class jdkProxyFactory {
    public static Object getProxy(Object target){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new jdkProxyHandler(target)
        );
    }
}

④具体使用。

通过第③步中实现的jdkProxyFactory动态生成代理对象(由于是Object类型,所以要强转成实际要用的StaticService类型),然后调用代理对象的方法即可。在调用代理对象方法的时候,会自动去调用第②步实现的jdkProxyHandler类中的invoke()方法,这个jdkProxyHandler类实现了InvocationHandler接口。

public class TestProxy {
    @Test
    public void testJDKProxy(){
        StaticService myProxy = (StaticService) jdkProxyFactory.getProxy(new StaticServiceImpl());
        myProxy.sayHi();
    }
}

总结一下:
在这里插入图片描述


四、CGLIB动态代理


JDK动态代理有一个缺点,那就是:只有一个类是有实现接口的类,这个类才能被增强。为解决这个问题,我们可以使用CGLIB动态代理。

同样的,在实现CGLIB动态代理之前,我们先认识一下需要用到的接口和类。首先是一个叫做MethodInterceptor的接口,我们需要实现这个接口及其intercept()方法,实现这个接口的目的跟实现JDK动态代理中的InvocationHandler接口类似。此外,还需要认识一个类Enhancer,这个类用来动态生成代理对象。

接下来,我们从代码中来理解CGLIB动态代理:

①首先,依旧是实现一个「目标类」,这个目标类还是之前那个,只不过我把它改成没有实现接口的了。

public class StaticServiceImpl{
    public void sayHi() {
        System.out.println("Hi~");
    }
}

②写一个类实现MethodInterceptor接口及其intercept()方法。

这个接口及方法的作用类似于JDK动态代理中的InvocationHandler接口和invoke()方法。

这个intercept()方法有4个参数:

  • obj:「目标类」对象。
  • method:被拦截的方法(「目标类」中需要增强的方法)。
  • args:method方法需要的参数。
  • proxy:用于调用原始方法。

在调用方法的前后,我们依然可以通过编写代码来达到增强的目的,如这里的两句输出语句。

public class CglibProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("CGLIB动态代理增强中biu - biu - biu - ");
        methodProxy.invokeSuper(o, args);
        System.out.println("成功!");
        return null;
    }
}

③动态生成代理类对象。

这里需要用的Enhancer类,我们先实例一个Enhancer类的对象,然后进行一些信息的设置,最后调用这个对象的create()方法来生成代理类。

public class CglibProxyFactory {
    public static Object getProxy(Class<?> clazz){
        Enhancer enhancer = new Enhancer();
        //  设置类加载器
        enhancer.setClassLoader(clazz.getClassLoader());
        //  设置目标类
        enhancer.setSuperclass(clazz);
        //  设置方法的拦截器
        enhancer.setCallback(new CglibProxy());
        //  创建代理类
        return enhancer.create();
    }
}

④实际使用。

依然是获取代理对象,然后调用代理对象的方法即可。

public class TestProxy {
    @Test
    public void testCglibProxy(){
        StaticServiceImpl myProxy = (StaticServiceImpl) CglibProxyFactory.getProxy(StaticServiceImpl.class);
        myProxy.sayHi();
    }
}

总结一下:
在这里插入图片描述


五、对比:JDK动态代理和CGLIB动态代理


在这里插入图片描述

—-完结撒花—-



版权声明:本文为lucas161543228原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。