Grobble
Classpath Index 본문
org.springframework.boot:spring-boot-maven-plugin
을 사용하면 Spring Boot Executable Jar를 생성할 수 있다. 이 Jar내부에는 BOOT-INF/classpath.idx
파일이 존재하게 되는데, 내용은 대략 아래와 같은 모양을 가진다
- "BOOT-INF/lib/pinpoint-web-2.6.0-SNAPSHOT.jar"
- "BOOT-INF/lib/pinpoint-commons-2.6.0-SNAPSHOT.jar"
- "BOOT-INF/lib/pinpoint-commons-server-2.6.0-SNAPSHOT.jar"
- "BOOT-INF/lib/pinpoint-commons-profiler-2.6.0-SNAPSHOT.jar"
- "BOOT-INF/lib/pinpoint-plugins-loader-2.6.0-SNAPSHOT.jar"
- "BOOT-INF/lib/spring-boot-autoconfigure-2.7.13.jar"
- "BOOT-INF/lib/jakarta.validation-api-2.0.2.jar"
- "BOOT-INF/lib/pinpoint-commons-server-cluster-2.6.0-SNAPSHOT.jar"
- "BOOT-INF/lib/curator-client-4.2.0.jar"
이 내용은 해당 프로젝트 디렉토리에서 mvn dependency:tree
를 실행했을 때의 결과에서 depth 정보를 뺀 것과 같다. 예를 들어, 위의 classpath.idx
내용이 나오는 프로젝트에서 mvn dependency:tree
를 실행하면 아래와 같이 나온다.
[INFO] com.navercorp.pinpoint:pinpoint-web-starter:jar:2.6.0-SNAPSHOT
[INFO] +- com.navercorp.pinpoint:pinpoint-web:jar:2.6.0-SNAPSHOT:compile
[INFO] | +- com.navercorp.pinpoint:pinpoint-commons:jar:2.6.0-SNAPSHOT:compile
[INFO] | +- com.navercorp.pinpoint:pinpoint-commons-server:jar:2.6.0-SNAPSHOT:compile
[INFO] | | +- com.navercorp.pinpoint:pinpoint-commons-profiler:jar:2.6.0-SNAPSHOT:compile
[INFO] | | +- com.navercorp.pinpoint:pinpoint-plugins-loader:jar:2.6.0-SNAPSHOT:compile
[INFO] | | +- org.springframework.boot:spring-boot-autoconfigure:jar:2.7.13:compile
[INFO] | | \- jakarta.validation:jakarta.validation-api:jar:2.0.2:compile
[INFO] | +- com.navercorp.pinpoint:pinpoint-commons-server-cluster:jar:2.6.0-SNAPSHOT:compile
[INFO] | | +- org.apache.curator:curator-client:jar:4.2.0:compile
보면, 첫줄은 자기 자신이므로 제외되고, 나머지는 순서가 그대로인 것을 볼 수 있다.
이걸 어디에 쓰나
JVM은 classpath를 여러 개 가질 수 있다. 그러다보니 동일한 classpath를 가지는 리소스가 복수 존재할 수 있는데, 이 리소스간의 우선순위는 classpath가 정의된 순서에 따라(먼저 등장한 쪽이 승리) 결정된다. 예를 들어 다음과 같이 java를 실행했다면,
java -cp "A.jar:B.jar" com.example.MainClass
classpath:application.yml
리소스를 로드했을 때, A.jar
, B.jar
양쪽에 application.yml
이 존재하더라도 A.jar
의 application.yml
만이 효력을 가진다.
spring boot 에서도 classpath.idx
에 등장하는 순서대로 classpath를 설정하기 때문에, pom.xml에 먼저 등장하는 dependency project가 우선권을 가지게 된다.
소스코드 검증
classpath.idx
가 작성되는 코드를 살펴보기 위한 첫 진입점은 아래인 것으로 보인다.
Map<String, Library> write(AbstractJarWriter writer) throws IOException {
Map<String, Library> writtenLibraries = new LinkedHashMap<>();
for (Entry<String, Library> entry : this.libraries.entrySet()) {
String path = entry.getKey();
Library library = entry.getValue();
if (library.isIncluded()) {
String location = path.substring(0, path.lastIndexOf('/') + 1);
writer.writeNestedLibrary(location, library);
writtenLibraries.put(path, library);
}
}
writeClasspathIndexIfNecessary(writtenLibraries.keySet(), getLayout(), writer);
return writtenLibraries;
}
private void writeClasspathIndexIfNecessary(Collection<String> paths, Layout layout, AbstractJarWriter writer)
throws IOException {
if (layout.getClasspathIndexFileLocation() != null) {
List<String> names = paths.stream().map((path) -> "- \"" + path + "\"").toList();
writer.writeIndexFile(layout.getClasspathIndexFileLocation(), names); // arg0이 보통 "BOOT-INF/classpath.idx"임
}
}
여기서 LinkedHashMap
은 엔트리가 들어온 순서를 유지하기 때문에, 순서는 this.libraries
맵에 들어온 순서로 결정된다. 그리고 this.libraries
도 마찬가지로 LinkedHashMap
인 걸 따라가서 확인했다. 계속 따라가면(꽤 많이 따라가야 해서 자세한 내용은 생략) spring-boot 프로젝트를 벗어나서 MavenProject
라는 객체가 외부에서 주입되는 것을 볼 수 있는데 이것이 순서 정보를 가지고 있다는 것을 확인할 수 있다. SpringBoot는 Maven이 정해주는 순서를 그대로 따르는 것 같다.
Maven 프로젝트로 넘어가서 대략 어디서 MavenProject
를 만드는지 찾아보면 DefaultProjectBuilder
라는 builder가 만든다는 것을 볼 수 있는데, 이 과정에서 DefaultProjectBuilder::resolveDependencies
가 있는 것을 볼 수 있다. 여기서 호출하는 Util 메소드인 RepositoryUtils::toArtifacts
를 보면 아래와 같은데
public static void toArtifacts(
Collection<org.apache.maven.artifact.Artifact> artifacts,
Collection<? extends DependencyNode> nodes,
List<String> trail,
DependencyFilter filter) {
for (DependencyNode node : nodes) {
org.apache.maven.artifact.Artifact artifact = toArtifact(node.getDependency());
List<String> nodeTrail = new ArrayList<>(trail.size() + 1);
nodeTrail.addAll(trail);
nodeTrail.add(artifact.getId());
if (filter == null || filter.accept(node, Collections.emptyList())) {
artifact.setDependencyTrail(nodeTrail);
artifacts.add(artifact);
}
// Recursive
toArtifacts(artifacts, node.getChildren(), nodeTrail, filter);
}
}
전형적인 깊이 우선 탐색 순회코드인 것을 볼 수 있다.
'공부' 카테고리의 다른 글
Pinpoint 개발 환경 구축 (0) | 2022.01.29 |
---|---|
PCC, OCC에 대해서 (0) | 2021.09.02 |
Simple random search provides a competitive approach to reinforcement learning (0) | 2021.08.07 |
Sim-to-Real: Learning Agile Locomotion ForQuadruped Robots (0) | 2021.07.19 |