编译代码拓展点
由于dubbo本身的SPI特性,因此可以自由的加载其他的插件进来,但如果想加载的extension没有对应的实现类怎么办呢?没关系,dubbo能自动的给你生成代码并编译为一个class!以下着重讲解dubbo的Compiler模块
一、可拓展的compile设计
由于dubbo本身具有的ExtensionLoader特性,可使用类似与jdk自带的serviceloader来实现加载外部拓展组建,以下为compiler的设计
二、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的规范来进行加载,做到了非侵入式的使用,这一点思想确实值得学习。