From d2d62469ee475961bfe0c7f193921b846aa8c028 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 9 Jul 2026 21:43:25 +0000 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9A=A1=20Bolt:=20=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=ED=96=A5=EC=83=81=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=9E=AC?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=20=EA=B0=80=EB=8A=A5=ED=95=9C=20HexFormat=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hex 변환을 위해 String.format("%02x", b)를 반복문 내에서 사용하거나 반복적으로 HexFormat.of() 인스턴스를 생성하는 방식은 불필요한 객체 생성을 초래합니다. 이를 해결하기 위해 DefaultDocumentConversionService, ArtifactLinkService, DefaultDocumentValidationService 클래스에 재사용 가능한 private static final java.util.HexFormat 상수(HEX_FORMAT)를 도입하고 변환 로직을 최적화했습니다. 이 최적화는 애플리케이션의 메모리 할당 및 가비지 컬렉션(GC) 부하를 줄여 성능을 향상시킵니다. 관련 내용은 .jules/bolt.md에 기록되었습니다. --- .jules/bolt.md | 3 +++ .../clearfolio/viewer/artifact/ArtifactLinkService.java | 9 ++++----- .../viewer/service/DefaultDocumentConversionService.java | 9 +++------ .../viewer/service/DefaultDocumentValidationService.java | 3 ++- 4 files changed, 12 insertions(+), 12 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 00000000..53973599 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2026-07-09 - Reuse HexFormat instance to reduce allocations and loops +**Learning:** Using `String.format("%02x", b)` in a loop or calling `HexFormat.of()` repeatedly for hex conversions creates unnecessary intermediate object allocations and reduces performance. +**Action:** Always use a single, reusable `private static final java.util.HexFormat HEX_FORMAT = java.util.HexFormat.of();` instance and its `formatHex` method for byte-to-hex string conversions. diff --git a/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java b/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java index 454b0448..7e2641de 100644 --- a/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java +++ b/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java @@ -9,6 +9,7 @@ import java.time.Instant; import java.util.Arrays; import java.util.Base64; +import java.util.HexFormat; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -38,6 +39,8 @@ @Service public class ArtifactLinkService { + private static final HexFormat HEX_FORMAT = HexFormat.of(); + /** * Query parameter used by PDF.js-compatible artifact URLs. */ @@ -423,11 +426,7 @@ private static String sha256Hex(byte[] bytes) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] raw = digest.digest(bytes); - StringBuilder hex = new StringBuilder(raw.length * 2); - for (byte b : raw) { - hex.append(String.format("%02x", b)); - } - return hex.toString(); + return HEX_FORMAT.formatHex(raw); } catch (GeneralSecurityException ex) { throw new IllegalStateException("SHA-256 digest unavailable", ex); } diff --git a/src/main/java/com/clearfolio/viewer/service/DefaultDocumentConversionService.java b/src/main/java/com/clearfolio/viewer/service/DefaultDocumentConversionService.java index b8787f35..faad73a2 100644 --- a/src/main/java/com/clearfolio/viewer/service/DefaultDocumentConversionService.java +++ b/src/main/java/com/clearfolio/viewer/service/DefaultDocumentConversionService.java @@ -7,6 +7,7 @@ import java.io.InputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.HexFormat; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -26,6 +27,7 @@ @Service public class DefaultDocumentConversionService implements DocumentConversionService { + private static final HexFormat HEX_FORMAT = HexFormat.of(); private final ConversionJobRepository repository; private final ConversionJobStateStore stateStore; private final DocumentValidationService validationService; @@ -160,12 +162,7 @@ private String contentHash(MultipartFile file) { } byte[] raw = digest.digest(); - StringBuilder hex = new StringBuilder(raw.length * 2); - for (byte b : raw) { - hex.append(String.format("%02x", b)); - } - - return hex.toString(); + return HEX_FORMAT.formatHex(raw); } catch (NoSuchAlgorithmException ex) { throw new IllegalStateException("SHA-256 digest unavailable", ex); } catch (IOException ex) { diff --git a/src/main/java/com/clearfolio/viewer/service/DefaultDocumentValidationService.java b/src/main/java/com/clearfolio/viewer/service/DefaultDocumentValidationService.java index b898f7ac..44ebf072 100644 --- a/src/main/java/com/clearfolio/viewer/service/DefaultDocumentValidationService.java +++ b/src/main/java/com/clearfolio/viewer/service/DefaultDocumentValidationService.java @@ -21,6 +21,7 @@ @Service public class DefaultDocumentValidationService implements DocumentValidationService { + private static final HexFormat HEX_FORMAT = HexFormat.of(); private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDocumentValidationService.class); private static final int FINGERPRINT_TRUNCATE_BYTES = 8; @@ -140,7 +141,7 @@ private String tokenFingerprint(String approvalToken) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hashed = digest.digest(approvalToken.getBytes(StandardCharsets.UTF_8)); - return HexFormat.of().formatHex(hashed, 0, FINGERPRINT_TRUNCATE_BYTES); + return HEX_FORMAT.formatHex(hashed, 0, FINGERPRINT_TRUNCATE_BYTES); } catch (NoSuchAlgorithmException ex) { throw new IllegalStateException("SHA-256 digest unavailable", ex); } From ec42116882080c65028c19b353e366885f049149 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 9 Jul 2026 23:34:52 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9A=A1=20Bolt:=20=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=ED=96=A5=EC=83=81=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=9E=AC?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=20=EA=B0=80=EB=8A=A5=ED=95=9C=20HexFormat=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20(=EC=88=98=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hex 변환을 위해 String.format("%02x", b)를 반복문 내에서 사용하거나 반복적으로 HexFormat.of() 인스턴스를 생성하는 방식은 불필요한 객체 생성을 초래합니다. 이를 해결하기 위해 DefaultDocumentConversionService, ArtifactLinkService, DefaultDocumentValidationService 클래스에 재사용 가능한 private static final java.util.HexFormat 상수(HEX_FORMAT)를 도입하고 변환 로직을 최적화했습니다. 이 최적화는 애플리케이션의 메모리 할당 및 가비지 컬렉션(GC) 부하를 줄여 성능을 향상시킵니다. 관련 내용은 .jules/bolt.md에 기록되었습니다. 이전 리뷰를 반영하여 기존의 PII 로깅 해싱 기능 변경을 롤백하고 본래 기능만을 유지하도록 수정했습니다. From 2bf90cae7630159987ed4a831eddb9b157086983 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 9 Jul 2026 23:55:39 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9A=A1=20Bolt:=20=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=ED=96=A5=EC=83=81=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=9E=AC?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=20=EA=B0=80=EB=8A=A5=ED=95=9C=20HexFormat=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20(=EC=88=98=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hex 변환을 위해 String.format("%02x", b)를 반복문 내에서 사용하거나 반복적으로 HexFormat.of() 인스턴스를 생성하는 방식은 불필요한 객체 생성을 초래합니다. 이를 해결하기 위해 DefaultDocumentConversionService, ArtifactLinkService, DefaultDocumentValidationService 클래스에 재사용 가능한 private static final java.util.HexFormat 상수(HEX_FORMAT)를 도입하고 변환 로직을 최적화했습니다. 이 최적화는 애플리케이션의 메모리 할당 및 가비지 컬렉션(GC) 부하를 줄여 성능을 향상시킵니다. 관련 내용은 .jules/bolt.md에 기록되었습니다. 이전 리뷰를 반영하여 기존의 PII 로깅 해싱 기능 변경을 롤백하고 본래 기능만을 유지하도록 수정했습니다. 또한, CI 빌드 실패의 원인이었던 취약한 `jackson-databind` 버전을 보안 권고에 맞게 2.18.4 버전으로 업데이트하여 CI trivy-fs 에러를 해결했습니다. --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 6b7e40df..7cca5a4a 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,7 @@ com.fasterxml.jackson.core jackson-databind + 2.18.4 From 1a72a8c3ccab7d860265b45770a56fa213865e50 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Fri, 10 Jul 2026 00:28:50 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9A=A1=20Bolt:=20=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=ED=96=A5=EC=83=81=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=9E=AC?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=20=EA=B0=80=EB=8A=A5=ED=95=9C=20HexFormat=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20(=EC=88=98=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hex 변환을 위해 String.format("%02x", b)를 반복문 내에서 사용하거나 반복적으로 HexFormat.of() 인스턴스를 생성하는 방식은 불필요한 객체 생성을 초래합니다. 이를 해결하기 위해 DefaultDocumentConversionService, ArtifactLinkService, DefaultDocumentValidationService 클래스에 재사용 가능한 private static final java.util.HexFormat 상수(HEX_FORMAT)를 도입하고 변환 로직을 최적화했습니다. 최적화된 코드에 주석을 추가하였습니다. 이 최적화는 애플리케이션의 메모리 할당 및 가비지 컬렉션(GC) 부하를 줄여 성능을 향상시킵니다. 관련 내용은 .jules/bolt.md에 기록되었습니다. 이전 리뷰를 반영하여 기존의 PII 로깅 해싱 기능 변경을 롤백하고 본래 기능만을 유지하도록 수정했습니다. 또한, CI 빌드 실패의 원인이었던 취약한 jackson 의존성을 해결하기 위해 `jackson-bom`을 2.18.4 버전으로 `dependencyManagement`에 명시하여 CI trivy-fs 에러를 해결했습니다. --- pom.xml | 14 +++++++++++++- .../viewer/artifact/ArtifactLinkService.java | 1 + .../service/DefaultDocumentConversionService.java | 1 + .../service/DefaultDocumentValidationService.java | 1 + 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7cca5a4a..ef6b6205 100644 --- a/pom.xml +++ b/pom.xml @@ -24,8 +24,21 @@ 3.0.3 4.10.38 + 2.18.4 + + + + com.fasterxml.jackson + jackson-bom + ${jackson.version} + pom + import + + + + org.springframework.boot @@ -58,7 +71,6 @@ com.fasterxml.jackson.core jackson-databind - 2.18.4 diff --git a/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java b/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java index 7e2641de..dcff2802 100644 --- a/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java +++ b/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java @@ -426,6 +426,7 @@ private static String sha256Hex(byte[] bytes) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] raw = digest.digest(bytes); + // Reused HexFormat for performance return HEX_FORMAT.formatHex(raw); } catch (GeneralSecurityException ex) { throw new IllegalStateException("SHA-256 digest unavailable", ex); diff --git a/src/main/java/com/clearfolio/viewer/service/DefaultDocumentConversionService.java b/src/main/java/com/clearfolio/viewer/service/DefaultDocumentConversionService.java index faad73a2..df1407cc 100644 --- a/src/main/java/com/clearfolio/viewer/service/DefaultDocumentConversionService.java +++ b/src/main/java/com/clearfolio/viewer/service/DefaultDocumentConversionService.java @@ -162,6 +162,7 @@ private String contentHash(MultipartFile file) { } byte[] raw = digest.digest(); + // Reused HexFormat for performance return HEX_FORMAT.formatHex(raw); } catch (NoSuchAlgorithmException ex) { throw new IllegalStateException("SHA-256 digest unavailable", ex); diff --git a/src/main/java/com/clearfolio/viewer/service/DefaultDocumentValidationService.java b/src/main/java/com/clearfolio/viewer/service/DefaultDocumentValidationService.java index 44ebf072..26553f3f 100644 --- a/src/main/java/com/clearfolio/viewer/service/DefaultDocumentValidationService.java +++ b/src/main/java/com/clearfolio/viewer/service/DefaultDocumentValidationService.java @@ -141,6 +141,7 @@ private String tokenFingerprint(String approvalToken) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hashed = digest.digest(approvalToken.getBytes(StandardCharsets.UTF_8)); + // Reused HexFormat for performance return HEX_FORMAT.formatHex(hashed, 0, FINGERPRINT_TRUNCATE_BYTES); } catch (NoSuchAlgorithmException ex) { throw new IllegalStateException("SHA-256 digest unavailable", ex);