<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://lmiz2.github.io//feed.xml" rel="self" type="application/atom+xml" /><link href="https://lmiz2.github.io//" rel="alternate" type="text/html" /><updated>2025-08-02T12:29:16+00:00</updated><id>https://lmiz2.github.io//feed.xml</id><title type="html">dokku__’s ⭐Favorites</title><subtitle>즐겨찾기를 옮겨두는 곳</subtitle><author><name>dokku__</name><email>shs3146@gmail.com</email></author><entry><title type="html">[Reactive Programing] Reactive와 Spring Webflux - 1</title><link href="https://lmiz2.github.io//reactive/2215/" rel="alternate" type="text/html" title="[Reactive Programing] Reactive와 Spring Webflux - 1" /><published>2021-05-02T13:15:00+00:00</published><updated>2021-05-02T13:15:00+00:00</updated><id>https://lmiz2.github.io//reactive/2215</id><content type="html" xml:base="https://lmiz2.github.io//reactive/2215/"><![CDATA[<p>오랜만에 블로그에 글을 쓴다.</p>

<p>그동안 정신없이 이것저것 시도하면서 혼자 공부를 하던 중, 이것만은 글로 기록해 둬야겠다는 생각이 들어서다.</p>

<p>나는 보통 어떤 기술에 대해 궁금한게 생기면, 그 기술을 직접 사용하여 무언가를 구현해보며 익히는 편이다.(일단 부딪히고 보는 타입)</p>

<p>이번에도 Kotlin과 Spring Webflux에 관심이 생겨 직접 부딪혀 보며 개발을 하려는데..</p>

<p>너무 욕심이 과했던 탓일까? 이해하기 힘든 부분들이 많아 이대로는 진행이 안되겠다 싶어, 예외적으로  선 공부후 개발에 착수하기로 마음먹었다.</p>

<p>이 글은 그 기록의 시작점이다.</p>

<h1 id="reactive">Reactive?</h1>

<p>전통적인(그리고 현재 대부분의) Spring 애플리케이션은 차단식 동기 프로그래밍으로 구현되어 있으며, 그 근본에는 절차지향(명령형) 프로그래밍이 있다.</p>

<p>말그대로 코드 한줄한줄 따라 내려가면서 명령어가 수행되는 구조인것이다.</p>

<p>이러한 절차지향 프로그래밍 기법은 일반적으로 “차단(Blocking)”을 유발하며, 이 차단은 리소스의 낭비를 유발한다.</p>

<p>웹 어플리케이션의 Requests(threads)와 Response를 공장 노동자와 생산되는 물건에 비유해보겠다.</p>

<h3 id="노동자와-결과물">노동자와 결과물</h3>

<p>어떤 공장에서는 전화를 통해 물건의 생산 주문을 실시간으로 받고, 주문을 받는 즉시 공장 노동자는 생산 작업에 착수하며,</p>

<p>생산 작업이 완료되면 물건을 주문자에게 보낸다. 여러개의 물건 생산 주문을 받으면 여러명의 노동자가 동시에 생산 작업에 착수한다.</p>

<p>그림으로 보면 다음과 같다.</p>

<p><img src="/assets/img/202105022243.png" alt="" /></p>

<p>이를 Spring Web Application에 비유하면, 노동자는 Thread로 볼 수 있고, 도토리는 Response로 볼 수 있다. 아래의 그림과 같이 말이다.</p>

<p><img src="/assets/img/202105041814.png" alt="" /></p>

<p>http Request가 오게되면, 서블릿 컨테이너(Tomcat 등)는 Thread Excutor를 통해 각 요청을 Thread로 처리한다. 이 때, 요청마다 별도로 정보를 저장하는 공간은 ThreadLocal이라는 객체를 통해 처리한다.</p>

<p>만약 노동자가 물건 생산 중 물건생산에 필요한 부품이 부족하여, 외부에서 부품을 사와야 하는 상황이 생긴다면? 당연히 노동자는 부품을 구할때까지 만들던 물건을 완성할 수 없고, 아무일도 하지 못할것이다.</p>

<p>보통의 웹 어플리케이션의 경우 JDBC를 이용하여 데이터베이스에서 정보를 얻어와 요청에 대한 처리를 진행하는 경우가 많다. 이 때 JDBC를 통해 DB에 데이터를 요청하게 되면 Thread는 DB가 데이터를 돌려주기 전까지 아무일도 하지 못하고 기다려야 하는데, 이를 Blocking 상태에 있다고 부른다.</p>

<p><img src="/assets/img/202105051629.png" alt="" /></p>

<h3 id="reactive-programing">Reactive Programing</h3>

<p>이처럼 Thread가 Blocking 상태에 빠져 아무것도 하지못하면, 컴퓨팅 자원이 낭비되는 셈이다.(노동자가 쉬는것처럼)</p>

<p>이를 방지할수 있는 방법중 하나가 바로 <strong>Reactive Programing</strong>이며, 반응형 프로그래밍이라고도 불리는데, Spring에서는 <strong>Spring Webflux</strong>가 이 Reactive Programing의 개념을 이용하여 개발되었다. <del>(Non-blocking과 Reactive-streams, Project Reactor에 대해서도 더 다루면 좋겠지만.. 여기서 다 다루기엔 양이 너무 많아서 PASS)</del></p>

<p>위에서 봤던 공장의 상황을 Reactive Programing 방식으로 다시 바꿔보면 아래와 같다.</p>

<p><img src="/assets/img/202105051709.png" alt="" /></p>

<p>주문이 들어오면, 각자 다른 역할을 맡은 노동자들이 컨베이어 벨트 위에 서고, 물건은 컨베이어 벨트 위를 지나면서 점점 완성되어 가는것이다.</p>

<p>이렇게 되면 노동자들은 쉬지 않고 자신의 일을 하는데에만 집중할 수 있으며, 여기서는 <strong>컨베이어 벨트가 Thread</strong>로, <strong>노동자는</strong> Thread가 아닌 <strong>Operator</strong>로 치환하여 생각할 수 있다. 여기서 Operator는 Spring Webflux에서 제공하는 메소드(함수)들로, 데이터를 가공하기 위해 쓰이는 함수들이다.(ex. flatMap, map 등)</p>

<h3 id="장점">장점</h3>

<p>이렇게 Spring Webflux를 이용하여 Reactive하게 시스템을 구성해 놓게 되면, 설령 DB와 같은 외부 IO접근을 통하더라도 Thread(컨베이어벨트)는 멈추지 않고 제 할일을 할 수 있다.(물론 JDBC는 blocking 방식의 Connector이기 때문에, 대신 R2DBC라는 Connector를 사용해야한다.)</p>

<p>결과적으로 상황에 따라서 기존 시스템보다 작은양의 컴퓨팅 자원으로도 비슷하거나 그 이상의 성능을 낼 수 있으며, 이는 거시적으로 볼 때 에너지 절약과도 이어진다. 이러한 점으로 볼 때 개인적인 생각으로는 미래에는 필수적으로 가져가야한 아키텍쳐중 하나가 아닐까 생각한다.</p>

<h3 id="단점">단점</h3>

<p>물론 장점만 있는것은 아닌데, 이렇게 선언적으로 프로그래밍을 하게 되면 무엇보다 데이터의 흐름을 직접 추적하기 어렵고, 이는 디버깅이 어려워지는 효과를 낳는다. 게다가 이 선언적 프로그래밍은 명령지향 프로그래밍보다 <strong>러닝커브가 높다</strong>. 협업이 필수인 개발자의 세계에서는 이 러닝커브가 큰 걸림돌이 될 수 있을것 같다.</p>

<h3 id="마치며">마치며</h3>

<p>사실 코드적인 부분들이 이해가 안가서 블로깅을 하려고 의도했는데.. 간단히 정리하려던 개념 부분이 생각보다 복잡하게 되었다…이것도 간략화 한거지만 ㅠㅠ</p>

<p>다음화부터는 본격적으로 코드를 곁들여가면서 기록을 시작해봐야겠다.</p>]]></content><author><name>dokku__</name><email>shs3146@gmail.com</email></author><category term="Reactive" /><category term="Reactive" /><category term="Spring Webflux" /><summary type="html"><![CDATA[오랜만에 블로그에 글을 쓴다.]]></summary></entry><entry><title type="html">[Spring R2DBC] Junit Test시 @Transactional 사용이 불가능한 문제 해결하기</title><link href="https://lmiz2.github.io//reactive/1843/" rel="alternate" type="text/html" title="[Spring R2DBC] Junit Test시 @Transactional 사용이 불가능한 문제 해결하기" /><published>2021-05-02T13:15:00+00:00</published><updated>2021-05-02T13:15:00+00:00</updated><id>https://lmiz2.github.io//reactive/1843</id><content type="html" xml:base="https://lmiz2.github.io//reactive/1843/"><![CDATA[<p>Spring Webflux로 Geo Query를 수행하는 서비스를 만들다, Test를 작성하게 되었다.</p>

<p>응..? 근데 왜자꾸 에러가 나지..? 뭐야대체..</p>

<p>하면서 삽질했던 부분을 기록해 둔다.</p>

<p>다음 테스트를 보자</p>

<p>[PostgresqlGeoRepositoryTest.kt]</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@SpringBootTest</span>
<span class="nd">@Transactional</span>
<span class="k">internal</span> <span class="kd">class</span> <span class="nc">PostgresqlGeoRepositoryTest</span> <span class="nd">@Autowired</span> <span class="k">constructor</span><span class="p">(</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">postgresqlGeoRepository</span><span class="p">:</span> <span class="nc">GeoRepository</span><span class="p">&lt;</span><span class="nc">MemberCurrentPoint</span><span class="p">,</span> <span class="nc">Long</span><span class="p">&gt;</span>
<span class="p">){</span>

    <span class="nd">@Test</span>
    <span class="k">fun</span> <span class="nf">noGeoInfo_findAroundPlaces_noResults</span><span class="p">(){</span>

        <span class="kd">var</span> <span class="py">l</span> <span class="p">=</span> <span class="p">-</span><span class="mi">1</span>
        <span class="nc">StepVerifier</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span>
            <span class="nc">Flux</span><span class="p">.</span><span class="nf">fromIterable</span><span class="p">(</span>
                <span class="nf">getLngLatPoints</span><span class="p">().</span><span class="nf">map</span> <span class="p">{</span>
                    <span class="n">l</span><span class="p">++</span>
                    <span class="nc">MemberCurrentPoint</span><span class="p">(</span><span class="n">l</span><span class="p">.</span><span class="nf">toLong</span><span class="p">(),</span><span class="n">it</span><span class="p">.</span><span class="n">first</span><span class="p">,</span> <span class="n">it</span><span class="p">.</span><span class="n">second</span><span class="p">)</span>
                <span class="p">}</span>
            <span class="p">).</span><span class="nf">flatMap</span> <span class="p">{</span>
                <span class="n">postgresqlGeoRepository</span><span class="p">.</span><span class="nf">save</span><span class="p">(</span><span class="n">it</span><span class="p">)</span>
            <span class="p">}.</span><span class="nf">switchMap</span><span class="p">{</span>
                <span class="n">postgresqlGeoRepository</span><span class="p">.</span><span class="nf">findAroundOf</span><span class="p">(</span>
                    <span class="nc">MemberCurrentPoint</span><span class="p">(</span>
                        <span class="mi">0</span><span class="p">,</span>
                        <span class="nf">getLngLatPoints</span><span class="p">()[</span><span class="mi">0</span><span class="p">].</span><span class="n">first</span><span class="p">,</span>
                        <span class="nf">getLngLatPoints</span><span class="p">()[</span><span class="mi">0</span><span class="p">].</span><span class="n">second</span>
                    <span class="p">)</span>
                <span class="p">)</span>
            <span class="p">}.</span><span class="nf">map</span> <span class="p">{</span>
                <span class="n">it</span><span class="p">.</span><span class="n">memberId</span>
            <span class="p">}</span>
        <span class="p">).</span><span class="nf">verifyComplete</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>

</code></pre></div></div>

<p>위 테스트를 실행하면 exception이 발생한다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>java.lang.IllegalStateException: Failed to retrieve PlatformTransactionManager for @Transactional test:...
</code></pre></div></div>

<p>찾아보니, paluch가 R2DBC의 경우 테스트에서 @Transactional을 통해 테스트 트랜잭션을 관리할 방법이 없음을 언급하고있다.</p>

<p>(https://github.com/spring-projects/spring-framework/issues/24226)</p>

<p>그리고 해당 이슈에서 힌트를 얻어, 불편하지만 매 단위 테스트마다 Transaction Operator를 달아 Rollback을 하는 수밖에 없었다.</p>

<p>Transaction Operator를 쉽게 관리하기 위해 Transaction Helper라는 클래스를 작성했다.</p>

<p>[TransactionHelper.kt]</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">..</span>

<span class="nd">@Component</span>
<span class="kd">class</span> <span class="nc">TransactionHelper</span> <span class="p">(</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">tx</span><span class="p">:</span> <span class="nc">TransactionalOperator</span>
<span class="p">){</span>
    <span class="k">fun</span> <span class="nf">getTransaction</span><span class="p">():</span> <span class="nc">TransactionalOperator</span> <span class="p">=</span> <span class="n">tx</span>

    <span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="nf">withRollback</span><span class="p">(</span><span class="n">publisher</span><span class="p">:</span> <span class="nc">Mono</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;)</span> <span class="p">:</span> <span class="nc">Mono</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">tx</span><span class="p">.</span><span class="nf">execute</span> <span class="p">{</span>
            <span class="n">it</span><span class="p">.</span><span class="nf">setRollbackOnly</span><span class="p">()</span>
            <span class="n">publisher</span>
        <span class="p">}.</span><span class="nf">next</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="nf">withRollback</span><span class="p">(</span><span class="n">publisher</span><span class="p">:</span> <span class="nc">Flux</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;)</span> <span class="p">:</span> <span class="nc">Flux</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">tx</span><span class="p">.</span><span class="nf">execute</span> <span class="p">{</span>
            <span class="n">it</span><span class="p">.</span><span class="nf">setRollbackOnly</span><span class="p">()</span>
            <span class="n">publisher</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="nf">executeMonoWithRollback</span><span class="p">(</span><span class="n">function</span><span class="p">:</span> <span class="p">()</span> <span class="p">-&gt;</span> <span class="nc">Mono</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;)</span> <span class="p">:</span> <span class="nc">Mono</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;{</span>
        <span class="k">return</span> <span class="n">tx</span><span class="p">.</span><span class="nf">execute</span> <span class="p">{</span>
            <span class="n">it</span><span class="p">.</span><span class="nf">setRollbackOnly</span><span class="p">()</span>
            <span class="n">function</span><span class="p">.</span><span class="nf">invoke</span><span class="p">()</span>
        <span class="p">}.</span><span class="nf">next</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="nf">executeFluxWithRollback</span><span class="p">(</span><span class="n">function</span><span class="p">:</span> <span class="p">()</span> <span class="p">-&gt;</span> <span class="nc">Flux</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;)</span> <span class="p">:</span> <span class="nc">Flux</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;{</span>
        <span class="k">return</span> <span class="n">tx</span><span class="p">.</span><span class="nf">execute</span> <span class="p">{</span>
            <span class="n">it</span><span class="p">.</span><span class="nf">setRollbackOnly</span><span class="p">()</span>
            <span class="n">function</span><span class="p">.</span><span class="nf">invoke</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>

<span class="p">}</span>
</code></pre></div></div>

<p>그런 다음 테스트 Operator들을 롤백구간으로 감싸준다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
    <span class="nd">@Test</span>
    <span class="k">fun</span> <span class="nf">noGeoInfo_findAroundPlaces_noResults</span><span class="p">(){</span>

        <span class="kd">var</span> <span class="py">l</span> <span class="p">=</span> <span class="p">-</span><span class="mi">1</span>
        <span class="nc">StepVerifier</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span>
            <span class="n">transactionHelper</span><span class="p">.</span><span class="nf">executeFluxWithRollback</span> <span class="p">{</span>
                <span class="nc">Flux</span><span class="p">.</span><span class="nf">fromIterable</span><span class="p">(</span>
                    <span class="nf">getLngLatPoints</span><span class="p">().</span><span class="nf">map</span> <span class="p">{</span>
                        <span class="n">l</span><span class="p">++</span>
                        <span class="nc">MemberCurrentPoint</span><span class="p">(</span><span class="n">l</span><span class="p">.</span><span class="nf">toLong</span><span class="p">(),</span> <span class="n">it</span><span class="p">.</span><span class="n">first</span><span class="p">,</span> <span class="n">it</span><span class="p">.</span><span class="n">second</span><span class="p">)</span>
                    <span class="p">}</span>
                <span class="p">).</span><span class="nf">flatMap</span> <span class="p">{</span>
                    <span class="n">postgresqlGeoRepository</span><span class="p">.</span><span class="nf">save</span><span class="p">(</span><span class="n">it</span><span class="p">)</span>
                <span class="p">}.</span><span class="nf">switchMap</span> <span class="p">{</span>
                    <span class="n">postgresqlGeoRepository</span><span class="p">.</span><span class="nf">findAroundOf</span><span class="p">(</span>
                        <span class="nc">MemberCurrentPoint</span><span class="p">(</span>
                            <span class="mi">0</span><span class="p">,</span>
                            <span class="nf">getLngLatPoints</span><span class="p">()[</span><span class="mi">0</span><span class="p">].</span><span class="n">first</span><span class="p">,</span>
                            <span class="nf">getLngLatPoints</span><span class="p">()[</span><span class="mi">0</span><span class="p">].</span><span class="n">second</span>
                        <span class="p">)</span>
                    <span class="p">)</span>
                <span class="p">}.</span><span class="nf">map</span> <span class="p">{</span>
                    <span class="n">it</span><span class="p">.</span><span class="n">memberId</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">).</span><span class="nf">verifyComplete</span><span class="p">()</span>
    <span class="p">}</span>
    
</code></pre></div></div>

<p>이후 테스트를 진행하면 롤백이 잘 되는것을 볼 수 있다.</p>

<h3 id="이유">이유?</h3>

<p>테스트가 아닌 Spring Context에서는 @Transactional이 잘 작동하지만, 왜 테스트에서는 사용이 불가능할까?</p>

<p>그 이유는 크게 두가지로 볼 수. 있다.</p>

<p>첫번째로, Reactive Opertator가 작동하는 방식이다.</p>

<p>기존 차단형 프로그래밍 방식에서 트랜잭션의 상태는 ThreadLocal을 이용해 관리되었다. 하지만 Reactive 프로그래밍에서는 비-차단 이며 단일 스레드로 동작해야하기에 ThreadLocal이라는 클래스는 사용할 수가 없다. (엄밀히 말하면 ThreadLocal의 존재 이유가 없다.)</p>

<p>이 문제를 해결하기 위해 R2DBC에서는 Context라는 객체를 이용하여 각 Flow에 대한 상태를 관리하는데,</p>

<p>문제는 현재 JUNIT에서 이러한 Context에 대한 지원이 완벽하지 않다는것이다.</p>

<p>두번째로, Assertion이다. Test Framework에서 트랜잭션 상태는 Assertion과 강한 커플링을 가진다. (Assertion 성공여부에 따라 트랜잭션이 롤백되거나 되지 않을수 있음을 생각해보면..) 하지만 이 Assertion은 차단을 유발하며, Reactive Flow중간에 이러한 차단 코드를 삽입할 수 없기 때문에, StepVerifier를 이용하여 테스트를 진행해야만 한다. 고로, 트랜잭션 상태를 관리할 수 없다.</p>

<p>..끝</p>

<p>(내가 잘못알고있다면 댓글 주세요..)</p>]]></content><author><name>dokku__</name><email>shs3146@gmail.com</email></author><category term="Reactive" /><category term="Reactive" /><category term="Spring Webflux" /><category term="R2DBC" /><summary type="html"><![CDATA[Spring Webflux로 Geo Query를 수행하는 서비스를 만들다, Test를 작성하게 되었다.]]></summary></entry><entry><title type="html">[Non-Blocking] Block Hound와 함께 삽질을!</title><link href="https://lmiz2.github.io//reactive/2234/" rel="alternate" type="text/html" title="[Non-Blocking] Block Hound와 함께 삽질을!" /><published>2021-05-02T13:15:00+00:00</published><updated>2021-05-02T13:15:00+00:00</updated><id>https://lmiz2.github.io//reactive/2234</id><content type="html" xml:base="https://lmiz2.github.io//reactive/2234/"><![CDATA[<h2 id="blocking-call--webflux">Blocking Call &amp; Webflux</h2>

<p>Spring Webflux로 만든 애플리케이션을 Production에 배포하기 전엔, 의도치 않은 Blocking Call이 있는지 확인해야한다.</p>

<p>Webflux는 Non-Blocking을 기반으로 동작하기 때문에, 소량의 스레드로 모든 요청을 처리한다. (코어당 하나의 스레드라고 함.)</p>

<p>이 때문에 어느 한 곳 이라도 차단 호출이 존재한다면, 안그래도 적은 수의 가용스레드가 더 적어지게 되고,</p>

<p>나아가 적은 수의 스레드만 사용하는 차단-프레임워크(Spring MVC web같은)처럼 동작하게 된다.</p>

<p>Spring MVC가 보통 약 천개의 스레드로 동작하는 것에 비해, 차단 호출 웹플럭스는 기껏해야 1~8개 정도의 스레드만 사용하므로,</p>

<p>성능차이는 어림잡아 백배 이상 날 것이라 추측 해 볼수 있다.</p>

<p>비 차단 프레임 워크에서 차단 호출을 막는것은 이렇게나 중요한데, 이 차단 호출을 감지하는데에 도움을 주는것이 바로 BlockHound이다.</p>

<h2 id="blockhound">BlockHound</h2>

<p>필자는 BlockHound를 그냥 붙이기만 하면 알아서 차단 호출을 감지해 Error를 발생시켜 줄테니, 그 BlockingError만 확인하고 고쳐주면 될 것이라고 쉽게 생각했다.</p>

<p>그리고 보기좋게 다시 삽질을 또 하게 되었다. 이 글은 삽질을 기억하기 위한 몸부림이다.(공병 아니랄까봐..)</p>

<h3 id="blockhound-설정">BlockHound 설정</h3>

<p>Gradle에 BlockHound 의존성을 추가한다.</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">...</span>
<span class="n">dependencies</span> <span class="o">{</span>
    <span class="n">implementation</span> <span class="s1">'io.projectreactor.tools:blockhound:1.0.6.RELEASE'</span>
  <span class="o">...</span>
  <span class="o">}</span>
</code></pre></div></div>

<p>main메소드에 BlockHound.install() 을 작성한다. 참고로 BlockHound는 실행중 바이트코드 수정이 가능하게 설정된 자바 에이전트이기에, Application을 띄운 다음에 install()을 호출해도 상관 없다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">SpringApplication</span><span class="o">.</span><span class="na">run</span><span class="o">(</span><span class="nc">MyApplication</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">args</span><span class="o">);</span>
    <span class="nc">BlockHound</span><span class="o">.</span><span class="na">install</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="차단-호출-확인하기처리하기">차단 호출 확인하기&amp;처리하기</h3>

<p>이제 어디서 차단-호출이 일어나는지 확인할 일만 남았는데, 막상 Application 이곳저곳을 실행해보면 오만상 BlockingOperationError가 발생한다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>reactor.blockhound.BlockingOperationError: Blocking call! java.io.RandomAccessFile#readBytes
	at java.base/java.io.RandomAccessFile.readBytes(RandomAccessFile.java) ~[na:na]
	...
</code></pre></div></div>

<p>저 <code class="language-plaintext highlighter-rouge">java.io.RandomAccessFile#readBytes</code> 는 BlockHound로 점검을 시작하면 가장 친해질 녀석인데, 처음에 이녀석이 나오면 당황스럽다. 내가 호출한게 아닌데 왜 나오는거지? 하며 말이다.</p>

<p>사실 완전한 Non-Blocking은 존재하기 어렵다. Call stack을 타고타고 깊이 가면 항상 끝에는 read()나 unsafe같은 같은 차단 호출과 관련된 녀석들이 기다리고 있다.</p>

<p>정말 중요한건 차단을 피하는게 아니라, <strong>차단으로 인해 CPU 사이클이 낭비되지 않도록 하는것이다.</strong> <a href="https://blog.frankel.ch/blockhound-how-it-works/">(https://blog.frankel.ch/blockhound-how-it-works/)</a></p>

<p>이 때문에 Non-Blocking을 지원하는 많은 Spring Modules가 BlockHound Integration에 자신들의 차단 호출이 어느 정도까지 허용될지 통합해 놓는다.</p>

<p>미처 통합되지 못한 차단 호출은, 직접 BlockHound.install(BlockHoundIntegration) 을 통해 허용할 수 있다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">BlockHound</span><span class="o">.</span><span class="na">install</span><span class="o">(</span><span class="n">b</span> <span class="o">-&gt;</span> <span class="n">b</span><span class="o">.</span><span class="na">allowBlockingCallsInside</span><span class="o">(</span><span class="nc">InflaterInputStream</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">(),</span> <span class="s">"read"</span><span class="o">)</span> <span class="c1">// OpenApi Blocking Call 허용</span>
        <span class="o">.</span><span class="na">allowBlockingCallsInside</span><span class="o">(</span><span class="nc">OpenAPIService</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">(),</span> <span class="s">"build"</span><span class="o">));</span> <span class="c1">// OpenApi Blocking Call 허용</span>
</code></pre></div></div>

<p>이렇게 하면 직접 차단 호출을 허용할 수 있지만, 너무 남발 했다가는 진짜 막아야 할 차단 호출을 놓칠수도 있으니 주의해서 사용해야 한다.</p>

<p>필자는 이렇게 해서 Jwt를 생성하는 부분과 RandomUUID 생성하는 부분에 차단이 있음을 알아냈고, Mono.create통해 Operation을 비차단 식으로 나누거나 Schedulers를 통해 다른 스레드에서 연산 되도록 수정하였다. 예를 들면,</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">UUID</span><span class="o">.</span><span class="na">randomUUID</span><span class="o">().</span><span class="na">toString</span><span class="o">();</span> <span class="c1">// BlockingOperationError!</span>

<span class="nc">Mono</span><span class="o">.&lt;</span><span class="nc">String</span><span class="o">&gt;</span><span class="n">create</span><span class="o">(</span><span class="n">sink</span> <span class="o">-&gt;</span> <span class="o">{</span>  <span class="c1">// OK</span>
  <span class="nc">String</span> <span class="n">uuid</span> <span class="o">=</span> <span class="no">UUID</span><span class="o">.</span><span class="na">randomUUID</span><span class="o">().</span><span class="na">toString</span><span class="o">();</span>
  <span class="n">sink</span><span class="o">.</span><span class="na">success</span><span class="o">(</span><span class="n">uuid</span><span class="o">);</span>
<span class="o">});</span>

<span class="nc">Mono</span><span class="o">.&lt;</span><span class="nc">String</span><span class="o">&gt;</span><span class="n">create</span><span class="o">(</span><span class="n">sink</span> <span class="o">-&gt;</span> <span class="o">{</span>  <span class="c1">// OK</span>
  <span class="nc">String</span> <span class="n">enc</span> <span class="o">=</span> <span class="n">doEncrypt</span><span class="o">();</span> <span class="c1">// 무거운 암호화 로직</span>
  <span class="n">sink</span><span class="o">.</span><span class="na">success</span><span class="o">(</span><span class="n">enc</span><span class="o">);</span>
<span class="o">}).</span><span class="na">subscribeOn</span><span class="o">(</span><span class="nc">Schedulers</span><span class="o">.</span><span class="na">boundedElastic</span><span class="o">());</span>
</code></pre></div></div>

<p>이런식으로 말이다.</p>

<h3 id="완성된-applicationjava-코드">완성된 Application.java 코드</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="nd">@SpringBootApplication</span>
<span class="nd">@ConfigurationPropertiesScan</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyApiApplication</span> <span class="o">{</span>

    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">ARG_ENABLE_BLOCKHOUND</span> <span class="o">=</span> <span class="s">"ENABLE_BLOCKHOUND"</span><span class="o">;</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Locale</span><span class="o">.</span><span class="na">setDefault</span><span class="o">(</span><span class="nc">Locale</span><span class="o">.</span><span class="na">KOREA</span><span class="o">);</span>
        <span class="nc">SpringApplication</span><span class="o">.</span><span class="na">run</span><span class="o">(</span><span class="nc">MyApiApplication</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">args</span><span class="o">);</span>
        <span class="n">enableBlockHoundIfArgsExists</span><span class="o">(</span><span class="n">args</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">enableBlockHoundIfArgsExists</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(</span><span class="nc">Objects</span><span class="o">.</span><span class="na">isNull</span><span class="o">(</span><span class="n">args</span><span class="o">))</span> <span class="o">{</span>
            <span class="k">return</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">arg</span> <span class="o">:</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">if</span> <span class="o">(</span><span class="nc">Objects</span><span class="o">.</span><span class="na">nonNull</span><span class="o">(</span><span class="n">arg</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="n">arg</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="no">ARG_ENABLE_BLOCKHOUND</span><span class="o">))</span> <span class="o">{</span>
                <span class="n">enableBlockHound</span><span class="o">();</span>
                <span class="k">return</span><span class="o">;</span>
            <span class="o">}</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">enableBlockHound</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"BlockHound Enabled."</span><span class="o">);</span>
        <span class="nc">BlockHound</span><span class="o">.</span><span class="na">install</span><span class="o">(</span><span class="n">b</span> <span class="o">-&gt;</span> <span class="n">b</span><span class="o">.</span><span class="na">allowBlockingCallsInside</span><span class="o">(</span><span class="nc">InflaterInputStream</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">(),</span> <span class="s">"read"</span><span class="o">)</span>
                <span class="o">.</span><span class="na">allowBlockingCallsInside</span><span class="o">(</span><span class="nc">OpenAPIService</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">(),</span> <span class="s">"build"</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>끝.</p>]]></content><author><name>dokku__</name><email>shs3146@gmail.com</email></author><category term="Reactive" /><category term="Reactive" /><category term="Spring Webflux" /><category term="Reactor" /><category term="Block Hound" /><category term="Non-Blocking" /><category term="java" /><summary type="html"><![CDATA[Blocking Call &amp; Webflux]]></summary></entry><entry><title type="html">[Java] 효율적인 equals와 hashCode</title><link href="https://lmiz2.github.io//java/1535/" rel="alternate" type="text/html" title="[Java] 효율적인 equals와 hashCode" /><published>2020-11-07T06:35:00+00:00</published><updated>2020-11-07T06:35:00+00:00</updated><id>https://lmiz2.github.io//java/1535</id><content type="html" xml:base="https://lmiz2.github.io//java/1535/"><![CDATA[<p>java에서는 모든 클래스가 Object 클래스를 상속한다.</p>

<p>그리고 이 Object 클래스에는 여러가지 기본적인 메소드들이 있는데, 오늘은 그 중 equals 와 hashCode 메소드에 대하여 공부했던 점에 대해 정리해 보고자 한다.</p>

<h2 id="object-클래스">Object 클래스</h2>

<p>자바에서 모든 클래스는 Object클래스를 상속하며, 따로 extends 키워드를 사용하여 상속하지 않더라도 컴파일러가 자동으로 Object를 상속하도록 만든다.</p>

<p>이는 모든 클래스가 공통적으로 가져야 하는 메소드들을 지원하기 위함이다.</p>

<p>Object 클래스에는 다음과 같은 11개의 메소드들이 정의되어 있다.</p>

<table>
  <thead>
    <tr>
      <th>Modifier and Type</th>
      <th>Method and Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">protected Object</code></td>
      <td><code class="language-plaintext highlighter-rouge">clone()</code> Creates and returns a copy of this object.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">boolean</code></td>
      <td><code class="language-plaintext highlighter-rouge">equals(Object obj)</code> Indicates whether some other object is “equal to” this one.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">protected void</code></td>
      <td><code class="language-plaintext highlighter-rouge">finalize()</code> Called by the garbage collector on an object when garbage collection determines that there are no more references to the object.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Class&lt;?&gt;</code></td>
      <td><code class="language-plaintext highlighter-rouge">getClass()</code> Returns the runtime class of this <code class="language-plaintext highlighter-rouge">Object</code>.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">int</code></td>
      <td><code class="language-plaintext highlighter-rouge">hashCode()</code> Returns a hash code value for the object.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">void</code></td>
      <td><code class="language-plaintext highlighter-rouge">notify()</code> Wakes up a single thread that is waiting on this object’s monitor.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">void</code></td>
      <td><code class="language-plaintext highlighter-rouge">notifyAll()</code> Wakes up all threads that are waiting on this object’s monitor.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">String</code></td>
      <td><code class="language-plaintext highlighter-rouge">toString()</code> Returns a string representation of the object.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">void</code></td>
      <td><code class="language-plaintext highlighter-rouge">wait()</code> Causes the current thread to wait until another thread invokes the  <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--"><code class="language-plaintext highlighter-rouge">notify()</code></a> method or the  <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notifyAll--"><code class="language-plaintext highlighter-rouge">notifyAll()</code></a> method for this object.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">void</code></td>
      <td><code class="language-plaintext highlighter-rouge">wait(long timeout)</code> Causes the current thread to wait until either another thread invokes the  <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--"><code class="language-plaintext highlighter-rouge">notify()</code></a> method or the  <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notifyAll--"><code class="language-plaintext highlighter-rouge">notifyAll()</code></a> method for this object, or a specified amount of time has elapsed.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">void</code></td>
      <td><code class="language-plaintext highlighter-rouge">wait(long timeout,    int nanos)</code> Causes the current thread to wait until another thread invokes the  <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--"><code class="language-plaintext highlighter-rouge">notify()</code></a> method or the  <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notifyAll--"><code class="language-plaintext highlighter-rouge">notifyAll()</code></a> method for this object, or some other thread interrupts the current thread, or a certain amount of real time has elapsed.</td>
    </tr>
  </tbody>
</table>

<p>이들 메소드들은 OOP의 효율적인 지원을 위해 꼭 필요한 메소드들이며, 많은 중요한 메소드가 있지만, 오늘은 equals와 hashCode에 대해서 정리해본다.</p>

<h2 id="equals-메소드">equals 메소드</h2>

<p>equals는 <u>논리적 동치성</u>을 검사하기 위해 존재한다고 볼수 있다. 논리적 동치성이란 그야말로 그 <strong>객체가 가지는 값</strong>이 같은가를 의미한다.</p>

<p>우리가 잘 알고 있는 java.lang.String 클래스를 예로 들어 보자.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java">		<span class="nc">String</span> <span class="n">str1</span> <span class="o">=</span> <span class="s">"123"</span><span class="o">;</span>
		<span class="nc">String</span> <span class="n">str2</span> <span class="o">=</span> <span class="s">"456"</span><span class="o">;</span>
		<span class="nc">String</span> <span class="n">str3</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="s">"123"</span><span class="o">);</span>
		
		<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">str1</span> <span class="o">==</span> <span class="n">str2</span><span class="o">);</span> <span class="c1">// false</span>
		<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">str1</span> <span class="o">==</span> <span class="n">str3</span><span class="o">);</span> <span class="c1">// false</span></code></pre></figure>

<p>위 코드의 출력은 모두 <strong>false</strong>이다.</p>

<p>이는 == 연산자가 단순히 객체의 메모리 주소를 비교하기 때문이며, <u>변수가 가리키는 인스턴스가 다르다면 false를 반환한다</u>.</p>

<p>결국 <em>str1</em>과 <em>str3</em> 은 같은 문자열을 가진 것처럼 보이지만, <em>str3</em>이 <em>new</em> 키워드를 통해 새로운 메모리 공간을 할당받아, Heap memory에 인스턴화 되어 존재하게 되었으므로, <u>str1과 str3는 다른 인스턴스가 된다</u>.</p>

<p>우리는 두 String이 똑같은 문자열 값을 가지는지 알고싶다. 그렇다면 어떻게 비교해야 할까?</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java">		<span class="nc">String</span> <span class="n">str1</span> <span class="o">=</span> <span class="s">"123"</span><span class="o">;</span>
		<span class="nc">String</span> <span class="n">str2</span> <span class="o">=</span> <span class="s">"456"</span><span class="o">;</span>
		<span class="nc">String</span> <span class="n">str3</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="s">"123"</span><span class="o">);</span>
		
		<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">str1</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">str2</span><span class="o">));</span> <span class="c1">// false</span>
		<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">str1</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">str3</span><span class="o">));</span> <span class="c1">// true</span></code></pre></figure>

<p>위와 같이 equals 메소드를 이용하여 비교하게 되면, 두 String 객체가 같은 문자열값을 가지는지 비교할 수 있다.</p>

<p>이는 java.lang.String 클래스에서 <u>equals 메소드를 아래와 같이 Override 하고 있기에 가능한 일이다.</u></p>

<figure class="highlight"><pre><code class="language-java" data-lang="java">    <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">equals</span><span class="o">(</span><span class="nc">Object</span> <span class="n">anObject</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(</span><span class="k">this</span> <span class="o">==</span> <span class="n">anObject</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// 같은 인스턴스인경우 true 리턴</span>
            <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">anObject</span> <span class="k">instanceof</span> <span class="nc">String</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// String 타입의 인스턴스인 경우</span>
            <span class="nc">String</span> <span class="n">anotherString</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span><span class="n">anObject</span><span class="o">;</span>
            <span class="kt">int</span> <span class="n">n</span> <span class="o">=</span> <span class="n">value</span><span class="o">.</span><span class="na">length</span><span class="o">;</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">n</span> <span class="o">==</span> <span class="n">anotherString</span><span class="o">.</span><span class="na">value</span><span class="o">.</span><span class="na">length</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// 문자열 값의 길이가 같은 경우, 값 비교 시작</span>
                <span class="kt">char</span> <span class="n">v1</span><span class="o">[]</span> <span class="o">=</span> <span class="n">value</span><span class="o">;</span>
                <span class="kt">char</span> <span class="n">v2</span><span class="o">[]</span> <span class="o">=</span> <span class="n">anotherString</span><span class="o">.</span><span class="na">value</span><span class="o">;</span>
                <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
                <span class="k">while</span> <span class="o">(</span><span class="n">n</span><span class="o">--</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
                    <span class="k">if</span> <span class="o">(</span><span class="n">v1</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">!=</span> <span class="n">v2</span><span class="o">[</span><span class="n">i</span><span class="o">])</span>
                        <span class="k">return</span> <span class="kc">false</span><span class="o">;</span> <span class="c1">// 중간에 다른 값이 있는 경우 false 리턴</span>
                    <span class="n">i</span><span class="o">++;</span>
                <span class="o">}</span>
                <span class="k">return</span> <span class="kc">true</span><span class="o">;</span> <span class="c1">// 모든 값이 같음, true 리턴</span>
            <span class="o">}</span>
        <span class="o">}</span>
        <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
    <span class="o">}</span></code></pre></figure>

<p>이 equals 메소드는 전략적으로 잘 짜여진 equals이다.</p>

<p>이펙티브 자바에서는 효율적인 equals로 재정의하기 위해서는 다음의 순서대로 구현하는것이 좋다고 언급되어 있다.</p>

<blockquote>
  <ol>
    <li>== 연산자를 이용하여 입력이 자기 자신의 참조인지 확인한다.</li>
    <li>instanceof 연산자로 입력이 올바른 타입인지 확인한다. 이때 null 체크까지 해결되므로, null인지 체크할 필요가 없어진다.</li>
    <li>입력을 명시적 형변환 한다.</li>
    <li>입력 객체와 자기 자신의 대응되는 논리적 핵심 필드들이 모두 일치하는지 하나하나 검사한다. 이 때, 하나라도 다르다면 false를 리턴하고, 모두 같다면 true를 리턴한다.</li>
  </ol>
</blockquote>

<p>이를 위의 String.equals와 비교해보면 완벽히 순서가 일치하는것을 알 수 있다.</p>

<p>추가적으로 덧붙이자면, <u>float 와 double을 제외한 Primitive type</u>은 == 연산자를 이용하여 비교하고, <u>Reference 타입 필드는 각각의 equals 메소드를 이용</u>하여 비교한다.</p>

<p><u>float와 double을 비교할 때 == 을 사용하지 않는 이유</u>는, float과 double은 부동소수점을 표현하는데에 사용되기도 하기 때문이다.</p>

<p><strong>부동소수점 표현 방식은 오차를 가질 수 있으므로, 정확한 값 비교 연산자인 ==를 사용하는것은 바람직하지 않다.</strong></p>

<p>float와 double을 비교해야만 하는 경우에는 <strong>Float.compare</strong> 메소드와 <strong>Double.compare</strong>메소드를 이용하도록 하자.</p>

<p>따라서, equals를 구현할 때에는 논리적인 값을 표현하는 필드에는 어떤 것 이 있는지 생각해보고, 해당되는 필드들에 대하여 위와 같은 순서로 구현하는 것이 좋다.</p>

<h2 id="hashcode-메소드">hashCode 메소드</h2>

<p>자바 Object 명세에서는 equals 메소드를 재정의 하였다면 hashCode 메소드 또한 재 정의 하도록 규정하고 있다.</p>

<p>hashCode 메소드는 자바의 Collection 프레임 워크에 존재하는 HashMap과 HashSet등을 지원하기 위하여 만들어 졌으며, 이 메소드는 해당 객체의 hash값을 반환해야한다.</p>

<p>또한, 이 객체와 다른 한 객체의 equals 결과가 true라면, <strong>그 둘의 hashCode 값은 반드시 같아야 한다.</strong></p>

<p>(반대로, hashCode 값이 같다고 해서 equals의 결과가 true일 필요는 없다.)</p>

<p>hashCode의 용도를 간단하게 축약해서 말하자면, “<strong>다양한 데이터 타입을 배열에 넣기 위한 인덱스</strong>” 정도로 말 할수 있을 것 같다.</p>

<p>다음과 같이, char 타입의 알파벳들을 배열에 저장하는 상황을 떠올려 보자.</p>

<p><img src="/assets/img/2020-11-15-222950.png" alt="" /></p>

<p>만약 ‘E’가 어디에 있는지 찾으려면, 단순 비교 검색으로는 <u>5번의 비교연산이 발생한다</u>.</p>

<p>하지만 hashCode를 이용하여 배열에 저장한다면 더 쉽게 찾을 수 있을 것이다.</p>

<p><img src="/assets/img/2020-11-15-223231.png" alt="" /></p>

<p>위와 같이 각 알파벳의 hashCode를 얻어, 배열의 길이로 나눈 나머지를 인덱스로 정한다면, 각 알파벳이 어디있는지는 hashCode만 계산해내면 알 수 있게 된다.</p>

<p>물론 위의 예는 간략한 예이고, 실제로는 <u>해시 충돌</u> 등 여러 문제를 해결해야 안정적이고 빠른 해시맵(셋)의 성능을 기대할 수 있다.</p>

<p>해시 충돌은 서로 다른 객체에서 동일한 해시코드를 얻는 현상을 일컫으며, 좋은 해시 함수일수록 해시 충돌이 적다.</p>

<p>해시 충돌을 위의 예제에서 찾아보자. 알파벳 ‘E’의 hashCode는 5였다. 하지만 알파벳 ‘M’의 hashCode 또한 5(<em>77 % 8 = 5</em>)이다. 이 둘은 다른 값을 가진 객체지만, 같은 해시코드 값을 가지게 되었다. 이것이 바로 해시 충돌이다.</p>

<p>하지만 안타깝게도 <u>완벽한 hashCode 함수를 만드는것은 불가능에 가깝다</u>. 다만 다행히도 hashCode 함수 또한 효율적으로 동작하도록 작성하는 방법이 있는데, 그것은 다음의 단계를 거친다.</p>

<blockquote>
  <ol>
    <li>
      <p>int 변수 result를 선언하고, 값 c로 초기화 한다. 이 때 c는 단계 2.1로 계산한 값이다.</p>
    </li>
    <li>
      <p>해당 객체의 논리적 핵심 필드 f들 각각에 대하여 다음을 수행한다.</p>

      <ol>
        <li>
          <p>해당 필드의 해시코드 c를 계산한다. 각 필드의 타입에 따라서 아래 사항을 따른다.</p>

          <ol>
            <li>
              <p>Primitive 타입 필드라면, <u>Type.hashCode(f)</u>를 수행한다. int로 예를 들면, Integer.hashCode()이다.</p>
            </li>
            <li>
              <p>참조타입 필드면서 이 클래스의 equals 메서드 안에서 이 필드의 equals를 사용한다면, 이 필드의 hashCode메서드 또한 이 단계에서 사용한다. 필드의 값이 null이면 0을 사용한다.</p>

              <p>만약 참조타입 필드면서 <u>이 클래스의 equals 메서드 안에서 이 필드의 equals를 사용하지 않는다면, 해당 필드는 계산에서 제외 해야만 한다.</u></p>
            </li>
            <li>
              <p>필드가 배열이라면,  각각을 별도 필드처럼 다루며, 즉 <u>최상위 1번 단계부터 끝단계</u> 까지를 <u>배열의 원소마다</u> 계산해낸다. 배열의 모든 원소가 논리적 핵심 필드라면 Arrays.hashCode를 사용한다.</p>
            </li>
          </ol>
        </li>
        <li>
          <p>2.1번 단계에서 계산한 해시코드 c를 이용하여 result 변수를 다음과 같이 계산한다.</p>
        </li>
      </ol>

      <p>result = 31 * result + c;</p>
    </li>
    <li>
      <p>result를 리턴한다.</p>
    </li>
  </ol>
</blockquote>

<p>위와 같은 과정을 통해 이상적인 hashCode 메소드를 구현할 수 있다.</p>

<p>String.hashCode도 위와 비슷한 과정을 따른다.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java">    <span class="kd">public</span> <span class="kt">int</span> <span class="nf">hashCode</span><span class="o">()</span> <span class="o">{</span>
        <span class="kt">int</span> <span class="n">h</span> <span class="o">=</span> <span class="n">hash</span><span class="o">;</span> <span class="c1">// h를 기본 hash값으로 초기화. 기본 hash변수는 0으로 초기화 되어 있다.</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">h</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">value</span><span class="o">.</span><span class="na">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
            <span class="kt">char</span> <span class="n">val</span><span class="o">[]</span> <span class="o">=</span> <span class="n">value</span><span class="o">;</span> <span class="c1">// value는 String클래스의 전역변수로, 실제 문자열 값을 char의 배열형태로 가지고 있다.</span>
    
            <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">value</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
                <span class="n">h</span> <span class="o">=</span> <span class="mi">31</span> <span class="o">*</span> <span class="n">h</span> <span class="o">+</span> <span class="n">val</span><span class="o">[</span><span class="n">i</span><span class="o">];</span> <span class="c1">//각각 char타입 변수에 대해 해시코드를 계산한다.</span>
            <span class="o">}</span>
            <span class="n">hash</span> <span class="o">=</span> <span class="n">h</span><span class="o">;</span> <span class="c1">// 기본 hash변수를 초기화 해준다.</span>
        <span class="o">}</span>
        <span class="k">return</span> <span class="n">h</span><span class="o">;</span> <span class="c1">// h(result)를 리턴한다.</span>
    <span class="o">}</span></code></pre></figure>

<p>hash의 경우 해시 충돌을 극복하는 방법 등 공부해야 할 게 많지만.. 시간이 된다면 그에 대해서도 좀 더 파서 글을 작성해야겠다.</p>

<p>이상 equals와 hashCode 메소드를 효율적으로 작성하는 방법에 대해 알아 보았다.</p>

<p>이 모든 구현이 귀찮거나 어렵다면, AutoValue 라이브러리로 생성하는것도 좋은 방법이다. AutoValue라이브러리는 위에서 진행한 모든 과정을 자동으로 대신해준다.</p>

<p><em><del>AutoValue에 대해서는 다음 기회에…</del></em></p>]]></content><author><name>dokku__</name><email>shs3146@gmail.com</email></author><category term="Java" /><category term="Java" /><category term="equals" /><category term="hashCode" /><summary type="html"><![CDATA[java에서는 모든 클래스가 Object 클래스를 상속한다.]]></summary></entry><entry><title type="html">[Network]내부망을 가능하게 만드는 NAT와 NAPT의 원리</title><link href="https://lmiz2.github.io//network/1850/" rel="alternate" type="text/html" title="[Network]내부망을 가능하게 만드는 NAT와 NAPT의 원리" /><published>2020-10-24T09:51:00+00:00</published><updated>2020-10-24T09:51:00+00:00</updated><id>https://lmiz2.github.io//network/1850</id><content type="html" xml:base="https://lmiz2.github.io//network/1850/"><![CDATA[<p>회사에서 일을 하다 보면, 내부망, 외부망 이라는 단어가 자주 들린다.</p>

<p>어렴풋이 회사 인트라넷을 구분하는 단어겠거니, 하고 자세한 개념은 모른채 넘어갔었는데,</p>

<p>문득 하나의 공인 IP로 어떻게 이 많은 컴퓨터들이 인터넷과 연결될까? 하는 궁금증이 생겼고,</p>

<p>그 답이 NAT(NAPT) 에 있다는것을 알게 되었다.</p>

<h1 id="nat-network-address-translation">NAT (Network Address Translation)</h1>

<p>Basic NAT, 즉 원래의 NAT는 1개의 공인IP 주소에 N개의 사설(가상) IP주소를 매핑하는 주소변환 기술의 이름이다.</p>

<p>현재 IPv4의 주소는 전 세계적으로 고갈 상태에 있는데, NAT는 이 고갈되어가는 주소에 대한 효과적인 해결책 중 하나이며, 회사같은 하나의 공동체에서 보안성을 향상시키기 위한 수단으로 활용될수도 있다.</p>

<p>다음과 같이 여러개의 사설망 IP와, 하나의 공인IP를 할당받은 라우터가 있다고 가정해보자.
<img src="/assets/img/2020-10-25-232445.png" alt="" /></p>

<p>(여기에 쓰인 IP주소는 임의의 주소이며, 이해를 돕기위해 사용되었다.)</p>

<p>이러한 상황에서 [사설망 클라이언트 1]이 외부 서버에 IPv4 패킷을 전송하게되면, 라우터는 이 패킷의 출발지 IP를 사설망 IP에서 공인IP주소로 변환한 후, 라우팅 테이블에 맵핑 정보를 기록한다.</p>

<p><img src="/assets/img/2020-10-26-223406.png" alt="" /></p>

<p>이렇게 기록해둔 라우팅 테이블의 정보를 참고하여, 외부 서버(위의 예에서 125.209.222.13)에서 전송되어오는 패킷을 어떤 내부 클라이언트(위의 예에서 10.20.30.1)에게 라우팅 할지 결정하게 된다.</p>

<p>그런데, 만약 둘 이상의 내부 클라이언트가 같은 외부서버에 접근하게 되면 위와 같은 라우팅 테이블로는 어떤 클라이언트에게 패킷을 라우팅 해야 할 지 알 수가 없게 된다.</p>

<p><img src="/assets/img/2020-10-29-222531.png" alt="" /></p>

<p>[<em>응답 패킷을 받아야 하는 클라이언트는 1인가, 3인가?</em>]</p>

<p>이 문제를 해결할 수 있는 대안이 바로 NAPT이다.</p>

<h1 id="napt-network-address-port-translation">NAPT (Network Address Port Translation)</h1>

<p>NAPT(또는 PAT)에서는 NAT에 Port Forwarding이 추가된 형태이다.</p>

<p>즉, [내부 클라이언트 IP , 내부 클라이언트 Port , 받을 Port ] 를 저장한다.</p>

<p>아래 그림을 보자.</p>

<p><img src="/assets/img/2020-10-31-171301.png" alt="" /></p>

<p>위와 같이 라우터는 “받을 Port”를 임의로 지정하여 각 내부망 클라이언트에 맵핑 해 줌으로써,</p>

<p>외부에서 하나의 공인 IP로 접근하는 여러 패킷들을 각 클라이언트에게 전달 해줄 수 있다.</p>

<p>이렇게 Port에 따라 다른 목적지로 패킷을 전송하는 것을 Port Forwarding 이라고 하며,</p>

<p>이 Port Forwarding이 NAT와 NAPT의 차이점이기도 하다.</p>

<p>현업에서는 대체적으로 NAPT와 NAT를 구분하지 않고 NAT라고 칭한다.</p>

<p><em>이상하거나 잘못된 내용에 대한 태클은 달게 받겠습니다!!</em></p>]]></content><author><name>dokku__</name><email>shs3146@gmail.com</email></author><category term="Network" /><category term="Network" /><category term="NAT" /><category term="NAPT" /><summary type="html"><![CDATA[회사에서 일을 하다 보면, 내부망, 외부망 이라는 단어가 자주 들린다.]]></summary></entry><entry><title type="html">[java] java와 maven으로 어디에서나 실행가능한 프로그램(exe) 만들기</title><link href="https://lmiz2.github.io//exp/2120/" rel="alternate" type="text/html" title="[java] java와 maven으로 어디에서나 실행가능한 프로그램(exe) 만들기" /><published>2020-09-28T12:22:02+00:00</published><updated>2020-09-28T12:22:02+00:00</updated><id>https://lmiz2.github.io//exp/2120</id><content type="html" xml:base="https://lmiz2.github.io//exp/2120/"><![CDATA[<p>개인적인 용도의 유틸리티성 프로그램을 만들다 보면, 문득 자바가 그리워졌다.</p>

<p>방대한 라이브러리로 인해 개발 생산성이 향상된다는 것이 그 이유인데,</p>

<p>C와는 달리 자바는 빌드 후 JVM을 이용하여 직접 실행해주어야 하며, 이로 인해 자바를 모르는 사람들은 자바로 만든 유틸리티를 실행하기 쉽지 않다는,</p>

<p><u>유틸리티</u>로써는 매우 치명적인 단점이 있다.</p>

<p>그러던 중, 우연히 jar파일을 exe로 변환하는 방법을 알게 되어 독립적인 프로그램을 만들기 위해 삽질을 해보았고,</p>

<p>exe파일에 나의 어플리케이션 코드와  기타 라이브러리를 번들로 컴파일하여 번거로운 실행으로부터 완전히 자유로운 자바 프로그램을 만들게 되어 그 경험을 공유하고자 한다.</p>

<h1 id="준비하기">준비하기</h1>

<p>우선 필자는 eclipse를 이용하여 maven 으로 jar 파일을 빌드 한 다음, Launch4j라는 툴을 이용하여 jar를 exe파일로 변환하는 식으로 진행하였다.</p>

<p>이를 위해 java와 ecelipse환경 세팅이 필요하며, Launch4j는 <a href="https://sourceforge.net/projects/launch4j/files/launch4j-3/">SorceForge</a>에서 구할 수 있다.</p>

<p>SourceForge에서 Download Latest Version 버튼을 눌러 launch4j 설치 파일(20.09.28 기준 버전 3.12)을 다운받아 실행한 후, 안내에 따라 설치한다.</p>

<p><img src="/assets/img/2020-09-28-221904.png" alt="sourceforge" /></p>

<p>그 다음 자바로 프로그램을 만들기 위해 프로젝트를 생성한 후, 메이븐 프로젝트로 전환해준다.</p>

<p><img src="/assets/img/2020-09-28-221055.png" alt="프로젝트를 메이븐 프로젝트로 변경" /></p>

<p>이렇게 하면 프로젝트 내에 pom.xml 파일이 생기는데, 이는 maven에서 Dependency를 관리하기 위한 파일로, 이는 여기서 다루기엔 너무 큰 주제이니, 기회가 되면 독립적인 주제로 다뤄보겠다.</p>

<p>pom.xml의 <plugins> 하위에  "maven-assembly-plugin" 이라는 플러그인을 추가해 준다.</plugins></p>

<figure class="highlight"><pre><code class="language-xml" data-lang="xml">  <span class="nt">&lt;plugin&gt;</span>  
    <span class="nt">&lt;groupId&gt;</span>org.apache.maven.plugins<span class="nt">&lt;/groupId&gt;</span>  
    <span class="nt">&lt;artifactId&gt;</span>maven-assembly-plugin<span class="nt">&lt;/artifactId&gt;</span>  
    <span class="nt">&lt;version&gt;</span>2.2.1<span class="nt">&lt;/version&gt;</span>  
    <span class="nt">&lt;configuration&gt;</span>  
      <span class="nt">&lt;descriptorRefs&gt;</span>  
        <span class="nt">&lt;descriptorRef&gt;</span>jar-with-dependencies<span class="nt">&lt;/descriptorRef&gt;</span>  
      <span class="nt">&lt;/descriptorRefs&gt;</span>  
      <span class="nt">&lt;archive&gt;</span>
          <span class="nt">&lt;manifest&gt;</span>
              <span class="nt">&lt;mainClass&gt;</span> <span class="c">&lt;!--메인 클래스의 path --&gt;</span> <span class="nt">&lt;/mainClass&gt;</span>
          <span class="nt">&lt;/manifest&gt;</span>
      <span class="nt">&lt;/archive&gt;</span>
    <span class="nt">&lt;/configuration&gt;</span>  
  <span class="nt">&lt;/plugin&gt;</span></code></pre></figure>

<p><a href="http://maven.apache.org/plugins/maven-assembly-plugin/">maven-assembly-plugin</a>은 maven 프로젝트의 결과물을 dependency, module, 기타등등의 파일을 단일 아카이브로 만들게 해주는 플러그인이다.</p>

<p>이를 이용해 우리가 만들 자바 프로젝트를 단일 jar파일로 만들고, 종속 모듈(dependencies)들을 모두 주입하여 하나의 실행파일(Excutable Jar)로 만들어낼것이다.</p>

<p>참고로, maven-assembly-plugin은 jar 뿐만 아니라 다른 형식도 지원한다.</p>

<ul>
  <li>zip</li>
  <li>tar</li>
  <li>tar.gz (or tgz)</li>
  <li>tar.bz2 (or tbz2)</li>
  <li>tar.snappy</li>
  <li>tar.xz (or txz)</li>
  <li>jar</li>
  <li>dir</li>
  <li>war</li>
  <li>and any other format that the ArchiveManager has been configured for</li>
</ul>

<p>이외 자세한 사항은 maven document를 참고하자.</p>

<p>메인 클래스는 말그대로 main메소드가 존재하는 클래스를 칭하며, 패키지경로를 포함하여 기입한다.</p>

<p><img src="/assets/img/2020-09-28-223525.png" alt="프로젝트 구조" /></p>

<p>(프로젝트의 구조가 위와 같고, 메인 메소드가 Application.java에 존재할 때, 메인클래스의 패스는 다음과 같은 형식을 띈다.)</p>

<p><strong>com.convert.prj.Application</strong></p>

<h1 id="make-it">Make It!</h1>

<p>이제 준비가 끝났다. 이제 원하는 프로그램을 java로 구현하고, 빌드할 차례다.</p>

<p>여기서는 Apache POI를 이용하여 엑셀 파일을 생성하는 예제를 만들어보겠다.</p>

<p>우선 POI를 사용하기 위해 pom.xml에 dependency를 추가해준다.</p>

<figure class="highlight"><pre><code class="language-xml" data-lang="xml">..

  <span class="nt">&lt;dependencies&gt;</span>
	  <span class="c">&lt;!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml --&gt;</span>
	<span class="nt">&lt;dependency&gt;</span>
	    <span class="nt">&lt;groupId&gt;</span>org.apache.poi<span class="nt">&lt;/groupId&gt;</span>
	    <span class="nt">&lt;artifactId&gt;</span>poi-ooxml<span class="nt">&lt;/artifactId&gt;</span>
	    <span class="nt">&lt;version&gt;</span>4.1.2<span class="nt">&lt;/version&gt;</span>
	<span class="nt">&lt;/dependency&gt;</span>
	  <span class="c">&lt;!-- https://mvnrepository.com/artifact/org.apache.poi/poi --&gt;</span>
	<span class="nt">&lt;dependency&gt;</span>
	    <span class="nt">&lt;groupId&gt;</span>org.apache.poi<span class="nt">&lt;/groupId&gt;</span>
	    <span class="nt">&lt;artifactId&gt;</span>poi<span class="nt">&lt;/artifactId&gt;</span>
	    <span class="nt">&lt;version&gt;</span>4.1.2<span class="nt">&lt;/version&gt;</span>
	<span class="nt">&lt;/dependency&gt;</span>
  <span class="nt">&lt;/dependencies&gt;</span>

..

<span class="nt">&lt;/project&gt;</span></code></pre></figure>

<p><a href="https://mvnrepository.com/artifact/org.apache.poi/poi">Apache POI</a>는 엑셀파일과 관련된 작업을 하기위해 가장 많이 사용되는 라이브러리이다.</p>

<p>이 라이브러리를 통해 엑셀 파일을 생성/수정하거나, 읽어들여 데이터화 할 수 있다.</p>

<p>우선, 엑셀 파일을 만드는 간단한 class를 작성한다.</p>

<p>ExcelMaker.java</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">package</span> <span class="nn">com.convert.prj</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">java.io.FileOutputStream</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.io.IOException</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">org.apache.poi.xssf.usermodel.XSSFWorkbook</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ExcelMaker</span> <span class="o">{</span>
	

<span class="err">​</span>	<span class="cm">/**
	 * 
	 * @return Not Saved to disk XSSFWorkbook Object 
	 * @throws IOException
	 */</span>
	<span class="kd">public</span> <span class="nc">XSSFWorkbook</span> <span class="nf">getExcelFile</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
		<span class="nc">XSSFWorkbook</span> <span class="n">wb</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">XSSFWorkbook</span><span class="o">();</span>
		<span class="c1">// do something...</span>
		<span class="k">return</span> <span class="n">wb</span><span class="o">;</span>
	<span class="o">}</span>
	
	<span class="cm">/**
	 * 
	 * @param wb XSSFWorkbook Object for save
	 * @param path where you save specified path
	 * @return save has successful then true else return false
	 */</span>
	<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">saveExcelFile</span><span class="o">(</span><span class="nc">XSSFWorkbook</span> <span class="n">wb</span><span class="o">,</span> <span class="nc">String</span> <span class="n">path</span><span class="o">)</span> <span class="o">{</span>
		<span class="k">try</span> <span class="o">(</span><span class="nc">FileOutputStream</span> <span class="n">stream</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FileOutputStream</span><span class="o">(</span><span class="n">path</span><span class="o">))</span> <span class="o">{</span>
			<span class="n">wb</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">stream</span><span class="o">);</span>
			<span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
		<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Throwable</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
			<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
			<span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
		<span class="o">}</span>
	<span class="o">}</span>

<span class="o">}</span></code></pre></figure>

<p>그리고 main에서, standard input으로 엑셀 파일을 생성할 path를 입력받은 후, ExcelMaker를 호출한다.</p>

<p>Application.java</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">package</span> <span class="nn">com.convert.prj</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">java.io.IOException</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Scanner</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Application</span> <span class="o">{</span>

<span class="err">​</span>	<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="err">​</span>		<span class="k">try</span><span class="o">(</span><span class="nc">Scanner</span> <span class="n">sc</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Scanner</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">in</span><span class="o">);){</span>
<span class="err">​</span>			<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"type the path : "</span><span class="o">);</span>
<span class="err">​</span>			<span class="nc">String</span> <span class="n">path</span> <span class="o">=</span> <span class="n">sc</span><span class="o">.</span><span class="na">nextLine</span><span class="o">();</span>
<span class="err">​</span>			<span class="nc">ExcelMaker</span> <span class="n">s</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ExcelMaker</span><span class="o">();</span>
<span class="err">​</span>			<span class="n">s</span><span class="o">.</span><span class="na">saveExcelFile</span><span class="o">(</span><span class="n">s</span><span class="o">.</span><span class="na">getExcelFile</span><span class="o">(),</span> <span class="n">path</span><span class="o">);</span>
<span class="err">​</span>		<span class="o">}</span><span class="k">catch</span><span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="err">​</span>			<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="err">​</span>		<span class="o">}</span>
<span class="err">​</span>	<span class="o">}</span>

<span class="o">}</span></code></pre></figure>

<p>어플리케이션 작성이 완료되면, 아래와 같이 Maven build를 수행해준다.</p>

<p><img src="/assets/img/2020-10-18-223125.png" alt="maven build 메뉴" /></p>

<p><img src="/assets/img/2020-10-18-223404.png" alt="maven build시 Goals" /></p>

<p>위와 같이 작성후 Run버튼을 눌러주면, maven이 Project의 target 폴더에 Excutable jar파일을 생성해준다.</p>

<p><img src="/assets/img/2020-10-18-223713.png" alt="" /></p>

<p>이렇게 생성된 Excutable jar 파일은 다른 jar 파일에 비하여 비교적 용량이 큰 것을 볼 수 있는데,</p>

<p>이는 실행에 필요한 라이브러리까지 모두 jar파일 내부에 포함시켰기 때문이다.</p>

<p>이 상태에서 java 명령어를 통해 jar파일을 바로 실행할 수도 있지만,</p>

<p>우리의 목적은 .exe이므로, 아직 가야할 길이 남았다.</p>

<p>우선 launch4j를 실행한다.</p>

<p><img src="/assets/img/2020-10-18-231216.png" alt="" /></p>

<p>output file에는 원하는 결과물의 경로와 파일명.exe를, jar 에는 우리가 방금 빌드한 Excutable jar 파일의 경로를 명시해준다. (각 칸의 옆에있는 폴더 모양 버튼을 통해 입력할 수도 있다.)</p>

<p><img src="/assets/img/2020-10-19-210558.png" alt="" /></p>

<p>그 다음 , Main Class를 명시하는 칸 옆에 폴더모양 버튼을 누르고, 우리가 생성한 Jar를 선택하면, 자동으로 Main Class가 잡힌다.</p>

<p>이제 모든 과정이 끝났다.</p>

<p>launch4j 상단의 톱니바퀴 모양의 버튼을 누르고, 설정파일을 저장하면 자동적으로 해당 경로에 결과물.exe파일이 생성된다. 생성된 실행파일을 실행하면…</p>

<p><img src="/assets/img/2020-10-19-211001.png" alt="" /></p>

<p>매우 잘 실행된다!</p>

<p>이렇게 우리는 라이브러리까지 모두 포함한 독립된 자바 프로그램을 편리하게 실행할 수 있게 되었다.</p>

<p>물론 JRE가 설치되어 있어야 한다는 제약사항이 존재하지만, 자바의 막강한 생산성은 그 단점을 커버 해줄수도 있다.</p>

<p>자바로 웹개발만 해왔다면, 그 웹개발로 쌓은 지식을 이용해 나만의 유틸리티를 만들어보는건 어떨까?</p>]]></content><author><name>dokku__</name><email>shs3146@gmail.com</email></author><category term="exp" /><category term="jar to exe" /><category term="java" /><category term="jar" /><category term="maven" /><category term="dependencies" /><category term="launch4j" /><summary type="html"><![CDATA[개인적인 용도의 유틸리티성 프로그램을 만들다 보면, 문득 자바가 그리워졌다.]]></summary></entry></feed>