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的规范来进行加载,做到了非侵入式的使用,这一点思想确实值得学习。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容

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