首页 文章 精选 留言 我的

精选列表

搜索[全模态理解],共10000篇文章
优秀的个人博客,低调大师

深入理解分布式事务

1、什么是分布式事务 分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。以上是百度百科的解释,简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。 2、分布式事务的产生的原因 2.1、数据库分库分表 当数据库单表一年产生的数据超过1000W,那么就要考虑分库分表,具体分库分表的原理在此不做解释,以后有空详细说,简单的说就是原来的一个数据库变成了多个数据库。这时候,如果一个操作既访问01库,又访问02库,而且要保证数据的一致性,那么就要用到分布式事务。 2.2、应用SOA化 所谓的SOA化,就是业务的服务化。比如原来单机支撑了整个电商网站,现在对整个网站进行拆解,分离出了订单中心、用户中心、库存中心。对于订单中心,有专门的数据库存储订单信息,用户中心也有专门的数据库存储用户信息,库存中心也会有专门的数据库存储库存信息。这时候如果要同时对订单和库存进行操作,那么就会涉及到订单数据库和库存数据库,为了保证数据一致性,就需要用到分布式事务。 以上两种情况表象不同,但是本质相同,都是因为要操作的数据库变多了! 3、事务的ACID特性 3.1、原子性(A) 所谓的原子性就是说,在整个事务中的所有操作,要么全部完成,要么全部不做,没有中间状态。对于事务在执行中发生错误,所有的操作都会被回滚,整个事务就像从没被执行过一样。 3.2、一致性(C) 事务的执行必须保证系统的一致性,就拿转账为例,A有500元,B有300元,如果在一个事务里A成功转给B50元,那么不管并发多少,不管发生什么,只要事务执行成功了,那么最后A账户一定是450元,B账户一定是350元。 3.3、隔离性(I) 所谓的隔离性就是说,事务与事务之间不会互相影响,一个事务的中间状态不会被其他事务感知。 3.4、持久性(D) 所谓的持久性,就是说一单事务完成了,那么事务对数据所做的变更就完全保存在了数据库中,即使发生停电,系统宕机也是如此。 4、分布式事务的应用场景 4.1、支付 最经典的场景就是支付了,一笔支付,是对买家账户进行扣款,同时对卖家账户进行加钱,这些操作必须在一个事务里执行,要么全部成功,要么全部失败。而对于买家账户属于买家中心,对应的是买家数据库,而卖家账户属于卖家中心,对应的是卖家数据库,对不同数据库的操作必然需要引入分布式事务。 4.2、在线下单 买家在电商平台下单,往往会涉及到两个动作,一个是扣库存,第二个是更新订单状态,库存和订单一般属于不同的数据库,需要使用分布式事务保证数据一致性。 5、常见的分布式事务解决方案 5.1、基于XA协议的两阶段提交 XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。XA实现分布式事务的原理如下: 总的来说,XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低。但是,XA也有致命的缺点,那就是性能不理想,特别是在交易下单链路,往往并发量很高,XA无法满足高并发场景。XA目前在商业数据库支持的比较理想,在mysql数据库中支持的不太理想,mysql的XA实现,没有记录prepare阶段日志,主备切换回导致主库与备库数据不一致。许多nosql也没有支持XA,这让XA的应用场景变得非常狭隘。 5.2、消息事务+最终一致性 所谓的消息事务就是基于消息中间件的两阶段提交,本质上是对消息中间件的一种特殊利用,它是将本地事务和发消息放在了一个分布式事务里,保证要么本地操作成功成功并且对外发消息成功,要么两者都失败,开源的RocketMQ就支持这一特性,具体原理如下: 1、A系统向消息中间件发送一条预备消息 2、消息中间件保存预备消息并返回成功 3、A执行本地事务 4、A发送提交消息给消息中间件 通过以上4步完成了一个消息事务。对于以上的4个步骤,每个步骤都可能产生错误,下面一一分析: 步骤一出错,则整个事务失败,不会执行A的本地操作 步骤二出错,则整个事务失败,不会执行A的本地操作 步骤三出错,这时候需要回滚预备消息,怎么回滚?答案是A系统实现一个消息中间件的回调接口,消息中间件会去不断执行回调接口,检查A事务执行是否执行成功,如果失败则回滚预备消息 步骤四出错,这时候A的本地事务是成功的,那么消息中间件要回滚A吗?答案是不需要,其实通过回调接口,消息中间件能够检查到A执行成功了,这时候其实不需要A发提交消息了,消息中间件可以自己对消息进行提交,从而完成整个消息事务 基于消息中间件的两阶段提交往往用在高并发场景下,将一个分布式事务拆成一个消息事务(A系统的本地操作+发消息)+B系统的本地操作,其中B系统的操作由消息驱动,只要消息事务成功,那么A操作一定成功,消息也一定发出来了,这时候B会收到消息去执行本地操作,如果本地操作失败,消息会重投,直到B操作成功,这样就变相地实现了A与B的分布式事务。原理如下: 虽然上面的方案能够完成A和B的操作,但是A和B并不是严格一致的,而是最终一致的,我们在这里牺牲了一致性,换来了性能的大幅度提升。当然,这种玩法也是有风险的,如果B一直执行不成功,那么一致性会被破坏,具体要不要玩,还是得看业务能够承担多少风险。 5.3、TCC编程模式 所谓的TCC编程模式,也是两阶段提交的一个变种。TCC提供了一个编程框架,将整个业务逻辑分为三块:Try、Confirm和Cancel三个操作。以在线下单为例,Try阶段会去扣库存,Confirm阶段则是去更新订单状态,如果更新订单失败,则进入Cancel阶段,会去恢复库存。总之,TCC就是通过代码人为实现了两阶段提交,不同的业务场景所写的代码都不一样,复杂度也不一样,因此,这种模式并不能很好地被复用。 6、总结 分布式事务,本质上是对多个数据库的事务进行统一控制,按照控制力度可以分为:不控制、部分控制和完全控制。不控制就是不引入分布式事务,部分控制就是各种变种的两阶段提交,包括上面提到的消息事务+最终一致性、TCC模式,而完全控制就是完全实现两阶段提交。部分控制的好处是并发量和性能很好,缺点是数据一致性减弱了,完全控制则是牺牲了性能,保障了一致性,具体用哪种方式,最终还是取决于业务场景。作为技术人员,一定不能忘了技术是为业务服务的,不要为了技术而技术,针对不同业务进行技术选型也是一种很重要的能力! 原文链接:http://www.codeceo.com/article/distributed-transaction.html 作者:吴极心

优秀的个人博客,低调大师

深入理解 Java 动态代理机制

Java 有两种代理方式,一种是静态代理,另一种是动态代理。对于静态代理,其实就是通过依赖注入,对对象进行封装,不让外部知道实现的细节。很多 API 就是通过这种形式来封装的。 代理模式结构图(图片来自《大话设计模式》) 下面看下两者在概念上的解释: 静态代理 静态代理类:由程序员创建或者由第三方工具生成,再进行编译;在程序运行之前,代理类的.class文件已经存在了。 静态代理类通常只代理一个类。 静态代理事先知道要代理的是什么。 动态代理 动态代理类:在程序运行时,通过反射机制动态生成。 动态代理类通常代理接口下的所有类。 动态代理事先不知道要代理的是什么,只有在运行的时候才能确定。 动态代理的调用处理程序必须事先InvocationHandler接口,及使用Proxy类中的newProxyInstance方法动态的创建代理类。 Java动态代理只能代理接口,要代理类需要使用第三方的CLIGB等类库。 动态代理的好处 Java动态代理的优势是实现无侵入式的代码扩展,也就是方法的增强;让你可以在不用修改源码的情况下,增强一些方法;在方法的前后你可以做你任何想做的事情(甚至不去执行这个方法就可以)。此外,也可以减少代码量,如果采用静态代理,类的方法比较多的时候,得手写大量代码。 动态代理实例 静态代理的实例这里就不说了,比较简单。在 java 的 java.lang.reflect 包下提供了一个 Proxy 类和一个 InvocationHandler 接口,通过这个类和这个接口可以生成 JDK 动态代理类和动态代理对象。下面讲讲动态代理的实现。 先定义一个接口: publicinterfacePerson{ voidsetName(Stringname); } 再定义一个学生 Student 类来实现 Person 接口,每一个学生都有一个自己的名字: publicclassStudentimplementsPerson{ privateStringmName; publicStudent(Stringname){ mName=name; } publicvoidsetName(Stringname){ mName=name; } } Student 类中,定义了一个私有变量 mName,用来表示 Student 的名字。接下去定义一个代理 handler,就是用来帮我们处理代理的 : publicclassPersonHandler<T>implementsInvocationHandler{ //代理的目标对象 privateTmTarget; publicPersonHandler(Ttarget){ mTarget=target; } @Override publicObjectinvoke(Objecto,Methodmethod,Object[]objects)throwsThrowable{ //调用开始前的操作 ProxyUtil.start(); //调用方法,通过反射的形式来调用mTarget的method Objectresult=method.invoke(mTarget,objects); //打印改名前的名字 ProxyUtil.log(objects[0].toString()); //调用结束后的操作 ProxyUtil.finish(); returnresult; } } 可以发现,我们在调用代码前后都做了一些操作,甚至可以直接拦截该方法,不让其运行。其中定义了一个 ProxyUtil 类,方便我们做一些操作: publicclassProxyUtil{ privatestaticfinalStringTAG="ProxyUtil"; publicstaticvoidstart(){ Log.d(TAG,"start:"+System.currentTimeMillis()); } publicstaticvoidfinish(){ Log.d(TAG,"finish:"+System.currentTimeMillis()); } publicstaticvoidlog(Stringname){ Log.d(TAG,"log:"+name); } } 接下去开始编写代理的实现: publicclassMainActivityextendsAppCompatActivity{ @Override protectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //创建一个实例对象,这个对象是被代理的对象 Personzhangsan=newStudent("张三"); //创建一个与代理对象相关联的InvocationHandler PersonHandlerstuHandler=newPersonHandler<>(zhangsan); //创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法 PersonstuProxy=(Person)Proxy.newProxyInstance(Person.class.getClassLoader(),newClass<?>[]{Person.class},stuHandler); //代理执行setName的方法 stuProxy.setName("王五"); } 看下打印输出: 可以发现代理成功了。并且我们在调用方式的之前之后,都做了一些操作。Spring 的 AOP 其就是通过动态代理的机制实现的。 其中,我们将 stuProxy 的类名打印出来: Log.d(TAG,"onCreate:"+stuProxy.getClass().getCanonicalName()); 发现其名字竟然是 $Proxy0。具体原因下面会解释。 源码分析 上面我们利用 Proxy 类的 newProxyInstance 方法创建了一个动态代理对象,查看该方法的源码: /** *Returnsaninstanceofaproxyclassforthespecifiedinterfaces *thatdispatchesmethodinvocationstothespecifiedinvocation *handler. * *<p>{@codeProxy.newProxyInstance}throws *{@codeIllegalArgumentException}forthesamereasonsthat *{@codeProxy.getProxyClass}does. * *@paramloadertheclassloadertodefinetheproxyclass *@paraminterfacesthelistofinterfacesfortheproxyclass *toimplement *@paramhtheinvocationhandlertodispatchmethodinvocationsto *@returnaproxyinstancewiththespecifiedinvocationhandlerofa *proxyclassthatisdefinedbythespecifiedclassloader *andthatimplementsthespecifiedinterfaces *@throwsIllegalArgumentExceptionifanyoftherestrictionsonthe *parametersthatmaybepassedto{@codegetProxyClass} *areviolated *@throwsSecurityExceptionifasecuritymanager,<em>s</em>,ispresent *andanyofthefollowingconditionsismet: *<ul> *<li>thegiven{@codeloader}is{@codenull}and *thecaller'sclassloaderisnot{@codenull}andthe *invocationof{@linkSecurityManager#checkPermission *s.checkPermission}with *{@codeRuntimePermission("getClassLoader")}permission *deniesaccess;</li> *<li>foreachproxyinterface,{@codeintf}, *thecaller'sclassloaderisnotthesameasoran *ancestoroftheclassloaderfor{@codeintf}and *invocationof{@linkSecurityManager#checkPackageAccess *s.checkPackageAccess()}deniesaccessto{@codeintf};</li> *<li>anyofthegivenproxyinterfacesisnon-publicandthe *callerclassisnotinthesame{@linkplainPackageruntimepackage} *asthenon-publicinterfaceandtheinvocationof *{@linkSecurityManager#checkPermissions.checkPermission}with *{@codeReflectPermission("newProxyInPackage.{packagename}")} *permissiondeniesaccess.</li> *</ul> *@throwsNullPointerExceptionifthe{@codeinterfaces}array *argumentoranyofitselementsare{@codenull},or *iftheinvocationhandler,{@codeh},is *{@codenull} */ @CallerSensitive publicstaticObjectnewProxyInstance(ClassLoaderloader, Class<?>[]interfaces, InvocationHandlerh) throwsIllegalArgumentException { //判空,判断h对象是否为空,为空就抛出NullPointerException Objects.requireNonNull(h); finalClass<?>[]intfs=interfaces.clone(); finalSecurityManagersm=System.getSecurityManager(); if(sm!=null){ //进行包访问权限、类加载器等权限检查 checkProxyAccess(Reflection.getCallerClass(),loader,intfs); } /* *Lookuporgeneratethedesignatedproxyclass. */ Class<?>cl=getProxyClass0(loader,intfs); /* *Invokeitsconstructorwiththedesignatedinvocationhandler. */ try{ if(sm!=null){ checkNewProxyPermission(Reflection.getCallerClass(),cl); } finalConstructor<?>cons=cl.getConstructor(constructorParams); finalInvocationHandlerih=h; if(!Modifier.isPublic(cl.getModifiers())){ AccessController.doPrivileged(newPrivilegedAction<Void>(){ publicVoidrun(){ cons.setAccessible(true); returnnull; } }); } returncons.newInstance(newObject[]{h}); }catch(IllegalAccessException|InstantiationExceptione){ thrownewInternalError(e.toString(),e); }catch(InvocationTargetExceptione){ Throwablet=e.getCause(); if(tinstanceofRuntimeException){ throw(RuntimeException)t; }else{ thrownewInternalError(t.toString(),t); } }catch(NoSuchMethodExceptione){ thrownewInternalError(e.toString(),e); } } 在生成代理类的过程中,会进行一些列检查,比如访问权限之类的。接下去我们来看 getProxyClass0 方法的源码: /** *Generateaproxyclass.MustcallthecheckProxyAccessmethod *toperformpermissionchecksbeforecallingthis. */ privatestaticClass<?>getProxyClass0(ClassLoaderloader, Class<?>...interfaces){ //数量超过65535就抛出异常,665535这个就不用说了吧 if(interfaces.length>65535){ thrownewIllegalArgumentException("interfacelimitexceeded"); } //Iftheproxyclassdefinedbythegivenloaderimplementing //thegiveninterfacesexists,thiswillsimplyreturnthecachedcopy; //otherwise,itwillcreatetheproxyclassviatheProxyClassFactory returnproxyClassCache.get(loader,interfaces); } 最后发现会对生成的代理类进行缓存,有了,就不直接返回,没有的,还得生成代理类,我们继续往下走: proxyClassCache=newWeakCache<>(newKeyFactory(),newProxyClassFactory()); 关键点在于 ProxyClassFactory 这个类,从名字也可以猜出来这个类的作用。看看代码: /** *Afactoryfunctionthatgenerates,definesandreturnstheproxyclassgiven *theClassLoaderandarrayofinterfaces. */ privatestaticfinalclassProxyClassFactory implementsBiFunction<ClassLoader,Class<?>[],Class<?>> { //prefixforallproxyclassnames定义前缀 privatestaticfinalStringproxyClassNamePrefix="$Proxy"; //nextnumbertouseforgenerationofuniqueproxyclassnames原子操作,适用于多线程 privatestaticfinalAtomicLongnextUniqueNumber=newAtomicLong(); @Override publicClass<?>apply(ClassLoaderloader,Class<?>[]interfaces){ Map<Class<?>,Boolean>interfaceSet=newIdentityHashMap<>(interfaces.length); for(Class<?>intf:interfaces){ /* *Verifythattheclassloaderresolvesthenameofthis *interfacetothesameClassobject. */ Class<?>interfaceClass=null; try{ //通过反射获取到接口类 interfaceClass=Class.forName(intf.getName(),false,loader); }catch(ClassNotFoundExceptione){ } //所得到的接口类与传进来的不相等,说明不是同一个类 if(interfaceClass!=intf){ thrownewIllegalArgumentException( intf+"isnotvisiblefromclassloader"); } /* *VerifythattheClassobjectactuallyrepresentsan *interface. */ if(!interfaceClass.isInterface()){ thrownewIllegalArgumentException( interfaceClass.getName()+"isnotaninterface"); } /* *Verifythatthisinterfaceisnotaduplicate. */ if(interfaceSet.put(interfaceClass,Boolean.TRUE)!=null){ thrownewIllegalArgumentException( "repeatedinterface:"+interfaceClass.getName()); } } StringproxyPkg=null;//packagetodefineproxyclassin intaccessFlags=Modifier.PUBLIC|Modifier.FINAL; /* *Recordthepackageofanon-publicproxyinterfacesothatthe *proxyclasswillbedefinedinthesamepackage.Verifythat *allnon-publicproxyinterfacesareinthesamepackage. */ for(Class<?>intf:interfaces){ intflags=intf.getModifiers(); if(!Modifier.isPublic(flags)){ accessFlags=Modifier.FINAL; Stringname=intf.getName(); intn=name.lastIndexOf('.'); Stringpkg=((n==-1)?"":name.substring(0,n+1)); if(proxyPkg==null){ proxyPkg=pkg; }elseif(!pkg.equals(proxyPkg)){ thrownewIllegalArgumentException( "non-publicinterfacesfromdifferentpackages"); } } } if(proxyPkg==null){ //ifnonon-publicproxyinterfaces,usecom.sun.proxypackage proxyPkg=ReflectUtil.PROXY_PACKAGE+"."; } /* *Chooseanamefortheproxyclasstogenerate. */ longnum=nextUniqueNumber.getAndIncrement(); //生产代理类的名字 StringproxyName=proxyPkg+proxyClassNamePrefix+num; /* *Generatethespecifiedproxyclass. */ byte[]proxyClassFile=ProxyGenerator.generateProxyClass( proxyName,interfaces,accessFlags); try{ returndefineClass0(loader,proxyName, proxyClassFile,0,proxyClassFile.length); }catch(ClassFormatErrore){ /* *AClassFormatErrorheremeansthat(barringbugsinthe *proxyclassgenerationcode)therewassomeother *invalidaspectoftheargumentssuppliedtotheproxy *classcreation(suchasvirtualmachinelimitations *exceeded). */ thrownewIllegalArgumentException(e.toString()); } } } 这里会通过反射获取接口的各种修饰符,包名等,然后根据规则命名代理类,最后调用 ProxyGenerator.generateProxyClass 生成了代理类。 ProxyGenerator.generateProxyClass 具体实现在 eclipse 上打开后,说是找不到源码: 不过,从其他地方找到了部分代码: publicstaticbyte[]generateProxyClass(finalStringname, Class[]interfaces) { ProxyGeneratorgen=newProxyGenerator(name,interfaces); //这里动态生成代理类的字节码,由于比较复杂就不进去看了 finalbyte[]classFile=gen.generateClassFile(); //如果saveGeneratedFiles的值为true,则会把所生成的代理类的字节码保存到硬盘上 if(saveGeneratedFiles){ java.security.AccessController.doPrivileged( newjava.security.PrivilegedAction<Void>(){ publicVoidrun(){ try{ FileOutputStreamfile= newFileOutputStream(dotToSlash(name)+".class"); file.write(classFile); file.close(); returnnull; }catch(IOExceptione){ thrownewInternalError( "I/Oexceptionsavinggeneratedfile:"+e); } } }); } //返回代理类的字节码 returnclassFile; } 我们可以自己试试 ProxyGenerator.generateProxyClass 的功能。 publicclassProxyGeneratorUtils{ /** *把代理类的字节码写到硬盘上 *@parampath保存路径 */ publicstaticvoidwriteProxyClassToHardDisk(Stringpath){ //获取代理类的字节码 byte[]classFile=ProxyGenerator.generateProxyClass("$Proxy11",Student.class.getInterfaces()); FileOutputStreamout=null; try{ out=newFileOutputStream(path); out.write(classFile); out.flush(); }catch(Exceptione){ e.printStackTrace(); }finally{ try{ out.close(); }catch(IOExceptione){ e.printStackTrace(); } } } } main 方法里面进行调用 : publicclassMain{ publicstaticvoidmain(String[]args){ ProxyGeneratorUtils.writeProxyClassToHardDisk("$Proxy0.class"); } } 可以发现,在根目录下生成了一个 $Proxy0.class 文件,文件内容反编译后如下: importjava.lang.reflect.InvocationHandler; importjava.lang.reflect.Method; importjava.lang.reflect.Proxy; importjava.lang.reflect.UndeclaredThrowableException; importproxy.Person; publicfinalclass$Proxy0extendsProxyimplementsPerson { privatestaticMethodm1; privatestaticMethodm2; privatestaticMethodm3; privatestaticMethodm0; /** *注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白 *为何代理对象调用方法都是执行InvocationHandler中的invoke方法,而InvocationHandler又持有一个 *被代理对象的实例,不禁会想难道是....?没错,就是你想的那样。 * *super(paramInvocationHandler),是调用父类Proxy的构造方法。 *父类持有:protectedInvocationHandlerh; *Proxy构造方法: *protectedProxy(InvocationHandlerh){ *Objects.requireNonNull(h); *this.h=h; *} * */ public$Proxy0(InvocationHandlerparamInvocationHandler) throws { super(paramInvocationHandler); } //这个静态块本来是在最后的,我把它拿到前面来,方便描述 static { try { //看看这儿静态块儿里面有什么,是不是找到了giveMoney方法。请记住giveMoney通过反射得到的名字m3,其他的先不管 m1=Class.forName("java.lang.Object").getMethod("equals",newClass[]{Class.forName("java.lang.Object")}); m2=Class.forName("java.lang.Object").getMethod("toString",newClass[0]); m3=Class.forName("proxy.Person").getMethod("giveMoney",newClass[0]); m0=Class.forName("java.lang.Object").getMethod("hashCode",newClass[0]); return; } catch(NoSuchMethodExceptionlocalNoSuchMethodException) { thrownewNoSuchMethodError(localNoSuchMethodException.getMessage()); } catch(ClassNotFoundExceptionlocalClassNotFoundException) { thrownewNoClassDefFoundError(localClassNotFoundException.getMessage()); } } /** * *这里调用代理对象的giveMoney方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。 *this.h.invoke(this,m3,null);这里简单,明了。 *来,再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象, *再联系到InvacationHandler中的invoke方法。嗯,就是这样。 */ publicfinalvoidgiveMoney() throws { try { this.h.invoke(this,m3,null); return; } catch(Error|RuntimeExceptionlocalError) { throwlocalError; } catch(ThrowablelocalThrowable) { thrownewUndeclaredThrowableException(localThrowable); } } //注意,这里为了节省篇幅,省去了toString,hashCode、equals方法的内容。原理和giveMoney方法一毛一样。 } jdk 为我们的生成了一个叫 $Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。 我们可以对 InvocationHandler 看做一个中介类,中介类持有一个被代理对象,在 invoke 方法中调用了被代理对象的相应方法,而生成的代理类中持有中介类,因此,当我们在调用代理类的时候,就是再调用中介类的 invoke 方法,通过反射转为对被代理对象的调用。 代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的 invoke 方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。 生成的代理类:$Proxy0 extends Proxy implements Person,我们看到代理类继承了 Proxy 类,所以也就决定了 java 动态代理只能对接口进行代理,Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理。

优秀的个人博客,低调大师

我理解的foreach, for in, for of 之间的异同

我所lforEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。 注意:forEach() 对于空数组是不会执行回调函数的。 示例代码: var arr = [4, 9, 16, 25]; arr.forEach(function(value, index) { console.log(index+': '+value+'\n'); }); 结果如下: for/in 语句用于循环对象属性。 循环中的代码每执行一次,就会对数组的元素或者对象的属性进行一次操作。 示例代码: var person = { name: 'jack', age: 18, gender: 'male' }; for(key in person) { console.log(key+': '+person[key]+'\n'); } 结果如下: 需要注意的是,如果使用for in语句遍历数组,可能会出现以下问题: 1. 索引为字符串类型,不能直接进行几何运算。 2. 遍历顺序有可能不是按照实际数组的内部顺序。 3. 使用for in会遍历数组所有的可枚举属性,包括原型。 如下所示: var arr = [12, 43, 19]; Array.prototype.index = 110;//这里的原型属性也会被打印出来 for(var index in arr) { console.log(index+': '+arr[index]+'\n'); } 运行结果: for of语句和for in 语句相比使用更方便,它是ES6新提出的遍历数组的方式, 具体使用: var arr = [12, 43, 19]; Array.prototype.index = 110;//此原型属性不会被打印出来 for(var value of arr) { console.log(value+'\n'); } 结果如下: 综述:foreach更多的用来遍历数组,但使用比较复杂;for in 一般常用来遍历 对象;for of 用来遍历数组非常方便且比较安全。 如果觉得写的不错,可以点个小小的赞或者关注我,如果有哪里写得不好,欢迎大家指出、

优秀的个人博客,低调大师

深入理解 RPC 消息协议设计

本节我们开始讲解 RPC 的消息协议设计背后的基本原理,了解 RPC 的协议开发背后有哪些需要考虑的基本点。在通晓原理之后,我们就可以自己设计一套协议来开发属于自己的 RPC 系统。 本节主要涉及的知识点和它们之见的关系如下图: 对于一串消息流,我们必须能确定消息边界,提取出单条消息的字节流片段,然后对这个片段按照一定的规则进行反序列化来生成相应的消息对象。 消息表示指的是序列化后的消息字节流在直观上的表现形式,它看起来是对人类友好还是对计算机友好。文本形式对人类友好,二进制形式对计算机友好。 每个消息都有其内部字段结构,结构构成了消息内部的逻辑规则,程序要按照结构规则来决定字段序列化的顺序。 接下来,我们初步详细拆解。 消息边界 RPC 需要在一条 TCP 链接上进行多次消息传递。在连续的两条消息之间必须有明确的分割规则,以便接收端可以将消息分割开来,这里的接收端可以是 RPC 服务器接收请求,也可以是 RPC 客户端接收响应。 基于 TCP 链接之上的单条消息如果过大,就会被网络协议栈拆分为多个数据包进行传送。如果消息过小,网络协议栈可能会将多个消息组合成一个数据包进行发送。对于接收端来说它看到的只是一串串的字节数组,如果没有明确的消息边界规则,接收端是无从知道这一串字节数组究竟是包含多条消息还是只是某条消息的一部分。 比较常用的两种分割方式是特殊分割符法和长度前缀法。 消息发送端在每条消息的末尾追加一个特殊的分割符,并且保证消息中间的数据不能包含特殊分割符。比如最为常见的分割符是 。当接收端遍历字节数组时发现了 ,就立即可以断定 之前的字节数组是一条完整的消息,可以传递到上层逻辑继续进行处理。HTTP 和 Redis 协议就大量使用了 分割符。此种消息一般要求消息体的内容是文本消息。 消息发送端在每条消息的开头增加一个 4 字节长度的整数值,标记消息体的长度。这样消息接受者首先读取到长度信息,然后再读取相应长度的字节数组就可以将一个完整的消息分离出来。此种消息比较常用于二进制消息。 基于特殊分割符法的优点在于消息的可读性比较强,可以直接看到消息的文本内容,缺点是不适合传递二进制消息,因为二进制的字节数组里面很容易就冒出连续的两个字节内容正好就是 分割符的 ascii 值。如果需要传递的话,一般是对二进制进行 base64 编码转变成普通文本消息再进行传送。 基于长度前缀法的优点和缺点同特殊分割符法正好是相反的。长度前缀法因为适用于二进制协议,所以可读性很差。但是对传递的内容本身没有特殊限制,文本和内容皆可以传输,不需要进行特殊处理。HTTP 协议的 Content-Length 头信息用来标记消息体的长度,这个也可以看成是长度前缀法的一种应用。 HTTP 协议是一种基于特殊分割符和长度前缀法的混合型协议。比如 HTTP 的消息头采用的是纯文本外加 分割符,而消息体则是通过消息头中的 Content-Type 的值来决定长度。HTTP 协议虽然被称之为文本传输协议,但是也可以在消息体中传输二进制数据数据的,例如音视频图像,所以 HTTP 协议被称之为「超文本」传输协议。 消息的结构 每条消息都有它包含的语义结构信息,有些消息协议的结构信息是显式的,还有些是隐式的。比如 json 消息,它的结构就可以直接通过它的内容体现出来,所以它是一种显式结构的消息协议。 json 这种直观的消息协议的可读性非常棒,但是它的缺点也很明显,有太多的冗余信息。比如每个字符串都使用双引号来界定边界,key/value 之间必须有冒号分割,对象之间必须使用大括号分割等等。这些还只是冗余的小头,最大的冗余还在于连续的多条 json 消息即使结构完全一样,仅仅只是 value 的值不一样,也需要发送同样的 key 字符串信息。 消息的结构在同一条消息通道上是可以复用的,比如在建立链接的开始 RPC 客户端和服务器之间先交流协商一下消息的结构,后续发送消息时只需要发送一系列消息的 value 值,接收端会自动将 value 值和相应位置的 key 关联起来,形成一个完成的结构消息。在 Hadoop 系统中广泛使用的 avro 消息协议就是通过这种方式实现的,在 RPC 链接建立之处就开始交流消息的结构,后续消息的传递就可以节省很多流量。 消息的隐式结构一般是指那些结构信息由代码来约定的消息协议,在 RPC 交互的消息数据中只是纯粹的二进制数据,由代码来确定相应位置的二进制是属于哪个字段。比如下面的这段代码 如果纯粹看消息内容是无法知道节点消息内容中的哪些字节的含义,它的消息结构是通过代码的结构顺序来确定的。这种隐式的消息的优点就在于节省传输流量,它完全不需要传输结构信息。 消息压缩 如果消息的内容太大,就要考虑对消息进行压缩处理,这可以减轻网络带宽压力。但是这同时也会加重 CPU 的负担,因为压缩算法是 CPU 计算密集型操作,会导致操作系统的负载加重。所以,最终是否进行消息压缩,一定要根据业务情况加以权衡。 如果确定压缩,那么在选择压缩算法包时,务必挑选那些底层用 C 语言实现的算法库,因为 Python 的字节码执行起来太慢了。比较流行的消息压缩算法有 Google 的 snappy 算法,它的运行性能非常好,压缩比例虽然不是最优的,但是离最优的差距已经不是很大。阿里的 SOFA RPC 就使用了 snappy 作为协议层压缩算法。 流量的极致优化 开源的流行 RPC 消息协议往往对消息流量优化到了极致,它们通过这种方式来打动用户,吸引用户来使用它们。比如对于一个整形数字,一般使用 4 个字节来表示一个整数值。 但是经过研究发现,消息传递中大部分使用的整数值都是很小的非负整数,如果全部使用 4 个字节来表示一个整数会很浪费。所以就发明了一个类型叫变长整数varint。数值非常小时,只需要使用一个字节来存储,数值稍微大一点可以使用 2 个字节,再大一点就是 3 个字节,它还可以超过 4 个字节用来表达长整形数字。 其原理也很简单,就是保留每个字节的最高位的 bit 来标识是否后面还有字节,1 表示还有字节需要继续读,0 表示到读到当前字节就结束。 那如果是负数该怎么办呢?-1 的 16 进制数是 0xFFFFFFFF,如果要按照这个编码那岂不是要 6 个字节才能存的下。-1 也是非常常见的整数啊。 于是 zigzag 编码来了,专门用来解决负数问题。zigzag 编码将整数范围一一映射到自然数范围,然后再进行 varint 编码。 zigzag 将负数编码成正奇数,正数编码成偶数。解码的时候遇到偶数直接除 2 就是原值,遇到奇数就加 1 除 2 再取负就是原值。 小结 现在我们知道了 RPC 消息结构的设计原理,遵循这些基本方法,就可以创造出一个又一个不同的消息协议。 欢迎工作一到五年的Java工程师朋友们加入Java架构开发:860113481 群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

优秀的个人博客,低调大师

深入理解HBase的系统架构

HBase的构成 物理上来说,HBase是由三种类型的服务器以主从模式构成的。这三种服务器分别是:Region server,HBase HMaster,ZooKeeper。 其中Region server负责数据的读写服务。用户通过沟通Region server来实现对数据的访问。 HBase HMaster负责Region的分配及数据库的创建和删除等操作。 ZooKeeper作为HDFS的一部分,负责维护集群的状态(某台服务器是否在线,服务器之间数据的同步操作及master的选举等)。 另外,Hadoop DataNode负责存储所有Region Server所管理的数据。HBase中的所有数据都是以HDFS文件的形式存储的。出于使Region server所管理的数据更加本地化的考虑,Region server是根据DataNode分布的。HBase的数据在写入的时候都存储在本地。但当某一个region被移除或被重新分配的时候,就可能产生数据不在本地的情况。这种情况只有在所谓的compaction之后才能解决。 NameNode负责维护构成文件的所有物理数据块的元信息(metadata)。 HBase结构如下图所示: Regions HBase中的表是根据row key的值水平分割成所谓的region的。一个region包含表中所有row key位于region的起始键值和结束键值之间的行。集群中负责管理Region的结点叫做Region server。Region server负责数据的读写。每一个Region server大约可以管理1000个region。Region的结构如下图所示:HBase的HMaster HMaster负责region的分配,数据库的创建和删除操作。 具体来说,HMaster的职责包括: 调控Region server的工作 在集群启动的时候分配region,根据恢复服务或者负载均衡的需要重新分配region。 监控集群中的Region server的工作状态。(通过监听zookeeper对于ephemeral node状态的通知)。 管理数据库 提供创建,删除或者更新表格的接口。 HMaster的工作如下图所示:ZooKeeper HBase利用ZooKeeper维护集群中服务器的状态并协调分布式系统的工作。ZooKeeper维护服务器是否存活,是否可访问的状态并提供服务器故障/宕机的通知。ZooKeeper同时还使用一致性算法来保证服务器之间的同步。同时也负责Master选举的工作。需要注意的是要保证良好的一致性及顺利的Master选举,集群中的服务器数目必须是奇数。例如三台或五台。 ZooKeeper的工作如下图所示:HBase各组成部分之间的合作 ZooKeeper用来协调分布式系统的成员之间共享的状态信息。Region Server及HMaster也与ZooKeeper连接。ZooKeeper通过心跳信息为活跃的连接维持相应的ephemeral node。如下图所示:每一个Region server都在ZooKeeper中创建相应的ephemeral node。HMaster通过监控这些ephemeral node的状态来发现正常工作的或发生故障下线的Region server。HMaster之间通过互相竞争创建ephemeral node进行Master选举。ZooKeeper会选出区中第一个创建成功的作为唯一一个活跃的HMaster。活跃的HMaster向ZooKeeper发送心跳信息来表明自己在线的状态。不活跃的HMaster则监听活跃HMaster的状态,并在活跃HMaster发生故障下线之后重新选举,从而实现了HBase的高可用性。 如果Region server或者HMaster不能成功向ZooKeeper发送心跳信息,则其与ZooKeeper的连接超时之后与之相应的ephemeral node就会被删除。监听ZooKeeper状态的其他节点就会得到相应node不存在的信息,从而进行相应的处理。活跃的HMaster监听Region Server的信息,并在其下线后重新分配Region server来恢复相应的服务。不活跃的HMaster监听活跃HMaster的信息,并在起下线后重新选出活跃的HMaster进行服务。 HBase的第一次读写 HBase中有一个特殊的起目录作用的表格,称为META table。META table中保存集群region的地址信息。ZooKeeper中会保存META table的位置。 当用户第一次想HBase中进行读或写操作时,以下步骤将被执行: 1.客户从ZooKeeper中得到保存META table的Region server的信息。2.客户向该Region server查询负责管理自己想要访问的row key的所在的region的Region server的地址。客户会缓存这一信息以及META table所在位置的信息。3.客户与负责其row所在region的Region Server通信,实现对该行的读写操作。 在未来的读写操作中,客户会根据缓存寻找相应的Region server地址。除非该Region server不再可达。这时客户会重新访问META table并更新缓存。这一过程如下图所示:HBase的META table META table中保存了HBase中所有region的信息。 META table的格式类似于B tree。 META table的结构如下: 键:region的起始键,region id。 值:Region server如下图所示: Region Server的组成 运行在HDFS DataNode上的Region server包含如下几个部分: WAL:既Write Ahead Log。WAL是HDFS分布式文件系统中的一个文件。WAL用来存储尚未写入永久性存储区中的新数据。WAL也用来在服务器发生故障时进行数据恢复。 Block Cache:Block cache是读缓存。Block cache将经常被读的数据存储在内存中来提高读取数据的效率。当Block cache的空间被占满后,其中被读取频率最低的数据将会被杀出。 MemStore:MemStore是写缓存。其中存储了从WAL中写入但尚未写入硬盘的数据。MemStore中的数据在写入硬盘之前会先进行排序操作。每一个region中的每一个column family对应一个MemStore。 Hfiles:Hfiles存在于硬盘上,根据排序号的键存储数据行。Region server的结构如下图所示: **HBase的写操作步骤 步骤一** 当HBase的用户发出一个PUT请求时(也就是HBase的写请求),HBase进行处理的第一步是将数据写入HBase的write-ahead log(WAL)中。 WAL文件是顺序写入的,也就是所有新添加的数据都被加入WAL文件的末尾。WAL文件存在硬盘上。 当server出现问题之后,WAL可以被用来恢复尚未写入HBase中的数据(因为WAL是保存在硬盘上的)。如下图所示: 步骤二 当数据被成功写入WAL后,HBase将数据存入MemStore。这时HBase就会通知用户PUT操作已经成功了。 过程如下图所示:HBase的MemStore Memstore存在于内存中,其中存储的是按键排好序的待写入硬盘的数据。数据也是按键排好序写入HFile中的。每一个Region中的每一个Column family对应一个Memstore文件。因此对数据的更新也是对应于每一个Column family。 如下图所示:HBase Region Flush 当MemStore中积累了足够多的数据之后,整个Memcache中的数据会被一次性写入到HDFS里的一个新的HFile中。因此HDFS中一个Column family可能对应多个HFile。这个HFile中包含了相应的cell,或者说键值的实例。这些文件随着MemStore中积累的对数据的操作被flush到硬盘上而创建。 需要注意的是,MemStore存储在内存中,这也是为什么HBase中Column family的数目有限制的原因。每一个Column family对应一个MemStore,当MemStore存满之后,里面所积累的数据就会一次性flush到硬盘上。同时,为了使HDFS能够知道当前哪些数据已经被存储了,MemStore中还保存最后一次写操作的序号。 每个HFile中最大的序号作为meta field存储在其中,这个序号标明了之前的数据向硬盘存储的终止点和接下来继续存储的开始点。当一个region启动的时候,它会读取每一个HFile中的序号来得知当前region中最新的操作序号是什么(最大的序号)。 如下图所示:HFile HBase中的键值数据对存储在HFile中。上面已经说过,当MemStore中积累足够多的数据的时候就会将其中的数据整个写入到HDFS中的一个新的HFile中。因为MemStore中的数据已经按照键排好序,所以这是一个顺序写的过程。由于顺序写操作避免了磁盘大量寻址的过程,所以这一操作非常高效。 如下图所示:HFile的结构 HFile中包含了一个多层索引系统。这个多层索引是的HBase可以在不读取整个文件的情况下查找数据。这一多层索引类似于一个B+树。 键值对根据键大小升序排列。 索引指向64KB大小的数据块。 每一个数据块还有其相应的叶索引(leaf-index)。 每一个数据块的最后一个键作为中间索引(intermediate index)。 根索引(root index)指向中间索引。 文件结尾指向meta block。因为meta block是在数据写入硬盘操作的结尾写入该文件中的。文件的结尾同时还包含一些别的信息。比如bloom filter及时间信息。Bloom filter可以帮助HBase加速数据查询的速度。因为HBase可以利用Bloom filter跳过不包含当前查询的键的文件。时间信息则可以帮助HBase在查询时跳过读操作所期望的时间区域之外的文件。 如下图所示:HFile的索引 HFile的索引在HFile被打开时会被读取到内存中。这样就可以保证数据检索只需一次硬盘查询操作。 如下图所示:HBase的读合并(Read Merge)以及读放大(Read amplification) 通过上面的论述,我们已经知道了HBase中对应于某一行数据的cell可能位于多个不同的文件或存储介质中。比如已经存入硬盘的行位于硬盘上的HFile中,新加入或更新的数据位于内存中的MemStore中,最近读取过的数据则位于内存中的Block cache中。所以当我们读取某一行的时候,为了返回相应的行数据,HBase需要根据Block cache,MemStore以及硬盘上的HFile中的数据进行所谓的读合并操作。 1.HBase会首先从Block cache(HBase的读缓存)中寻找所需的数据。2.接下来,HBase会从MemStore中寻找数据。因为作为HBase的写缓存,MemStore中包含了最新版本的数据。3.如果HBase从Block cache和MemStore中没有找到行所对应的cell所有的数据,系统会接着根据索引和bloom filter从相应的HFile中读取目标行的cell的数据。 如下图所示:这里一个需要注意的地方是所谓的读放大效应(Read amplification)。根据前文所说,一个MemStore对应的数据可能存储于多个不同的HFile中(由于多次的flush),因此在进行读操作的时候,HBase可能需要读取多个HFile来获取想要的数据。这会影响HBase的性能表现。 如下图所示:HBase的Compaction Minor Compaction HBase会自动选取一些较小的HFile进行合并,并将结果写入几个较大的HFile中。这一过程称为Minor compaction。Minor compaction通过Merge sort的形式将较小的文件合并为较大的文件,从而减少了存储的HFile的数量,提升HBase的性能。 这一过程如下图所示:Major Compaction 所谓Major Compaction指的是HBase将对应于某一个Column family的所有HFile重新整理并合并为一个HFile,并在这一过程中删除已经删除或过期的cell,更新现有cell的值。这一操作大大提升读的效率。但是因为Major compaction需要重新整理所有的HFile并写入一个HFile,这一过程包含大量的硬盘I/O操作以及网络数据通信。这一过程也称为写放大(Write amplification)。在Major compaction进行的过程中,当前Region基本是处于不可访问的状态。 Major compaction可以配置在规定的时间自动运行。为避免影响业务,Major compaction一般安排在夜间或周末进行。 需要注意的一点事,Major compaction会将当前Region所服务的所有远程数据下载到本地Region server上。这些远程数据可能由于服务器故障或者负载均衡等原因而存储在于远端服务器上。 这一过程如下图所示:Region的分割(Region split) 首先我们快速复习一下Region: HBase中的表格可以根据行键水平分割为一个或几个region。每个region中包含了一段处于某一起始键值和终止键值之间的连续的行键。 每一个region的默认大小为1GB。 相应的Region server负责向客户提供访问某一region中的数据的服务。 每一个Region server能够管理大约1000个region(这些region可能来自同一个表格,也可能来自不同的表格)。 如下图所示:每一个表格最初都对应于一个region。随着region中数据量的增加,region会被分割成两个子region。每一个子region中存储原来一半的数据。同时Region server会通知HMaster这一分割。出于负载均衡的原因,HMaster可能会将新产生的region分配给其他的Region server管理(这也就导致了Region server服务远端数据的情况的产生)。 如下图所示:读操作的负载均衡(Read Load Balancing) Region的分割最初是在Region server本地发生的。但是出于负载均衡的原因,HMaster可能会将新产生的region分配给其他的Region server进行管理。这也就导致了Region server管理存储在远端服务器上的region情况的产生。这一情况会持续至下一次Major compaction之前。如上文所示,Major compaction会将任何不在本地的数据下载至本地。 也就是说,HBase中的数据在写入时总是存储在本地的。但是随着region的重新分配(由于负载均衡或数据恢复),数据相对于Region server不再一定是本地的。这种情况会在Major compaction后得到解决。 如下图所示:HDFS的数据备份(Data Replication) HDFS中所有的数据读写操作都是针对主节点进行的。HDFS会自动备份WAL和HFile。HBase以来HDFS来提供可靠的安全的数据存储。当数据被写入HDFS本地时,另外两份备份数据会分别存储在另外两台服务器上。 如下图所示:HBase的异常恢复(Crash Recovery) WAL文件和HFile都存储于硬盘上且存在备份,因此恢复它们是非常容易的。那么HBase如何恢复位于内存中的MemStore呢?当Region server宕机的时候,其所管理的region在这一故障被发现并修复之前是不可访问的。ZooKeeper负责根据服务器的心跳信息来监控服务器的工作状态。当某一服务器下线之后,ZooKeeper会发送该服务器下线的通知。HMaster收到这一通知之后会进行恢复操作。 HMaster会首先将宕机的Region server所管理的region分配给其他仍在工作的活跃的Region server。然后HMaster会将该服务器的WAL分割并分别分配给相应的新分配的Region server进行存储。新的Region server会读取并顺序执行WAL中的数据操作,从而重新创建相应的MemStore。 如下图所示:数据恢复(Data Recovery) WAL文件之中存储了一系列数据操作。每一个操作对应WAL中的一行。新的操作会顺序写在WAL文件的末尾。 那么当MemStore中存储的数据因为某种原因丢失之后应该如何恢复呢?HBase以来WAL对其进行恢复。相应的Region server会顺序读取WAL并执行其中的操作。这些数据被存入内存中当前的MemStore并排序。最终当MemStore存满之后,这些数据被flush到硬盘上。 如下图所示:Apache HBase的优缺点 优点 强一致性模型 当一个写操作得到确认时,所有的用户都将读到同一个值。 可靠的自动扩展 当region中的数据太多时会自动分割。 使用HDFS分布存储并备份数据。 内置的恢复功能 使用WAL进行数据恢复。 与Hadoop集成良好 MapReduce在HBase上非常直观。 缺点 WAL回复较慢。 异常恢复复杂且低效。 需要进行占用大量资源和大量I/O操作的Major compaction。 推荐阅读:大数据高手实战系列课程-深入剖析Hbase内核原理,互联网应用最佳实践 文章来源:https://blog.csdn.net/zougfang/article/details/80623997

优秀的个人博客,低调大师

用中文传话游戏理解 RNN

循环神经网络(RNN)是人工神经网络中非常流行的一个版本,其可以很好地处理序列数据类型。该数据类型指的是将一组数据点以特定顺序排列,从而使相关的数据点一个挨着一个,具体的例子有:股票市场价格、音频和视频数据、DNA 序列、传感器数据、自然语言文本等等。 为了直观地了解 RNN 是如何工作的,假设我们正在玩一个很受欢迎的儿童游戏——中文传话。该游戏的目的是最后一个人能准确预测出句子中所漏掉的词语,比如「The brown dog has four _____」。 中文传话游戏 玩家们需要排成一队,并且根据句子中词语的排列,每个人都会得到其中的一个词语,比如玩家 1 得到词语「the」,玩家 2 得到词语「brown」,玩家 3 得到词语「dog」,以此类推。首先,队伍中的第一个人把他听到的词语悄悄地告诉队伍中站在他身后的玩家。然后这个玩家

优秀的个人博客,低调大师

深入理解SpringBoot之自动装配

SpringBoot的自动装配是拆箱即用的基础,也是微服务化的前提。其实它并不那么神秘,我在这之前已经写过最基本的实现了,大家可以参考这篇文章。这次主要的议题是,来看看它是怎么样实现的,我们透过源代码来把握自动装配的来龙去脉。 一、自动装配过程分析 1.1、关于@SpringBootApplication 我们在编写SpringBoot项目时,@SpringBootApplication是最常见的注解了,我们可以看一下源代码: /* * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.context.TypeExcludeFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.core.annotation.AliasFor; /** * Indicates a {@link Configuration configuration} class that declares one or more * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience * annotation that is equivalent to declaring {@code @Configuration}, * {@code @EnableAutoConfiguration} and {@code @ComponentScan}. * * @author Phillip Webb * @author Stephane Nicoll * @since 1.2.0 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { /** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude") Class<?>[] exclude() default {}; /** * Exclude specific auto-configuration class names such that they will never be * applied. * @return the class names to exclude * @since 1.3.0 */ @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName") String[] excludeName() default {}; /** * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses} * for a type-safe alternative to String-based package names. * @return base packages to scan * @since 1.3.0 */ @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {}; /** * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to * scan for annotated components. The package of each class specified will be scanned. * <p> * Consider creating a special no-op marker class or interface in each package that * serves no purpose other than being referenced by this attribute. * @return base packages to scan * @since 1.3.0 */ @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") Class<?>[] scanBasePackageClasses() default {}; } View Code 这里面包含了@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan,此处@ComponentScan由于没有指定扫描包,因此它默认扫描的是与该类同级的类或者同级包下的所有类,另外@SpringBootConfiguration,通过源码得知它是一个@Configuration: /* * Copyright 2012-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.boot; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Configuration; /** * Indicates that a class provides Spring Boot application * {@link Configuration @Configuration}. Can be used as an alternative to the Spring's * standard {@code @Configuration} annotation so that configuration can be found * automatically (for example in tests). * <p> * Application should only ever include <em>one</em> {@code @SpringBootConfiguration} and * most idiomatic Spring Boot applications will inherit it from * {@code @SpringBootApplication}. * * @author Phillip Webb * @since 1.4.0 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { } View Code 由此我们可以推断出@SpringBootApplication等同于@Configuration @ComponentScan @EnableAutoConfiguration 1.2、@EnableAutoConfiguration 一旦加上此注解,那么将会开启自动装配功能,简单点讲,Spring会试图在你的classpath下找到所有配置的Bean然后进行装配。当然装配Bean时,会根据若干个(Conditional)定制规则来进行初始化。我们看一下它的源码: /* * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.io.support.SpringFactoriesLoader; /** * Enable auto-configuration of the Spring Application Context, attempting to guess and * configure beans that you are likely to need. Auto-configuration classes are usually * applied based on your classpath and what beans you have defined. For example, If you * have {@code tomcat-embedded.jar} on your classpath you are likely to want a * {@link TomcatEmbeddedServletContainerFactory} (unless you have defined your own * {@link EmbeddedServletContainerFactory} bean). * <p> * When using {@link SpringBootApplication}, the auto-configuration of the context is * automatically enabled and adding this annotation has therefore no additional effect. * <p> * Auto-configuration tries to be as intelligent as possible and will back-away as you * define more of your own configuration. You can always manually {@link #exclude()} any * configuration that you never want to apply (use {@link #excludeName()} if you don't * have access to them). You can also exclude them via the * {@code spring.autoconfigure.exclude} property. Auto-configuration is always applied * after user-defined beans have been registered. * <p> * The package of the class that is annotated with {@code @EnableAutoConfiguration}, * usually via {@code @SpringBootApplication}, has specific significance and is often used * as a 'default'. For example, it will be used when scanning for {@code @Entity} classes. * It is generally recommended that you place {@code @EnableAutoConfiguration} (if you're * not using {@code @SpringBootApplication}) in a root package so that all sub-packages * and classes can be searched. * <p> * Auto-configuration classes are regular Spring {@link Configuration} beans. They are * located using the {@link SpringFactoriesLoader} mechanism (keyed against this class). * Generally auto-configuration beans are {@link Conditional @Conditional} beans (most * often using {@link ConditionalOnClass @ConditionalOnClass} and * {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations). * * @author Phillip Webb * @author Stephane Nicoll * @see ConditionalOnBean * @see ConditionalOnMissingBean * @see ConditionalOnClass * @see AutoConfigureAfter * @see SpringBootApplication */ @SuppressWarnings("deprecation") @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(EnableAutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; /** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ Class<?>[] exclude() default {}; /** * Exclude specific auto-configuration class names such that they will never be * applied. * @return the class names to exclude * @since 1.3.0 */ String[] excludeName() default {}; } View Code 虽然根据文档注释的说明它指点我们去看EnableAutoConfigurationImportSelector。但是该类在SpringBoot1.5.X版本已经过时了,因此我们看一下它的父类AutoConfigurationImportSelector: /* * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.boot.autoconfigure; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.Aware; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.DeferredImportSelector; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** * {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration * auto-configuration}. This class can also be subclassed if a custom variant of * {@link EnableAutoConfiguration @EnableAutoConfiguration}. is needed. * * @author Phillip Webb * @author Andy Wilkinson * @author Stephane Nicoll * @author Madhura Bhave * @since 1.3.0 * @see EnableAutoConfiguration */ public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { private static final String[] NO_IMPORTS = {}; private static final Log logger = LogFactory .getLog(AutoConfigurationImportSelector.class); private ConfigurableListableBeanFactory beanFactory; private Environment environment; private ClassLoader beanClassLoader; private ResourceLoader resourceLoader; @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } try { AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); configurations = sort(configurations, autoConfigurationMetadata); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return configurations.toArray(new String[configurations.size()]); } catch (IOException ex) { throw new IllegalStateException(ex); } } protected boolean isEnabled(AnnotationMetadata metadata) { return true; } /** * Return the appropriate {@link AnnotationAttributes} from the * {@link AnnotationMetadata}. By default this method will return attributes for * {@link #getAnnotationClass()}. * @param metadata the annotation metadata * @return annotation attributes */ protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) { String name = getAnnotationClass().getName(); AnnotationAttributes attributes = AnnotationAttributes .fromMap(metadata.getAnnotationAttributes(name, true)); Assert.notNull(attributes, "No auto-configuration attributes found. Is " + metadata.getClassName() + " annotated with " + ClassUtils.getShortName(name) + "?"); return attributes; } /** * Return the source annotation class used by the selector. * @return the annotation class */ protected Class<?> getAnnotationClass() { return EnableAutoConfiguration.class; } /** * Return the auto-configuration class names that should be considered. By default * this method will load candidates using {@link SpringFactoriesLoader} with * {@link #getSpringFactoriesLoaderFactoryClass()}. * @param metadata the source metadata * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation * attributes} * @return a list of candidate configurations */ protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; } /** * Return the class used by {@link SpringFactoriesLoader} to load configuration * candidates. * @return the factory class */ protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; } private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) { List<String> invalidExcludes = new ArrayList<String>(exclusions.size()); for (String exclusion : exclusions) { if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) { invalidExcludes.add(exclusion); } } if (!invalidExcludes.isEmpty()) { handleInvalidExcludes(invalidExcludes); } } /** * Handle any invalid excludes that have been specified. * @param invalidExcludes the list of invalid excludes (will always have at least one * element) */ protected void handleInvalidExcludes(List<String> invalidExcludes) { StringBuilder message = new StringBuilder(); for (String exclude : invalidExcludes) { message.append("\t- ").append(exclude).append(String.format("%n")); } throw new IllegalStateException(String .format("The following classes could not be excluded because they are" + " not auto-configuration classes:%n%s", message)); } /** * Return any exclusions that limit the candidate configurations. * @param metadata the source metadata * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation * attributes} * @return exclusions or an empty set */ protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) { Set<String> excluded = new LinkedHashSet<String>(); excluded.addAll(asList(attributes, "exclude")); excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName"))); excluded.addAll(getExcludeAutoConfigurationsProperty()); return excluded; } private List<String> getExcludeAutoConfigurationsProperty() { if (getEnvironment() instanceof ConfigurableEnvironment) { RelaxedPropertyResolver resolver = new RelaxedPropertyResolver( this.environment, "spring.autoconfigure."); Map<String, Object> properties = resolver.getSubProperties("exclude"); if (properties.isEmpty()) { return Collections.emptyList(); } List<String> excludes = new ArrayList<String>(); for (Map.Entry<String, Object> entry : properties.entrySet()) { String name = entry.getKey(); Object value = entry.getValue(); if (name.isEmpty() || name.startsWith("[") && value != null) { excludes.addAll(new HashSet<String>(Arrays.asList(StringUtils .tokenizeToStringArray(String.valueOf(value), ",")))); } } return excludes; } RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(getEnvironment(), "spring.autoconfigure."); String[] exclude = resolver.getProperty("exclude", String[].class); return (Arrays.asList(exclude == null ? new String[0] : exclude)); } private List<String> sort(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) throws IOException { configurations = new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata).getInPriorityOrder(configurations); return configurations; } private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) { long startTime = System.nanoTime(); String[] candidates = configurations.toArray(new String[configurations.size()]); boolean[] skip = new boolean[candidates.length]; boolean skipped = false; for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) { invokeAwareMethods(filter); boolean[] match = filter.match(candidates, autoConfigurationMetadata); for (int i = 0; i < match.length; i++) { if (!match[i]) { skip[i] = true; skipped = true; } } } if (!skipped) { return configurations; } List<String> result = new ArrayList<String>(candidates.length); for (int i = 0; i < candidates.length; i++) { if (!skip[i]) { result.add(candidates[i]); } } if (logger.isTraceEnabled()) { int numberFiltered = configurations.size() - result.size(); logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms"); } return new ArrayList<String>(result); } protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader); } private MetadataReaderFactory getMetadataReaderFactory() { try { return getBeanFactory().getBean( SharedMetadataReaderFactoryContextInitializer.BEAN_NAME, MetadataReaderFactory.class); } catch (NoSuchBeanDefinitionException ex) { return new CachingMetadataReaderFactory(this.resourceLoader); } } protected final <T> List<T> removeDuplicates(List<T> list) { return new ArrayList<T>(new LinkedHashSet<T>(list)); } protected final List<String> asList(AnnotationAttributes attributes, String name) { String[] value = attributes.getStringArray(name); return Arrays.asList(value == null ? new String[0] : value); } private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) { List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners(); if (!listeners.isEmpty()) { AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions); for (AutoConfigurationImportListener listener : listeners) { invokeAwareMethods(listener); listener.onAutoConfigurationImportEvent(event); } } } protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader); } private void invokeAwareMethods(Object instance) { if (instance instanceof Aware) { if (instance instanceof BeanClassLoaderAware) { ((BeanClassLoaderAware) instance) .setBeanClassLoader(this.beanClassLoader); } if (instance instanceof BeanFactoryAware) { ((BeanFactoryAware) instance).setBeanFactory(this.beanFactory); } if (instance instanceof EnvironmentAware) { ((EnvironmentAware) instance).setEnvironment(this.environment); } if (instance instanceof ResourceLoaderAware) { ((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader); } } } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory); this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; } protected final ConfigurableListableBeanFactory getBeanFactory() { return this.beanFactory; } @Override public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; } protected ClassLoader getBeanClassLoader() { return this.beanClassLoader; } @Override public void setEnvironment(Environment environment) { this.environment = environment; } protected final Environment getEnvironment() { return this.environment; } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } protected final ResourceLoader getResourceLoader() { return this.resourceLoader; } @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE - 1; } } View Code 首先该类实现了DeferredImportSelector接口,这个接口继承了ImportSelector: /* * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.context.annotation; import org.springframework.core.type.AnnotationMetadata; /** * Interface to be implemented by types that determine which @{@link Configuration} * class(es) should be imported based on a given selection criteria, usually one or more * annotation attributes. * * <p>An {@link ImportSelector} may implement any of the following * {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective * methods will be called prior to {@link #selectImports}: * <ul> * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li> * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}</li> * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}</li> * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}</li> * </ul> * * <p>ImportSelectors are usually processed in the same way as regular {@code @Import} * annotations, however, it is also possible to defer selection of imports until all * {@code @Configuration} classes have been processed (see {@link DeferredImportSelector} * for details). * * @author Chris Beams * @since 3.1 * @see DeferredImportSelector * @see Import * @see ImportBeanDefinitionRegistrar * @see Configuration */ public interface ImportSelector { /** * Select and return the names of which class(es) should be imported based on * the {@link AnnotationMetadata} of the importing @{@link Configuration} class. */ String[] selectImports(AnnotationMetadata importingClassMetadata); } View Code 该接口主要是为了导入@Configuration的配置项,而DeferredImportSelector是延期导入,当所有的@Configuration都处理过后才会执行。 回过头来我们看一下AutoConfigurationImportSelector的selectImport方法: @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } try { AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); configurations = sort(configurations, autoConfigurationMetadata); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return configurations.toArray(new String[configurations.size()]); } catch (IOException ex) { throw new IllegalStateException(ex); } } 该方法刚开始会先判断是否进行自动装配,而后会从META-INF/spring-autoconfigure-metadata.properties读取元数据与元数据的相关属性,紧接着会调用getCandidateConfigurations方法: /** * Return the auto-configuration class names that should be considered. By default * this method will load candidates using {@link SpringFactoriesLoader} with * {@link #getSpringFactoriesLoaderFactoryClass()}. * @param metadata the source metadata * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation * attributes} * @return a list of candidate configurations */ protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; } /** * Return the class used by {@link SpringFactoriesLoader} to load configuration * candidates. * @return the factory class */ protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; } 在这里又遇到我们的老熟人了--SpringFactoryiesLoader, 它会读取META-INF/spring.factories下的EnableAutoConfiguration的配置,紧接着在进行排除与过滤,进而得到需要装配的类。最后让所有配置在META-INF/spring.factories下的AutoConfigurationImportListener执行AutoConfigurationImportEvent事件,代码如下: private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) { List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners(); if (!listeners.isEmpty()) { AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions); for (AutoConfigurationImportListener listener : listeners) { invokeAwareMethods(listener); listener.onAutoConfigurationImportEvent(event); } } } protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader); } 二、何时进行自动装配 在前面的环节里只是最终要确定哪些类需要被装配,在SpringBoot时何时处理这些自动装配的类呢?下面我们简要的分析一下: 2.1、AbstractApplicationContext的refresh方法: 这个方法老生常谈了其中请大家关注一下这个方法: // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); 在这里是处理BeanFactoryPostProcessor的,那么我们在来看一下这个接口BeanDefinitionRegistryPostProcessor: /* * Copyright 2002-2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.beans.factory.support; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; /** * Extension to the standard {@link BeanFactoryPostProcessor} SPI, allowing for * the registration of further bean definitions <i>before</i> regular * BeanFactoryPostProcessor detection kicks in. In particular, * BeanDefinitionRegistryPostProcessor may register further bean definitions * which in turn define BeanFactoryPostProcessor instances. * * @author Juergen Hoeller * @since 3.0.1 * @see org.springframework.context.annotation.ConfigurationClassPostProcessor */ public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor { /** * Modify the application context's internal bean definition registry after its * standard initialization. All regular bean definitions will have been loaded, * but no beans will have been instantiated yet. This allows for adding further * bean definitions before the next post-processing phase kicks in. * @param registry the bean definition registry used by the application context * @throws org.springframework.beans.BeansException in case of errors */ void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException; } View Code 该接口继承了BeanFactoryPostProcessor。 2.2、ConfigurationClassPostProcessor 类 该类主要处理@Configuration注解的,它实现了BeanDefinitionRegistryPostProcessor, 那么也间接实现了BeanFactoryPostProcessor,关键代码如下: @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { int factoryId = System.identityHashCode(beanFactory); if (this.factoriesPostProcessed.contains(factoryId)) { throw new IllegalStateException( "postProcessBeanFactory already called on this post-processor against " + beanFactory); } this.factoriesPostProcessed.add(factoryId); if (!this.registriesPostProcessed.contains(factoryId)) { // BeanDefinitionRegistryPostProcessor hook apparently not supported... // Simply call processConfigurationClasses lazily at this point then. processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory); } enhanceConfigurationClasses(beanFactory); beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory)); } /** * Build and validate a configuration model based on the registry of * {@link Configuration} classes. */ public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { //.....省略部分代码 // Parse each @Configuration class ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates); Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size()); do { parser.parse(candidates); parser.validate(); Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); candidates.clear(); if (registry.getBeanDefinitionCount() > candidateNames.length) { String[] newCandidateNames = registry.getBeanDefinitionNames(); Set<String> oldCandidateNames = new HashSet<String>(Arrays.asList(candidateNames)); Set<String> alreadyParsedClasses = new HashSet<String>(); for (ConfigurationClass configurationClass : alreadyParsed) { alreadyParsedClasses.add(configurationClass.getMetadata().getClassName()); } for (String candidateName : newCandidateNames) { if (!oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName); if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && !alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(new BeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; } } while (!candidates.isEmpty()); // ....省略部分代码 } 其实这里注释已经很清楚了,我们可以清楚的看到解析每一个@ConfigurationClass的关键类是:ConfigurationClassParser,那么我们继续看一看这个类的parse方法: public void parse(Set<BeanDefinitionHolder> configCandidates) { this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>(); for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { if (bd instanceof AnnotatedBeanDefinition) { parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); } } processDeferredImportSelectors(); } 在这里大家留意一下最后一句processDeferredImportSelectors方法,在这里将会对DeferredImportSelector进行处理,这样我们就和AutoConfigurationSelectImporter结合到一起了: private void processDeferredImportSelectors() { List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors; this.deferredImportSelectors = null; Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR); for (DeferredImportSelectorHolder deferredImport : deferredImports) { ConfigurationClass configClass = deferredImport.getConfigurationClass(); try { String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata()); processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", ex); } } } 请大家关注这句代码:String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());在这里deferredImport的类型为DeferredImportSelectorHolder: private static class DeferredImportSelectorHolder { private final ConfigurationClass configurationClass; private final DeferredImportSelector importSelector; public DeferredImportSelectorHolder(ConfigurationClass configClass, DeferredImportSelector selector) { this.configurationClass = configClass; this.importSelector = selector; } public ConfigurationClass getConfigurationClass() { return this.configurationClass; } public DeferredImportSelector getImportSelector() { return this.importSelector; } } 在这个内部类里持有了一个DeferredImportSelector的引用,至此将会执行自动装配的所有操作 三、总结 1)自动装配还是利用了SpringFactoriesLoader来加载META-INF/spring.factoires文件里所有配置的EnableAutoConfgruation,它会经过exclude和filter等操作,最终确定要装配的类 2) 处理@Configuration的核心还是ConfigurationClassPostProcessor,这个类实现了BeanFactoryPostProcessor, 因此当AbstractApplicationContext执行refresh方法里的invokeBeanFactoryPostProcessors(beanFactory)方法时会执行自动装配

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册