very fantastic code# code compile

编译代码拓展点

由于dubbo本身的SPI特性,因此可以自由的加载其他的插件进来,但如果想加载的extension没有对应的实现类怎么办呢?没关系,dubbo能自动的给你生成代码并编译为一个class!以下着重讲解dubbo的Compiler模块

一、可拓展的compile设计
由于dubbo本身具有的ExtensionLoader特性,可使用类似与jdk自带的serviceloader来实现加载外部拓展组建,以下为compiler的设计

compiler.png

二、Compiler接口

该接口只有一个compiler方法,接受源代码code和一个类加载器classLoader. @spi指定为jdk为默认编译方式。

@SPI("jdk")
public interface Compiler {

    /**
     * compile code
     * @param code   java source code
     * @param classLoader
     * @return compiled class
     */
    Class<?> compiler(String code, ClassLoader classLoader);
}

三、AbstractCompiler作用

匹配源代码是否有package包名,class类名,满足以上条件后尝试用Class.forName加载类,若该类不存在jvm中,则抛出ClassNotFoundException异常,随后尝试编译这段代码

   private static final Pattern PACKAGE_PATTER = Pattern.compile("package\\s+([$_a-zA-Z][$_a-zA-Z0-9\\.]*);");

   private static final Pattern CLASS_PATTER = Pattern.compile("class\\s+([$_a-zA-Z][$_a-zA-Z0-9*]\\s+);");

   @Override
   public Class<?> compiler(String code, ClassLoader classLoader) {
       code = code.trim();
       Matcher matcher = PACKAGE_PATTER.matcher(code);
       String packageName;
       if (matcher.find()){
           packageName = matcher.group(1);
       }else {
           packageName = "";
       }

       matcher = CLASS_PATTER.matcher(code);
       String cls;
       if (matcher.find()){
           cls = matcher.group(1);
       }else {
           throw new IllegalArgumentException("no such class name in " + code);
       }
       String className = StringUtils.isBlank(packageName) ? cls : packageName + "." + cls;
       try {
           return Class.forName(className, true, ClassHelper.getCallerClassLoader(getClass()));
       } catch (ClassNotFoundException e){
           if (!code.endsWith("}")){
               throw new IllegalArgumentException("not as } end class in "+code);
           }

           try {
               return doCompile(className, code);
           } catch (RuntimeException rt){
               throw rt;
           }catch (Throwable tx) {
               throw new IllegalStateException("failed compile class code, cause by: " + tx.getMessage() + "  class:" + className + " code:" + code);
           }
       }
   }

四、AdaptiveCompiler作用

由于dubbo的spi要求一个接口要有一个适配类(若没有会自动生成代码),因此该类作用辅助ExtensionLoader来加载JdkCompiler组件。

先获取Compiler接口的ExtensionLoader对象,随后判断是否指定了编译方式,若没有指定会获取默认的Extension。获取到Extension后调用compiler方法进行编译

    private static volatile String DEFAULT_COMPILER;

    public static void setDefaultCompiler(String compiler){
        DEFAULT_COMPILER = compiler;
    }

    @Override
    public Class<?> compiler(String code, ClassLoader classLoader) {
        Compiler compiler;
        ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
        String name = DEFAULT_COMPILER;
        if (StringUtils.isBlank(name)){
            compiler = loader.getDefaultExtension();
        }else {
            compiler = loader.getExtension(name);
        }

        return compiler.compiler(code, classLoader);
    }

五、JdkCompiler作用

JdkCompiler以jdk的方式进行编译

采用javax.tools包下的JavaCompiler进行编译,其中DiagnosticCollector作用是收集编译时的一些错误信息,JavaFileManagerImpl和ClassLoaderImpl分别继承了JavaFileManager和ClassLoader,JavaFileManagerImpl管理了JavaFileObjectImpl(这是一个未编译的java文件对象),option存储的是编译参数。

public class JdkCompiler extends AbstractCompiler{

    private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    private DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector();

    private final JavaFileManagerImpl javaFileManager;

    private final ClassLoaderImpl classLoader;

    private volatile List<String> option;

    public JdkCompiler(){
        option = new ArrayList<>();
        option.add("-target");
        option.add("1.8");
        StandardJavaFileManager manager = compiler.getStandardFileManager(collector, null, null);
        final ClassLoader loader = Thread.currentThread().getContextClassLoader();
        if (loader instanceof URLClassLoader && (!loader.getClass().getName().equals("sun.misc.Launcher$AppClassLoader"))){
            try {
                URLClassLoader urlClassLoader = (URLClassLoader) loader;
                List<File> files = new ArrayList<>();
                for (URL url : urlClassLoader.getURLs()){
                    files.add(new File(url.getFile()));
                }
                manager.setLocation(StandardLocation.CLASS_PATH, files);
            } catch (IOException e){
                throw new IllegalStateException(e.getMessage(), e);
            }
        }
        classLoader = AccessController.doPrivileged((PrivilegedAction<ClassLoaderImpl>) () -> new ClassLoaderImpl(loader));
        javaFileManager = new JavaFileManagerImpl(manager, classLoader);
    }

    @Override
    protected Class<?> doCompile(String name, String source) throws Throwable {
        // get package name and class name
        int i = name.lastIndexOf(".");
        String pkg = i < 0 ? "" : name.substring(0, i);
        String cls = i < 0 ? "" : name.substring(i+1);
        JavaFileObject javaFileObject = new JavaFileObjectImpl(cls, source);
        javaFileManager.putFileForInput(StandardLocation.SOURCE_PATH, pkg, cls + ClassUtils.JAVA_EXTENSION, javaFileObject);
        // do compile task
        Boolean res = compiler.getTask(null, javaFileManager, collector, option, null, Arrays.asList(new JavaFileObject[]{javaFileObject})).call();
        if (res == null || !res.booleanValue()){
            throw new IllegalStateException("failed compile java source, class name is [" + name + "]" + "cause: [" + collector);
        }
        // 将编译好的类用classLoader加载后返回
        return classLoader.loadClass(name);
    }

    /**
     * make java source code to java source file
     */
    private static final class JavaFileObjectImpl extends SimpleJavaFileObject{

        private final CharSequence source;
        private ByteArrayOutputStream byteCode;

        public JavaFileObjectImpl(final String baseName, final CharSequence source){
            super(ClassUtils.toURI(baseName + ClassUtils.JAVA_EXTENSION), Kind.SOURCE);
            this.source = source;
        }

        JavaFileObjectImpl(final String name, final Kind kind){
            super(ClassUtils.toURI(name), kind);
            this.source = null;
        }

        /**
         * @param uri  the URI for this file object
         * @param kind the kind of this file object
         */
        public JavaFileObjectImpl(URI uri, Kind kind) {
            super(uri, kind);
            this.source = null;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
            if (Objects.isNull(source)){
                throw new UnsupportedOperationException("source is null");
            }
            return source;
        }

        @Override
        public InputStream openInputStream() throws IOException {
            return new ByteArrayInputStream(getByteCode());
        }

        @Override
        public OutputStream openOutputStream() throws IOException {
            return byteCode = new ByteArrayOutputStream();
        }

        public byte[] getByteCode() {
            return byteCode.toByteArray();
        }
    }

    private static final class JavaFileManagerImpl extends ForwardingJavaFileManager<JavaFileManager>{

        private final ClassLoaderImpl classLoader;

        private final Map<URI, JavaFileObject> fileObjects = new HashMap<>();

        public JavaFileManagerImpl(JavaFileManager fileManager, ClassLoaderImpl classLoader) {
            super(fileManager);
            this.classLoader = classLoader;
        }

        private URI uri(Location location, String packageName, String relativeName){
            return ClassUtils.toURI(location.getName() + "/" + packageName + "/" + relativeName);
        }

        public void putFileForInput(StandardLocation location, String packageName, String relativeName, JavaFileObject file){
            fileObjects.put(uri(location, packageName, relativeName), file);
        }

        @Override
        public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
            FileObject o = fileObjects.get(uri(location, packageName, relativeName));
            if (Objects.nonNull(o)){
                return o;
            }
            return super.getFileForInput(location, packageName, relativeName);
        }

        @Override
        public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject outputFile) throws IOException {
            JavaFileObjectImpl file = new JavaFileObjectImpl(className, kind);
            classLoader.add(className, file);
            return file;
        }

        @Override
        public ClassLoader getClassLoader(Location location) {
            return classLoader;
        }

        @Override
        public String inferBinaryName(Location location, JavaFileObject file) {
            if (file instanceof JavaFileObjectImpl){
                return file.getName();
            }

            return super.inferBinaryName(location, file);
        }

        @Override
        public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {

            Iterable<JavaFileObject> result = super.list(location, packageName, kinds, recurse);

            ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
            List<URL> urls = new ArrayList<>();
            Enumeration<URL> e = contextClassLoader.getResources("cn");
            while (e.hasMoreElements()){
                urls.add(e.nextElement());
            }

            ArrayList<JavaFileObject> files = new ArrayList<>();
            if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS)){
                files.stream().filter(o -> o.getKind() == JavaFileObject.Kind.CLASS && o.getName().startsWith(packageName)).forEach(o -> files.add(o));
            } else if (location == StandardLocation.SOURCE_PATH && kinds.contains(JavaFileObject.Kind.SOURCE)){
                files.stream().filter(o -> o.getKind() == JavaFileObject.Kind.SOURCE && o.getName().startsWith(packageName)).forEach(o -> files.add(o));
            }

            for (JavaFileObject object : result){
                files.add(object);
            }

            return files;
        }
    }

    private static final class ClassLoaderImpl extends ClassLoader{

        private final Map<String, JavaFileObject> classes = new HashMap<>();

        ClassLoaderImpl(final ClassLoader parentClassLoader){
            super((parentClassLoader));
        }

        Collection<JavaFileObject> files(){
            return Collections.unmodifiableCollection(classes.values());
        }

        void add(final String qualifiedClassName, final JavaFileObject javaFile){
            classes.put(qualifiedClassName, javaFile);
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            JavaFileObject javaFileObject = classes.get(name);
            if (javaFileObject != null){
                byte[] bytes = ((JavaFileObjectImpl) javaFileObject).getByteCode();
                return defineClass(name, bytes, 0, bytes.length);
            }
            try {
                return ClassHelper.forNameWithCallerClassLoader(name, getClass());
            }catch (ClassNotFoundException e){
                return super.findClass(name);
            }
        }

        @Override
        protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            return super.loadClass(name, resolve);
        }

        @Override
        public InputStream getResourceAsStream(String name) {
            if (name.endsWith(ClassUtils.CLASS_EXTENSION)){
                String qualifiedClassName = name.substring(0, ClassUtils.CLASS_EXTENSION.length()).replace("/", ".");
                JavaFileObjectImpl file = (JavaFileObjectImpl) classes.get(qualifiedClassName);
                if (Objects.nonNull(file)){
                    return new ByteArrayInputStream(file.getByteCode());
                }
            }
            return super.getResourceAsStream(name);
        }
    }
}

小结

dubbo核心采用的ExtensionLoader可以自由的加载和拓展组件,只需按照他的规范配置即可。compiler也是ExtensionLoader的一部分,在给定接口没有adaptive时会生成相应的代码,随后交给compiler进行编译,虽然compiler是ExtensionLoader的核心部分,但它仍然遵守ExtensionLoader的规范来进行加载,做到了非侵入式的使用,这一点思想确实值得学习。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 前面我们了解过了Java的SPI扩展机制,对于Java扩展机制的原理以及优缺点也有了大概的了解,这里继续深入一下D...
    加大装益达阅读 5,080评论 2 20
  • 以下内容均来自 梁飞 的个人博客 http://javatar.iteye.com/blog/1056664[ht...
    高广超阅读 5,158评论 1 53
  • 还记得,第一天送你们去幼儿园,爸爸和你们大手牵小手,走过楼头那几株紫红色的月季花丛,还有那片茂盛的三叶草地...
    小言米西阅读 308评论 0 0
  • 这段时间一直被“林妙可无缘北电”与“郑爽小号放飞自我”的八卦新闻刷屏,我很好奇的翻了翻网友的评论,怎么说?...
    MM帽檐阅读 385评论 0 2
  • 【同读一本书】 2016-03-23-040 《影响力》 【原文】 我们不应把注意力直接放在那些令我们对让人顺从的...
    杨平的阅读 254评论 6 2