资讯专栏INFORMATION COLUMN

理解 Java AOP - JDK动态代理《二》

voidking / 2100人阅读

摘要:原理动态代理要求代理目标必须是接口的实现类,通过接口生成模板类,模板类实现所有接口方法,实现方法是一个个模板方法,只是简单的通过反射把请求委托给处理。

使用 业务接口: IBiz
public interface IBiz {
    void doSomething();
}
业务实现类: BizImpl
public class BizImpl implements IBiz {

    public void doSomething() {
        System.out.println("做一些业务逻辑");
    }
}
横切逻辑: PerformanceMonitor
public class PerformanceMonitor {

    public void start() {
        System.out.println("开始时间: " + String.valueOf(System.currentTimeMillis()));
    }

    public void end() {
        System.out.println("结束时间: " + String.valueOf(System.currentTimeMillis()));
    }
}
代理调用处理器: BizInvocationHandler

为接口生成的模板代理类,所有方法调用时都会委托给InvocationHandler.invoke(...)代为处理,它根据传入的Method信息,使用反射机制调用真实的方法。

public class BizInvocationHandler implements InvocationHandler {

    private IBiz target;
    private PerformanceMonitor monitor;

    public BizInvocationHandler(IBiz target) {
        this.target = target;
        this.monitor = new PerformanceMonitor();
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        monitor.start();
        method.invoke(target);
        monitor.end();
        return null;
    }
}
代理生成器: ProxyBuilder
public class ProxyBuilder {

    private Class [] interfaces;
    private InvocationHandler handler;
    private ClassLoader classLoader = ProxyBuilder.class.getClassLoader();

    public static ProxyBuilder newProxyBuilder() {
        return new ProxyBuilder();
    }

    public ProxyBuilder setInterFaces(Class[] interFaces) {
        this.interfaces = interFaces;
        return this;
    }

    public ProxyBuilder setClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
        return this;
    }

    public ProxyBuilder setInvocationHandler(InvocationHandler handler) {
        this.handler = handler;
        return this;
    }

    public Object build() {
        return Proxy.newProxyInstance(classLoader, interfaces, handler);
    }

    public void buildClassFile(String className, String dir) {
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(className, interfaces);

        StringBuilder strBuilder = new StringBuilder();
        strBuilder.append(dir).append("/").append(className).append("class");
        String classFileName = strBuilder.toString();

        FileOutputStream out = null;
        try {
            out = new FileOutputStream(classFileName);
            out.write(proxyClassFile);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

其中build()生成代理对象;buildClassFile(...)生成代理类的class文件。

测试
public class JDKProxyTest {

    @Test
    public void testBiz() {
        IBiz biz = new BizImpl();
        BizInvocationHandler hander = new BizInvocationHandler(biz);

        IBiz proxy = (IBiz)ProxyBuilder.newProxyBuilder()
            .setClassLoader(Thread.currentThread().getContextClassLoader())
            .setInterFaces(new Class[] {IBiz.class})
            .setInvocationHandler(hander)
            .build();
        proxy.doSomething();
    }
}

执行输出:

开始时间: 1500530510627
做一些业务逻辑
结束时间: 1500530510628
使用小结

JDK动态代理使用步骤如下:

实现InvocationHandler接口,为其载入代理的目标实例&横切逻辑,通过实现invoke方法实现横切逻辑织入。

通过Proxy.newProxyInstance(...)把要代理的接口和InvocationHandler实例联系起来生成最终的代理实例。

通过强制类型转换可以把生成的代理实例转换成任何一个代理的接口类型,从而调用接口方法。

原理

JDK动态代理要求代理目标必须是接口的实现类,通过接口生成 模板类 ,模板类实现所有接口方法,实现方法是一个个 模板方法 ,只是简单的通过反射把请求委托给InvocationHandler.invoke(...)处理。

回头看下ProxyBuilder.buildClassFile(...),它通过ProxyGenerator.generateProxyClass(...)生成IBiz的代理类。

生成代理类
public class JDKProxyTest {

    @Test
    public void testBuildClassFile() {
        IBiz biz = new BizImpl();
        BizInvocationHandler hander = new BizInvocationHandler(biz);

        ProxyBuilder.newProxyBuilder()
            .setClassLoader(Thread.currentThread().getContextClassLoader())
            .setInterFaces(new Class[] {IBiz.class})
            .setInvocationHandler(hander)
            .buildClassFile("proxy", ".");
    }
}
反编译生成的代理类

直接通过Idea打开生成的proxy.class文件即可,反编译后的代码一下(注意:这里去掉了hashCodetoString等无强关联性代码):

public final class proxy extends Proxy implements IBiz {
    private static Method m1;
    
    ...

    public proxy(InvocationHandler var1) throws  {
        super(var1);
    }

    public final void doSomething() throws  {
        try {
            super.h.invoke(this, m1, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("jdkproxy.IBiz").getMethod("doSomething", new Class[0]);
            
            ...
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

哈哈,代码非常简单,无谓多说。

代理类实例化过程

入口: Proxy.newProxyInstance(...)

    public static Object newProxyInstance(ClassLoader loader,
                                          Class[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        ...
        
        final Class[] intfs = interfaces.clone();
        
        // 获取或生成指定接口的代理类,这里会对生成的代理类进行缓存,下面展开。
        Class cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            ...

            // 获取代理类的构造方法
            final Constructor cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                // 构造方法不为public的话,修改其访问属性
                AccessController.doPrivileged(new PrivilegedAction() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            
            // 通过反射调用代理类的构造方法实例化代理对象返回。
            return cons.newInstance(new Object[]{h});
        } catch ...
        
        ...
    }

Proxy.getProxyClass0(...)

    private static Class getProxyClass0(ClassLoader loader,
                                           Class... interfaces) {
        // 限制接口数量
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // 这是一个WeakCach。
        // 如果cache中存在由loader加载并且实现了interfaces接口的代理类,就直接返回。
        // 否则就通过ProxyClassFactory创建代理类
        // proxyClassCache = (new WeakCache<>(new KeyFactory(), new ProxyClassFactory());)
        return proxyClassCache.get(loader, interfaces);
    }

ProxyClassFactory.apply(...)

最终生成代理类的逻辑就在这里

        public Class apply(ClassLoader loader, Class[] interfaces) {
            ....
            // 生成代理类的字节码,buildClassFile.buildClassFile也是这样生成代理类的。
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                // 返回定义的代理类
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                ...
            }
        }
总结

通过JDK代理生成的代理类是一个模板类,它通过反射找到接口的所有方法,并为每一个方法生成模板方法,通过反射调InvocationHandler.invoke(...),通常业务逻辑和横切都在这里被调用。

由于JDK代理生成的代理类相对cglib生成子类要轻量级一些,所以在生成代理的效率上要优于cglib代理,但是在调用时,GDK代理通过反射的方式调用,相对cglib直接调用效率上要低。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67434.html

相关文章

  • Spring AOP就是这么简单啦

    摘要:是一种特殊的增强切面切面由切点和增强通知组成,它既包括了横切逻辑的定义也包括了连接点的定义。实际上,一个的实现被拆分到多个类中在中声明切面我们知道注解很方便,但是,要想使用注解的方式使用就必须要有源码因为我们要 前言 只有光头才能变强 上一篇已经讲解了Spring IOC知识点一网打尽!,这篇主要是讲解Spring的AOP模块~ 之前我已经写过一篇关于AOP的文章了,那篇把比较重要的知...

    Jacendfeng 评论0 收藏0
  • 【好好面试】学完Aop,连动态代理的原理都不懂?

    摘要:总结动态代理的相关原理已经讲解完毕,接下来让我们回答以下几个思考题。 【干货点】 此处是【好好面试】系列文的第12篇文章。文章目标主要是通过原理剖析的方式解答Aop动态代理的面试热点问题,通过一步步提出问题和了解原理的方式,我们可以记得更深更牢,进而解决被面试官卡住喉咙的情况。问题如下 SpringBoot默认代理类型是什么 为什么不用静态代理 JDK动态代理原理 CGLIB动态代理...

    Keven 评论0 收藏0
  • 从源码入手,一文带你读懂Spring AOP面向切面编程

    摘要:,,面向切面编程。,切点,切面匹配连接点的点,一般与切点表达式相关,就是切面如何切点。例子中,注解就是切点表达式,匹配对应的连接点,通知,指在切面的某个特定的连接点上执行的动作。,织入,将作用在的过程。因为源码都是英文写的。 之前《零基础带你看Spring源码——IOC控制反转》详细讲了Spring容器的初始化和加载的原理,后面《你真的完全了解Java动态代理吗?看这篇就够了》介绍了下...

    wawor4827 评论0 收藏0
  • Spring AOP() 修饰者模式和JDK Proxy

    摘要:修饰者模式设计模式中的修饰者模式能动态地给目标对象增加额外的职责。修饰者模式调用的时序图如下图所示。的实现原理和修饰者模式类似。  在上边一篇文章中我们介绍了Spring AOP的基本概念,今天我们就来学习一下与AOP实现相关的修饰者模式和Java Proxy相关的原理,为之后源码分析打下基础。 修饰者模式  Java设计模式中的修饰者模式能动态地给目标对象增加额外的职责(Respon...

    Jackwoo 评论0 收藏0
  • Java 代理模式与 AOP

    摘要:本文首发于作者最近在学,研究了下和代理模式,写点心得和大家分享下。所以下面来重点分析下代理模式。这里代理模式分为静态代理和动态代理两种,我们分别来看下。代理模式,代理,意味着有一方代替另一方完成一件事。 本文首发于 https://jaychen.cc作者 jaychen 最近在学 Spring,研究了下 AOP 和代理模式,写点心得和大家分享下。 AOP 先说下AOP,AOP 全称 ...

    jk_v1 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<