Java实习生面试复习(九):聊聊动态代理

我是一名很普通的双非大三学生。我会坚持写博客,输出知识的同时巩固自己的基础,记录自己的成长和锻炼自己,奥利给!!
今年找个21届实习真的好难呀,擦干眼泪,,埋头苦干,可能你们看到这篇文章的时候,已经过去很久了。

我们大家都直接或者间接的使用过动态代理,无论是日志框架还是Spring 框架,它们都包含了动态代理。

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。主要有静态代理和动态代理,静态代理这里就不赘述了。
动态代理是程序在运行期间动态构建代理对象和动态调用代理方法的一种机制。

这篇文章我们就来学习一下,如何实现动态代理?JDK Proxy 和 CGLib 有什么区别?

动态代理

说到动态代理,我想大家脑子里可能第一个蹦出来的词是**反射**,动态代理的常用实现方式是反射。
注意!是常用实现方式,不是所有的代理都是反射实现的哦~,**反射机制是指什么呢?**
是指程序在运行期间可以访问、检测和修改其本身状态或行为的一种能力,使用反射我们可以调用任意一个类对象,以及类对象中包含的属性及方法。

更多反射内容可看这篇文章 => 万字总结之反射(框架之魂)

但动态代理不止有反射一种实现方式,例如,动态代理很出名的CGLib,CGLib就是基于ASM(一个Java字节码操作框架)而非反射实现的。简单来说,动态代理是一种行为方式,而反射或ASM只是它的一种实现手段而已。

JDK Proxy 和 CGLib 的区别主要体现在以下几个方面:

  • JDKProxy是Java语言自带的,无需通过加载第三方类实现,而CGLib 是第三方提供的工具,基于 ASM 实现的;
  • Java对JDKProxy提供了稳定的支持,并且会持续的升级,例如Java8版本中 JDK Proxy 性能相比于之前版本提升了很多;
  • JDK Proxy 是通过拦截器加反射的方式实现的;
  • JDK Proxy 只能代理继承接口的类,而CGLib 无需通过接口来实现,它是通过实现子类的方式来完成调用的;

JDK 动态代理和 CGLib 的使用

### JDK 动态代理实现 JDK Proxy 动态代理的实现无需引用第三方类,只需要实现 InvocationHandler 接口,重写 invoke() 方法即可,整个实现代码如下所示:
public interface HelloService {
    void sayHello(String name);

    void sayHello1(String name);
}


public class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello(String name) {
        System.out.println("Hello" + name);
    }

    @Override
    public void sayHello1(String name) {
        System.out.println("Hello" + name);
    }
}


public class HelloServiceInvocationHandler implements InvocationHandler {
    private Object target;

    public Object bind(Object target){
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("############我是jdk动态代理##############");
        System.out.println("我准备说hello");
        Object result = method.invoke(target,args);
        System.out.println("我说过hello了");
        return result;
    }
}


public class HelloServiceJdkMain {
    public static void main(String[] args) {
        HelloServiceInvocationHandler helloServiceInvocationHandler = new HelloServiceInvocationHandler();
        HelloService proxy = (HelloService) helloServiceInvocationHandler.bind(new HelloServiceImpl());
        proxy.sayHello("张三");
        proxy.sayHello1("李四");
    }
}
复制代码

运行结果如下: 可以看出 JDK Proxy 实现动态代理的核心是实现 Invocation 接口,一般主要涉及到以下两个类InvocationHandlerh和Proxy

我们查看 Invocation 的源码,会发现里面其实只有一个 invoke() 方法,源码如下:

public interface InvocationHandler {

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

第一个参数proxy一般是指代理类,method是被代理的方法,args为该方法的参数数组。

Proxy即为动态代理类

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
复制代码

获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。返回代理类的一个实例,返回后的代理类可以当作被代理类使用。

动态代理步骤

  1. 创建一个实现接口InvocationHandler的类,它必须实现invoke方法
  2. 创建被代理的类以及接口
  3. 通过Proxy的静态方法
  4. newProxyInstance(ClassLoaderloader,Class[]interfaces,InvocationHandler h) 创建一个代理
  5. 通过代理调用方法

CGLib 的实现

使用 CGLib 的话,我们需要在项目中引入 CGLib 框架,在 pom.xml 中添加如下配置:

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.5</version>
        </dependency>
复制代码

CGLib 实现代码如下:

public class HelloServiceMethodInterceptor implements MethodInterceptor {

    private Object target;

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

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("我是cglib动态代理");
        /**
         * 这里需要注意invoke和invokeSuper的区别
         * 建议看这篇文章:https://juejin.im/post/5a8f750af265da4e983f2369
         */
        methodProxy.invokeSuper(o,objects);
        return null;
    }
}

public class HelloServiceCglibMain {
    public static void main(String[] args) {
// 		  输出cglib生成的class文件
//        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\proxy");
        HelloServiceImpl helloService = new HelloServiceImpl();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(HelloServiceImpl.class);
        enhancer.setCallback(new HelloServiceMethodInterceptor(helloService));
        helloService = (HelloServiceImpl) enhancer.create();
        helloService.sayHello("11");

    }
}
复制代码

运行截图: 从上述的两个简单案例来看, CGLib 和 JDK Proxy 的实现代码是比较类似的,都是通过实现代理器的接口,再调用某一个方法完成动态代理的。

不同的是:

  • CGLib 在初始化被代理类时,是通过 Enhancer 对象把代理对象设置为被代理类的子类来实现动态代理的。因此被代理类不能被关键字 final 修饰(被final修饰的类不能够被继承),如果被 final 修饰,再使用 Enhancer 设置父类时会报错,动态代理的构建会失败。
  • JDK 动态代理是通过委托机制也即实现目标类的接口,然后将目标类在构造动态代理时作为参数传入,使代理对象持有目标对象,再通过代理对象的 InvocationHandler 实现动态代理的操作。

总的来说,JDK 动态代理利用接口实现代理,CGLIB 动态代理利用继承的方式实现代理。

动态代理知识点扩充

动态代理和静态代理的区别

静态代理其实就是事先写好代理类,可以手工编写也可以使用工具生成,但它的缺点是每个业务类都要对应一个代理类,特别不灵活也不方便,于是就有了动态代理。 动态代理的常见使用场景有 RPC 框架的封装、AOP(面向切面编程)的实现(高频面试题)、JDBC 的连接等。

Spring 框架中同时用到了两种动态代理 JDK Proxy 和 CGLib,当 Bean 实现了接口时,Spring 就会使用 JDK Proxy,在没有实现接口时就会使用 CGLib。

猜你喜欢

转载自juejin.im/post/5eac181cf265da7c07509d40
今日推荐