【组件治理】raft终端组件框架设计文档

此文用于描述组件化框架

基本概念

package com.tencent.qq;

public class QQHelloService implements IHelloService {

    public  String sayHello(){
				print("Hello~");
		}

}

服务的导出:服务的实现类被打包存储在组件中,若组件的开发者希望进一步方便其服务的使用,则需要先对服务进行导出。导出过程中需要填写两个字段, 一个是 服务的名称, 另一个是 服务的实现类。服务的导出信息存储在组件的描述文件中。

exports-services:
	- name: HelloService # 服务的名称
	  class: com.tencent.qq.QQHelloService # 服务的实现类

服务的使用:服务由组件框架加载并管理,管理服务的具体实现类为ServiceManager。在使用服务时,需要先通过ServiceManager获取服务, 然后再调用服务的具体方法。

IHelloService helloService = g_ServiceManager.getService("HelloService"); // 获取服务
helloService.sayHello(); // 调用服务

组件的描述文件:因为组件是服务的集合,为了清晰的描述出组件中所包含的所有服务。同时描述出组件的基本信息,还需要 组件描述文件。如下组件的描述文件中包含了组件的基本信息,同时组件的描述文件中还导出了 QQAccount 和 QQLoginService 两个服务。

apiVersion: com.raft.uas/v1alpha1
platform: Android
kind: Component
name: tencent/QQAccount  # 非基础组件, 使用二级名称
version: 1.2.3

exports:
	- name: QQAccount
	  class: com.tencent.qq.chatRoom.QQAccount
		type: service

	- name: QQLoginService
	  class: com.qq.account.QQLoginService
		type: service #默认类型就是service, 后续会考虑 resource, config 等类型。

组件目录结构:组件的目录结构如下图所示, 包含组件的描述文件, 和组件的源代码(或二进制代码)。组件描述以文件形式伴随组件文件存在,但其并不强行入侵到组件的jar、dll包中(非入侵)。

└── QQAccount
    ├── raft.yml  # 组件描述文件
    └── src
        ├── QQAccount.java      # 导出的服务
        ├── QQLoginService.java # 导出的服务
        ├── QQUserDao.java
        └── QQUserItem.java

Application (应用程序)

应用程序是组件的集合,如下是一个应用程序的目录结构。 其中包含一个components目录。

同样每个应用也伴随有一个 raft.yml 文件,用于描述应用的基本信息和应用程序的版本。

此外, 在目录结构中也可以看出。 一个遵从raft标准的应用程序在components目录中,会存在一个名称为RaftFramework的特殊组件,即组件框架。

apiVersion: com.raft.uas/v1alpha1
platform: Android
kind: Application # Application 说明这是一个应用。

services:
	- name: ChatRoomService
	  class: com.tencent.wx.ChatRoomService

	- name: QQAccount
	  class: com.tencent.qq.chatRoom.QQAccount

	- name: QQLoginService
	  class: com.qq.account.QQLoginService

components:
	- name: tencent/WXChatRoom/1.0.1 # 使用WXChatRoom的1.0.1版本
	- name: tencent/QQAccount/latest # 最新版本
	- name: QQLog # 不加版本号默认为latest版本
public static void main(String []args){

		// 读取描述文件,并加载描述文件中所描述的组件。
    ServiceManager g_ServiceManager = new ServiceManager("raft.yml");

		// 查询服务
    //	- name: QQAccount
	  //    class: com.tencent.qq.chatRoom.QQAccount
    QQAccount qqAccount = (QQAccount)g_ServiceManager.getService("QQAccount");
    String userName = qqAccount.getUserName();
    System.out.println("UserName:" + userName);


    // 查询服务,并向上转型成对应的接口(IoC)
    // 	- name: QQLoginService
	  //    class: com.qq.account.QQLoginService
    ILog log = (ILog)g_ServiceManager.getService("QQLogService");
    log.e("Main", "error: this is a test information");


    // 查询以类厂方式导出的组件
    //	- name: ChatRoomService
		//    factory-class: com.qq.WXChatRoomFactory # 制定创建服务的工厂类
    //    factory-method: getInstance                # 这里使用工厂方法创建此类
    WXChatRoomService chatroom = (WXChatRoomService)g_ServiceManager.getService("ChatRoomService");
    chatroom.doSomeThing();

}

Application Framework (应用程序框架)

Service Interface (关键接口)

UIInterface

ServiceNameAware

InitializingBean

DisposableBean

BeanDefinitionRegistryPostProcessor

FactoryBean

BeanPostProcessor

class ApplicationServiceManager implements ServiceManager{

	public ApplicationServiceManager(String appSpecFilePath) {

		 // STEP1: 构造一个 ServiceFactory
		ServiceFactory serviceFactory = new ServiceFactory(); 

		// STEP2: 加载app的描述文件
		loadAppSpecFile(appSpecFilePath); 

		// STEP3: 初始化Services
		initServices();	
	}
	
  @override
	public void startServices() {
        Map lifecycleBeans = getLifecycleBeans();
        for (Iterator it = new LinkedHashSet(lifecycleBeans.keySet()).iterator(); it.hasNext();) {
            String beanName = (String) it.next();
            doStart(lifecycleBeans, beanName);
        }
        publishEvent(new ContextStartedEvent(this));
  }
	
	private void doStart(Map lifecycleBeans, String beanName) {
	    Lifecycle bean = (Lifecycle) lifecycleBeans.get(beanName);
	    if (bean != null) {
	        String[] dependenciesForBean = getBeanFactory().getDependenciesForBean(beanName); //找到依赖的类
	        for (int i = 0; i < dependenciesForBean.length; i++) {
	            doStart(lifecycleBeans, dependenciesForBean[i]);//递归调用找到依赖的类
	        }
	        if (!bean.isRunning()) {
	            bean.start();
	        }
	        lifecycleBeans.remove(beanName);
	    }
	}
	
	
}

Specification File (描述文件)

raft中通过 描述文件 准确的描述一个代码包的平台、版本、依赖的组件、导出服务等信息。描述文件采用YML格式, 并

关键字段

如下是一个组件的 描述文件

apiVersion: com.raft.uas/v1alpha1
platform: Android
kind: Component  # 如果是一个组件则这里为Component
name: QQ/Account
version: 1.0.0
description: 腾讯QQ账号的组件。提供账号信息的查询、登录等服务。

services:
	- name: AccountService
	  class: com.qq.account.AccountService

	- name: LoginService
	  class: com.qq.account.LoginService

	- name: OrderService
		factory-class: com.qq.OrderServiceFactory # 制定创建服务的工厂类
    factory-method: getInstance               # 这里使用工厂方法创建此类

	- name: ChatRoomService
		class: com.qq.account.AccountService
    config:
			- name : ChatRoomServer
				description: 聊天服务器的地址,可以是URL地址或者是IP。
			  type : string
				default: https://qq.chatroom.com

			- name : ChatRoomAppId
				description: 聊天室与服务器建立连接所用的key。在聊天组件官网申请。
				type : string
				default: JJ_mas891jvZ

			- name : ChatRoomTheme
				description: 聊天室UI的皮肤。
				type: string
				default: DarkTheme


	- name: SayHelloService
	  class: com.qq.account.sayHelloImple
		scope: singleton
		constructor-arg:  # 这里使用构造方法传参
		- name: who
			description: mongo username
      type: string
    - name: times
      defaultValue: 100
      type: integer

components:
	- name: tencent/WXChatRoom/1.0.1 # 依赖WXChatRoom的1.0.1版本
	- name: tencent/QQAccount/latest # 依赖最新版本
	- name: QQLog # 不加版本号默认为latest版本
	- name: MyTestComponent # 也可以依赖其他平台上的组件
		git: http://git.code.oa.com/raft-templates/generator-raft-android.git

组件化框架

组件的扫描与注册

配置加载

启动任务

在应用启动的时候,我们通常会有很多工作需要做,为了提高启动速度,我们会尽可能让这些工作并发进行。但这些工作之间可能存在前后依赖的关系,所以我们又需要想办法保证他们执行顺序的正确性。因此启动过程的设计应该着重考虑如下三个方面:

  • 依赖关系:配置启动任务的先后依赖关系。即一个任务的前置依赖任务,和后续任务,前置依赖和后续任务可以是一个或多个。

  • 并行能力:为了提升应用程序的启动速度,应该尽可能多的利用多核(多线程)能力加快启动速度。因此框架应该通过依赖关系和处理器核心数目,安排每个任务所处的线程。

  • 特定线程:对于线程有特殊要求的任务,如:某些启动任务必须在主线程执行。框架应该有能力指定启动任务所在的的线程。

针对以上需求, raft框架为启动框架抽象出StartupTask类。其主要声明如下:

public abstract class Task{

	// 任务名称
	public String mTaskName;
	
	// 前置任务和后继任务
	private List mSuccessorList = new ArrayList();
  protected Set mPredecessorSet = new HashSet();

	// 是否在主线程执行
	private boolean mInMainThread = false;

	// 任务状态
	public static final int STATE_IDLE = 0;
	public static final int STATE_RUNNING = 1;
	public static final int STATE_FINISHED = 2;
	public static final int STATE_WAIT = 3;
	private volatile int mCurrentState = STATE_IDLE;

	public Task(String taskName) {
        mTaskName= taskName;
    }

	// 抽象方法 用来填写task的具体实现。
	public abstract void run();
}
public class TaskGraph extends Task{

	// 构造器,以链式表达构造出TaskGraph对象
	public static class Builder {
			public Builder add(Task task){...}
			public Builder after(Task task){...}
	}
	
	//  内置两个特殊节点,用保证单入单出。
	public Task mStartTask, mFinishTask;

  public void setTaskStateListener(TaskStateListenerlistener);
	public void start();
	public void stop();
}
interface TaskStateListener{
	
	// 所有Task执行开始、结束时分别调用。
	onTasksStart();
	onTasksFinished();

	// 某一个task开始、结束时分别调用。
	onTaskStart(Task task);
	onTaskFinished(Task task);
} 

在一个应用程序中, 如果希望执行特定的操作,则需要进行如下操作:

  • 先声明一个Task,在task中加入特定的执行代码。

  • 将这个Task的实例化,并加入到TaskGraph中,加入时指定其依赖关系。

  • 最后执行TaskGraph。

以下是这三个步骤的实例代码:

public class TaskA extends Task{
        public TaskA() {
            super("TaskA");
        }

        @Override
        public void run() {
            Log.d(TAG, "run TaskA");
        }
}
Task a = new TaskA();
...
Task e = new TaskE();
e.mInMainThread = true; // 强制设定e在主线程中运行。

TaskListener listener = new TaskListener(){...};

TaskGraph.Builder builder = new TaskGraph.Builder();
builder.add(a); // 没有指定after则在mStartTask后执行
builder.add(b).after(a);
builder.add(c).after(a);
builder.add(d).after(b, c);
builder.add(e).after(a); // 因为e是最后一个节点,因此e之后为FinishTask。
TaskGraph group = builder.create();
group.setListener(listener)
group.start();

下图即上面代码的图形化表达。


由于参与到一个app启动过程中的任务会非常多,且开发者难以记住每个服务的名称。此时如果仅仅通过任务对象来简历依赖关系会非常麻烦。为此, raft框架提供了几个标准的初始化阶段(stage),用于简化依赖关系的配置。开发者只需要将task加入到对应的阶段中, 即可完成启动任务的依赖关系。这几个阶段分别是:

  • FrameworkInit:初始化Raft框架核心功能所的阶段。如Log,数据上报等。 一般Task不考虑放入此阶段。

  • BasicLibraryInit:初始化基础库的阶段,DB, Network等基础功能的初始化过程放入该阶段。

  • FunctionalComponentInit:初始化功能组件的阶段, 如视频播放出,图片查看器等功能性组件的处处华过程放入此阶段。

  • BusiniessComponentInit:业务组件初始化阶段, 如个人页,信息流等组件的初始化过程放入此阶段。

  • ReadyToStart:上述所有阶段执行完后的最后一个阶段。

// 框架会先初始化好这5个阶段。
TaskGraph.Builder builder = new TaskGraph.Builder();
builder.add(new Task("__FrameworkInit__"))
builder.add(new Task("__BasicLibraryInit__")).after("__FrameworkInit__");;
builder.add(new Task("__FunctionalComponentInit__")).after("__BasicLibraryInit__");;
builder.add(new Task("__BusiniessComponentInit__")).after("__FunctionalComponentInit__");;
builder.add(new Task("__ReadyToStart__")).after("__BusiniessComponentInit__");;

...

// 之后初始化任务可以直接用阶段的名称建立依赖关系。
builder.add(a).after("__FrameworkInit__"); 
builder.add(b).after("__BasicLibraryInit__");
builder.add(c).after("__BasicLibraryInit__");
builder.add(d).after("__FunctionalComponentInit__");
builder.add(e).after("__ReadyToStart__"); // 因为e是最后一个节点,因此e之后为FinishTask。

TaskGraph group = builder.create();
group.setListener(listener)
group.start();

框架的主要结构

Sample工程

Q&A

1、为什么一个组件需要暴露出多个服务?


发表评论

电子邮件地址不会被公开。 必填项已用*标注