Spring Boot และไฟล์กำหนดค่าภายนอกหลายไฟล์


125

ฉันมีไฟล์คุณสมบัติหลายไฟล์ที่ต้องการโหลดจาก classpath มีชุดหนึ่งภายใต้เริ่มต้นคือซึ่งเป็นส่วนหนึ่งของ/src/main/resources คาดว่าไฟล์myapp.jarของฉันspringcontextจะอยู่บน classpath กล่าวคือ

<util:properties id="Job1Props"
    location="classpath:job1.properties"></util:properties>

<util:properties id="Job2Props"
    location="classpath:job2.properties"></util:properties>

ฉันยังต้องการตัวเลือกในการแทนที่คุณสมบัติเหล่านี้ด้วยชุดภายนอก ฉันมีโฟลเดอร์ config ภายนอกในcwd. ตามโฟลเดอร์ config doc ของ spring boot ควรอยู่ใน classpath แต่ไม่ชัดเจนจาก doc ว่าจะแทนที่applicaiton.propertiesจากที่นั่นหรือคุณสมบัติทั้งหมดใน config เท่านั้น

เมื่อผมทดสอบเพียงได้รับเลือกขึ้นและส่วนที่เหลือของคุณสมบัติยังคงหยิบขึ้นมาจากapplication.properties /src/main/resourcesฉันได้ลองใช้เป็นรายการที่คั่นด้วยเครื่องหมายจุลภาคspring.config.locationแต่ชุดเริ่มต้นยังไม่ถูกลบล้าง

ฉันจะสร้างไฟล์กำหนดค่าภายนอกหลายไฟล์แทนที่ไฟล์เริ่มต้นได้อย่างไร

ในขณะนี้ฉันใช้วิธีแก้ปัญหาapp.config.location(คุณสมบัติเฉพาะของแอป) ซึ่งฉันจัดหาผ่านบรรทัดคำสั่ง กล่าวคือ

java -jar myapp.jar app.config.location=file:./config

และฉันเปลี่ยนapplicationcontextเป็น

<util:properties id="Job2Props"
    location="{app.config.location}/job2.properties"></util:properties>

และนี่คือวิธีที่ฉันทำการแยกระหว่างไฟล์และคลาสพา ธ ขณะโหลดแอปพลิเคชัน
การแก้ไข:

//psuedo code

if (StringUtils.isBlank(app.config.location)) {
            System.setProperty(APP_CONFIG_LOCATION, "classpath:");
}

ฉันไม่ต้องการใช้วิธีแก้ปัญหาข้างต้นและให้สปริงแทนที่ไฟล์กำหนดค่าภายนอกทั้งหมดบนคลาสพา ธ เหมือนที่ทำกับapplication.propertiesไฟล์


4
application.propertiesมักจะเต็มไปด้วยspring.config.locationคุณสามารถเพิ่มสถานที่การกำหนดค่าเพิ่มเติมที่ได้รับการตรวจสอบไฟล์ (นั่นคือเมื่อมันจบลงด้วย/) แต่ถ้าคุณใส่จุลภาครายการที่แยกออกจากกันในการมีที่ชี้ไปยังไฟล์เหล่านั้นจะถูกโหลด นอกจากนี้ยังมีคำอธิบายใน Spring Boot Reference Guide ที่นี่
M. Deinum

คำตอบ:


156

เมื่อใช้ Spring Boot คุณสมบัติจะถูกโหลดตามลำดับต่อไปนี้ (ดูExternalized Configurationในคู่มืออ้างอิง Spring Boot)

  1. อาร์กิวเมนต์บรรทัดคำสั่ง
  2. คุณสมบัติของระบบ Java (System.getProperties ())
  3. ตัวแปรสภาพแวดล้อมระบบปฏิบัติการ
  4. JNDI แอตทริบิวต์จาก java: comp / env
  5. RandomValuePropertySource ที่มีคุณสมบัติในการสุ่มเท่านั้น *.
  6. คุณสมบัติของแอ็พพลิเคชันภายนอก jar แบบแพ็กเกจของคุณ (application.properties รวมถึง YAML และตัวแปรโปรไฟล์)
  7. คุณสมบัติของแอ็พพลิเคชันที่บรรจุอยู่ใน jar ของคุณ (application.properties รวมถึง YAML และตัวแปรโปรไฟล์)
  8. คำอธิบายประกอบ @PropertySource บนคลาส @Configuration ของคุณ
  9. คุณสมบัติดีฟอลต์ (ระบุโดยใช้ SpringApplication.setDefaultProperties)

เมื่อแก้ไขคุณสมบัติ (เช่น @Value("${myprop}")การแก้ไขจะทำในลำดับย้อนกลับ (ดังนั้นเริ่มต้นด้วย 9)

ในการเพิ่มไฟล์อื่นคุณสามารถใช้spring.config.locationคุณสมบัติที่ใช้รายการไฟล์คุณสมบัติหรือตำแหน่งไฟล์ (ไดเร็กทอรี) ที่คั่นด้วยเครื่องหมายจุลภาค

-Dspring.config.location=your/config/dir/

รายการด้านบนจะเพิ่มไดเร็กทอรีซึ่งจะได้รับการพิจารณาสำหรับapplication.propertiesไฟล์

-Dspring.config.location=classpath:job1.properties,classpath:job2.properties

สิ่งนี้จะเพิ่มไฟล์คุณสมบัติ 2 ไฟล์ลงในไฟล์ที่โหลด

ไฟล์การกำหนดค่าเริ่มต้นและตำแหน่งจะถูกโหลดก่อนไฟล์ที่ระบุเพิ่มเติมspring.config.locationซึ่งหมายความว่าส่วนหลังจะแทนที่คุณสมบัติที่ตั้งค่าไว้ในไฟล์ก่อนหน้านี้เสมอ (ดูส่วนนี้ของคู่มืออ้างอิง Spring Boot)

หากspring.config.locationมีไดเร็กทอรี (ซึ่งตรงข้ามกับไฟล์) ควรลงท้ายด้วย / (และจะต่อท้ายด้วยชื่อที่สร้างขึ้นspring.config.nameก่อนที่จะโหลด) เส้นทางการค้นหาเริ่มต้นมักใช้โดยไม่คำนึงถึงความคุ้มค่าของclasspath:,classpath:/config,file:,file:config/ spring.config.locationด้วยวิธีนี้คุณสามารถตั้งค่าเริ่มต้นสำหรับแอปพลิเคชันของคุณในapplication.properties(หรือชื่อฐานอื่น ๆ ที่คุณเลือกspring.config.name) และแทนที่ในรันไทม์ด้วยไฟล์อื่นโดยคงค่าเริ่มต้นไว้

UPDATE: เนื่องจากพฤติกรรมของ spring.config.location จะแทนที่ค่าเริ่มต้นแทนที่จะเพิ่มเข้าไป คุณต้องใช้ spring.config.additional-location เพื่อคงค่าเริ่มต้นไว้ นี่คือการเปลี่ยนแปลงพฤติกรรมจาก 1.x เป็น 2.x


2
ขอบคุณ แต่ฉันได้อ่านเอกสารอ้างอิงนี้แล้วและสิ่งต่อไปนี้ทำให้ฉันสับสน "-Dspring.config.location = your / config / dir / ข้อความข้างต้นจะเพิ่มไดเร็กทอรีที่จะใช้ในการปรึกษาสำหรับไฟล์ application.properties" หมายถึงอะไรโดยไฟล์ application.properties นั่นเป็นเพียงไฟล์เดียว ไม่ว่าในกรณีใดหากสามารถเลือกไดเร็กทอรีทั้งหมดที่มี "/" ต่อท้ายฉันก็ไม่จำเป็นต้องระบุแต่ละรายการเป็นรายการที่คั่นด้วยจุลภาค ฉันคิดว่าฉันได้ลองทั้งสองวิธีตามที่ฉันกล่าวไว้ในโพสต์ของฉันแล้ว แต่ฉันจะลองอีกครั้ง
nir

ตามที่ระบุในเอกสารจะรับได้รับการพิจารณาเช่นตำแหน่งที่ตั้งเริ่มต้นอื่น ๆ สำหรับและapplication.properties application-[env].propertiesไม่คำนึงถึงไฟล์คุณสมบัติอื่น ๆ นอกจากนี้ยังระบุไว้ในคู่มืออ้างอิง (ในส่วนลิงก์นำไปสู่และคำพูดจากคู่มืออ้างอิง)
M. Deinum

1
ใช่ แต่นั่นคือสิ่งที่ไม่สมเหตุสมผลสำหรับฉัน .. ทำไมต้องพิจารณาไฟล์ประเภทเดียวจากไดเร็กทอรีบน classpath แทนที่จะเป็นไดเร็กทอรีทั้งหมด มันบังคับให้คุณใช้ไฟล์คุณสมบัติเดียวเท่านั้นซึ่งไม่ใช่ imo ที่ดี เช่นเดียวกับใน tomcat ฉันสามารถกำหนดค่า common.loader ให้ใส่ไดเร็กทอรีเฉพาะ (และทุกอย่างไว้ด้านใน) บน classpath ทำไมไม่สามารถ boot classloader สามารถรองรับได้
nir

3
เอกสารอ้างอิงไม่เป็นประโยชน์ หากเอกสารมีความชัดเจน (เพียงพอในวิธีที่จำเป็นโดยเฉพาะหรือไม่) คำถามก็ไม่จำเป็น ตัวอย่างเช่นในกรณีนี้มันไม่ชัดเจนว่าจะโต้ตอบconfig.locationและconfig.namesโต้ตอบอย่างไรแม้ว่าอาจดูเหมือนจะชัดเจนสำหรับคนที่รู้อยู่แล้วว่าพวกเขาโต้ตอบอย่างไร คุณสามารถอัปเดตคำตอบเพื่อเพิ่มบางสิ่งในเอกสารได้หรือไม่
Narfanator

13
สิ่งนี้ควรได้รับการอัปเดตเนื่องจากพฤติกรรมของspring.config.locationตอนนี้จะแทนที่ค่าเริ่มต้นแทนที่จะเพิ่มเข้าไป คุณจำเป็นต้องใช้spring.config.additional-locationเพื่อรักษาค่าเริ่มต้น นี่คือการเปลี่ยนแปลงพฤติกรรมจาก 1.x เป็น 2.x
Robin

32

เมื่อใช้ Spring boot spring.config.location จะทำงานได้เพียงแค่ใส่ไฟล์คุณสมบัติที่คั่นด้วยเครื่องหมายจุลภาค

ดูรหัสด้านล่าง

@PropertySource(ignoreResourceNotFound=true,value="classpath:jdbc-${spring.profiles.active}.properties")
public class DBConfig{

     @Value("${jdbc.host}")
        private String jdbcHostName;
     }
}

หนึ่งสามารถใส่เวอร์ชันเริ่มต้นของ jdbc.properties ภายในแอ็พพลิเคชัน รุ่นภายนอกสามารถตั้งค่าได้ตามนี้

java -jar target/myapp.jar --spring.config.location=classpath:file:///C:/Apps/springtest/jdbc.properties,classpath:file:///C:/Apps/springtest/jdbc-dev.properties

ตามค่าโปรไฟล์ที่กำหนดโดยใช้คุณสมบัติ spring.profiles.active ค่าของ jdbc.host จะถูกหยิบขึ้นมา ดังนั้นเมื่อ (บน windows)

set spring.profiles.active=dev

jdbc.host จะรับค่าจาก jdbc-dev.properties

สำหรับ

set spring.profiles.active=default

jdbc.host จะรับค่าจาก jdbc.properties


ฉันไม่เชื่อว่าการบล็อกโค้ดอันแรกจะใช้ได้ผล ฉันรู้ในขณะที่ฉันสะดุดตัวเองกับสิ่งนี้และทำตามคำตอบนี้ ดูjira.springsource.org/browse/SPR-8539อ้างอิงในคำตอบสำหรับคำอธิบายที่เหมาะสม
Sowka

27

Spring boot 1.X และ Spring Boot 2.X ไม่มีตัวเลือกและลักษณะการทำงานที่เหมือนกันเกี่ยวกับไฟล์Externalized Configuration.

คำตอบที่ดีมากของ M. Deinum หมายถึงข้อมูลจำเพาะของ Spring Boot 1
ฉันจะอัปเดตสำหรับ Spring Boot 2 ที่นี่

แหล่งที่มาและลำดับคุณสมบัติของสภาพแวดล้อม

Spring Boot 2 ใช้PropertySourceคำสั่งเฉพาะที่ออกแบบมาเพื่อให้สามารถลบล้างค่าได้อย่างสมเหตุสมผล คุณสมบัติจะพิจารณาตามลำดับต่อไปนี้:

  • คุณสมบัติการตั้งค่าส่วนกลาง Devtools บนโฮมไดเร็กทอรีของคุณ (~ / .spring-boot-devtools.properties เมื่อ devtools แอ็คทีฟ)

  • @TestPropertySource คำอธิบายประกอบในการทดสอบของคุณ

  • @SpringBootTest#propertiesแอตทริบิวต์คำอธิบายประกอบในการทดสอบของคุณ อาร์กิวเมนต์บรรทัดคำสั่ง

  • คุณสมบัติจากSPRING_APPLICATION_JSON(inline JSON ที่ฝังอยู่ในตัวแปรสภาพแวดล้อมหรือคุณสมบัติระบบ)

  • ServletConfig พารามิเตอร์ init

  • ServletContext พารามิเตอร์ init

  • JNDI แอตทริบิวต์จากjava:comp/env.

  • คุณสมบัติของระบบ Java ( System.getProperties())

  • ตัวแปรสภาพแวดล้อมระบบปฏิบัติการ

  • A RandomValuePropertySourceที่มีคุณสมบัติในการสุ่มเท่านั้น *.

  • คุณสมบัติของแอปพลิเคชันเฉพาะโปรไฟล์ภายนอกโถบรรจุของคุณ ( application-{profile}.propertiesและตัวแปร YAML)

  • คุณสมบัติการใช้งานเฉพาะโปรไฟล์ที่บรรจุอยู่ในโถของคุณ ( application-{profile}.propertiesและตัวแปร YAML)

  • คุณสมบัติของแอปพลิเคชันภายนอกโถบรรจุของคุณ ( application.propertiesและตัวแปร YAML)

  • คุณสมบัติการใช้งานที่บรรจุอยู่ในโถของคุณ ( application.propertiesและตัวแปร YAML)

  • @PropertySourceคำอธิบายประกอบใน@Configurationชั้นเรียนของคุณ คุณสมบัติเริ่มต้น (ระบุโดยการตั้งค่า SpringApplication.setDefaultProperties)

ในการระบุไฟล์คุณสมบัติภายนอกตัวเลือกเหล่านี้คุณควรสนใจ:

  • คุณสมบัติของแอปพลิเคชันเฉพาะโปรไฟล์ภายนอกโถบรรจุของคุณ ( application-{profile}.propertiesและตัวแปร YAML)

  • คุณสมบัติของแอปพลิเคชันภายนอกโถบรรจุของคุณ ( application.propertiesและตัวแปร YAML)

  • @PropertySourceคำอธิบายประกอบใน@Configurationชั้นเรียนของคุณ คุณสมบัติเริ่มต้น (ระบุโดยการตั้งค่า SpringApplication.setDefaultProperties)

คุณสามารถใช้เพียงหนึ่งใน 3 ตัวเลือกเหล่านี้หรือรวมเข้าด้วยกันตามความต้องการของคุณ
ตัวอย่างเช่นสำหรับกรณีที่ง่ายมากโดยใช้คุณสมบัติเฉพาะโปรไฟล์ก็เพียงพอแล้ว แต่ในกรณีอื่น ๆ คุณอาจต้องการใช้ทั้งคุณสมบัติเฉพาะโปรไฟล์คุณสมบัติเริ่มต้นและ@PropertySource.

ตำแหน่งดีฟอลต์สำหรับไฟล์ application.properties

เกี่ยวกับapplication.propertiesไฟล์ (และตัวแปร) โดยค่าเริ่มต้น Spring จะโหลดและเพิ่มคุณสมบัติในสภาพแวดล้อมจากสิ่งเหล่านี้ตามลำดับต่อไปนี้:

  • ไดเร็กทอรีย่อย A / config ของไดเร็กทอรีปัจจุบัน

  • ไดเร็กทอรีปัจจุบัน

  • แพ็คเกจ classpath / config

  • รูท classpath


classpath:/,classpath:/config/,file:./,file:./config/จัดลำดับความสำคัญสูงจึงตัวอักษร:

จะใช้ไฟล์คุณสมบัติที่มีชื่อเฉพาะได้อย่างไร?

ตำแหน่งเริ่มต้นไม่เพียงพอเสมอไป: ตำแหน่งเริ่มต้นเช่นชื่อไฟล์เริ่มต้น ( application.properties) อาจไม่เหมาะสม นอกจากนี้เช่นเดียวกับในคำถาม OP คุณอาจต้องระบุไฟล์การกำหนดค่าหลายไฟล์นอกเหนือจากapplication.properties(และตัวแปร)
ดังนั้นspring.config.nameจะไม่เพียงพอ

ในกรณีนี้คุณควรระบุตำแหน่งที่ชัดเจนโดยใช้spring.config.locationคุณสมบัติสภาพแวดล้อม (ซึ่งเป็นรายการตำแหน่งไดเร็กทอรีหรือเส้นทางไฟล์ที่คั่นด้วยเครื่องหมายจุลภาค)
หากต้องการเป็นอิสระเกี่ยวกับรูปแบบชื่อไฟล์จะชอบรายการพา ธ ไฟล์มากกว่ารายการไดเร็กทอรี
ตัวอย่างเช่นทำเช่นนั้น:

java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties

วิธีนี้เป็นวิธีที่ละเอียดที่สุดเพียงแค่ระบุโฟลเดอร์ แต่ยังเป็นวิธีระบุไฟล์คอนฟิกูเรชันของเราอย่างละเอียดและจัดทำเอกสารคุณสมบัติที่ใช้อย่างมีประสิทธิภาพอย่างชัดเจน

spring.config.location ตอนนี้แทนที่ตำแหน่งเริ่มต้นแทนที่จะเพิ่มเข้าไป

ด้วย Spring Boot 1 spring.config.locationอาร์กิวเมนต์จะเพิ่มตำแหน่งที่ระบุในสภาพแวดล้อม Spring
แต่จาก Spring Boot 2 spring.config.locationแทนที่ตำแหน่งเริ่มต้นที่ใช้โดย Spring โดยตำแหน่งที่ระบุในสภาพแวดล้อม Spring ตามที่ระบุไว้ในเอกสารในเอกสาร

เมื่อกำหนดค่าตำแหน่งการกำหนดค่าแบบกำหนดเองโดยใช้ตำแหน่ง spring.config.locationเหล่านี้จะแทนที่ตำแหน่งเริ่มต้น ตัวอย่างเช่นถ้าspring.config.locationมีการกำหนดค่าที่มีค่า classpath:/custom-config/, file:./custom-config/, ลำดับการค้นหาจะกลายเป็นดังต่อไปนี้:

  1. file:./custom-config/

  2. classpath:custom-config/

spring.config.locationตอนนี้เป็นวิธีตรวจสอบให้แน่ใจว่าapplication.propertiesต้องระบุไฟล์ใด ๆอย่างชัดเจน
สำหรับ uber JAR ที่ไม่ควรทำแพ็กเกจapplication.propertiesไฟล์นั้นค่อนข้างดี

หากต้องการรักษาพฤติกรรมเก่า ๆspring.config.locationในขณะที่ใช้ Spring Boot 2 คุณสามารถใช้spring.config.additional-locationคุณสมบัติใหม่แทนที่จะspring.config.locationยังคงเพิ่มตำแหน่งตามที่ระบุไว้ในเอกสาร :

หรือเมื่อกำหนดค่าตำแหน่งการกำหนดค่าแบบกำหนดเองโดยใช้ spring.config.additional-locationจะใช้นอกเหนือจากตำแหน่งเริ่มต้น


ในทางปฏิบัติ

ดังนั้นสมมติว่าในคำถาม OP คุณมีไฟล์คุณสมบัติภายนอก 2 ไฟล์ที่จะระบุและ 1 ไฟล์คุณสมบัติที่รวมอยู่ในโถ uber

ในการใช้เฉพาะไฟล์คอนฟิกูเรชันที่คุณระบุ:

-Dspring.config.location=classpath:/job1.properties,classpath:/job2.properties,classpath:/applications.properties   

ในการเพิ่มไฟล์การกำหนดค่าลงในตำแหน่งเริ่มต้น:

-Dspring.config.additional-location=classpath:/job1.properties,classpath:/job2.properties

classpath:/applications.properties อยู่ในตัวอย่างสุดท้ายที่ไม่จำเป็นเนื่องจากตำแหน่งเริ่มต้นมีและตำแหน่งเริ่มต้นนั้นไม่ได้ถูกเขียนทับ แต่ขยาย


คำตอบของคุณสมบูรณ์จริงๆยกเว้นสิ่งหนึ่ง: Spring จะค้นหา job1.properties ภายนอกบนดิสก์ได้ที่ไหนถ้าคุณระบุ: "classpath: /job1.properties"? คุณเพิ่มไดเร็กทอรีของคุณที่มีคุณสมบัติภายนอกลงใน classpath ที่นี่ได้อย่างไร?
Tristan

@Tristan โดยพื้นฐานแล้วสปริงสามารถอ่านapplication.propertiesค่าพารามิเตอร์ทั้งหมดและหลายตัว${file_name}.propertiesพร้อมชุดคุณสมบัติที่กำหนดไว้บางส่วน ดังนั้นหากคุณใช้@PropertySourceหรือลิงก์ที่ชัดเจนอื่น ๆ ไปยังไฟล์คุณสามารถสร้างไฟล์ภายนอกอื่น ๆ และแทนที่คุณสมบัตินั้นได้ (เช่นจากclasspath:file.properties)
Mister_Jesus

23

ลองดู PropertyPlaceholderConfigurer ฉันพบว่ามันชัดเจนกว่าการใช้คำอธิบายประกอบ

เช่น

@Configuration
public class PropertiesConfiguration {


    @Bean
    public PropertyPlaceholderConfigurer properties() {
        final PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
//        ppc.setIgnoreUnresolvablePlaceholders(true);
        ppc.setIgnoreResourceNotFound(true);

        final List<Resource> resourceLst = new ArrayList<Resource>();

        resourceLst.add(new ClassPathResource("myapp_base.properties"));
        resourceLst.add(new FileSystemResource("/etc/myapp/overriding.propertie"));
        resourceLst.add(new ClassPathResource("myapp_test.properties"));
        resourceLst.add(new ClassPathResource("myapp_developer_overrides.properties")); // for Developer debugging.

        ppc.setLocations(resourceLst.toArray(new Resource[]{}));

        return ppc;
    }

ขอบคุณมากสำหรับคำตอบนี้ คุณช่วยแจ้งให้ฉันทราบได้อย่างไรว่าฉันจะบรรลุสิ่งเดียวกันในโครงการที่มีการกำหนดค่า XML สำหรับสิ่งต่างๆที่ไม่มีไฟล์ XML พื้นฐานได้อย่างไร คำตอบของคุณข้างต้นช่วยฉันในโครงการอื่น ๆ ซึ่งใช้คำอธิบายประกอบ ขอขอบคุณอีกครั้ง
Chetan

8

นี่เป็นแนวทางง่ายๆอย่างหนึ่งโดยใช้สปริงบูต

TestClass.java

@Configuration
@Profile("one")
@PropertySource("file:/{selected location}/app.properties")
public class TestClass {

    @Autowired
    Environment env;

    @Bean
    public boolean test() {
        System.out.println(env.getProperty("test.one"));
        return true;
    }
}

app.propertiesบริบทของคุณในสถานที่ที่เลือก

test.one = 1234

โปรแกรมสปริงบูตของคุณ

@SpringBootApplication

public class TestApplication {

    public static void main(String[] args) {
        SpringApplication.run(testApplication.class, args);
    }
}

และบริบทapplication.properties ที่กำหนดไว้ล่วงหน้า

spring.profiles.active = one

คุณสามารถเขียนคลาสการกำหนดค่าได้มากเท่าที่คุณต้องการและเปิด / ปิดการใช้งานเพียงแค่ตั้งค่าspring.profiles.active = ชื่อ / ชื่อโปรไฟล์ {คั่นด้วยเครื่องหมายจุลภาค}

อย่างที่คุณเห็นว่าสปริงบูตนั้นยอดเยี่ยมเพียงแค่ต้องการทำความคุ้นเคยในบางครั้งมันก็คุ้มค่าที่จะกล่าวถึงคุณอาจใช้ @Value ในฟิลด์ของคุณเช่นกัน

@Value("${test.one}")
String str;

7

ผมมีปัญหาเหมือนกัน. ฉันต้องการมีความสามารถในการเขียนทับไฟล์คอนฟิกูเรชันภายในเมื่อเริ่มต้นด้วยไฟล์ภายนอกซึ่งคล้ายกับการตรวจหาแอปพลิเคชัน Spring Boot.properties ในกรณีของฉันเป็นไฟล์ user.properties ที่เก็บผู้ใช้แอปพลิเคชันของฉัน

ความต้องการของฉัน:

โหลดไฟล์จากตำแหน่งต่อไปนี้ (ตามลำดับนี้)

  1. คลาสพา ธ
  2. A / config subdir ของไดเร็กทอรีปัจจุบัน
  3. ไดเร็กทอรีปัจจุบัน
  4. จากไดเร็กทอรีหรือตำแหน่งไฟล์ที่กำหนดโดยพารามิเตอร์บรรทัดคำสั่งเมื่อเริ่มต้น

ฉันคิดวิธีแก้ปัญหาต่อไปนี้:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.PathResource;
import org.springframework.core.io.Resource;

import java.io.IOException;
import java.util.Properties;

import static java.util.Arrays.stream;

@Configuration
public class PropertiesConfig {

    private static final Logger LOG = LoggerFactory.getLogger(PropertiesConfig.class);

    private final static String PROPERTIES_FILENAME = "user.properties";

    @Value("${properties.location:}")
    private String propertiesLocation;

    @Bean
    Properties userProperties() throws IOException {
        final Resource[] possiblePropertiesResources = {
                new ClassPathResource(PROPERTIES_FILENAME),
                new PathResource("config/" + PROPERTIES_FILENAME),
                new PathResource(PROPERTIES_FILENAME),
                new PathResource(getCustomPath())
        };
        // Find the last existing properties location to emulate spring boot application.properties discovery
        final Resource propertiesResource = stream(possiblePropertiesResources)
                .filter(Resource::exists)
                .reduce((previous, current) -> current)
                .get();
        final Properties userProperties = new Properties();

        userProperties.load(propertiesResource.getInputStream());

        LOG.info("Using {} as user resource", propertiesResource);

        return userProperties;
    }

    private String getCustomPath() {
        return propertiesLocation.endsWith(".properties") ? propertiesLocation : propertiesLocation + PROPERTIES_FILENAME;
    }

}

ตอนนี้แอปพลิเคชันใช้ทรัพยากร classpath แต่ตรวจสอบทรัพยากรในตำแหน่งที่ตั้งอื่น ๆ ด้วย ทรัพยากรสุดท้ายที่มีอยู่จะถูกเลือกและใช้ ฉันสามารถเริ่มต้นแอปของฉันด้วย java -jar myapp.jar --properties.location = / directory / myproperties.properties เพื่อใช้ตำแหน่งคุณสมบัติที่ลอยเรือของฉัน

รายละเอียดที่สำคัญที่นี่: ใช้สตริงว่างเป็นค่าเริ่มต้นสำหรับ properties.location ในคำอธิบายประกอบ @Value เพื่อหลีกเลี่ยงข้อผิดพลาดเมื่อไม่ได้ตั้งค่าคุณสมบัติ

หลักการสำหรับ properties.location คือ: ใช้ไดเร็กทอรีหรือพา ธ ไปยังไฟล์คุณสมบัติเป็น properties.location

หากคุณต้องการแทนที่เฉพาะคุณสมบัติเฉพาะ PropertiesFactoryBean ที่มี setIgnoreResourceNotFound (true) สามารถใช้ได้กับอาร์เรย์ทรัพยากรที่ตั้งค่าเป็นตำแหน่ง

ฉันแน่ใจว่าโซลูชันนี้สามารถขยายเพื่อจัดการไฟล์หลายไฟล์ ...

แก้ไข

นี่คือวิธีแก้ปัญหาของฉันสำหรับไฟล์หลาย ๆ ไฟล์ :) เช่นก่อนหน้านี้สามารถใช้ร่วมกับ PropertiesFactoryBean

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.PathResource;
import org.springframework.core.io.Resource;

import java.io.IOException;
import java.util.Map;
import java.util.Properties;

import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toMap;

@Configuration
class PropertiesConfig {

    private final static Logger LOG = LoggerFactory.getLogger(PropertiesConfig.class);
    private final static String[] PROPERTIES_FILENAMES = {"job1.properties", "job2.properties", "job3.properties"};

    @Value("${properties.location:}")
    private String propertiesLocation;

    @Bean
    Map<String, Properties> myProperties() {
        return stream(PROPERTIES_FILENAMES)
                .collect(toMap(filename -> filename, this::loadProperties));
    }

    private Properties loadProperties(final String filename) {
        final Resource[] possiblePropertiesResources = {
                new ClassPathResource(filename),
                new PathResource("config/" + filename),
                new PathResource(filename),
                new PathResource(getCustomPath(filename))
        };
        final Resource resource = stream(possiblePropertiesResources)
                .filter(Resource::exists)
                .reduce((previous, current) -> current)
                .get();
        final Properties properties = new Properties();

        try {
            properties.load(resource.getInputStream());
        } catch(final IOException exception) {
            throw new RuntimeException(exception);
        }

        LOG.info("Using {} as user resource", resource);

        return properties;
    }

    private String getCustomPath(final String filename) {
        return propertiesLocation.endsWith(".properties") ? propertiesLocation : propertiesLocation + filename;
    }

}

วิธีแก้ปัญหาที่ดี เช่นเดียวกับที่สร้าง java8! ฉันไม่สามารถใช้สิ่งนั้นได้เนื่องจากฉันต้องการถั่วคุณสมบัติหลายตัวไม่ใช่แค่เมล็ดเดียว หากคุณเห็นการแก้ไขของฉันวิธีแก้ปัญหาของฉันค่อนข้างคล้ายกันและเรียบร้อยสำหรับกรณีการใช้งานของฉัน
nir

ฉันโพสต์เวอร์ชันสำหรับไฟล์หลายไฟล์เพื่อความสมบูรณ์;)
mxsb

6

สปริงบูตช่วยให้เราสามารถเขียนโปรไฟล์ที่แตกต่างกันเพื่อเขียนสำหรับสภาพแวดล้อมที่แตกต่างกันตัวอย่างเช่นเราสามารถมีไฟล์คุณสมบัติแยกต่างหากสำหรับการผลิต qa และสภาพแวดล้อมภายใน

application-local.properties ที่มีการกำหนดค่าตามเครื่องโลคัลของฉันคือ

spring.profiles.active=local

spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=users
spring.data.mongodb.username=humble_freak
spring.data.mongodb.password=freakone

spring.rabbitmq.host=localhost
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.port=5672

rabbitmq.publish=true

ในทำนองเดียวกันเราสามารถเขียน application-prod.properties และ application-qa.properties เป็นไฟล์คุณสมบัติได้มากเท่าที่เราต้องการ

จากนั้นเขียนสคริปต์เพื่อเริ่มแอปพลิเคชันสำหรับสภาพแวดล้อมที่แตกต่างกันเช่น

mvn spring-boot:run -Drun.profiles=local
mvn spring-boot:run -Drun.profiles=qa
mvn spring-boot:run -Drun.profiles=prod

5

ฉันเพิ่งมีปัญหาคล้าย ๆ กันนี้และในที่สุดก็หาสาเหตุได้: ไฟล์ application.properties มีแอตทริบิวต์ความเป็นเจ้าของและ rwx ที่ไม่ถูกต้อง ดังนั้นเมื่อ tomcat เริ่มต้นไฟล์ application.properties อยู่ในตำแหน่งที่ถูกต้อง แต่เป็นของผู้ใช้รายอื่น:

$ chmod 766 application.properties

$ chown tomcat application.properties

ฉันคิดว่าฉันมีปัญหาที่คล้ายกัน ฉันได้ติดตั้ง tomcat ในโฟลเดอร์ opt คุณวางไฟล์ใบสมัครไว้ที่ไหน ฉันควรเปลี่ยนแอตทริบิวต์ของโฟลเดอร์ด้วยหรือไม่?
anakin59490

3

โซลูชัน @mxsb เวอร์ชันแก้ไขที่ช่วยให้เรากำหนดไฟล์หลายไฟล์และในกรณีของฉันไฟล์เหล่านี้คือไฟล์ yml

ใน application-dev.yml ของฉันฉันได้เพิ่มการกำหนดค่านี้เพื่อให้ฉันฉีด yml ทั้งหมดที่มี -dev.yml อยู่ในนั้น นี่อาจเป็นรายการไฟล์เฉพาะได้ด้วย "classpath: /test/test.yml,classpath: /test2/test.yml"

application:
  properties:
    locations: "classpath*:/**/*-dev.yml"

สิ่งนี้ช่วยในการรับแผนที่คุณสมบัติ

@Configuration

public class PropertiesConfig {

private final static Logger LOG = LoggerFactory.getLogger(PropertiesConfig.class);

@Value("${application.properties.locations}")
private String[] locations;

@Autowired
private ResourceLoader rl;

@Bean
Map<String, Properties> myProperties() {
    return stream(locations)
            .collect(toMap(filename -> filename, this::loadProperties));
}

private Properties loadProperties(final String filename) {

    YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
    try {
        final Resource[] possiblePropertiesResources = ResourcePatternUtils.getResourcePatternResolver(rl).getResources(filename);
        final Properties properties = new Properties();
        stream(possiblePropertiesResources)
                .filter(Resource::exists)
                .map(resource1 -> {
                    try {
                        return loader.load(resource1.getFilename(), resource1);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }).flatMap(l -> l.stream())
                .forEach(propertySource -> {
                    Map source = ((MapPropertySource) propertySource).getSource();
                    properties.putAll(source);
                });

        return properties;
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
}

อย่างไรก็ตามหากเป็นเช่นนั้นในกรณีของฉันฉันต้องการแยกไฟล์ yml สำหรับแต่ละโปรไฟล์แล้วโหลดและฉีดเข้าไปในการกำหนดค่าสปริงโดยตรงก่อนที่จะเริ่มต้นถั่ว

config
    - application.yml
    - application-dev.yml
    - application-prod.yml
management
    - management-dev.yml
    - management-prod.yml

... คุณเข้าใจแล้ว

ส่วนประกอบแตกต่างกันเล็กน้อย

@Component
public class PropertiesConfigurer extends     PropertySourcesPlaceholderConfigurer
    implements EnvironmentAware, InitializingBean {

private final static Logger LOG = LoggerFactory.getLogger(PropertiesConfigurer.class);

private String[] locations;

@Autowired
private ResourceLoader rl;
private Environment environment;

@Override
public void setEnvironment(Environment environment) {
    // save off Environment for later use
    this.environment = environment;
    super.setEnvironment(environment);
}

@Override
public void afterPropertiesSet() throws Exception {
    // Copy property sources to Environment
    MutablePropertySources envPropSources = ((ConfigurableEnvironment) environment).getPropertySources();
    envPropSources.forEach(propertySource -> {
        if (propertySource.containsProperty("application.properties.locations")) {
            locations = ((String) propertySource.getProperty("application.properties.locations")).split(",");
            stream(locations).forEach(filename -> loadProperties(filename).forEach(source ->{
                envPropSources.addFirst(source);
            }));
        }
    });
}


private List<PropertySource> loadProperties(final String filename) {
    YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
    try {
        final Resource[] possiblePropertiesResources = ResourcePatternUtils.getResourcePatternResolver(rl).getResources(filename);
        final Properties properties = new Properties();
        return stream(possiblePropertiesResources)
                .filter(Resource::exists)
                .map(resource1 -> {
                    try {
                        return loader.load(resource1.getFilename(), resource1);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }).flatMap(l -> l.stream())
                .collect(Collectors.toList());
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

}


3

หากคุณต้องการแทนที่ค่าที่ระบุในไฟล์ application.properties ของคุณคุณสามารถเปลี่ยนโปรไฟล์ที่แอ็คทีฟของคุณในขณะที่คุณรันแอ็พพลิเคชันของคุณและสร้างไฟล์คุณสมบัติแอ็พพลิเคชันสำหรับโปรไฟล์ ตัวอย่างเช่นให้ระบุโปรไฟล์ที่ใช้งานอยู่ "แทนที่" จากนั้นสมมติว่าคุณได้สร้างไฟล์คุณสมบัติของแอปพลิเคชันใหม่ชื่อ "application-override.properties" ภายใต้ / tmp จากนั้นคุณสามารถเรียกใช้

java -jar yourApp.jar --spring.profiles.active="override" --spring.config.location="file:/tmp/,classpath:/" 

ค่าที่ระบุภายใต้ spring.config.location จะได้รับการประเมินตามลำดับย้อนกลับ ดังนั้นในตัวอย่างของฉัน classpat จะถูกประเมินก่อนตามด้วยค่าไฟล์

หากไฟล์ jar และไฟล์ "application-override.properties" อยู่ในไดเร็กทอรีปัจจุบันคุณสามารถใช้

java -jar yourApp.jar --spring.profiles.active="override"

เนื่องจาก Spring Boot จะค้นหาไฟล์คุณสมบัติให้คุณ


1
จะบอกให้สปริงใช้โปรไฟล์ "ลบล้าง" เป็นโปรไฟล์ที่ใช้งานอยู่ของคุณ แน่นอนมันจะเกินค่าที่ระบุในไฟล์ application.yml หรือ application.properties
acaruci

มันจะดูภายในโฟลเดอร์สำหรับไฟล์ config ใด ๆ .ymal หรือ. คุณสมบัติในกรณีของฉันฉันใส่เฉพาะ application-profile.yml จากนั้นจะใช้อย่างถูกต้องขอบคุณ @acaruci มันเป็นการเดินทางที่ดี
Ahmed Salem

0

ฉันพบว่านี่เป็นรูปแบบที่มีประโยชน์ในการปฏิบัติตาม:

@RunWith(SpringRunner)
@SpringBootTest(classes = [ TestConfiguration, MyApplication ],
        properties = [
                "spring.config.name=application-MyTest_LowerImportance,application-MyTest_MostImportant"
                ,"debug=true", "trace=true"
        ]
)

ในที่นี้เราจะแทนที่การใช้ "application.yml" เพื่อใช้ "application-MyTest_LowerImportance.yml" และ "application-MyTest_MostImportant.yml"
(Spring จะค้นหาไฟล์. คุณสมบัติด้วย)

นอกจากนี้ยังรวมเป็นโบนัสพิเศษคือการตั้งค่าการดีบักและการติดตามในบรรทัดแยกต่างหากเพื่อให้คุณสามารถแสดงความคิดเห็นได้หากจำเป็น]

การดีบัก / การติดตามมีประโยชน์อย่างไม่น่าเชื่อเนื่องจาก Spring จะถ่ายโอนชื่อของไฟล์ทั้งหมดที่โหลดและไฟล์ที่พยายามโหลด
คุณจะเห็นบรรทัดเช่นนี้ในคอนโซลขณะรันไทม์:

TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'file:./config/application-MyTest_MostImportant.properties' (file:./config/application-MyTest_MostImportant.properties) resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'file:./config/application-MyTest_MostImportant.xml' (file:./config/application-MyTest_MostImportant.xml) resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'file:./config/application-MyTest_MostImportant.yml' (file:./config/application-MyTest_MostImportant.yml) resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'file:./config/application-MyTest_MostImportant.yaml' (file:./config/application-MyTest_MostImportant.yaml) resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'file:./config/application-MyTest_LowerImportance.properties' (file:./config/application-MyTest_LowerImportance.properties) resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'file:./config/application-MyTest_LowerImportance.xml' (file:./config/application-MyTest_LowerImportance.xml) resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'file:./config/application-MyTest_LowerImportance.yml' (file:./config/application-MyTest_LowerImportance.yml) resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'file:./config/application-MyTest_LowerImportance.yaml' (file:./config/application-MyTest_LowerImportance.yaml) resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'file:./application-MyTest_MostImportant.properties' (file:./application-MyTest_MostImportant.properties) resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'file:./application-MyTest_MostImportant.xml' (file:./application-MyTest_MostImportant.xml) resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'file:./application-MyTest_MostImportant.yml' (file:./application-MyTest_MostImportant.yml) resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'file:./application-MyTest_MostImportant.yaml' (file:./application-MyTest_MostImportant.yaml) resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'file:./application-MyTest_LowerImportance.properties' (file:./application-MyTest_LowerImportance.properties) resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'file:./application-MyTest_LowerImportance.xml' (file:./application-MyTest_LowerImportance.xml) resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'file:./application-MyTest_LowerImportance.yml' (file:./application-MyTest_LowerImportance.yml) resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'file:./application-MyTest_LowerImportance.yaml' (file:./application-MyTest_LowerImportance.yaml) resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'classpath:/config/application-MyTest_MostImportant.properties' resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'classpath:/config/application-MyTest_MostImportant.xml' resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'classpath:/config/application-MyTest_MostImportant.yml' resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'classpath:/config/application-MyTest_MostImportant.yaml' resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'classpath:/config/application-MyTest_LowerImportance.properties' resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'classpath:/config/application-MyTest_LowerImportance.xml' resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'classpath:/config/application-MyTest_LowerImportance.yml' resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'classpath:/config/application-MyTest_LowerImportance.yaml' resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'classpath:/application-MyTest_MostImportant.properties' resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'classpath:/application-MyTest_MostImportant.xml' resource not found
DEBUG 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Loaded config file 'file:/Users/xxx/dev/myproject/target/test-classes/application-MyTest_MostImportant.yml' (classpath:/application-MyTest_MostImportant.yml)
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'classpath:/application-MyTest_MostImportant.yaml' resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'classpath:/application-MyTest_LowerImportance.properties' resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'classpath:/application-MyTest_LowerImportance.xml' resource not found
DEBUG 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Loaded config file 'file:/Users/xxx/dev/myproject/target/test-classes/application-MyTest_LowerImportance.yml' (classpath:/application-MyTest_LowerImportance.yml)
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'classpath:/application-MyTest_LowerImportance.yaml' resource not found
TRACE 93941 --- [   main] o.s.b.c.c.ConfigFileApplicationListener  : Skipped config file 'file:./config/application-MyTest_MostImportant-test.properties' (file:./config/application-MyTest_MostImportant-test.properties) resource not found

-1

ฉันพบปัญหามากมายเมื่อพยายามคิดออก นี่คือการตั้งค่าของฉัน

Dev Env: Windows 10, Java: 1.8.0_25, Spring Boot: 2.0.3.RELEASE, Spring: 5.0.7.RELEASE

สิ่งที่ฉันพบคือฤดูใบไม้ผลิยึดตามแนวคิด "ค่าเริ่มต้นที่เหมาะสมสำหรับการกำหนดค่า" สิ่งที่แปลได้คือคุณต้องมีไฟล์คุณสมบัติทั้งหมดของคุณเป็นส่วนหนึ่งของไฟล์สงครามของคุณ จากนั้นคุณสามารถแทนที่ได้โดยใช้คุณสมบัติบรรทัดคำสั่ง "--spring.config.additional-location" เพื่อชี้ไปที่ไฟล์คุณสมบัติภายนอก แต่จะไม่ทำงานหากไฟล์คุณสมบัติไม่ได้เป็นส่วนหนึ่งของไฟล์สงครามดั้งเดิม

รหัสสาธิต: https://github.com/gselvara/spring-boot-property-demo/tree/master

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.