spring是如何启动容器的
常见的一种在本地使用main方法启动spring的方法
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-provider.xml"});
context.start();
...
//System.in.read(); // 按任意键退出
context.close();
}
dubbo是如何启动容器的
这个大家应该都知道,通过com.alibaba.dubbo.container.Main.main方法来启动的。
public class Main {
//在dubbo.properties中配置, 以配置dubbo.container=log4j,spring为例
public static final String CONTAINER_KEY = "dubbo.container";
public static final String SHUTDOWN_HOOK_KEY = "dubbo.shutdown.hook";
private static final Logger logger = LoggerFactory.getLogger(Main.class);
//整个dubbo,最先使用ExtensionLoader的地方
private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);
private static volatile boolean running = true;
public static void main(String[] args) {
try {
//1. 从dubbo.properties里面读取dubbo.container这个配置;
if (args == null || args.length == 0) {
String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName());
args = Constants.COMMA_SPLIT_PATTERN.split(config);
}
//2. 使用Container接口的ExtensionLoader中获取具体的Container实现类;
final List<Container> containers = new ArrayList<Container>();
//agrs中有两个值 "log4j,spring"
for (int i = 0; i < args.length; i++) {
containers.add(loader.getExtension(args[i]));
}
logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce.");
if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) {
//5. 当主线程被外部终止时,会触发 shutdownhook,执行Container的stop与close方法
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
for (Container container : containers) {
try {
container.stop();
logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!");
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
synchronized (Main.class) {
running = false;
//6.通知下面的锁操作,主线程正常走完代码,并最终停止。
Main.class.notify();
}
}
}
});
}
//3. 执行Container接口的start方法;
for (Container container : containers) {
container.start();
logger.info("Dubbo " + container.getClass().getSimpleName() + " started!");
}
System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Dubbo service server started!");
} catch (RuntimeException e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
System.exit(1);
}
//4. 用一个死循环,保留主线程;
synchronized (Main.class) {
while (running) {
try {
Main.class.wait();
} catch (Throwable e) {
}
}
}
}
}
dubbo容器的SPI功能实现
明确下面几个概念
- 扩展接口 com.alibaba.dubbo.container.Container
- 扩展配置 dubbo.container = log4j,spring
- 扩展实现
-
com.alibaba.dubbo.container.log4j.Log4jContainer
log4j的日志初始工作,当多进程启动时,做日志隔离
-
com.alibaba.dubbo.container.logback.LogbackContainer
logback的日志初始工作
-
com.alibaba.dubbo.container.spring.SpringContainer
spring容器的启动,使用spring容器来实现aop与ioc,**【这个配置,往往是必选的】**
-
com.alibaba.dubbo.container.jetty.JettyContainer
启动一个Servlet Web容器,提供了一个web页面,做一些监控之类的时期,注意:在写HttpResponse的时候,也是用SPI机制,不同的请
求页面经过PageServlet交个不同的PageHandler去实现
- com.alibaba.dubbo.monitor.simple.RegistryContainer
我们来想一个这样的问题,上面是dubbo支持的容器,包括log4j、logback、spring、jetty、registry,那么dubbo是如何通过配置的方式来实现容器的可扩展的呢?假如给你做你怎么做呢?
- spring的API(Application Programming Interface、应用编程接口)方式,接口多实现类的动态调动;
- JDK标准的SPI(Service Provider Interface、)机制
dubbo的扩展点加载机制是从JDK的spi机制加强而来。
dubbo改进了JDK标准的SPI机制以下问题: - spring与JDK的SPI都会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上,也会加载。
- JDK的SPI机制不支持Ioc与Aop功能,而dubbo中的扩展点可以直接setter注入其他扩展点。【这个一部分,下面会有涉及,我们会在下一个文章中详细描述】
扩展接口Container源码
关键说明,
- 必须带有SPI注解
- 注解里面的值,是默认实现,在ExtensionLoader源码去细讲。
/**
* Container. (SPI, Singleton, ThreadSafe)
*
* @author william.liangf
*/
@SPI("spring")
public interface Container {
/**
* start.
*/
void start();
/**
* stop.
*/
void stop();
}
ExtensionLoader源码
关键说明,
1. ExtensionLoader有一个private的构造函数,并通过getExtensionLoader这个镜头方法返回实例,是一个单例工厂类。
2. 一个扩展接口对应一个ExtensionLoader实例,也就是说最终我们加载了多少个扩展接口(注意是扩展接口,而不是扩展实现类),就多少个实例;
3. 关键static final变量,所有实例共享
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
4. 所有的final变量,单个实例共享,每一个扩展接口对应的ExtensionLoader都不一样
//扩展接口名称
private final Class<?> type;
//也是一个扩展接口,用于注入扩展接口中需要注入的类,实现dubbo的扩展点的自动注入
private final ExtensionFactory objectFactory;
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
private volatile Class<?> cachedAdaptiveClass = null;
private String cachedDefaultName;
private volatile Throwable createAdaptiveInstanceError;
private Set<Class<?>> cachedWrapperClasses;
private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();
结合Main类的使用,讲一下几个核心方法
核心方法 -> ExtensionLoader.getExtensionLoader
获得ExtensionLoader实例
private static final ExtensionLoader<Container> loader = ExtensionLoader.**getExtensionLoader**(Container.class);
获取ExtensionLoader实例
-
getExtensionLoader(Container.class)【将返回的实例放到EXTENSION_LOADERS变量中】
- new ExtensionLoader<T>(type) 【初始化type与objectFactory变量,初始化objectFactory变量的时候有一点点的绕。假如这个接口不是ExtensionFactory,就需要初始化这样的一个objectFactory,否则就需要,具体后面会将】
获取ExtensionLoader实例结束
核心方法 -> ExtensionLoader.getExtension
获得扩展实现
注意此时已经拿到了扩展接口Container对应的那个ExtensionLoader实例了,在下面的处理中,基本都是更新这个实例的变量,而很少会更新类变量了。
for (int i = 0; i < args.length; i++) {
containers.add(loader.getExtension(args[i]));
}
-
getExtension("log4j" or "spring" or "logback" ....)
-
createExtension("log4j" or "spring" or "logback" ....) --创建指定类型的扩展接口的instance
-
getExtensionClasses() --加载扩展接口的所有class文件
-
loadExtensionClasses() --扩展接口的所有的class文件
- loadFile() --从三个路径下,查找class文件
-
- clazz.newInstance() --创建指定class的instance
-
injectExtension(instace) --注入属性Ioc
- objectFactory.getExtension(pt, property) --反射的方式,解析setXxx(Xxx xxx)方法,注入Xxx实例
- injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); --对实例进行层层包装,最终返回一个包装过后的instance
-
-
上面总体逻辑就是
图片
具体介绍一下loadFile方法
//...
private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
//...
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadFile(extensionClasses, DUBBO_DIRECTORY);
loadFile(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
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 {
try {
clazz.getConstructor(type);
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} catch (NoSuchMethodException e) {
clazz.getConstructor();
if (name == null || name.length() == 0) {
name = findAnnotationName(clazz);
if (name == null || name.length() == 0) {
if (clazz.getSimpleName().length() > type.getSimpleName().length()
&& clazz.getSimpleName().endsWith(type.getSimpleName())) {
name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
} else {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
}
}
}
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);
}
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());
}
}
}
}
}
从上面三个路径下加载dubbo扩展点的配置。我们以DUBBO_INTERNAL_DIRECTORY路径下的配置文件为例,说明下dubbo下扩展的配置。
- 扩展接口实现类,实现Container接口,例如SpringContainer.java
- 在资源META-INF.dubbo.internal文件夹下,有一个以Container接口全路径名称为名字的文件;
- 上述文件名中内容格式为 {key}={value},key为扩展点实现类的配置名称,例如spring、log4j等;value为SpringContainer类的全路径名称
loadFile中就是以这样的规则,解析这样的配置文件,并放到extensionClasses这样的Map中返回,extensionClasses的key是这个{key},value是这个{value}对应的class。
这里面主要是四个逻辑,涉及到几种情况。
图片
拿到所有配置的Container实例
for (Container container : containers) {
container.start();
logger.info("Dubbo " + container.getClass().getSimpleName() + " started!");
}
执行SpringContainer.java的start方法
public void start() {
String configPath = ConfigUtils.getProperty(SPRING_CONFIG);
if (configPath == null || configPath.length() == 0) {
configPath = DEFAULT_SPRING_CONFIG;
}
context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+"));
context.start();
}
这个不是这篇文章最开始的那个问题的答案嘛,原来dubbo就是通过这么简单的方式的来启动spring容器的。这算是一个首尾呼应嘛~
终于,终于,第一篇文章写完了~ 下篇文章会讲解扩展点是如何IOC的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。