Buku “Java in the cloud. Spring Boot, Spring Cloud, Cloud Foundry »

gambar Halo semuanya! Buku ini terutama ditujukan untuk pengembang mesin Java dan JVM yang mencari cara untuk membuat perangkat lunak yang lebih baik dalam waktu singkat menggunakan Spring Boot, Spring Cloud dan Cloud Foundry. Itu untuk mereka yang sudah mendengar suara naik sekitar layanan microser. Anda mungkin sudah menyadari ketinggian apa yang telah diambil Spring Boot, dan Anda terkejut bahwa perusahaan dewasa ini menggunakan platform Cloud Foundry. Jika demikian, maka buku ini untuk Anda.

Kutipan. 3. Gaya konfigurasi aplikasi dua belas faktor


Bab ini akan membahas cara menerapkan konfigurasi aplikasi.

Tentukan sejumlah istilah kosa kata. Ketika datang ke konfigurasi di Spring, yang sering saya maksud adalah memperkenalkan berbagai implementasi konteks aplikasi, ApplicationContext , ke lingkungan Spring, yang membantu wadah memahami bagaimana menghubungkan komponen bean. Konfigurasi ini dapat direpresentasikan sebagai file XML yang harus disajikan dalam ClassPathXmlApplicationContext , atau kelas Java yang dijelaskan dengan cara yang dapat disediakan untuk objek AnnotationConfigApplicationContext . Dan tentu saja, ketika mempelajari opsi terakhir, kita akan merujuk ke konfigurasi Java.

Tetapi dalam bab ini, kita akan melihat konfigurasi dalam bentuk yang didefinisikan dalam manifes aplikasi 12-faktor . Dalam hal ini, ini menyangkut nilai literal yang dapat berubah dari satu lingkungan ke lingkungan lain: kita berbicara tentang kata sandi, port dan nama host, atau tentang flag properti. Konfigurasi mengabaikan konstanta ajaib yang dibangun ke dalam kode. Manifes mencakup kriteria yang sangat baik untuk konfigurasi yang benar: dapatkah basis kode aplikasi menjadi sumber terbuka kapan saja tanpa mengungkapkan dan membahayakan kredensial penting? Konfigurasi semacam ini merujuk secara eksklusif pada nilai-nilai yang bervariasi dari satu lingkungan ke yang lain, dan tidak berlaku, misalnya, untuk menghubungkan kacang Spring atau mengkonfigurasi rute Ruby.

Dukungan dalam kerangka Spring


Di Musim Semi, gaya konfigurasi 12-faktor telah didukung sejak munculnya kelas PropertyPlaceholderConfigurer . Setelah turunannya didefinisikan, ia menggantikan literal dalam konfigurasi XML dengan nilai yang diekstrak dari file dengan ekstensi .properties. Di lingkungan Spring, kelas PropertyPlaceholderConfigurer telah ditawarkan sejak 2003. Spring 2.5 memperkenalkan dukungan untuk namespace XML, dan pada saat yang sama mendukung substitusi properti di ruang ini. Ini memungkinkan substitusi dalam konfigurasi XML nilai literal untuk definisi komponen bean dengan nilai yang ditetapkan untuk kunci dalam file properti eksternal (dalam hal ini, dalam file properti simple.properties, yang dapat muncul di jalur kelas atau menjadi eksternal untuk aplikasi).

Konfigurasi dalam gaya 12 faktor ditujukan untuk menghilangkan ketidakpercayaan string sihir yang ada, yaitu nilai-nilai seperti alamat database dan akun untuk menghubungkannya, port, dll., Yang dikodekan keras dalam aplikasi yang dikompilasi. Jika konfigurasi dipindahkan di luar aplikasi, maka dapat diganti tanpa beralih ke rakitan kode baru.

Kelas PropertyPlaceholderConfigurer


Mari kita lihat contoh penggunaan kelas PropertyPlaceholderConfigurer, definisi XML komponen Spring bean, dan file dengan ekstensi .properties yang diambil dari batas aplikasi. Kami hanya perlu mencetak nilai yang tersedia di file properti ini. Ini akan membantu untuk melakukan kode yang ditunjukkan pada Contoh 3.1.

Contoh 3.1 File Properti: some.properties

configuration.projectName=Spring Framework 

Ini adalah class Spring ClassPathXmlApplicationContext, jadi kami menggunakan XML namespace dari konteks Spring dan arahkan ke file some.properties kami. Kemudian kita menggunakan literal dalam bentuk $ {configuration.projectName} dalam definisi komponen bean, dan Spring akan menggantinya dengan nilai-nilai dari file properti kami selama eksekusi (contoh 3.2).

Contoh 3.2. File pegas konfigurasi XML

 <context:property-placeholder location="classpath:some.properties"/> (1) <bean class="classic.Application"> <property name="configurationProjectName" value="${configuration.projectName}"/> </bean> 

(1) classpath: lokasi yang merujuk ke file di blok kode yang dikompilasi saat ini (.jar, .war, dll.). Spring mendukung banyak alternatif, termasuk file: dan url :, yang memungkinkan file ada selain dari blok kode.

Akhirnya, mari kita lihat bagaimana tampilan kelas Java, berkat yang memungkinkan untuk menyatukan semua ini (Contoh 3.3).

Contoh 3.3 Kelas Java yang harus dikonfigurasi dengan nilai properti
paket klasik;

 import org.apache.commons.logging.LogFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Application { public static void main(String[] args) { new ClassPathXmlApplicationContext("classic.xml"); } public void setConfigurationProjectName(String pn) { LogFactory.getLog(getClass()).info("the configuration project name is " + pn); } } 

Contoh pertama menggunakan format XML konfigurasi Spring bean. Di Spring 3.0 dan 3.1, situasi untuk pengembang yang menggunakan konfigurasi Java telah meningkat secara signifikan. Anotasi Nilai dan abstraksi Lingkungan diperkenalkan dalam rilis ini.

Lingkungan dan Nilai Abstrak


Abstraksi Lingkungan mewakili, selama pelaksanaan kode, hubungan tidak langsungnya dengan lingkungan di mana ia berjalan, dan memungkinkan aplikasi untuk mengajukan pertanyaan ("Apa garis pemisah adalah garis. Pembatas pada platform ini?") Tentang sifat-sifat lingkungan. Abstraksi bertindak sebagai pemetaan dari kunci dan nilai. Dengan mengkonfigurasi PropertySource di Lingkungan, Anda dapat mengonfigurasi dari mana nilai-nilai ini akan dibaca. Secara default, Spring memuat kunci sistem dan nilai lingkungan, seperti line.separator. Anda dapat menginstruksikan Spring untuk memuat kunci konfigurasi dari file dalam urutan yang sama yang dapat digunakan dalam rilis sebelumnya dari solusi substitusi properti Spring menggunakan anotasi @PropertySource.

Anotasi Nilai menyediakan cara untuk menanamkan nilai lingkungan dalam konstruktor, setter, bidang, dll. Nilai-nilai ini dapat dihitung menggunakan Spring Expression Language atau sintaks substitusi properti asalkan PropertySourcesPlaceholderConfigurer terdaftar, seperti dalam Contoh 3.4.

Contoh 3.4. Daftarkan PropertySourcesPlaceholderConfigurer
paket env;

 import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.env.Environment; import javax.annotation.PostConstruct; (1) @Configuration @PropertySource("some.properties") public class Application { private final Log log = LogFactory.getLog(getClass()); public static void main(String[] args) throws Throwable { new AnnotationConfigApplicationContext(Application.class); } (2) @Bean static PropertySourcesPlaceholderConfigurer pspc() { return new PropertySourcesPlaceholderConfigurer(); } (3) @Value("${configuration.projectName}") private String fieldValue; (4) @Autowired Application(@Value("${configuration.projectName}") String pn) { log.info("Application constructor: " + pn); } (5) @Value("${configuration.projectName}") void setProjectName(String projectName) { log.info("setProjectName: " + projectName); } (6) @Autowired void setEnvironment(Environment env) { log.info("setEnvironment: " + env.getProperty("configuration.projectName")); } (7) @Bean InitializingBean both(Environment env, @Value("${configuration.projectName}") String projectName) { return () -> { log.info("@Bean with both dependencies (projectName): " + projectName); log.info("@Bean with both dependencies (env): " + env.getProperty("configuration.projectName")); }; } @PostConstruct void afterPropertiesSet() throws Throwable { log.info("fieldValue: " + this.fieldValue); } } 

(1) Anotasi @PropertySource adalah singkatan yang mirip dengan placeholder-properti, yang mengatur PropertySource dari file dengan ekstensi .properties.

(2) PropertySourcesPlaceholderConfigurer perlu didaftarkan sebagai kacang statis, karena ini merupakan implementasi dari BeanFactoryPostProcessor dan harus dipanggil pada tahap awal dalam siklus hidup inisialisasi pada kacang Spring. Saat menggunakan komponen kacang dalam konfigurasi Spring XML, nuansa ini tidak terlihat.

(3) Anda dapat menghias bidang dengan anotasi Nilai (tapi jangan lakukan ini, jika tidak, kode tidak akan lulus ujian!) ...

(4) ... atau anotasi Nilai dapat menghiasi parameter konstruktor ...

(5) ... atau gunakan metode instalasi ...

(6) ... atau embed objek Lingkungan Pegas dan lakukan resolusi tombol secara manual.

(7) Parameter dengan anotasi Nilai juga dapat digunakan dalam penyedia argumen metode Bean dalam konfigurasi Spring Java.

Dalam contoh ini, nilai-nilai dimuat dari file simple.properties, dan kemudian memiliki nilai configuration.projectName, yang disediakan dengan berbagai cara.

Profil


Antara lain, abstraksi Lingkungan memperkenalkan profil . Ini memungkinkan Anda untuk menetapkan label (profil) untuk mengelompokkan komponen bean. Profil harus digunakan untuk menggambarkan komponen kacang dan grafik kacang yang bervariasi dari lingkungan ke lingkungan. Beberapa profil dapat diaktifkan secara bersamaan. Kacang yang tidak memiliki profil yang ditetapkan padanya selalu diaktifkan. Kacang yang memiliki profil default diaktifkan hanya jika mereka tidak memiliki profil aktif lainnya. Atribut profil dapat ditentukan dalam definisi komponen kacang dalam XML atau dalam kelas tag, kelas konfigurasi, komponen kacang individu, atau dalam metode penyedia Bean menggunakan Profil .

Profil memungkinkan Anda untuk menguraikan set komponen kacang yang harus dibuat di satu lingkungan yang agak berbeda dari yang lain. Dalam profil pengembang pengembangan lokal, misalnya, Anda dapat menggunakan sumber data H2 built-in javax.sql.DataSource, dan kemudian, ketika profil prod aktif, beralih ke sumber data javax.sql.DataSource diperoleh menggunakan pencarian JNDI atau dengan membaca properti dari variabel lingkungan di Cloud Foundry . Dalam kedua kasus, kode Anda akan berfungsi: Anda mendapatkan javax.sql.DataSource, tetapi keputusan tentang contoh khusus mana yang akan digunakan dibuat dengan mengaktifkan satu profil atau beberapa (contoh 3.5).

Contoh 3.5 Menunjukkan bahwa kelas-kelas @Configuration dapat memuat berbagai file konfigurasi dan menyediakan kacang yang berbeda berdasarkan
profil aktif

 package profiles; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.*; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.env.Environment; import org.springframework.util.StringUtils; @Configuration public class Application { private Log log = LogFactory.getLog(getClass()); @Bean static PropertySourcesPlaceholderConfigurer pspc() { return new PropertySourcesPlaceholderConfigurer(); } (1) @Configuration @Profile("prod") @PropertySource("some-prod.properties") public static class ProdConfiguration { @Bean InitializingBean init() { return () -> LogFactory.getLog(getClass()).info("prod InitializingBean"); } } @Configuration @Profile({ "default", "dev" }) (2) @PropertySource("some.properties") public static class DefaultConfiguration { @Bean InitializingBean init() { return () -> LogFactory.getLog(getClass()).info("default InitializingBean"); } } (3) @Bean InitializingBean which(Environment e, @Value("${configuration.projectName}") String projectName) { return () -> { log.info("activeProfiles: '" + StringUtils.arrayToCommaDelimitedString(e.getActiveProfiles()) + "'"); log.info("configuration.projectName: " + projectName); }; } public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(); ac.getEnvironment().setActiveProfiles("dev"); (4) ac.register(Application.class); ac.refresh(); } } 

(1) Kelas konfigurasi ini dan semua definisi Bean yang terkandung di dalamnya akan dihitung hanya jika profil prod aktif.

(2) Kelas konfigurasi ini dan semua definisi Bean yang terkandung di dalamnya akan dihitung hanya jika profil dev aktif atau tidak ada profil yang aktif, termasuk dev.

(3) Komponen InitializingBean ini hanya merekam profil yang sedang aktif dan memasukkan nilai yang akhirnya dimasukkan ke file properti.

(4) Mengaktifkan profil (atau profil) secara pemrograman cukup sederhana.

Spring merespons beberapa metode aktivasi profil lainnya menggunakan token spring_profiles_active atau spring.profiles.active. Profil dapat diatur menggunakan variabel lingkungan (misalnya, SPRING_PROFILES_ACTIVE), properti JVM (‑Dspring.profiles.active = ...), parameter inisialisasi aplikasi servlet, atau secara programatis.

Konfigurasi yang bagus


Spring Boot secara signifikan meningkatkan situasi. Lingkungan awalnya akan memuat properti secara otomatis dari hierarki lokasi yang telah ditentukan. Argumen baris perintah menimpa nilai properti yang berasal dari JNDI, yang menggantikan properti yang diperoleh dari System.getProperties (), dll.

- Argumen baris perintah.
- Atribut JNDI dari java: comp / env.
- Properti System.getProperties ().
- Variabel lingkungan sistem operasi.
- File properti eksternal dalam sistem file: (config /)? Application. (Yml.properties).
- File properti internal dalam arsip (config /)? Aplikasi. (Yml.properties).
- Anotasi @PropertySource di kelas konfigurasi.
- Sumber properti dari SpringApplication.getDefaultProperties ().

Jika profil aktif, data dari file konfigurasi berdasarkan nama profil akan secara otomatis dibaca, misalnya, dari file seperti src / main / resources / application-foo.properties, di mana foo adalah profil saat ini.

Jika pustaka SnakeYAML disebutkan di classpaths, file YAML juga akan secara otomatis dimuat, mengikuti konvensi yang pada dasarnya sama.

Halaman spesifikasi YAML menyatakan bahwa "YAML adalah standar yang dapat dibaca manusia untuk membuat serialisasi data untuk semua bahasa pemrograman." YAML adalah representasi nilai secara hierarkis. Dalam file biasa dengan ekstensi .properties, hierarki ditunjukkan oleh titik ("."), Dan dalam file YAML, baris baru dan tingkat indentasi tambahan digunakan. Akan lebih baik untuk menggunakan file-file ini untuk menghindari kebutuhan untuk menentukan akar umum di hadapan pohon konfigurasi yang sangat bercabang.

Isi file dengan ekstensi .yml ditunjukkan pada Contoh 3.6.

Contoh 3.6 File properti Application.yml. Data disajikan dalam urutan hierarkis.

 configuration: projectName : Spring Boot management: security: enabled: false 

Selain itu, lingkungan Spring Boot membuatnya lebih mudah untuk mendapatkan hasil yang tepat dalam kasus-kasus umum. Mengubah argumen -D menjadi variabel lingkungan proses dan java yang tersedia sebagai properti. Itu bahkan melakukan normalisasi mereka, di mana variabel lingkungan $ CONFIGURATION_PROJECTNAME (PROJECT_NAME CONFIGURATION) atau argumen -D dalam bentuk -Dconfiguration.projectName (configuration.project_name) dibuat tersedia menggunakan konfigurasi.projectName key (konfigurasi.project_name) sama seperti sebelumnya token spring_profiles_active tersedia.

Nilai-nilai konfigurasi adalah string, dan jika cukup, mereka dapat menjadi tidak dapat dibaca ketika mencoba memastikan bahwa kunci-kunci semacam itu sendiri tidak menjadi string ajaib dalam kode. Spring Boot memperkenalkan tipe komponen @ConfigurationProperties. Saat memberi anotasi pada POJO - Obyek Java Kuno - menggunakan @ConfigurationProperties dan menetapkan awalan, Spring akan berusaha memetakan semua properti yang dimulai dengan awalan ini ke properti POJO. Dalam contoh di bawah ini, nilai untuk configuration.projectName akan dipetakan ke instance POJO, yang kemudian dapat disuntikkan semua kode dan dereferensi untuk membaca nilai tipe-aman. Akibatnya, Anda hanya akan memiliki pemetaan dari kunci (String) di satu tempat (Contoh 3.7).

Contoh 3.7. Secara otomatis menyelesaikan properti dari src / main / resources / application.yml
paket boot;

 import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.stereotype.Component; (1) @EnableConfigurationProperties @SpringBootApplication public class Application { private final Log log = LogFactory.getLog(getClass()); public static void main(String[] args) { SpringApplication.run(Application.class); } @Autowired public Application(ConfigurationProjectProperties cp) { log.info("configurationProjectProperties.projectName = " + cp.getProjectName()); } } (2) @Component @ConfigurationProperties("configuration") class ConfigurationProjectProperties { private String projectName; (3) public String getProjectName() { return projectName; } public void setProjectName(String projectName) { this.projectName = projectName; } } 

(1) Anotasi @EnableConfigurationProperties memberitahu Spring untuk memetakan properti ke POJO yang dijelaskan dengan @ConfigurationProperties.

(2) Anotasi @ConfigurationProperties menunjukkan Spring bahwa kacang ini harus digunakan sebagai root untuk semua properti yang dimulai dengan konfigurasi., Diikuti oleh token yang memetakan ke properti objek.

(3) Bidang projectName pada akhirnya akan memiliki nilai yang ditetapkan untuk kunci properti configuration.projectName.

Spring Boot secara aktif menggunakan mekanisme @ConfigurationProperties untuk memberi pengguna kemampuan untuk mengganti komponen dasar sistem. Anda mungkin memperhatikan bahwa kunci properti memungkinkan Anda untuk membuat perubahan, misalnya, dengan menambahkan org.springframework.boot: ketergantungan spring-boot-starter-actuator ke aplikasi web Spring Boot, dan kemudian mengunjungi halaman 127.0.0.1 : 8080 / configprops.

Titik akhir aktuator akan dibahas secara lebih rinci di Bab 13. Mereka dikunci dan memerlukan nama pengguna dan kata sandi standar. Langkah-langkah keamanan dapat dinonaktifkan (tetapi hanya untuk melihat titik-titik ini) dengan menentukan management.security.enabled = false di file application.properties (atau di application.yml).

Anda akan mendapatkan daftar properti konfigurasi yang didukung berdasarkan tipe yang disajikan dalam classpath saat runtime. Saat jumlah jenis Boot Musim Semi bertambah, properti tambahan akan ditampilkan. Titik akhir ini juga akan menampilkan properti yang diekspor oleh POJO Anda yang memiliki anotasi @ConfigurationProperties.

Konfigurasi Terdaftar Terpusat Menggunakan Server Konfigurasi Cloud Spring


Sejauh ini, semuanya baik-baik saja, tetapi segala sesuatunya harus lebih sukses lagi. Kami masih belum menjawab pertanyaan tentang kasus penggunaan umum:

  • setelah perubahan dibuat pada konfigurasi aplikasi, diperlukan restart;
  • tidak ada keterlacakan: bagaimana cara menentukan perubahan yang dimasukkan ke dalam operasi, dan jika perlu, gulung kembali?
  • konfigurasi didesentralisasi; tidak segera jelas di mana perubahan harus dilakukan untuk mengubah satu atau aspek lainnya;
  • tidak ada dukungan instalasi untuk pengodean dan pengodean untuk tujuan keamanan.

Server Konfigurasi Awan Pegas


Masalah memusatkan konfigurasi dapat diselesaikan dengan menyimpan konfigurasi dalam satu direktori dan mengarahkan semua aplikasi ke dalamnya. Anda juga dapat menginstal kontrol versi direktori ini menggunakan Git atau Subversion. Kemudian dukungan yang diperlukan untuk verifikasi dan pendaftaran akan diterima. Namun dua persyaratan terakhir masih belum terpenuhi, sehingga dibutuhkan sesuatu yang lebih canggih. Beralih ke server konfigurasi Spring Cloud . Platform Spring Cloud menawarkan server konfigurasi dan klien untuk server ini.

Server Spring Cloud Config adalah REST API yang akan disambungkan klien kami untuk mengambil konfigurasi mereka. Server juga mengelola repositori konfigurasi kontrol versi. Dia adalah perantara antara pelanggan kami dan repositori konfigurasi, dan dengan demikian berada dalam posisi yang menguntungkan untuk mengimplementasikan alat keamanan untuk menghubungkan koneksi dari klien ke layanan dan koneksi dari layanan ke repositori konfigurasi dengan kontrol versi. Klien Spring Cloud Config menyediakan aplikasi klien dengan ruang lingkup baru, penyegaran, yang memungkinkan Anda untuk mengkonfigurasi komponen Spring lagi tanpa harus me-restart aplikasi.

Teknologi seperti server Spring Cloud Config memainkan peran penting, tetapi memerlukan biaya tambahan. Idealnya, tanggung jawab ini harus ditransfer ke platform dan diotomatisasi. Saat menggunakan Cloud Foundry, Anda dapat menemukan layanan Config Server di katalog layanan, yang tindakannya didasarkan pada penggunaan server Spring Cloud Config.

Pertimbangkan contoh sederhana. Pertama, konfigurasikan server Spring Cloud Config. Salah satu layanan tersebut dapat diakses oleh beberapa aplikasi Spring Boot sekaligus. Anda harus membuatnya bekerja di suatu tempat dan entah bagaimana. Maka tetap hanya untuk memberi tahu semua layanan kami tentang di mana menemukan layanan konfigurasi. Ia bekerja sebagai semacam perantara untuk kunci dan nilai konfigurasi yang dibaca dari penyimpanan Git melalui jaringan atau dari disk. Tambahkan string org.springframework.cloud: spring-cloud-config-server ke rakitan aplikasi Spring Boot Anda untuk masuk ke server Spring Cloud Config (contoh 3.8).

Contoh 3.8. Untuk menanamkan server konfigurasi dalam perakitan, gunakan anotasi @EnableConfigServer

 package demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; (1) @SpringBootApplication @EnableConfigServer public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } 

(1) Menggunakan anotasi @EnableConfigServer menyebabkan server Spring Cloud Config diinstal.

Contoh 3.9 menunjukkan konfigurasi untuk layanan konfigurasi.

Contoh 3.9. Konfigurasi server konfigurasi Src / main / resources / application.yml

server.port = 8888
spring.cloud.config.server.git.uri = \
github.com/cloud-native-java/config-server-configuration-repository (1)

(1) Indikasi repositori Git yang berfungsi yang bersifat lokal atau dapat diakses melalui jaringan (misalnya, di GitHub (https://github.com/)) dan digunakan oleh server Spring Cloud Config.

Di sini, layanan konfigurasi Spring Cloud diinstruksikan untuk mencari file konfigurasi untuk masing-masing klien di repositori Git di GitHub. Kami menunjuk ke repositori ini, tetapi tautan ke GIT URI yang valid akan melakukannya. Tentu saja, ia bahkan tidak harus mendaftar ke sistem Git, Anda dapat menggunakan Subversion atau bahkan direktori yang tidak dikelola (walaupun kami sangat menyarankan untuk tidak melakukan ini). Dalam hal ini, URI penyimpanan hardcoded, tetapi tidak ada yang mencegahnya dari argumen -D, dari argumen, atau dari variabel lingkungan.

»Informasi lebih lanjut tentang buku ini dapat ditemukan di situs web penerbit
» Isi
» Kutipan

20% diskon kupon untuk Java Fermentor - Jawa

Source: https://habr.com/ru/post/id425109/


All Articles