最常见的一种场景,某些功能服务项目里根本没有使用到,但是因为项目里引用了该依赖包的class类,所以你不得不在即使没有使用到该服务的情况下,仍让要添加依赖到项目中。

但是通过动态加载class类,可以让你的项目大大减少第三方包的依赖。

核心思路就是通过反射和基本的判断语句去控制对象的实例化。

以Kafka源码为例:

以下是kafka中的压缩模块代码

public Compressor(ByteBuffer buffer, CompressionType type) {
        this.type = type;
        this.initPos = buffer.position();

        ......

        appendStream = wrapForOutput(bufferStream, type, COMPRESSION_DEFAULT_BUFFER_SIZE); // kafka数据压缩函数
    }
// the following two functions also need to be public since they are used in MemoryRecords.iteration
    static public DataOutputStream wrapForOutput(ByteBufferOutputStream buffer, CompressionType type, int bufferSize) {
        try {
            switch (type) {
                case NONE:
                    return new DataOutputStream(buffer);
                case GZIP: // 直接new
                    return new DataOutputStream(new GZIPOutputStream(buffer, bufferSize));
                case SNAPPY: // 通过反射(不使用SNAPPY时,减少依赖包)
                    try {
                        OutputStream stream = (OutputStream) snappyOutputStreamSupplier.get().newInstance(buffer, bufferSize);
                        return new DataOutputStream(stream);
                    } catch (Exception e) {
                        throw new KafkaException(e);
                    }
                case LZ4:
                    try {
                        OutputStream stream = (OutputStream) lz4OutputStreamSupplier.get().newInstance(buffer);
                        return new DataOutputStream(stream);
                    } catch (Exception e) {
                        throw new KafkaException(e);
                    }
                default:
                    throw new IllegalArgumentException("Unknown compression type: " + type);
            }
        } catch (IOException e) {
            throw new KafkaException(e);
        }
    }

大家会发现,使用GZIP时是直接new,而SNAPPY和LZ4是通过反射来实现的。

源码中对snappyOutputStreamSupplier的解释:

// dynamically load the snappy and lz4 classes to avoid runtime dependency if we are not using compression
    // caching constructors to avoid invoking of Class.forName method for each batch
    private static MemoizingConstructorSupplier snappyOutputStreamSupplier = new MemoizingConstructorSupplier(new ConstructorSupplier() {
        @Override
        public Constructor get() throws ClassNotFoundException, NoSuchMethodException {
            return Class.forName("org.xerial.snappy.SnappyOutputStream")
                .getConstructor(OutputStream.class, Integer.TYPE);
        }
    });
动态加载snappy和lz4类,以避免在不使用压缩/缓存构造函数的情况下出现运行时依赖关系,从而避免为每个批调用class.forname方法

以报警业务来举例,有A、B、C、D、E ,共5个客户购买了你的项目,5个客户都需要在你项目里增加客户自己定义的报警代码,假设都是使用的webservice。

此时按照常规思路,你需要在你的项目里引入5个客户,也就是5个webservice的依赖包。

但是通常某客户只需要使用到自己定义的报警逻辑。以A举例,A需要的只是A自身的报警逻辑,但你却在提供的项目里让A强行引入B、C、D、E四个完全不相关的依赖。

但通过反射,你只需要流程控制下走哪一条反射的逻辑,然后添加该条逻辑的依赖即可。

如果是更多的客户以及更复杂的业务逻辑,通过反射进行动态加载,可以让你的项目依赖大大减少。

02-20 17:27