腾讯插件化—Shadow源码
腾讯插件化框架Shadow介绍及源码解析 Shadow是一个由腾讯自主研发的Android插件框架,经过线上亿级用户量的端源检验,其在插件技术领域展现出不俗的码插码服实力。Shadow不仅开源分享了关键代码,源码源码还全面分享了上线部署所需的插件设计方案。 与市面上其他插件框架相比,服务企业源码打包Shadow在技术特点上主要体现在:支持特性编译与开发环境准备:建议使用最新稳定版本的端源Android Studio,推荐打开工程并选择sample-app或sample-host模块直接运行,码插码服体验不同安装情况下的源码源码运行效果。
代码结构清晰:所有代码集中在projects目录下的插件三个子目录中,sample目录为体验Shadow的服务最佳环境,详细信息可参考README文档。端源
插件加载与启动流程解析 插件加载是码插码服Shadow框架的核心,从loadPlugin作为起点,源码源码通过一系列步骤实现插件的插件动态加载与启动。包括但不限于:本地启动顺序:重点关注启动流程的第一、二步,回溯整个过程最终调用Plugin Manager的DynamicPluginManager.enter方法。
跨进程调用与Activity加载:调用mDynamicPluginLoader.callApplicationOnCreate方法执行插件加载,之后通过FastPluginManager.convertActivityIntent方法启动Activity。
Activity与Service加载机制 在Activity与Service加载机制上,Shadow采用与Android系统自身一致的实现方式:通过修改ClassLoader的parent属性,插入DexClassLoader实现插件apk的加载与Activity的实例化。具体步骤包括:new一个DexClassLoader加载插件apk,从插件ClassLoader中load指定的插件Activity名字,newInstance之后强转为Activity类型使用。 Shell Activity复用与资源管理 为了解决资源复用与访问问题,Shadow通过代理Activity的方式,通过Intent的参数确定构造哪个Activity,令壳子Activity能够复用,实现资源的隔离管理。此外,对同名View与资源的情感女声在线朗读源码处理也非常关键,通过自定义类加载器与AOP技术,解决此类问题。 组件调用与优化 对于Service、Content Provider与Broadcast Receiver的调用,Shadow提供了优化方案,如通过ShadowContext启动Service、使用ShadowAcpplication注册静态广播等。 总结与学习建议 本文详细解析了插件化框架Shadow的源码与实现机制,深入探讨了其解决插件加载、Activity启动、资源管理等问题的策略。对于深入理解Android插件化技术,实现高效、稳定的插件化解决方案具有重要参考价值。建议对Android核心技术感兴趣的开发者深入阅读《Android核心技术手册》,了解更多关于插件化、热修复等技术的详细内容。Mybatis:PageHelper分页插件源码及原理剖析
PageHelper是一款强大的Mybatis分页插件,以其开源和免费的特性受到赞誉。其功能复杂性远超初印象,实现了物理分页的强大与彻底。核心在于保持分页插件的基本功能,同时提供智能参数以适应复杂场景。
基本使用与配置方面,PageHelper依赖于jsqlparser包,Maven会自动引入。关键参数dialect = mysql,与oracle等其他数据库对应参数可根据需要选择使用或避免使用,保持分页插件的简洁性。
源码分析中,PageHelper包含SqlUtil、SqlUtilConfig等类,涨跌天数统计指标源码负责数据库类型专用SQL工具的管理与执行,如count查询、分页查询等。自动检测当前数据库方言的机制和缓存策略提高了插件的灵活性与效率。
PageSqlSource类展示了如何解析SQL并添加分页参数,确保分页查询的正确执行。Parser类则负责解析SQL并添加所需分页参数,实现分页功能的关键步骤。
执行分页查询的`SqlUtil.doProcessPage()`方法,展示了创建count查询、执行分页查询、修改参数列表等关键步骤,确保分页查询的高效执行。
PageHelper提供了两种使用方式:直接通过RowBounds参数进行分页查询和使用静态方法`PageHelper.startPage()`。其原理在于使用ThreadLocal传递和保存Page对象,每次查询时需单独设置,以保持分页功能的灵活性。
最佳使用建议包括明确指定方言以及编写SQL分页业务与对应的count查询,避免简化操作以提高性能。推荐进一步阅读相关聚合内容,如SpringBoot、设计模式、Mybatis、多线程等,以深入理解其应用与优化。
服务端代码遵守gplv3协议开源,我为其编写的插件是否能使用
在讨论服务端代码使用GPLv3协议开源,以及为该代码编写的插件是否能使用的背景下,关键在于理解GPLv3协议对插件发布的具体要求。
根据GPLv3的FAQ,若编写了一个与主程序相关的插件,发布时需要考虑的是主程序与插件之间的整合程度。如果主程序与插件之间共享数据结构、股票暴雨指标源码通过复杂通信机制紧密结合,那么它们被视为单一的组合程序。在这种情况下,插件必须按照GPLv3或任何与之兼容的自由软件许可协议发布,包括提供源代码。
相反,如果主程序与插件之间的接口简单,仅通过基本的fork和exec调用插件,且没有建立复杂的数据交换或函数调用机制,那么它们被视为独立的两个程序。此时,主程序的许可协议对插件的使用并不构成强制要求。
关于组合程序与单一程序的界定,主要取决于主程序调用插件的方式和它们之间数据交换的深度。在动态连接且相互调用函数共享数据结构的情况下,两者被看作一个单一组合程序,需遵循扩展性原则。而如果主程序仅调用插件的主函数并等待返回值,且数据交换仅限于此,则可能会被归类为一个可以视为单一组合程序的特殊情况,也可能被视为独立程序。
综上所述,编写为使用GPLv3协议开源服务端代码的插件时,若主程序与插件之间存在紧密集成,遵循GPLv3或兼容协议发布插件是必要的。反之,如果两者较为独立,可根据具体情况选择许可协议,但需确保遵循相应开源许可的条款和条件。
Vue—关于插件(源码级别的插件分析+实践)
Vue插件的原理基于Vue的`use`方法,该方法接收一个函数或者提供`install`方法的对象作为参数,如果传入的高级开发源码分析参数是函数,这个函数会被当作`install`方法。在Vue 2.6.版本中,`use`方法内部使用`initUse`函数给Vue添加了一个静态方法`use`。以vuex为例,它暴露了一个`install`方法,通过`Vue.use(vuex)`来安装插件。vuex的`install`函数会调用`applyMixin`函数,并将Vue传递过去。`applyMixin`函数在Vue 2.x版本中会直接使用`Vue.mixin`来扩展功能,通过在组件的`beforeCreate`钩子中初始化vuex插件。
在Vue中使用混入(mixin)是一种设计模式,可以轻松地被子类继承功能,目的是实现函数复用。Vue中也应用了这一设计模式,通过`Vue.mixin`可以用来分发可复用逻辑。混入可以分为全局混入和局部混入,全局混入会影响所有的Vue实例,如果组件中与mixin中具有同名的属性,会进行选项合并,除了生命周期外,其它的所有属性都会被组件自身的属性覆盖。使用混入可以节省代码量,类似于类继承。
要自己实现一个提示框插件,可以通过`this.$notify()`进行调用,并且可以传入自定义模板。创建一个Vue工程,在`src`目录下新建`plugin`目录,然后创建一个`notify`目录,新建`index.js`和`Notify.vue`。在`index.js`中,引入`Notify.vue`组件,并通过`install`方法中注入的Vue来完成功能。实例挂载之后才可以访问`$el`选项,可以通过`Vue.use`来使用插件,然后在App.vue中验证功能是否正常。要实现传入模板并且显示出来,可以通过`$mount` API手动挂载一个实例,并在调用`$notify`方法时将挂载的元素插入到文档中。通过创建Vue组件,将DOM、JS、Style都创建好,最后调用`$notify`方法将组件插入到页面中。要实现传入模板,可以使用`v-html`指令来插入模板,并在Notify.vue中新增接收参数的方法。在App.vue中传递一段模板,页面上操作的效果为显示提示框,两秒后消失。
一文搞懂Mybatis插件原理
Mybatis插件原理详解,让你轻松实现自定义功能
在深入Mybatis源码时,你可能会遇到名为"plugin"的包,这实际上用于扩展框架的功能,如PageHelper分页插件。本文将揭示插件的运作机制,教你如何理解其内部工作并自行实现,如分页或慢SQL统计。
使用PageHelper插件分页的简单步骤是:一,在pom.xml中添加依赖;二,在mybatis-config.xml中配置插件;三,在需要分页的mapper接口前调用startPage方法,并确保每次查询后关闭Page对象以避免数据冲突。
接下来,我们将探索插件的总体流程:首先,通过拦截器接口实现动态代理,如PageInterceptor,它会在Executor的query方法执行前后添加自定义逻辑。拦截器通过Invocation对象获取目标对象和方法参数,实现增强功能。
分页拦截器如PageInterceptor拦截query方法,若需要分页,会获取原始SQL并添加limit语句。配置插件时,在mybatis-config.xml中添加相应拦截器,如``标签和Interceptor实现类的全限定名。
当我们创建SqlSession时,拦截器通过Configuration的newExecutor方法注册并应用到Executor对象上,利用JDK动态代理生成增强的代理对象,从而在执行SQL时调用拦截器的intercept方法。
自定义慢SQL统计插件同样基于Interceptor,只需配置目标对象为StatementHandler,拦截方法为query、update或batch,然后在配置文件中添加插件的全限定名。
mybatis插件机制源码解析
引言
本篇源码解析基于MyBatis3.5.8版本。
首先需要说明的是,本篇文章不是mybatis插件开发的教程,而是从源码层面分析mybatis是如何支持用户自定义插件开发的。
mybatis的插件机制,让其扩展能力大大增加。比如我们项目中经常用到的PageHelper,这就是一款基于mybatis插件能力开发的产品,它的功能是让基于mybatis的数据库分页查询更容易使用。
当然基于插件我们还可以开发其它功能,比如在执行sql前打印日志、做权限控制等。
正文mybatis插件也叫mybatis拦截器,它支持从方法级别对mybatis进行拦截。整体架构图如下:
解释下几个相关概念:
Interceptor拦截器接口,用户自定义的拦截器就是实现该接口。
InterceptorChain拦截器链,其内部维护一个interceptorslist,表示拦截器链中所有的拦截器,并提供增加或获取拦截器链的方法。比如有个核心的方法是pluginAll。该方法用来生成代理对象。
Invocation拦截器执行时的上下文环境,其实就是目标方法的调用信息,包含目标对象、调用的方法信息、参数信息。核心方法是proceed。该方法的主要目的就是进行处理链的传播,执行完拦截器的方法后,最终需要调用目标方法的invoke方法。
mybatis支持在哪些地方进行拦截呢?你只需要在代码里搜索interceptorChain.pluginAll的使用位置就可以获取答案,一共有四处:
parameterHandler=(ParameterHandler)interceptorChain.pluginAll(parameterHandler);resultSetHandler=(ResultSetHandler)interceptorChain.pluginAll(resultSetHandler);statementHandler=(StatementHandler)interceptorChain.pluginAll(statementHandler);executor=(Executor)interceptorChain.pluginAll(executor);这四处实现的原理都是一样的,我们只需要选择一个进行分析就可以了。
我们先来看下自定义的插件是如何加载进来的,比如我们使用PageHelper插件,通常会在mybatis-config.xml中加入如下的配置:
<plugins><plugininterceptor="com.github.pagehelper.PageInterceptor"><!--configparamsasthefollowing--><propertyname="param1"value="value1"/></plugin></plugins>mybatis在创建SqlSessionFactory的时候会加载配置文件,
publicConfigurationparse(){ if(parsed){ thrownewBuilderException("EachXMLConfigBuildercanonlybeusedonce.");}parsed=true;parseConfiguration(parser.evalNode("/configuration"));returnconfiguration;}parseConfiguration方法会加载包括plugins在内的很多配置,
privatevoidparseConfiguration(XNoderoot){ try{ ...pluginElement(root.evalNode("plugins"));...}catch(Exceptione){ thrownewBuilderException("ErrorparsingSQLMapperConfiguration.Cause:"+e,e);}}privatevoidpluginElement(XNodeparent)throwsException{ if(parent!=null){ for(XNodechild:parent.getChildren()){ Stringinterceptor=child.getStringAttribute("interceptor");Propertiesproperties=child.getChildrenAsProperties();InterceptorinterceptorInstance=(Interceptor)resolveClass(interceptor).getDeclaredConstructor().newInstance();interceptorInstance.setProperties(properties);configuration.addInterceptor(interceptorInstance);}}}pluginElement干了几件事情:
创建Interceptor实例
设置实例的属性变量
添加到Configuration的interceptorChain拦截器链中
mybatis的插件是通过动态代理实现的,那肯定要生成代理对象,生成的逻辑就是前面提到的pluginAll方法,比如对于Executor生成代理对象就是,
executor=(Executor)interceptorChain.pluginAll(executor);接着看pluginAll方法,
/***该方法会遍历用户定义的插件实现类(Interceptor),并调用Interceptor的plugin方法,对target进行插件化处理,*即我们在实现自定义的Interceptor方法时,在plugin中需要根据自己的逻辑,对目标对象进行包装(代理),创建代理对象,*那我们就可以在该方法中使用Plugin#wrap来创建代理类。*/publicObjectpluginAll(Objecttarget){ for(Interceptorinterceptor:interceptors){ target=interceptor.plugin(target);}returntarget;}这里遍历所有我们定义的拦截器,调用拦截器的plugin方法生成代理对象。有人可能有疑问:如果有多个拦截器,target不是被覆盖了吗?
其实不会,所以如果有多个拦截器的话,生成的代理对象会被另一个代理对象代理,从而形成一个代理链条,执行的时候,依次执行所有拦截器的拦截逻辑代码。
plugin方法是接口Interceptor的默认实现类,
defaultObjectplugin(Objecttarget){ returnPlugin.wrap(target,this);}然后进入org.apache.ibatis.plugin.Plugin#wrap,
publicstaticObjectwrap(Objecttarget,Interceptorinterceptor){ Map<Class<?>,Set<Method>>signatureMap=getSignatureMap(interceptor);Class<?>type=target.getClass();Class<?>[]interfaces=getAllInterfaces(type,signatureMap);if(interfaces.length>0){ returnProxy.newProxyInstance(type.getClassLoader(),interfaces,newPlugin(target,interceptor,signatureMap));}returntarget;}首先是获取我们自己实现的Interceptor的方法签名映射表。然后获取需要代理的对象的Class上声明的所有接口。比如如果我们wrap的是Executor,就是Executor的所有接口。然后就是最关键的一步,用Proxy类创建一个代理对象(newProxyInstance)。
注意,newProxyInstance方法的第三个参数,接收的是一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。
我们这里传入的是Plugin类,故在动态运行过程中会执行Plugin的invoker方法。
如果对这一段不是很理解,建议先了解下java动态代理的原理。java动态代理机制中有两个重要的角色:InvocationHandler(接口)和Proxy(类),这个是背景知识需要掌握的。
我们在深入看下上面的getSignatureMap方法,
privatestaticMap<Class<?>,Set<Method>>getSignatureMap(Interceptorinterceptor){ //从Interceptor的类上获取Intercepts注解,说明我们自定义拦截器需要带注解InterceptsinterceptsAnnotation=interceptor.getClass().getAnnotation(Intercepts.class);//issue#if(interceptsAnnotation==null){ thrownewPluginException("No@Interceptsannotationwasfoundininterceptor"+interceptor.getClass().getName());}Signature[]sigs=interceptsAnnotation.value();Map<Class<?>,Set<Method>>signatureMap=newHashMap<>();//解析Interceptor的values属性(Signature[])数组,存入HashMap,Set<Method>>for(Signaturesig:sigs){ Set<Method>methods=MapUtil.computeIfAbsent(signatureMap,sig.type(),k->newHashSet<>());try{ Methodmethod=sig.type().getMethod(sig.method(),sig.args());methods.add(method);}catch(NoSuchMethodExceptione){ thrownewPluginException("Couldnotfindmethodon"+sig.type()+"named"+sig.method()+".Cause:"+e,e);}}returnsignatureMap;}首先需要从Interceptor的类上获取Intercepts注解,说明我们自定义拦截器需要带注解,比如PageHelper插件的定义如下:
<plugins><plugininterceptor="com.github.pagehelper.PageInterceptor"><!--configparamsasthefollowing--><propertyname="param1"value="value1"/></plugin></plugins>0所以我们可以知道,getSignatureMap其实就是拿到我们自定义拦截器声明需要拦截的类以及类对应的方法。
前面说过,当我们调用代理对象时,最终会执行Plugin类的invoker方法,我们看下Plugin的invoker方法,
<plugins><plugininterceptor="com.github.pagehelper.PageInterceptor"><!--configparamsasthefollowing--><propertyname="param1"value="value1"/></plugin></plugins>1Interceptor接口的intercept方法就是我们自定义拦截器需要实现的逻辑,其参数为Invocation,可从Invocation参数中拿到执行方法的对象,方法,方法参数,比如我们可以从statementHandler拿到SQL语句,实现自己的特殊逻辑。
在该方法的结束需要调用invocation#proceed()方法,进行拦截器链的传播。
参考:
blogs.com/chenpi/p/.html
2024-12-23 06:29
2024-12-23 05:42
2024-12-23 05:10
2024-12-23 04:58
2024-12-23 04:19