kurnek学习笔记

源码剖析JDK动态代理

上篇文章介绍了JDK的动态代理的用法,结尾处挖了个坑,这次文章就顺着两个问题来剖析JDK动态代理的源码。

首先来看第一个问题,Proxy.newProxyInstance做了些什么?

这里我们结合源码来分析,源码如下所示,关键部位已经做了中文注释:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
{
    Objects.requireNonNull(h);

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     */
    // 关键代码1:根据传入的接口列表创建动态代理类
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }
        // 关键代码2:获取动态代理类的构造方法
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        // 关键代码3:调用动态代理类的构造方法,传入InvocationHandler,创建动态代理实例
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

其中最关键的代码是以下这行:

getProxyClass0(loader, intfs); 

这行代码传入的第一个参数是需要实现的接口的ClassLoader,第二个参数是需要实现的接口列表。

这个方法会先在缓存里面查找是否已经创建过该代理类,如果没有,则会动态创建一个代理类。

再深入一点取看 getProxyClass0 的源码的话,会发现它最终调用的是 ProxyClassFactory.apply() 方法来创建代理类,以下是完整代码,关键部分也加上了中文注释。

private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
    // prefix for all proxy class names
    // 动态代理类的类名前缀
    private static final String proxyClassNamePrefix = "$Proxy";

    // next number to use for generation of unique proxy class names
    // 原子计数器,用于动态代理类的类名后缀
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        // 检查需要实现的接口是否存在,是否存在重复
        for (Class<?> intf : interfaces) {
            /*
             * Verify that the class loader resolves the name of this
             * interface to the same Class object.
             */
            Class<?> interfaceClass = null;
            try {
                interfaceClass = Class.forName(intf.getName(), false, loader);
            } catch (ClassNotFoundException e) {
            }
            if (interfaceClass != intf) {
                throw new IllegalArgumentException(
                    intf + " is not visible from class loader");
            }
            /*
             * Verify that the Class object actually represents an
             * interface.
             */
            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException(
                    interfaceClass.getName() + " is not an interface");
            }
            /*
             * Verify that this interface is not a duplicate.
             */
            if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                throw new IllegalArgumentException(
                    "repeated interface: " + interfaceClass.getName());
            }
        }

        String proxyPkg = null;     // package to define proxy class in
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

        /*
         * Record the package of a non-public proxy interface so that the
         * proxy class will be defined in the same package.  Verify that
         * all non-public proxy interfaces are in the same package.
         */
        // 根据接口的可见性,来决定动态代理的包名
        for (Class<?> intf : interfaces) {
            int flags = intf.getModifiers();
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;
                String name = intf.getName();
                int n = name.lastIndexOf('.');
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }

        if (proxyPkg == null) {
            // if no non-public proxy interfaces, use com.sun.proxy package
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }

        /*
         * Choose a name for the proxy class to generate.
         */
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num;

        /*
         * Generate the specified proxy class.
         */
        // 关键代码 1:创建动态代理类的字节码
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {
            // 关键代码 2:定义动态代理类
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            /*
             * A ClassFormatError here means that (barring bugs in the
             * proxy class generation code) there was some other
             * invalid aspect of the arguments supplied to the proxy
             * class creation (such as virtual machine limitations
             * exceeded).
             */
            throw new IllegalArgumentException(e.toString());
        }
    }
}

其中最关键的代码是以下这句:

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);

这个方法第一个参数是代理类的类名,第二个参数是需要实现的接口列表,第三个参数是访问控制Flag,返回的是一个byte数组。

返回的byte数组就是动态代理类的字节码,也就是说jdk创建了一个新的类。

下一行关键代码是:

defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);

这一句代码第一个参数是classLoader,第二个参数是类名,第三个参数是字节码,作用是动态地将刚才创建的字节码定义成了一个新的类,也就是说JDK在运行时生成了一个新类,是不是突然有了一股动态语言的味道了。


接下来回答第二个问题,即以下代码为什么会报 ClassCastException

Person proxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), Person.class.getInterfaces(), handler);

其实看过前面的源码以后,这个问题就已经非常明显了,因为 Proxy.newProxyInstance 返回的是一个新的类,这个类实现了Person类实现的所有接口,但是跟Person类不是同一个类,因此无法Cast成Person类。

通过IDE的调试功能我们可以更加直观地理解这个现象。

undefined

在下方的变量监控界面可以看到 proxy 变量的类名是 $Proxy12 ,这个 $Proxy12 就是JDK运行时动态创建出来的新类,并不是我们在编写代码时定义的。

$Proxy12 虽然实现了IPerson接口,但是由于跟Person类没有继承关系,因此如果将proxy对象cast到Person类的话,就会报错。

JDK动态代理的基础知识基本就是这些了。

对象Mapping工具Orika简介

最近几天在工作中遇到了一个场景,是将一个对象转换成另外一个结构类似的对象,如下所示:

Class Person {
	private String id;
	private String name;
	private String birthday;
	//getters and setters
}

Class PersonDto {
	private String id;
	private String name;
	private Date birthday;
	//getters and setters
}

比方说上面两个类的对象,想要将Person转换为PersonDto,最直接的写法当然是直接用setter:

Class Mapper {
	public PersonDto map(Persion p) {
		PersonDto pd = new PersonDto();
		pd.setId(p.getId());
		pd.setName(p.getName());
		pd.setBirthday(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(p.getBirthday));
                return pd;
	}
}

但如果Person对象和PersonDto对象的属性非常多,而且还存在对象嵌套呢?

再用直接赋值的方法,代码会显得非常难看臃肿,非常地不便利,这时我就想是否存在一个工具能做这样的转换,于是google了一会,果然有不少提供了对象Mapping功能的工具,其中一个佼佼者就是本文想跟大家介绍的Orika。

Orika简介

GitHub

官方文档

Orika是基于Apache License 2.0的开源项目,项目目标是为了让开发者们从冗长的对象转换逻辑中脱身,将时间放在编写真正有价值的业务代码上。

Orika会自动收集类的元信息,并且以此来生成mapper,用于之后的对象转换和复制,并且是支持递归转换的。

引入

POM引入

<dependency>
   <groupId>ma.glasnost.orika</groupId>
   <artifactId>orika-core</artifactId>
   <version>1.4.2</version><!-- or latest version -->
</dependency>

如果不用maven的话,可以下载jar包,但是还要自行引入依赖javassist/slf4j/paranamer,版本要求见官方文档。

基本用法

//创建mapper工厂
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

//注册mapper
mapperFactory.classMap(PersonSource.class, PersonDestination.class)
   .field("firstName", "givenName")
   .field("lastName", "sirName")
   .byDefault()
   .register();

//获取mapper门面
MapperFacade mapper = mapperFactory.getMapperFacade();
//创建PersonSource对象
PersonSource source = new PersonSource();
// 给source对象设置一些字段
...
// 将PersonSource对象转换为PersonDest
PersonDest destination = mapper.map(source, PersonDest.class);

主要看注册mapper的部分:

  • classMap方法注册了类的映射关系。
  • field方法可以将源对象的字段映射到目标对象的字段,也就是将PersonSource对象映射到PersonDestination对象的时候,PersonSource.firstName会复制到PersonDestination.givenName,lastName同理映射到sirName。
  • byDefault方法则是告诉mapper使用默认mapping策略,基本上来说就是mapper会找同名同类型的字段进行映射。
  • register方法用于完成mapper的注册。

在完成mapper的注册以后,只要用工厂获取mapper门面,然后用map方法就可以完成转换了,省去了自己new对象以及逐个字段赋值的大段代码。

另外如果需要映射的类只有一对,即mapper只用来将PersonSource映射到PersonDestination,而不会再配置一个映射是从PersonSource到PersonDestination2的话,那么更好的实践是采用BoundMapperFacade,即约束mapper,具体代码如下

BoundMapperFacade<PersonSource, PersonDest> boundMapper = mapperFactory.getMapperFacade(PersonSource.class, PersonDest.class);
 
PersonSource source = new PersonSource();
// 为PersonSource对象设置一些字段
...
PersonDest destination = boundMapper.map(source);
//反向映射
PersonSource source2 = boundMapper.mapReverse(destination);

BoundMapper对比标准Mapper提供了更好的性能,使用BoundMapper时如果希望反向映射的话,可以使用mapReverse方法。

OK,以上就是Orika的基本介绍了,当然这还只是皮毛,Orika还提供了更多的高级特性,比方说可以自定义mapper的转换逻辑的custom converters,自定义对象工厂的object factories,自定义mapper,映射过滤器filters等等,基本上能满足绝大部分对象转换的场景,在官方文档上有更加详细的介绍。

Home