最近写了一个小的异步框架,顺便认真研究了下一个请求处理的内部流程,所以这篇文章是一个衍生品。我们的分析从pipeline开始,前面的filter到pipeline的过程就略过了!
整体简化版流程
 
我们以一个screen的处理为例来说明整体的处理流程,如下图所示:
在深入每一步的细节之前,我们先了解一些背景知识。
target
官方解释:target是一个抽象的概念,指明当前请求要完成的任务。Target由pipeline来解释,它可能被解释成模板名,也可能被解释成别的东西。
 
module
 
官方解释:在Webx Turbine中,module是指screen、action、control等,大致相当于其它框架中的action或者controller。
对于webx来说它是能被框架处理的独立单元,一个请求最终会被映射到某个module进行处理。
module在webx中被定义成了接口,这意味着我们可以实现module来扩展自定义module,例如webx-rpc框架就定制了自己的
RPCModule来处理异步请求。
好了,开始步入正题!
分析URL
分析url,最主要的目的是通过url得到target,这一过程需要依赖mappingRuleService来完成。
MappingRuleService
类图(缺失,原网页就看不了):
对应的sample配置如下:
 <services:mapping-rules xmlns=“http://www.alibaba.com/schema/services/mapping-rules” >
 
        <!– External target name => Internal target name –>
        <extension-rule id =“extension.input”>
            <!– 默认后缀 –>
            <mapping extension =“” to=“” />
 
            <!– JSP –>
            <mapping extension =“jhtml” to=“” />
            <mapping extension =“jsp” to=“” />
            <mapping extension =“php” to=“” />
 
            <!– Velocity –>
            <mapping extension =“htm” to=“” />
            <mapping extension =“vhtml” to=“” />
            <mapping extension =“vm” to=“” />
        </extension-rule>
 
        <!– Internal target name => External target name –>
        <extension-rule id =“extension.output”>
            <!– 默认后缀 –>
            <mapping extension =“” to=“htm” />
 
            <!– JSP –>
            <mapping extension =“jhtml” to=“jhtml” />
            <mapping extension =“jsp” to=“jhtml” />
            <mapping extension =“php” to=“jhtml” />
 
            <!– Velocity –>
            <mapping extension =“htm” to=“htm” />
            <mapping extension =“vhtml” to=“htm” />
            <mapping extension =“vm” to=“htm” />
        </extension-rule>
 
        <!– Target name => Action module name –>
        <direct-module-rule id =“action” />
 
        <!– Target name => Screen module name (*.do) –>
        <direct-module-rule id =“screen.notemplate” />
 
        <!– Target name => Screen module name (*.jsp, *.vm) –>
        <fallback-module-rule id =“screen” moduleType= “screen” />
 
        <!– Target name => Screen template name –>
        <direct-template-rule id =“screen.template” templatePrefix=“screen” />
 
        <!– Target name => Layout template name –>
        <fallback-template-rule id =“layout.template” templatePrefix=“layout” />
 
        <!– Target name => Control module name (setControl method) –>
        <direct-module-rule id =“control.notemplate” />
 
        <!– Target name => Control module name (setTemplate method) –>
        <fallback-module-rule id =“control” moduleType= “control” />
 
        <!– Target name => Control template name –>
        <direct-template-rule id =“control.template” templatePrefix=“control” />
 
    </services:mapping-rules>
mappingRuleService 通过一系列的mappingRule来完成映射工作。比如,url的分析就是通过这段
        <!– External target name => Internal target name –>
        <extension-rule id = “extension.input”>
            <!– 默认后缀 –>
            <mapping extension = “” to= “” />
 
            <!– JSP –>
            <mapping extension = “jhtml” to= “” />
            <mapping extension = “jsp” to= “” />
            <mapping extension = “php” to= “” />
 
            <!– Velocity –>
            <mapping extension = “htm” to= “” />
            <mapping extension = “vhtml” to= “” />
            <mapping extension = “vm” to= “” />
        </extension-rule>
来完成的。后面这些rule被用到的时候我们再去分析它们的作用。
moduleLoaderService的配置与初始化
 
target映射到module是通过moduleLoaderService来完成的。
我们不妨先看一段moduleLoaderService的配置
    <services:module-loader>
        <ml-factories:class-modules>
            <ml-factories:search-packages type =“$1″ packages=“com.tmall.tmcrm.admin.module.*” />
        </ml-factories:class-modules>
         <ml-adapters:adapter class=“com.alibaba.citrus.extension.rpc.integration.RPCModuleAdapterFactory” />
    </services:module-loader>
xml文件中定义了namespace
xmlns:ml-adapters=“http://www.alibaba.com/schema/services/module-loader/adapters”
xmlns:ml-factories=“http://www.alibaba.com/schema/services/module-loader/factories”
所以事实上adapters和factories都是module-loader的捐献(donation)
对应的parser文件分别是
文件名:services-module-loader-adapters.bean-definition-parsers
内容:
action-event-adapter=com.alibaba.citrus.service.moduleloader.impl.adapter.ActionEventAdapterFactoryDefinitionParser
data-binding-adapter=com.alibaba.citrus.service.moduleloader.impl.adapter.DataBindingAdapterFactoryDefinitionParser
文件名:services-module-loader-factories.bean-definition-parsers
内容:
class-modules=com.alibaba.citrus.service.moduleloader.impl.factory.ClassModuleFactoryDefinitionParser
script-modules=com.alibaba.citrus.service.moduleloader.impl.factory.ScriptModuleFactoryDefinitionParser

moduleFactory用来加载和生成module,而adaptor用来将module适配到对应的module类型,例如screen,action.
webx默认使用的moduleFactory是com.alibaba.citrus.service.moduleloader.impl.factory.ClassModuleFactory
它的definitionParser除了解析bean的定义之外,还完成了一件重要的初始化工作:
protected void postProcessItems(Element element, ParserContext parserContext, BeanDefinitionBuilder builder,
Map <String, ParsingModuleInfo> items, String tags) {
// 注册所有明确定义的beans
for ( ParsingModuleInfo item : items .values()) {
if ( item.bd != null ) {
assertNotNull (item. key, “Specificly-defined module could not be found in %s: %s”, tags, item .itemName);
item .beanName = SpringExtUtil.generateBeanName(item.getBaseBeanName (), parserContext.getRegistry());
parserContext.getRegistry().registerBeanDefinition(item .beanName, item.bd);
}
}
// 设置ModuleFactory.setModules()
List <Object> moduleList = createManagedList (element, parserContext);
log .debug( “Defined {} modules with {}” , items. size(), getBeanClass(null).getSimpleName());
for ( ParsingModuleInfo item : items .values()) {
if ( item.beanName != null ) {
BeanDefinitionBuilder bdb = BeanDefinitionBuilder. genericBeanDefinition(ModuleInfo .class);
bdb .addConstructorArgValue(item.key );
bdb .addConstructorArgValue(item.beanName );
bdb .addConstructorArgValue(item.itemName ); // className or script resourceName
moduleList .add( bdb.getBeanDefinition ());
log .debug( “Defined module {} with {}” , new Object[] { item.key, item.itemName });
}
}
builder .addPropertyValue(“modules” , moduleList);
}
 
这段代码完成了两件事:
第一件事,把扫描的每个module都生成一个beanDefinition(如果解析出来的beanDefinition不为空的话),并注册到BeanDefinitionRegistry。

第二件事,把所有的module的beanDefinition放在一个数组里,并添加到BeanDefinitionBuilder中,后续再注册到BeanDefinitionRegistry。
moduleLoaderService 的definitionParser除了读取配置之外,也做了额外的一件事:
if (includeDefaultAdapters) {
// default adapter: action event adapter
addDefaultAdapter (adapterList, ActionEventAdapterFactory. class);
// default adapter: data binding adapter
addDefaultAdapter (adapterList, DataBindingAdapterFactory. class);
}
 
对用的xsd默认includeDefaultAdapters 为true,所有默认就添加了两个ActionEventAdapterFactory和DataBindingAdapterFactory 这两个AdaptorFactory

映射module

 
moduleFactory扫描了所有的module,并生成一个数组放到beanDefinition。当一个请求过来以后,我们会根据当前请求参数的名称获取对用的moduleName。
例如screen解析moduleName的方式如下:
/**
* 根据target取得 screen模块名。子类可以修改映射规则。
*/
protected String getModuleName (String target) {
return mappingRuleService. getMappedName(SCREEN_MODULE_NO_TEMPLATE , target);
}
 
这里用到了mappingRuleService里对应的这段配置
     <!– Target name => Screen module name (*.do) –>
        <direct-module-rule id =“screen.notemplate” />
对应的实现类是DirectModuleMappingRule,它是最简单模块映射规则。
  1. "/"替换成"."
  2. 除去文件名后缀。
  3. 将最后一个单词首字母改成大写,以符合模块命名的规则。
  4. 该rule默认不cache结果。

例如:将模板名:"about/directions/driving.vm"映射到screen module: "about.directions.Driving"

action解析moduleName的方式如下:
action = mappingRuleService .getMappedName(ACTION_MODULE, action );
 
对应的mappingRuleService 配置

    <!– Target name => Action module name –>
         <direct-module-rule id =“action” />
它的工作方式与screen module映射方式类似,只不过参数不是来自于target而是来自于请求参数里的action参数(可配置参数名)。
查找module的时候,我们除了提供moduleName,我们还需要提供module type。module type的值取决于我们在moduleLoaderService中配置:
        <ml-factories:class-modules>
            <ml-factories:search-packages type =“$1″ packages=“com.tmall.tmcrm.admin.module.*” />
        </ml-factories:class-modules>
也就说出现在package 路径module单词后面的第一个单词将作为type的值。
这当然只是一种配置方式,如果你有兴趣可以阅读对应的xsd文件,位置在 /META-INF/services/module-loader/factories/class-modules.xsd,你完全可以通过修改配置来实现type的另一个定义。
接下来就要真正开始查找module了。我们在moduleLoaderService的配置和初始化中提到,definitionParser在解析过程中做了一步工作,就是把ModuleList注册到beanDefinition中去。
ModuleFactory则是这个list的直接使用者,只不过它对数据又做了一次封装:
public void setModules (ModuleInfo[] modules) {
this.modules = createHashMap();
if ( modules != null) {
for ( ModuleInfo module : modules ) {
Map <String, ModuleInfo> typedModules = this.modules .get( module.getKey ().getModuleType());
if ( typedModules == null) {
typedModules = createHashMap();
this.modules .put( module.getKey ().getModuleType(), typedModules);
}
typedModules .put( module.getKey ().getModuleName(), module);
}
}
}
 
也就是按类型存放了一下。
适配module
 
module的适配是一个很有背景的话题,我们知道在webx2中所有的screen,action和control等module事实上必须实现module接口,也即是:
/**
* 代表一个模块。
*
@author Michael Zhou
*/
public interface Module {
/**
* 执行模块。
*/
void execute () throws Exception;
}
 
如果你的module实现了这个接口,那么事实上我们也不需要做适配了。但是这种设计的诟病在于
  • 约束了代码的灵活性
  • 参数的获取变得非常麻烦
webx3对此做了改进,module pojo化了,也就是说module可以是任意pojo对象。这么做的结果当然让开发者更为舒适,同时扩展性也变好了。
但是想要灵活没错,但总归还得让框架能认识它,怎么做呢?答案是适配!
适配器要做的工作简单来说归结为两点
  • 判断类型,决定是否要进行适配
  • 实现module接口中定义execute方法
判断类型意味着我们可以针对不同的module类型进行不同的适配,不同的适配器只关注自己的module
实现module接口除了满足框架的身份要求外,我们还可以在这个过程中添加自己的逻辑,例如:参数绑定(下一部分会进行详解)。
如类图所示适配的过程其实是根据module类型找到对应的AdaptorFactory并生成对应的adapter.
值得注意的是:(缺失图片,原网页就看不了)
  1. adapter中封装了MethodInvoker对象(通过分析方法的参数生成的)。
  2. 在DataBindingAdapter中由于只定义了一个execute方法(对应screen module),它是以一个对象存在,而在actionEventAdapter中则以map形似存在(对应action module的多个doxx方法)。
  3. methodInvoker中封装了方法中每个参数对应的DataResolver。
这些都为参数绑定做好了准备。
参数绑定与业务方法执行
 
参数绑定是通过dataResolverService来完成的,首先看一段sample配置
    <!– 支持注入参数。 –>
    <services:data-resolver xmlns=“http://www.alibaba.com/schema/services/data-resolver/factories” >
        <turbine-rundata-resolver />
        <parameter-resolver />
        <form-resolver />
        <!– 自定义的data-resolver –>
        <custom-resolver />
    </services:data-resolver>
除了最后一个,其它的默认都是webx3默认提供的,我们来看一下它的类图结构(缺失图片,原网页就看不了)
说明: DataResolver的实现类实在太多,所以上图并没有把它的实现类画出来。、
适配工作完成后,我们得到了一个module,接下来执行它的execute方法。
以screen module为例,事实上它执行的是DataBindingAdapter的execute,由于这个类整体很简单,我们把代码都贴出来
public class DataBindingAdapter extends AbstractDataBindingAdapter {
private final MethodInvoker executeMethod ;
DataBindingAdapter (Object moduleObject, MethodInvoker executeMethod) {
super(moduleObject );
this.executeMethod = executeMethod;
}
public void execute () throws Exception {
executeMethod .invoke( moduleObject, log );
}
@Override
public String toString () {
MapBuilder mb = new MapBuilder();
mb .append( “moduleClass”, moduleObject .getClass().getName());
mb .append( “executeMethod”, executeMethod );
return new ToStringBuilder(). append(getClass ().getSimpleName()).append(mb ).toString();
}
}

真正处理业务逻辑的其实是methodInvoker的invoke方法
public void invoke(Object moduleObject , Logger log) throws Exception {
Object [] args = new Object[resolvers .length];
for ( int i = 0 ; i < args.length ; i++) {
Object value ;
try {
value = resolvers[i].resolve ();
} catch (SkipModuleExecutionException e) {
if ( skippable) {
log .debug( “Module execution has been skipped. Method: {}, {}”, fastMethod, e .getMessage());
return;
}
value = e. getValueForNonSkippable();
}
// 特别处理:防止对primitive类型设置 null
Class <?> paramType = fastMethod.getJavaMethod().getParameterTypes()[];
if ( value == null && paramType.isPrimitive ()) {
value = getPrimitiveDefaultValue(paramType);
}
args [i] = value;
}
try {
fastMethod .invoke( moduleObject, args );
} catch (InvocationTargetException e) {
throwExceptionOrError (e. getCause());
return;
}
}
 
这段代码简单来说干了两件事情:
  • 为方法的每个参数绑定值
  • 执行业务方法
action module的逻辑稍微复杂一些。它需要查找对应doxx方法,另外如果你在action里提供了beforeExecution 或者afterExecution方法 它会在业务代码执行前后执行这两个方法。
定制自己的dataResolver
尽管webx3为我们提供了很多DataResolver,如下图:(缺失图片,原网页就看不了)

但是很多情况下,这些resolver还是不够用,我们还需要定制自己的DataResolver. 那么如何定义自己的dataResolver?
步骤一,编写自定义类实现DataResolverFactory接口。
webx3会扫描所有的DataResolverFactory寻找第一个合适的DataResolver,我们无法修改webx3现有的DataResolverFactory的实现类,所以只能自己先添加一个
步骤二,编写自己的类实现DataResolver接口。
最常用的两个方式,一种是通过annotation的方式匹配DataResolver,另一种是通过参数类型的方式匹配参数
步骤三,编写webx3 donation
1.编写{donation}.xsd,并放到META-INF/services/data-resolver/factories/ 目录下
例如:
(缺失图片,原网页就看不了)
 
2.在META-INF目录下新增services-data-resolver-factories.bean-definition-parsers文件,并配置对应的映射
例如:
步骤四,根据添加的donation,修改DataResolverService的配置(缺失图片,原网页就看不了)
例如红色部分:
    <!– 支持注入参数。 –>
    <services:data-resolver xmlns=“http://www.alibaba.com/schema/services/data-resolver/factories” >
        <turbine-rundata-resolver />
        <parameter-resolver />
        <form-resolver />
        <!– 自定义的data-resolver –>
        <custom-resolver />
    </services:data-resolver>

sample code:
public class CustomResolverFactory implements DataResolverFactory {
@Autowired
private UicReadServiceClient       uicReadServiceClient ;
@Autowired
private HttpServletRequest         request ;
private final ParserRequestContext parserRequestContext ;
public CustomResolverFactory (ParserRequestContext parserRequestContext) {
this.parserRequestContext = assertProxy(parserRequestContext);
}
public DataResolver getDataResolver (DataResolverContext context) {
assertNotNull (parserRequestContext, “no ParserRequestContext defined”);
OwnerId ownerIdAnnotation = context.getAnnotation(OwnerId.class );
if ( ownerIdAnnotation != null) {
return new OwnerIdResolver(ownerIdAnnotation );
}
BizId bizIdAnnotation = context.getAnnotation(BizId .class);
if ( bizIdAnnotation != null) {
return new BizIdResolver(bizIdAnnotation );
}
return null ;
}
/**
* 类 OwnerIdResolver 的实现描述:用来解析 ownerId
*
@author 晓飞 2013-10-11 上午10:16:53
*/
private class OwnerIdResolver implements DataResolver {
private OwnerId annotation;
public OwnerIdResolver(OwnerId annotation) {
this.annotation = annotation;
}
public Object resolve() throws IllegalArgumentException {
if ( request == null) {
throw new IllegalArgumentException(“No httpServletRequest in context”);
}
HttpSession session = request.getSession();
if ( session == null) {
throw new IllegalArgumentException(“No session in request”);
}
String userId = ( String) session.getAttribute(SessionKeeper.ATTRIBUTE_USER_ID_NUM );
ResultDO <BaseUserDO> result = uicReadServiceClient. getBaseUserByUserId(Long
.parseLong(userId));
BaseUserDO data = null ;
if ( result.isSuccess ()) {
data = result.getModule();
} else {
throw new IllegalArgumentException(“error reading userId from uic”);
}
if ( data == null) {
throw new IllegalArgumentException(“error reading userId from uic”);
}
//检查是否是小二
if ( data.getUserRank ().longValue() == 46) {
String name = annotation.name();
String value = trimToNull(request.getParameter (name));
if ( value == null) {
value = annotation.defaultValue();
}
return Long. parseLong(value );
}
//如果是子账号则获取父账号 userId
if ( MMPTools.isSubUserLogin ()) {
long id = MMPTools.getUserId ();
return id;
} else {
return Long. parseLong(userId );
}
}
}
/**
* 类 BizIdResolver 的实现描述:用来解析 BizId
*
@author 晓飞 2013-10-11 上午10:16:53
*/
private class BizIdResolver implements DataResolver {
private BizId annotation;
public BizIdResolver(BizId annotation) {
this.annotation = annotation;
}
public Object resolve() throws IllegalArgumentException {
if ( request == null) {
throw new IllegalArgumentException(“No httpServletRequest in context”);
}
HttpSession session = request.getSession();
if ( session == null) {
throw new IllegalArgumentException(“No session in request”);
}
String userId = ( String) session.getAttribute(SessionKeeper.ATTRIBUTE_USER_ID_NUM );
ResultDO <BaseUserDO> result = uicReadServiceClient. getBaseUserByUserId(Long
.parseLong(userId));
BaseUserDO data = null ;
if ( result.isSuccess ()) {
data = result.getModule();
} else {
throw new IllegalArgumentException(“error reading userId from uic”);
}
if ( data == null) {
throw new IllegalArgumentException(“error reading userId from uic”);
}
//检查是否是小二
if ( data.getUserRank ().longValue() == 46) {
if(StringUtil .isBlank(annotation.valueIfStaff ())){
return null ;
}
try{
return Long. parseLong(annotation .valueIfStaff());
}catch(NumberFormatException e){
throw new IllegalArgumentException(“error parse value if staff string into Long type”);
}
}
//如果是子账号则获取父账号 userId
if ( MMPTools.isSubUserLogin ()) {
long id = MMPTools.getUserId ();
return id;
} else {
return Long. parseLong(userId );
}
}
}
public static class DefinitionParser extends
AbstractSingleBeanDefinitionParser <CustomResolverFactory> {
@Override
protected void doParse(Element element , ParserContext parserContext,
BeanDefinitionBuilder builder ) {
addConstructorArg (builder, false, ParserRequestContext. class);
}
}
}
 
 
 
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.PARAMETER })
public @interface OwnerId {
/**
* 参数名字
@author 晓飞
* 2013 -10- 9下午4:23:17
@return
*/
String name();
/**
* 参数默认值
@author 晓飞
* 2013 -10- 9下午4:23:25
@return
*/
String defaultValue() default “0″ ;
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.PARAMETER })
public @interface BizId {
/**
* 如果是小二时返回的值
*
@author 晓飞 2013- 10-12下午5:02:54
@return
*/
String valueIfStaff() default “0″;
}

完成这些后你就可以在screen或者action类中使用@BizId和@OwnerId绑定参数了。
渲染模板
 
我们在执行完module后,会往context放入数据,这些数据会作为数据源的一部分被模板引擎使用,渲染模板并生成response返回到客户端。由于篇幅的原因,这里不再扩展,我会再单独写一篇介绍velocity引擎如何在webx3中工作并渲染模板的。

转自:http://ju.outofmemory.cn/entry/96975

webx3对请求的处理流程详解一相关推荐

  1. java处理请求的流程_Java Spring mvc请求处理流程详解

    Spring mvc请求处理流程详解 前言 spring mvc框架相信很多人都很熟悉了,关于这方面的资料也是一搜一大把.但是感觉讲的都不是很细致,让很多初学者都云里雾里的.本人也是这样,之前研究过, ...

  2. 杂志订阅管理系统c++_电池管理系统BMS功能安全开发流程详解

    点击上面 "电动知家"可以订阅哦! BMS功能安全开发流程详解 BMS和ISO26262 - BMS & ISO26262简介 BMS即Battery Management ...

  3. linux中流设备_[快速上手Linux设备驱动]之块设备驱动流程详解一

    [快速上手Linux设备驱动]之块设备驱动流程详解一 walfred已经在[快速上手Linux设备驱动]之我看字符设备驱动一 文中详细讲解了linux下字符设备驱动,并紧接着用四篇文章描述了Linux ...

  4. 推荐系统整体架构及算法流程详解

    省时查报告-专业.及时.全面的行研报告库 省时查方案-专业.及时.全面的营销策划方案库 知识图谱在美团推荐场景中的应用实践 搜索场景下的智能实体推荐 机器学习在B站推荐系统中的应用实践 小红书推荐系统 ...

  5. [nRF51822] 5、 霸屏了——详解nRF51 SDK中的GPIOTE(从GPIO电平变化到产生中断事件的流程详解)...

    :由于在大多数情况下GPIO的状态变化都会触发应用程序执行一些动作.为了方便nRF51官方把该流程封装成了GPIOTE,全称:The GPIO Tasks and Events (GPIOTE) . ...

  6. 负载均衡原理与实践详解 第五篇 负载均衡时数据包流程详解

    负载均衡原理与实践详解 第五篇 负载均衡时数据包流程详解 系列文章: 负载均衡详解第一篇:负载均衡的需求 负载均衡详解第二篇:服务器负载均衡的基本概念-网络基础 负载均衡详解第三篇:服务器负载均衡的基 ...

  7. Springboot启动流程详解

    SpringMVC请求流程详解 SpringMVC框架是一个基于请求驱动的Web框架,并且使用了'前端控制器'模型来进行设计,再根据'请求映射规则'分发给相应的页面控制器进行处理. (一)整体流程 每 ...

  8. iOS APP上架流程详解

    iOS APP上架流程详解 青葱烈马 2016.04.28  前言:作为一名 iOS 开发工程师, APP 的上架是必备技能. iOS 上架的流程主要可以简单总结为: 一个包,两个网址,三个证书, 一 ...

  9. 推荐系统架构与算法流程详解

    你知道的越多,不知道的就越多,业余的像一棵小草! 成功路上并不拥挤,因为坚持的人不多. 编辑:业余草 zhuanlan.zhihu.com/p/259985388 推荐:https://www.xtt ...

  10. 《MySQL安装流程详解》及《MySQL安装一直失败,重新安装显示已安装》

    <MySQL安装流程详解>及<MySQL安装一直失败,重新安装显示已安装> 本文由博主经过查阅网上资料整理总结后编写,如存在错误或不恰当之处请留言以便更正,内容仅供大家参考学习 ...

最新文章

  1. 大数据下的电商新打法
  2. ASP.NET中常用功能代码总结(5)——文件操作篇
  3. 编程学将成为必然趋势,青少年编程,从哪里开始?这里推荐Python
  4. OpenCASCADE绘制测试线束:形状修复命令之一般命令
  5. 快速求平方根,这个好牛逼
  6. 获取族_批量添加族参数(上)
  7. 2013_chengdu_online
  8. Ansroid系统(262)---MTK安卓sim卡相关源码分析
  9. 互联时代如何真正支持与实现数据经济
  10. 小米盒子显示连接服务器失败,小米盒子连接AirPlay失败的解决方法
  11. [转]安装win7系统不产生100M保留分区
  12. mapbox gl本地化部署实践
  13. Mac谷歌浏览器添加JSONView的插件以提高开发的效率
  14. python实现对遥感影像经纬度获取并实现海陆分离
  15. curl: (51) Unable to communicate securely with peer: requested domain name does not match the server
  16. 函函函函函函函函函函函数——two
  17. Kubeedge 1.5 部署指南
  18. 趁着中秋节来临之际,学学如何做好团队管理
  19. 趣图:大佬如何解决bug的
  20. 2020Cfa最新mock下载和使用

热门文章

  1. Python中的yield from语法
  2. 选课系统源码html,高校选课系统 - WEB源码|源代码 - 源码中国
  3. 如何将BMP文件转换为JPG文件
  4. 快速预警、高效疏通,ZBOX打造高速公路智慧通信站
  5. 为什么它有典型FaaS能力,却是非典型FaaS架构?
  6. 小程序-云开发:云开发是什么?
  7. walking机器人入门教程-离线建图-cartographer算法建图
  8. 大二〕一直在寻找 生而为人的意义(转载)
  9. c语言乘法运算结果小数位数,如何用c语言计算小数点后位数
  10. 没有服务器认证消息,关于《跑跑卡丁车》没有服务器认证消息的问题,怎么解决?...