Apache Camel源碼研究之TypeConverter

在EIP的概念中,數據格式的轉換是不可避免的一環,而作爲EIP理論實現者的Apache Camel在多年的發展迭代中也是給出了多種解決方案,本文將集中討論其中的一種實現方法 —— TypeConverter。

1. 概述

TypeConverter作爲Apache Camel中實現自動數據格式轉換的主要方式,瞭解其底層實現邏輯對我們實現自定義TypeConverter,以及更好地使用該功能都是大有裨益地。

2. 源碼解讀

本小節分爲兩個部分。

2.1 初始化

首先讓我們看看Apache Camel如何初始化TypeConverter相關組件的。
初始化入口
在之前的一篇博客 Apache Camel源碼研究之啓動 中,我們提到 Camel在啓動時候會調用CamelContext.forceLazyInitialization();來將一些懶加載的基礎必要組件進行率先強制喚醒,而這其中正有本次的主角 —— TypeConverter組件。

通過上述截圖我們可以看到,DefaultTypeConverter組件覆寫了基類的doStart()方法,參與到Camel啓動生命週期中:

  1. 首先通過loadCoreTypeConverters();加載camel-core組件中定義的TypeConverter。(實現類CoreTypeConverterLoader[直接繼承自AnnotationTypeConverterLoader],通過覆寫基類的findPackageNames()方法指定掃描PACKAGE爲{"org.apache.camel.converter", "org.apache.camel.component.bean", "org.apache.camel.component.file"},好吧,其實往後看你們就知道,這個配置對於掃描@Converter並沒有起到作用,真正發揮作用的是CorePackageScanClassResolver類,其在構造函數中顯式進行了添加。)
  2. 然後通過loadTypeConverters();來加載外部組件擴展的TypeConverter。(實現類AnnotationTypeConverterLoader)。常見的外部擴展有:
    • org.apache.camel.component.jackson.converter.JacksonTypeConverters
    1. org.apache.camel.component.cxf.converter.CxfPayloadConverter
    2. org.apache.camel.spring.converter.ResourceConverter
    3. org.apache.camel.component.http.RequestEntityConverter
    4. org.apache.camel.component.exec.ExecResultConverter
    5. org.apache.camel.component.jetty.JettyConverter
    6. org.apache.camel.http.common.HttpConverter
    7. org.apache.camel.component.cxf.converter.CxfConverter

關於以上兩次加載,最終的實現邏輯都將歸結於load(TypeConverterRegistry registry)方法。


// AnnotationTypeConverterLoader.load()  實現自接口TypeConverterLoader
@Override
public void load(TypeConverterRegistry registry) throws TypeConverterLoaderException {
    String[] packageNames;
	
	// META_INF_SERVICES 值爲: "META-INF/services/org/apache/camel/TypeConverter" (相信部分讀者一定很眼熟)
    LOG.trace("Searching for {} services", META_INF_SERVICES);
    try {
        // 查找進行@Converter掃描的package
        // 子類 CoreTypeConverterLoader 正是通過覆寫該方法來實現"指定掃描路徑"的
        packageNames = findPackageNames();
        if (packageNames == null || packageNames.length == 0) {
            throw new TypeConverterLoaderException("Cannot find package names to be used for classpath scanning for annotated type converters.");
        }
    } catch (Exception e) {
        throw new TypeConverterLoaderException("Cannot find package names to be used for classpath scanning for annotated type converters.", e);
    }

    // if we only have camel-core on the classpath then we have already pre-loaded all its type converters
    // but we exposed the "org.apache.camel.core" package in camel-core. This ensures there is at least one
    // packageName to scan, which triggers the scanning process. That allows us to ensure that we look for
    // META-INF/services in all the JARs.
    if (packageNames.length == 1 && "org.apache.camel.core".equals(packageNames[0])) {
        LOG.debug("No additional package names found in classpath for annotated type converters.");
        // no additional package names found to load type converters so break out
        return;
    }

    // 過濾掉 對於 org.apache.camel.core PACKAGE的掃描.
    // now filter out org.apache.camel.core as its not needed anymore (it was just a dummy)
    packageNames = filterUnwantedPackage("org.apache.camel.core", packageNames);

    // 外部擴展者可能直接註冊Converter類, 而非PACKAGE, 這一步正是將這兩者進行拆分
    // 1. PACKAGE 作爲返回值
    // 2. CLASS 存放到作爲參數傳入的 classes 中.
    // filter out package names which can be loaded as a class directly so we avoid package scanning which
    // is much slower and does not work 100% in all runtime containers
    Set<Class<?>> classes = new HashSet<Class<?>>();
    packageNames = filterPackageNamesOnly(resolver, packageNames, classes);
    if (!classes.isEmpty()) {
        LOG.debug("Loaded " + classes.size() + " @Converter classes");
    }

    // 對上面過濾出的PACKAGE進行掃描, 找出被 @Converter 註解的類. 收集到上面定義的 classes 字段中
    // if there is any packages to scan and load @Converter classes, then do it
    if (packageNames != null && packageNames.length > 0) {
        LOG.trace("Found converter packages to scan: {}", packageNames);
        Set<Class<?>> scannedClasses = resolver.findAnnotated(Converter.class, packageNames);
        if (scannedClasses.isEmpty()) {
            throw new TypeConverterLoaderException("Cannot find any type converter classes from the following packages: " + Arrays.asList(packageNames));
        }
        LOG.debug("Found " + packageNames.length + " packages with " + scannedClasses.size() + " @Converter classes to load");
        classes.addAll(scannedClasses);
    }

    // 至此所有的Converter註冊類收集完畢,開始進行註冊TypeConverter的操作
    // load all the found classes into the type converter registry
    for (Class<?> type : classes) {
        if (LOG.isTraceEnabled()) {
            LOG.trace("Loading converter class: {}", ObjectHelper.name(type));
        }
        // 加載 Converter 方法, 詳見下方說明
        loadConverterMethods(registry, type);
    }

    // now clear the maps so we do not hold references
    visitedClasses.clear();
    visitedURIs.clear();
}

// ====================================================
// AnnotationTypeConverterLoader.loadConverterMethods()
protected void loadConverterMethods(TypeConverterRegistry registry, Class<?> type) {
    // 避免重複掃描
    if (visitedClasses.contains(type)) {
        return;
    }
    visitedClasses.add(type);
    try {
        Method[] methods = type.getDeclaredMethods();
        CachingInjector<?> injector = null;

        for (Method method : methods) {
            // 
            // this may be prone to ClassLoader or packaging problems when the same class is defined
            // in two different jars (as is the case sometimes with specs).
            // 註解 @Converter
            if (ObjectHelper.hasAnnotation(method, Converter.class, true)) {
                boolean allowNull = false;
                
                if (method.getAnnotation(Converter.class) != null) {
                    allowNull = method.getAnnotation(Converter.class).allowNull();
                }
                injector = handleHasConverterAnnotation(registry, type, injector, method, allowNull);
            } else if (ObjectHelper.hasAnnotation(method, FallbackConverter.class, true)) {// 註解 @FallbackConverter
                boolean allowNull = false;
                if (method.getAnnotation(FallbackConverter.class) != null) {
                    allowNull = method.getAnnotation(FallbackConverter.class).allowNull();
                }
                injector = handleHasFallbackConverterAnnotation(registry, type, injector, method, allowNull);
            }
        }

        // 遞歸處理基類
        Class<?> superclass = type.getSuperclass();
        if (superclass != null && !superclass.equals(Object.class)) {
            loadConverterMethods(registry, superclass);
        }
    } catch (NoClassDefFoundError e) {
        LOG.warn("Ignoring converter type: " + type.getCanonicalName() + " as a dependent class could not be found: " + e, e);
    }
}

// ====================================================
// AnnotationTypeConverterLoader.handleHasConverterAnnotation()
private CachingInjector<?> handleHasConverterAnnotation(TypeConverterRegistry registry, Class<?> type,
                                                        CachingInjector<?> injector, Method method, boolean allowNull) {
    // 合格的方法簽名:
    //  1. 有且只有一個參數
    //  2. 有兩個參數, 第二個參數類型爲Exchange
    //  3. 返回值不能爲Void
    if (isValidConverterMethod(method)) {
        int modifiers = method.getModifiers();
        // 方法不能爲abstract, 必須爲public
        if (isAbstract(modifiers) || !isPublic(modifiers)) {
            LOG.warn("Ignoring bad converter on type: " + type.getCanonicalName() + " method: " + method
                    + " as a converter method is not a public and concrete method");
        } else {
            Class<?> toType = method.getReturnType();
            // 返回值不能爲Void
            if (toType.equals(Void.class)) {
                LOG.warn("Ignoring bad converter on type: " + type.getCanonicalName() + " method: "
                        + method + " as a converter method returns a void method");
            } else {
                // 傳入參數中的第一個參數的類型爲 fromType
                // 返回值參數的類型爲 toType
                // 註冊爲 StaticMethodTypeConverter 實例
                Class<?> fromType = method.getParameterTypes()[0];
                if (isStatic(modifiers)) {
                    // 這裏會調用 `BaseTypeConverterRegistry.addTypeConverter`方法來將StaticMethodTypeConverter實例註冊到 BaseTypeConverterRegistry實例的typeMappings字段中(ConcurrentMap<TypeMapping, TypeConverter>類型)
                    // 而上述typeMappings字段類型中的KEY值爲TypeMapping類型, 其不出意料地覆寫了hashCode和equal方法 
                    // 而更重要的是 BaseTypeConverterRegistry.addTypeConverter() 方法中,對於重複性的TypeMapping, 默認情況下是覆蓋Override(即後註冊的同類型Converter將覆前面註冊的), 但可以通過配置 context.getTypeConverterRegistry().setTypeConverterExists 來更改 
                    registerTypeConverter(registry, method, toType, fromType,
                            new StaticMethodTypeConverter(method, allowNull));
                } else {
                    if (injector == null) {
                        injector = new CachingInjector<Object>(registry, CastUtils.cast(type, Object.class));
                    }
                    registerTypeConverter(registry, method, toType, fromType,
                            new InstanceMethodTypeConverter(injector, method, registry, allowNull));
                }
            }
        }
    } else {
        LOG.warn("Ignoring bad converter on type: " + type.getCanonicalName() + " method: " + method
                + " as a converter method should have one parameter");
    }
    return injector;
}

以上,就是Apache Camel如何在初始化時候將TypeConverter相關組件加載組裝完畢。

2.2 運行時

對於註冊的 TypeConverter如何生效,Apache Camel提供瞭如下兩種方式:

// 
context.getTypeConverter().convertTo(String.class, file);
// 其實最終還是回調了上述方法
message.getBody(String.class); 

關於相關源碼,感興趣的讀者可以參見 BaseTypeConverterRegistry.doConvertTo(final Class<?> type, final Exchange exchange, final Object value, final boolean tryConvert) 方法,因爲涉及到的內容較多,貼出有點湊字數嫌疑。Apache Camel作了非常細緻的處理,將可以做到的轉換基本做到了極致。對於BaseTypeConverterRegistry.doConvertTo中的四個參數:

  1. 第一個參數爲將要轉換到的類型
  2. 第三個參數爲將要被轉換的數據。

3. 自定義擴展

最後我們來寫個自定義的擴展示例:

  1. 編寫如下代碼:
@Converter
public class TypeConverter_Tests extends CamelTestSupport {
	@Test
	public void customConvert() throws Exception {
		CamelTestUtil.defaultPrepareTest2(new RouteBuilder() {
			@SuppressWarnings("deprecation")
			@Override
			public void configure() throws Exception {
				from("stream:in?promptMessage=Enter something:")
				.setBody(constant(new Person()))
				.convertBodyTo(String.class)
				.to("stream:err");
			}
		});
	}

// =================================
	@Converter
	public static String toStr(Person person) throws IOException {
		return "111"+ person.toString();
	}
}
  1. 接着在 META-INF/services/org/apache/camel/TypeConverter文件中填入 TypeConverter_Tests類的完整命名 xxx.yyy.zzz.TypeConverter_Tests
  2. 執行單元測試,結果如下:
    結果

4. Links

  1. Office Site
  2. 《Apache Camel Developer’s CookBook》 P104
  3. 《Camel In Action》 P88
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章