1.Spring源码从入门到精通---@Import(五)
2.手写webpacktapable源码,批量批量官方tapable的注册注册性能真的就一定是好的吗?
3.Spring Configuration:@Import的用法和源码解析
Spring源码从入门到精通---@Import(五)
深入解析如何给容器注册bean
通过ComponentScan+注解如@Controller,@Service,@Compoment,@Repository实现自动扫描bean
@Bean+@Configuration定义导入第三方bean
利用@Import快速批量导入组件,优势在于简化配置
文章重点解析@Import的源码源码三种用法:直接导入容器、自定义importSelector实现、批量批量自定义ImportBeanDefinitionRegistrar手动注册
1)@import注解直接导入容器,注册注册id默认为全类名
2) 自定义importSelector类,源码源码前锋狙击源码返回需要注册的批量批量全类名数组
3) 实现ImportBeanDefinitionRegistrar接口,自定义组件注册和id
通过@Import源码,注册注册导入的源码源码实质是一个数组,允许批量导入多个类
演示通过import将组件如color和red导入容器,批量批量并展示容器中组件的注册注册打印
提供JUnit测试类,重复利用方法提取getDefinitionNames(),源码源码简化测试步骤
新增1)@Import基础使用部分,批量批量删除原有代码,注册注册便于理解@Import
运行示例,源码源码展示导入组件后的容器打印结果,突出import的优势
详细步骤:
2)自定义myImportSelector类实现ImportSelector,返回新增组件路径,结合扫描自定义类
结果展示:blue和yellow组件成功注册容器,验证自定义importSelect功能
3)实现ImportBeanDefinitionRegistrar接口,ssm项目指标源码自定义组件名注册到容器
junit测试不变,运行结果:验证容器中包含red、yellow组件,满足自定义id需求
手写webpacktapable源码,官方tapable的性能真的就一定是好的吗?
完整的手写源码仓库tapable是Webpack?插件机制核心。?mini-tapable?不仅解读官方?tapable?的源码,还用自己的思路去实现一遍,并且和官方的运行时间做了个比较,我和webpack作者相关的讨论可以点击查看。webpacktapable源码内部根据newFunction动态生成函数执行体这种优化方式不一定是好的。当我们熟悉了tapable后,就基本搞懂了webpackplugin的底层逻辑,再回头看webpack源码就轻松很多
目录src目录。这个目录下是手写所有的tapablehook的源码,每个hook都用自己的思路实现一遍,并且和官方的hook执行时间做个对比。
tapable的设计理念:单态、多态及内联缓存由于在webpack打包构建的过程中,会有上千(数量其实是坤乐盒源码取决于自身业务复杂度)个插件钩子执行,同时同类型的钩子在执行时,函数参数固定,函数体相同,因此tapable针对这些业务场景进行了相应的优化。这其中最重要的是运用了单态性及多态性概念,内联缓存的原理,也可以看这个issue。为了达到这个目标,tapable采用newFunction动态生成函数执行体的方式,主要逻辑在源码的HookCodeFactory.js文件中。
如何理解tapable的设计理念思考下面两种实现方法,哪一种执行效率高,哪一种实现方式简洁?
//方法一:constcallFn=(...tasks)=>(...args)=>{ for(constfnoftasks){ fn(...args)}}//方法二:constcallFn2=(a,b,c)=>(x,y)=>{ a(x,y);b(x,y);c(x,y);}callFn及callFn2的目的都是为了实现将一组方法以相同的参数调用,依次执行。很显然,方法一效率明显更高,并且容易扩展,能支持传入数量不固定的一组方法。但是西安到西宁源码,如果根据单态性以及内联缓存的说法,很明显方法二的执行效率更高,同时也存在一个问题,即只支持传入a,b,c三个方法,参数形态也固定,这种方式显然没有方法一灵活,那能不能同时兼顾效率以及灵活性呢?答案是可以的。我们可以借助newFunction动态生成函数体的方式。
classHookCodeFactory{ constructor(args){ this._argNames=args;this.tasks=[];}tap(task){ this.tasks.push(task);}createCall(){ letcode="";//注意思考这里是如何拼接参数已经函数执行体的constparams=this._argNames.join(",");for(leti=0;i<this.tasks.length;i++){ code+=`varcallback${ i}=this.tasks[${ i}];callback${ i}(${ params})`;}returnnewFunction(params,code);}call(...args){ constfinalCall=this.createCall();//将函数打印出来,方便观察最终拼接后的结果console.log(finalCall);returnfinalCall.apply(this,args);}}//构造函数接收的arg数组里面的参数,就是taska、b、c三个函数的参数constcallFn=newHookCodeFactory(["x","y","z"]);consta=(x,y,z)=>{ console.log("taska:",x,y,z);};constb=(x,y,z)=>{ console.log("taskb:",x,y,z);};constc=(x,y,z)=>{ console.log("taskc:",x,y,z);};callFn.tap(a);callFn.tap(b);callFn.tap(c);callFn.call(4,5,6);当我们在浏览器控制台执行上述代码时:
拼接后的完整函数执行体:
可以看到,通过这种动态生成函数执行体的方式,我们能够同时兼顾性能及灵活性。我们可以通过tap方法添加任意数量的任务,同时通过在初始化构造函数时newHookCodeFactory(['x',文件源码怎么配置'y',...,'n'])传入任意参数。
实际上,这正是官方tapable的HookCodeFactory.js的简化版本。这是tapable的精华所在。
tapable源码解读tapable最主要的源码在Hook.js以及HookCodeFactory.js中。Hook.js主要是提供了tap、tapAsync、tapPromise等方法,每个Hook都在构造函数内部调用consthook=newHook()初始化hook实例。HookCodeFactory.js主要是根据newFunction动态生成函数执行体。
demo以SyncHook.js为例,SyncHook钩子使用如下:
const{ SyncHook}=require("tapable");debugger;consttesthook=newSyncHook(["compilation","name"]);//注册plugin1testhook.tap("plugin1",(compilation,name)=>{ console.log("plugin1",name);compilation.sum=compilation.sum+1;});//注册plugin2testhook.tap("plugin2",(compilation,name)=>{ console.log("plugin2..",name);compilation.sum=compilation.sum+2;});//注册plugin3testhook.tap("plugin3",(compilation,name)=>{ console.log("plugin3",compilation,name);compilation.sum=compilation.sum+3;});constcompilation={ sum:0};//第一次调用testhook.call(compilation,"mytest1");//第二次调用testhook.call(compilation,"mytest2");//第三次调用testhook.call(compilation,"mytest3");...//第n次调用testhook.call(compilation,"mytestn");我们用这个demo做为用例,一步步debug。
SyncHook.js源码主要逻辑如下:
constHook=require("./Hook");constHookCodeFactory=require("./HookCodeFactory");//继承HookCodeFactoryclassSyncHookCodeFactoryextendsHookCodeFactory{ }constfactory=newSyncHookCodeFactory();constCOMPILE=function(options){ factory.setup(this,options);returnfactory.create(options);};functionSyncHook(args=[],name=undefined){ //初始化Hookconsthook=newHook(args,name);//注意这里修改了hook的constructorhook.constructor=SyncHook;...//每个钩子都必须自行实现自己的compile方法!!!hook.compile=COMPILE;returnhook;}Hook.js源码主要逻辑如下:
//问题一:思考一下为什么需要CALL_DELEGATEconstCALL_DELEGATE=function(...args){ //当第一次调用时,实际上执行的是CALL_DELEGATE方法this.call=this._createCall("sync");//当第二次或者第n次调用时,此时this.call方法已经被设置成this._createCall的返回值returnthis.call(...args);};...classHook{ constructor(args=[],name=undefined){ this._args=args;this.name=name;this.taps=[];//存储我们通过hook.tap注册的插件this.interceptors=[];this._call=CALL_DELEGATE;//初始化时,this.call被设置成CALL_DELEGATEthis.call=CALL_DELEGATE;...//问题三:this._x=undefined是什么this._x=undefined;//this._x实际上就是this.taps中每个插件的回调//问题四:为什么需要在构造函数中绑定这些函数this.compile=this.compile;this.tap=this.tap;this.tapAsync=this.tapAsync;this.tapPromise=this.tapPromise;}//每个钩子必须自行实现自己的compile方法。compile方法根据this.taps以及this._args动态生成函数执行体compile(options){ thrownewError("Abstract:shouldbeoverridden");}//生成函数执行体_createCall(type){ returnthis.compile({ taps:this.taps,interceptors:this.interceptors,args:this._args,type:type});}..._tap(type,options,fn){ ...this._insert(options);}tap(options,fn){ this._tap("sync",options,fn);}_resetCompilation(){ this.call=this._call;this.callAsync=this._callAsync;this.promise=this._promise;}_insert(item){ //问题二:为什么每次调用testhook.tap()注册插件时,都需要重置this.call等方法?this._resetCompilation();...}}思考Hook.js源码中的几个问题问题一:为什么需要CALL_DELEGATE
问题二:为什么每次调用testhook.tap()注册插件时,都需要重置this.call等方法?
问题三:this._x=undefined是什么
问题四:为什么需要在构造函数中绑定this.compile、this.tap、this.tapAsync以及this.tapPromise等方法
当我们每次调用testhook.tap方法注册插件时,流程如下:
方法往this.taps数组中添加一个插件。this.__insert方法逻辑比较简单,但这里有一个细节需要注意一下,为什么每次注册插件时,都需要调用this._resetCompilation()重置this.call等方法?我们稍后再看下这个问题。先继续debug。
当我们第一次(注意是第一次)调用testhook.call时,实际上调用的是CALL_DELEGATE方法
constCALL_DELEGATE=function(...args){ //当第一次调用时,实际上执行的是CALL_DELEGATE方法this.call=this._createCall("sync");//当第二次或者第n次调用时,此时this.call方法已经被缓存成this._createCall的返回值returnthis.call(...args);};CALL_DELEGATE调用this._createCall函数根据注册的this.taps动态生成函数执行体。并且this.call被设置成this._createCall的返回值缓存起来,如果this.taps改变了,则需要重新生成。
此时如果我们第二次调用testhook.call时,就不需要再重新动态生成一遍函数执行体。这也是tapable的优化技巧之一。这也回答了问题一:为什么需要CALL_DELEGATE。
如果我们调用了n次testhook.call,然后又调用testhook.tap注册插件,此时this.call已经不能重用了,需要再根据CALL_DELEGATE重新生成一次函数执行体,这也回答了问题二:为什么每次调用testhook.tap()注册插件时,都需要重置this.call等方法。可想而知重新生成的过程是很耗时的。因此我们在使用tapable时,最好一次性注册完所有插件,再调用call
testhook.tap("plugin1");testhook.tap("plugin2");testhook.tap("plugin3");testhook.call(compilation,"mytest1");//第一次调用call时,会调用CALL_DELEGATE动态生成函数执行体并缓存起来testhook.call(compilation,"mytest2");//不会重新生成函数执行体,使用第一次的testhook.call(compilation,"mytest3");//不会重新生成函数执行体,使用第一次的避免下面的调用方式:
testhook.tap("plugin1");testhook.call(compilation,"mytest1");//第一次调用call时,会调用CALL_DELEGATE动态生成函数执行体并缓存起来testhook.tap("plugin2");testhook.call(compilation,"mytest2");//重新调用CALL_DELEGATE生成函数执行体testhook.tap("plugin3");testhook.call(compilation,"mytest3");//重新调用CALL_DELEGATE生成函数执行体现在让我们看看第三个问题,调用this.compile方法时,实际上会调用HookCodeFacotry.js中的setup方法:
setup(instance,options){ instance._x=options.taps.map(t=>t.fn);}对于问题四,实际上这和V8引擎的HiddenClass有关,通过在构造函数中绑定这些方法,类中的属性形态固定,这样在查找这些方法时就能利用V8引擎中HiddenClass属性查找机制,提高性能。
HookCodeFactory.js主要逻辑:
classHookCodeFactory{ constructor(config){ this.config=config;this.options=undefined;this._args=undefined;}create(options){ this.init(options);letfn;switch(this.options.type){ case'sync':fn=newFunction(...)breakcase'async':fn=newFunction(...)breakcase'promise':fn=newFunction(...)break}this.deinit();returnfn;}setup(instance,options){ instance._x=options.taps.map(t=>t.fn);}...}手写tapable每个Hook手写tapable中所有的hook,并比较我们自己实现的hook和官方的执行时间
这里面每个文件都会实现一遍官方的hook,并比较执行时间,以SyncHook为例,批量注册个插件时,我们自己手写的MySyncHook执行时间0.ms,而官方的需要6ms,这中间整整倍的差距!!!
具体可以看我的仓库
原文:/post/Spring Configuration:@Import的用法和源码解析
Spring 3.0之后的@Configuration注解和注解配置体系革新了bean的配置方式。本文主要解析@Import的用法和源码实现。1. @Import的用法
配置类,如带有@Configuration注解的类,可作为bean注册起点。除了@Bean方法声明bean,@Import注解允许批量注册相关bean。例如,WebMvcConfig通过@Import导入其他配置类,同时借助@EnableWebMvc导入另一配置类。2. 直接导入
用户可以通过@Import注解在配置类上导入一个或多个类,甚至可以嵌套在父类注解中,如WebMvcConfig导入的DelegatingWebMvcConfiguration。3. ImportBeanDefinitionRegistrar和ImportSelector
-
如@EnableAspectJAutoProxy通过ImportBeanDefinitionRegistrar实现,注册AnnotationAwareAspectJAutoProxyCreator。
-
@EnableTransactionManagement通过ImportSelector(如TransactionManagementConfigurationSelector)选择需要的事务配置类。
4. 源码解析
ConfigurationClassPostProcessor负责处理@Configuration类,通过ConfigurationClassParser解析配置类及其导入的类,然后由ConfigurationClassBeanDefinitionReader注册BeanDefinition。处理@Import时,通过深度优先搜索避免循环导入。 解析过程中,配置类的递归导入需防止环形依赖,通过导入链映射表判断。此外,还考虑了内部配置类递归导入外部类的情况。5. ImportBeanDefinitionRegistrar和ImportSelector的行为
-
导入注册器和选择器时,会提前触发Aware接口方法,并在BeanDefinition注册时执行注册方法。
-
DeferredImportSelector处理时机独特,但处理逻辑与普通选择器类似,只是在解析末尾进行。
总结
@Configuration的@Import提供了丰富的导入方式,展现了灵活性。源码中的处理策略确保了解析过程的稳定性和效率,体现了Spring框架的精细设计和用户自定义的便捷性。2024-11-25 10:26
2024-11-25 09:56
2024-11-25 09:16
2024-11-25 08:14
2024-11-25 08:07
2024-11-25 08:03