For example:
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
First of all, analyze the getExtensionLoader method: it is very simple to query whether the extension "loaders" exist or not, and then new returns
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null) { throw new IllegalArgumentException("Extension type == null"); } if (!type.isInterface()) { throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); } if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!"); } ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }
Reanalyze the getadapteextension method (get an extension class)
public T getAdaptiveExtension() { //From cache checking Object instance = cachedAdaptiveInstance.get(); if (instance == null) { if (createAdaptiveInstanceError == null) { synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { try { //Create one if you can't find it instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { createAdaptiveInstanceError = t; throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t); } } } } else { throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } } return (T) instance; }
private T createAdaptiveExtension() { try { //First, analyze getAdaptiveExtensionClass //Second, analyze the injectExtension return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e); } }
private Class<?> getAdaptiveExtensionClass() { //Get xtensionClasses getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
private Map<String, Class<?>> getExtensionClasses() { //Or fetch from cache first Map<String, Class<?>> classes = cachedClasses.get(); if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { //Load it if you can't get it classes = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes; }
// synchronized in getExtensionClasses //The load configuration items are mainly under these paths //META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol //META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol //META-INF/services/ com.alibaba.dubbo.rpc.Protocol private Map<String, Class<?>> loadExtensionClasses() { final SPI defaultAnnotation = type.getAnnotation(SPI.class); if (defaultAnnotation != null) { String value = defaultAnnotation.value(); if ((value = value.trim()).length() > 0) { String[] names = NAME_SEPARATOR.split(value); if (names.length > 1) { throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names)); } if (names.length == 1) { cachedDefaultName = names[0]; } } } Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); //Load profile and return to loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName()); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); return extensionClasses; }
//Create this class private Class<?> createAdaptiveExtensionClass() { String code = createAdaptiveExtensionClassCode(); ClassLoader classLoader = findClassLoader(); org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); return compiler.compile(code, classLoader); }
//Injection attribute private T injectExtension(T instance) { try { if (objectFactory != null) { //Traversal method injection properties for (Method method : instance.getClass().getMethods()) { if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) { /** * Check {@link DisableInject} to see if we need auto injection for this property */ if (method.getAnnotation(DisableInject.class) != null) { continue; } Class<?> pt = method.getParameterTypes()[0]; try { String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : ""; //Here is an ioc implementation //Get objects from objectFactory and inject them into instance Object object = objectFactory.getExtension(pt, property); if (object != null) { method.invoke(instance, object); } } catch (Exception e) { logger.error("fail to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e); } } } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance; }
The final obtained classes are as follows
package com.alibaba.dubbo.rpc; import com.alibaba.dubbo.common.extension.ExtensionLoader; /** * Protocol adaptive class */ public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol { public void destroy() { throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"); } public int getDefaultPort() { throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"); } /** * Service Citation */ public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException { if (arg1 == null) throw new IllegalArgumentException("url == null"); com.alibaba.dubbo.common.URL url = arg1; String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); // Find the specific adaption class according to extName, then call the method. com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.refer(arg0, arg1); } /** * Service exposure */ public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException { if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null"); if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null"); com.alibaba.dubbo.common.URL url = arg0.getUrl(); String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); // Find the specific adaption class according to extName, then call the method. com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.export(arg0); } }
End of getting adaptive class----------------------------------------------------------------------------------
There are ioc processes in the above processes. Under this analysis
The ExtensionLoader initialization code is as follows
private ExtensionLoader(Class<?> type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
Here we analyze the objectFactory
If the configuration file / META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory in the project is
spring=org.apache.dubbo.config.spring.extension.SpringExtensionFactory
Then the above code to get the attribute injection is as follows
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.dubbo.config.spring.extension; import org.apache.dubbo.common.extension.ExtensionFactory; import org.apache.dubbo.common.extension.SPI; import org.apache.dubbo.common.logger.Logger; import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.common.utils.ConcurrentHashSet; import org.apache.dubbo.config.DubboShutdownHook; import org.apache.dubbo.config.spring.util.BeanFactoryUtils; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; import java.util.Set; /** * SpringExtensionFactory */ public class SpringExtensionFactory implements ExtensionFactory { private static final Logger logger = LoggerFactory.getLogger(SpringExtensionFactory.class); private static final Set<ApplicationContext> contexts = new ConcurrentHashSet<ApplicationContext>(); private static final ApplicationListener shutdownHookListener = new ShutdownHookListener(); public static void addApplicationContext(ApplicationContext context) { contexts.add(context); BeanFactoryUtils.addApplicationListener(context, shutdownHookListener); } public static void removeApplicationContext(ApplicationContext context) { contexts.remove(context); } public static Set<ApplicationContext> getContexts() { return contexts; } // currently for test purpose public static void clearContexts() { contexts.clear(); } @Override @SuppressWarnings("unchecked") public <T> T getExtension(Class<T> type, String name) { //SPI should be get from SpiExtensionFactory if (type.isInterface() && type.isAnnotationPresent(SPI.class)) { return null; } for (ApplicationContext context : contexts) { if (context.containsBean(name)) { Object bean = context.getBean(name); if (type.isInstance(bean)) { return (T) bean; } } } logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName()); if (Object.class == type) { return null; } for (ApplicationContext context : contexts) { try { return context.getBean(type); } catch (NoUniqueBeanDefinitionException multiBeanExe) { logger.warn("Find more than 1 spring extensions (beans) of type " + type.getName() + ", will stop auto injection. Please make sure you have specified the concrete parameter type and there's only one extension of that type."); } catch (NoSuchBeanDefinitionException noBeanExe) { if (logger.isDebugEnabled()) { logger.debug("Error when get spring extension(bean) for type:" + type.getName(), noBeanExe); } } } logger.warn("No spring extension (bean) named:" + name + ", type:" + type.getName() + " found, stop get bean."); return null; } private static class ShutdownHookListener implements ApplicationListener { @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ContextClosedEvent) { // we call it anyway since dubbo shutdown hook make sure its destroyAll() is re-entrant. // pls. note we should not remove dubbo shutdown hook when spring framework is present, this is because // its shutdown hook may not be installed. DubboShutdownHook shutdownHook = DubboShutdownHook.getDubboShutdownHook(); shutdownHook.destroyAll(); } } } }
That is, get the bean directly from the spring container and inject it into the property.
Through the obtained Protocol$Adaptive class, we found the getExtension method, which is the core method we analyzed. We get different implementation classes according to name
public T getExtension(String name) { if (name == null || name.length() == 0) { throw new IllegalArgumentException("Extension name == null"); } if ("true".equals(name)) { return getDefaultExtension(); } //From cache checking Holder<Object> holder = cachedInstances.get(name); if (holder == null) { cachedInstances.putIfAbsent(name, new Holder<Object>()); holder = cachedInstances.get(name); } Object instance = holder.get(); if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { //Create an Extension class instance = createExtension(name); holder.set(instance); } } } return (T) instance; }
private T createExtension(String name) { //Getting the class class is generated by reflection when loading the configuration file and saved in the cache //key:name value:class Class<?> clazz = getExtensionClasses().get(name); if (clazz == null) { throw findException(name); } try { //Get examples T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { //Get by newInstance EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } //ioc injection properties injectExtension(instance); //The following is the implementation loop of aop to implement layer by layer calls Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && !wrapperClasses.isEmpty()) { for (Class<?> wrapperClass : wrapperClasses) { //Assemble instance into wrapper class to realize aop function //Injecting properties into a wrapper class instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type + ") could not be instantiated: " + t.getMessage(), t); } }
ioc module has been analyzed above. Here we focus on the implementation of aop.
The cachedWrapperClasses is obtained when the configuration file is loaded
org.apache.dubbo.common.extension.ExtensionLoader#loadDirectory
-->org.apache.dubbo.common.extension.ExtensionLoader#loadResource
-->org.apache.dubbo.common.extension.ExtensionLoader#loadClass
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { if (!type.isAssignableFrom(clazz)) { throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface."); } if (clazz.isAnnotationPresent(Adaptive.class)) { if (cachedAdaptiveClass == null) { cachedAdaptiveClass = clazz; } else if (!cachedAdaptiveClass.equals(clazz)) { throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName()); } } else if (isWrapperClass(clazz)) { //wrappers are initialized here Set<Class<?>> wrappers = cachedWrapperClasses; if (wrappers == null) { cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); wrappers = cachedWrapperClasses; } wrappers.add(clazz); } else { clazz.getConstructor(); if (name == null || name.length() == 0) { name = findAnnotationName(clazz); if (name.length() == 0) { throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); } } String[] names = NAME_SEPARATOR.split(name); if (names != null && names.length > 0) { Activate activate = clazz.getAnnotation(Activate.class); if (activate != null) { cachedActivates.put(names[0], activate); } else { // support com.alibaba.dubbo.common.extension.Activate com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class); if (oldActivate != null) { cachedActivates.put(names[0], oldActivate); } } for (String n : names) { if (!cachedNames.containsKey(clazz)) { cachedNames.put(clazz, n); } Class<?> c = extensionClasses.get(n); if (c == null) { extensionClasses.put(n, clazz); } else if (c != clazz) { throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName()); } } } } }
Example:
Protocol$Adaptive from the above process. If the export method is invoked in the program, it actually calls the org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#export method.