نحن نعلم أنه في SpringCloud ، عندما يتغير التكوين ، يمكننا الحصول على أحدث تكوين دون بدء الخدمة من خلال زيارة http: // xxxx/refresh. فكيف يفعل ذلك؟ كيف يمكننا الحصول على أحدث كائن مصدر البيانات بعد تغيير تكوين قاعدة البيانات وتحديثه؟ دعونا نرى كيف يفعل SpringCloud ذلك.
1. التغييرات البيئية
1.1. حول contextrefresher
عندما نصل /تحديث ، ستتم معالجتها بواسطة فئة RefreshEndPoint. دعونا نلقي نظرة على الكود المصدر:
/ * * حقوق الطبع والنشر 2013-2014 المؤلف أو المؤلفين الأصليين. * * مرخصة بموجب ترخيص Apache ، الإصدار 2.0 ("الترخيص") ؛ * لا يجوز لك استخدام هذا الملف إلا في الامتثال للترخيص. * يمكنك الحصول على نسخة من الترخيص على * * http://www.apache.org/licenses/license-2.0 * * ما لم يكن مطلوبًا بموجب القانون المعمول به أو يتم الاتفاق عليه في الكتابة ، يتم توزيع البرمجيات * الموزعة بموجب الترخيص على أساس "كما هو" ، * دون ضمانات أو شروط من أي نوع ، إما صريحة أو ضمنية. * راجع ترخيص الأذونات اللغوية المحددة والقيود * بموجب الترخيص. */package org.springframework.cloud.endpoint ؛ استيراد java.util.arrays ؛ استيراد java.util.collection ؛ استيراد java.util.set ؛ استيراد org.springframework.boot.actuate.endpoint.abstractendpoint org.springframework.cloud.context.refresh.contextrefresher ؛ استيراد org.springframework.jmx.export.annotation.managedoperation */@configurationProperties (prefix = "endpoints.refresh" ، تجاهل unknownfields = false) managedResourcepublic class refreshendpoint يمتد مجردة <Collogy <string >> {private contextrefresher contextrefresher ؛ Public RefreshEndPoint (contextrefresher contextrefresher) {super ("refresh") ؛ this.contextrefresher = contextrefresher ؛ } managedOperation public string [] refresh () {set <string> keys = contextrefresher.refresh () ؛ إرجاع Keys.Toarray (سلسلة جديدة [Keys.size ()]) ؛ } Override Public Collection <Tring> invoke () {return arrays.aslist (refresh ()) ؛ }}من خلال رمز المصدر ، تعلمنا أنه عند الوصول إلى نقطة النهاية للتحديث ، يتم تنفيذ طريقة التحديث لـ ContextreFresher بالفعل. ثم نستمر في تتبع الكود المصدري ونجد طريقة التحديث الخاصة به:
مجموعة متزامنة عامة <string> refresh () {map <string ، object> قبل = extract (this.context.getenvironment (). getPropertySources ()) ؛ AddConfigfilestoenvironment () ؛ SET <STRING> KEYS = Changes (من قبل ، extract (this.context.getenvironment (). getPropertySources ())). KeySet () ؛ this.context.publishevent (New EnvironmentChangeEvent (Context ، Keys)) ؛ this.scope.refreshall () ؛ إرجاع مفاتيح }يمكننا أن نرى أن طريقة التحديث تفعل ما يلي:
1) احصل على جميع propertysources قبل التحديث
2) Call AddConfiGfilestoenVironment طريقة للحصول على أحدث تكوين
3) استدعاء طريقة التغييرات لتحديث معلومات التكوين
4) نشر حدث changeNvent
5) استدعاء طريقة refreshall من RefreshScope لتحديث النطاق
دعونا نركز على خطوات 2 و 3 و 4
1.2. AddConfigfilestoenvironment طريقة
دعونا نلقي نظرة أولاً على كيفية تنفيذ هذه الطريقة:
/ * للاختبار */ configurableApplicationContext AddConfigFileStoenVironment () {configurableApplicationContext capture = null ؛ حاول {standardenvironment بيئة = calcEnvironment (this.context.getenvironment ()) ؛ springapplicationBuilder Builder = new SpringApplicationBuilder (فارغة. // فقط المستمعين الذين يؤثرون على البيئة (على سبيل المثال ، باستثناء تسجيل التسجيل // المستمع لأنه له آثار جانبية) Builder.Application () capture = builder.run () ؛ if (evely.getPropertySources (). } TargetPropertySources Target = this.context.getenvironment () .getPropertySources () ؛ سلسلة TargetName = null ؛ لـ (propertySource <؟> المصدر: Environment.getPropertySources ()) {string name = source.getName () ؛ if (target.contains (name)) {targetName = name ؛ } if (! this.standardsources.contains (name)) {if (target.contains (name)) {target.replace (name ، source) ؛ } آخر {if (targetName! = null) {target.addafter (targetName ، source) ؛ } آخر {// targetname كان فارغًا ، لذلك نحن في بداية القائمة target.addfirst (source) ؛ TargetName = name ؛ }}}}}} أخيرًا {configurableApplicationContext close = capture ؛ بينما (closable! = null) {try {closeable.close () ؛ } catch (استثناء e) {// تجاهل ؛ } if (closable.getParent () مثيل من configurableApplicationContext) {closleable = (configurableApplicationContext) closeable.getParent () ؛ } آخر {break ؛ }}} الإرجاع. }1) هذه الطريقة تقوم أولاً بنسخ البيئة الحالية
2) بناء برنامج بدء تشغيل SPRINGBOOT بسيط من خلال springapplicationbuilder وبدء
builder.application ().
سيتم إضافة مستمعين هنا: BootStrapPlicationListener و ConfigFileApplicationListener. من خلال التعلم السابق ، نعلم أن BootStrapPlicationLicationListener هو المستمع الأساسي لبرنامج bootstrap ، وأن configfileApplicationListener هو أيضًا فئة مهمة للغاية:
/ * * حقوق الطبع والنشر 2012-2017 المؤلف أو المؤلفين الأصليين. * * مرخصة بموجب ترخيص Apache ، الإصدار 2.0 ("الترخيص") ؛ * لا يجوز لك استخدام هذا الملف إلا في الامتثال للترخيص. * يمكنك الحصول على نسخة من الترخيص على * * http://www.apache.org/licenses/license-2.0 * * ما لم يكن مطلوبًا بموجب القانون المعمول به أو يتم الاتفاق عليه في الكتابة ، يتم توزيع البرمجيات * الموزعة بموجب الترخيص على أساس "كما هو" ، * دون ضمانات أو شروط من أي نوع ، إما صريحة أو ضمنية. * راجع ترخيص الأذونات اللغوية المحددة والقيود * بموجب الترخيص. */package org.springframework.boot.context.config ؛ استيراد java.io.ioException ؛ استيراد java.util.arraylist ؛ استيراد java.util.arrays ؛ java.util.collection ؛ استيراد java.util.collections ؛ استيراد java.util java.util.linkedlist ؛ import java.util.list ؛ import java.util.queue ؛ import java.util org.springframework.beans.factory.config.beanfactorypostprocessor ؛ استيراد org.springframework.beans.factory.config.configurableListableBeanfactory ؛ استيراد org.springframework.boot.springapplication ؛ org.springframework.boot.bind.propertiesConfigurationFactory ؛ استيراد org.springframework.boot.bind.propertysourcespropertyvalues org.springframework.boot.context.event.ApplicationEnvironmentpreparedEvent ؛ استيراد org.springframework.boot.context.event.ApplicationPreparedEvent ؛ import org.springframework.boot.env.enumerableCompositeProperce ؛ org.springframework.boot.env.environmentpostprocessor ؛ استيراد org.springframework.boot.env.propertysourcesloader ؛ استيراد org.springframework.boot.logging.deferredlog org.springframework.context.configurableApplicationContext ؛ استيراد org.springframework.context.annotation.configurationClassPostProcessor ؛ import org.springframework.context.smartapplication ؛ org.springframework.core.annotation.annotationawareDormparator ؛ استيراد org.springframework.core.convert.conversions ؛ import org.springframework.core.core.core.suport.defaultconverservice ؛ org.springframework.core.env.enumerablePropertySource ؛ استيراد org.springframework.core.env.mutablePropertysources ؛ import org.springframework.core.env.propertysources org.springframework.core.io.defaultresourceloader ؛ import org.springframework.core.io.resourceloader ؛ org.springframework.core.io.support.springfactoriesload ؛ org.springframework.util.ResourceUtils ؛ استيراد org.springframework.util.stringutils ؛ استيراد org.springframework.validation.bindexception ؛/** * {link environpostprocessor} التي تقوم بتكوين بيئة السيق بشكل افتراضي ، سيتم تحميل الخصائص من * 'application.properties' و/أو 'application.yml' في المواقع التالية: * <ul> * <li> classpath: </li> * <li> ملف: ./ </li> * <li> classpath: config/</li> * <li> ملف:. يمكن تحديدها باستخدام * {link #setsearchlocations (سلسلة)} و {link #SetSearchNames (string)}. * <p> * سيتم أيضًا تحميل الملفات الإضافية بناءً على ملفات تعريف نشطة. على سبيل المثال ، إذا كان ملف تعريف "الويب" * نشطًا ، فسيتم النظر في تطبيق "web.properties" و "Application-Web.yml". * <p> * يمكن استخدام خاصية "spring.config.name" لتحديد اسم بديل لتحميل * ويمكن استخدام خاصية "spring.config.location" لتحديد مواقع البحث البديلة أو الملفات المحددة. * <p> * خصائص التكوين مرتبطة أيضًا بـ {link springapplication}. هذا يجعل * من الممكن تعيين {link springapplication} خصائص ديناميكيًا ، مثل المصادر * ("spring.main.sources" - قائمة CSV) العلامة للإشارة إلى بيئة ويب * ("spring.main.web_environment = true") أو العلامة لإيقاف الشجار * ("spring.main.show_banner = passal"). * * Author Dave Syer * Author Phillip Webb * Author Stephane Nicoll * Author Andy Wilkinson * Author Edú Meléndez */Class Public ConfigfileApplicationListener تنفصل عن البيئة PostProcessor ، SmartApplicationListener ، order {private static final string_peroperties = " // لاحظ أن الترتيب هو من الأقل إلى الأكثر تحديداً (آخر واحد يفوز) Static Final String Sefault_Search_Locations = "classpath:/، classpath:/config/، file: ./ ، file: ./ config/" ؛ Static Final String Default_Names = "Application" ؛ /*** اسم خاصية "ملفات التعريف النشطة". */ Static Final String Active_profiles_property = "Spring.profiles.active" ؛ /*** اسم خاصية "تشمل الملفات الشخصية". */ السلسلة النهائية الثابتة العامة تشمل _profiles_property = "spring.profiles.include" ؛ /*** اسم خاصية "Config Name". */ Static Final String config_name_property = "spring.config.name" ؛ /*** اسم خاصية "موقع التكوين". */ Static Final String config_location_property = "spring.config.location" ؛ /*** الترتيب الافتراضي للمعالج. */ public static final int default_order = order.highest_precedence + 10 ؛ /*** اسم تكوين التطبيق {link propertySource}. */ public static final string application_configuration_property_source_name = "ApplicationConfigurationProperties" ؛ logger efferredlog النهائي الخاص = جديد مؤلف من مؤشر () ؛ SearchLocations الخاصة بالسلسلة ؛ أسماء السلسلة الخاصة ؛ Order int الخاص = default_order ؛ enversionservice النهائي الخاص = جديد defaultConversionService () ؛ Override Public Boolean SupportSeventType (فئة <؟ تمديد ApplicationEvent> EventType) {return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom (EventType) || ApplicationPreparedEvent.class.isassIgnableFrom (eventType) ؛ } Override Public Boolean SupportsSourCeType (الفئة <؟> ACLASS) {return true ؛ } Override public void onapplicationevent (applicationevent event) {if (event eventueof applicationenvironmentpreparedevent) {onapplicationEnvironmentPreparedEvent ((ApplicationEnvironmentParedEvent)) ؛ } if (event eventueof ApplicationPreparedEvent) {onapplicationPreparedEvent (event) ؛ }} private void onapplicationEnvironmentPreparedEvent (ApplicationEnvironmentPreparedEvent حدث) {List <EvicepoStProcessor> postprocessors = loadPostProcessors () ؛ postprocessors.Add (هذا) ؛ anotationawareorderComparator.sort (Postprocessors) ؛ لـ (البيئة PostProcessor PostProcessor: postprocessors) {postprocessor.postProcessEnvironment (event.getenvironment () ، event.getSpringApplication ()) ؛ }} LIST <EVECTIONPostProcessor> loadPostProcessors () {return SpringFactoriesLoader.loadFactories (eventypostprocessor.class ، getClass (). getClassloader ()) ؛ } Override public void postprocessenvironment (بيئة البيئة القابلة للتكوين ، تطبيق springapplication) {addPropertySources (البيئة ، application.getResourceloader ()) ؛ ConfigignoreBeanInfo (البيئة) ؛ BindTospringApplication (البيئة ، التطبيق) ؛ } private void configignoreBeanInfo (بيئة البيئة القابلة للتكوين) {if (system.getProperty (CacheDinTrospectionResults.ignore_beaninfo_property_name) == null) {ReliefPropertyResolver Resolver = New RelainPropertyResolver (environfo. " Boolean Dection = resolver.getProperty ("تجاهل" ، boolean.class ، boolean.true) ؛ System.SetProperty (CacheDinTrospectionResults.ignore_beaninfo_property_name ، reghore.toString ()) ؛ }} private void onapplicationPreparedEvent (applicationevent event) {this.logger.replayto (configfiLeApplicationListener.class) ؛ AddPostProcsors (((ApplicationPreparedEvent) حدث) .getApplicationContext ()) ؛ } /*** أضف مصادر خاصية ملف التكوين إلى البيئة المحددة. * param Environment البيئة لإضافة مصدر إلى * param resourceloader the morder loader * see #adpostprocorsors (configurableApplicationContext) */ proid void addPropertysources (environmableenvironment ، resourceLoader ResourceLoader) New Loader (Environment ، ResourCeloader) .Load () ؛ } /*** ربط البيئة بـ {link springapplication}. * Param Environment البيئة لربط * param التطبيق لربط *// محمي bindtospringapplication (بيئة تكوين البيئة ، تطبيق springapplication) {propertiesConfigurationFactory <PringApplication> Binder = new propertiesConfigurationFactory <SpringApplication> (application) ؛ Binder.SetTargetName ("spring.main") ؛ Binder.setConversionService (this.conversionservice) ؛ Binder.setPropertySources (evًري. getPropertySources ()) ؛ جرب {binder.bindpropertoestOtarget () ؛ } catch (bindException ex) {refl new alficalstateException ("لا يمكن أن يرتبط بـ springapplication" ، ex) ؛ }} /*** أضف المعالجات المناسبة لما بعد التكوين لمصادر العقار. * param سياق السياق لتكوين */ void void addPostProcessors (configurableApplicationContext Context) {context.addbeanfactorypostprocessor (propertySourceOrceDringPostProcessor (Context)) ؛ } public void setorder (int order) {this.order = order ؛ } Override public int getorder () {return this.order ؛ } /*** قم بتعيين مواقع البحث التي سيتم اعتبارها قائمة مفصولة بفاصلة. يجب أن يكون كل موقع للبحث مسارًا دليلًا (ينتهي في "/") وسيتم مقدمة * بأسماء الملفات التي تم إنشاؤها من {Link #SetSearchNames (سلسلة) أسماء البحث} و * ملفات التعريف (إن وجدت) بالإضافة إلى امتدادات الملفات المدعومة من قبل برامز الخصائص. * يتم النظر في المواقع بالترتيب المحدد ، مع الأسبقية العناصر اللاحقة * (مثل دمج الخريطة). * @param مواقع Search Morts */ public void setSearchLocations (مواقع السلسلة) {assert.haslength (المواقع ، "يجب ألا تكون المواقع فارغة") ؛ this.searchlocations = المواقع ؛ } /** * يعين أسماء الملفات التي يجب تحميلها (باستثناء امتداد الملف) كقائمة مفصولة *. * param اسم الأسماء لتحميل */ public void setSearchNames (أسماء السلسلة) {Assert.hasLength (الأسماء ، "يجب ألا تكون الأسماء فارغة") ؛ this.names = أسماء ؛ } /*: */ propertysourceordingpostprocessor previctionsprocessister ، تم طلب {سياق configurableApplicationContext الخاص ؛ PropertySourceOrdeingPostProcessor (ConfigurableApplicationContext Context) {this.context = context ؛ } Override public int getorder () {return order.highest_precedence ؛ } Override public void postprocessbeanfactory (configurableListableBeanfactory Beanfactory) يلقي beansexception {reordersources (this.context.getenvironment ()) ؛ } private void reordersources (بيئة البيئة القابلة للتكوين) {configurationPropertySources .FinishAndRealocate (evesice.getPropertySources ()) ؛ PropertySource <؟> defaultPropertySources = evely.getPropertySources () .remove (default_properties) ؛ if (defaultProperties! = null) {evEnchSpleS.GetPropertySources (). addLast (defaultProperties) ؛ }}} /*** يحمل مصادر خاصية المرشح وتكوين ملفات التعريف النشطة. */ private class loader {private final log logger = configfileApplicationListener.Tis.logger ؛ بيئة البيئة النهائية النهائية الخاصة ؛ ResourceLoader النهائي الخاص ResourceLoader ؛ PropertySourCesloader PropertiesLoader ؛ قائمة انتظار خاصة <ffrints> ملفات تعريف ؛ قائمة خاصة <ffression> ProcessedProfiles ؛ خاصات المنطقية الخاصة ببروفيلز ؛ loader (بيئة البيئة القابلة للتكوين ، ResourCeloader ResourceLoader) {this.environment = Environment ؛ this.resourceloader = resourceLoader == null؟ New DefaultResourceloader (): ResourceLoader ؛ } public void load () {this.propertiesloader = new propertySourCesloader () ؛ this.ActivatedProfiles = false ؛ this.profiles = collections.aslifoqueue (new LinkedList <ffression> ()) ؛ this.processedProfiles = new LinkedList <ffression> () ؛ // ملفات تعريف نشطة موجودة مسبقًا تم تعيينها عبر البيئة. SET <ffress> initialactiveProfiles = initializeActiveProfiles () ؛ this.profiles.addall (getUnprocountactactactactivePlofiles (initialactiveProfiles)) ؛ if (this.profiles.isempty ()) {for (string defaultProfiLename: this.environment.getDefaultProfiles ()) {profile defaultProfile = new profile (defaultProfilename ، true) ؛ if (! this.profiles.contains (defaultProfile)) {this.profiles.add (defaultProfile) ؛ }}} // يتم تمثيل ملف التعريف الافتراضي لهذه الأغراض على أنه فارغ. نضيفه // أخيرًا بحيث يتم الخروج أولاً من قائمة الانتظار (ستجعل الملامح النشطة // أي إعدادات في الإعدادات الافتراضية عند عكس القائمة لاحقًا). this.profiles.add (null) ؛ بينما (! هذا. لـ (موقع السلسلة: getSearchLocations ()) {if (! location.endswith ("/")) {// الموقع هو اسم ملف بالفعل ، لذلك لا تبحث عن المزيد من الأسماء // } آخر {for (اسم السلسلة: getSearchNames ()) {load (الموقع ، الاسم ، الملف الشخصي) ؛ }}} هذا. } addConfigurationProperties (this.propertiesloader.getPropertySources ()) ؛ } مجموعة خاصة <ffression> initializeActiveProfiles () {if (! this.environment.containsProperty (Active_profiles_property) &&! this.environment.containsProperty (include_profiles_property)) {return collections.emptyset () ؛ } // أي ملفات تعريف نشطة موجودة مسبقًا تم تعيينها عبر مصادر الممتلكات (مثل System // Properties) لها الأسبقية على تلك المضافة في ملفات التكوين. SpringProfiles SpringProfiles = bindSpringProfiles (this.environment.getPropertySources ()) ؛ SET <ffress> ActiveProfiles = new LinkedHashSet <ffression> (SpringProfiles.getActiveProfiles ()) ؛ ActiveProfiles.addall (SpringProfiles.getInCludeProfiles ()) ؛ MaybeActivateProfiles (ActiveProfiles) ؛ إرجاع ActiveProfiles ؛ } /*** إرجاع الملفات الشخصية النشطة التي لم تتم معالجتها بعد. إذا تم تمكين ملف تعريف * عبر كل من {link #active_profiles_property} و * {link configableenvironment #addactiveProfile (string)} يجب ترشيحه بحيث يتم تصفيته بحيث تتطلب القيمة {link #active_profiles_property} * <p> * بشكل ملموس ، إذا تم تمكين ملف تعريف "Cloud" عبر البيئة ، فسوف يتطلب الأمر * أسبقية أقل تم تعيينه عبر {link #active_profiles_property}. * param initialactiveprofiles تُمكّن الملفات الشخصية التي تم تمكينها عبر * {link #active_profiles_property} * return ملفات التعريف النشطة غير المجهزة من البيئة إلى تمكين */ قائمة خاصة <ffrint> getUnprayTaCtactactactactive (set <sirofive> initialiCtiveProfiles) لـ (String pforname: this.environment.getActiveProfiles ()) {profile profile = ملف تعريف جديد (profilename) ؛ if (! initialactiveprofiles.contains (profile)) {غير مجهول النشاط. }} // عكسها بحيث يكون الترتيب هو نفسه من GetProfilesForValue () // (آخر واحد يفوز عند حل الخصائص في نهاية المطاف) مجموعة. إرجاع غير معالجتها. } تحميل void الخاص (موقع السلسلة ، اسم السلسلة ، ملف تعريف الملف الشخصي) {string group = "profile =" + (profile == null؟ "": profile) ؛ if (! StringUtils.hastext (name)) {// حاول التحميل مباشرة من موقع loadIntogroup (المجموعة ، الموقع ، الملف الشخصي) ؛ } else {// ابحث عن ملف يحمل الاسم المحدد لـ (string ext: this.propertiesloader.getAllFileStensions ()) {if (profile! = null) {// جرب ملف الملف الشخصي المحمل (المجموعة ، الموقع + "-" + profile + ". لـ (profile processedProfile: this.procoutprofiles) {if (processedProfile! = null) {loadIntogRoup (group ، location + name + "-" + processedProfile + "." + ext ، profile) ؛ }} // في بعض الأحيان يضع الأشخاص "spring.profiles: dev" in // application-dev.yml (GH-340). يمكن القول إننا يجب أن نحاول أن نحاول أن نخطئ في ذلك ، لكن يمكننا أن نكون طيبين ونحمله على أي حال. loadIntogroup (المجموعة ، الموقع + الاسم + "-" + profile + "." + تحويلة ، ملف تعريف) ؛ } // أيضًا جرب القسم الخاص بالملف الشخصي (إن وجد) من ملف ledintogroup العادي (المجموعة ، الموقع + الاسم + "." + تحويلة ، ملف تعريف) ؛ }}} propertysource <؟> loadIntogroup (معرف السلسلة ، موقع السلسلة ، ملف تعريف الملف الشخصي) {try {return doloadintogroup (المعرف ، الموقع ، الملف الشخصي) ؛ } catch (استثناء ex) {رمي new alficalstateException ("فشل في تحميل مصدر الخاصية من الموقع '" + location + "'" ، ex) ؛ }} propertysource <؟> doloadintogroup (معرف السلسلة ، موقع السلسلة ، ملف تعريف الملف الشخصي) يلقي iOexception {Resource Resource = this.resourceloader.getResource (location) ؛ PropertySource <؟> propertySource = null ؛ StringBuilder msg = new StringBuilder () ؛ if (Resource! = null && Resource.exists ()) {string name = "applicationConfig: [" + location + "]" ؛ string group = "applicationConfig: [" + Identifier + "]" ؛ propertySource = this.propertiesloader.load (المورد ، المجموعة ، الاسم ، (ملف تعريف == null؟ null: profile.getName ())) ؛ if (propertySource! = null) {msg.append ("loaded") ؛ HandleProfilePerties (PropertySource) ؛ } آخر {msg.append ("Skipped (فارغ)") ؛ }} else {msg.append ("Skipped") ؛ } msg.append ("config file") ؛ msg.append (getResourCedescription (الموقع ، المورد)) ؛ if (profile! = null) {msg.append ("for profile") .Append (profile) ؛ } if (resource == null ||! resource.exists ()) {msg.append ("Resource not found") ؛ this.logger.trace (msg) ؛ } آخر {this.logger.debug (msg) ؛ } إرجاع PropertySource ؛ } سلسلة خاصة getResourCedEscription (موقع السلسلة ، مورد المورد) {String ResourceCriptive = "'" + location + "' '" ؛ if (Resource! = null) {try {resourcedescription = string.format ("'٪ s' (٪ s)" ، resource.geturi (). toasciistring () ، location) ؛ } catch (ioException ex) {// استخدم الموقع كوصف}} resourcespressicive ؛ } private void galleprofileproperties (propertySource <؟> propertySource) {SpringProfiles SpringProfiles = bindSpringProfiles (propertySource) ؛ MaybeActivateProfiles (SpringProfiles.getActiveProfiles ()) ؛ addProfiles (SpringProfiles.getInCludeProfiles ()) ؛ } SpringProfiles الخاص bindSpringProfiles (propertySource <؟> propertySource) {propertyPropertySources orperalSources = new MonitablePropertySources () ؛ PropertySources.Addfirst (PropertySource) ؛ Return BindSpringProfiles (PropertySources) ؛ } SpringProfiles الخاص bindSpringProfiles (PropertySources PropertySources) {SpringProfiles SpringProfiles = new SpringProfiles () ؛ databinder RelaxedDatabinder = جديد RelainDdatabinder (SpringProfiles ، "Spring.Profiles") ؛ Databinder.bind (PropertySourCespropertyValues (PropertySources ، false)) ؛ SpringProfiles.setActive (ResolvePlesholers (SpringProfiles.getActive ())) ؛ SpringProfiles.setInclude (ResolvePlesholders (SpringProfiles.getInclude ())) ؛ إرجاع SpringProfiles. } قائمة خاصة <Tring> soldingplaceholders (قائمة <String> قيم) {List <String> Resolved = new ArrayList <String> () ؛ لـ (قيمة السلسلة: القيم) {solved.add (this.environment.Resromplaceholers (value)) ؛ } عودة حلها ؛ } private void maybeActivaleProfiles (set <fullbly> profiles) {if (this.ActivateProfiles) {if (! perfile.isempty ()) {this.logger.debug ("ملفات التعريف تم تنشيطها بالفعل ، '" + profiles + "' ' } يعود؛ } if (! perfile.isempty ()) {addProfiles (ملفات تعريف) ؛ this.logger.debug ("ملفات التعريف المنشطة" + stringUtils.CollectionTocommadElimitedString (ملفات تعريف)) ؛ هذا. removeUnprocessedDefaultProfiles () ؛ }} private void removeUnprocoundDefaultProfiles () {for (iterator <fullbl> iterator = this.profiles.iterat أو () ؛ iterator .hasnext () ؛) {if (iterator.next (). isDefaultProfile ()) {iterator.remove () ؛ }}} private void addProfiles (set <ffression> perffiles) {for (ملف تعريف الملف الشخصي: ملفات التعريف) {this.profiles.add (profile) ؛ if (! extremthasactiveProfile (profile.getName ())) {// إذا تم قبولها بالفعل ، فإننا نفترض أن الأمر قد تم تعيينه // intending prependprofile (this.environment ، profile) ؛ }}} private boolean extriedhasactiveprofile (ملف تعريف السلسلة) {for (string activeProfile: this.environment.getActiveProfiles ()) {if (ActiveProfile.equals (profile)) {return true ؛ }} إرجاع خطأ ؛ } private prependprofile (بيئة ConfigableEnvironment ، ملف تعريف الملف الشخصي) {Set <String> Prisiles = new LinkedHashset <String> () ؛ البيئة. getActiveProfiles () ؛ // تأكد من تهيئتها // ولكن يجب أن يذهب هذا أولاً (الفوز الأخير في صدام مفتاح خاصية). perfile.addall (arrays.aslist (evesice.getactiveprofiles ())) ؛ البيئة. } مجموعة خاصة <string> getSearchLocations () {set <String> socations = new LinkedHashSet <String> () ؛ // الإعدادات التي يتم تكوينها للمستخدم لها الأسبقية ، لذلك نقوم بها أولاً إذا (this.environment.containsProperty (config_location_property)) {for (مسار السلسلة: asResolvedSet (this.environment.getProperty (config_location_property) ، null)) {if ( if (! ResourceUtils.isurl (path)) {path = resourceUtils.file_url_prefix + path ؛ }} المواقع. add (path) ؛ }} SOCATIONS.ADDALL (ASRESOLVEDSET (configfiLeApplicationListener.Tis.SearchLocations ، default_search_locations)) ؛ مواقع العودة ؛ } مجموعة خاصة <Tring> getSearchNames () {if (this.environment.containsProperty (config_name_property)) {return asresolvedset (this.environment.getProperty (config_name_property) ، null) ؛ } إرجاع AsResolvedSet (configfileApplicationListener.This.names ، default_names) ؛ } مجموعة خاصة <string> asResolvedSet (قيمة السلسلة ، متوقفة السلسلة) {list <string> list = arrays.aslist (stringUtils.TrimArrayElements (stringUtils.CommadElimitedListTtringArray (value! = null؟ collections.reverse (قائمة) ؛ إرجاع New LinkedHashset <String> (List) ؛ } private void addConfigurationProperties (مصادر matiblePropertySources) {list <propertySource <؟ >> reorderedSources = new ArrayList <propertySource <؟ >> () ؛ لـ (propertySource <؟> العنصر: مصادر) {reorderSources.Add (item) ؛ } addConfigurationProperties (configurationpropertysources (reorderedsources) ؛} private addConfigurationProperties (configurationPropertySources Configurationsourations) AppoStSources.Addbefore "يجب أن يكون الاسم NULL" this.name.hashcode () ؛ PropertySource} يمكن أن يتم تحويلها * بمجرد معالجة فئات التكوين. Super (application_configuration_property_source_name) ؛ المصدر). ) (propertysource مثيل enumerableComposeProperty) {PropertySources.Addafter (PropertySource) ؛ {القائمة الخاصة بـ Active = ArtingList <String> () ؛ this.include =} set <ffressions> الأطراف)وفقًا لتعليق Javadoc ، ستقوم هذه الفئة بتحميل التطبيق. يرجى الانتباه إلى هذه الطرق:
Override public void onapplicationEvent (ApplicationEvent event) {if (event eventueof applicationenvironmentpreparedevent) {onapplicationEnvironmentPreparedEvent ((ApplicationEnvironmentParedEvent)) ؛ } if (event eventueof ApplicationPreparedEvent) {onapplicationPreparedEvent (event) ؛ }} عند بدء تشغيل برنامج Springboot ، سيتم بالتأكيد تشغيل الاستماع إلى الحدث. If the current ApplicationEnvironmentPreparedEvent event is currently called, the onApplicationEnvironmentPreparedEvent method will be called, and the method will be executed in the end:
@Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { addPropertySources(environment, application.getResourceLoader()); configureIgnoreBeanInfo(environment); bindToSpringApplication(environment, application); }其中bindToSpringApplication方法为:
/** * Bind the environment to the {@link SpringApplication}. * @param environment the environment to bind * @param application the application to bind to */ protected void bindToSpringApplication(ConfigurableEnvironment environment, SpringApplication application) { PropertiesConfigurationFactory<SpringApplication> binder = new PropertiesConfigurationFactory<SpringApplication>( application); binder.setTargetName("spring.main"); binder.setConversionService(this.conversionService); binder.setPropertySources(environment.getPropertySources()); try { binder.bindPropertiesToTarget(); } catch (BindException ex) { throw new IllegalStateException("Cannot bind to SpringApplication", ex); }}很明显该方法是将Environment绑定到对应SpringApplication上,通过这个类就可以获取到我们更改过后的配置了
1.3、changes方法
private Map<String, Object> changes(Map<String, Object> before, Map<String, Object> after) { Map<String, Object> result = new HashMap<String, Object>(); for (String key : before.keySet()) { if (!after.containsKey(key)) { result.put(key, null); } else if (!equal(before.get(key), after.get(key))) { result.put(key, after.get(key)); } } for (String key : after.keySet()) { if (!before.containsKey(key)) { result.put(key, after.get(key)); } } return result; }changes方法其实就是处理配置变更信息的,分以下几种情况:
1)如果刷新过后配置文件新增配置就添加到Map里
2) 如果有配置变更就添加变更后的配置
3) 如果删除了原先的配置,就把原先的key对应的值设置为null
至此经过changes方法后,上下文环境已经拥有最新的配置了。
1.4、发布事件
当上述步骤都执行完毕后,紧接着会发布EnvrionmentChangeEvent事件,可是这个事件谁来监听呢?在这里我贴出官网的一段描述:
应用程序将收听EnvironmentChangeEvent,并以几种标准方式进行更改(用户可以以常规方式添加ApplicationListeners附加ApplicationListeners)。当观察到EnvironmentChangeEvent时,它将有一个已更改的键值列表,应用程序将使用以下内容:
1.重新绑定上下文中的任何@ConfigurationProperties bean
2.为logging.level.*中的任何属性设置记录器级别
根据官网描述我们知道将变更一下操作行为@ConfigurationProperties的bean与更改日志level,那么如何做到的呢?结合官网文档我们来关注以下两个类:
ConfigurationPropertiesRebinder:
/* * Copyright 2013-2014 the original author or authors. * * مرخصة بموجب ترخيص Apache ، الإصدار 2.0 ("الترخيص") ؛ * لا يجوز لك استخدام هذا الملف إلا في الامتثال للترخيص. * يمكنك الحصول على نسخة من الترخيص على * * http://www.apache.org/licenses/license-2.0 * * ما لم يكن مطلوبًا بموجب القانون المعمول به أو يتم الاتفاق عليه في الكتابة ، يتم توزيع البرمجيات * الموزعة بموجب الترخيص على أساس "كما هو" ، * دون ضمانات أو شروط من أي نوع ، إما صريحة أو ضمنية. * راجع ترخيص الأذونات اللغوية المحددة والقيود * بموجب الترخيص. */package org.springframework.cloud.context.properties;import java.util.HashSet;import java.util.Map;import java.util.Set;import java.util.concurrent.ConcurrentHashMap;import org.springframework.aop.framework.Advised;import org.springframework.aop.support.AopUtils;import org.springframework.beans.BeansException;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.cloud.context.config.annotation.RefreshScope;import org.springframework.cloud.context.environment.EnvironmentChangeEvent;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.context.ApplicationListener;import org.springframework.core.env.Environment;import org.springframework.jmx.export.annotation.ManagedAttribute;import org.springframework.jmx.export.annotation.ManagedOperation;import org.springframework.jmx.export.annotation.ManagedResource;import org.springframework.stereotype.Component;/** * Listens for {@link EnvironmentChangeEvent} and rebinds beans that were bound to the * {@link Environment} using {@link ConfigurationProperties * <code>@ConfigurationProperties</code>}. When these beans are re-bound and * re-initialized the changes are available immediately to any component that is using the * <code>@ConfigurationProperties</code> bean. * * @see RefreshScope for a deeper and optionally more focused refresh of bean components * * @author Dave Syer * */@Component@ManagedResourcepublic class ConfigurationPropertiesRebinder implements ApplicationContextAware, ApplicationListener<EnvironmentChangeEvent> { private ConfigurationPropertiesBeans beans; application applicationcontext applicationContext ؛ private Map<String, Exception> errors = new ConcurrentHashMap<>(); public ConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) { this.beans = beans; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } /** * A map of bean name to errors when instantiating the bean. * * @return the errors accumulated since the latest destroy */ public Map<String, Exception> getErrors() { return this.errors; } @ManagedOperation public void rebind() { this.errors.clear(); for (String name : this.beans.getBeanNames()) { rebind(name); } } @ManagedOperation public boolean rebind(String name) { if (!this.beans.getBeanNames().contains(name)) { return false; } if (this.applicationContext != null) { try { Object bean = this.applicationContext.getBean(name); if (AopUtils.isAopProxy(bean)) { bean = getTargetObject(bean); } this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean); this.applicationContext.getAutowireCapableBeanFactory(); this.applicationContext.getAutowireCapableBeanFactory(); .initializeBean(bean, name); العودة صحيح. } catch (RuntimeException e) { this.errors.put(name, e); throw e; }} إرجاع خطأ ؛ } @SuppressWarnings("unchecked") private static <T> T getTargetObject(Object candidate) { try { if (AopUtils.isAopProxy(candidate) && (candidate instanceof Advised)) { return (T) ((Advised) candidate).getTargetSource().getTarget(); } } catch (Exception ex) { throw new IllegalStateException("Failed to unwrap proxied object", ex); } return (T) candidate; } @ManagedAttribute public Set<String> getBeanNames() { return new HashSet<String>(this.beans.getBeanNames()); } @Override public void onApplicationEvent(EnvironmentChangeEvent event) { if (this.applicationContext.equals(event.getSource()) // Backwards compatible || event.getKeys().equals(event.getSource())) { rebind(); }}}我们可以看到该类监听了ChangeEnvrionmentEvent事件,它最主要作用是拿到更新的配置以后,重新绑定@ConfigurationProperties标记的类使之能够读取最新的属性
LoggingRebinder:
/* * Copyright 2013-2014 the original author or authors. * * مرخصة بموجب ترخيص Apache ، الإصدار 2.0 ("الترخيص") ؛ * لا يجوز لك استخدام هذا الملف إلا في الامتثال للترخيص. * يمكنك الحصول على نسخة من الترخيص على * * http://www.apache.org/licenses/license-2.0 * * ما لم يكن مطلوبًا بموجب القانون المعمول به أو يتم الاتفاق عليه في الكتابة ، يتم توزيع البرمجيات * الموزعة بموجب الترخيص على أساس "كما هو" ، * دون ضمانات أو شروط من أي نوع ، إما صريحة أو ضمنية. * راجع ترخيص الأذونات اللغوية المحددة والقيود * بموجب الترخيص. */package org.springframework.cloud.logging;import java.util.Map;import java.util.Map.Entry;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.boot.bind.RelaxedPropertyResolver;import org.springframework.boot.logging.LogLevel;import org.springframework.boot.logging.LoggingSystem;import org.springframework.cloud.context.environment.EnvironmentChangeEvent;import org.springframework.context.ApplicationListener;import org.springframework.context.EnvironmentAware;import org.springframework.core.env.Environment;/** * Listener that looks for {@link EnvironmentChangeEvent} and rebinds logger levels if any * changed. * * @author Dave Syer * */public class LoggingRebinder implements ApplicationListener<EnvironmentChangeEvent>, EnvironmentAware { private final Log logger = LogFactory.getLog(getClass()); private Environment environment; @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public void onApplicationEvent(EnvironmentChangeEvent event) { if (this.environment == null) { return; } LoggingSystem system = LoggingSystem.get(LoggingSystem.class.getClassLoader()); setLogLevels(system, this.environment); } protected void setLogLevels(LoggingSystem system, Environment environment) { Map<String, Object> levels = new RelaxedPropertyResolver(environment) .getSubProperties("logging.level."); for (Entry<String, Object> entry : levels.entrySet()) { setLogLevel(system, environment, entry.getKey(), entry.getValue().toString()); } } private void setLogLevel(LoggingSystem system, Environment environment, String name, String level) { try { if (name.equalsIgnoreCase("root")) { name = null; } level = environment.resolvePlaceholders(level); system.setLogLevel(name, LogLevel.valueOf(level.toUpperCase())); } catch (RuntimeException ex) { this.logger.error("Cannot set level: " + level + " for '" + name + "'"); }}}该类也是监听了ChangeEnvrionmentEvent事件,用于重新绑定日志级别
二、刷新范围
我们考虑如下场景,当我们变更数据库配置后,通过refresh刷新,虽然能获取到最新的配置,可是我们的DataSource对象早就被初始化好了,换句话说即便配置刷新了我们拿到的依然是配置刷新前的对象。怎么解决这个问题呢?
我们继续看ContextRefresher的refresh方法,最后有一处代码值得我们关注一下this.scope.refreshAll(),此处scope对象是RefreshScope类型,那么这个类有什么作用呢?那么我们先要关注一下@RefreshScope注解。在这里我在贴出官网一段解释:
当配置更改时,标有@RefreshScope的Spring @Bean将得到特殊处理。这解决了状态bean在初始化时只注入配置的问题。例如,如果通过Environment更改数据库URL时DataSource有开放连接,那么我们可能希望这些连接的持有人能够完成他们正在做的工作。然后下一次有人从游泳池借用一个连接,他得到一个新的URL
刷新范围bean是在使用时初始化的懒惰代理(即当调用一个方法时),并且作用域作为初始值的缓存。要强制bean重新初始化下一个方法调用,您只需要使其缓存条目无效。RefreshScope是上下文中的一个bean,它有一个公共方法refreshAll()来清除目标缓存中的范围内的所有bean。还有一个refresh(String)方法可以按名称刷新单个bean。此功能在/refresh端点(通过HTTP或JMX)中公开。
这里我贴出@RefreshScope源码:
/* * Copyright 2013-2014 the original author or authors. * * مرخصة بموجب ترخيص Apache ، الإصدار 2.0 ("الترخيص") ؛ * لا يجوز لك استخدام هذا الملف إلا في الامتثال للترخيص. * يمكنك الحصول على نسخة من الترخيص على * * http://www.apache.org/licenses/license-2.0 * * ما لم يكن مطلوبًا بموجب القانون المعمول به أو يتم الاتفاق عليه في الكتابة ، يتم توزيع البرمجيات * الموزعة بموجب الترخيص على أساس "كما هو" ، * دون ضمانات أو شروط من أي نوع ، إما صريحة أو ضمنية. * راجع ترخيص الأذونات اللغوية المحددة والقيود * بموجب الترخيص. */package org.springframework.cloud.context.config.annotation;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.Scope;import org.springframework.context.annotation.ScopeedProxyMode;/** * Convenience annotation to put a <code>@Bean</code> definition in * {@link org.springframework.cloud.context.scope.refresh.RefreshScope refresh scope}. * Beans annotated this way can be refreshed at runtime and any components that are using * them will get a new instance on the next method call, fully initialized and injected * with all dependencies. * * @author Dave Syer * */@Target({ ElementType.TYPE, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Scope("refresh")@Documentedpublic @interface RefreshScope { /** * @see Scope#proxyMode() */ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;}在这个注解上我们关注一下此处标记了@Scope("refresh"),我们知道Spring的Bean属性有个叫scope的,它定义了bean的作用范围,常见的有singleon,prototype,session等。此处新定义了一个范围叫做refresh,在此我贴出RefreshScope的源代码来分析一下:
/* * Copyright 2002-2009 the original author or authors. * * مرخصة بموجب ترخيص Apache ، الإصدار 2.0 ("الترخيص") ؛ 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.cloud.context.scope.refresh;import java.io.Serializable;import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanDefinition;import org.springframework.beans.factory.support.BeanDefinitionRegistry;import org.springframework.cloud.context.scope.GenericScope;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.context.event.ContextRefreshedEvent;import org.springframework.context.event.EventListener;import org.springframework.core.Ordered;import org.springframework.jmx.export.annotation.ManagedOperation;import org.springframework.jmx.export.annotation.ManagedResource;/** * <p> * A Scope implementation that allows for beans to be refreshed dynamically at runtime * (see {@link #refresh(String)} and {@link #refreshAll()}). If a bean is refreshed then * the next time the bean is accessed (ie a method is executed) a new instance is * created. All lifecycle methods are applied to the bean instances, so any destruction * callbacks that were registered in the bean factory are called when it is refreshed, and * then the initialization callbacks are invoked as normal when the new instance is * created. A new bean instance is created from the original bean definition, so any * externalized content (property placeholders or expressions in string literals) is * re-evaluated when it is created. * </p> * <p> * Note that all beans in this scope are <em>only</em> initialized when first accessed, so * the scope forces lazy initialization semantics. The implementation involves creating a * proxy for every bean in the scope, so there is a flag * {@link #setProxyTargetClass(boolean) proxyTargetClass} which controls the proxy * creation, defaulting to JDK dynamic proxies and therefore only exposing the interfaces * implemented by a bean. If callers need access to other methods then the flag needs to * be set (and CGLib present on the classpath). Because this scope automatically proxies * all its beans, there is no need to add <code><aop:auto-proxy/></code> to any bean * definitions. * </p> * <p> * The scoped proxy approach adopted here has a side benefit that bean instances are * automatically {@link Serializable}, and can be sent across the wire as long as the * receiver has an identical application context on the other side. To ensure that the two * contexts agree that they are identity they have to have the same serialization id. One * will be generated automatically by default from the bean names, so two contexts with * the same bean names are by default able to exchange beans by name. If you need to * override the default id then provide an explicit {@link #setId(String) id} when the * Scope is declared. * </p> * * @author Dave Syer * * @since 3.1 * */@ManagedResourcepublic class RefreshScope extends GenericScope implements ApplicationContextAware, Ordered { private ApplicationContext context; private BeanDefinitionRegistry registry; private boolean eager = true; private int order = Ordered.LOWEST_PRECEDENCE - 100; /** * Create a scope instance and give it the default name: "refresh". */ public RefreshScope() { super.setName("refresh"); } @Override public int getOrder() { return this.order; } public void setOrder(int order) { this.order = order; } /** * Flag to determine whether all beans in refresh scope should be instantiated eagerly * on startup. Default true. * * @param eager the flag to set */ public void setEager(boolean eager) { this.eager = eager; } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { this.registry = registry; super.postProcessBeanDefinitionRegistry(registry); } @EventListener public void start(ContextRefreshedEvent event) { if (event.getApplicationContext() == this.context && this.eager && this.registry != null) { eagerlyInitialize(); } } private void eagerlyInitialize() { for (String name : this.context.getBeanDefinitionNames()) { BeanDefinition definition = this.registry.getBeanDefinition(name); if (this.getName().equals(definition.getScope()) && !definition.isLazyInit()) { Object bean = this.context.getBean(name); if (bean != null) { bean.getClass(); } } } } @ManagedOperation(description = "Dispose of the current instance of bean name provided and force a refresh on next method execution.") public boolean refresh(String name) { if (!name.startsWith(SCOPED_TARGET_PREFIX)) { // User wants to refresh the bean with this name but that isn't the one in the // cache... name = SCOPED_TARGET_PREFIX + name; } // Ensure lifecycle is finished if bean was disposable if (super.destroy(name)) { this.context.publishEvent(new RefreshScopeRefreshedEvent(name)); العودة صحيح. } إرجاع خطأ ؛ } @ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.") public void refreshAll() { super.destroy(); this.context.publishEvent(new RefreshScopeRefreshedEvent()); } @Override public void setApplicationContext(ApplicationContext context) throws BeansException { this.context = context; }}该类继承了GenericScope:
/* * Copyright 2002-2009 the original author or authors. * * مرخصة بموجب ترخيص Apache ، الإصدار 2.0 ("الترخيص") ؛ 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.cloud.context.scope;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.Arrays;import java.util.Collection;import java.util.Collections;import java.util.LinkedHashSet;import java.util.List;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ConcurrentMap;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock;import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;import org.aopalliance.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.aop.framework.Advised;import org.springframework.aop.scope.ScopedObject;import org.springframework.aop.scope.ScopedProxyFactoryBean;import org.springframework.aop.support.AopUtils;import org.springframework.beans.BeansException;import org.springframework.beans.factory.BeanFactory;import org.springframework.beans.factory.DisposableBean;import org.springframework.beans.factory.ObjectFactory;import org.springframework.beans.factory.config.BeanDefinition;import org.springframework.beans.factory.config.BeanFactoryPostProcessor;import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;import org.springframework.beans.factory.config.Scope;import org.springframework.beans.factory.support.BeanDefinitionRegistry;import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;import org.springframework.beans.factory.support.DefaultListableBeanFactory;import org.springframework.beans.factory.support.RootBeanDefinition;import org.springframework.expression.Expression;import org.springframework.expression.ExpressionParser;import org.springframework.expression.ParseException;import org.springframework.expression.spel.standard.SpelExpressionParser;import org.springframework.expression.spel.support.StandardEvaluationContext;import org.springframework.util.ReflectionUtils;import org.springframework.util.StringUtils;/** * <p> * A generic Scope implementation. * </p> * @author Dave Syer * * @since 3.1 * */public class GenericScope implements Scope, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor, DisposableBean { private static final Log logger = LogFactory.getLog(GenericScope.class); public static final String SCOPED_TARGET_PREFIX = "scopedTarget."; private BeanLifecycleWrapperCache cache = new BeanLifecycleWrapperCache( new StandardScopeCache()); private String name = "generic"; private ConfigurableListableBeanFactory beanFactory; private StandardEvaluationContext evaluationContext; private String id; private Map<String, Exception> errors = new ConcurrentHashMap<>(); private ConcurrentMap<String, ReadWriteLock> locks = new ConcurrentHashMap<>(); /** * Manual override for the serialization id that will be used to identify the bean * factory. The default is a unique key based on the bean names in the bean factory. * * @param id the id to set */ public void setId(String id) { this.id = id; } /** * The name of this scope. Default "generic". * * @param name the name value to set */ public void setName(String name) { this.name = name; } /** * The cache implementation to use for bean instances in this scope. * * @param cache the cache to use */ public void setScopeCache(ScopeCache cache) { this.cache = new BeanLifecycleWrapperCache(cache); } /** * A map of bean name to errors when instantiating the bean. * * @return the errors accumulated since the latest destroy */ public Map<String, Exception> getErrors() { return this.errors; } @Override public void destroy() { List<Throwable> errors = new ArrayList<Throwable>(); Collection<BeanLifecycleWrapper> wrappers = this.cache.clear(); for (BeanLifecycleWrapper wrapper : wrappers) { try { Lock lock = locks.get(wrapper.getName()).writeLock(); lock.lock(); try { wrapper.destroy(); } finally { lock.unlock(); } } catch (RuntimeException e) { errors.add(e); } } if (!errors.isEmpty()) { throw wrapIfNecessary(errors.get(0)); } this.errors.clear(); } /** * Destroy the named bean (ie flush it from the cache by default). * * @param name the bean name to flush * @return true if the bean was already cached, false otherwise */ protected boolean destroy(String name) { BeanLifecycleWrapper wrapper = this.cache.remove(name); if (wrapper != null) { Lock lock = locks.get(wrapper.getName()).writeLock(); lock.lock(); try { wrapper.destroy(); } finally { lock.unlock(); } this.errors.remove(name); العودة صحيح. } إرجاع خطأ ؛ } @Override public Object get(String name, ObjectFactory<?> objectFactory) { BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory)); locks.putIfAbsent(name, new ReentrantReadWriteLock()); try { return value.getBean(); } catch (RuntimeException e) { this.errors.put(name, e); throw e; } } @Override public String getConversationId() { return this.name; } @Override public void registerDestructionCallback(String name, Runnable callback) { BeanLifecycleWrapper value = this.cache.get(name); if (value == null) { return; } value.setDestroyCallback(callback); } @Override public Object remove(String name) { BeanLifecycleWrapper value = this.cache.remove(name); if (value == null) { return null; } // Someone might have added another object with the same key, but we // keep the method contract by removing the // value we found anyway return value.getBean(); } @Override public Object resolveContextualObject(String key) { Expression expression = parseExpression(key); return expression.getValue(this.evaluationContext, this.beanFactory); } private Expression parseExpression(String input) { if (StringUtils.hasText(input)) { ExpressionParser parser = new SpelExpressionParser(); try { return parser.parseExpression(input); } catch (ParseException e) { throw new IllegalArgumentException("Cannot parse expression: " + input, e); } } else { return null; } } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; beanFactory.registerScope(this.name, this); setSerializationId(beanFactory); } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { for (String name : registry.getBeanDefinitionNames()) { BeanDefinition definition = registry.getBeanDefinition(name); if (definition instanceof RootBeanDefinition) { RootBeanDefinition root = (RootBeanDefinition) definition; if (root.getDecoratedDefinition() != null && root.hasBeanClass() && root.getBeanClass() == ScopedProxyFactoryBean.class) { if (getName().equals(root.getDecoratedDefinition().getBeanDefinition() .getScope())) { root.setBeanClass(LockedScopedProxyFactoryBean.class); } } } } } } } /** * If the bean factory is a DefaultListableBeanFactory then it can serialize scoped * beans and deserialize them in another context (even in another JVM), as long as the * ids of the bean factories match. This method sets up the serialization id to be * either the id provided to the scope instance, or if that is null, a hash of all the * bean names. * * @param beanFactory the bean factory to configure */ private void setSerializationId(ConfigurableListableBeanFactory beanFactory) { if (beanFactory instance of DefaultListableBeanFactory) { String id = this.id; if (id == null) { List<String> list = new ArrayList<>( Arrays.asList(beanFactory.getBeanDefinitionNames())); Collections.sort(list); String names = list.toString(); logger.debug("Generating bean factory id from names: " + names); id = UUID.nameUUIDFromBytes(names.getBytes()).toString(); } logger.info("BeanFactory id=" + id); ((DefaultListableBeanFactory) beanFactory).setSerializationId(id); } else { logger.warn( "BeanFactory was not a DefaultListableBeanFactory, scoped proxy beans " + "cannot be serialized."); } } static RuntimeException wrapIfNecessary(Throwable throwable) { if (throwable instanceof RuntimeException) { return (RuntimeException) throwable; } if (throwable instanceof Error) { throw (Error) throwable; } return new IllegalStateException(throwable); } protected String getName() { return this.name; } private static class BeanLifecycleWrapperCache { private final ScopeCache cache; public BeanLifecycleWrapperCache(ScopeCache cache) { this.cache = cache; } public BeanLifecycleWrapper remove(String name) { return (BeanLifecycleWrapper) this.cache.remove(name); } public Collection<BeanLifecycleWrapper> clear() { Collection<Object> values = this.cache.clear(); Collection<BeanLifecycleWrapper> wrappers = new LinkedHashSet<BeanLifecycleWrapper>(); for (Object object : values) { wrappers.add((BeanLifecycleWrapper) object); } return wrappers; } public BeanLifecycleWrapper get(String name) { return (BeanLifecycleWrapper) this.cache.get(name); } public BeanLifecycleWrapper put(String name, BeanLifecycleWrapper value) { return (BeanLifecycleWrapper) this.cache.put(name, value); } } /** * Wrapper for a bean instance and any destruction callback (DisposableBean etc.) that * is registered for it. Also decorates the bean to optionally guard it from * concurrent access (for instance). * * @author Dave Syer * */ private static class BeanLifecycleWrapper { private Object bean; private Runnable callback; private final String name; private final ObjectFactory<?> objectFactory; public BeanLifecycleWrapper(String name, ObjectFactory<?> objectFactory) { this.name = name; this.objectFactory = objectFactory; } public String getName() { return this.name; } public void setDestroyCallback(Runnable callback) { this.callback = callback; } public Object getBean() { if (this.bean == null) { synchronized (this.name) { if (this.bean == null) { this.bean = this.objectFactory.getObject(); } } } return this.bean; } public void destroy() { if (this.callback == null) { return; } synchronized (this.name) { Runnable callback = this.callback; if (callback != null) { callback.run(); } this.callback = null; this.bean = null; } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); نتيجة العودة } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } BeanLifecycleWrapper other = (BeanLifecycleWrapper) obj; if (this.name == null) { if (other.name != null) { return false; } } else if (!this.name.equals(other.name)) { return false; } إعادة صواب ؛ } } @SuppressWarnings("serial") public class LockedScopedProxyFactoryBean extends ScopedProxyFactoryBean implements MethodInterceptor { private String targetBeanName; @Override public void setBeanFactory(BeanFactory beanFactory) { super.setBeanFactory(beanFactory); Object proxy = getObject(); if (proxy instance of Advised) { Advised advised = (Advised) proxy; advised.addAdvice(0, this); } } @Override public void setTargetBeanName(String targetBeanName) { super.setTargetBeanName(targetBeanName); this.targetBeanName = targetBeanName; } @Override public Object invoke(MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); if (AopUtils.isEqualsMethod(method) || AopUtils.isToStringMethod(method) || AopUtils.isHashCodeMethod(method) || isScopedObjectGetTargetObject(method)) { return invocation.proceed(); } Object proxy = getObject(); Lock lock = locks.get(this.targetBeanName).readLock(); lock.lock(); try { if (proxy instance of Advised) { Advised advised = (Advised) proxy; ReflectionUtils.makeAccessible(method); return ReflectionUtils.invokeMethod(method, advised.getTargetSource().getTarget(), invocation.getArguments()); } return invocation.proceed(); } finally { lock.unlock(); } } private boolean isScopedObjectGetTargetObject(Method method) { return method.getDeclaringClass().equals(ScopedObject.class) && method.getName().equals("getTargetObject") && method.getParameterTypes().length == 0; }}}这里面我们先看一下RefreshScope的构造函数:
/** * Create a scope instance and give it the default name: "refresh". */ public RefreshScope() { super.setName("refresh"); }这里面创建了一个名字为refresh的scope。
紧接着在它的父类里我们可以看一下这个方法:
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; beanFactory.registerScope(this.name, this); setSerializationId(beanFactory); }此方法中使用BeanFactory注册了一个refresh的范围,使得scope为refresh的bean生效。@RefreshScope标注的类还有一个特点:会使用代理对象并进行延迟加载。我们来看一下postProcessBeanDefinitionRegistry方法
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { for (String name : registry.getBeanDefinitionNames()) { BeanDefinition definition = registry.getBeanDefinition(name); if (definition instanceof RootBeanDefinition) { RootBeanDefinition root = (RootBeanDefinition) definition; if (root.getDecoratedDefinition() != null && root.hasBeanClass() && root.getBeanClass() == ScopedProxyFactoryBean.class) { if (getName().equals(root.getDecoratedDefinition().getBeanDefinition() .getScope())) { root.setBeanClass(LockedScopedProxyFactoryBean.class); } } } } }该方法遍历所有的bean定义如果当前的bean的scope为refresh,那么就把当前的bean设置为LockedScopedProxyFactoryBean的代理对象。
RefreshScope还会监听一个ContextRefreshedEvent,该事件会在ApplicationContext初始化或者refreshed时触发,我们来看一下代码:
@EventListener public void start(ContextRefreshedEvent event) { if (event.getApplicationContext() == this.context && this.eager && this.registry != null) { eagerlyInitialize(); } } private void eagerlyInitialize() { for (String name : this.context.getBeanDefinitionNames()) { BeanDefinition definition = this.registry.getBeanDefinition(name); if (this.getName().equals(definition.getScope()) && !definition.isLazyInit()) { Object bean = this.context.getBean(name); if (bean != null) { bean.getClass(); } } } }注意此处获取refreshscope的bean,其中getBean是一个复杂而又繁琐的过程,此处我们先不在这里讨论,只不过经过这个方法以后,其通过代理机制会在GernericScope的BeanLifecycleWrapperCache缓存里把这个@RefreshScope标记的bean添加进去。
最后我们回过头来看一看RefreshScope的refreshAll方法:
@ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.") public void refreshAll() { super.destroy(); this.context.publishEvent(new RefreshScopeRefreshedEvent()); }//.......GernericScope's destroy method @Override public void destroy() { List<Throwable> errors = new ArrayList<Throwable>(); Collection<BeanLifecycleWrapper> wrappers = this.cache.clear(); for (BeanLifecycleWrapper wrapper : wrappers) { try { Lock lock = locks.get(wrapper.getName()).writeLock(); lock.lock(); try { wrapper.destroy(); } finally { lock.unlock(); } } catch (RuntimeException e) { errors.add(e); } } if (!errors.isEmpty()) { throw wrapIfNecessary(errors.get(0)); } this.errors.clear(); }这里的代码逻辑很简单清除与释放缓存里被@RefreshScope标记的bean 。
当我们要获取对象时,我们可以关注如下方法:
@Override public Object get(String name, ObjectFactory<?> objectFactory) { BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory)); locks.putIfAbsent(name, new ReentrantReadWriteLock()); try { return value.getBean(); } catch (RuntimeException e) { this.errors.put(name, e); throw e; } } //...... BeanLifecycleWrapper method public Object getBean() { if (this.bean == null) { synchronized (this.name) { if (this.bean == null) { this.bean = this.objectFactory.getObject(); } } } return this.bean; }BeanLifecycleWrapper这个是@RefreshScope标记bean的一个包装类,会被存储到缓存里,在这里取不到值的话就会从objectFactory里去拿
三、示例与总结
3.1. مثال
创建AppConfig类代码如下:
package com.bdqn.lyrk.refresh.scope.server;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.cloud.context.config.annotation.RefreshScope;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration@EnableConfigurationProperties(StudentConfig.class)public class AppConfig { @RefreshScope @Bean public Student student(StudentConfig config) { Student student = new Student(); student.setName(config.getName()); return student; }}在这里,将Student设置为@RefreshScope 那么刷新以后会获取最新的Bean
启动类:
package com.bdqn.lyrk.refresh.scope.server;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@SpringBootApplication@RestControllerpublic class RefreshScopeApplication { @Autowired private Student student; @GetMapping public String student() { return student.getName(); } public static void main(String[] args) throws InterruptedException { SpringApplication.run(RefreshScopeApplication.class, args); }}application.yml文件:
spring: application: name: refresh-scope-serverendpoints: refresh: sensitive: falseserver: port: 8089student: name: admin
这里把refresh端点开放出来,然后变更配置后就可以获取最新的对象了
3.2. ملخص
1) 当配置更新并通过refresh端点刷新后,会执行ContextRefresher的refresh方法,该方法会记录当前的Environment,而后构建一个简易的SpringApplicationBuilder并执行其run方法,此时ConfigFileApplicationListener会读取我们修改过后的配置并绑定到SpringApplication对象上,最后进行changes操作来变更已有的PropertySource
2) @RefreshScope最好配合@Bean使用,当且仅当变更配置后,需要重新获取最新的bean时使用。加上该注解的Bean会被代理并且延迟加载,所有的scope属性为Refresh的bean会被包装成BeanLifecycleWrapper存入缓存(ConcurrentHashMap)中,所有的读取,修改,删除都是基于该缓存的。
لخص
The above is the analysis of the SpringCloud configuration refresh principle introduced by the editor. آمل أن يكون ذلك مفيدًا للجميع. إذا كان لديك أي أسئلة ، يرجى ترك رسالة لي. The editor will reply to everyone in time!