Kita tahu bahwa di SpringCloud, ketika konfigurasi berubah, kita bisa mendapatkan konfigurasi terbaru tanpa memulai layanan dengan mengunjungi http: // xxxx/refresh. Jadi bagaimana melakukannya? Bagaimana kita bisa mendapatkan objek sumber data terbaru setelah kita mengubah konfigurasi database dan menyegarkan? Mari kita lihat bagaimana Springcloud melakukannya.
1. Perubahan Lingkungan
1.1. Tentang Contextrefresher
Saat kami mengakses /menyegarkan, itu akan diproses oleh kelas RefreshendPoint. Mari kita lihat kode sumbernya:
/ * * Hak Cipta 2013-2014 Penulis atau penulis asli. * * Dilisensikan di bawah lisensi Apache, versi 2.0 ("lisensi"); * Anda tidak boleh menggunakan file ini kecuali sesuai dengan lisensi. * Anda dapat memperoleh salinan lisensi di * * http://www.apache.org/licenses/license-2.0 * * kecuali diharuskan oleh hukum yang berlaku atau disepakati secara tertulis, perangkat lunak * yang didistribusikan di bawah lisensi didistribusikan atas dasar "sebagaimana adanya", * tanpa jaminan atau ketentuan apa pun, baik yang diungkapkan atau disiratkan. * Lihat lisensi untuk izin yang mengatur bahasa tertentu dan * batasan di bawah lisensi. */Paket org.springframework.cloud.endpoint; import java.util.arrays; import java.util.collection; import java.util.set; impor org.springframework.boot.actuate.endpoint.abstractendpoint; impor org.springframework.boot.actuate.endpoint.abstractendpoint; impor org.springframework.boot.cute. org.springframework.cloud.context.refresh.contextrefresher; impor org.springframework.jmx.export.annotation.managedoperation; Impor org.springframework.jmx.export.annotation.ManMuageSource; */@Configurationproperties (prefix = "endpoints.refresh", degnerunknownfields = false) @ManagedResourcePublic kelas Refreshendpoint memperluas Abstractendpoint <Collection <string>> {private ContexTrefResher ContextrefResher; Public RefreshendPoint (ContextrefResher ContextrefResher) {super ("Refresh"); this.contextrefresher = contextrefresher; } @ManageDoperation Public String [] refresh () {set <string> keys = contextrefresher.refresh (); return keys.toArray (string baru [keys.size ()]); } @Override Public Collection <String> Invoke () {return arrays.aslist (refresh ()); }}Melalui kode sumber, kami belajar bahwa ketika mengakses titik akhir refresh, metode penyegaran contextrefresher sebenarnya dieksekusi. Kemudian kami terus melacak kode sumber dan menemukan metode penyegarannya:
set disinkronkan publik <string> refresh () {peta <string, objek> sebelum = ekstrak (this.context.getEnvironment (). getPropertySources ()); addConfigFilestoenvironment (); Atur <string> keys = perubahan (sebelum, ekstrak (this.context.getEnvironment (). GetPropertySources ())). Keyset (); this.context.publishevent (New EnvironmentChangeEvent (Context, Keys)); this.scope.refreshall (); kunci kembali; }Kita dapat melihat bahwa metode penyegaran melakukan hal berikut:
1) Dapatkan semua sumber properti sebelum menyegarkan
2) Hubungi Metode Tambah ConfigFilestoenvironment untuk mendapatkan konfigurasi terbaru
3) Hubungi metode perubahan untuk memperbarui informasi konfigurasi
4) Publish EnvironmentChangeEnvent Event
5) Panggilan metode penyegaran refreshscope untuk menyegarkan jangkauan
Mari fokus pada 2, 3, dan 4 langkah
1.2. Tambah metode lingkungan
Pertama -tama mari kita lihat bagaimana metode ini diimplementasikan:
/ * untuk pengujian */ configurableApplicationContext addConfigFilestoenvironment () {configurableApplicationContext capture = null; coba {StandardEnvironment Environment = CopyEnvironment (this.context.getEnvironment ()); SpringApplicationBuilder builder = New SpringApplicationBuilder (kosong.class) .Bannermode (Mode.Off) .web (false) .environment (lingkungan); // Hanya pendengar yang mempengaruhi lingkungan (misalnya tidak termasuk logging // pendengar karena memiliki efek samping) builder.application () .setListeners (arrays.aslist (bootstrapapplicationListener baru (), configFileAppLicationListener baru ()); capture = builder.run (); if (lingkungan.getPropertySources (). berisi (refresh_args_property_source)) {lingkungan.getPropertySources (). hapus (refresh_args_property_source); } MutablePropertySources target = this.context.getEnvironment () .getPropertySources (); String targetName = null; untuk (propertiSource <?> Sumber: lingkungan.getPropertySources ()) {string name = source.getName (); if (target.contains (name)) {targetName = name; } if (! this.standardsources.contains (name)) {if (target.contains (name)) {target.replace (name, source); } else {if (targetName! = null) {target.addafter (targetName, sumber); } else {// TargetName adalah null jadi kami berada di awal daftar target.addfirst (sumber); targetName = name; }}}}}} akhirnya {configurableApplicationContext closeable = capture; while (closeable! = null) {coba {closeable.close (); } catch (Exception e) {// abaikan; } if (closeable.getParent () Instance dari ConfigurableApplicationContext) {closeable = (ConfigurableApplicationContext) closeable.getParent (); } else {break; }}} return capture; }1) Metode ini pertama -tama menyalin lingkungan saat ini
2) Bangun program startup springboot sederhana melalui springapplicationbuilder dan mulai
builder.application (). setListeners (arrays.aslist (bootstrapApplicationListener baru (), configFileApplicationListener baru ()));
Dua pendengar akan ditambahkan di sini: BootstrapApplicationListener dan configFileApplyChistener. Melalui pembelajaran sebelumnya, kita tahu bahwa BootstrapApplicationListener adalah pendengar inti dari program bootstrap, dan ConfigFileApplicationListener juga merupakan kelas yang sangat penting:
/ * * Hak Cipta 2012-2017 Penulis atau penulis asli. * * Dilisensikan di bawah lisensi Apache, versi 2.0 ("lisensi"); * Anda tidak boleh menggunakan file ini kecuali sesuai dengan lisensi. * Anda dapat memperoleh salinan lisensi di * * http://www.apache.org/licenses/license-2.0 * * kecuali diharuskan oleh hukum yang berlaku atau disepakati secara tertulis, perangkat lunak * yang didistribusikan di bawah lisensi didistribusikan atas dasar "sebagaimana adanya", * tanpa jaminan atau ketentuan apa pun, baik yang diungkapkan atau disiratkan. * Lihat lisensi untuk izin yang mengatur bahasa tertentu dan * batasan di bawah lisensi. */paket org.springframework.boot.context.config; impor java.io.ioexception; impor java.util.arraylist; import java.util.arrays; import java.util.collection; impor java.util.collections; impor java.util.utilect; java.util.linkedlist; impor java.util.list; impor java.util.queue; impor java.util.set; impor org.apache.commons.logging.log; impor org.springframework.beans.beansException; impor org.spramram.beancrospechyrospection org.springframework.beans.factory.config.beanfactorypostprocessor; impor org.springframework.beans.factory.config.configurableListableBeanFactory; impor org.springframework.boot.springplication; impor org.springframework.boot.bind.propertiesconfigurationfactory; impor org.springframework.boot.bind.propertysourcespropertyvalues; impor org.springframework.bind. org.springframework.boot.context.event.applicationEnvironmentPreparedEvent; impor org.springframework.boot.context.event.applicationPreparedEvent; import org.springframework.boot.env.enumerableCompositepropertysource org.springframework.boot.env.environmentPostProcessor; impor org.springframework.boot.env.propertysourcesloader; impor org.springframework.context.application.deferredLog; impor org.springframework.context org.springframework.context.configurableApplicationContext; impor org.springframework.context.annotation.configurationclasspostprocessor; impor org.spramework.context.event.smartapplicationListener; impor org.spramework.context.event.smartapplicationener; org.springframework.core.annotation.AnnotationAwareOrderComparator; impor org.springframework.core.convert.conversionService; org.springframework.core.convert.support.defaultConVersionService; impor org.springfram. org.springframework.core.env.enumerablePropertySource; impor org.springframework.core.env.MutablePropertySources; impor org.springframework.core.env.propertysources; impor org.springframework.core.env.properces; org.springframework.core.io.defaultresourceloader; impor org.springframework.core.io.Resoureload; Impor org.springframework.core.io.support. org.springframework.util.stringutils; impor org.springframework.validation.bindException;/** * {@link EnvironmentPostProcessor} yang mengkonfigurasi lingkungan konteks dengan memuat * properti dari lokasi file yang terkenal. Secara default properti akan dimuat dari file * 'application.properties' dan/atau 'application.yml' di lokasi berikut: * <ul> * <li> classpath: </li> * <li> File: ./ </li> * Pencarian ClassPath: config/</li> * <li> File :./ Config/: </Li> LOPATH: Konfigurasi/</li> * <li> File :./. Nama dapat ditentukan menggunakan * {@link #setsearchlocations (string)} dan {@link #setsearchnames (string)}. * <p> * File tambahan juga akan dimuat berdasarkan profil aktif. Misalnya jika profil 'Web' * aktif 'aplikasi-web.properties' dan 'application-web.yml' akan * dipertimbangkan. * <p> * Properti 'spring.config.name' dapat digunakan untuk menentukan nama alternatif untuk memuat * dan properti 'spring.config.location' dapat digunakan untuk menentukan lokasi pencarian alternatif * atau file tertentu. * <p> * Properti konfigurasi juga terikat pada {@link springApplication}. Ini memungkinkan * untuk mengatur properti {@link springApplication} secara dinamis, seperti sumber * ("spring.main.sources" - daftar csv) bendera untuk menunjukkan lingkungan web * ("spring.main.web_environment = true") atau bendera untuk mematikan spanduk * ("spring.main.show_show = false = fals). * * @Author Dave Syer * @author Phillip Webb * @author Stephane Nicoll * @author Andy Wilkinson * @author eddú Meléndez */Deflistener Kelas Publik, PRivate StaticListener; // Catatan pesanan dari paling sedikit ke paling sedikit (yang terakhir menang) string final statis pribadi default_search_locations = "classpath:/, classpath:/config/, file: ./, file: ./ config/"; Private Static Final String Default_Names = "Application"; /*** Nama properti "Profil Aktif". */ string final statis public Active_profiles_property = "spring.profiles.active"; /*** Nama properti "Termasuk Profil". */ string akhir statis public include_profiles_property = "spring.profiles.include"; /*** Nama properti "config name". */ public static final string config_name_property = "spring.config.name"; /*** Nama properti "Lokasi Konfigurasi". */ public static final string config_location_property = "spring.config.location"; /*** Pesanan default untuk prosesor. */ public static final int default_order = ordered.highest_precedence + 10; /*** Nama konfigurasi aplikasi {@link PropertiesSource}. */ string final statis public application_configuration_property_source_name = "ApplicationConfigurationProperties"; Private Final DeferredLog Logger = New DeferredLog (); Private String SearchLocations; nama string pribadi; private int order = default_order; ConversionService Final ConversionService privat = DefaultConversionService baru (); @Override Public Boolean SupportSEventType (kelas <? Extends ApplicationEvent> EventType) {return applicationEnvironmentPreparedEvent.class.isassignableFrom (eventType) || ApplicationPreparedEvent.class.IsassignableFrom (EventType); } @Override public boolean mendukungSourCetype (class <?> Aclass) {return true; } @Override public void onApplicationEvent (event ApplicationEvent) {if (event instanceof applicationEnvironmentPreparedEvent) {onApplicationEnvironmentPreparedEvent ((ApplicationEnvironmentPreparedEvent) acara); } if (Event instanceof ApplicationPreparedEvent) {OnApplicationPreparedEvent (Event); }} private void onApplicationEnvironmentPreparedEvent (eventsvironmironmentPreparedEvent event) {list <urvevenperpostprocessor> postprocessors = loadPostProcessors (); postprocessors.add (ini); AnnotationAwareOrdComparator.sort (postprocessors); untuk (EnvironmentPostProcessor postprocessor: postprocessors) {postprocessor.postprocessenvironment (event.getEnvironment (), event.getspringapplication ()); }} Daftar <VERVERVERSIRPOSTPROCESS> LOADPOSTPROCESSORS () {return springFactoriesLoader.LoadFactories (EnvironmentPostProcessor.class, getClass (). GetClassLoader ()); } @Override public void postprocessEnvironment (lingkungan ConfigAbleNeNvironment, Aplikasi SpringApplication) {AddPropertySources (Environment, Application.getResourceloader ()); ConfigureIndeBeanInfo (Lingkungan); BindToSpringApplication (lingkungan, aplikasi); } private void configureignoreBeanInfo (lingkungan lingkungan yang dapat dikonfigurasi) {if (System.getProperty (CachedIntrospectionResults.ignore_beaninfo_property_name) == NULL) {relaxedPropertyLver. Boolean abaikan = resolver.getProperty ("abaikan", boolean.class, boolean.true); System.setProperty (CachedIntrospectionResults.ignore_beaninfo_property_name, abaikan.tostring ()); }} private void onApplicationPreparedEvent (event ApplicationEvent) {this.logger.replayto (configFileApplicationListener.class); Acara addPostProcessors (((ApplicationPreparedEvent)) .getApplicationContext ()); } /*** Tambahkan sumber properti file konfigurasi ke lingkungan yang ditentukan. * @param Lingkungan Lingkungan untuk menambahkan sumber ke * @param Resourceloader The Resource Loader * @See #AddPostProcessors (ConfigurableApplicationContext) */ Void AddPropertySources (ConfigAbleSource.AVINGLEADERER (ResourcoLoader) {randomValUePropertySource.AVINGLOADER) {randomValUePROPERTYSOURCE.ADVERCOADER) {randomSource.addoAdoDoaderer) {randomValUEPROPERTYSOURCE.AVINGLEADER) {randomSource.AreveRoader) Loader baru (lingkungan, sumber daya) .Load (); } /*** mengikat lingkungan ke {@link springApplication}. * @param lingkungan lingkungan untuk mengikat * @param Aplikasi Aplikasi untuk mengikat ke */ Protected void BindToSpringApplication (lingkungan ConfigAbleNeNvironment, SpringApplication Application) {PropertiesConfigurationFactory <SpingApplication> binder = new New PropertiesConfigurationFactory <springapplication> (Application); binder.setargetname ("spring.main"); binder.setConversionservice (this.conversionservice); binder.setpropertysources (lingkungan.getPropertySources ()); coba {binder.bindproperterestoTarget (); } catch (BindException ex) {lempar IllegalStateException baru ("tidak dapat mengikat ke springapplication", ex); }} /*** Tambahkan post-prosesor yang sesuai untuk mengkonfigurasi sumber daya properti. * @param konteks Konteks untuk mengonfigurasi */ protected void addPostProcessors (ConfigurableApplicationContext Context) {Context.AddBeanFactoryPostProcessor (New PropertiesourceOrderingPostProcessor (Context)); } public void setorder (int order) {this.order = order; } @Override public int getorder () {return this.order; } /*** Atur lokasi pencarian yang akan dianggap sebagai daftar yang dipisahkan koma. Setiap * lokasi pencarian harus berupa jalur direktori (diakhiri dengan "/") dan akan diawali * oleh nama file yang dibangun dari {@link #setsearchnames (string) Nama pencarian} dan * profil (jika ada) plus ekstensi file yang didukung oleh loader properti. * Lokasi dipertimbangkan dalam urutan yang ditentukan, dengan item kemudian diutamakan * (seperti gabungan peta). * @param Lokasi Lokasi pencarian */ public void setSearchlocations (Lokasi String) {assert.haslength (lokasi, "lokasi tidak boleh kosong"); this.searchlocations = lokasi; } /** * Mengatur nama file yang harus dimuat (tidak termasuk ekstensi file) sebagai * daftar yang dipisahkan koma. * @param Nama nama untuk memuat */ public void setSearchnames (nama string) {assert.haslength (nama, "nama tidak boleh kosong"); this.names = nama; } /** * {@link beanFactoryPostProcessor} untuk memesan ulang sumber properti kami di bawah semua * {@code @propertysource} item yang ditambahkan oleh {@link ConfigurationClassPostProcessor}. ? PropertySourceOrderingPostProcessor (ConfigurableApplicationContext Context) {this.context = konteks; } @Override public int getorder () {return orded.highest_precedence; } @Override public void postprocessBeanFactory (configurableListableBeanFactory beanFactory) melempar beansException {reordersources (this.context.getEnvironment ()); } private void reorderSources (lingkungan ConfigAbleNenvironment) {ConfigurationPropertySources .finishandrelocate (lingkungan.getPropertySources ()); PropertiesSource <?> DefaultPropertySources = lingkungan.getPropertySources () .remove (default_properties); if (defaultProperties! = null) {lingkungan.getPropertySources (). addLast (defaultProperties); }}} /*** Memuat sumber properti kandidat dan mengkonfigurasi profil aktif. */ private class loader {private final log logger = configFileApplicationListener.this.logger; Lingkungan Private Final ConfigableArtvironment; sumber daya sumber daya final final swasta; Properti Properti Pribadi Muat PropertiesLoader; antrian pribadi <prile> profil; Daftar Pribadi <Men profil> ProfesedProfil; Private Boolean diaktifkan Profil; Loader (lingkungan ConfigAbleNEnvironment, Resourceloader Resourceloader) {this.environment = lingkungan; this.resourceloader = resourceloader == null? DeFaultresourceloader baru (): Resourceloader; } public void load () {this.propertiesLoader = new PropertiesOursOurcloader (); this.activatedProfiles = false; this.profiles = collections.aslifoqueUe (LinkedList baru <profile> ()); this.processedprofiles = new LinkedList <prile> (); // Profil aktif yang sudah ada sebelumnya yang ditetapkan melalui lingkungan.setActiveProfiles () // adalah profil tambahan dan file konfigurasi diizinkan untuk menambahkan lebih banyak jika // mereka ingin, jadi jangan hubungi addActiveProfiles () di sini. Atur <prile> initialactiveProfiles = initializeActiveProfiles (); this.profiles.addall (getUnpressedActiveProfiles (InitialActiveProfiles)); if (this.profiles.isempty ()) {for (String defaultProfileName: this.environment.getDefaultProfiles ()) {profile defaultProfile = profil baru (defaultProfileName, true); if (! this.profiles.contains (defaultProfile)) {this.profiles.add (defaultProfile); }}} // Profil default untuk tujuan ini direpresentasikan sebagai nol. Kami menambahkannya // terakhir sehingga pertama kali keluar dari antrian (profil aktif akan // mengesampingkan pengaturan apa pun di default ketika daftar dibalik nanti). this.profiles.add (null); while (! this.profiles.isempty ()) {profile profile = this.profiles.poll (); untuk (Lokasi String: GetSearchLocations ()) {if (! Location.endswith ("/")) {// Lokasi sudah ada nama file, jadi jangan mencari lebih banyak // FileNames Load (Lokasi, Null, Profile); } else {for (string name: getsearchnames ()) {load (lokasi, nama, profil); }}} this.procedProfiles.add (profil); } addConfigurationProperties (this.propertiesloader.getPropertySources ()); } set private <profile> initializeActiveProfiles () {if (! this.environment.containsproperty (active_profiles_property) &&! this.environment.containsproperty (include_profiles_property)) {return collections.emptyset (); } // Setiap profil aktif yang sudah ada sebelumnya yang ditetapkan melalui sumber properti (misalnya sistem // properti) diutamakan daripada yang ditambahkan dalam file konfigurasi. Springprofiles springprofiles = bindspringprofiles (this.environment.getPropertySources ()); Atur <profile> ActiveProfiles = new LinkedHashset <profile> (springprofiles.getActiveProfiles ()); ActiveProfiles.addall (springprofiles.getincludeprofiles ()); MaybeactivateProfiles (ActiveProfiles); Return ActiveProfiles; } /*** Kembalikan profil aktif yang belum diproses. Jika sebuah profil * diaktifkan melalui keduanya {@link #active_profiles_property} dan * {@link ConfigAbleNeNvironment #addactiveProfile (string)} Itu perlu * difilter sehingga nilai {@link #active_profiles_property} menjadi presedensi. * <p> * Secara konkret, jika profil "cloud" diaktifkan melalui lingkungan, itu akan membutuhkan * lebih sedikit prioritas bahwa profil apa pun yang ditetapkan melalui {@link #active_profiles_property}. * @param InitialActiveProfiles Profil yang telah diaktifkan melalui * {@link #active_profiles_property} * @return Profil aktif yang belum diproses dari lingkungan untuk mengaktifkan */ daftar privat <Profil> GetunprocedActiveProfiles (set <profile> InitialActiveProfiles) {list <Profile) (Profil Profil> UNVOCTECTECES (ProfileCRECTESSECTESSECTESSPROFILE (PROFFECTECTESS (ProfilePROFESSECESSECESSECTESS (ProfileProfes (Profile (Profile (Profile, untuk (string profilename: this.environment.getActiveProfiles ()) {profile profile = profil baru (profilename); if (! InitialActiveProfiles.Contains (profil)) {UnprocedActiveProfiles.Add (profil); }} // membalikkannya sehingga urutannya sama dengan dari getProfileForValue () // (yang terakhir menang ketika properti akhirnya diselesaikan). mengembalikanProfil yang tidak diproses; } private void load (Lokasi String, Nama String, Profil Profil) {String Group = "Profile =" + (Profile == NULL? "": Profil); if (! stringutils.hastext (name)) {// Coba muat langsung dari lokasi LoadIntogroup (grup, lokasi, profil); } else {// Cari file dengan nama yang diberikan untuk (string ext: this.propertiesloader.getAllFileExtensions ()) {if (profile! = null) {// coba file spesifik profil LoadIntogroup (grup, lokasi + nama + "-" + profil + "." + Ext, null); untuk (Profile ProcessedProfile: this.procedProfiles) {if (ProcessedProfile! = null) {loadIntOgroup (grup, lokasi + nama + "-" + ProcessedProfile + "." + ext, profile); }} // Kadang-kadang orang menempatkan "spring.profiles: dev" di // application-dev.yml (gh-340). Bisa dibilang kita harus mencoba dan melakukan kesalahan // keluar tentang itu, tetapi kita bisa tetap baik dan memuatnya. LoadIntOgoup (grup, lokasi + nama + "-" + profil + "." + ext, profil); } // Juga coba bagian khusus profil (jika ada) dari file normal loadIntogroup (grup, lokasi + nama + "." + Ext, profil); }}} private propertysource <?> LoadIntOgoup (pengidentifikasi string, lokasi string, profil profil) {coba {return doloadIntOgoup (pengidentifikasi, lokasi, profil); } catch (Exception ex) {lempar baru ilegalstateException ("Gagal memuat sumber properti dari lokasi '" + lokasi + "'", ex); }} private propertysource <?> doloadIntOgoup (pengidentifikasi string, lokasi string, profil profil) melempar ioException {sumber daya sumber daya = this.resourceloader.getResource (lokasi); PropertiesSource <?> PropertiesSource = null; StringBuilder msg = stringBuilder baru (); if (sumber daya! = null && resource.exists ()) {string name = "applicationConfig: [" + location + "]"; String group = "ApplicationConfig: [" + Identifier + "]"; PropertiesSource = this.PropertiesLoader.Load (sumber daya, grup, nama, (profil == null? null: profile.getname ())); if (propertysource! = null) {msg.append ("dimuat"); handleProfileproPerties (PropertiesSource); } else {msg.append ("dilewati (kosong)"); }} else {msg.append ("dilewati"); } msg.append ("file config"); msg.append (getResourcedescription (lokasi, sumber daya)); if (profile! = null) {msg.append ("untuk profil") .append (profil); } if (resource == null ||! Resource.exists ()) {msg.append ("Resource Not Found"); this.logger.trace (msg); } else {this.logger.debug (msg); } return PropertiesSource; } Private String getResourcedescription (Lokasi String, Sumber Daya Sumber Daya) {String Resourcedescription = "'" + Lokasi + "'"; if (sumber daya! = null) {coba {resourcedescription = string.format ("'%s' (%s)", resource.geturi (). toasciistring (), lokasi); } catch (IoException ex) {// Gunakan lokasi sebagai deskripsi}} return resourcedescription; } private void handleProfileProPerties (propertiysource <?> PropertiesSource) {springprofiles springprofiles = bindspringprofiles (propertiesource); maybeactivateProfiles (springprofiles.getActiveProfiles ()); addProfiles (springprofiles.getincludeprofiles ()); } private springprofiles bindspringprofiles (propertiysource <?> PropertiesSource) {mutablePropertysources propertysources = new MutablePropertySources (); PropertiesSources.AddFirst (PropertiesSource); pengembalian bindspringprofiles (properti sumber); } private springprofiles bindspringprofiles (properti properti properti) {springprofiles springprofiles = springprofiles baru (); Databinder relaxeddatabinder = new relaxedDatabinder (Springprofiles, "spring.profiles"); databinder.bind (Properti NewsySourcesPropertyValues (PropertiesSources, FALSE)); springprofiles.setActive (ResolVEpLevolder (springprofiles.getActive ())); springprofiles.setinclude (ResolveplaceHolders (springprofiles.getInclude ())); return springprofiles; } Daftar Privat <String> ResolVEpLOmLODERS (Daftar <String> nilai) {Daftar <String> diselesaikan = ArrayList baru <string> (); untuk (nilai string: values) {resolved.add (this.environment.resolvePlaceHolders (value)); } return resolved; } private void maybeactivateProfiles (set <Troft> profil) {if (this.activateProfiles) {if (! Profiles.isempty ()) {this.logger.debug ("Profil yang sudah diaktifkan, '" + profil + "' tidak akan diterapkan"); } kembali; } if (! Profiles.isempty ()) {addProfiles (profil); this.logger.debug ("profil aktif" + stringutils.collectiontocommadelimitedString (profil)); this.activatedProfiles = true; RemeprosprosedDefaultProfiles (); }} private void removeUnpressedDefaultProfiles () {for (iterator <profile> iterator = this.profiles.iterat atau (); iterator .hasnext ();) {if (iterator.next (). isDefaultProfile ()) {iterator.remove (); }}} private void addProfiles (atur <profile> profil) {for (profil profil: profil) {this.profiles.add (profil); if (! EnvironmhaSactiveProfile (Profile.getName ())) {// Jika sudah diterima, kami menganggap pesanan telah ditetapkan // secara prependProfile (this.environment, profil); }}} private Boolean EnvironmentHasActiveProfile (Profil String) {for (String ActiveProfile: this.environment.getActiveProfiles ()) {if (ActiveProfile.equals (profil)) {return true; }} return false; } private void prependProfile (lingkungan ConfigAbleNeNvironment, profil profil) {set <string> profil = LinkedHashSet baru <String> (); lingkungan.getActiveProfiles (); // Pastikan mereka diinisialisasi // tetapi yang ini harus pergi pertama (menang terakhir dalam bentrokan kunci properti) profil.add (profile.getname ()); profiles.addall (arrays.aslist (lingkungan.getActiveProfiles ())); lingkungan.setActiveProfiles (Profiles.toArray (string baru [Profiles.size ()])); } Private Set <String> getSearchLocations () {set <string> lokasi = new LinkedHashSet <String> (); // Pengaturan yang dikonfigurasi pengguna lebih diutamakan, jadi kami melakukannya terlebih dahulu jika (this.environment.containsproperty (config_location_property)) {for (string path: asResolvedSet (this.environment.getProperty (config_location_property), null)) {if. Stringutils.cleanpath (path); if (! Resourceutils.isUrl (path)) {path = resourceutils.file_url_prefix + path; }} locations.add (path); }} locations.addall (asResolvedSet (configFileApplicationListener.this.searchlocations, default_search_locations)); Lokasi Kembali; } set private <string> getsearchnames () {if (this.environment.containsproperty (config_name_property)) {return asResolvedSet (this.environment.getProperty (config_name_property), null); } return asResolvedSet (configFileApplicationListener.this.names, default_names); } set private <string> asResolvedSet (nilai string, string fallback) {daftar <string> list = arrays.aslist (stringutils.trimarrayElements (stringutils.commadelimitedListToStringArray (value! = null? this.environment.resolveHolders (value): fallback))); Collections.reverse (daftar); return new LinkedHashSet <String> (list); } private void addConfigurationProperties (sumber MutablePropertySources) {List <PropertiesSource <? >> REORDEDEDSOURCE = ARRAYLIST baru <propertiysource <? >> (); untuk (propertiSource <?> Item: Sumber) {reorderedsources.add (item); } AddConfigurationProperties (ConfigurationPropertySources baru (REORDEDEDSOURE);} Private void addConfigurationProperties (ConfigurationPropertySysySources Sumber) {MutablePropertySources Exeursources = this.environment .getPropertySources (); ifabault {exerucources. ExeTREurces.AddBefore (Default_Properties, ConfigurationSources); Assert.notnull (nama, nama tidak boleh nol "); hashCode () {return this.name.hashcode (); Memegang konfigurasi {@link Propertiessource} S karena dimuat dapat memindahkan * mereka setelah kelas konfigurasi telah diproses Sumber) {super (application_configuration_property_source_name, sumber); (EnumerablePropertySource <?>) Sumber) .getPropertynames ())); ! PropertiesSource: Hapus. Sumber) {if (PropertiesSource dari enumerableCompositEpropertysource) {enumerableCompositeproperteSource composite = (enumerableCompositepropere name (name (for) name (for?> = Nested.getName (); Spring.profiles} Properti. ini. Daftar <prile> Profils = New ArrayList <prile> ();Menurut komentar Javadoc, kelas ini akan memuat aplikasi. Properti atau application.yml dari lokasi yang ditentukan dan membaca propertinya menjadi lingkungan. Harap perhatikan metode ini:
@Override public void onApplicationEvent (event ApplicationEvent) {if (event instanceof applicationEnvironmentPreparedEvent) {onApplicationEnvironmentPreparedEvent ((ApplicationEnvironmentPreparedEvent) acara); } if (Event instanceof ApplicationPreparedEvent) {OnApplicationPreparedEvent (Event); }} Ketika program Springboot dimulai, mendengarkan acara pasti akan dipicu. 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 method
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. Publish events
当上述步骤都执行完毕后,紧接着会发布EnvrionmentChangeEvent事件,可是这个事件谁来监听呢?在这里我贴出官网的一段描述:
应用程序将收听EnvironmentChangeEvent,并以几种标准方式进行更改(用户可以以常规方式添加ApplicationListeners附加ApplicationListeners)。当观察到EnvironmentChangeEvent时,它将有一个已更改的键值列表,应用程序将使用以下内容:
1.重新绑定上下文中的任何@ConfigurationProperties bean
2.为logging.level.*中的任何属性设置记录器级别
根据官网描述我们知道将变更一下操作行为@ConfigurationProperties的bean与更改日志level,那么如何做到的呢?结合官网文档我们来关注以下两个类:
ConfigurationPropertiesRebinder:
/* * Copyright 2013-2014 the original author or authors. * * Dilisensikan di bawah lisensi Apache, versi 2.0 ("lisensi"); * Anda tidak boleh menggunakan file ini kecuali sesuai dengan lisensi. * Anda dapat memperoleh salinan lisensi di * * http://www.apache.org/licenses/license-2.0 * * kecuali diharuskan oleh hukum yang berlaku atau disepakati secara tertulis, perangkat lunak * yang didistribusikan di bawah lisensi didistribusikan atas dasar "sebagaimana adanya", * tanpa jaminan atau ketentuan apa pun, baik yang diungkapkan atau disiratkan. * Lihat lisensi untuk izin yang mengatur bahasa tertentu dan * batasan di bawah lisensi. */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; Private 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); Kembali Benar; } catch (RuntimeException e) { this.errors.put(name, e); throw e; } } return false; } @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. * * Dilisensikan di bawah lisensi Apache, versi 2.0 ("lisensi"); * Anda tidak boleh menggunakan file ini kecuali sesuai dengan lisensi. * Anda dapat memperoleh salinan lisensi di * * http://www.apache.org/licenses/license-2.0 * * kecuali diharuskan oleh hukum yang berlaku atau disepakati secara tertulis, perangkat lunak * yang didistribusikan di bawah lisensi didistribusikan atas dasar "sebagaimana adanya", * tanpa jaminan atau ketentuan apa pun, baik yang diungkapkan atau disiratkan. * Lihat lisensi untuk izin yang mengatur bahasa tertentu dan * batasan di bawah lisensi. */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. * * Dilisensikan di bawah lisensi Apache, versi 2.0 ("lisensi"); * Anda tidak boleh menggunakan file ini kecuali sesuai dengan lisensi. * Anda dapat memperoleh salinan lisensi di * * http://www.apache.org/licenses/license-2.0 * * kecuali diharuskan oleh hukum yang berlaku atau disepakati secara tertulis, perangkat lunak * yang didistribusikan di bawah lisensi didistribusikan atas dasar "sebagaimana adanya", * tanpa jaminan atau ketentuan apa pun, baik yang diungkapkan atau disiratkan. * Lihat lisensi untuk izin yang mengatur bahasa tertentu dan * batasan di bawah lisensi. */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. * * Dilisensikan di bawah lisensi Apache, versi 2.0 ("lisensi"); 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)); Kembali Benar; } return false; } @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. * * Dilisensikan di bawah lisensi Apache, versi 2.0 ("lisensi"); 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); Kembali Benar; } return false; } @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()); hasil pengembalian; } @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; } return true; } } @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 instance of 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. Contoh
创建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
Kelas Startup:
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. Ringkasan
1) 当配置更新并通过refresh端点刷新后,会执行ContextRefresher的refresh方法,该方法会记录当前的Environment,而后构建一个简易的SpringApplicationBuilder并执行其run方法,此时ConfigFileApplicationListener会读取我们修改过后的配置并绑定到SpringApplication对象上,最后进行changes操作来变更已有的PropertySource
2) @RefreshScope最好配合@Bean使用,当且仅当变更配置后,需要重新获取最新的bean时使用。加上该注解的Bean会被代理并且延迟加载,所有的scope属性为Refresh的bean会被包装成BeanLifecycleWrapper存入缓存(ConcurrentHashMap)中,所有的读取,修改,删除都是基于该缓存的。
Meringkaskan
The above is the analysis of the SpringCloud configuration refresh principle introduced by the editor. Saya harap ini akan membantu semua orang. If you have any questions, please leave me a message. The editor will reply to everyone in time!