我维护一个IntelliJ插件 ,可以改善编写Spock规范的体验。 这个项目的挑战是在单个代码库中支持多个不兼容的IntelliJ API版本。 回想起来,该解决方案很简单(这是狂野的适配器模式的一个示例),但最初它需要一些思想和示例。 我今天再次在代码中修复对新版本的支持 ,因此决定记录一下我最初是如何解决该问题的。
根本问题是我的编译代码可以在存在多个不同API版本的JVM运行时环境中加载。 我的解决方案是将项目分为四个部分:
- 一个主项目,它不依赖于任何变化的API调用,因此在所有API版本中都是兼容的。 主项目还具有根据其自身所在的运行时环境加载适当的适配器实现的代码。在这种情况下,我可以利用IntelliJ PicoContainer进行服务查找,但是反射API或依赖项注入也具有需要什么。
- 一组抽象适配器,为主项目提供使用的API。 该项目也不依赖于API版本之间不同的任何代码。
- 为每个受支持的API版本实现抽象适配器的类集。 每组适配器包装不断变化的API调用,并针对特定的API版本进行编译。
最简单的处理方法是重构API中的某些内容。 这也是实际上破坏了最后一个版本的原因。 我的主要代码需要com.intellij.lang.Language的Groovy实例。 该实例在IntelliJ 14中移动。
该代码一直持续到14岁,因此在这种情况下,我要添加一个新适配器。 在适配器模块中,我有一个抽象类LanguageLookup.java :
package com.cholick.idea.spock;import com.intellij.lang.Language;
import com.intellij.openapi.components.ServiceManager;public abstract class LanguageLookup {public static LanguageLookup getInstance() {return ServiceManager.getService(LanguageLookup.class);}public abstract Language groovy();
}
我支持的最低IntelliJ API版本是11。在11-13之间查找Groovy语言实例是恒定的,因此第一个具体适配器位于针对IntelliJ 11 API编译的模块中。 LanguageLookup11.java :
package com.cholick.idea.spock;import com.intellij.lang.Language;
import org.jetbrains.plugins.groovy.GroovyFileType;public class LanguageLookup11 extends LanguageLookup {public Language groovy() {return GroovyFileType.GROOVY_LANGUAGE;}
}
最新的API引入了重大变化,因此第二个具体适配器位于针对其API版本14编译的模块中。 LanguageLookup14.java :
package com.cholick.idea.spock;import com.intellij.lang.Language;
import org.jetbrains.plugins.groovy.GroovyLanguage;public class LanguageLookup14 extends LanguageLookup {public Language groovy() {return GroovyLanguage.INSTANCE;}
}
最后,主项目有一个SpockPluginLoader.java类,它根据加载的运行时API注册正确的适配器类(我省略了一些与示例无关的方法):
package com.cholick.idea.spock.adapter;import com.cholick.idea.spock.LanguageLookup;
import com.cholick.idea.spock.LanguageLookup11;
import com.cholick.idea.spock.LanguageLookup14;
import com.intellij.openapi.application.ApplicationInfo;
import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.components.impl.ComponentManagerImpl;
import org.jetbrains.annotations.NotNull;
import org.picocontainer.MutablePicoContainer;public class SpockPluginLoader implements ApplicationComponent {private ComponentManagerImpl componentManager;SpockPluginLoader(@NotNull ComponentManagerImpl componentManager) {this.componentManager = componentManager;}@Overridepublic void initComponent() {MutablePicoContainer picoContainer = componentManager.getPicoContainer();registerLanguageLookup(picoContainer);}private void registerLanguageLookup(MutablePicoContainer picoContainer) {if(isAtLeast14()) {picoContainer.registerComponentInstance(LanguageLookup.class.getName(), new LanguageLookup14());} else {picoContainer.registerComponentInstance(LanguageLookup.class.getName(), new LanguageLookup11());}}private IntelliJVersion getVersion() {int version = ApplicationInfo.getInstance().getBuild().getBaselineVersion();if (version >= 138) {return IntelliJVersion.V14;} else if (version >= 130) {return IntelliJVersion.V13;} else if (version >= 120) {return IntelliJVersion.V12;}return IntelliJVersion.V11;}private boolean isAtLeast14() {return getVersion().compareTo(IntelliJVersion.V14) >= 0;}enum IntelliJVersion {V11, V12, V13, V14}
}
最后,在需要Groovy com.intellij.lang.Language的代码中,我获得了LanguageLookup服务的所有权并调用其groovy方法:
...
Language groovy = LanguageLookup.getInstance().groovy();
if (PsiUtilBase.getLanguageAtOffset(file, offset).isKindOf(groovy)) {
...
该解决方案允许相同的已编译插件JAR跨版本11-14支持IntelliJ的各种API。 我以为Android开发人员通常会实现这样的解决方案,但是作为Web应用程序开发人员,我从来不必编写这种解决方案。
翻译自: https://www.javacodegeeks.com/2014/09/runtime-class-loading-to-support-a-changing-api.html