From ca261df534421c6c9993413726e6a6448708a6ab Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Fri, 17 Jul 2026 21:21:47 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20HexFormat=20=EC=9E=AC?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9D=84=20=ED=86=B5=ED=95=9C=20=EB=A9=94?= =?UTF-8?q?=EB=AA=A8=EB=A6=AC=20=ED=95=A0=EB=8B=B9=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/bolt.md | 3 +++ .../clearfolio/viewer/artifact/FileSystemArtifactStore.java | 4 +++- .../clearfolio/viewer/controller/ConversionController.java | 6 ++++-- .../controller/ConversionControllerMultipartLimitTest.java | 4 +++- .../service/DefaultDocumentValidationServiceTest.java | 4 +++- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 284d180..c3aa752 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -19,3 +19,6 @@ ## 2026-07-13 - 단일 패스 문자열 치환 최적화 (O(N) 단일 스캔 및 지연 할당) **Learning:** `String.replace()`를 여러 번 체이닝하여 호출하면, 문자열 치환이 발생하지 않는 경우에도 내부적으로 불필요한 스캔이 중복 발생하고, 치환 시마다 새로운 문자열 객체와 char 배열이 할당되어 메모리 낭비와 성능 저하(GC 압박)가 발생한다. **Action:** 여러 문자를 한 번에 치환해야 하는 경우, O(N) 단일 스캔을 통해 `charAt()`으로 문자를 확인하고, 치환이 실제로 필요한 경우에만 `StringBuilder`를 지연 할당(Lazy allocation)하여 성능을 최적화하고 불필요한 메모리 할당을 방지한다. +## 2026-07-17 - HexFormat 재사용 최적화 +**Learning:** `java.util.HexFormat.of()`를 매번 호출하는 것은 불필요한 객체 할당을 발생시킵니다. +**Action:** `private static final java.util.HexFormat HEX_FORMAT = java.util.HexFormat.of();` 로 선언하고 재사용해야 합니다. diff --git a/src/main/java/com/clearfolio/viewer/artifact/FileSystemArtifactStore.java b/src/main/java/com/clearfolio/viewer/artifact/FileSystemArtifactStore.java index fe69737..8770c4e 100644 --- a/src/main/java/com/clearfolio/viewer/artifact/FileSystemArtifactStore.java +++ b/src/main/java/com/clearfolio/viewer/artifact/FileSystemArtifactStore.java @@ -35,6 +35,8 @@ interface BytesReader { private static final String PDF_SUFFIX = ".pdf"; private static final String METADATA_SUFFIX = ".meta.properties"; + private static final java.util.HexFormat HEX_FORMAT = java.util.HexFormat.of(); + private final Path rootDir; private final BytesWriter bytesWriter; private final BytesReader bytesReader; @@ -134,7 +136,7 @@ private static String sha256Hex(final byte[] bytes) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] raw = digest.digest(bytes); - return java.util.HexFormat.of().formatHex(raw); + 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/controller/ConversionController.java b/src/main/java/com/clearfolio/viewer/controller/ConversionController.java index 604edb5..daecfc8 100644 --- a/src/main/java/com/clearfolio/viewer/controller/ConversionController.java +++ b/src/main/java/com/clearfolio/viewer/controller/ConversionController.java @@ -55,6 +55,8 @@ public class ConversionController { */ public static final String OPERATOR_ID_HEADER = "X-Clearfolio-Operator-Id"; + private static final java.util.HexFormat HEX_FORMAT = java.util.HexFormat.of(); + private final DocumentConversionService conversionService; private final TenantAccessService tenantAccessService; private final ArtifactLinkService artifactLinkService; @@ -294,9 +296,9 @@ private String calculateSha256(final byte[] data) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hash = digest.digest(data); - // Optimization: java.util.HexFormat.of().formatHex() is faster + // Optimization: HexFormat.formatHex() is faster // and allocates less memory than String.format. - return java.util.HexFormat.of().formatHex(hash); + return HEX_FORMAT.formatHex(hash); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("SHA-256 algorithm not available", e); } diff --git a/src/test/java/com/clearfolio/viewer/controller/ConversionControllerMultipartLimitTest.java b/src/test/java/com/clearfolio/viewer/controller/ConversionControllerMultipartLimitTest.java index ff77627..5241d90 100644 --- a/src/test/java/com/clearfolio/viewer/controller/ConversionControllerMultipartLimitTest.java +++ b/src/test/java/com/clearfolio/viewer/controller/ConversionControllerMultipartLimitTest.java @@ -51,6 +51,8 @@ ) class ConversionControllerMultipartLimitTest { + private static final HexFormat HEX_FORMAT = HexFormat.of(); + @SpringBootConfiguration @EnableAutoConfiguration @EnableConfigurationProperties(ConversionProperties.class) @@ -149,7 +151,7 @@ private String generateSignature(String approverId, String extension, String sec mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); String payload = approverId.length() + ":" + approverId + extension; byte[] hashed = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8)); - return HexFormat.of().formatHex(hashed); + return HEX_FORMAT.formatHex(hashed); } catch (Exception ex) { throw new RuntimeException(ex); } diff --git a/src/test/java/com/clearfolio/viewer/service/DefaultDocumentValidationServiceTest.java b/src/test/java/com/clearfolio/viewer/service/DefaultDocumentValidationServiceTest.java index 4f27bdc..3e8b8e8 100644 --- a/src/test/java/com/clearfolio/viewer/service/DefaultDocumentValidationServiceTest.java +++ b/src/test/java/com/clearfolio/viewer/service/DefaultDocumentValidationServiceTest.java @@ -26,6 +26,8 @@ class DefaultDocumentValidationServiceTest { + private static final HexFormat HEX_FORMAT = HexFormat.of(); + @Test void sanitizeFilenameReturnsNullWhenFilenameIsNull() throws Exception { ConversionProperties conversionProperties = new ConversionProperties(); @@ -89,7 +91,7 @@ private String generateSignature(String approverId, String extension, String sec mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); String payload = approverId.length() + ":" + approverId + extension; byte[] hashed = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8)); - return HexFormat.of().formatHex(hashed); + return HEX_FORMAT.formatHex(hashed); } catch (Exception ex) { throw new RuntimeException(ex); }