%23 ๋ฐํ์ ๋๋ ํฐ๋ฆฌ ์ ํ: Java / Spring Boot์์ ํ์ผ ์ ์ฅ ์์น ๊ฒฐ์
์์ฝ
์ด ๋ฌธ์๋ ์ ํ๋ฆฌ์ผ์ด์
์คํ ์ ํ์ผ์ ์ ์ฅํ ์ ๋ขฐํ ์ ์๋ "๊ธฐ๋ณธ ๋๋ ํฐ๋ฆฌ(base directory)"๋ฅผ ์ ํํ๋ ๋ฐฉ๋ฒ์ ์ ๋ฆฌํฉ๋๋ค. ์: logs์ ๊ฐ์ ๋ฐํ์ ํด๋ ์์ web-client ํด๋๋ฅผ ์์ฑํ๋ ๊ฒฝ์ฐ๋ฅผ ๋ค๋ฃจ๋ฉฐ, IDE/ํจํค์ง(JAR)/์๋น์ค ํ๊ฒฝ๋ณ ์ฃผ์์ ๊ณผ Spring Boot์ ์ ํฉํ ๊ถ์ฅ ์ ๋ต์ ์ ๊ณตํฉ๋๋ค.
๋ฌธ์ ์ ์
์ ํ๋ฆฌ์ผ์ด์ ์ด ๋ก์ปฌ ๋๋ ํฐ๋ฆฌ์ ํ์ผ์ ์ ์ฅํด์ผ ํ ๋, ์ด๋ ์์น์ ๋๋ ํฐ๋ฆฌ๋ฅผ ๋ง๋ค์ง ๊ฒฐ์ ํ๋ ๊ฒ์ ์ค์ํฉ๋๋ค:
- IDE์์ ์คํํ๋ฉด ํด๋์ค ํ์ผ์ด
target/classes์๋์ ์กด์ฌํฉ๋๋ค. - JAR๋ก ํจํค์งํด ์คํํ๋ฉด ์ฝ๋๊ฐ JAR ๋ด๋ถ์ ์๊ณ , ๋ก๊ทธ ๋ฑ ๋ฐํ์ ์ฐ์ถ๋ฌผ์ ๋ณดํต JAR ํ์ผ๊ณผ ๊ฐ์ ๋๋ ํฐ๋ฆฌ์ ์์ฑ๋ฉ๋๋ค.
- systemd ๊ฐ์ ์๋น์ค๋ก ๋ฑ๋กํ๋ฉด ํ๋ก์ธ์ค์ ์์ ๋๋ ํฐ๋ฆฌ(working directory)๊ฐ JAR ์์น์ ๋ค๋ฅผ ์ ์์ต๋๋ค.
๋ชฉํ: web-client ํด๋๊ฐ ์์ธก ๊ฐ๋ฅํ ์์น(์: ํจํค์ง๋ JAR ์, logs์ ๋์ผํ ์์น)์ ์์ฑ๋๋๋ก ๊ฒฐ์ ํ๋ ๋ฐฉ๋ฒ์ ์ฐพ์ต๋๋ค.
์ ํ์ง์ ์ฅ๋จ์
System.getProperty("user.dir")(ํ๋ก์ธ์ค ์์ ๋๋ ํฐ๋ฆฌ)- ์ฅ์ : ๊ฐ๋จํ๋ฉฐ ํ์ฌ ์์ ๋๋ ํฐ๋ฆฌ๋ฅผ ๋ฐ์ํฉ๋๋ค.
- ๋จ์ : ํ๋ก์ธ์ค๋ฅผ ์ด๋ป๊ฒ ์์ํ๋์ง์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋๋ค. ์๋น์ค ๋งค๋์ ๊ฐ ๋ค๋ฅธ ์์ ๋๋ ํฐ๋ฆฌ๋ฅผ ์ง์ ํ ์ ์์ด JAR ์์น๋ฅผ ๋ณด์ฅํ์ง ์์ต๋๋ค.
- ProtectionDomain / codeSource๋ก JAR/ํด๋์ค ์์น ๊ฒฐ์
- ์:
MyClass.class.getProtectionDomain().getCodeSource().getLocation() - ์ฅ์ : JAR๋ก ์คํ๋ ๋ JAR ํ์ผ URL์ ๋ฐํํ๋ฏ๋ก JAR์ ๋ถ๋ชจ ํด๋๋ฅผ ๊ธฐ์ค์ผ๋ก ์ผ์ ์ ์์ต๋๋ค.
- ๋จ์ : IDE์์ ์คํํ๋ฉด
target/classes๋ฅผ ๊ฐ๋ฆฌํฌ ์ ์๊ณ , ๋ณด์ ๊ด๋ฆฌ์๋ ํน์ํ ํด๋์ค๋ก๋ ๊ตฌ์ฑ์์ null์ด ๋ ์ ์์ต๋๋ค.
- ์:
- Spring Boot์
ApplicationHomeorg.springframework.boot.system.ApplicationHome์ Spring Boot ์ ํ๋ฆฌ์ผ์ด์ ์์ ์คํ ์์น(๋๋ JAR ์์น)๋ฅผ ์์ ์ ์ผ๋ก ํด๊ฒฐํฉ๋๋ค.- ์ฅ์ : ์ด ๋ชฉ์ ์ ์ํด ์ค๊ณ๋์๊ณ JAR/ํญ๋ฐ๋(exploded) ํํ ๋ชจ๋๋ฅผ ์ ์ ํ ์ฒ๋ฆฌํฉ๋๋ค.
- ๋จ์ : Spring Boot ์์กด์ฑ์ด ํ์ํ๋ฉฐ, ๋น-Spring ํ๊ฒฝ์์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- ์ค์ ๊ธฐ๋ฐ ๊ฒฝ๋ก (properties / environment)
- ์ฅ์ : ๊ฐ์ฅ ๋ช
์์ ์ด๊ณ ์ ์ด ๊ฐ๋ฅํจ (์:
app.base-dir=/opt/myapp/data). ์ด์ ํ๊ฒฝ์์ ๊ถ์ฅ๋ฉ๋๋ค. - ๋จ์ : ์ด์ํ์ด ๊ฐ์ ์ค์ ํด์ผ ํ๋ฉฐ ๋ก์ปฌ ๊ฐ๋ฐ์์๋ ๋ฒ๊ฑฐ๋ก์ธ ์ ์์ต๋๋ค.
- ์ฅ์ : ๊ฐ์ฅ ๋ช
์์ ์ด๊ณ ์ ์ด ๊ฐ๋ฅํจ (์:
- ClassLoader ๋ฆฌ์์ค ์์น ๋๋ ServletContext
- WAR ๋ฐฐํฌ ํ๊ฒฝ์์๋
ServletContext.getRealPath()๊ฐ ๋์์ด ๋ ์ ์์ผ๋ JAR ํ๊ฒฝ์์๋ ์ ๋ขฐํ๊ธฐ ์ด๋ ต์ต๋๋ค.
- WAR ๋ฐฐํฌ ํ๊ฒฝ์์๋
๊ถ์ฅ ์ ๋ต
IDE์์ ์คํํ๊ฑฐ๋ JAR/์๋น์ค๋ก ํจํค์งํด ์คํํ๋ ๊ฒฝ์ฐ๋ฅผ ๋ชจ๋ ๊ณ ๋ คํ ๋, ๋ค์ ๊ณ์ธตํ๋ ์ ๊ทผ์ ๊ถ์ฅํฉ๋๋ค:
- ์ค์ ๊ฐ(
app.base-dir)์ ์ฐ์ ์ฌ์ฉํฉ๋๋ค(์ฐ์ ์์ ๋์). ์:application.properties๋๋ ํ๊ฒฝ ๋ณ์APP_BASE_DIR. - ์ค์ ์ด ์์ผ๋ฉด Spring Boot๊ฐ ์ฌ์ฉ ์ค์ธ ๊ฒฝ์ฐ
ApplicationHome์ ์ฌ์ฉํด JAR ๋๋ ํด๋์ค๊ฐ ์์นํ ๋๋ ํฐ๋ฆฌ๋ฅผ ์ฐพ์ ๊ทธ ์๋web-client๊ฐ์ ํ์ ํด๋๋ฅผ ์์ฑํฉ๋๋ค. ApplicationHome์ ์ฌ์ฉํ ์ ์๊ฑฐ๋ ์คํจํ๋ฉด ProtectionDomain/codeSource๋ฅผ ์๋ํฉ๋๋ค.- ์ ๋ฐฉ๋ฒ๋ค์ด ๋ชจ๋ ์คํจํ๋ฉด
user.dir๋ก ํด๋ฐฑํฉ๋๋ค.
์ด ๋ฐฉ์์ ์๋น์ค ํ๊ฒฝ์์ ์์ธก ๊ฐ๋ฅํ ๋์์ ์ ๊ณตํ๋ฉด์ ๊ฐ๋ฐ ํ๊ฒฝ์์์ ํธ์์ฑ๋ ๋ณด์ฅํฉ๋๋ค.
๊ตฌํ ์ฃผ์์ฌํญ (Java / Spring Boot)
- ๋๋ ํฐ๋ฆฌ ์์ฑ์๋ ํญ์
java.nio.file.Path์Files.createDirectories(...)๋ฅผ ์ฌ์ฉํ์ธ์. - ๊ฒฝ๋ก ๊ตฌ๋ถ์๋ ํ๋์ฝ๋ฉ๋
/๋\\๋์File.separator๋๋PathAPI๋ฅผ ์ฌ์ฉํ์ธ์. IOException๋ฑ์ ์ ์ ํ ์ฒ๋ฆฌํ๊ณ ์ด๋ค ํด๋ฐฑ์ ์ฌ์ฉํ๋์ง ๋ก๊ทธ๋ก ๋จ๊ธฐ์ธ์.- ํ๋ก๋์ ํ๊ฒฝ์์๋ ์ค์ ์ผ๋ก ๋ฒ ์ด์ค ๋๋ ํฐ๋ฆฌ๋ฅผ ์ฃผ์ ํ๋ ๋ฐฉ๋ฒ์ ๊ถ์ฅํฉ๋๋ค.
์์ ๊ตฌํ (Spring Boot ์นํ์ )
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.io.File;
import org.springframework.boot.system.ApplicationHome;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public final class BaseDirResolver {
private static final String DEFAULT_FOLDER = "web-client";
public static Path resolveBaseDir(String configuredBaseDir) {
if (configuredBaseDir != null && !configuredBaseDir.isBlank()) {
return Paths.get(configuredBaseDir).toAbsolutePath();
}
// 1) ApplicationHome ์๋ (Spring Boot)
try {
ApplicationHome home = new ApplicationHome(BaseDirResolver.class);
File dir = home.getDir();
if (dir != null) {
return dir.toPath().resolve(DEFAULT_FOLDER).toAbsolutePath();
}
} catch (Throwable t) {
log.debug("ApplicationHome ์ฌ์ฉ ๋ถ๊ฐ, ํด๋ฐฑ ์งํ", t);
}
// 2) ProtectionDomain / codeSource ์๋
try {
java.net.URL codeLocation = BaseDirResolver.class.getProtectionDomain().getCodeSource().getLocation();
Path codePath = Paths.get(codeLocation.toURI());
Path parent = codePath.getParent();
if (parent != null) {
return parent.resolve(DEFAULT_FOLDER).toAbsolutePath();
}
} catch (Throwable t) {
log.debug("codeSource ์กฐํ ์คํจ, ํด๋ฐฑ", t);
}
// 3) user.dir ํด๋ฐฑ
return Paths.get(System.getProperty("user.dir")).resolve(DEFAULT_FOLDER).toAbsolutePath();
}
public static Path ensureBaseDir(Path baseDir) throws java.io.IOException {
Files.createDirectories(baseDir);
return baseDir;
}
}
์ฌ์ฉ ๋ฐฉ๋ฒ (Spring Boot ์๋น์ค)
- ์ด์ ํ๊ฒฝ์์๋
application.properties๋๋ ํ๊ฒฝ ๋ณ์์app.base-dir๋ฅผ ์ค์ ํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค. - ์ค์ ์ด ์์ผ๋ฉด ์ด ๊ตฌํ์ JAR์ด ์์นํ ๋๋ ํฐ๋ฆฌ ์์
web-client๋ฅผ ์์ฑํ๋ฏ๋ก ๋ณดํตlogs๊ฐ ์์ฑ๋๋ ์์น์ ๋์ผํด์ง๋๋ค.
systemd / ์๋น์ค ๊ณ ๋ ค์ฌํญ
- systemd ์ ๋์
WorkingDirectory๋ฅผ ์ค์ ํ ์ ์๋๋ฐ,user.dir์ ์์กดํ๋ ๊ฒฝ์ฐ ์ด๋ฅผ ๋ช ์ํด์ผ ํฉ๋๋ค. - JAR ์์น๋ฅผ ๊ธฐ์ค์ผ๋ก ์ผ์ผ๋ฉด
WorkingDirectory์ค์ ๊ณผ ๋ฌด๊ดํ๊ฒ JAR์ ๋ถ๋ชจ ๋๋ ํฐ๋ฆฌ๋ฅผ ์ฐพ์ ์ฌ์ฉํ๋ฏ๋ก ๋ ์์ ์ ์ ๋๋ค.
์์ systemd ์ค๋ํซ:
[Service]
User=appuser
ExecStart=/usr/bin/java -jar /opt/myapp/myapp.jar
WorkingDirectory=/opt/myapp
Restart=on-failure
ํ ์คํธ ๋ฐ ๊ฒ์ฆ
- ๋จ์ ํ ์คํธ: ์ธ ๊ฐ์ง(์ค์ , ApplicationHome, codeSource/user.dir) ๊ฒฝ๋ก๋ฅผ ๋ชจ์(mock)ํ๊ฑฐ๋ ์๋ฎฌ๋ ์ด์ ํด ๋ฐํ๊ฐ์ ๊ฒ์ฆํฉ๋๋ค.
- ํตํฉ ํ
์คํธ: ํจํค์ง๋ JAR์ ๋ก์ปฌ์์ ์คํํด JAR ์์
web-client๊ฐ ์์ฑ๋๋์ง ํ์ธํฉ๋๋ค. - ์๋น์ค ํ
์คํธ: ์ค์ ์๋ฒ์ ๋ฐฐํฌํด systemd๋ก ์์ ํ
logs์web-client๊ฐ ๋์ผ ์์น์ ์์ฑ๋๋์ง ํ์ธํฉ๋๋ค.
๋ณด์ ๋ฐ ๊ถํ
- ํ๋ก์ธ์ค๊ฐ ์ ํํ ๋ฒ ์ด์ค ๋๋ ํฐ๋ฆฌ์ ์ฐ๊ธฐ ๊ถํ์ด ์๋์ง ํ์ธํ์ธ์.
- ๋ถํ์ํ๊ฒ ์ ์ฒด ๊ถํ(๋ชจ๋ ์ฐ๊ธฐ)์ผ๋ก ๋๋ ํฐ๋ฆฌ๋ฅผ ์์ฑํ์ง ๋ง์ธ์.
- ์ ๋ก๋ ํ์ผ๋ช ์ ๊ฒฝ๋ก ํ์(path traversal) ๊ณต๊ฒฉ์ ๋ฐฉ์งํ๋๋ก ๊ฒ์ฆํ์ธ์.
๋ง์ด๊ทธ๋ ์ด์ / ๋ฐฐํฌ ์ฒดํฌ๋ฆฌ์คํธ
- ์ด์ํ์ด ๋ช
์์ ๊ฒฝ๋ก๋ฅผ ์ ํธํ๋ฉด
application-*.properties์app.base-dir๋ฅผ ์ถ๊ฐํ์ธ์. - JAR ์์น์ ์์กดํ๋ค๋ฉด ์๋น์ค ๋งค๋์ ๊ฐ JAR์ ์๋ํ ๋๋ ํฐ๋ฆฌ์ ๋๋๋ก ๋ฌธ์ํํ์ธ์.
- ์ด์์์ฉ ๋ฌธ์์ ๋๋ ํฐ๋ฆฌ ๊ฒฐ์ ๋ฐฉ์์ ๋ช ์ํ์ธ์.
๊ฒฐ๋ก
์ค์ ์ฐ์ ์ ๊ทผ(๋จผ์ app.base-dir ํ์ธ), ๊ทธ ๋ค์ ApplicationHome(๋๋ codeSource), ๋ง์ง๋ง์ผ๋ก user.dir ํด๋ฐฑ์ ์ฌ์ฉํ๋ ๊ฒ์ด ๊ฐ์ฅ ์์ธก ๊ฐ๋ฅํ๊ณ ์ค๋ฌด์ ์ ํฉํฉ๋๋ค. ์ด๋ ํ๋ก๋์
(JAR/์๋น์ค)์์๋ ์ผ๊ด์ฑ์ ๋ณด์ฅํ๊ณ ๊ฐ๋ฐ(IDE) ํ๊ฒฝ์์๋ ํธ์์ฑ์ ์ ๊ณตํฉ๋๋ค.
์ฐธ๊ณ : ์ด ๋
ธํธ๋ ์ต๊ทผ LocalFileUtils.getBaseDir ๋ณ๊ฒฝ ์ฌํญ๊ณผ ๋ฐํ์ logs ์์ web-client๋ฅผ ์์ฑํด์ผ ํ๋ ์๊ตฌ์ฌํญ์ ๋ฐํ์ผ๋ก ์์ฑ๋์์ต๋๋ค.