<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>김용환 블로그</title>
    <link>https://yonghwankim-dev.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 19 Jun 2026 19:00:48 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>김용환</managingEditor>
    <image>
      <title>김용환 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/4727559/attach/5d51d80a235a4b608f9598c706857c8b</url>
      <link>https://yonghwankim-dev.tistory.com</link>
    </image>
    <item>
      <title>배포 환경, 클라이언트 및 서버가 크로스 도메인 상태에서 XSRF-TOKEN 쿠키 값 읽기 에러 문제 해결</title>
      <link>https://yonghwankim-dev.tistory.com/637</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 개인 프로젝트 invest72에서 웹서버에 있는 자바스크립트가 웹 애플리케이션 서버가 발급한 XSRF-TOKEN 쿠키 값을 읽지 못하는 에러를 해결하는 경험을 소개합니다. 문제 해결 경험에서는 문제가 발생한 배경, 문제 원인, 해결 방법에 대해서 소개합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 배포중인 웹서버와 웹 애플리케이션 상태는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹서버(React) : `&lt;a href=&quot;https://invest72.web.app&quot;&gt;https://invest72.web.app&lt;/a&gt;`&lt;/li&gt;
&lt;li&gt;웹 애플리케이션 서버(Spring) : `&lt;a href=&quot;https://invest72-api.duckdns.org&quot;&gt;https://invest72-api.duckdns.org&lt;/a&gt;`&lt;/li&gt;
&lt;li&gt;CSRF 공격을 막기 위해서 웹 애플리케이션 서버에서 CSRF 토큰을 발급합니다. 이 토큰은 XSRF-TOKEN 쿠키에 값을 담아서 응답합니다.&lt;/li&gt;
&lt;li&gt;XSRF-TOKEN 쿠키의 속성은 `HttpOnly=false`, `Secure`, `SameSite=None`, `domain=invest72-api.duckdns.org`, `Path=/` 입니다.&lt;/li&gt;
&lt;li&gt;클라이언트에서 HTTP 요청시 XSRF-TOKEN 쿠키 값을 읽어서 X-XSRF-TOKEN 헤더에 담아 전송하는 것으로 구현되어 있습니다.&lt;/li&gt;
&lt;li&gt;클라이언트가 HTTP 요청시 Axios 라이브러리를 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 발생한 배경은 클라이언트(웹서버)가 웹 애플리케이션 서버에 로그인을 요청을 수행하고 정상적으로 쿠키 방식으로 세션 ID와 XSRF-TOKEN 쿠키를 저장할 수 있었습니다. 그러나 GET 메서드를 포함한 POST, PUT과 같은 HTTP 요청시 X-XSRF-TOKEN 헤더에 XSRF-TOKEN 쿠키의 값을 담지 않고 전송하고 있습니다. 결국 CSRF 토큰 값을 검증하지 못하고 403 Forbidden 응답을 받게 됩니다. 그림으로 표현하면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;984&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OHC8J/dJMcaiv0EkV/85uTvHe1g77ngtKA6ZvrRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OHC8J/dJMcaiv0EkV/85uTvHe1g77ngtKA6ZvrRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OHC8J/dJMcaiv0EkV/85uTvHe1g77ngtKA6ZvrRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOHC8J%2FdJMcaiv0EkV%2F85uTvHe1g77ngtKA6ZvrRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;510&quot; height=&quot;629&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;984&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 디버깅 결과 `document.cookie` 호출시 빈 공백이 출력된 것을 확인하였습니다. 그러나 쿠키 저장소를 보면 정상적으로 JSESSIONID와 XSRF-TOKEN 쿠키가 저장된 것을 확인할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제가 발생한 원인은 배포환경에서는 자바스크립트가 실행되는 페이지의 도메인(invest72.web.app)과 발급 받은 쿠키의 도메인(invest72-api.duckdns.org)이 일치하지 않으면 쿠키값에 접근하지 못하기 때문입니다.&lt;/b&gt;&amp;nbsp;해당 문제 원인을 파악하는데 시간이 걸린 이유는 `httpOnly=false` 옵션을 설정하면 크로스 도메인 상태에서도 쿠키값을 읽을 수 있다고 착각하였습니다. 로컬 개발 환경에서는 웹 서버와 웹 애플리케이션 서버 모두 도메인이 `localhost`였기 때문에 정상적으로 X-XSRF-TOKEN 헤더에 값을 전달할 수 있었고 배포 환경에서는 데이터베이스의 데이터가 이미 존재했기 때문에 별도에 금융 상품 수정 테스트를 하지 않아서, 로컬 개발 환경에서 X-XSRF-TOKEN 헤더에 값을 전달 성공한 것을 배포 환경에서 성공했다고 혼동하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존까지 알고 있었던 생각은 쿠키에 `httpOnly=false` 속성을 설정하면 크로스 도메인 상태여도 쿠키의 값을 읽을 수 있다고 생각하였습니다. 이 생각이 맞는지 확인하기 위해서 MDN 문서 등을 탐색하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MDN 문서의 Cookie 관련된 설명을 보면 다음과 같이 설명되어 있습니다. 다음 설명을 보면 쿠키의 도메인이 자바스크립트의 출처(origin)의 도메인과 일치해야 한다고 설명하고 있습니다. 그리고 외부 도메인 쿠키에 접근하는 것은 무시된다고 설명하고 있습니다. 다음 설명을 통해서 크로스 도메인 상태에서 자바스크립트가 다른 도메인의 쿠키 값을 읽는 것은 불가능하다는 것을 알게 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1432&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dKRdIJ/dJMcagrrW17/Ho3CT6kZQnzevdR2ckF5gK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dKRdIJ/dJMcagrrW17/Ho3CT6kZQnzevdR2ckF5gK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dKRdIJ/dJMcagrrW17/Ho3CT6kZQnzevdR2ckF5gK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdKRdIJ%2FdJMcagrrW17%2FHo3CT6kZQnzevdR2ckF5gK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1432&quot; height=&quot;210&quot; data-origin-width=&quot;1432&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 도메인 불일치 문제를 해결하기 위해서 `invest72.site` 라는 도메인을 가비아 플랫폼을 통해서 구매하였고, 웹서버는 `www.invest72.site`, 웹 애플리케이션은 `api.invest72.site`라는 서브 도메인으로 설정하여 배포하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 서브 도메인 기반으로 변경되었기 때문에 XSRF-TOKEN 쿠키의 속성중 `SameSite=None` 속성을 `SameSite=Lax` 속성으로 변경하였습니다. 문제를 해결하고 실행 과정을 그림으로 표현하면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1770&quot; data-origin-height=&quot;974&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceXRah/dJMcaaY4dZ9/IenJkiuBzikY5YY9IWPl1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceXRah/dJMcaaY4dZ9/IenJkiuBzikY5YY9IWPl1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceXRah/dJMcaaY4dZ9/IenJkiuBzikY5YY9IWPl1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceXRah%2FdJMcaaY4dZ9%2FIenJkiuBzikY5YY9IWPl1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1770&quot; height=&quot;974&quot; data-origin-width=&quot;1770&quot; data-origin-height=&quot;974&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배포된 웹서버와 웹 애플리케이션 서버가 도메인이 서로 다른 크로스-도메인 상태에서는 자바스크립트가 다른 도메인을 가진 쿠키에 접근이 불가능합니다.&lt;/li&gt;
&lt;li&gt;`httpOnly=false` 속성은 도메인(서브 도메인 포함)이 서로 매칭되는 상태에서 쿠키의 값을 읽을 수 있는 것이지 도메인이 다르다면 쿠키값에 접근할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;References&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>문제해결</category>
      <author>김용환</author>
      <guid isPermaLink="true">https://yonghwankim-dev.tistory.com/637</guid>
      <comments>https://yonghwankim-dev.tistory.com/637#entry637comment</comments>
      <pubDate>Wed, 1 Apr 2026 16:02:52 +0900</pubDate>
    </item>
    <item>
      <title>자바 인터페이스 메서드의 리턴타입 리팩토링</title>
      <link>https://yonghwankim-dev.tistory.com/636</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 인터페이스 메서드가 정의되어 있고, 몇개의 구현체 클래스가 이미 구현된 상태이고 클라이언트 코드에서 이미 호출되어 있는 상황입니다. 하지만 요구사항의 변경으로 인하여 특정 인터페이스의 메서드 리턴타입을 변경해야 하는 경우에 리팩토링하는 방법에 대해서 소개합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 소스코드에 대해서는 현재 개인 프로젝트인 invest72 프로젝트의 소스 코드를 활용합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;현재 인터페이스 메서드 상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;투자 금액을 의미하는 InvestmentAmount 인터페이스는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 상황에서 요구사항의 변경으로 getAmount() 메서드의 리턴타입을 &lt;code&gt;BigDecimal&lt;/code&gt;에서 래퍼 클래스인 &lt;code&gt;Money&lt;/code&gt; 클래스 타입으로 변경해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface InvestmentAmount {

    Money calAnnualInterest(InterestRate interestRate);

    BigDecimal calMonthlyInterest(InterestRate interestRate);

    BigDecimal getAmount();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 InvestmentAmount 구현체 클래스 상황&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FixedDepositAmount&lt;/li&gt;
&lt;li&gt;MonthlyInstallmentInvestmentAmount&lt;/li&gt;
&lt;li&gt;YearlyInstallmentInvestmentAmount&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 InvestmentAmount 객체를 이용하여 getAmount 메서드를 호출하는 클라이언트 코드 상황. 현재 28개의 클라이언트에서 객체를 이용하여 getAmount 메서드를 호출하고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;982&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PEgEI/dJMcagrcgP6/4c0kBnDutAvjPw3dD9J060/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PEgEI/dJMcagrcgP6/4c0kBnDutAvjPw3dD9J060/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PEgEI/dJMcagrcgP6/4c0kBnDutAvjPw3dD9J060/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPEgEI%2FdJMcagrcgP6%2F4c0kBnDutAvjPw3dD9J060%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;982&quot; height=&quot;220&quot; data-origin-width=&quot;982&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 getAmount 메서드의 리턴 타입을 Money로 변경한다면 다음과 같은 문제점이 발생할 것입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구현체 클래스의 재정의 메서드에서 컴파일 타임 에러가 발생하여 리턴 타입을 Money로 변경해야 함&lt;/li&gt;
&lt;li&gt;클라이언트 코드의 getAmount() 메서드의 리턴 타입이 달라져서 클라이언트 코드를 28곳을 각각 수정해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리팩토링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 컴파일 타임 에러 및 각각 수정을 하는데 많은 시간을 소비하는 것을 피하기 위하여 리팩토링 기술을 사용하여 메서드의 리턴 타입을 변경하고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;default 메서드 추가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선은 리턴 타입이 Money인 새로운 기본 메서드를 인터페이스에 정의합니다. 자바 문법상 메서드 리턴 타입이 달라도 파라미터 구조가 동일하고, 메서드 이름이 동일하면 컴파일 에러가 발생합니다. 따라서 임시적으로 접미사에 Money를 붙여서 기존 메서드와 다르게 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;default 메서드로 정의하였기 때문에 구현체 클래스들에서는 컴파일 에러가 발생하지 않습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1773377778023&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface InvestmentAmount {
    // ...

	BigDecimal getAmount();

	default Money getAmountMoney() {
		throw new UnsupportedOperationException(&quot;todo - 금액을 Money로 반환하는 메서드 구현 필요&quot;);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;구현체 클래스에서 기본 메서드 재정의&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;기본 메서드를 인터페이스에 정의하였다면 해당 인터페이스를 구현하는 구현체 클래스에서 getAmountMoney 메서드를 재정의합니다. 다른 구현체 클래스에서도 다음과 같이 비슷하게 getAmountMoney 메서드를 재정의합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773378105752&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package co.invest72.investment.domain.amount;

import java.math.BigDecimal;

import co.invest72.investment.domain.InterestRate;
import co.invest72.investment.domain.LumpSumInvestmentAmount;
import co.invest72.money.domain.Money;

public class FixedDepositAmount implements LumpSumInvestmentAmount {

	private final Money amount;

	public FixedDepositAmount(BigDecimal amount, String currency) {
		this(Money.of(amount, currency));
	}
    
    // ...
    
	@Override
	public BigDecimal getAmount() {
		return getDepositAmount().getValue();
	}
    
	@Override
	public Money getAmountMoney() {
		return amount;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;default 메서드 해제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현체 클래스에 재정의를 하였다면 기본 메서드로 정의했던 getAmountMoney 메서드의 default 키워드 및 구현 내용을 제거합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1773378255136&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface InvestmentAmount {
	// ...
	BigDecimal getAmount();

	Money getAmountMoney();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;제거하고자 하는 기존 메서드에 default 메서드 적용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 메서드인 getAmount 메서드에 default 키워드를 적용하여 기본 메서드로 정의합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1773378326934&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;default BigDecimal getAmount() {
    return getAmountMoney().getValue();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;구현체 클래스의 기존 메서드 제거&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현체 클래스들에서 기존 메서드인 getAmount 메서드를 제거합니다. getAmount 메서드는 default 메서드인 상태이기 때문에 제거하여도 컴파일 에러가 발생하지 않습니다. 이것을 모든 구현체 클래스에서 반복합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1773380344908&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class FixedDepositAmount implements LumpSumInvestmentAmount {

	private final Money amount;

	public FixedDepositAmount(BigDecimal amount, String currency) {
		this(Money.of(amount, currency));
	}

	// ...

	// 코드 제거
	//public BigDecimal getAmount() {
	//	return getDepositAmount().getValue();
	//}

	@Override
	public Money getAmountMoney() {
		return amount;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인라인 메서드 적용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 getAmount 메서드를 호출하는 클라이언트 코드는 다음과 같습니다. 메서드를 호출하여 roundToWholeAmount.apply(BigDecimal) 함수에 값을 전달하고 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1773378392134&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public BigDecimal getPrincipal(int month) {
    return roundToWholeAmount.apply(investmentAmount.getAmount());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 위의 코드를 다음과 같이 변경해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1773378467432&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public BigDecimal getPrincipal(int month) {
    return roundToWholeAmount.apply(investmentAmount.getAmountMoney().getValue());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 위와 같이 getAmount() 메서드를 호출하는 클라이언트 코드는 28곳이나 됩니다. 위와 같은 수정을 일일히 하는 것은 매우 비용이 듭니다. 따라서 일괄적으로 적용하기 위해서 &lt;b&gt;인라인 메서드&lt;/b&gt;를 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스의 getAmount 메서드로 이동한 다음에 인라인 메서드를 수행합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;getAmount 메서드에 커서 올리기&lt;/li&gt;
&lt;li&gt;Refactor -&amp;gt; Inline Method 메뉴 선택&lt;/li&gt;
&lt;li&gt;Refactor 클릭&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZyB10/dJMcacvBabO/KOwYFu6iyd6bPcpfrKLrk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZyB10/dJMcacvBabO/KOwYFu6iyd6bPcpfrKLrk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZyB10/dJMcacvBabO/KOwYFu6iyd6bPcpfrKLrk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZyB10%2FdJMcacvBabO%2FKOwYFu6iyd6bPcpfrKLrk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;956&quot; height=&quot;616&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;616&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실행 결과 확인&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getAmountMoney 메서드를 선택하고 사용처를 검색해봅니다. 실행 결과를 보면 정상적으로 getAmountMoney 메서드를 호출하고 BigDecimal 매개변수에 전달하기 위해서 추가적인 getValue()를 호출하는 방식으로 성공적으로 변경되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 BigDecimal 매개변수를 받는 roundToWholeAmount와 같은 함수들도 일괄적으로 변경되면 좋겠지만 이 부분은 별도의 리팩토링 작업을 수행해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2376&quot; data-origin-height=&quot;760&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kFFtJ/dJMcabpVhM8/NO75BmKhsQAvPeop5RA1hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kFFtJ/dJMcabpVhM8/NO75BmKhsQAvPeop5RA1hk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kFFtJ/dJMcabpVhM8/NO75BmKhsQAvPeop5RA1hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkFFtJ%2FdJMcabpVhM8%2FNO75BmKhsQAvPeop5RA1hk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2376&quot; height=&quot;760&quot; data-origin-width=&quot;2376&quot; data-origin-height=&quot;760&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 및 테스트 결과 또한 컴파일 에러 없이 테스트를 성공적으로 수행되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1978&quot; data-origin-height=&quot;78&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lv0DQ/dJMcadnF0RK/KjeL9pR3e4jdD4ZwSaSy7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lv0DQ/dJMcadnF0RK/KjeL9pR3e4jdD4ZwSaSy7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lv0DQ/dJMcadnF0RK/KjeL9pR3e4jdD4ZwSaSy7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flv0DQ%2FdJMcadnF0RK%2FKjeL9pR3e4jdD4ZwSaSy7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1978&quot; height=&quot;78&quot; data-origin-width=&quot;1978&quot; data-origin-height=&quot;78&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;새로운 메서드 이름을 기존 메서드 이름으로 변경&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링 rename 기능을 이용하여 getAmountMoney 메서드 이름을 기존 메서드 이름인 getAmount 이름으로 변경합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1773379270846&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface InvestmentAmount {

	Money calAnnualInterest(InterestRate interestRate);

	BigDecimal calMonthlyInterest(InterestRate interestRate);

	Money getAmount();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스의 메서드 리턴 타입 변경 과정&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;변경하고자 하는 새로운 메서드 A 추가 (default 메서드 적용)&lt;/li&gt;
&lt;li&gt;구현체 클래스에서 새로운 default 메서드 A 구현&lt;/li&gt;
&lt;li&gt;메서드 A의 default 키워드 제거&lt;/li&gt;
&lt;li&gt;기존 메서드에 default 키워드 추가&lt;/li&gt;
&lt;li&gt;default 기존 메서드 구현 내용에 새로운 메서드 A를 호출하는 코드로 구현&lt;/li&gt;
&lt;li&gt;구현체 클래스에서 기존 메서드 구현을 제거&lt;/li&gt;
&lt;li&gt;기존 메서드를 대상으로 인라인 메서드(Inline Method) 리팩토링 기능을 실행&lt;/li&gt;
&lt;li&gt;Rename 리팩토링 기능을 사용하여 새로운 메서드의 이름을 기존 메서드로 동일하게 변경(getAmountMoney -&amp;gt; getAmount)&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>JAVA</category>
      <author>김용환</author>
      <guid isPermaLink="true">https://yonghwankim-dev.tistory.com/636</guid>
      <comments>https://yonghwankim-dev.tistory.com/636#entry636comment</comments>
      <pubDate>Fri, 13 Mar 2026 14:22:15 +0900</pubDate>
    </item>
    <item>
      <title>PostgreSQL DECIMAL vs NUMERIC</title>
      <link>https://yonghwankim-dev.tistory.com/635</link>
      <description>&lt;h3&gt;개요&lt;/h3&gt;
&lt;p&gt;PosgreSQL 컬럼 데이터 타입중에서 고정 소수점 타입에 해당하는 Numeric과 Decimal의 차이점을 분석합니다.&lt;/p&gt;
&lt;h3&gt;Storage and syntax&lt;/h3&gt;
&lt;p&gt;Postgres는 &lt;code&gt;DECIMAL&lt;/code&gt; 또는 &lt;code&gt;NUMERIC&lt;/code&gt; 데이터 타입을 제공하고 있습니다. 해당 데이터 타입은 사용자 정의 정밀도(precision)을 제공하여 특정 자릿수까지 숫자를 정확하게 표현할 수 있습니다.&lt;/p&gt;
&lt;p&gt;Decimal 컬럼 정의 문법&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DECIMAL(precision, scale)&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;가변 길이&lt;/li&gt;
&lt;li&gt;정수 부분 최대 131072자리, 소수점 최대 16383자리까지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NUMERIC(precision, scale)&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;가변 길이&lt;/li&gt;
&lt;li&gt;정수 부분 최대 131072자리, 소수점 최대 16383자리까지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;precision&lt;/strong&gt;&lt;br&gt;정밀도(precision)은 소수점을 기준으로 왼쪽과 오른쪽을 모두 포함한 숫자의 총개수입니다. 예를 들어 precision이 10이라면 소수점 숫자를 포함한 숫자의 개수가 총 10개여야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;scale&lt;/strong&gt;&lt;br&gt;스케일(scale)는 소수점의 숫자 개수입니다. 예를 들어 scale이 2라면 0.00과 같이 소수점의 개수가 2개로 제한됩니다.&lt;/p&gt;
&lt;p&gt;정확도(precision)와 스케일(scale)을 설정하지 않고, 컬럼을 &lt;code&gt;NUMERIC&lt;/code&gt;으로 선언하면 시스템이 허용하는 최대 자릿수(구현 한계치)까지 값을 그대로 저장합니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;NUMERIC&lt;/code&gt;의 예시&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT 1234.56::NUMERIC(10, 4) AS num_A, 
1234.56::NUMERIC(10, 1) AS num_B, 
1234.56789::NUMERIC AS num_C;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;결과&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;num_a | num_b | num_c 
----------+--------+------------ 
1234.5600 | 1234.6 | 1234.56789&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;부동 소수점(floating-point)과의 차이점&lt;/h3&gt;
&lt;p&gt;부동 소수점(&lt;code&gt;REAL&lt;/code&gt;, &lt;code&gt;DOUBLE PRECISION&lt;/code&gt;)과 고정 소수점(&lt;code&gt;NUMERIC&lt;/code&gt;, &lt;code&gt;DECIMAL&lt;/code&gt;)의 차이점은 중요합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;정밀도(Precision)&lt;/strong&gt; : &lt;code&gt;DECIMAL&lt;/code&gt; / &lt;code&gt;NUMERIC&lt;/code&gt; 타입은 정확한 정밀도를 유지합니다. 반면에 부동 소수점 타입은 근사적(approximate)이며, 반올림 오류를 일으킬수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;성능(Performance)&lt;/strong&gt; : &lt;code&gt;DECIMAL&lt;/code&gt; / &lt;code&gt;NUMERIC&lt;/code&gt; 연산은 정밀도와 계산의 복잡성때문에 일반적으로 부동 소수점 타입보다 느립니다. &lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;DECIMAL 데이터 타입은 무엇인가?&lt;/h3&gt;
&lt;p&gt;PostgreSQL에서 &lt;code&gt;DECIMAL&lt;/code&gt; 데이터 타입은 고정된 정밀도와 스케일로된 숫자를 저장하는데 사용됩니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DECIMAL&lt;/code&gt; 데이터 타입 문법&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;column_name DECIMAL(precision, scale)&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;precision : 소수점을 포함한 전체 숫자 개수&lt;/li&gt;
&lt;li&gt;scale : 소수점 개수&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;NUMERIC 데이터 타입은 무엇인가?&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;NUMERIC&lt;/code&gt; 데이터타입은 기능적으로는 &lt;code&gt;DECIMAL&lt;/code&gt; 타입과 동일합니다. 고정된 정밀도와 스케일로된 숫자를 저장하는데 사용됩니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;NUMERIC&lt;/code&gt; 데이터 타입 문법&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;column_name NUMERIC(precision, scale)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;DECIMAL vs NUMERIC Datatype in PostgreSQL&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;기능/관점&lt;/th&gt;
&lt;th&gt;DECIMAL&lt;/th&gt;
&lt;th&gt;NUMERIC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;기능&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NUMERIC&lt;/code&gt;과 기능적으로 동일&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DECIMAL&lt;/code&gt;과 기능적으로 동일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;정밀도(precision)와 스케일(scale)&lt;/td&gt;
&lt;td&gt;숫자에 정밀도와 스케일 설정 허용&lt;/td&gt;
&lt;td&gt;숫자에 정밀도와 스케일 설정 허용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;성능&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DECIMAL&lt;/code&gt;과 &lt;code&gt;NUMERIC&lt;/code&gt;의 성능 차이 없음&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DECIMAL&lt;/code&gt;과 &lt;code&gt;NUMERIC&lt;/code&gt;의 성능 차이 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;차이점&lt;/td&gt;
&lt;td&gt;PostgreSQL에서 &lt;code&gt;DECIMAL&lt;/code&gt;은 사실상 &lt;code&gt;NUMERIC&lt;/code&gt;의 별칭입니다.&lt;/td&gt;
&lt;td&gt;PostgreSQL에서 &lt;code&gt;NUMERIC&lt;/code&gt;은 사실상 &lt;code&gt;DECIMAL&lt;/code&gt;의 별칭입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;결론&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;DECIMAL&lt;/code&gt;과 &lt;code&gt;NUMERIC&lt;/code&gt;은 기능적으로 동일하고, &lt;code&gt;DECIMAL&lt;/code&gt;은 사실상 &lt;code&gt;NUMERIC&lt;/code&gt;의 별칭입니다. 두 타입간에는 성능 차이도 없습니다. 그래서 두 타입 중 하나를 선택하는 것은 주로 조직 내에서 선호도나 관습 차이입니다.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://neon.com/docs/data-types/decimal&quot;&gt;https://neon.com/docs/data-types/decimal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/postgresql/decimal-vs-numeric-datatype-in-postgresql/&quot;&gt;https://www.geeksforgeeks.org/postgresql/decimal-vs-numeric-datatype-in-postgresql/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>김용환</author>
      <guid isPermaLink="true">https://yonghwankim-dev.tistory.com/635</guid>
      <comments>https://yonghwankim-dev.tistory.com/635#entry635comment</comments>
      <pubDate>Thu, 12 Mar 2026 14:29:46 +0900</pubDate>
    </item>
    <item>
      <title>Spring AOP 로깅 메모리 누수 문제 해결</title>
      <link>https://yonghwankim-dev.tistory.com/634</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;배경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 배포되고 있는 Spring 서버를 대상으로 힙덤프를 이용한 MemoryAnalyzer 툴의 메모리 누수 의심 보고서가 다음과 같았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;812&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kXqx5/dJMcaiPz7cL/IKLoNhXs7o75YaklWXuJ61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kXqx5/dJMcaiPz7cL/IKLoNhXs7o75YaklWXuJ61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kXqx5/dJMcaiPz7cL/IKLoNhXs7o75YaklWXuJ61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkXqx5%2FdJMcaiPz7cL%2FIKLoNhXs7o75YaklWXuJ61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1794&quot; height=&quot;812&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;812&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세번째 메모리 누수 의심은 AspectJExpressionPointcut 인스턴스가 10.35% 메모리 점유하고 있습니다. 해당 인스턴스들은 대부분 `ConcurrentHashMap$Node[]` 배열에서 참조하고 있습니다. 그리고 이러한 배열 데이터는 DefaultListableBeanFactory에 의해서 참조되고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;1286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vlz7U/dJMcahb6ukk/0SBDGYk16CQ7HgWJfj58o0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vlz7U/dJMcahb6ukk/0SBDGYk16CQ7HgWJfj58o0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vlz7U/dJMcahb6ukk/0SBDGYk16CQ7HgWJfj58o0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvlz7U%2FdJMcahb6ukk%2F0SBDGYk16CQ7HgWJfj58o0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1290&quot; height=&quot;1286&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;1286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 누수가 발생한 원인은 fineAnts Spring 서버의 기능중에서 service 패키지에 존재하는 클래스의 메서드가 실행될때마다 AOP가 과도하게 로깅을 수행하기 때문이었습니다. 그중에서 가장 많이 발생한 것은 종목의 현재가를 5초 간격으로 갱신하는 스케줄러 부분에서 많이 발생하였습니다. 로깅 AOP의 코드는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1768886804337&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component  
@Aspect  
@Slf4j  
@Profile(&quot;!test&quot;)  
public class ServiceLogAspect {  
    private long startTime;  
  
    // service의 모든 메서드에 대해 적용  
    @Pointcut(&quot;execution(* co.fineants..service.*.*(..))&quot;)  
    public void pointCut() {  
  
    }  
  
    // 메서드 호출 전 로그 남기기  
    @Before(&quot;pointCut()&quot;)  
    public void logBefore(JoinPoint joinPoint) {  
       startTime = System.currentTimeMillis();  
       String methodName = ((MethodSignature)joinPoint.getSignature()).getMethod().getName();  
       String args = Arrays.toString(joinPoint.getArgs());  
       log.info(&quot;Entering Service: Method={} with Args={}&quot;, methodName, args);  
    }  
  
    // 메서드 호출 후 정상적으로 반환된 경우 로그 남기기  
    @AfterReturning(pointcut = &quot;pointCut()&quot;, returning = &quot;result&quot;)  
    public void logAfterReturning(JoinPoint joinPoint, Object result) {  
       String methodName = ((MethodSignature)joinPoint.getSignature()).getMethod().getName();  
       log.info(&quot;Exiting Service: Method={}, with Return={}&quot;, methodName, result);  
    }  
  
    // 완전히 종료된후 메서드 실행시간 측정하기  
    @After(&quot;pointCut()&quot;)  
    public void logAfter(JoinPoint joinPoint) {  
       long executionTime = System.currentTimeMillis() - startTime;  
       String methodName = ((MethodSignature)joinPoint.getSignature()).getMethod().getName();  
       log.info(&quot;Method={}, ExecutionTime={}ms&quot;, methodName, executionTime);  
    }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 로깅 내용은 실효성이 없고 메모리 누수 문제 또한 발생하였기 때문에 해당 클래스를 제거하기로 결정하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실행결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AOP 클래스를 제거한 다음에 배포하고 다시 메모리 누수 보고서를 실행한 결과는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과를 보면 AOP 관련된 메모리 누수가 제거된 것을 볼수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1762&quot; data-origin-height=&quot;888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rrtP9/dJMcaa48lxr/ORcwqKnCeh8QbPLxmUcOOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rrtP9/dJMcaa48lxr/ORcwqKnCeh8QbPLxmUcOOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rrtP9/dJMcaa48lxr/ORcwqKnCeh8QbPLxmUcOOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrrtP9%2FdJMcaa48lxr%2FORcwqKnCeh8QbPLxmUcOOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1762&quot; height=&quot;888&quot; data-origin-width=&quot;1762&quot; data-origin-height=&quot;888&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;References&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Github : &lt;a href=&quot;https://github.com/fine-ants/FineAnts-was&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/fine-ants/FineAnts-was&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>문제해결</category>
      <author>김용환</author>
      <guid isPermaLink="true">https://yonghwankim-dev.tistory.com/634</guid>
      <comments>https://yonghwankim-dev.tistory.com/634#entry634comment</comments>
      <pubDate>Tue, 20 Jan 2026 14:30:05 +0900</pubDate>
    </item>
    <item>
      <title>프로메테우스(Prometheus) 컨테이너 배포시 &amp;quot;data/queries.active&amp;quot; 파일 작성 실패 문제 해결</title>
      <link>https://yonghwankim-dev.tistory.com/633</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;배경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로메테우스 컨테이너를 클라우드에 배포하려고 했으나 다음과 같은 에러가 발생하여 계속 재시작하는 문제가 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2418&quot; data-origin-height=&quot;542&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9jobC/dJMcaiPz30c/YtkVISD8xAV9GRsT4KI8pK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9jobC/dJMcaiPz30c/YtkVISD8xAV9GRsT4KI8pK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9jobC/dJMcaiPz30c/YtkVISD8xAV9GRsT4KI8pK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9jobC%2FdJMcaiPz30c%2FYtkVISD8xAV9GRsT4KI8pK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2418&quot; height=&quot;542&quot; data-origin-width=&quot;2418&quot; data-origin-height=&quot;542&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 에러 결과를 보면 컨테이너의 `/prometheus/data/queries.active` 파일을 `./data` 디렉토리 아래에 작성하려고 했으나 `./data` 디렉토리가 존재하지 않아서 에러가 발생했다는 결과입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 docker-compose의 프로메테우스 서비스의 설정은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 설정 중에서 주목할 부분은 볼륨의 바인드 마운트 방식으로 설정된 부분입니다. 호스트 디렉토리의 `./prometheus/volume` 경로와 컨테이너의 `/promethues` 경로를 연결하도록 설정하였습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;prometheus:
  container_name: fineAnts_prometheus
  image: prom/prometheus:v2.48.0
  restart: always
  ports:
    - &quot;9090:9090&quot;
  command:
    - '--web.enable-lifecycle'
    - '--config.file=/etc/prometheus/prometheus.yml'
    - '--web.console.libraries=/etc/prometheus/console_libraries'
    - '--web.console.templates=/etc/prometheus/consoles'
  volumes:
    - ./prometheus/volume:/prometheus
  networks:
    - spring-net&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 에러가 발생한 원인은 &lt;b&gt;호스트 디렉토리의 `./prometheus/volume` 디렉토리 아래에 `data` 디렉토리가 존재하지 않기 때문&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호스트 디렉토리의 `./prometheus/volume` 디렉토리 경로 아래에 `data` 디렉토리를 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1768878364118&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mkdir -p $WORK_DIR/prometheus/volume/data&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과를 보면 `data` 디렉토리가 volume 디렉토리 아래에 정상적으로 생성된 것을 볼수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2118&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjtKep/dJMcadHwaTj/0oZQJt8VvyPYV0vZkccFMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjtKep/dJMcadHwaTj/0oZQJt8VvyPYV0vZkccFMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjtKep/dJMcadHwaTj/0oZQJt8VvyPYV0vZkccFMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjtKep%2FdJMcadHwaTj%2F0oZQJt8VvyPYV0vZkccFMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2118&quot; height=&quot;236&quot; data-origin-width=&quot;2118&quot; data-origin-height=&quot;236&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여기서 해결되는 것이 아닌 추가적인 권한 및 소유자를 다시 설정해주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 `data` 디렉토리의 소유자는 `fineants` 사용자로 설정되어 있습니다. 이렇게 설정되어 있으면 &lt;b&gt;컨테이너의 내부 사용자(nobody, 65534)와 호스트 파일 시스템(fineants 사용자)간의 권한 불일치로 인하여 에러가 발생&lt;/b&gt;합니다. 프로메테우스 공식 이미지 내부에서는 nobody(65534)라는 사용자가 미리 생성되어 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2398&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ncWjs/dJMcabbTF2l/mT3uoTXE78lVVxYR8Yra01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ncWjs/dJMcabbTF2l/mT3uoTXE78lVVxYR8Yra01/img.png&quot; data-alt=&quot;data 디렉토리 아래에 queries.active 파일을 작성시 권한 부족으로 에러가 발생한 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ncWjs/dJMcabbTF2l/mT3uoTXE78lVVxYR8Yra01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FncWjs%2FdJMcabbTF2l%2FmT3uoTXE78lVVxYR8Yra01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2398&quot; height=&quot;256&quot; data-origin-width=&quot;2398&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;data 디렉토리 아래에 queries.active 파일을 작성시 권한 부족으로 에러가 발생한 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 위와 같은 권한 문제를 해결하기 위해서 `data` 디렉토리의 소유자 권한을 `fineants`가 아닌 `nobody(65534)`로 설정해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1768878903496&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo chown -R 65534:65534 $WORK_DIR/prometheus/volume/data&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과를 보면 `data` 디렉토리의 소유자가 `nobody`로 변경된 것을 볼수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2388&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wWmdl/dJMcabJKTaY/n8LrOhMcX3rNV1A1INg1oK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wWmdl/dJMcabJKTaY/n8LrOhMcX3rNV1A1INg1oK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wWmdl/dJMcabJKTaY/n8LrOhMcX3rNV1A1INg1oK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwWmdl%2FdJMcabJKTaY%2Fn8LrOhMcX3rNV1A1INg1oK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2388&quot; height=&quot;292&quot; data-origin-width=&quot;2388&quot; data-origin-height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 실행 결과를 통해서 `data` 디렉토리의 권한을 보면 `775(rwxrwxr-x)`로 설정되어 있습니다. 해당 디렉토리 및 디렉토리 아래의 파일 권한을 `755(rwxr-xr-x)`로 변경합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1768879071928&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo chmod -R 755 $WORK_DIR/prometheus/volume/data&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과를 보면 정상적으로 권한이 변경된 것을 볼수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2418&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VgIeV/dJMcaaxiOF4/w9kEI6sKf3r0Hgfa8n6i81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VgIeV/dJMcaaxiOF4/w9kEI6sKf3r0Hgfa8n6i81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VgIeV/dJMcaaxiOF4/w9kEI6sKf3r0Hgfa8n6i81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVgIeV%2FdJMcaaxiOF4%2Fw9kEI6sKf3r0Hgfa8n6i81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2418&quot; height=&quot;236&quot; data-origin-width=&quot;2418&quot; data-origin-height=&quot;236&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;docker 컨테이너 실행시 user 옵션 명세적으로 설정하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로메테우스 컨테이너 실행시 `user` 옵션을 명세적으로 &quot;65534:65534&quot;로 설정합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;prometheus:
  container_name: fineAnts_prometheus
  image: prom/prometheus:v2.48.0
  restart: always
  user: &quot;65534:65534&quot;
  ports:
    - &quot;9090:9090&quot;
  command:
    - '--web.enable-lifecycle'
    - '--config.file=/etc/prometheus/prometheus.yml'
    - '--web.console.libraries=/etc/prometheus/console_libraries'
    - '--web.console.templates=/etc/prometheus/consoles'
  volumes:
    - ./prometheus/volume:/prometheus
  networks:
    - spring-net&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;배포 과정중에 프로메테우스 data 디렉토리 생성 및 권한 설정하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 프로젝트의 서버 배포는 Github Actions를 이용하여 수행됩니다. 배포 과정 중에서 프로메테우스 서비스의 설정 파일은 시크릿 정보이기 때문에 설정파일을 복사하기 전에 디렉토리 준비 단계에서 `data` 디렉토리 또한 미리 생성하고 권한을 설정하여 별도로 디렉토리 생성 및 권한을 수동적으로 하지 않도록 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 스텝을 보면 Compute Engine에 SSH 접속하여 `config` 및 `volume/data` 디렉토리를 직접 생성하고 파일 및 디렉토리 권한, 소유자를 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1768879275578&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;      - name: Prepare Prometheus Directory
        uses: appleboy/ssh-action@v0.1.7
        with:
          host: ${{ env.GCP_INSTANCE_IP }}
          username: ${{ env.GCP_SSH_USERNAME }}
          passphrase: ${{ env.GCP_SSH_PASSPHRASE }}
          key: ${{ env.GCP_SSH_PRIVATE_KEY }}
          script: |
            mkdir -p ${{ env.WORK_DIR }}/prometheus/config
            mkdir -p ${{ env.WORK_DIR }}/prometheus/volume/data
            sudo chown -R ${{ env.GCP_SSH_USERNAME }}:${{ env.GCP_SSH_USERNAME }} ${{ env.WORK_DIR }}/prometheus
            sudo chmod -R 755 ${{ env.WORK_DIR }}/prometheus
            sudo chown -R 65534:65534 ${{ env.WORK_DIR }}/prometheus/volume/data&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실행 결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker 또는 docker-compose 명령어를 이용하여 컨테이너를 실행시킨 다음에 다음 명령어를 실행하여 프로메테우스 컨테이너가 정상적으로 실행되고 있는지 확인합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1768879537440&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -I http://localhost:9090/-/healthy&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과를 보면 정상적으로 200 OK가 응답되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNvW55/dJMcadtXOlN/yCWphb5MVitawx0eZrjhOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNvW55/dJMcadtXOlN/yCWphb5MVitawx0eZrjhOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNvW55/dJMcadtXOlN/yCWphb5MVitawx0eZrjhOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNvW55%2FdJMcadtXOlN%2FyCWphb5MVitawx0eZrjhOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2032&quot; height=&quot;198&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;198&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;References&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@leejaeyoung/Prometheus-Grafana-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-UID-%ED%98%B8%EC%8A%A4%ED%8A%B8-%EA%B6%8C%ED%95%9C-%EB%AC%B8%EC%A0%9C&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@leejaeyoung/Prometheus-Grafana-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-UID-%ED%98%B8%EC%8A%A4%ED%8A%B8-%EA%B6%8C%ED%95%9C-%EB%AC%B8%EC%A0%9C&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>문제해결</category>
      <author>김용환</author>
      <guid isPermaLink="true">https://yonghwankim-dev.tistory.com/633</guid>
      <comments>https://yonghwankim-dev.tistory.com/633#entry633comment</comments>
      <pubDate>Tue, 20 Jan 2026 12:31:20 +0900</pubDate>
    </item>
    <item>
      <title>VisualVM Profiler 사용할 때 특정 Profile Class가 표시되지 않는 문제 해결</title>
      <link>https://yonghwankim-dev.tistory.com/632</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Troubleshooting Java 도서에서 서버의 특정 엔드포인트(/demo)에 대한 CPU 프로파일링을 실습하고 있었습니다. 해당 엔드포인트는 요청을 받으면 내부적으로 OpenFeign 라이브러리를 사용하여 &quot;httpbin.org&quot; 사이트에 요청을 보내고 일부러 5초 동안 응답 지연합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로파일링 결과는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2358&quot; data-origin-height=&quot;1382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uNKEu/dJMcahCXje1/D5vkD6tmLZEWEsfCqOa73K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uNKEu/dJMcahCXje1/D5vkD6tmLZEWEsfCqOa73K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uNKEu/dJMcahCXje1/D5vkD6tmLZEWEsfCqOa73K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuNKEu%2FdJMcahCXje1%2FD5vkD6tmLZEWEsfCqOa73K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2358&quot; height=&quot;1382&quot; data-origin-width=&quot;2358&quot; data-origin-height=&quot;1382&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 결과를 보면 DemoController.demo() 메서드에 대한 정보만 나올 뿐이지 OpenFeign 호출에 대한 상세한 정보는 프로파일링 되지 않고 있습니다. 제가 원하는 것은 OpenFeign에 대한 프로파일링 정보까지도 표시되는 것이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로파일러의 CPU settings에서 Profile classes 입력창에 OpenFeign 라이브러리 경로를 설정하지 않아서 프로파일링 되지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 실행중인 서버의 프로파일링을 중지하게 되면 오른족의 CPU Settings 탭의 Profile classes 입력창이 활성화되어 편집할 수 있게 됩니다. 편집이 가능하게 되면 Profile classes 창에 다음과 같이 OpenFeign 라이브러리 경로를 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2354&quot; data-origin-height=&quot;1302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfI5V3/dJMcajgqtLM/zCaorKz6kSisIK6o7fPSo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfI5V3/dJMcajgqtLM/zCaorKz6kSisIK6o7fPSo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfI5V3/dJMcajgqtLM/zCaorKz6kSisIK6o7fPSo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfI5V3%2FdJMcajgqtLM%2FzCaorKz6kSisIK6o7fPSo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2354&quot; height=&quot;1302&quot; data-origin-width=&quot;2354&quot; data-origin-height=&quot;1302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 설정하였으면 다시 CPU 버튼을 눌러서 프로파일링을 시작하고 엔드포인트를 요청한 다음에 모니터링해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과를 보면 정상적으로 OpenFeign 관련된 호출 정보가 표시된 것을 볼수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2360&quot; data-origin-height=&quot;1302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvL6XZ/dJMcafSGAdI/2RRdaZdy81D8XhkP7tiJK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvL6XZ/dJMcafSGAdI/2RRdaZdy81D8XhkP7tiJK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvL6XZ/dJMcafSGAdI/2RRdaZdy81D8XhkP7tiJK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvL6XZ%2FdJMcafSGAdI%2F2RRdaZdy81D8XhkP7tiJK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2360&quot; height=&quot;1302&quot; data-origin-width=&quot;2360&quot; data-origin-height=&quot;1302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;References&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/6619610/visualvm-not-showing-any-methods-called-for-cpu-performance-profiling&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/6619610/visualvm-not-showing-any-methods-called-for-cpu-performance-profiling&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>문제해결</category>
      <author>김용환</author>
      <guid isPermaLink="true">https://yonghwankim-dev.tistory.com/632</guid>
      <comments>https://yonghwankim-dev.tistory.com/632#entry632comment</comments>
      <pubDate>Mon, 15 Dec 2025 14:24:33 +0900</pubDate>
    </item>
    <item>
      <title>Github Action, act 로컬 테스트 실행시 SSH Private Key 저장 문제 해결</title>
      <link>https://yonghwankim-dev.tistory.com/631</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 Github Action의 워크플로우 파일을 로컬 개발 환경에서 act라는 오픈소스 프로세스를 실행하여 테스트하다가 SSH 접속이 안되는 문제를 해결하는 것을 다루고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/nektos/act&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/nektos/act&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1764912428771&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - nektos/act: Run your GitHub Actions locally  &quot; data-og-description=&quot;Run your GitHub Actions locally  . Contribute to nektos/act development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/nektos/act&quot; data-og-url=&quot;https://github.com/nektos/act&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/WKHDt/hyZPbzpTQX/9p9PM14c4BwaG6fLnZokm1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/nSnMt/hyZOIrfMUz/8cGyTnAK0jMvkkyfE3rhl1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/nektos/act&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/nektos/act&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/WKHDt/hyZPbzpTQX/9p9PM14c4BwaG6fLnZokm1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/nSnMt/hyZOIrfMUz/8cGyTnAK0jMvkkyfE3rhl1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - nektos/act: Run your GitHub Actions locally  &lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Run your GitHub Actions locally  . Contribute to nektos/act development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCP 클라우드 기반의 배포를 위한 워크플로우 파일을 작성하는 과정에서 Github Action 워크플로우를 로컬 개발 환경에서 테스트를 하기 위해서 &lt;b&gt;act&lt;/b&gt;라는 프로세스를 이용하여 테스트를 하고 있었습니다. 그중에서 문제가 되었던 것은 클라우드의 VM 인스턴스에 접속하기 위해서 &lt;b&gt;SSH 접속을 시도하는 과정&lt;/b&gt;에서 문제가 발생하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 발생했던 워크플로우의 스텝은 다음과 같습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;jobs:
  deploy:
    runs-on: ubuntu-22.04
    environment: gcp-production
    needs: build-image
    defaults:
      run:
        shell: bash

    steps:
      # ...
      # gcp ssh 연결 확인
      - name: Test SSH Connection
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ env.GCP_INSTANCE_IP }}
          username: ${{ env.GCP_SSH_USERNAME }}
          passphrase: ${{ env.GCP_SSH_PASSPHRASE }}
          key: ${{ env.GCP_SSH_PRIVATE_KEY }}
          port: 22
          script: |
            echo &quot;SSH connection successful!&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;act 명령어 실행&lt;/p&gt;
&lt;pre id=&quot;code_1764912194804&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;act -W .github/workflows/ci.cd.production.gcp.yml \
--container-architecture linux/amd64 \
--secret-file .secrets \
--job deploy&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.secrets 파일&lt;/p&gt;
&lt;pre id=&quot;code_1764912315466&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GIT_TOKEN={GIT_TOKEN_VALUE}
DOCKER_USERNAME={USERNAME}
DOCKER_PASSWORD={PASSWORD}
GCP_INSTANCE_IP={VM_INSTANCE_IP}
GCP_SSH_USERNAME={SSH_USERNAME}
GCP_SSH_PASSPHRASE={SSH_PASSPHRASE}
GCP_SSH_PRIVATE_KEY=-----BEGIN OPENSSH PRIVATE KEY-----\n{데이터 본문}\n-----END OPENSSH PRIVATE KEY-----&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r6yft/dJMcaiV4gOk/eC6BPXNliB1oTntDlgQJQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r6yft/dJMcaiV4gOk/eC6BPXNliB1oTntDlgQJQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r6yft/dJMcaiV4gOk/eC6BPXNliB1oTntDlgQJQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr6yft%2FdJMcaiV4gOk%2FeC6BPXNliB1oTntDlgQJQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2386&quot; height=&quot;750&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;750&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 SSH 접속을 실패한 원인은 &lt;b&gt;SSH Private Key 값이 정확하지 않았기 때문&lt;/b&gt;입니다. 현재 설정된 GCP_SSH_PRIVATE_KEY 변수의 값의 형식은 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764913134134&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GCP_SSH_PRIVATE_KEY=-----BEGIN OPENSSH PRIVATE KEY-----\n{데이터 본문}\n-----END OPENSSH PRIVATE KEY-----&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 pem 파일에 저장된 SSH Private Key 값은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2170&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wDINc/dJMcadf9Aim/OjfmrBF6PDRXnKtUNXFR71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wDINc/dJMcadf9Aim/OjfmrBF6PDRXnKtUNXFR71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wDINc/dJMcadf9Aim/OjfmrBF6PDRXnKtUNXFR71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwDINc%2FdJMcadf9Aim%2FOjfmrBF6PDRXnKtUNXFR71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2170&quot; height=&quot;306&quot; data-origin-width=&quot;2170&quot; data-origin-height=&quot;306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 값이 나오게된 경위는 Private Key값이 줄바꿈이 포함되어 저장되어 있습니다. 이러한 값을 한줄의 문자열로 저장하기 위해서 줄바꿈을 &quot;\n&quot; 문자열로 변경하여 넣어야 겠다고 생각하였습니다. 그래서 다음과 같은 명령어를 사용하여 줄바꿈을 &quot;\n&quot;으로 변경하여 출력하도록 하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764913411377&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;KEY_FILE_PATH=&quot;{pem 파일 경로}&quot;
awk '{printf &quot;%s\\n&quot;, $0}' &quot;$KEY_FILE_PATH&quot; | sed 's/\\n$//'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 위와 같이 생성된 &lt;b&gt;한줄의 문자열 형태는 정확하지 않은 값이기 때문에 SSH 접속에 실패&lt;/b&gt;한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 SSH Private Key 값을 프로퍼티 값에 넣기 위해서는 다음과 같은 형태로 저장되어야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764913550506&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;GCP_SSH_PRIVATE_KEY=&quot;-----BEGIN OPENSSH PRIVATE KEY-----
...mNyeXB0AAAAGAAAABDsFvUfRU
...
...sE6n0A==
-----END OPENSSH PRIVATE KEY-----&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 act 명령어를 실행하여 SSH 접속이 되는지 확인해봅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764913741541&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;act -W .github/workflows/ci.cd.production.gcp.yml \
--container-architecture linux/amd64 \
--secret-file .secrets \
--job deploy&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과를 보면 SSH 접속하여 echo 명령어로 출력한 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2428&quot; data-origin-height=&quot;822&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEBAjn/dJMcag44wu8/bRzRBuoGG6BZxRZQHTwgGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEBAjn/dJMcag44wu8/bRzRBuoGG6BZxRZQHTwgGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEBAjn/dJMcag44wu8/bRzRBuoGG6BZxRZQHTwgGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEBAjn%2FdJMcag44wu8%2FbRzRBuoGG6BZxRZQHTwgGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2428&quot; height=&quot;822&quot; data-origin-width=&quot;2428&quot; data-origin-height=&quot;822&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;References&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;workflow : &lt;a href=&quot;https://github.com/fine-ants/FineAnts-was/actions/runs/19954351891/workflow&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/fine-ants/FineAnts-was/actions/runs/19954351891/workflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/appleboy/ssh-action/issues/6&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/appleboy/ssh-action/issues/6&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>문제해결</category>
      <author>김용환</author>
      <guid isPermaLink="true">https://yonghwankim-dev.tistory.com/631</guid>
      <comments>https://yonghwankim-dev.tistory.com/631#entry631comment</comments>
      <pubDate>Fri, 5 Dec 2025 15:11:27 +0900</pubDate>
    </item>
    <item>
      <title>Gradle의 Java 플러그인, implementation과 api의 차이</title>
      <link>https://yonghwankim-dev.tistory.com/630</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Gradle Java 플러그인과 Java-Library 플러그인&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Java 플러그인은 무엇인가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gradle 설정에서 Java 플러그인을 추가하면 해당 프로젝트를 자바 프로젝트로 만들고 자바 소스코드를  컴파일, 테스트, 빌드하는데 필용한 기능들을 Gradle에 부여해줍니다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;plugins {  
    id 'java'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 플러그인을 추가하면 다음 그림과 같은 테스크들을 사용할 수 있습니다. 각 테스크들간에 화살표 관계는 해당 테스크를 완료하기 위한 선행 조건 테스크들을 의미합니다. 예를 들어 build 테스크를 수행하기 위해서는 check, assemble 테스크가 먼저 수행 및 완료되어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;329&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBT3RL/dJMcaawZ0zL/ctrapbkXp9fwDjlKDD0s51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBT3RL/dJMcaawZ0zL/ctrapbkXp9fwDjlKDD0s51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBT3RL/dJMcaawZ0zL/ctrapbkXp9fwDjlKDD0s51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBT3RL%2FdJMcaawZ0zL%2FctrapbkXp9fwDjlKDD0s51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1250&quot; height=&quot;329&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;329&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 컴파일러가 자바 소스 코드(&lt;code&gt;*.java&lt;/code&gt;)를 컴파일 하게 되면 바이트 코드(&lt;code&gt;*.class&lt;/code&gt;)를 생성합니다. 이러한 바이트 코드들은 JVM(Java Virtual Machine)에 의해서 읽어지게 됩니다. 그리고 읽어진 바이트 코드들은 기계어로 번역됩니다. JVM이 자바 소스코드를 컴파일하거나 컴파일된 바이트코드를 읽어서 실행하기 위해서 파일과 패키지를 탐색하는데, 이러한 경로를 &lt;b&gt;클래스패스(classpath)&lt;/b&gt;라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스패스에는 두가지 종류가 존재합니다. 첫번째는 컴파일 클래스 패스(compile class path), 두번째는 런타임 클래스 패스(runtime class path)입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴파일 클래스 패스(compile classpath) : 자바 소스 코드(&lt;code&gt;*.java&lt;/code&gt;)를 바이트 코드(&lt;code&gt;*.class&lt;/code&gt;)로 컴파일 할때 탐색하는 경로&lt;/li&gt;
&lt;li&gt;런타임 클래스 패스(runtime classpath) : 컴파일 된 바이트 코드(&lt;code&gt;*.class&lt;/code&gt;)을 JVM이 읽기 위해서 탐색하는 경로&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Gradle의 클래스패스 의존성 설정&lt;/b&gt;&lt;br /&gt;Gradle을 기반으로 자바 애플리케이션을 빌드하기 위해서 설정하는 의존성 설정이 존재합니다. 그래서 Gradle 설정에서 각각의 라이브러리의 의존성을 추가할때 어느 범위로 노출시킬 것인지 결정할 수 있습니다. 종류는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;compileOnly : 컴파일 클래스 경로에만 라이브러리를 설정함&lt;/li&gt;
&lt;li&gt;runtimeOnly : 런타임 클래스 경로에만 라이브러리를 설정함&lt;/li&gt;
&lt;li&gt;implementation : 컴파일 클래스 경로, 런타임 클래스 경로 두곳에 라이브러리를 설정함&lt;/li&gt;
&lt;li&gt;api : 컴파일 클래스 경로, 런타임 클래스 경로 두곳에 라이브러리를 설정함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Java-Library 플러그인은 무엇인가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java-Library 플러그인은 기본 Java 플러그인의 모든 기능을 포함하고, 라이브러리 개발에 필요한 특화된 규칙(예: 프로덕션 코드는 src/main/java에 위치)을 추가합니다.&lt;br /&gt;Java 플러그인과의 차이점은 라이브러리 개발시 어떤 의존성을 라이브러리의 외부 API로 노출하고(다른 프로젝트가 접근 가능), 어떤 의존성을 내부 구현으로 숨길지(다른 프로젝트가 접근 불가)을 정의할 수 있습니다.(이때 &lt;code&gt;api&lt;/code&gt;, &lt;code&gt;implementation&lt;/code&gt; 설정이 사용됨)&lt;br /&gt;Java-Library를 사용하면 java 플러그인이 제공하는 기본적인 디렉토리 구조, 빌드 테스크(compileJava, jar 등), 그리고 의존성 설정(testImplementation 등)을 모두 자동으로 사용할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Gradle implementation과 api의 차이&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전이 의존성이란 무엇인가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gradle 설정에서 의존성 라이브러리 설정시 다양한 설정이 옵션이 존재합니다. 대표적으로 implementation과 api 의존성 설정이 존재합니다. 두 설정은 모두 컴파일 클래스경로(compile classpath)와 런타임 클래스경로(runtime classpath) 두곳 모두에 라이브러리를 설정하는 공통점을 가지고 있습니다. 하지만 대표적인 차이점은 &lt;b&gt;전의 의존성(transitive dependency)의 컴파일 경로 노출 여부&lt;/b&gt;가 있습니다.&lt;br /&gt;전이 의존성은 어떤 프로젝트 A가 직접적으로 명시하지 않은 라이브러리 C인데도 불구하고, 프로젝트 A가 다른 프로젝트 B를 의존하는 것만으로도 라이브러리 C를 사용할 수 있다면 전이 의존성이 있는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqkt6s/dJMcajglVzJ/bEd0mv1YvFSfNsxTik6Ff1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqkt6s/dJMcajglVzJ/bEd0mv1YvFSfNsxTik6Ff1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqkt6s/dJMcajglVzJ/bEd0mv1YvFSfNsxTik6Ff1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdqkt6s%2FdJMcajglVzJ%2FbEd0mv1YvFSfNsxTik6Ff1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1160&quot; height=&quot;224&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;implementation과 api의 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;api&lt;/code&gt; 의존성 설정은 전이 의존성을 허용하고 있습니다.&lt;/b&gt; 예를 들어 프로젝트 A,B,C가 존재하고 프로젝트 B는 프로젝트 C를 의존하고 프로젝트 A는 프로젝트 B를 의존하는 관계라고 가정합니다. 그림으로 보면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EWwqf/dJMcacIlYVO/K7ao7zr1M8CUjz9UCmo131/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EWwqf/dJMcacIlYVO/K7ao7zr1M8CUjz9UCmo131/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EWwqf/dJMcacIlYVO/K7ao7zr1M8CUjz9UCmo131/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEWwqf%2FdJMcacIlYVO%2FK7ao7zr1M8CUjz9UCmo131%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1204&quot; height=&quot;224&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림을 보면 프로젝트 C에는 Hello라는 클래스가 public으로 구현되어 있습니다. 그런한 프로젝트 C를 프로젝트 B는 api 설정으로 의존성 설정되어 있습니다. &lt;code&gt;api&lt;/code&gt;로 설정하였기 때문에 전이 의존성을 허용합니다. 그래서 프로젝트 B를 의존하고 있는 프로젝트 A에서는 프로젝트 C에 정의되어 있는 Hello 클래스를 참조하여 객체를 생성할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에 &lt;code&gt;implementation&lt;/code&gt; 의존성 설정은 전이 의존성을 허용하고 있지 않습니다. 예를 들어 위 그림에서 프로젝트 B가 프로젝트 C의 의존성 설정을 &lt;code&gt;api&lt;/code&gt;가 아닌 &lt;code&gt;implementation&lt;/code&gt;으로 설정해보겠습니다. 그렇게 되면 상황은 다음 그림과 같이 됩니다.&lt;br /&gt;프로젝트 B의 의존성 라이브러리 설정에서 &lt;code&gt;implementation&lt;/code&gt;으로 설정된 의존성 라이브러리들은 내부 구현으로 숨겨져 있기 때문에 프로젝트 A가 프로젝트 C의 Hello 클래스를 사용할 수 없습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hm6Ky/dJMcaiPhTfp/CWACx8qYHo3xfCc4TcDZik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hm6Ky/dJMcaiPhTfp/CWACx8qYHo3xfCc4TcDZik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hm6Ky/dJMcaiPhTfp/CWACx8qYHo3xfCc4TcDZik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHm6Ky%2FdJMcaiPhTfp%2FCWACx8qYHo3xfCc4TcDZik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1158&quot; height=&quot;260&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;implementation과 api의 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;api&lt;/code&gt; 대신 &lt;code&gt;implementation&lt;/code&gt;을 사용하는 경우 장점은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴파일 클래스패스(compile classpath)에 &lt;code&gt;implementation&lt;/code&gt;으로 설정된 의존성 라이브러리들이 노출되지 않아서 해당 라이브러리들에 대해서 종속적이지 않게 됩니다.&lt;/li&gt;
&lt;li&gt;의존성 라이브러리들이 노출되지 않아서 그만큼 컴파일 속도가 빨라집니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;implementation&lt;/code&gt;으로 설정된 의존성 라이브러리들이 변경되면 다시 컴파일하지 않아도 되서 재컴파일 횟수가 줄어듭니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;api&lt;/code&gt;는 애플리케이션 바이너리 인터페이스(Application Binary Interface)로써 특정 상황에서 사용하면 좋습니다. 애플리케이션 바이너리 인터페이스란 컴파일된 코드(Binary) 레벨에서 다른 모듈과의 호환성을 정의하는 인터페이스입니다.&lt;br /&gt;프로젝트에서 ABI에 해당하는 타입이 변경되면, 해당 프로젝트를 의존하는 외부의 프로젝트는 반드시 재컴파일해야 합니다. 반면에 ABI에 해당하지 않는 타입들은 내부 구현만 변경되면 외부의 프로젝트들은 다시 컴파일할 필요없이 빌드 속도가 빨라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;api&lt;/code&gt;(ABI에 노출되는 부분)&lt;br /&gt;&lt;code&gt;api&lt;/code&gt;로 설정된 의존성 라이브러리들은 프로젝트를 사용하는 외부의 프로젝트들로부터 노출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;api&lt;/code&gt;를 사용해야 하는 경우(ABI 해당)&lt;br /&gt;다음의 경우는 외부 프로젝트들이 해당 타입을 직접 알아야 하거나 접근해야 하는 경우입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 부모 클래스 또는 인터페이스에 사용되는 타입&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어 당신의 프로젝트 B가 &lt;b&gt;외부 라이브러리 A의 인터페이스와 상속받은 클래스를 외부에 공개할 경우&lt;/b&gt;에 외부의 프로젝트인 C는 외부 라이브러리 A의 존재를 알고 있어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOxVBV/dJMcagxc2XJ/jpSAWQ24wMm5RMwtKg9Fn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOxVBV/dJMcagxc2XJ/jpSAWQ24wMm5RMwtKg9Fn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOxVBV/dJMcagxc2XJ/jpSAWQ24wMm5RMwtKg9Fn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOxVBV%2FdJMcagxc2XJ%2FjpSAWQ24wMm5RMwtKg9Fn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1268&quot; height=&quot;248&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;248&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. public 메서드 파라미터/반환 타입으로 사용하는 타입&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어 &lt;code&gt;public List&amp;lt;User&amp;gt; getUsers()&lt;/code&gt; 메서드와 같이 외부 라이브러리 A의 &lt;code&gt;List&lt;/code&gt;나 &lt;code&gt;User&lt;/code&gt; 타입을 반환하면, 당신의 프로젝트를 사용하는 외부의 프로젝트들은 컴파일 시점에 외부 라이브러리 A의 &lt;code&gt;User&lt;/code&gt; 타입을 알고 있어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O9UCN/dJMcahJFprd/FTA3vTso0xE7SWF3gakKb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O9UCN/dJMcahJFprd/FTA3vTso0xE7SWF3gakKb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O9UCN/dJMcahJFprd/FTA3vTso0xE7SWF3gakKb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO9UCN%2FdJMcahJFprd%2FFTA3vTso0xE7SWF3gakKb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1154&quot; height=&quot;206&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. public 필드에 사용되는 타입&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부에 공개된 타입이나 annotation 타입은 외부에서 직접 참조하므로 ABI에 해당됩니다.&lt;/li&gt;
&lt;li&gt;예를 들어 MyLibrary 모듈(프로젝트)이 구현한 클래스에서 외부 라이브러이인 Gson 라이브러리의 JsonElement 타입을 public으로 설정하여 구현하였습니다. 이러한 MyLibrary 모듈을 의존하는 외부 프로젝트인 AppModule이 DataContainer 객체의 element public 필드를 참조한다면 AppModule 모듈은 Gson 라이브러리의 JsonElement를 import 할 수 있어야 합니다. 이렇게 하기 위해서 gson 라이브러리를 api로 의존성 해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;457&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFPifB/dJMcaawZ0HI/sfrMw4NveRdkcMHAyySqN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFPifB/dJMcaawZ0HI/sfrMw4NveRdkcMHAyySqN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFPifB/dJMcaawZ0HI/sfrMw4NveRdkcMHAyySqN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFPifB%2FdJMcaawZ0HI%2FsfrMw4NveRdkcMHAyySqN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;457&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;457&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. public annotation 타입&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부에 공개되는 annotation 타입은 외부에서 직접 참조할 수 있으므로 ABI에 해당됩니다.&lt;/li&gt;
&lt;li&gt;예를 들어 MyLibrary 모듈에서 validation 외부 라이브러리를 의존하고 그 라이브러리의 애노테이션 중 하나인 &lt;code&gt;@NotNull&lt;/code&gt;을 필드에 사용합니다. 그리고 MyLibrary를 의존하는 외부 프로젝트인 AppModule 모듈이 프로덕션 코드 중에서 매개변수로 받은 객체의 클래스 정보 중에서 NotNull을 가지고 있는지 확인합니다. 이 상황에서 AppMoudle은 validation 외부 라이브러리를 알아야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;517&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/edUSDx/dJMcabJrU4B/pKhQpFLt2cO430nL6kBck1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/edUSDx/dJMcabJrU4B/pKhQpFLt2cO430nL6kBck1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/edUSDx/dJMcabJrU4B/pKhQpFLt2cO430nL6kBck1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FedUSDx%2FdJMcabJrU4B%2FpKhQpFLt2cO430nL6kBck1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;517&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;517&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;api&lt;/code&gt;를 사용하지 않아야 하는 경우 (ABI에 해당 안됨)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 메서드의 바디에서만 사용되는 타입&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어 MyLibrary 모듈은 Guava 라이브러리를 의존하고 있습니다. 그리고 processData 메서드를 구현하는 과정에서 Guava 라이브러리의 ImmutableList를 import하여 사용하고 있습니다. 하지만 반환 타입은 List 타입을 사용하고 있기 때문에 외부 프로젝트에서는 ImmutableList 타입을 알지 않아도 됩니다. 그래서 외부 프로젝트인 AppMoudle에서는 DataProcessor 객체를 사용할때 processData 메서드를 호출하여도 ImmutableList 타입은 외부에 노출되지 않고 사용할 수 있습니다. 이러한 경우에 일부로 guava 라이브러리를 &lt;code&gt;api&lt;/code&gt;로 노출시키지 않아도 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;452&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhktaQ/dJMcac2BETj/icmCW6qpcpHtzXwCWykiM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhktaQ/dJMcac2BETj/icmCW6qpcpHtzXwCWykiM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhktaQ/dJMcac2BETj/icmCW6qpcpHtzXwCWykiM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhktaQ%2FdJMcac2BETj%2FicmCW6qpcpHtzXwCWykiM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;452&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;452&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. private 멤버로 사용되는 타입&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt; 내부에서만 사용되는 private 멤버로 사용되는 타입이라면 &lt;code&gt;api&lt;/code&gt; 사용하지 않아도 됩니다.&lt;/li&gt;
&lt;li&gt;예를 들어 MyLibrary 모듈에서 lombok 라이브러리를 의존합니다. 그리고 Hello 클래스 구현시 Lombok 라이브러리의 Logger 클래스를 사용할때 private로 선언되어 있고 Hello 클래스에서만 내부적으로 사용되고 있습니다. 이러한 경우에 외부 프로젝트인 AppModule에서 MyLibrary 모듈을 의존하고 Hello 객체를 생성하고 사용해도 Logger에 대한 노출이 없기 때문에 Lombok이 변경되어도 재컴파일하지 않아도 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pyitr/dJMcacuOEgx/30QejgYatuCrVgWyyulch1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pyitr/dJMcacuOEgx/30QejgYatuCrVgWyyulch1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pyitr/dJMcacuOEgx/30QejgYatuCrVgWyyulch1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpyitr%2FdJMcacuOEgx%2F30QejgYatuCrVgWyyulch1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;528&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 내부 클래스에서 발견되는 타입&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 라이브러리가 오직 외부에 공개되지 않은 내부 클래스(Inner Class)나 익명 클래스에서만 사용될때 해당됩니다.&lt;/li&gt;
&lt;li&gt;예를 들어 MyLibrary 모듈이 StringUtils 외부 라이브러리를 의존합니다. 그러나 MyLibrary 모듈을 사용하는 외부의 프로젝트에서는 ObjectFactory 객체가 create 메서ㄷ를 수행할때 StringUtils 라이브러리를 참조할 필요가 없습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CxWbd/dJMcahv7uiP/KvGNPZkrPZxK3fH4H4ryeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CxWbd/dJMcahv7uiP/KvGNPZkrPZxK3fH4H4ryeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CxWbd/dJMcahv7uiP/KvGNPZkrPZxK3fH4H4ryeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCxWbd%2FdJMcahv7uiP%2FKvGNPZkrPZxK3fH4H4ryeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;900&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;900&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;implementation과 api 차이 요약&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style4&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;implementation (권장)&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;api (제한적 사용)&lt;/b&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;의존성 전파 (Transitivity)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;차단&lt;/b&gt; (노출 안 함)&lt;/td&gt;
&lt;td&gt;&lt;b&gt;허용&lt;/b&gt; (노출 함)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;외부 노출&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;이 모듈을 사용하는 &lt;b&gt;외부 모듈에게 라이브러리 노출 안 함&lt;/b&gt; (전이 의존성 X)&lt;/td&gt;
&lt;td&gt;이 모듈을 사용하는 &lt;b&gt;외부 모듈에게 라이브러리 노출 함&lt;/b&gt; (전이 의존성 O)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;사용 목적&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;모듈의 &lt;b&gt;내부 구현&lt;/b&gt;에만 사용하는 의존성 (예: 유틸리티, 로깅 구현체)&lt;/td&gt;
&lt;td&gt;모듈의 &lt;b&gt;공개된 API&lt;/b&gt;를 구성하는 데 사용되는 의존성 (예: Public 메서드의 파라미터 타입)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;빌드 성능&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;우수함.&lt;/b&gt; 내부 구현 변경 시 외부 모듈은 재컴파일 불필요.&lt;/td&gt;
&lt;td&gt;&lt;b&gt;낮음.&lt;/b&gt; &lt;code&gt;api&lt;/code&gt; 의존성 변경 시 이를 사용하는 모든 외부 모듈이 &lt;b&gt;재컴파일 필요.&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;References&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://mangkyu.tistory.com/296&quot;&gt;https://mangkyu.tistory.com/296&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/java_library_plugin.html&quot;&gt;https://docs.gradle.org/current/userguide/java_library_plugin.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/java_plugin.html&quot;&gt;https://docs.gradle.org/current/userguide/java_plugin.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://robin00q.tistory.com/15&quot;&gt;https://robin00q.tistory.com/15&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>김용환</author>
      <guid isPermaLink="true">https://yonghwankim-dev.tistory.com/630</guid>
      <comments>https://yonghwankim-dev.tistory.com/630#entry630comment</comments>
      <pubDate>Tue, 2 Dec 2025 15:38:31 +0900</pubDate>
    </item>
    <item>
      <title>Gradle 기반 멀티 모듈 생성 및 의존성 추가 방법</title>
      <link>https://yonghwankim-dev.tistory.com/629</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Gradle 기반으로 멀티 모듈 Project를 생성하는 방법을 학습&lt;/li&gt;
&lt;li&gt;project, subprojects, allprojects을 통하여 특정 모듈들에 설정하는 방법을 학습&lt;/li&gt;
&lt;li&gt;모듈간에 다른 의존성을 공유하는 방법을 학습&lt;/li&gt;
&lt;li&gt;외부 파일을 기반으로 의존성 라이브러리를 관리하는 방법을 학습&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;멀티 모듈 프로젝트는 무엇인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;멀티 모듈 프로젝트는 여러개의 작은 프로젝트들로 구성된 프로젝트입니다.&lt;/b&gt; 해당 프로젝트에는 루트 프로젝트가 존재하고 루트 프로젝트 아래에 여러개의 모듈이 존재할 수 있습니다.&lt;br /&gt;다음 프로젝트를 보면 프로젝트 이름은 &quot;gradlebasics&quot; 라는 이름이 프로젝트 이름이고 하나의 gradlebasics 라는 프로젝트 이름 아래에 sub-project-1, sub-project-2, common라는 모듈이 포함되어 있는 형태입니다. 물론 gradlebasics 라는 프로젝트 자체가 하나의 모듈로서 작동할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QCGCg/dJMcaaDKHjG/uK7lCFcj31K5XQeisVUFc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QCGCg/dJMcaaDKHjG/uK7lCFcj31K5XQeisVUFc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QCGCg/dJMcaaDKHjG/uK7lCFcj31K5XQeisVUFc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQCGCg%2FdJMcaaDKHjG%2FuK7lCFcj31K5XQeisVUFc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;806&quot; height=&quot;466&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;루트 프로젝트 생성 및 모듈 생성&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;gradlebasics 루트 프로젝트 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gradlebasics라는 이름의 루트 프로젝트를 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ntyma/dJMcac9mRpS/IKptimFipfsAn7xclUFc51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ntyma/dJMcac9mRpS/IKptimFipfsAn7xclUFc51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ntyma/dJMcac9mRpS/IKptimFipfsAn7xclUFc51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fntyma%2FdJMcac9mRpS%2FIKptimFipfsAn7xclUFc51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1100&quot; height=&quot;922&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;922&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gradlebasics 프로젝트 생성 확인&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;./gradlew build&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bserIV/dJMcafyiyFC/g7PcuUQXMnnVmMbvLFEaFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bserIV/dJMcafyiyFC/g7PcuUQXMnnVmMbvLFEaFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bserIV/dJMcafyiyFC/g7PcuUQXMnnVmMbvLFEaFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbserIV%2FdJMcafyiyFC%2Fg7PcuUQXMnnVmMbvLFEaFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;204&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;204&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 화면을 보면 gradlebasics라는 프로젝트가 루트 프로젝트가 되는 것을 볼수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zI6XT/dJMcafZmJps/xrIUwJWKAF4HepXacz3VK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zI6XT/dJMcafZmJps/xrIUwJWKAF4HepXacz3VK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zI6XT/dJMcafZmJps/xrIUwJWKAF4HepXacz3VK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzI6XT%2FdJMcafZmJps%2FxrIUwJWKAF4HepXacz3VK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;956&quot; height=&quot;656&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;656&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;sub-project-1 모듈 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sub-project-1 모듈 생성시 &quot;Module...&quot; 메뉴를 통하여 생성하는 것이 아닌 단순히 Directory 메뉴를 통하여 생성해봅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZ6qe2/dJMcagjFBhe/NWk6ORz7mDysGBoLsPbKKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZ6qe2/dJMcagjFBhe/NWk6ORz7mDysGBoLsPbKKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZ6qe2/dJMcagjFBhe/NWk6ORz7mDysGBoLsPbKKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZ6qe2%2FdJMcagjFBhe%2FNWk6ORz7mDysGBoLsPbKKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;296&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성한 sub-project-1 디렉토리가 모듈로 취급하기 위해서 루트 프로젝트의 &lt;b&gt;&lt;code&gt;settings.gradle&lt;/code&gt;&lt;/b&gt; 파일을 수정하여 sub-project-1 모듈을 포함하도록 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l9ROg/dJMcahQpQRC/HghoBKR7kLFBTccUICucr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l9ROg/dJMcahQpQRC/HghoBKR7kLFBTccUICucr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l9ROg/dJMcahQpQRC/HghoBKR7kLFBTccUICucr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl9ROg%2FdJMcahQpQRC%2FHghoBKR7kLFBTccUICucr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;238&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈 프로젝트 확인&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;./gradlew projects&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 실행 화면을 보면 루트 프로젝트가 &quot;gradlebasics&quot;이고 모듈 프로젝트가 &quot;sub-project-1&quot; 프로젝트인 것을 볼수 있습니다. 우리는 sub-project-1 프로젝트를 하나의 모듈로 취급합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1054&quot; data-origin-height=&quot;626&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BFOzy/dJMcai2OtBa/J6JGPgOY9cZevPvkbgp6z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BFOzy/dJMcai2OtBa/J6JGPgOY9cZevPvkbgp6z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BFOzy/dJMcai2OtBa/J6JGPgOY9cZevPvkbgp6z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBFOzy%2FdJMcai2OtBa%2FJ6JGPgOY9cZevPvkbgp6z0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1054&quot; height=&quot;626&quot; data-origin-width=&quot;1054&quot; data-origin-height=&quot;626&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sub-project-1 모듈에 소스 코드 디렉토리 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q9VCW/dJMcaiuYRRR/I1702bagTV7KsTR0PidzKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q9VCW/dJMcaiuYRRR/I1702bagTV7KsTR0PidzKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q9VCW/dJMcaiuYRRR/I1702bagTV7KsTR0PidzKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq9VCW%2FdJMcaiuYRRR%2FI1702bagTV7KsTR0PidzKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;922&quot; height=&quot;206&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sub-project-1 모듈, 패키지 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbZemk/dJMcaiaIMpI/5R0X3AQNFcmL9m6rMQie9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbZemk/dJMcaiaIMpI/5R0X3AQNFcmL9m6rMQie9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbZemk/dJMcaiaIMpI/5R0X3AQNFcmL9m6rMQie9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbZemk%2FdJMcaiaIMpI%2F5R0X3AQNFcmL9m6rMQie9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;886&quot; height=&quot;336&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;subprojectone 패키지에 클래스 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTRbH4/dJMcadUJxQ8/wAKpjCHBw5GYFbmzb6keq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTRbH4/dJMcadUJxQ8/wAKpjCHBw5GYFbmzb6keq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTRbH4/dJMcadUJxQ8/wAKpjCHBw5GYFbmzb6keq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTRbH4%2FdJMcadUJxQ8%2FwAKpjCHBw5GYFbmzb6keq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;886&quot; height=&quot;348&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;348&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;build.gradle&lt;/code&gt; 파일 생성&lt;br /&gt;해당 디렉토리에 &lt;code&gt;build.gradle&lt;/code&gt; 파일을 생성한다는 의미는 해당 디렉토리를 독립적인 Gradle 모듈로 정의하고 그 모듈의 &lt;b&gt;빌드 방식과 설정을 명시&lt;/b&gt;하겠다는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wQvrx/dJMcaaDKHnz/GPAk0QhjPwghw296K39cz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wQvrx/dJMcaaDKHnz/GPAk0QhjPwghw296K39cz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wQvrx/dJMcaaDKHnz/GPAk0QhjPwghw296K39cz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwQvrx%2FdJMcaaDKHnz%2FGPAk0QhjPwghw296K39cz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;622&quot; height=&quot;146&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;146&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[!NOTE]  IntelliJ IDEA에서 모듈 취급하기&lt;br /&gt; 서브 모듈을 디렉토리와 같이 직접적으로 생성하는 경우 IntelliJ IDEA 편집창에서 해당 디렉토리를 서브 모듈로 취급하지 않을 수 있습니다. 이 문제를 해결하기 위해서 IntelliJ의 Gradle 메뉴에서 &lt;b&gt;&quot;Reload All Gradle Project&quot;&lt;/b&gt; 기능을 실행하여 sub-project-1와 같은 디렉토리를 모듈 취급하게 할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Moi20/dJMcag420Qe/T0PMO208IgxQKh39wIVcFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Moi20/dJMcag420Qe/T0PMO208IgxQKh39wIVcFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Moi20/dJMcag420Qe/T0PMO208IgxQKh39wIVcFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMoi20%2FdJMcag420Qe%2FT0PMO208IgxQKh39wIVcFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;930&quot; height=&quot;172&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kXwyh/dJMcafZmJvE/gFO72J5oy7OfrdZkzctIr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kXwyh/dJMcafZmJvE/gFO72J5oy7OfrdZkzctIr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kXwyh/dJMcafZmJvE/gFO72J5oy7OfrdZkzctIr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkXwyh%2FdJMcafZmJvE%2FgFO72J5oy7OfrdZkzctIr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;722&quot; height=&quot;370&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;sub-project-2 모듈 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sub-project-1 모듈과 같이 디렉토리를 직접 생성하는 방법이 아닌 Intellij Module 메뉴를 통해서 생성해봅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B11bS/dJMcadNXM6s/fay90kEpj7FnoJT1vCHMek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B11bS/dJMcadNXM6s/fay90kEpj7FnoJT1vCHMek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B11bS/dJMcadNXM6s/fay90kEpj7FnoJT1vCHMek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB11bS%2FdJMcadNXM6s%2Ffay90kEpj7FnoJT1vCHMek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;308&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성창에서 이름과 JDk 버전, 상위 모듈 또는 프로젝트를 설정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1108&quot; data-origin-height=&quot;692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PVswa/dJMcahXb2Pd/vAjRMk8GJj7ySLyAwYjYGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PVswa/dJMcahXb2Pd/vAjRMk8GJj7ySLyAwYjYGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PVswa/dJMcahXb2Pd/vAjRMk8GJj7ySLyAwYjYGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPVswa%2FdJMcahXb2Pd%2FvAjRMk8GJj7ySLyAwYjYGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1108&quot; height=&quot;692&quot; data-origin-width=&quot;1108&quot; data-origin-height=&quot;692&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sub-project-2 모듈 생성 확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDkhS1/dJMcacBz4RF/RGckCJqL6p04sCIM2ek31K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDkhS1/dJMcacBz4RF/RGckCJqL6p04sCIM2ek31K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDkhS1/dJMcacBz4RF/RGckCJqL6p04sCIM2ek31K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDkhS1%2FdJMcacBz4RF%2FRGckCJqL6p04sCIM2ek31K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;920&quot; height=&quot;300&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;build.gradle&lt;/code&gt; 확인&lt;br /&gt;파일을 확인하면 sub-project-2 모듈의 독립적인 설정들이 존재하는데, 이번 학습에서는 중복적인 설정을 모두 루트 프로젝트의 Gradle 설정에서 제어할 예정이기 때문에 다음 설정들을 모두 제거합니다. 이는 sub-project-1 모듈 또한 동일합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;932&quot; data-origin-height=&quot;818&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pv6EF/dJMb99Y9kXt/SmBUpZF8lHu1oGBrR67kLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pv6EF/dJMb99Y9kXt/SmBUpZF8lHu1oGBrR67kLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pv6EF/dJMb99Y9kXt/SmBUpZF8lHu1oGBrR67kLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpv6EF%2FdJMb99Y9kXt%2FSmBUpZF8lHu1oGBrR67kLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;932&quot; height=&quot;818&quot; data-origin-width=&quot;932&quot; data-origin-height=&quot;818&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1002&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AAkQN/dJMcahCSrSv/TVQ6PlbEVtL5GefM9i3N41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AAkQN/dJMcahCSrSv/TVQ6PlbEVtL5GefM9i3N41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AAkQN/dJMcahCSrSv/TVQ6PlbEVtL5GefM9i3N41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAAkQN%2FdJMcahCSrSv%2FTVQ6PlbEVtL5GefM9i3N41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1002&quot; height=&quot;258&quot; data-origin-width=&quot;1002&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDGKMp/dJMcab3JKOm/r26OZ2WJYJFjgvnO3NjEPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDGKMp/dJMcab3JKOm/r26OZ2WJYJFjgvnO3NjEPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDGKMp/dJMcab3JKOm/r26OZ2WJYJFjgvnO3NjEPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDGKMp%2FdJMcab3JKOm%2Fr26OZ2WJYJFjgvnO3NjEPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;772&quot; height=&quot;266&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;루트 프로젝트 - project 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;build.gradle&lt;/code&gt; 파일에 project 설정을 이용하여 특정 모듈의 Gradle 설정을 추가할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;project 설정을 활용하여 서브 모듈인 sub-project-1 모듈에 &quot;hello&quot; 라는 이름의 테스크를 추가해보겠습니다.&lt;br /&gt; 추가적으로 sub-project-1 모듈에 java 플러그인을 추가합니다. java 플러그인을 추가함으로써 sub-project-1 모듈은 표준 자바 프로젝트로 인식하고, 자바 코드를 빌드하는데 필요한 모든 설정(compileJava, jar, test 등)을 자동으로 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;762&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2NQCi/dJMcai2OtC5/YSmucvniZ0akjKR3uuxfw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2NQCi/dJMcai2OtC5/YSmucvniZ0akjKR3uuxfw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2NQCi/dJMcai2OtC5/YSmucvniZ0akjKR3uuxfw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2NQCi%2FdJMcai2OtC5%2FYSmucvniZ0akjKR3uuxfw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;978&quot; height=&quot;762&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;762&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sub-project-1 hello Task 수행&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;./gradlew :sub-project-1:hello&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과를 보면 정상적으로 hello 테스크를 수행하여 프로젝트 이름을 출력한 것을 볼수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDFeRp/dJMcabo7TKJ/DcYAsA8aLSJSR5OFisvFhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDFeRp/dJMcabo7TKJ/DcYAsA8aLSJSR5OFisvFhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDFeRp/dJMcabo7TKJ/DcYAsA8aLSJSR5OFisvFhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDFeRp%2FdJMcabo7TKJ%2FDcYAsA8aLSJSR5OFisvFhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1272&quot; height=&quot;188&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;루트 프로젝트 - subprojects 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;project 설정을 이용하여 특정 모듈에 테스크 및 플러그인을 적용할 수 있었습니다. 하지만 만약에 동일한 테스크 및 플러그인 설정을 다른 모듈에도 적용하기 위해서는 어떻게 해야할까요? project 설정을 추가하여 동일한 코드의 테스크와 플러그인 설정을 추가해야 할 것입니다. 하지만 이러한 방법은 코드 중복이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 중복 문제를 해결하기 위해서 subprojects 설정을 사용하여 해결할 수 있습니다. &lt;b&gt;subprojects 설정을 사용하면 루트 프로젝트를 제외한 서브 모듈들에 동일한 설정을 제공&lt;/b&gt;할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;subprojects 설정을 사용하여 sub-project-1, sub-project-2 모듈에 java 플러그인, spring boot 플러그인, hello 테스크를 적용해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;782&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQRfBz/dJMcabo7TKZ/UCDzWixfUcJUFKy22LP6s0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQRfBz/dJMcabo7TKZ/UCDzWixfUcJUFKy22LP6s0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQRfBz/dJMcabo7TKZ/UCDzWixfUcJUFKy22LP6s0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQRfBz%2FdJMcabo7TKZ%2FUCDzWixfUcJUFKy22LP6s0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;970&quot; height=&quot;782&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;782&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트 프로젝트 hello 테스크 테스트&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;./gradlew hello&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과를 보면 2개의 서브모듈의 hello 테스크가 실행된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/efrQcf/dJMcac9mRGi/uDNClYFlBCjedubsKAx1pK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/efrQcf/dJMcac9mRGi/uDNClYFlBCjedubsKAx1pK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/efrQcf/dJMcac9mRGi/uDNClYFlBCjedubsKAx1pK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FefrQcf%2FdJMcac9mRGi%2FuDNClYFlBCjedubsKAx1pK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1046&quot; height=&quot;682&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;루트 프로젝트 - allprojects 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 subprojects 설정을 이용하여 루트 프로젝트를 제외한 서브 모듈들에 동일한 설정을 적용할 수 있었습니다. 하지만 만약에 루트 프로젝트를 포함한 서브 모듈들에도 동일한 설정을 적용하기 위해서는 어떻게 해야 할까요?&lt;br /&gt;위 문제를 해결하기 위해서 allprojects 설정을 사용할 수 있습니다. allprojects 설정을 사용하면 루트 프로젝트 포함 서브 모듈들에도 동일한 설정을 적용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트 프로젝트의 build.gradle 파일에 다음과 같이 allprojects 설정을 사용하여 동일한 설정을 적용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;1226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxm0BL/dJMcacn2CvU/6plci4IKjeYZdVn7sgGef0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxm0BL/dJMcacn2CvU/6plci4IKjeYZdVn7sgGef0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxm0BL/dJMcacn2CvU/6plci4IKjeYZdVn7sgGef0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbxm0BL%2FdJMcacn2CvU%2F6plci4IKjeYZdVn7sgGef0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1064&quot; height=&quot;1226&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;1226&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;subprojects 설정에서 등록했었던 hello 테스크를 allprojects 설정으로 이동시킵니다. 이렇게 함으로써 루트 프로젝트 자체의 hello 테스크를 수행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Io3bu/dJMcacn2Cwc/0wm6z9fnrVMSC30PZbew3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Io3bu/dJMcacn2Cwc/0wm6z9fnrVMSC30PZbew3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Io3bu/dJMcacn2Cwc/0wm6z9fnrVMSC30PZbew3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIo3bu%2FdJMcacn2Cwc%2F0wm6z9fnrVMSC30PZbew3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;762&quot; height=&quot;318&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트 프로젝트의 hello 테스크 실행&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;./gradlew hello&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과를 보면 루트 프로젝트의 hello 실행만이 아닌 서브 모듈들의 hello도 실행한 것을 볼수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;822&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLlAPk/dJMcabimRmh/ktMPL0yOosHRvEd7CpVLIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLlAPk/dJMcabimRmh/ktMPL0yOosHRvEd7CpVLIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLlAPk/dJMcabimRmh/ktMPL0yOosHRvEd7CpVLIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLlAPk%2FdJMcabimRmh%2FktMPL0yOosHRvEd7CpVLIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1174&quot; height=&quot;822&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;822&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[!NOTE] java toolchain 설정을 이용하여 JDK 버전 명시&lt;br /&gt;java toolchain 설정을 이용하여 실행하는 Gradle의 JDK 버전을 명세적으로 설정할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XU0Fj/dJMcacO6yUb/GFDhzwOBiVqnuYBcPxHIG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XU0Fj/dJMcacO6yUb/GFDhzwOBiVqnuYBcPxHIG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XU0Fj/dJMcacO6yUb/GFDhzwOBiVqnuYBcPxHIG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXU0Fj%2FdJMcacO6yUb%2FGFDhzwOBiVqnuYBcPxHIG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;912&quot; height=&quot;496&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;common 모듈 생성하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브 모듈들간에 공통적으로 사용하는 의존성 설정이 존재한다면 common과 같은 이름의 모듈을 생성하여 sub-project-1, sub-project-2과 같은 서브 모듈들에서 설정을 의존할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;common 모듈 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;1348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kWuqQ/dJMcai9zU0u/QXKkxsEYwmo3RGd0TNYtZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kWuqQ/dJMcai9zU0u/QXKkxsEYwmo3RGd0TNYtZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kWuqQ/dJMcai9zU0u/QXKkxsEYwmo3RGd0TNYtZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkWuqQ%2FdJMcai9zU0u%2FQXKkxsEYwmo3RGd0TNYtZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1114&quot; height=&quot;1348&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;1348&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패키지 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v4Fux/dJMcaf58q1k/WPhVZBNwlr56AJBqHLkPxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v4Fux/dJMcaf58q1k/WPhVZBNwlr56AJBqHLkPxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v4Fux/dJMcaf58q1k/WPhVZBNwlr56AJBqHLkPxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv4Fux%2FdJMcaf58q1k%2FWPhVZBNwlr56AJBqHLkPxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;890&quot; height=&quot;494&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;494&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CommonEntity 클래스 생성&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@AllArgsConstructor  
public class CommonEntity {  
    private final String id;  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build.gradle 설정 추가&lt;br /&gt;common 모듈을 별도의 jar 파일로 생성할 수 있도록 다음 설정을 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;jar.enabled = true&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 설정을 true로 설정하면 모듈을 jar 파일로 생성할 수 있습니다.&lt;/li&gt;
&lt;li&gt;이 설정을 하는 이유는 프로젝트를 다른 곳에서 라이브러리로 사용해야할 때 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모듈간 의존성 추가하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 내의 서브 모듈이 다른 모듈의 클래스를 참조하기 위해서는 모듈간의 의존성을 추가하여 참조할 수 있습니다. 예를 들어 sub-project-1 모듈이 common 모듈의 CommonEntity 클래스를 참조하기 위해서 모듈간 의존성을 추가해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sub-project-1 모듈에서 common 모듈에 대한 모듈간 의존성 추가, build.gradle&lt;/p&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;dependencies {  
    implementation project(':common')  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sub-project-1, ModuleOneUser 클래스에서 common 모듈에 있는 CommonEntity 클래스를 참조합니다.&lt;br /&gt;다음 코드를 보면 import문을 이용해서 common 모듈에 있는 CommonEntity를 가져오는 것을 볼수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYZqEB/dJMcagYg4md/vbIx0yh1J4mkt9Po1Tu0K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYZqEB/dJMcagYg4md/vbIx0yh1J4mkt9Po1Tu0K0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYZqEB/dJMcagYg4md/vbIx0yh1J4mkt9Po1Tu0K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYZqEB%2FdJMcagYg4md%2FvbIx0yh1J4mkt9Po1Tu0K0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;922&quot; height=&quot;630&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모듈간에 의존성들을 추가 방법 정리&lt;/b&gt;&lt;br /&gt;다음 그림을 보면 moudle-2 모듈에서는 moudle-1을 의존하도록 설정되어 있습니다. 이렇게 함으로써 moudle-2의 프로덕션 코드에서 module-1 모듈에 존재하는 코드를 import하여 가져올 수 있습니다.&lt;br /&gt;단, moudle-1 모듈에 존재하는 'commons-io:commons-io:2.8.0' 의존성은 module-2에서 import하여 가져올수 없습니다. 이는 해당 의존성 설정이 &lt;b&gt;implementation&lt;/b&gt;으로 설정되어 있기 때문에 전이 의존성이 발생하지 않습니다.&lt;br /&gt;하지만 api 의존성으로 설정되어 있는 'junit:junit:4.2' 의존성 같은 경우에는 api로 설정되어 있기 때문에 전이 의존성이 발생하여 module-2 모듈에서 참조가 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;337&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dszDog/dJMcac9mRHP/IzGfaHwWKODNNlHWTnohak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dszDog/dJMcac9mRHP/IzGfaHwWKODNNlHWTnohak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dszDog/dJMcac9mRHP/IzGfaHwWKODNNlHWTnohak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdszDog%2FdJMcac9mRHP%2FIzGfaHwWKODNNlHWTnohak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;337&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;337&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;moudle-2 모듈에서는 commons-io 라이브러리에 접근 불가능&lt;/li&gt;
&lt;li&gt;단, module-2 모듈에서는 junit 라이브러리는 접근 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;common 모듈에 guava 의존성을 추가하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;common 모듈에 guava 의존성을 추가하여 common 모듈을 의존하고 있는 sub-project-1 모듈에서도 guava 라이브러리 클래스를 사용할 수 있도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트 프로젝트에 있는 guava 의존성 설정을 common 모듈로 이동시킵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bD8o3U/dJMcaaX3PdQ/Cxy7rMr0kq4L0SOFccmOb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bD8o3U/dJMcaaX3PdQ/Cxy7rMr0kq4L0SOFccmOb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bD8o3U/dJMcaaX3PdQ/Cxy7rMr0kq4L0SOFccmOb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbD8o3U%2FdJMcaaX3PdQ%2FCxy7rMr0kq4L0SOFccmOb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1102&quot; height=&quot;328&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;common 모듈에서 guava 라이브러리의 클래스를 사용하여 객체를 생성해봅니다. 다음 코드를 보면 common 모듈의 CommonEntity 클래스에서 guava 라이브러리 클래스인 ImmutableMap 클래스를 참조하는 것을 볼수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;651&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eDrYQY/dJMcabQcmT4/d1PEbnj47ilGjKjt2UdhK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eDrYQY/dJMcabQcmT4/d1PEbnj47ilGjKjt2UdhK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eDrYQY/dJMcabQcmT4/d1PEbnj47ilGjKjt2UdhK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeDrYQY%2FdJMcabQcmT4%2Fd1PEbnj47ilGjKjt2UdhK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;651&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;651&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 이번에는 common 모듈을 참조하고 있는 sub-project-1 모듈을 대상으로 guava 라이브러리 클래스인 ImmutableMap 객체를 생성할 수 있는지 확인해봅니다. 다음 화면을 보면 sub-project-1 모듈에서 ImmutableMap 클래스를 참조할 수 없는 것을 볼수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX9zcl/dJMcahXb2TT/u0Yklzb7sIRuxKjqk72kaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX9zcl/dJMcahXb2TT/u0Yklzb7sIRuxKjqk72kaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX9zcl/dJMcahXb2TT/u0Yklzb7sIRuxKjqk72kaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX9zcl%2FdJMcahXb2TT%2Fu0Yklzb7sIRuxKjqk72kaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;450&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sub-project-1 모듈에서 common 모듈의 guava 의존성을 가져오지 못하는 이유는 &lt;b&gt;guava 의존성이 implementation 의존성으로 설정되었기 때문&lt;/b&gt;입니다. implementation 의존성으로 설정되면 &lt;b&gt;전이 의존성&lt;/b&gt;이 발생하지 않아서 해당 모듈을 참조하는 다른 모듈에서 implementation 으로 설정한 의존성 라이브러리를 참조할 수 없습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgdrJt/dJMb99LCcS5/hyvYow1YU4Ot1D1cA96wCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgdrJt/dJMb99LCcS5/hyvYow1YU4Ot1D1cA96wCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgdrJt/dJMb99LCcS5/hyvYow1YU4Ot1D1cA96wCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgdrJt%2FdJMb99LCcS5%2FhyvYow1YU4Ot1D1cA96wCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;984&quot; height=&quot;290&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 문제를 해결하기 위해서는 implementation 의존성 설정이 아닌 &lt;b&gt;api 의존성 설정&lt;/b&gt;으로 해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;api 의존성 설정을 사용하기 위해서는 java-library 플러그인을 추가해야 합니다.&lt;/li&gt;
&lt;li&gt;해당 실습에서는 루트 프로젝트의 subprojects 설정에 java-library 플러그인을 적용한 상태입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bilaSj/dJMcagKJ1aU/Vsd5rzOV32XtmIhMgJmnm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bilaSj/dJMcagKJ1aU/Vsd5rzOV32XtmIhMgJmnm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bilaSj/dJMcagKJ1aU/Vsd5rzOV32XtmIhMgJmnm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbilaSj%2FdJMcagKJ1aU%2FVsd5rzOV32XtmIhMgJmnm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;858&quot; height=&quot;328&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;api 의존성 설정으로 변경후 Gradle을 리로드한 다음에 sub-project-1 모듈의 ModuleOneUser 클래스를 보면 ImmutableMap 클래스를 참조할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cK7o8w/dJMcagKJ1a2/BtTds1BIOlMZw2K9NSIVMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cK7o8w/dJMcagKJ1a2/BtTds1BIOlMZw2K9NSIVMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cK7o8w/dJMcagKJ1a2/BtTds1BIOlMZw2K9NSIVMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcK7o8w%2FdJMcagKJ1a2%2FBtTds1BIOlMZw2K9NSIVMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;456&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;456&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 Gradle Build하여 빌드가 되는지 테스트해봅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0xTyX/dJMcaacGsOt/hE63RWxJl6KRe2cpJ03Ny1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0xTyX/dJMcaacGsOt/hE63RWxJl6KRe2cpJ03Ny1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0xTyX/dJMcaacGsOt/hE63RWxJl6KRe2cpJ03Ny1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0xTyX%2FdJMcaacGsOt%2FhE63RWxJl6KRe2cpJ03Ny1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;374&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;외부 파일을 이용한 의존성 관리하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 의존성 설정은 루트 프로젝트의 의존성 설정입니다. 그중에서 lombok 라이브러리(1.18.22)를 compileOnly, annotationProcessor 의존성 설정한 것을 볼수 있습니다. 해당 설정에서 &quot;org.projectlombok:1.18.22&quot; 라이브러리 및 버전 설정을 2곳에 각각에 설정하였습니다. 만약에 버전이 변경된다면 2곳 모두를 수정해야 할 것입니다. 즉, 코드 중복의 문제점이 발생할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nuDBK/dJMcafyiyUQ/T27YX8ISg7KFnvoetuXZM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nuDBK/dJMcafyiyUQ/T27YX8ISg7KFnvoetuXZM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nuDBK/dJMcafyiyUQ/T27YX8ISg7KFnvoetuXZM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnuDBK%2FdJMcafyiyUQ%2FT27YX8ISg7KFnvoetuXZM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1258&quot; height=&quot;594&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 코드 중복의 문제점을 해결하기 위해서 외부의 gradle 파일을 이용하여 의존성을 관리할 수 있습니다. 별도의 gradle 파일을 생성하기 위해서 다음과 같이 루트 프로젝트에 &lt;code&gt;dependencies.gradle&lt;/code&gt; 파일을 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5vtFg/dJMcabbA48J/OxcC39zAMciI3xN7SkkZZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5vtFg/dJMcabbA48J/OxcC39zAMciI3xN7SkkZZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5vtFg/dJMcabbA48J/OxcC39zAMciI3xN7SkkZZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5vtFg%2FdJMcabbA48J%2FOxcC39zAMciI3xN7SkkZZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;612&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트 프로젝트에 있는 모든 의존성을 dependencis.gradle 파일로 이동시키고 ext 설정을 이용해서 다음과 같이 설정합니다. 또한 common 모듈에 있는 guava 의존성 또한 가져와 설정하도록 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TzdPq/dJMcaaqdTS6/OhSfaAhjch9uHogvpFaRK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TzdPq/dJMcaaqdTS6/OhSfaAhjch9uHogvpFaRK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TzdPq/dJMcaaqdTS6/OhSfaAhjch9uHogvpFaRK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTzdPq%2FdJMcaaqdTS6%2FOhSfaAhjch9uHogvpFaRK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;418&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트 프로젝트의 build.gradle에서 &lt;code&gt;dependencis.gradle&lt;/code&gt; 파일을 참조하여 의존성 설정을 적용합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbX7c8/dJMcaiBJ4iH/RlxiDA6yOovipSgkQ56QmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbX7c8/dJMcaiBJ4iH/RlxiDA6yOovipSgkQ56QmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbX7c8/dJMcaiBJ4iH/RlxiDA6yOovipSgkQ56QmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbX7c8%2FdJMcaiBJ4iH%2FRlxiDA6yOovipSgkQ56QmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;990&quot; height=&quot;130&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;130&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caEMR8/dJMcaiBJ4iO/fGw9nryTgk2TE8kuiv6YVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caEMR8/dJMcaiBJ4iO/fGw9nryTgk2TE8kuiv6YVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caEMR8/dJMcaiBJ4iO/fGw9nryTgk2TE8kuiv6YVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaEMR8%2FdJMcaiBJ4iO%2FfGw9nryTgk2TE8kuiv6YVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;848&quot; height=&quot;404&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;404&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 설정한 다음에 build 하여 설정이 적용되는지 확인해봅니다. 실행 결과를 보면 정상적으로 빌드된 것을 볼수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clMTkM/dJMcaaDKHyU/pG8W64yk2JnKTkz0FET4kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clMTkM/dJMcaaDKHyU/pG8W64yk2JnKTkz0FET4kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clMTkM/dJMcaaDKHyU/pG8W64yk2JnKTkz0FET4kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclMTkM%2FdJMcaaDKHyU%2FpG8W64yk2JnKTkz0FET4kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;408&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sub-project-1 모듈에서 junitJupiterApi가 정상적으로 동작하는지 테스트 클래스를 생성하고 실행해봅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1054&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ety3EQ/dJMcadNXNkI/Nlf9mTkU0kZJsGwMz32sq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ety3EQ/dJMcadNXNkI/Nlf9mTkU0kZJsGwMz32sq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ety3EQ/dJMcadNXNkI/Nlf9mTkU0kZJsGwMz32sq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fety3EQ%2FdJMcadNXNkI%2FNlf9mTkU0kZJsGwMz32sq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1054&quot; height=&quot;424&quot; data-origin-width=&quot;1054&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과를 보면 정상적으로 테스트가 성공한 것을 볼수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;./gradlew test&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biAwPk/dJMcadUJx16/7RAK64kTF2utm2ZSXKoSV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biAwPk/dJMcadUJx16/7RAK64kTF2utm2ZSXKoSV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biAwPk/dJMcadUJx16/7RAK64kTF2utm2ZSXKoSV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiAwPk%2FdJMcadUJx16%2F7RAK64kTF2utm2ZSXKoSV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;420&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;References&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/pSKY3-K9_qc?si=1V8G94YUMscrVGkk&quot;&gt;https://youtu.be/pSKY3-K9_qc?si=1V8G94YUMscrVGkk&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>김용환</author>
      <guid isPermaLink="true">https://yonghwankim-dev.tistory.com/629</guid>
      <comments>https://yonghwankim-dev.tistory.com/629#entry629comment</comments>
      <pubDate>Mon, 1 Dec 2025 15:10:04 +0900</pubDate>
    </item>
    <item>
      <title>Gradle 빌드 수행시 JDK 버전과 Lombok 라이브러리 버전 충돌로 인한 빌드 실패 문제 해결</title>
      <link>https://yonghwankim-dev.tistory.com/628</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IntelliJ IDEA가 아닌 Gradle 기반으로 빌드 명령어를 수행 시 QueryDSL의 QClass를 찾을 수 없다고 에러를 출력하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;257&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kIYMM/dJMcacVRw9M/zFDKiqghM4v7goDY5YcTf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kIYMM/dJMcacVRw9M/zFDKiqghM4v7goDY5YcTf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kIYMM/dJMcacVRw9M/zFDKiqghM4v7goDY5YcTf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkIYMM%2FdJMcacVRw9M%2FzFDKiqghM4v7goDY5YcTf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;257&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;257&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 IntelliJ IDEA를 이용하여 빌드를 수행할 때는 정상적으로 QueryDSL의 QClass를 생성하고 빌드 완료된 것을 볼수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/creUy7/dJMcaiBJk63/tmLrKKgUGXN00ABKfuolc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/creUy7/dJMcaiBJk63/tmLrKKgUGXN00ABKfuolc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/creUy7/dJMcaiBJk63/tmLrKKgUGXN00ABKfuolc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcreUy7%2FdJMcaiBJk63%2FtmLrKKgUGXN00ABKfuolc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1136&quot; height=&quot;682&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Project Version 정보&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Boot 3.1.1&lt;/li&gt;
&lt;li&gt;Java : temurin-17(open jdk 17)&lt;/li&gt;
&lt;li&gt;QueryDSL : 5.0.0&lt;/li&gt;
&lt;li&gt;Lombok : 1.18.22&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gradle QueryDSL Task 설정&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;968&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJb2NA/dJMcai9zacW/jHypKRKBQn5P0zOVKVTf50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJb2NA/dJMcai9zacW/jHypKRKBQn5P0zOVKVTf50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJb2NA/dJMcai9zacW/jHypKRKBQn5P0zOVKVTf50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJb2NA%2FdJMcai9zacW%2FjHypKRKBQn5P0zOVKVTf50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1020&quot; height=&quot;968&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;968&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IntelliJ IDE를 이용하여 빌드를 수행할 때는 temurin-17(open jdk17) 버전으로 빌드를 수행했기 때문에 정상적으로 완료된 것입니다. 다음 화면을 보면 Project의 SDK가 temurin-17로 설정된 것을 볼수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xQNnM/dJMcagRuALk/GYkKTkdTnUCWUhrx4tdKIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xQNnM/dJMcagRuALk/GYkKTkdTnUCWUhrx4tdKIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xQNnM/dJMcagRuALk/GYkKTkdTnUCWUhrx4tdKIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxQNnM%2FdJMcagRuALk%2FGYkKTkdTnUCWUhrx4tdKIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;380&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 터미널에서 Gradle 명령어를 이용하여 build를 수행하는 경우에는 다른 JDK 버전으로 수행된 것을 볼수 있었습니다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;./gradlew build --info&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 실행 결과를 보면 Gradle build 명령어 실행시 정작 실행되는 JDK 버전은 temurin-21(open jdk21)인것을 볼수 있습니다. 제 로컬 개발 환경 경우에는 mac os 운영체제에 temurin-17과 temurin-21이 같이 설치되어 있는 상태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSc2pq/dJMcagqqoRW/VlCLKsNWfKTKjkzfkihgkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSc2pq/dJMcagqqoRW/VlCLKsNWfKTKjkzfkihgkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSc2pq/dJMcagqqoRW/VlCLKsNWfKTKjkzfkihgkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSc2pq%2FdJMcagqqoRW%2FVlCLKsNWfKTKjkzfkihgkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;314&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;open jdk21 버전으로 컴파일 할때의 문제점은 Lombok 라이브러리와 버전 충돌이 발생한다는 점입니다.&lt;/b&gt; 현재 Lombok 라이브러리 버전은 1.18.22 버전으로 JDK17 버전에 맞추어져 있는 최소 버전입니다. jdk21으로 컴파일시 Lombok 1.18.22 버전을 사용하면 컴파일러의 내부 API 충돌이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 화면을 보면 1.18.30 버전부터 JDK21이 지원된다는 것을 알 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xiN9j/dJMcagcTRg7/G1ujQJfJ7dA3CXyukAL530/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xiN9j/dJMcagcTRg7/G1ujQJfJ7dA3CXyukAL530/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xiN9j/dJMcagcTRg7/G1ujQJfJ7dA3CXyukAL530/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxiN9j%2FdJMcagcTRg7%2FG1ujQJfJ7dA3CXyukAL530%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;254&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;254&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에 1.18.22 버전부터 JDK17이 지원됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;293&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qpK4F/dJMcajgkMtf/zjyDypbhMkjUikpFK5FlTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qpK4F/dJMcajgkMtf/zjyDypbhMkjUikpFK5FlTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qpK4F/dJMcajgkMtf/zjyDypbhMkjUikpFK5FlTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqpK4F%2FdJMcajgkMtf%2FzjyDypbhMkjUikpFK5FlTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;293&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;293&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 Gradle build 명령어 수행시 컴파일에 실행한 이유는 QueryDSL 문제 이전에 Gradle이 JDK21 버전으로 수행되고 Lombok 라이브러리와 버전 충돌이 발생하면서 Annotation Processor 작업이 실패한 것입니다. 그로인해서 QClass가 생성되지 않고 에러 결과에서는 QClass가 존재하지 않는다고 실패한 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dZXMcT/dJMcagjEUx3/2KFBoxZswAgY9qYoT0mWsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dZXMcT/dJMcagjEUx3/2KFBoxZswAgY9qYoT0mWsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dZXMcT/dJMcagjEUx3/2KFBoxZswAgY9qYoT0mWsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdZXMcT%2FdJMcagjEUx3%2F2KFBoxZswAgY9qYoT0mWsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;286&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 컴파일 문제를 해결하기 위해서는 Gradle이 실행하는 JDK 버전을 JDK17 버전으로 변경합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build.gradle 파일 수정&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;toolchain&lt;/code&gt;을 이용하여 JDK 17버전을 사용하도록 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;java {  
    sourceCompatibility = '17'  

    toolchain {  
        languageVersion = JavaLanguageVersion.of(17)  
    }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gradle Build 테스트&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;./gradlew clean build --info&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gradle 로그를 보면 JDK17을 사용하는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;361&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/47VBA/dJMcabWW7gz/cXG1aIDkSTenT2JvBXN5v1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/47VBA/dJMcabWW7gz/cXG1aIDkSTenT2JvBXN5v1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/47VBA/dJMcabWW7gz/cXG1aIDkSTenT2JvBXN5v1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F47VBA%2FdJMcabWW7gz%2FcXG1aIDkSTenT2JvBXN5v1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;361&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;361&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 실행 결과를 보면 Gradle build가 성공적으로 수행된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4FoNP/dJMcabJqIOh/XzW7Lkd1iNXLlU38ImjnJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4FoNP/dJMcabJqIOh/XzW7Lkd1iNXLlU38ImjnJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4FoNP/dJMcabJqIOh/XzW7Lkd1iNXLlU38ImjnJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4FoNP%2FdJMcabJqIOh%2FXzW7Lkd1iNXLlU38ImjnJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;286&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IntelliJ IDEA에서는 JDK17 버전으로 수행하여 빌드가 성공했지만 Gradle build 테스크를 이용하여 빌드하는 경우에는 QClass를 찾을 수 없다고 에러가 발생하였습니다.&lt;/li&gt;
&lt;li&gt;빌드 실패의 원인은 &lt;b&gt;Gradle 실행시에는 JDK21 버전으로 수행하여 Lombok 라이브러리와 버전 충돌이 발생하여 빌드가 실패&lt;/b&gt;하였습니다.&lt;/li&gt;
&lt;li&gt;빌드 실패 문제를 해결하기 위해서 Gradle 설정에 toolchain을 추가하여 JDK17를 사용하도록 설정하여 문제를 해결하였습니다.&lt;/li&gt;
&lt;li&gt;QueryDSL 관련하여 빌드가 실패한줄 알았지만 실제 문제는 JDK 버전과 Lombok 라이브러리간 버전 충돌 때문에 발생한 문제였습니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>문제해결</category>
      <author>김용환</author>
      <guid isPermaLink="true">https://yonghwankim-dev.tistory.com/628</guid>
      <comments>https://yonghwankim-dev.tistory.com/628#entry628comment</comments>
      <pubDate>Sat, 29 Nov 2025 15:08:23 +0900</pubDate>
    </item>
  </channel>
</rss>