ARouter路由框架

2023-10-17 00:28
文章标签 路由 框架 arouter

本文主要是介绍ARouter路由框架,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

序、时间于同样的方式留经我们每个人,每个人却以不同的方式对待时间。

前言

一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦

1.原理解析

1.1初始化

使用ARouter在进行Activity跳转非常简单:初始化ARouter、添加注解@Route、发起路由。

初始化SDK

// 在module app中
//1.初始化SDK
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
// moduleA
// 2.在支持路由的页面上添加注解(必选)
// 路径注意至少需有两级,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {...
}
// moduleB(没有依赖 moduleA)
// 3.发起路由跳转
ARouter.getInstance().build("/test/activity").navigation();

这样就使得 没有依赖moduleA的moduleB能跳转到moduleA的Activity了。服务获取也是类似简单的代码就可实现。

疑问:如何通过没有依赖完成 解耦组件间的路由操作呢?

1.2构建构建PostCard

先看ARouter.getInstance():

public static ARouter getInstance() {if (!hasInit) { // 未初始化则报异常throw new InitException("ARouter::Init::Invoke init(context) first!");} else {if (instance == null) {synchronized (ARouter.class) {if (instance == null) {instance = new ARouter();}}}return instance;}
}

获取ARouter单实例,没有初始化则报异常。再看它的build(string)方法:

public Postcard build(String path) {return _ARouter.getInstance().build(path);
}

这里调用的是_ARouter的同名方法,返回的是(Postcard)。

protected Postcard build(String path) {if (TextUtils.isEmpty(path)) { //path不能为空throw new HandlerException(Consts.TAG + "Parameter is invalid!");} else {//path替换,这是预处理PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);if (null != pService) {path = pService.forString(path);}return build(path, extractGroup(path), true);}
}

其中参数group是通过extractGroup(path)获取,也就是path的第一级,即"/test/activity"中的"test"。group的作用是作为路由的默认分组。

路由中的分组概念:

  • SDK中针对所有的路径(/test/1、/test/2)进行分组,分组只有在分组中的某一个路径第一次被访问的时候,该分组才会被初始化
  • 可以通过 @Route 注解主动指定分组,否则使用路径中第一段字符串(/*/)作为分组
  • 注意:一旦主动指定分组之后,应用内路由需要使用 ARouter.getInstance().build(path, group) 进行跳转,手动指定分组,否则无法找到 @Route(path = "/test/1", group = "app")

1.3路由过程

通过path构建了PostCard后调用了其navigation()方法,也就是开始了路由过程:

public Object navigation() {return navigation(null);
}
public Object navigation(Context context) {return navigation(context, null);
}
public Object navigation(Context context, NavigationCallback callback) {return ARouter.getInstance().navigation(context, this, -1, callback);
}

看到连续走了两个重载方法,最后走到ARouter的navigation方法,并且把自己传了进去。ARouter的navigation方法同样会走到_ARouter的同名方法:

// @param context     Activity or null.// @param postcard    Route metas// @param requestCode RequestCode,startActivity的requestCode// @param callback    cb,路由回调:找到目的地、未找到、中断、到达
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {//若有PretreatmentService的实现,就进行预处理。可以在真正路由前进行一些判断然后中断路由。PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {// 预处理失返回false,路由取消.return null;}// 给路由设置context,例如启动activity需要。如果没有传就使用mContext,即初始化ARouter时传入的Applicationpostcard.setContext(null == context ? mContext : context);try {// 1. 完善postcard信息(目前只有path、group,还需要知道具体目的地,例如要跳转到的activity信息)LogisticsCenter.completion(postcard);} catch (NoRouteFoundException ex) {//这里就是debug包中,没找到路由目的地时 经常出现的提示if (debuggable()) {runInMainThread(new Runnable() {public void run() {Toast.makeText(mContext, "There's no route matched!\n" +" Path = [" + postcard.getPath() + "]\n" +" Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();}});}//没找到的回调if (null != callback) {callback.onLost(postcard);} else {// 没有callback的话, (如果有)就回调到降低服务 DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);if (null != degradeService) {degradeService.onLost(context, postcard);}}return null;}//找到的回调if (null != callback) {callback.onFound(postcard);}// 2. 不是绿色通道的话,要先走拦截器if (!postcard.isGreenChannel()) {interceptorService.doInterceptions(postcard, new InterceptorCallback() {//拦截器处理结果:继续路由@Overridepublic void onContinue(Postcard postcard) {// 3. 获取路由结果_navigation(postcard, requestCode, callback);}//拦截器处理结果:中断路由,回调中断@Overridepublic void onInterrupt(Throwable exception) {if (null != callback) {callback.onInterrupt(postcard);}}});} else {//绿色通道,不走拦截器,就获取路由结果return _navigation(postcard, requestCode, callback);}return null;
}

使用前面构建好的PastCard经过整体3个步骤,就完成了路由:

  1. 完善postcard信息:只有path、group还不行,还需要知道具体目的地,例如要跳转到的Activity信息。
  2. 拦截器处理:不是绿色通道的话,要先经过路由器的处理,结果是中断或者继续。
  3. 获取路由结果:例如进行目标Activity的跳转、获取IProvider实现对象、获取Fragment等。

1.4获取路由结果

private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {final Context currentContext = postcard.getContext();//根据路由类型来走对应逻辑switch (postcard.getType()) {case ACTIVITY:// Activity, 使用postcard.getDestination()来构建intent、传入Extras、设置 flags、actionfinal Intent intent = new Intent(currentContext, postcard.getDestination());intent.putExtras(postcard.getExtras());int flags = postcard.getFlags();if (0 != flags) {intent.setFlags(flags);}// 当前的context不是activity,需要添加flag:FLAG_ACTIVITY_NEW_TASK//(启动startActivity需有任务栈的,application/service启动activity没有任务栈,所以必须要new_task_flag新建一个任务栈)if (!(currentContext instanceof Activity)) {intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);}String action = postcard.getAction();if (!TextUtils.isEmpty(action)) {intent.setAction(action);}// 最后在主线程执行 熟悉的startActivity,runInMainThread(new Runnable() {@Overridepublic void run() {startActivity(requestCode, currentContext, intent, postcard, callback);}});break;case PROVIDER://provider,指的是想要获取的服务,即IProvider的实现类。直接从postCard中获取。return postcard.getProvider();case BOARDCAST:case CONTENT_PROVIDER:case FRAGMENT://Broadcast、ContentProvider、Fragment,都是使用postcard.getDestination()反射创建实例Class<?> fragmentMeta = postcard.getDestination();try {Object instance = fragmentMeta.getConstructor().newInstance();if (instance instanceof Fragment) {((Fragment) instance).setArguments(postcard.getExtras());} else if (instance instanceof android.support.v4.app.Fragment) {((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());}return instance;} ...}return null;
}

从上面可见,postcard 经过完善后,路由类型type、目的地destination等都已经被赋了值。destination就是目标类的class对象。

方法内容就是根据路由类型来走对应逻辑:

  • Activity, 使用postcard.getDestination()来构建intent、传入Extras、设置 flags、action,最后在主线程执行 熟悉的startActivity
  • provider,指的是想要获取的服务,即IProvider的实现类。是直接从postCard中获取的,因为服务类是单例,只会在首次获取时创建()。
  • Broadcast、ContentProvider、Fragment,都是使用postcard.getDestination()反射创建实例

整体逻辑还是比较简单的。这里你可能好奇 destination的值是如何获取的,因为无论哪种类型的路由,都是要使用目标class,这个就是ARouter最为核心的内容——如何获取 无直接依赖的模块的 class对象,也就是完善postcard信息的过程。 不过我们先来把拦截器逻辑分析完,最后再来看这个核心点。

1.5拦截器

未设置绿色通道的路由需要经过拦截器处理,也就是interceptorService的doInterceptions()方法。interceptorService是啥呢?

final class ARouter {...private static InterceptorService interceptorService;...//ARouter的初始化方法public static void init(Application application) {if (!hasInit) {logger = _ARouter.logger;hasInit = _ARouter.init(application);if (hasInit) {_ARouter.afterInit();}}}...
}

  //_ARouter.javastatic void afterInit() {interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();}

InterceptorService继承IProvider,可见interceptorService也是一个服务,在ARouter初始化后 获取,用于处理拦截器的逻辑。具体的实现类是InterceptorServiceImpl:

@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService {
...@Overridepublic void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {//有拦截器if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {...LogisticsCenter.executor.execute(new Runnable() { //放入线程池异步执行@Overridepublic void run() { //interceptorCounter 用于保证所有拦截器都走完,并且设置了超时CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());try {//执行第一个拦截器,如果没有中断 则递归调用继续后面的拦截器_execute(0, interceptorCounter, postcard);interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);if (interceptorCounter.getCount() > 0) {    // count>0说明超时了,拦截器还没处理完.callback.onInterrupt(new HandlerException("The interceptor processing timed out."));} else if (null != postcard.getTag()) {    //Tag!=null说明被某个拦截器回调中断了callback.onInterrupt((Throwable) postcard.getTag());} else {callback.onContinue(postcard); // 所有拦截器处理完、没超时、也没异常,则继续路由}}...}});} else {//没有拦截器则继续路由callback.onContinue(postcard);}}private static void _execute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {if (index < Warehouse.interceptors.size()) {//从Warehouse.interceptors中获取第index个拦截器,走process方法,如果回调到onContinue就继续下一个;IInterceptor iInterceptor = Warehouse.interceptors.get(index);iInterceptor.process(postcard, new InterceptorCallback() {@Overridepublic void onContinue(Postcard postcard) {counter.countDown();_execute(index + 1, counter, postcard);  // 继续下一个}@Overridepublic void onInterrupt(Throwable exception) {postcard.setTag(null == exception ? new HandlerException("No message.") : exception);    // save the exception message for backup.counter.cancel();...}});}}@Override //此init方法会在服务被创建后调用。这里就是反射创建所有的拦截器实例public void init(final Context context) {LogisticsCenter.executor.execute(new Runnable() {@Overridepublic void run() {if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {//遍历Warehouse.interceptorsIndex ,使用存储与其中的拦截器class对象反射创建拦截器实例for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {Class<? extends IInterceptor> interceptorClass = entry.getValue();try {IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();iInterceptor.init(context);//存入 Warehouse.interceptorsWarehouse.interceptors.add(iInterceptor);}...}interceptorHasInit = true;...                }}});}...
}

doInterceptions()方法中判断如果有拦截器,就放入线程池异步执行第一个拦截器,且使用interceptorCounter 保证所有拦截器都走完,同时也设置了超时。 如果第一个拦截器没有回调中断 则递归调用继续后面的拦截器。

拦截器的执行,是从Warehouse.interceptors中获取第index个拦截器,走process方法,如果回调到onContinue就继续下一个;若回调onInterrupt就中断路由。

拦截器的执行逻辑还是比较清晰的。那么拦截器是怎么获取的呢?我们来看下InterceptorServiceImpl的init方法:init()方法会在服务被创建后立即调用,如上所示就是遍历Warehouse.interceptorsIndex ,使用存储在其中的拦截器class对象 反射创建拦截器实例,然后存在存入 Warehouse.interceptors。 也即是说,ARouter初始化完成后就获取到了所有拦截器实例

那么Warehouse又是啥呢?interceptorsIndex是如何存储的所有拦截器的class的?

1.6路由原信息的仓库

Warehouse意为仓库,用于存放被 @Route、@Interceptor注释的 路由相关的信息,也就是我们关注的destination等信息。既然是仓库,那么就是有存有取

前面举的例子:moduleB发起路由跳转到moduleA的activity,moduleB没有依赖moduleA,只是在moduleA的activity上增加了@Route注解。 由于进行activity跳转需要目标Activity的class对象来构建intent,所以必须有一个中间人,把路径"/test/activity"翻译成Activity的class对象,然后moduleB才能实现跳转。(因此在ARouter的使用中 moduleA、moduleB 都是需要依赖 arouter-api的)

这个中间人那就是ARouter了,而这个翻译工所作用到的词典就是 Warehouse,它存着所有路由信息。

class Warehouse {//所有IRouteGroup实现类的class对象,是在ARouter初始化中赋值,key是path第一级//(IRouteGroup实现类是编译时生成,代表一个组,即path第一级相同的所有路由,包括Activity和Provider服务)static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>(); //所有路由元信息,是在completion中赋值,key是path//首次进行某个路由时就会加载整个group的路由,即IRouteGroup实现类中所有路由信息。包括Activity和Provider服务static Map<String, RouteMeta> routes = new HashMap<>();//所有服务provider实例,在completion中赋值,key是IProvider实现类的classstatic Map<Class, IProvider> providers = new HashMap<>();//所有provider服务的元信息(实现类的class对象),是在ARouter初始化中赋值,key是IProvider实现类的全类名。//主要用于使用IProvider实现类的class发起的获取服务的路由,例如ARouter.getInstance().navigation(HelloService.class)static Map<String, RouteMeta> providersIndex = new HashMap<>();//所有拦截器实现类的class对象,是在ARouter初始化时收集到,key是优先级static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("...");//所有拦截器实例,是在ARouter初始化完成后立即创建static List<IInterceptor> interceptors = new ArrayList<>();
...
}

Warehouse存了哪些信息呢?

  • groupsIndex所有路由组元信息。是所有IRouteGroup实现类的class对象,是在ARouter初始化中赋值,key是path第一级。IRouteGroup实现类是编译时生成,代表一个组,即path第一级相同的所有路由,包括Activity和Provider服务)。
  • routes所有路由元信息。是在LogisticsCenter.completion中赋值,key是path。首次进行某个路由时就会加载整个group的路由,即IRouteGroup实现类中所有路由信息。包括Activity和Provider服务
  • providers所有服务provider实例。在LogisticsCenter.completion中赋值,key是IProvider实现类的class
  • providersIndex所有provider服务元信息(实现类的class对象)。是在ARouter初始化中赋值,key是IProvider实现类的全类名。用于使用IProvider实现类class发起的获取服务的路由,例如ARouter.getInstance().navigation(HelloService.class)
  • interceptorsIndex所有拦截器实现类class对象。是在ARouter初始化时收集到,key是优先级
  • interceptors所有拦截器实例。是在ARouter初始化完成后立即创建

其中groupsIndex、providersIndex、interceptorsIndex是ARouter初始化时就准备好的基础信息,为业务中随时发起路由操作(Activity跳转、服务获取、拦截器处理)做好准备。

那么Warehouse的信息是如何收集到的呢? 下面就先来看下ARouter初始化具体做了哪些事情:

final class _ARouter {...protected static synchronized boolean init(Application application) {mContext = application;LogisticsCenter.init(mContext, executor);...return true;}

_ARouter的init方法中 调用了LogisticsCenter的init方法。LogisticsCenter意为物流中心,上面提到的完善postcard的completion操作也是此类提供。

//LogisticsCenter.java
//LogisticsCenter初始化,加载所有的路由元信息
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {...try {long startInit = System.currentTimeMillis();//先尝试使用AGP transform 收集 根帮助类 后 写好的注入代码(要先引入插件才行 apply plugin: 'com.alibaba.arouter')loadRouterMap();if (registerByPlugin) {//registerByPlugin为true说明使用AGP加载ok了(通常都会用AGP,即registerByPlugin为true)logger.info(TAG, "Load router map by arouter-auto-register plugin.");} else {//若没有使用 AGP transform,就用ClassUtils.getFileNameByPackageName来搜集dex中ROUTE_ROOT_PAKCAGE包下的所有类,即编译时生成的所有帮助类//这样的话,就是运行时 遍历搜集 会比较耗时,也就是init会较为耗时;而AGP transform 是在编译时完成收集的。//当前app是新安装时才会走(收集到的帮助类会缓存到SP文件)Set<String> routerMap;if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {logger.info(TAG, "Run with debug mode or new install, rebuild router map.");// 这写帮助类是在编译时由arouter-compiler生成routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);if (!routerMap.isEmpty()) {context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();}PackageUtils.updateVersion(context);} else {//不是新安装的版本,就从SP文件中读取routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));}...//遍历帮助类,区分是哪种帮助类,然后反射创建帮助类实例后,调用其loadInto方法来填充Warehouse相应的Mapfor (String className : routerMap) {if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {//类名开头:com.alibaba.android.arouter.routes.ARouter$$Root//填充Warehouse.groupsIndex,即所有IRouteGroup实现类的class对象((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {//类名开头:com.alibaba.android.arouter.routes.ARouter$$Interceptors//填充Warehouse.interceptorsIndex,即所有IInterceptor实现类的class对象((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {//类名开头:com.alibaba.android.arouter.routes.ARouter$$Providers//填充Warehouse.providersIndex,即所有provider的RouteMeta((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);}}}...}
}

LogisticsCenter初始化 就是加载所有的路由元信息的过程,有两种方式:

  1. 走loadRouterMap()方法:直接使用在编译时收集好的帮助类信息,然后反射创建帮助类实例后,调用其loadInto方法来填充Warehouse相应的Map。这需要开发者要先引入插件才行 apply plugin: 'com.alibaba.arouter'。如果加载成功,registerByPlugin这个值就为true,否则false。
  2. 若第一步没有加载成功,即registerByPlugin为false:就会使用ClassUtils.getFileNameByPackageName在运行时搜集dex中"com.alibaba.android.arouter.routes"包下的所有类(即帮助类),然后遍历,区分是哪种帮助类。接着反射创建帮助类实例后,调用其loadInto方法来填充Warehouse相应的Map。

两种方式对比:

  • 相同点:都是使用帮助类信息反射创建帮助类实例后,调用其loadInto方法来填充Warehouse相应的Map。
  • 不同点:在于帮助类信息的收集方式。前者是在编译时搜集就完毕了,后者是运行时。

一般都是使用第一种,因为运行时遍历dex搜集会比较耗时,而第一种在编译时已经收集好了。 第一种方式的loadRouterMap()方法的实现逻辑 我们稍后再看。

2.流程图

参考

“终于懂了” 系列:组件化框架 ARouter 完全解析(一) 原理详解 - 掘金

“终于懂了” 系列:组件化框架 ARouter 完全解析(二)APT技术 - 掘金

“终于懂了” 系列:组件化框架 ARouter 完全解析(三)AGP/Transform/ASM—动态代码注入 - 掘金

阿里的ARouter,问几个你“不会”的问题!

ARouter.md

这篇关于ARouter路由框架的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/221709

相关文章

cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个?

跨平台系列 cross-plateform 跨平台应用程序-01-概览 cross-plateform 跨平台应用程序-02-有哪些主流技术栈? cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个? cross-plateform 跨平台应用程序-04-React Native 介绍 cross-plateform 跨平台应用程序-05-Flutte

Spring框架5 - 容器的扩展功能 (ApplicationContext)

private static ApplicationContext applicationContext;static {applicationContext = new ClassPathXmlApplicationContext("bean.xml");} BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanF

数据治理框架-ISO数据治理标准

引言 "数据治理"并不是一个新的概念,国内外有很多组织专注于数据治理理论和实践的研究。目前国际上,主要的数据治理框架有ISO数据治理标准、GDI数据治理框架、DAMA数据治理管理框架等。 ISO数据治理标准 改标准阐述了数据治理的标准、基本原则和数据治理模型,是一套完整的数据治理方法论。 ISO/IEC 38505标准的数据治理方法论的核心内容如下: 数据治理的目标:促进组织高效、合理地

ZooKeeper 中的 Curator 框架解析

Apache ZooKeeper 是一个为分布式应用提供一致性服务的软件。它提供了诸如配置管理、分布式同步、组服务等功能。在使用 ZooKeeper 时,Curator 是一个非常流行的客户端库,它简化了 ZooKeeper 的使用,提供了高级的抽象和丰富的工具。本文将详细介绍 Curator 框架,包括它的设计哲学、核心组件以及如何使用 Curator 来简化 ZooKeeper 的操作。 1

【Kubernetes】K8s 的安全框架和用户认证

K8s 的安全框架和用户认证 1.Kubernetes 的安全框架1.1 认证:Authentication1.2 鉴权:Authorization1.3 准入控制:Admission Control 2.Kubernetes 的用户认证2.1 Kubernetes 的用户认证方式2.2 配置 Kubernetes 集群使用密码认证 Kubernetes 作为一个分布式的虚拟

Spring Framework系统框架

序号表示的是学习顺序 IoC(控制反转)/DI(依赖注入): ioc:思想上是控制反转,spring提供了一个容器,称为IOC容器,用它来充当IOC思想中的外部。 我的理解就是spring把这些对象集中管理,放在容器中,这个容器就叫Ioc这些对象统称为Bean 用对象的时候不用new,直接外部提供(bean) 当外部的对象有关系的时候,IOC给它俩绑好(DI) DI和IO

Sentinel 高可用流量管理框架

Sentinel 是面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。 Sentinel 具有以下特性: 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应

利用Django框架快速构建Web应用:从零到上线

随着互联网的发展,Web应用的需求日益增长,而Django作为一个高级的Python Web框架,以其强大的功能和灵活的架构,成为了众多开发者的选择。本文将指导你如何从零开始使用Django框架构建一个简单的Web应用,并将其部署到线上,让世界看到你的作品。 Django简介 Django是由Adrian Holovaty和Simon Willison于2005年开发的一个开源框架,旨在简

Yii框架relations的使用

通过在 relations() 中声明这些相关对象,我们就可以利用强大的 Relational ActiveRecord (RAR) 功能来访问资讯的相关对象,例如它的作者和评论。不需要自己写复杂的 SQL JOIN 语句。 前提条件 在组织数据库时,需要使用主键与外键约束才能使用ActiveReocrd的关系操作; 场景 申明关系 两张表之间的关系无非三种:一对多;一对一;多对多; 在

laravel框架实现redis分布式集群原理

在app/config/database.php中配置如下: 'redis' => array('cluster' => true,'default' => array('host' => '172.21.107.247','port' => 6379,),'redis1' => array('host' => '172.21.107.248','port' => 6379,),) 其中cl