<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://jang-go.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://jang-go.github.io/" rel="alternate" type="text/html" hreflang="en" /><updated>2024-12-29T05:56:48+00:00</updated><id>https://jang-go.github.io/feed.xml</id><title type="html">Jang-GO의 개발 블로그</title><subtitle>개발 관련된 공부 내용을 지속적으로 포스팅 합니다.</subtitle><author><name>HyeonSeo Jang</name><email>jang8195@naver.com</email></author><entry><title type="html">스프링 OAuth (1)</title><link href="https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-OAuth2(1)/" rel="alternate" type="text/html" title="스프링 OAuth (1)" /><published>2024-12-29T03:21:06+00:00</published><updated>2024-12-29T03:21:06+00:00</updated><id>https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-OAuth2(1)</id><content type="html" xml:base="https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-OAuth2(1)/"><![CDATA[<p>OAuth2에 대하여 알아보자.<br />
<!--more-->
요즘 왠만한 사이트에서는 자체 회원가입과 더불어 소셜 로그인 기능을 지원한다.<br />
필자도 소셜로그인으로 접속하는 사이트가 많다.(편하니까^.^)<br />
이때 <code class="language-plaintext highlighter-rouge">OAuth2.0</code>이라는 기술이 사용되는 것을 알고있었으며 아직 한번도 적용시켜본 적이 없고 어떠한 원리로 구현되어있는지 모른다.<br />
따라서 이번 포스팅에서는 이에대한 스프링의 지원을 살펴보려고한다.<br />
또한 사용자인증과 관련된 부분이므로 스프링 시큐리티에서는 이를 어떻게 호환하는지도 살펴보자.<br /></p>
<blockquote>
  <p>역시나 모든 내용은 공식문서를 토대로 작성하는 것임을 알립니다.</p>
</blockquote>

<h2 id="개요">개요</h2>
<p>스프링 시큐리티의 OAuth 2.0지원은 아래 두가지의 기능셋으로 구성된다.</p>
<ul>
  <li>OAuth2 Resource Server</li>
  <li><strong>OAuth2 Client</strong></li>
</ul>

<p>이중에서 우리가 중점적으로 알아볼 부분은 소셜로그인 즉, OAuth2 Login을 지원하는 OAuth2 Client에 대하여 집중적으로 알아볼 것이다.<br />
소셜로그인은 주로 클라이언트 애플리케이션이 <strong>서드파티 인증 서버</strong>와 상호작용 하는 방식으로 처리한다.</p>

<p>학습하기를 원하는 OAuth2 로그인, OAuth2 클라이언트를 분석해보자.</p>

<h2 id="oauth2-log-in">OAuth2 Log In</h2>
<p>공식문서에서 이번챕터 내용을 구글 로그인 예제를 따라가는 방식으로 제공한다. 그대로 따라가보자.</p>

<h3 id="initial-setup">Initial Setup</h3>
<p>일단 로그인에 OAuth 2.0 인증 시스템을 사용하려면 Google API 콘솔에서 프로젝트를 설정하여 OAuth 2.0 Credential을 얻어야한다.<br />
이에대한 부분은 구글에서 다른 블로그를 참조하여 발급받도록 하자. 잘 나와있다.</p>

<h3 id="setting-the-redirect-uri">Setting the Redirect URI</h3>
<p>최종 사용자가 Google 로그인이 완료된 후 리다이렉션되는 경로를 설정해야한다.<br />
승인된 리디렉션 URI에
<code class="language-plaintext highlighter-rouge">http://localhost:8080/login/oauth2/code/google</code>와 같이 잘 설정되어있는지 확인하자.</p>
<ul>
  <li>디폴트URI 템플릿은 <code class="language-plaintext highlighter-rouge">{baseurl}/login/oauth2/code/{registrationId}</code>이며 <code class="language-plaintext highlighter-rouge">registrationId</code>는 <code class="language-plaintext highlighter-rouge">ClientRegistration</code>의 식별자이다.</li>
</ul>

<h3 id="configure-applicationyml">Configure application.yml</h3>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">spring</span><span class="pi">:</span>
  <span class="na">security</span><span class="pi">:</span>
    <span class="na">oauth2</span><span class="pi">:</span>
      <span class="na">client</span><span class="pi">:</span>
        <span class="na">registration</span><span class="pi">:</span><span class="err">	</span>
          <span class="na">google</span><span class="pi">:</span><span class="err">	</span>
            <span class="na">client-id</span><span class="pi">:</span> <span class="pi">{</span><span class="nv">google-client-id</span><span class="pi">}</span>
            <span class="na">client-secret</span><span class="pi">:</span> <span class="pi">{</span><span class="nv">google-client-secret</span><span class="pi">}</span>
</code></pre></div></div>
<ul>
  <li><code class="language-plaintext highlighter-rouge">spring.security.oauth2.client.registration</code>는 OAuth Client 프로퍼티에 대한 기본 접두어이다.</li>
  <li>기본 속성 접두사 다음에는 <code class="language-plaintext highlighter-rouge">google</code>과 같은 <code class="language-plaintext highlighter-rouge">ClientRegistration</code>의 ID가 온다.</li>
</ul>

<h2 id="이제-localhost8080경로에-접근하면-구글로그인-창으로-넘어가며-로그인이-가능해진다">이제 <code class="language-plaintext highlighter-rouge">localhost:8080</code>경로에 접근하면 구글로그인 창으로 넘어가며, 로그인이 가능해진다.</h2>
<p>아주 간략하게 찍먹만 해봤는데 내용이 많고, 먼저 시큐리티 환경을 설정하고 진행해야할 필요가 있을것 같아 직접 적용시켜본 뒤 어떠한 방식으로 구현했는지 적는게 더욱 도움이 될것같다.</p>]]></content><author><name>HyeonSeo Jang</name><email>jang8195@naver.com</email></author><category term="스프링" /><category term="시큐리티" /><summary type="html"><![CDATA[OAuth2에 대하여 알아보자.]]></summary></entry><entry><title type="html">스프링 STOMP 뜯어보기</title><link href="https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-STOMP/" rel="alternate" type="text/html" title="스프링 STOMP 뜯어보기" /><published>2024-11-25T11:30:06+00:00</published><updated>2024-11-25T11:30:06+00:00</updated><id>https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-STOMP</id><content type="html" xml:base="https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-STOMP/"><![CDATA[<p>스프링 웹 소켓 가이드 따라가기<br />
<!--more--></p>
<blockquote>
  <p>본 글은 <a href="/스프링-웹-소켓-알아보기/">스프링 웹 소켓 알아보기</a> 포스팅 이후의 포스팅입니다.<br /></p>
</blockquote>

<p>이전에 웹 소켓과 STOMP에 대하여 학습하였다.<br />
이론을 배웠으면 코드로 적용하는 시간이 필요하다!<br />
시작으로 실시간 채팅을 구현하기 전, 스프링 공식문서 가이드에서 제공하는 템플릿을 실습해보며 다시한번 코드 작성과 구조에 대하여 파악해보려고 한다.<br />
<a href="https://spring.io/guides/gs/messaging-stomp-websocket">공식문서 가이드 링크</a></p>
<h3 id="의존성-추가">의존성 추가</h3>
<p>필자는 자바 21 버전으로 프로젝트를 만들었다. 아마 버전이 달라도 큰 차이는 없을것이다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-websocket'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
</code></pre></div></div>
<ul>
  <li>내장 톰캣 사용을 위한 <code class="language-plaintext highlighter-rouge">starter-web</code></li>
  <li>웹 소켓 및 STOMP 사용을 위한 <code class="language-plaintext highlighter-rouge">starter-websocket</code></li>
  <li>추후 채팅방 대기열 구현을 위한 <code class="language-plaintext highlighter-rouge">starter-redis</code></li>
  <li>html을 서버사이드로 내리기 위한 <code class="language-plaintext highlighter-rouge">starter-thymeleaf</code></li>
</ul>

<h3 id="stomp-설정">STOMP 설정</h3>
<p>이제 소켓통신을 위한 기반을 마련해야한다.<br />
스프링에서는 간단하게 소켓 설정클래스를 컴포넌트로 등록해주면 된다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="nd">@EnableWebSocketMessageBroker</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">WebSocketConfig</span> <span class="kd">implements</span> <span class="nc">WebSocketMessageBrokerConfigurer</span> <span class="o">{</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">registerStompEndpoints</span><span class="o">(</span><span class="nc">StompEndpointRegistry</span> <span class="n">registry</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">registry</span><span class="o">.</span><span class="na">addEndpoint</span><span class="o">(</span><span class="s">"/livechat"</span><span class="o">).</span><span class="na">withSockJS</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">configureMessageBroker</span><span class="o">(</span><span class="nc">MessageBrokerRegistry</span> <span class="n">registry</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">registry</span><span class="o">.</span><span class="na">enableSimpleBroker</span><span class="o">(</span><span class="s">"/topic"</span><span class="o">,</span> <span class="s">"/queue"</span><span class="o">);</span>
        <span class="n">registry</span><span class="o">.</span><span class="na">setApplicationDestinationPrefixes</span><span class="o">(</span><span class="s">"/app"</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<ul>
  <li><code class="language-plaintext highlighter-rouge">@EnableWebSocketMessageBroker</code>를 통해 WebSocket 메시지 처리 및 STOMP지원을 활성화한다.</li>
  <li>이제 애플리케이션은 연결된 클라이언트에 대한 STOMP브로커가 된다.</li>
  <li><code class="language-plaintext highlighter-rouge">registerStompEndpoints</code>
    <ul>
      <li><code class="language-plaintext highlighter-rouge">addEndpoint</code> 메서드로 클라이언트가 웹 소켓 핸드셰이크를 할 경로를 제공한다.</li>
      <li>즉, 클라이언트는 <code class="language-plaintext highlighter-rouge">ws://localhost:8080/livechat</code>의 경로로 요청을 보내면 웹 소켓 연결이 활성화 된다.</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">configureMessageBroker</code>
    <ul>
      <li>앞선 포스팅에서 살펴봤듯 <code class="language-plaintext highlighter-rouge">clientInboundChannel</code>에서 메시지는 브로커로 직접 라우팅, 혹은 컨트롤러의 <code class="language-plaintext highlighter-rouge">@MessageMapping</code>메서드로 라우팅될 수 있다.</li>
      <li>여기서는 <code class="language-plaintext highlighter-rouge">/topic</code>과 <code class="language-plaintext highlighter-rouge">/queue</code>경로는 브로커를 향한 경로이고 <code class="language-plaintext highlighter-rouge">/app</code>은 <code class="language-plaintext highlighter-rouge">@MessageMapping</code>메서드의 접두사가 된다.</li>
    </ul>
  </li>
</ul>

<p><br /></p>

<hr />
<h3 id="시작">시작</h3>
<p>우리가 만들 애플리케이션은 아래와 같다.<br />
이름을 입력하고 서버로 요청하면 서버에서 인사말을 붙여 반환하는 애플리케이션이다.<br />
여기서 중요한점은 소켓을 통한 통신이기 때문에 요청당 HTTP요청이 생성되지 않고 연결을 계속 유지한다는 점이다.
<img src="/assets/프로젝트/exex.jpg" alt="ex" /></p>

<p><br />
STOMP에 대한 설정을 위에서 마쳤다.<br />
서버에 이름이 요청으로 가고, 웹에는 인사말이 응답으로 반환되므로 간단한 DTO를 생성하자.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Data</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">HelloMessage</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>

<span class="nd">@Data</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Greeting</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">content</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">Greeting</span><span class="o">(</span><span class="nc">String</span> <span class="n">content</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">content</span> <span class="o">=</span> <span class="n">content</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>이제 컨트롤러를 작성해보자.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Controller</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">GreetingController</span> <span class="o">{</span>

    <span class="nd">@MessageMapping</span><span class="o">(</span><span class="s">"/hello"</span><span class="o">)</span>
    <span class="nd">@SendTo</span><span class="o">(</span><span class="s">"/topic/greetings"</span><span class="o">)</span>
    <span class="kd">public</span> <span class="nc">Greeting</span> <span class="nf">greeting</span><span class="o">(</span><span class="nc">HelloMessage</span> <span class="n">message</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nf">Greeting</span><span class="o">(</span><span class="s">"Hello, "</span> <span class="o">+</span> <span class="nc">HtmlUtils</span><span class="o">.</span><span class="na">htmlEscape</span><span class="o">(</span><span class="n">message</span><span class="o">.</span><span class="na">getName</span><span class="o">())</span> <span class="o">+</span> <span class="s">"!"</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">/app</code> 접두사로 인해 <code class="language-plaintext highlighter-rouge">@MessageMapping</code> 메서드로 라우팅된다.</li>
  <li>접두사 제거 이후 <code class="language-plaintext highlighter-rouge">/hello</code>부분이 <code class="language-plaintext highlighter-rouge">@MessageMapping("/hello")</code>에 매핑된다.</li>
  <li><code class="language-plaintext highlighter-rouge">@SendTo</code>는 메서드의 반환값을 특정 메시지 브로커 경로로 발행하는데 사용된다.
    <ul>
      <li>이 경우, 반환된 <code class="language-plaintext highlighter-rouge">Greeting</code>객체는 STOMP메시지로 변환되어 <code class="language-plaintext highlighter-rouge">/topic/greetings</code> 경로를 구독하고 있는 모든 클라이언트에게 브로드캐스트 된다.</li>
    </ul>
  </li>
</ul>

<p>지금까지의 흐름을 정리해보자.</p>
<ol>
  <li>클라이언트가 <code class="language-plaintext highlighter-rouge">/app/hello</code>로 메시지 전송</li>
  <li>메시지는 <code class="language-plaintext highlighter-rouge">@MessageMapping("/hello")</code>가 선언된 메서드로 라우팅</li>
  <li>메서드가 처리된 뒤 반환된 값은 <code class="language-plaintext highlighter-rouge">@SendTo("/topic/greetings")</code>로 지정된 경로로 발행</li>
  <li><code class="language-plaintext highlighter-rouge">/topic/greetings</code>를 구독 중인 모든 클라이언트가 이 메시지를 수신</li>
</ol>

<p>이제 서버에서 구현할 일은 전부 마쳤다.<br />
간단하게 html과 자바스크립트를 이용하여 클라이언트 코드를 구현해보자.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;!DOCTYPE html&gt;</span>
<span class="nt">&lt;html</span> <span class="na">xmlns:th=</span><span class="s">"http://www.thymeleaf.org"</span><span class="nt">&gt;</span>
<span class="nt">&lt;head&gt;</span>
    <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
    <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"https://cdn.jsdelivr.net/npm/@stomp/stompjs@7.0.0/bundles/stomp.umd.min.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
    <span class="nt">&lt;title&gt;</span>Greeting Chat<span class="nt">&lt;/title&gt;</span>
    <span class="nt">&lt;script </span><span class="na">th:src=</span><span class="s">"@{/app.js}"</span><span class="nt">&gt;&lt;/script&gt;</span>
<span class="nt">&lt;/head&gt;</span>
<span class="nt">&lt;body&gt;</span>
<span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"chat-container"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;header&gt;</span>
        <span class="nt">&lt;h1&gt;</span>Greeting Chat<span class="nt">&lt;/h1&gt;</span>
    <span class="nt">&lt;/header&gt;</span>
    <span class="nt">&lt;main&gt;</span>
        <span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"chat-box"</span> <span class="na">class=</span><span class="s">"chat-box"</span><span class="nt">&gt;</span>
            <span class="c">&lt;!-- 채팅 메시지가 여기 표시됨 --&gt;</span>
        <span class="nt">&lt;/div&gt;</span>
    <span class="nt">&lt;/main&gt;</span>
    <span class="nt">&lt;footer&gt;</span>
        <span class="nt">&lt;form</span> <span class="na">id=</span><span class="s">"chat-form"</span><span class="nt">&gt;</span>
            <span class="nt">&lt;input</span> <span class="na">id=</span><span class="s">"name-input"</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">placeholder=</span><span class="s">"Enter your name"</span> <span class="na">autocomplete=</span><span class="s">"off"</span><span class="nt">&gt;</span>
            <span class="nt">&lt;button</span> <span class="na">type=</span><span class="s">"button"</span> <span class="na">onclick=</span><span class="s">"sendName()"</span> <span class="na">disabled</span><span class="nt">&gt;</span>Send<span class="nt">&lt;/button&gt;</span>
        <span class="nt">&lt;/form&gt;</span>
    <span class="nt">&lt;/footer&gt;</span>
<span class="nt">&lt;/div&gt;</span>
<span class="nt">&lt;/body&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</code></pre></div></div>
<p>소켓 연결을 설정할 자바스크립트코드는 아래와같다.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">stompClient</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">connected</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>

<span class="kd">function</span> <span class="nx">connect</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">StompJs</span><span class="p">.</span><span class="nx">Client</span><span class="p">({</span>
        <span class="na">webSocketFactory</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="k">new</span> <span class="nx">SockJS</span><span class="p">(</span><span class="dl">'</span><span class="s1">/livechat</span><span class="dl">'</span><span class="p">),</span>
        <span class="na">onConnect</span><span class="p">:</span> <span class="p">(</span><span class="nx">frame</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Connected: </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">frame</span><span class="p">);</span>
            <span class="nx">connected</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
            <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">button</span><span class="dl">'</span><span class="p">).</span><span class="nx">disabled</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>

            <span class="nx">client</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">(</span><span class="dl">'</span><span class="s1">/topic/greetings</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">message</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="kd">const</span> <span class="nx">chatBox</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">chat-box</span><span class="dl">"</span><span class="p">);</span>
                <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">message</span><span class="p">.</span><span class="nx">body</span><span class="p">);</span>
                <span class="kd">const</span> <span class="nx">newMessage</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">"</span><span class="s2">div</span><span class="dl">"</span><span class="p">);</span>
                <span class="nx">newMessage</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">response</span><span class="p">.</span><span class="nx">content</span><span class="p">;</span>
                <span class="nx">chatBox</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">newMessage</span><span class="p">);</span>
            <span class="p">});</span>
        <span class="p">},</span>
        <span class="na">onDisconnect</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Disconnected!</span><span class="dl">'</span><span class="p">);</span>
            <span class="nx">connected</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
            <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">button</span><span class="dl">'</span><span class="p">).</span><span class="nx">disabled</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
            <span class="nx">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="nx">client</span><span class="p">.</span><span class="nx">activate</span><span class="p">();</span>
            <span class="p">},</span> <span class="mi">5000</span><span class="p">);</span>
        <span class="p">},</span>
        <span class="na">onStompError</span><span class="p">:</span> <span class="p">(</span><span class="nx">frame</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Broker reported error: </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">frame</span><span class="p">.</span><span class="nx">headers</span><span class="p">[</span><span class="dl">'</span><span class="s1">message</span><span class="dl">'</span><span class="p">]);</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Additional details: </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">frame</span><span class="p">.</span><span class="nx">body</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">});</span>

    <span class="nx">stompClient</span> <span class="o">=</span> <span class="nx">client</span><span class="p">;</span>
    <span class="nx">client</span><span class="p">.</span><span class="nx">activate</span><span class="p">();</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">sendName</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">stompClient</span> <span class="o">&amp;&amp;</span> <span class="nx">connected</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">input</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">name-input</span><span class="dl">"</span><span class="p">);</span>
        <span class="kd">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">input</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
        <span class="k">try</span> <span class="p">{</span>
            <span class="nx">stompClient</span><span class="p">.</span><span class="nx">publish</span><span class="p">({</span>
                <span class="na">destination</span><span class="p">:</span> <span class="dl">"</span><span class="s2">/app/hello</span><span class="dl">"</span><span class="p">,</span>
                <span class="na">body</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span> <span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">:</span> <span class="nx">name</span> <span class="p">})</span>
            <span class="p">});</span>
            <span class="nx">input</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
        <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Failed to send message:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Not connected to WebSocket server</span><span class="dl">'</span><span class="p">);</span>
        <span class="nx">alert</span><span class="p">(</span><span class="dl">'</span><span class="s1">연결 중입니다. 잠시 후 다시 시도해주세요.</span><span class="dl">'</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// 페이지 로드시 연결</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
    <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">button</span><span class="dl">'</span><span class="p">).</span><span class="nx">disabled</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
    <span class="nx">connect</span><span class="p">();</span>
<span class="p">};</span>

<span class="c1">// 페이지 언로드시 연결 해제</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">onbeforeunload</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">stompClient</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">stompClient</span><span class="p">.</span><span class="nx">deactivate</span><span class="p">();</span>
    <span class="p">}</span>
    <span class="nx">connected</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<h3 id="실행화면">실행화면</h3>
<p><img src="/assets/프로젝트/excuteSample1.png" alt="sample1" />
이름에 Hi를 입력하고 Send를 누르니 메시지가 정상적으로 도착하였다.<br />
이번엔 똑같은 창 3개를 띄워보자. 모두 같은 경로를 구독하므로 똑같은 메시지가 모두에게 도착할 것으로 예상된다.
<img src="/assets/프로젝트/excuteSample2.png" alt="sample2" />
예상한대로 모두에게 브로드캐스트된다.<br /></p>]]></content><author><name>HyeonSeo Jang</name><email>jang8195@naver.com</email></author><category term="스프링" /><summary type="html"><![CDATA[스프링 웹 소켓 가이드 따라가기]]></summary></entry><entry><title type="html">스프링 웹 소켓 알아보기(5)</title><link href="https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EC%86%8C%EC%BC%93-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0(5)/" rel="alternate" type="text/html" title="스프링 웹 소켓 알아보기(5)" /><published>2024-11-23T09:53:06+00:00</published><updated>2024-11-23T09:53:06+00:00</updated><id>https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EC%86%8C%EC%BC%93-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0(5)</id><content type="html" xml:base="https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EC%86%8C%EC%BC%93-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0(5)/"><![CDATA[<p>스프링 웹 소켓 알아보기 마지막 포스팅<br />
<!--more--></p>

<p>스프링 웹 소켓에 대한 마지막 포스팅이다.<br />
핵심 개념이라 생각되는 것 들은 앞 포스팅에서 정리해왔고 이제는 부가적인 정보만 정리해보겠다.</p>

<h2 id="어노테이션">어노테이션</h2>

<h3 id="messagemapping">@MessageMapping</h3>
<ul>
  <li>STOMP 메시지의 destination 헤더에 따라 메시지를 특정 메서드로 라우팅하는데 사용</li>
  <li>메서드, 클래스 레벨에 적용 가능
    <ul>
      <li>메서드 레벨 : 개별 메시지 경로 매핑</li>
      <li>클래스 레벨 : 해당 클래스의 모든 메서드에 공통 경로 적용</li>
    </ul>
  </li>
  <li>Ant-style, 템플릿 변수 지원
    <ul>
      <li>Ant-style ex) <code class="language-plaintext highlighter-rouge">/thing*</code>, <code class="language-plaintext highlighter-rouge">/thing/**</code></li>
      <li>템플릿 변수 ex) <code class="language-plaintext highlighter-rouge">/thing/{id}</code> -&gt; <code class="language-plaintext highlighter-rouge">@DestionationVariable</code>로 접근 가능</li>
    </ul>
  </li>
  <li>경로 스타일 지원
    <ul>
      <li><code class="language-plaintext highlighter-rouge">/room/{id}</code> (기본 슬래시 스타일)</li>
      <li><code class="language-plaintext highlighter-rouge">room.id</code> (점(.) 스타일)</li>
    </ul>
  </li>
</ul>

<p><b>지원 메서드</b><br /></p>

<table>
  <thead>
    <tr>
      <th>인자</th>
      <th>설명</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Message</code></td>
      <td>메시지 전체를 가져올 수 있음</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">MessageHeaders</code></td>
      <td>메시지 헤더에 접근 가능</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">@Payload</code></td>
      <td>* 메시지의 본문에 접근.<br /> * 자동변환은 설정된 <code class="language-plaintext highlighter-rouge">MessageConverter</code>에 의해 처리됨<br />* <code class="language-plaintext highlighter-rouge">@Valid</code>를 적용하여 검증기능 가능</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">@Header</code></td>
      <td>특정 헤더 값에 접근</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">@Headers</code></td>
      <td>모든 헤더에 접근 가능.(Map 형태로 주입)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">java.security.Principal</code></td>
      <td>WebSocket 핸드셰이크 시 인증된 사용자 정보를 반영</td>
    </tr>
  </tbody>
</table>

<h3 id="subscribemapping">@SubscribeMapping</h3>
<ul>
  <li>구독 요청에 대해 한 번만 응답하는 <strong>1회성 요청-응답</strong> 처리</li>
  <li>기본적으로 브로커가 아닌 클라이언트로 직접 응답함
    <ul>
      <li><code class="language-plaintext highlighter-rouge">brokerChannel</code>이 아닌 <code class="language-plaintext highlighter-rouge">clientOutboundChannel</code>로 응답</li>
    </ul>
  </li>
  <li>구독 정보를 저장하거나 반복 브로드캐스트 하지 않음</li>
  <li>사용 사례: 초기 데이터 로드나 클라이언트 초기화 요청 처리</li>
</ul>

<h3 id="messageexceptionhandler">@MessageExceptionHandler</h3>
<ul>
  <li><code class="language-plaintext highlighter-rouge">@MessageMapping</code>에서 발생한 예외를 처리</li>
  <li>예외 객체를 메서드 인자로 받아서 처리</li>
  <li>동일한 컨트롤러 내에서만 적용되며, 전역으로 적용하려면 <code class="language-plaintext highlighter-rouge">@ControllerAdvice</code>사용</li>
</ul>

<h2 id="simpmessagingtemplate">SimpMessagingTemplate</h2>
<ul>
  <li>스프링에서 제공하는 메시지 전송 유틸리티 클래스</li>
  <li>컨트롤러 외부에서도 WebSocket 클라이언트로 메시지를 전송할 수 있음</li>
</ul>

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

	<span class="kd">private</span> <span class="nc">SimpMessagingTemplate</span> <span class="n">template</span><span class="o">;</span>

	<span class="nd">@Autowired</span>
	<span class="kd">public</span> <span class="nf">GreetingController</span><span class="o">(</span><span class="nc">SimpMessagingTemplate</span> <span class="n">template</span><span class="o">)</span> <span class="o">{</span>
		<span class="k">this</span><span class="o">.</span><span class="na">template</span> <span class="o">=</span> <span class="n">template</span><span class="o">;</span>
	<span class="o">}</span>

	<span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">path</span><span class="o">=</span><span class="s">"/greetings"</span><span class="o">,</span> <span class="n">method</span><span class="o">=</span><span class="no">POST</span><span class="o">)</span>
	<span class="kd">public</span> <span class="kt">void</span> <span class="nf">greet</span><span class="o">(</span><span class="nc">String</span> <span class="n">greeting</span><span class="o">)</span> <span class="o">{</span>
		<span class="nc">String</span> <span class="n">text</span> <span class="o">=</span> <span class="s">"["</span> <span class="o">+</span> <span class="n">getTimestamp</span><span class="o">()</span> <span class="o">+</span> <span class="s">"]:"</span> <span class="o">+</span> <span class="n">greeting</span><span class="o">;</span>
		<span class="k">this</span><span class="o">.</span><span class="na">template</span><span class="o">.</span><span class="na">convertAndSend</span><span class="o">(</span><span class="s">"/topic/greetings"</span><span class="o">,</span> <span class="n">text</span><span class="o">);</span>
	<span class="o">}</span>

<span class="o">}</span>
</code></pre></div></div>
<ul>
  <li><code class="language-plaintext highlighter-rouge">SimpMessagingTemplate</code>은 내부적으로 <code class="language-plaintext highlighter-rouge">brokerChannel</code>로 메시지를 전송</li>
  <li>활용 사례:
    <ul>
      <li>백그라운드 작업 완료 후 결과 알림</li>
      <li>특정 사용자에게 실시간 알림 전송</li>
      <li>외부 이벤트 기반 메시지 전송</li>
    </ul>
  </li>
</ul>

<h2 id="인증처리">인증처리</h2>
<p><i>(스프링 시큐리티를 사용한다고 가정)</i>
<br />기본 인증시와 토큰기반 인증상황을 살펴보자.</p>

<ul>
  <li>기본인증
    <ul>
      <li>대부분 HTTP 요청에서 인증을 이미 설정</li>
      <li>인증된 사용자는 쿠키 기반 세션을 통해 HTTP 요청에서 인증정보를 유지</li>
      <li>WebSocket 핸드셰이크 시, Spring이 HTTP세션의 인증정보를 가져와 WebSocket 세션과 연결</li>
      <li>이후 STOMP 메시지에 <code class="language-plaintext highlighter-rouge">user</code>헤더로 인증정보가 자동으로 포함</li>
    </ul>
  </li>
  <li>토큰기반인증
    <ul>
      <li>WebSocket은 핸드셰이크에서 커스텀 헤더를 전송할 수 없음</li>
      <li>따라서 핸드셰이크 이후 CONNECT 프레임을 전송할때 Authorization 프로퍼티를 추가하여 보내면 됨</li>
      <li>클라이언트 라이브러리(예: stomp.js)를 사용하면 CONNECT 프레임은 자동 전송됨.</li>
      <li>커스텀 클라이언트의 경우 수동으로 CONNECT 프레임에 Authorization 헤더를 추가해야 함.</li>
    </ul>
  </li>
</ul>

<p>여기까지 웹 소켓에 대한 이론적인 내용을 마치도록 하겠다.<br />
원래는 비트코인 자동매매시 거래정보를 가져오는 방식으로 웹 소켓을 이용하려고 하였는데 아직 한번도 써보지 않았으며 투자에 대한 지식을 더 마련해야할 것 같다.<br />
따라서 실시간 채팅을 만들어보면서 이해도를 가져가보려고 한다.</p>]]></content><author><name>HyeonSeo Jang</name><email>jang8195@naver.com</email></author><category term="스프링" /><summary type="html"><![CDATA[스프링 웹 소켓 알아보기 마지막 포스팅]]></summary></entry><entry><title type="html">스프링 웹 소켓 알아보기(4)</title><link href="https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EC%86%8C%EC%BC%93-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0(4)/" rel="alternate" type="text/html" title="스프링 웹 소켓 알아보기(4)" /><published>2024-11-22T11:08:06+00:00</published><updated>2024-11-22T11:08:06+00:00</updated><id>https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EC%86%8C%EC%BC%93-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0(4)</id><content type="html" xml:base="https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EC%86%8C%EC%BC%93-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0(4)/"><![CDATA[<p>이번 포스팅부터는 STOMP에 대하여 알아보자
<!--more-->
앞선 포스팅에서 알아보았듯, WebSocket은 HTTP와 달리 메시지의 <strong>구조나 의미(semantics)</strong>에 대한 표준화된 규약이 없다.<br />
따라서 WebSocket을 사용할 때, 클라이언트와 서버가 주고받을 메시지의 형식과 내용을 직접 정의해야 한다.
<br /><br />
하지만 이렇게 모든 메시지의 규칙을 직접 구현하는 것은 복잡하고 번거롭다.
이를 해결하기 위해 등장한 것이 바로 <strong>STOMP(Simple/Streaming Text Oriented Messaging Protocol)</strong>다.<br />
STOMP는 WebSocket 위에서 동작하며, 메시지의 형식과 의미를 표준화하여 메시징을 간단하고 효율적으로 처리할 수 있도록 도와준다.</p>

<h3 id="stomp-프레임-구조">STOMP 프레임 구조</h3>
<p>STOMP는 HTTP를 모델로 한 프레임 기반 프로토콜이다.</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>COMMAND
header1:value1
header2:value2

Body^@
</code></pre></div></div>
<ul>
  <li>COMMAND : 실행할 명령(SEND, SUBSCRIBE 등)</li>
  <li>헤더 : 메시지의 속성과 목적지를 지정</li>
  <li>Body : 메시지 내용</li>
</ul>

<p>클라이언트는 <code class="language-plaintext highlighter-rouge">SEND</code> 또는 <code class="language-plaintext highlighter-rouge">SUBSCRIBE</code> 명령을 사용하여 메시지를 보내거나 구독할 수 있다. 이 메시지는 브로커를 통해 다른 연결된 클라이언트에게 메시지를 보내거나, 서버에 메시지를 보내 작업을 수행하도록 요청할 수 있다.<br />
이게 간단한 pub/sub 메커니즘이다.</p>

<h3 id="spring에서의-stomp">Spring에서의 STOMP</h3>
<ul>
  <li>스프링의 STOMP 지원을 활성화하면 스프링 애플리케이션이 STOMP 브로커 역할을 한다.</li>
  <li>메시지를 <code class="language-plaintext highlighter-rouge">@Controller</code>의 메시지 핸들링 메서드로 라우팅하거나, 간단한 메모리 브로커를 통해 구독자에게 브로드캐스트한다.</li>
  <li>RabbitMQ, ActiveMQ 같은 외부 STOMP 브로커와 통합 가능</li>
  <li>Spring은 브로커와의 TCP 연결을 유지하며, 메시지를 중계하거나 WebSocket 클라이언트와 교환</li>
</ul>

<h3 id="stomp-예시">STOMP 예시</h3>
<p>공식문서에 있는 예시를 통해 이해해보자.<br />
아래는 주식시세를 주기적으로 가져오는 서버에 클라이언트가 구독하는 예시이다.<br /></p>

<h4 id="1-구독-요청클라이언트---서버">1. 구독 요청(클라이언트 -&gt; 서버)</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SUBSCRIBE
id:sub-1
destination:/topic/price.stock.*
^@
</code></pre></div></div>
<ul>
  <li>클라이언트는 <code class="language-plaintext highlighter-rouge">/topic/price.stock.*</code>경로의 메시지를 수신하도록 요청</li>
</ul>

<hr />

<h4 id="2-메시지-전송-클라이언트---서버">2. 메시지 전송 (클라이언트 -&gt; 서버)</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SEND
destination:/queue/trade
content-type:application/json
content-length:44

{"action":"BUY","ticker":"MMM","shares",44}^@
</code></pre></div></div>

<ul>
  <li>클라이언트가 서버에 주식 구매 요청을 보냄</li>
  <li>서버는 <code class="language-plaintext highlighter-rouge">@MessageMapping</code> 메서드를 통해 이를 처리</li>
</ul>

<hr />

<h4 id="3-메시지-브로드캐스트서버---클라이언트">3. 메시지 브로드캐스트(서버 -&gt; 클라이언트)</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>MESSAGE
message-id:nxahklf6-1
subscription:sub-1
destination:/topic/price.stock.MMM

{"ticker":"MMM","price":129.45}^@
</code></pre></div></div>

<ul>
  <li>서버가 <code class="language-plaintext highlighter-rouge">/topic/price.stock.MMM</code>을 구독한 클라이언트에게 주식 가격 메시지를 전달</li>
</ul>

<h4 id="stomp의-destination">STOMP의 destination</h4>
<p>STOMP에서는 목적지의 의미가 명확하게 정해져 있지 않으며 서버에서 정의한다.</p>
<ul>
  <li>일반적인 패턴
    <ul>
      <li><code class="language-plaintext highlighter-rouge">/topic/..</code> : Publish-Subscribe(1:N)</li>
      <li><code class="language-plaintext highlighter-rouge">/queue/..</code> : Point-to-Point(1:1)</li>
    </ul>
  </li>
</ul>

<h4 id="제한사항">제한사항</h4>
<ul>
  <li>서버는 구독 요청이 없는 경우 메시지를 전송할 수 없다.</li>
  <li>서버에서 보낸 메시지는 항상 특정 클라이언트 구독과 연관되어야 한다.</li>
</ul>

<h3 id="spring에서-stomp-활성화">Spring에서 STOMP 활성화</h3>
<p>스프링에서는 WebSocket 위에서 STOMP를 사용하는 기능을 제공한다.<br />
STOMP는 <code class="language-plaintext highlighter-rouge">spring-messaging</code> 및 <code class="language-plaintext highlighter-rouge">spring-websocket</code> 모듈에서 제공한다.<br />
사용하는 공식문서 예제를 보자.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="nd">@EnableWebSocketMessageBroker</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">WebSocketConfiguration</span> <span class="kd">implements</span> <span class="nc">WebSocketMessageBrokerConfigurer</span> <span class="o">{</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">registerStompEndpoints</span><span class="o">(</span><span class="nc">StompEndpointRegistry</span> <span class="n">registry</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">// 클라이언트가 WebSocket 핸드셰이크를 요청할 URL</span>
        <span class="n">registry</span><span class="o">.</span><span class="na">addEndpoint</span><span class="o">(</span><span class="s">"/portfolio"</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">configureMessageBroker</span><span class="o">(</span><span class="nc">MessageBrokerRegistry</span> <span class="n">config</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">// "/app"으로 시작하는 메시지는 @Controller의 @MessageMapping 메서드로 라우팅</span>
        <span class="n">config</span><span class="o">.</span><span class="na">setApplicationDestinationPrefixes</span><span class="o">(</span><span class="s">"/app"</span><span class="o">);</span>

        <span class="c1">// /topic 또는 /queue로 시작하는 메시지는 내장 브로커로 전송</span>
        <span class="n">config</span><span class="o">.</span><span class="na">enableSimpleBroker</span><span class="o">(</span><span class="s">"/topic"</span><span class="o">,</span> <span class="s">"/queue"</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<ul>
  <li>Spring에서 STOMP 지원을 활성화하려면
<code class="language-plaintext highlighter-rouge">@EnableWebSocketMessageBroker</code>를 사용하면 된다.
주석을 확인하면 이해하기 쉬울 것이다.만약 외부 브로커를 사용할 경우 브로커가 지원하는 STOMP 목적지와 접두사를 확인해야한다.</li>
</ul>

<h3 id="서버측에서의-메시지-흐름">서버측에서의 메시지 흐름</h3>
<p>STOMP 엔드포인트를 노출하면 스프링 애플리케이션은 연결된 클라이언트에 대한 STOMP 브로커가 된다.<br />
이 과정에서 스프링은 메시지 전송 및 라우팅을 처리하기 위해 다양한 구성요소를 활용한다.</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">Message</code>
    <ul>
      <li>메시지의 헤더와 페이로드를 포함한 단순한 메시지 표현</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">MessageHandler</code>
    <ul>
      <li>메시지를 처리하는 핸들러</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">MessageChannel</code>
    <ul>
      <li>메시지를 송신할 수 있는 채널로, 생산자와 소비자를 느슨하게 결합</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">SubscribableChannel</code>
    <ul>
      <li>MessageChannel의 구독 가능 버전</li>
      <li>MessageHandler가 구독자로 추가될 수 있음</li>
    </ul>
  </li>
</ul>

<p>아래는 먼저 스프링 내장 브로커를 사용할 경우의 공식문서 다이어그램이다.
<img src="/assets/스프링/simplebroker.png" alt="simple" />
이때 사용되는 채널흐름은 다음과 같다.</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">clientInboundChannel</code> : 클라이언트의 메시지 처리</li>
  <li><code class="language-plaintext highlighter-rouge">clientOutboundChannel</code> : 서버 -&gt; 클라이언트로 메시지전송</li>
  <li><code class="language-plaintext highlighter-rouge">brokerchannel</code> : 서버 -&gt; 브로커로 메시지전달</li>
</ol>

<p>외부 브로커를 사용할 경우 다이어그램은 아래와 같다.
<img src="/assets/스프링/externalbroker.png" alt="exbroker" /></p>
<ul>
  <li>외부 브로커를 사용하는 경우 StompBrokerRelay가 되었다.</li>
  <li>StompBrokerRelay는 외부 브로커로 메시지를 전송하거나 클라이언트의 메시지를 브로커로 중계한다.</li>
  <li>TCP 연결을 통해 외부 브로커와 통신하며 구독 및 메시지 브로드캐스트를 관리한다.</li>
</ul>

<p>이제 메시지 처리 흐름을 살펴보자</p>
<ol>
  <li>WebSocket 클라이언트에서 메시지 수신
    <ul>
      <li>WebSocket 연결에서 수신된 메시지는 STOMP 프레임으로 디코딩되고, Spring의 <code class="language-plaintext highlighter-rouge">Message</code> 표현으로 변환된 뒤 <code class="language-plaintext highlighter-rouge">clientInboundChannel</code>로 전달된다.</li>
    </ul>
  </li>
  <li>메시지 라우팅
    <ul>
      <li>위에서 <code class="language-plaintext highlighter-rouge">Message</code> 표현된 <code class="language-plaintext highlighter-rouge">destination</code>헤더가 <code class="language-plaintext highlighter-rouge">/app</code>으로 시작하면 컨트롤러의 <code class="language-plaintext highlighter-rouge">@MessageMapping</code>메서드로 라우팅된다.</li>
      <li><code class="language-plaintext highlighter-rouge">/topic</code>이나<code class="language-plaintext highlighter-rouge">/queue</code>경로라면 메시지는 메시지 브로커로 직접 라우팅된다.</li>
    </ul>
  </li>
  <li>메시지 브로커 동작
    <ul>
      <li>컨트롤러는 메시지를 처리한 후 <code class="language-plaintext highlighter-rouge">brokerChannel</code>을 통해 메시지를 브로커로 전송</li>
      <li>브로커는 해당 메시지를 구독자에게 브로드캐스트하며, 이는 <code class="language-plaintext highlighter-rouge">clientOutboundChannel</code>을 통해 클라이언트에게 전달된다.</li>
    </ul>
  </li>
</ol>

<h3 id="예시코드">예시코드</h3>
<p>공식문서의 예제코드를 보며 동작을 파악해보자.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="nd">@EnableWebSocketMessageBroker</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">WebSocketConfiguration</span> <span class="kd">implements</span> <span class="nc">WebSocketMessageBrokerConfigurer</span> <span class="o">{</span>

	<span class="nd">@Override</span>
	<span class="kd">public</span> <span class="kt">void</span> <span class="nf">registerStompEndpoints</span><span class="o">(</span><span class="nc">StompEndpointRegistry</span> <span class="n">registry</span><span class="o">)</span> <span class="o">{</span>
		<span class="n">registry</span><span class="o">.</span><span class="na">addEndpoint</span><span class="o">(</span><span class="s">"/portfolio"</span><span class="o">);</span>
	<span class="o">}</span>

	<span class="nd">@Override</span>
	<span class="kd">public</span> <span class="kt">void</span> <span class="nf">configureMessageBroker</span><span class="o">(</span><span class="nc">MessageBrokerRegistry</span> <span class="n">registry</span><span class="o">)</span> <span class="o">{</span>
		<span class="n">registry</span><span class="o">.</span><span class="na">setApplicationDestinationPrefixes</span><span class="o">(</span><span class="s">"/app"</span><span class="o">);</span>
		<span class="n">registry</span><span class="o">.</span><span class="na">enableSimpleBroker</span><span class="o">(</span><span class="s">"/topic"</span><span class="o">);</span>
	<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

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

	<span class="nd">@MessageMapping</span><span class="o">(</span><span class="s">"/greeting"</span><span class="o">)</span>
	<span class="kd">public</span> <span class="nc">String</span> <span class="nf">handle</span><span class="o">(</span><span class="nc">String</span> <span class="n">greeting</span><span class="o">)</span> <span class="o">{</span>
		<span class="k">return</span> <span class="s">"["</span> <span class="o">+</span> <span class="n">getTimestamp</span><span class="o">()</span> <span class="o">+</span> <span class="s">": "</span> <span class="o">+</span> <span class="n">greeting</span><span class="o">;</span>
	<span class="o">}</span>

	<span class="kd">private</span> <span class="nc">String</span> <span class="nf">getTimestamp</span><span class="o">()</span> <span class="o">{</span>
		<span class="k">return</span> <span class="k">new</span> <span class="nf">SimpleDateFormat</span><span class="o">(</span><span class="s">"MM/dd/yyyy h:mm:ss a"</span><span class="o">).</span><span class="na">format</span><span class="o">(</span><span class="k">new</span> <span class="nc">Date</span><span class="o">());</span>
	<span class="o">}</span>

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

<ol>
  <li>클라이언트 연결
    <ul>
      <li>클라이언트는 <code class="language-plaintext highlighter-rouge">localhost:8080/portfolio</code>로 WebSocket연결을 생성한다.</li>
      <li>연결이 성립되면, STOMP 프레임(SUBSCRIBE, SEND 등)을 통해 클라이언트와 서버 간 메시지가 전송되기 시작.</li>
    </ul>
  </li>
  <li>구독 요청(SUBSCRIBE)
    <ul>
      <li>클라이언트는 아래와 같이 STOMP 프레임 전송
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> SUBSCRIBE
 destination:/topic/greeting
</code></pre></div>        </div>
      </li>
      <li>수신 및 디코딩 되면 메시지는 <code class="language-plaintext highlighter-rouge">clientInboundChannel</code>로 전달되고</li>
      <li>메시지가 메시지 브로커로 라우팅되어 클라이언트의 구독 정보를 저장</li>
    </ul>
  </li>
  <li>메시지 전송(SEND)
    <ul>
      <li>클라이언트는 아래와 같이 STOMP 프레임 전송
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> SEND
 destination:/app/greeting
</code></pre></div>        </div>
      </li>
      <li><code class="language-plaintext highlighter-rouge">/app</code>접두사를 통해 메시지가 Spring 컨트롤러의 <code class="language-plaintext highlighter-rouge">@MessageMapping</code>메서드로 라우팅</li>
      <li><code class="language-plaintext highlighter-rouge">/app</code> 접두사는 제거되고, <code class="language-plaintext highlighter-rouge">/greeting</code> 부분이 GreetingController의 <code class="language-plaintext highlighter-rouge">@MessageMapping("/greeting")</code> 메서드로 매핑</li>
    </ul>
  </li>
  <li>컨트롤러에서 메시지 처리
    <ul>
      <li>GreetingController의 <code class="language-plaintext highlighter-rouge">@MessageMapping</code> 메서드는 클라이언트의 메시지를 처리하고 결과를 반환</li>
      <li>처리 후 반환된 값은 Spring <code class="language-plaintext highlighter-rouge">Message</code>로 변환되어 <code class="language-plaintext highlighter-rouge">brokerChannel</code>을 통해 메시지 브로커로 전송됨</li>
    </ul>
  </li>
  <li>메시지 전송(MESSAGE)
    <ul>
      <li>메시지 브로커는 매칭된 모든 구독자에게 MESSAGE 프레임을 생성하여 전송
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> MESSAGE
 destination:/topic/greeting
 payload: "Hello, Client!"
</code></pre></div>        </div>
      </li>
      <li>메시지는 <code class="language-plaintext highlighter-rouge">clientOutboundChannel</code>을 통해 WebSocket으로 전송</li>
      <li>클라이언트는 WebSocket 연결을 통해 메시지 수신</li>
    </ul>
  </li>
</ol>

<h3 id="요약">요약</h3>
<ul>
  <li><strong>STOMP(Simple Text Oriented Messaging Protocol)</strong>는 WebSocket 위에서 메시지의 형식과 의미를 표준화하여 메시징 처리를 단순화함.</li>
  <li>Spring에서는 @EnableWebSocketMessageBroker를 통해 STOMP 지원을 활성화하며, 메시지를 컨트롤러(@MessageMapping)나 브로커로 라우팅 가능.</li>
  <li>내장 브로커(Simple Broker)는 /topic(1:N)과 /queue(1:1) 경로를 통해 메시지를 구독자에게 전달하며, 외부 브로커(RabbitMQ, ActiveMQ 등)와도 통합 가능.</li>
  <li>STOMP 메시지는 <code class="language-plaintext highlighter-rouge">clientInboundChannel</code> → <code class="language-plaintext highlighter-rouge">brokerChannel</code> → <code class="language-plaintext highlighter-rouge">clientOutboundChannel</code>의 흐름으로 처리되며, WebSocket 클라이언트 간 실시간 통신을 지원.</li>
  <li>Spring STOMP를 사용하면 WebSocket과 HTTP 기반 메시징을 통합하여 유연한 실시간 애플리케이션을 개발할 수 있음.</li>
</ul>

<p>여기까지 STOMP와 메시지흐름에 대하여 살펴보았다.<br /> 
웹 소켓 통신에 대한 핵심적인 내용은 거의다 정리한 것 같다. 그러나 공식문서에는
매우많은 섹션이 남아있다.<br />
다음 챕터를 마지막으로 공식문서에서 필요할만한 내용만 부가적으로 정리하여 웹소켓에 대한 포스팅을 마치려고한다.
이후 직접 코드를 짜보면서 이해하는 시간을 가져야겠다.</p>]]></content><author><name>HyeonSeo Jang</name><email>jang8195@naver.com</email></author><category term="스프링" /><summary type="html"><![CDATA[이번 포스팅부터는 STOMP에 대하여 알아보자]]></summary></entry><entry><title type="html">스프링 웹 소켓 알아보기(3)</title><link href="https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EC%86%8C%EC%BC%93-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0(3)/" rel="alternate" type="text/html" title="스프링 웹 소켓 알아보기(3)" /><published>2024-11-22T08:50:06+00:00</published><updated>2024-11-22T08:50:06+00:00</updated><id>https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EC%86%8C%EC%BC%93-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0(3)</id><content type="html" xml:base="https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EC%86%8C%EC%BC%93-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0(3)/"><![CDATA[<p>Spring WebSocket API<br />
<!--more-->
이번 포스팅에서는 스프링에서 제공하는 웹소켓 관련 API에 대하여 알아보자.</p>

<h2 id="websockethandler">WebSocketHandler</h2>
<p>WebSocket 서버를 구현하는 방법은 간단하다.</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">WebSocketHandler</code> 인터페이스를 직접 구현</li>
  <li><code class="language-plaintext highlighter-rouge">TextWebSocketHandler</code> 또는 <code class="language-plaintext highlighter-rouge">BinaryWebSocketHanlder</code> 등의 클래스를 상속하여 사용</li>
</ol>

<p>일반적으로는 스프링에서 기본 구현을 제공하는 2번 방식이 더 많이 사용된다고 한다.<br /><br />
<img src="/assets/스프링/WebSocketHandler.png" alt="WebSocketHandler" />
<code class="language-plaintext highlighter-rouge">WebSocketHandler</code> 인터페이스를 직접 구현할 시 재정의해야하는 메소드들은 위와 같다.<br />
메소드 명을 통해 알 수 있듯 커넥션 연결 후, 메시지 처리방식, 전송에러시 등 WebSocket 연결의 라이프사이클과 메시지 처리를 구현하면 된다.<br /></p>

<p>아래는 웹 소켓 구현에 대한 스프링 공식문서의 예제코드이다.<br /></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyHandler</span> <span class="kd">extends</span> <span class="nc">TextWebSocketHandler</span> <span class="o">{</span>

	<span class="nd">@Override</span>
	<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">handleTextMessage</span><span class="o">(</span><span class="nc">WebSocketSession</span> <span class="n">session</span><span class="o">,</span> <span class="nc">TextMessage</span> <span class="n">message</span><span class="o">)</span> <span class="o">{</span>
		<span class="c1">// ...</span>
	<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p><br />
이제 이렇게 구현한 <code class="language-plaintext highlighter-rouge">WebSocketHandler</code>를 특정 URL에 매핑하기 위하여 Configuration파일에 등록하면 된다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="nd">@EnableWebSocket</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">WebSocketConfiguration</span> <span class="kd">implements</span> <span class="nc">WebSocketConfigurer</span> <span class="o">{</span>

	<span class="nd">@Override</span>
	<span class="kd">public</span> <span class="kt">void</span> <span class="nf">registerWebSocketHandlers</span><span class="o">(</span><span class="nc">WebSocketHandlerRegistry</span> <span class="n">registry</span><span class="o">)</span> <span class="o">{</span>
		<span class="n">registry</span><span class="o">.</span><span class="na">addHandler</span><span class="o">(</span><span class="n">myHandler</span><span class="o">(),</span> <span class="s">"/myHandler"</span><span class="o">);</span>
	<span class="o">}</span>

	<span class="nd">@Bean</span>
	<span class="kd">public</span> <span class="nc">WebSocketHandler</span> <span class="nf">myHandler</span><span class="o">()</span> <span class="o">{</span>
		<span class="k">return</span> <span class="k">new</span> <span class="nf">MyHandler</span><span class="o">();</span>
	<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<ul>
  <li>
    <p>WebSocket에 대한 설정 클래스는 <code class="language-plaintext highlighter-rouge">WebSocketConfigurer</code>를 구현해야한다.</p>
  </li>
  <li>
    <p>구현 메소드에서 핸들러와, 매핑 URL을 등록해주면 된다.</p>
  </li>
</ul>

<h4 id="기타-사항">기타 사항</h4>
<ul>
  <li>Spring MVC와 통합하면 <code class="language-plaintext highlighter-rouge">DispatcherServlet</code>에 등록해야함</li>
  <li>그러나 Spring MVC에 의존적인 것이 아닌 <code class="language-plaintext highlighter-rouge">WebSocketRequestHandler</code>를 사용하여 WebSocket 요청을 처리할 수 있음</li>
  <li>WebSocket 표준(JSR-356)은 동시메시지 전송을 허용하지 않으므로 WebSocketSession을<code class="language-plaintext highlighter-rouge">ConcurrentWebScketSessionDecorator</code>를 사용하여 래핑해야함</li>
</ul>

<h2 id="websocket-handshake">WebSocket Handshake</h2>
<p>스프링에서 WebSocket 핸드셰이크 과정을 커스터마이징하거나 확장할 수 있다.</p>
<h4 id="1-handshakeinterceptor-사용">1. HandshakeInterceptor 사용</h4>
<ul>
  <li>핸드셰이크 과정에 개입할 가장 쉬운 방법은 <code class="language-plaintext highlighter-rouge">HandshakeInterceptor</code>를 사용하는 것이다.</li>
  <li><code class="language-plaintext highlighter-rouge">HandshakeIntercpetor</code>는  beforeHandshake와 afterHandshake 메서드를 제공하여 핸드셰이크 전후 작업을 처리할 수 있다.</li>
</ul>

<p>아래는 내장 인터셉터를 사용하여 HTTP 세션 속성을 WebSocket 세션에 전달한다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="nd">@EnableWebSocket</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">WebSocketConfiguration</span> <span class="kd">implements</span> <span class="nc">WebSocketConfigurer</span> <span class="o">{</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">registerWebSocketHandlers</span><span class="o">(</span><span class="nc">WebSocketHandlerRegistry</span> <span class="n">registry</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">registry</span><span class="o">.</span><span class="na">addHandler</span><span class="o">(</span><span class="k">new</span> <span class="nc">MyHandler</span><span class="o">(),</span> <span class="s">"/myHandler"</span><span class="o">)</span>
                <span class="o">.</span><span class="na">addInterceptors</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpSessionHandshakeInterceptor</span><span class="o">());</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h4 id="2-defaulthandshakehandler-상속">2. DefaultHandshakeHandler 상속</h4>
<ul>
  <li>더 고급 작업이 필요할 경우 <code class="language-plaintext highlighter-rouge">DefaultHandshakeHanlder</code>를 상속하여 확장할 수 있다.</li>
</ul>

<h2 id="dispatcherservlet과-requestupgradestrategy">DispatcherServlet과 RequestUpgradeStrategy</h2>
<p>기존 웹소켓 API 표준(JSR-356)에서는 <code class="language-plaintext highlighter-rouge">프론트 컨트롤러</code>라는 개념이 존재하지 않아 핸드셰이크 요청과 HTTP 요청을 따로 처리해야했다.<br />
또한 JSR-356방식은 <code class="language-plaintext highlighter-rouge">DispatcherServlet</code>의 매핑체계와 별도로 작동하여 Spring MVC와 통합되지 않는다.<br />
<strong>이를 위해 스프링에서는 <code class="language-plaintext highlighter-rouge">RequestUpgradeStrategy</code>를 사용하여 <code class="language-plaintext highlighter-rouge">DispatcherServlet</code>에서 WebSocket요청과 HTTP요청을 함께 처리할 수 있도록 구현체를 제공한다.</strong><br />
여기까지 접근할 일은 거의 없을것 같으므로 키워드만 알아두고 필요할때 찾아보자.</p>

<h2 id="websocket-서버-설정">WebSocket 서버 설정</h2>
<p><code class="language-plaintext highlighter-rouge">ServletServerContainerFactoryBean</code>을 활용해 서버 설정을 커스터마이징할 수 있다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">WebSocketConfiguration</span> <span class="o">{</span>

	<span class="nd">@Bean</span>
	<span class="kd">public</span> <span class="nc">ServletServerContainerFactoryBean</span> <span class="nf">createWebSocketContainer</span><span class="o">()</span> <span class="o">{</span>
		<span class="nc">ServletServerContainerFactoryBean</span> <span class="n">container</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ServletServerContainerFactoryBean</span><span class="o">();</span>
		<span class="n">container</span><span class="o">.</span><span class="na">setMaxTextMessageBufferSize</span><span class="o">(</span><span class="mi">8192</span><span class="o">);</span>  <span class="c1">// 텍스트 메시지 버퍼 크기</span>
		<span class="n">container</span><span class="o">.</span><span class="na">setMaxBinaryMessageBufferSize</span><span class="o">(</span><span class="mi">8192</span><span class="o">);</span>  <span class="c1">// 바이너리 메시지 버퍼 크기</span>
		<span class="k">return</span> <span class="n">container</span><span class="o">;</span>
	<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>만약 Jetty서버를 사용한다면 <code class="language-plaintext highlighter-rouge">JettyRequestUpgradeStrategy</code>를 활용해 서버 설정을 커스터마이징 할 수 있다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Bean</span>
	<span class="kd">public</span> <span class="nc">DefaultHandshakeHandler</span> <span class="nf">handshakeHandler</span><span class="o">()</span> <span class="o">{</span>
		<span class="nc">JettyRequestUpgradeStrategy</span> <span class="n">strategy</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">JettyRequestUpgradeStrategy</span><span class="o">();</span>
		<span class="n">strategy</span><span class="o">.</span><span class="na">addWebSocketConfigurer</span><span class="o">(</span><span class="n">configurable</span> <span class="o">-&gt;</span> <span class="o">{</span>
			<span class="n">configurable</span><span class="o">.</span><span class="na">setInputBufferSize</span><span class="o">(</span><span class="mi">8192</span><span class="o">);</span>  <span class="c1">// 입력 버퍼 크기</span>
			<span class="n">configurable</span><span class="o">.</span><span class="na">setIdleTimeout</span><span class="o">(</span><span class="nc">Duration</span><span class="o">.</span><span class="na">ofSeconds</span><span class="o">(</span><span class="mi">600</span><span class="o">));</span>  <span class="c1">// 유휴 타임아웃 설정</span>
		<span class="o">});</span>
		<span class="k">return</span> <span class="k">new</span> <span class="nf">DefaultHandshakeHandler</span><span class="o">(</span><span class="n">strategy</span><span class="o">);</span>
	<span class="o">}</span>
</code></pre></div></div>

<h2 id="allowed-origin">Allowed Origin</h2>
<p>기본적으로 Spring에서 WebSocket은 <code class="language-plaintext highlighter-rouge">same-origin requests</code>만 허용하도록 설정되어있다.<br />
간단하게 <code class="language-plaintext highlighter-rouge">setAllowedOrigins</code>메서드로 Origin을 설정할 수 있다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="nd">@EnableWebSocket</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">WebSocketConfiguration</span> <span class="kd">implements</span> <span class="nc">WebSocketConfigurer</span> <span class="o">{</span>

	<span class="nd">@Override</span>
	<span class="kd">public</span> <span class="kt">void</span> <span class="nf">registerWebSocketHandlers</span><span class="o">(</span><span class="nc">WebSocketHandlerRegistry</span> <span class="n">registry</span><span class="o">)</span> <span class="o">{</span>
		<span class="c1">// 특정 Origin만 허용</span>
		<span class="n">registry</span><span class="o">.</span><span class="na">addHandler</span><span class="o">(</span><span class="n">myHandler</span><span class="o">(),</span> <span class="s">"/myHandler"</span><span class="o">).</span><span class="na">setAllowedOrigins</span><span class="o">(</span><span class="s">"https://mydomain.com"</span><span class="o">);</span>
		
		<span class="c1">// 모든 Origin 허용</span>
		<span class="c1">// registry.addHandler(myHandler(), "/myHandler").setAllowedOrigins("*");</span>
	<span class="o">}</span>

	<span class="nd">@Bean</span>
	<span class="kd">public</span> <span class="nc">WebSocketHandler</span> <span class="nf">myHandler</span><span class="o">()</span> <span class="o">{</span>
		<span class="k">return</span> <span class="k">new</span> <span class="nf">MyHandler</span><span class="o">();</span>
	<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h2 id="마무리">마무리</h2>
<p>여기까지 스프링 공식문서에서 제공하는 주요 WebSocket API에 대하여 알아보았다.<br />
다음 포스팅부터는 STOMP에 대하여 공부해 볼 예정이다. 이후 STOMP가 주요 사용되는곳인 채팅방을 구현하여 이론과 실습까지 겸하면 좋을것같다!</p>]]></content><author><name>HyeonSeo Jang</name><email>jang8195@naver.com</email></author><category term="스프링" /><summary type="html"><![CDATA[Spring WebSocket API]]></summary></entry><entry><title type="html">스프링 웹 소켓 알아보기(2)</title><link href="https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EC%86%8C%EC%BC%93-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0(2)/" rel="alternate" type="text/html" title="스프링 웹 소켓 알아보기(2)" /><published>2024-11-21T10:02:06+00:00</published><updated>2024-11-21T10:02:06+00:00</updated><id>https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EC%86%8C%EC%BC%93-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0(2)</id><content type="html" xml:base="https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EC%86%8C%EC%BC%93-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0(2)/"><![CDATA[<p>웹 소켓이란 뭘까?
<!--more--></p>

<h2 id="웹-소켓-프로토콜">웹 소켓 프로토콜</h2>
<ul>
  <li>웹 소켓 프로토콜은 단일 TCP연결을 통해 <b>클라이언트와 서버가 full-duplex 즉, 전 이중 통신을 수행하는 방법이다.</b></li>
  <li>HTTP와는 다른 TCP 프로토콜이지만 80 및 443 포트를 사용하고 기존 방화벽 규칙을 재사용할 수 있도록 HTTP를 통해 작동하도록 설계되었다.</li>
  <li><code class="language-plaintext highlighter-rouge">WebSocket</code> 상호작용은 HTTP 업그레이드 헤더를 사용하여 업그레이드하거나, WebSocket 프로토콜로 전환하는 HTTP 요청으로 시작한다.
    <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">GET /spring-websocket-portfolio/portfolio HTTP/1.1</span>
<span class="na">Host</span><span class="pi">:</span> <span class="s">localhost:8080</span>
<span class="na">Upgrade</span><span class="pi">:</span> <span class="s">websocket</span>
<span class="na">Connection</span><span class="pi">:</span> <span class="s">Upgrade</span>
<span class="na">Sec-WebSocket-Key</span><span class="pi">:</span> <span class="s">Uc9l9TMkWGbHFD2qnFHltg==</span>
<span class="na">Sec-WebSocket-Protocol</span><span class="pi">:</span> <span class="s">v10.stomp, v11.stomp</span>
<span class="na">Sec-WebSocket-Version</span><span class="pi">:</span> <span class="m">13</span>
<span class="na">Origin</span><span class="pi">:</span> <span class="s">http://localhost:8080</span>
</code></pre></div>    </div>
  </li>
  <li><code class="language-plaintext highlighter-rouge">Upgrade</code>헤더와 Connection은 <code class="language-plaintext highlighter-rouge">Upgrade</code>를 사용해야함</li>
</ul>

<p>이 경우 웹 소켓을 지원하는 서버는 확인했다는 신호로 2xx번대 상태코드가 아닌 <code class="language-plaintext highlighter-rouge">101 Switching Protocols</code>의 리턴이 온다.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">HTTP/1.1 101 Switching Protocols</span>
<span class="na">Upgrade</span><span class="pi">:</span> <span class="s">websocket</span>
<span class="na">Connection</span><span class="pi">:</span> <span class="s">Upgrade</span>
<span class="na">Sec-WebSocket-Accept</span><span class="pi">:</span> <span class="s">1qVdfYHU9hPOl4JYYNXF623Gzn0=</span>
<span class="na">Sec-WebSocket-Protocol</span><span class="pi">:</span> <span class="s">v10.stomp</span>
</code></pre></div></div>
<p>성공적인 핸드셰이크 후, HTTP 업그레이드 요청을 기반으로 하는 TCP 소켓은 클라이언트와 서버 모두가 메시지를 계속 보내고 받을 수 있도록 열려 있는다.</p>

<h2 id="http-vs-websocket">HTTP vs WebSocket</h2>
<p>앞서 말했듯 WebSocket은 HTTP와 호환되도록 설계되었지만 <b>두 프로토콜이 매우 다른 아키텍처와 애플리케이션 프로그래밍 모델을 가져온다는 점을 이해하는 것이 중요하다.</b></p>

<h3 id="http">HTTP</h3>
<ul>
  <li>보통의 REST는 여러 URL로 모델링되며, 애플리케이션과 상호 작용하기 위해 클라이언트는 request-response 스타일로 URL에 접근한다. <br />이후 서버는 HTTP URL, 메서드 및 헤더에 따라 적절한 핸들러로 요청을 라우팅한다.</li>
</ul>

<h3 id="websocket">WebSocket</h3>
<ul>
  <li>그러나 WebSocket에서 일반적으로 초기 연결에 대한 URL하나만 존재한다. 그 후 모든 애플리케이션 메시지는 동일한 TCP 연결에서 흐른다.</li>
  <li>또한 HTTP와 달리 메시지의 <strong>구조나 의미(semantics)</strong>에 대한 표준화된 규약이 없다.</li>
  <li>따라서 WebSocket 클라이언트와 서버는 HTTP 핸드셰이크 과정에서 <code class="language-plaintext highlighter-rouge">Sec-WebSocket-Protocol</code>헤더를 통해 상위 레벨의 메시징 프로토콜을 협상해야한다.</li>
</ul>

<h3 id="stomp">STOMP??</h3>
<p><code class="language-plaintext highlighter-rouge">STOMP</code>는 메시지의 구조와 의미를 표준화하여 클라이언트와 서버 간의 통신을 간소화한다.</p>
<h4 id="특징">특징</h4>
<ul>
  <li>메시징 프로토콜 표준화 : 클라이언트와 서버가 메시지의 의미와 형식을 쉽게 이해할 수 있도록 정의</li>
  <li>Pub/Sub 모델 지원 : 특정 topic에 대한 구독과 메시지 발송이 가능하며 이를 통해 채팅 애플리케이션, 실시간 알림 시스템 구현에 적합</li>
  <li>WebSocket과 통합: WebSocket 핸드셰이크 요청의 Sec-WebSocket-Protocol 헤더에서 v10.stomp, v11.stomp 등을 사용해 STOMP 협상 가능.</li>
</ul>

<h2 id="웹-소켓을-사용하는것이-더-유리할-경우">웹 소켓을 사용하는것이 더 유리할 경우</h2>
<h3 id="websocket을-사용할-필요가-없는-경우">WebSocket을 사용할 필요가 없는 경우</h3>
<ul>
  <li><b>주기적인 업데이트로 충분한 경우</b><br />
뉴스 피드, 이메일 알림, 소셜 미디어 피드와 같은 애플리케이션은 실시간 업데이트가 필요하지만, 몇 분 간격으로 업데이트되는 것으로 충분할 수 있다. 이러한 경우 WebSocket은 오히려 과한 선택이 될 수 있다.</li>
  <li><b>메시지 빈도가 낮은 경우</b><br />
네트워크 상태 모니터링처럼 메시지의 빈도가 낮거나 간헐적으로 발생하는 경우, HTTP 스트리밍이나 폴링 방식으로도 충분히 효율적으로 구현할 수 있다.</li>
</ul>

<h3 id="websocket이-적합한-경우">WebSocket이 적합한 경우</h3>
<ul>
  <li>
    <p><b>실시간 상호작용이 필요한 애플리케이션</b><br />
협업 도구(문서 편집, 화상 회의), 온라인 게임, 금융 애플리케이션(주식 거래, 차트 갱신)과 같이 낮은 지연 시간(Latency)과 높은 응답성을 요구하는 경우에는 WebSocket이 훨씬 적합하다.</p>
  </li>
  <li>
    <p><b>낮은 지연 시간, 높은 빈도, 높은 메시지 볼륨의 조합</b><br />
메시지가 자주 발생하고(고빈도), 데이터의 양이 많으며(고볼륨), 실시간 처리가 필요한 애플리케이션에서 WebSocket은 가장 적합한 선택이다.</p>
  </li>
</ul>]]></content><author><name>HyeonSeo Jang</name><email>jang8195@naver.com</email></author><category term="스프링" /><summary type="html"><![CDATA[웹 소켓이란 뭘까?]]></summary></entry><entry><title type="html">스프링 웹 소켓 알아보기(1)</title><link href="https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EC%86%8C%EC%BC%93-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0/" rel="alternate" type="text/html" title="스프링 웹 소켓 알아보기(1)" /><published>2024-11-21T06:02:06+00:00</published><updated>2024-11-21T06:02:06+00:00</updated><id>https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EC%86%8C%EC%BC%93-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</id><content type="html" xml:base="https://jang-go.github.io/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EC%86%8C%EC%BC%93-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0/"><![CDATA[<p><i>이번 1번 포스팅은 소켓에 대한 학습을 시작하게된 이유와 배경에 대하여 살펴본다.<br />
관심이 없는분은 바로 2번 포스팅으로 넘어가면 좋을것 같다.</i><br /><br /></p>

<p>필자는 현재 졸업을 앞두고있는 4학년 학생이다.<br />
취준을 해야할 시기인데 그거 어떻게하는거지..? 
😂😂<br />
재미있고 흥미로운 프로젝트를 진행하고 그에 대한 깊은 이해를 바탕으로 서류를 꾸미다 보면 어디서든 데려가겠지 라는 마음으로 요즘 지내고있다.<br /><br /></p>

<p>최근 멘탈적으로 힘든 상황에 놓여있다. 생활비도 벌어야하고 취업하려면 공부도 해야하고 그러나 머릿속은 놀고싶고 ㅎ..(아직 정신을 못차린 1人)
<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSXb1-_Uah_CTElvJKhE6wkooSAN3MPFURWRA&amp;s" alt="" /></p>

<p>최근 <code class="language-plaintext highlighter-rouge">비트코인 자동매매</code> 라는 프로젝트를 진행해보고자 하였다. 물론 돈이 부족하다고 비트코인에 댄 건 아니다. 그저 재미있어 보이며 외부 API를 이용해서 진행하는 프로젝트를 해보고싶었기 때문이다.<br /><br /></p>

<p>비트코인은 가격변동이 분 단위로 심하다고 생각하여 스케쥴러보다는 웹 소켓으로 실시간으로 데이터를 받아오는게 좋다고 생각했다.
또한 실시간으로 통신하는 ex) 실시간 채팅 등<br />
기술로 나중에 진행할 프로젝트에 접목시킬 수 있을 아주 좋은 학습주제라고 생각한다.<br /><br /></p>

<p>스프링 시큐리티를 학습했을 때 처럼 공식문서를 통해 학습할 예정이다.<br /> 이제 시작해보자.</p>]]></content><author><name>HyeonSeo Jang</name><email>jang8195@naver.com</email></author><category term="스프링" /><summary type="html"><![CDATA[이번 1번 포스팅은 소켓에 대한 학습을 시작하게된 이유와 배경에 대하여 살펴본다. 관심이 없는분은 바로 2번 포스팅으로 넘어가면 좋을것 같다.]]></summary></entry><entry><title type="html">이펙티브 자바[아이템2]</title><link href="https://jang-go.github.io/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-%EC%95%84%EC%9D%B4%ED%85%9C2/" rel="alternate" type="text/html" title="이펙티브 자바[아이템2]" /><published>2024-10-22T15:10:06+00:00</published><updated>2024-10-22T15:10:06+00:00</updated><id>https://jang-go.github.io/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C%20%EC%9E%90%EB%B0%94%5B%EC%95%84%EC%9D%B4%ED%85%9C2%5D</id><content type="html" xml:base="https://jang-go.github.io/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-%EC%95%84%EC%9D%B4%ED%85%9C2/"><![CDATA[<h2 id="아이템-2-생성자에-매개변수가-많다면-빌더를-고려하라">아이템 2. 생성자에 매개변수가 많다면 빌더를 고려하라.</h2>
<!--more-->
<p>생성자에 매개변수가 많다면 코드가 상당히 지저분해진다. 
또한 생성자의 종류가 많을수록 중복되는 코드가 늘어난다.<br />
이럴땐 <code class="language-plaintext highlighter-rouge">this()</code>키워드를 통해 <b>생성자 체이닝</b>을 고려할 수 있다.<br /></p>
<ul>
  <li>이는 매개변수가 적은 부분에서 같은 클래스내의 다른 생성자를 호출하는 것이다</li>
  <li>IDE의 도움이 없다면 객체를 생성할 때 어떤 파라미터를 넘겨줘야하며, 어떤 필드에 값이 들어가는지 체이닝을 타고가기 때문에 파악하기 까다롭다.</li>
</ul>

<p>혹은 <b>클래스 내의 필수값을</b>지정하기 어려울 수 있다. 때문에 다른 대안으로 <b>자바빈즈 패턴</b>을 사용할 수 있다.(추후 설명)</p>
<ul>
  <li>완벽한 객체를 만드려면 <code class="language-plaintext highlighter-rouge">setter</code>를 여러번 호출해야한다.</li>
  <li><code class="language-plaintext highlighter-rouge">setter</code>를 제공하기 때문에 클래스를 <b>불변으로 만들 수 없다.</b></li>
</ul>

<h3 id="builder">Builder</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">NutritionFacts</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">servingSize</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">servings</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">calories</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">fat</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">sodium</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">carbohydrate</span><span class="o">;</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">Builder</span> <span class="o">{</span>
        <span class="c1">// 필수 매개변수</span>
        <span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">servingSize</span><span class="o">;</span>
        <span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">servings</span><span class="o">;</span>

        <span class="c1">// 선택 매개변수 - 기본값으로 초기화한다.</span>
        <span class="kd">private</span> <span class="kt">int</span> <span class="n">calories</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
        <span class="kd">private</span> <span class="kt">int</span> <span class="n">fat</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
        <span class="kd">private</span> <span class="kt">int</span> <span class="n">sodium</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
        <span class="kd">private</span> <span class="kt">int</span> <span class="n">carbohydrate</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>

        <span class="kd">public</span> <span class="nf">Builder</span><span class="o">(</span><span class="kt">int</span> <span class="n">servingSize</span><span class="o">,</span> <span class="kt">int</span> <span class="n">servings</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">this</span><span class="o">.</span><span class="na">servingSize</span> <span class="o">=</span> <span class="n">servingSize</span><span class="o">;</span>
            <span class="k">this</span><span class="o">.</span><span class="na">servings</span> <span class="o">=</span> <span class="n">servings</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="kd">public</span> <span class="nc">Builder</span> <span class="nf">calories</span><span class="o">(</span><span class="kt">int</span> <span class="n">val</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">calories</span> <span class="o">=</span> <span class="n">val</span><span class="o">;</span>
            <span class="k">return</span> <span class="k">this</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="kd">public</span> <span class="nc">Builder</span> <span class="nf">fat</span><span class="o">(</span><span class="kt">int</span> <span class="n">val</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">fat</span> <span class="o">=</span> <span class="n">val</span><span class="o">;</span>
            <span class="k">return</span> <span class="k">this</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="kd">public</span> <span class="nc">Builder</span> <span class="nf">sodium</span><span class="o">(</span><span class="kt">int</span> <span class="n">val</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">sodium</span> <span class="o">=</span> <span class="n">val</span><span class="o">;</span>
            <span class="k">return</span> <span class="k">this</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="kd">public</span> <span class="nc">Builder</span> <span class="nf">carbohydrate</span><span class="o">(</span><span class="kt">int</span> <span class="n">val</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">carbohydrate</span> <span class="o">=</span> <span class="n">val</span><span class="o">;</span>
            <span class="k">return</span> <span class="k">this</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="kd">public</span> <span class="nc">NutritionFacts</span> <span class="nf">build</span><span class="o">()</span> <span class="o">{</span>
            <span class="k">return</span> <span class="k">new</span> <span class="nf">NutritionFacts</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>
    <span class="kd">private</span> <span class="nf">NutritionFacts</span><span class="o">(</span><span class="nc">Builder</span> <span class="n">builder</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">servingSize</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">servingSize</span><span class="o">;</span>
        <span class="n">servings</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">servings</span><span class="o">;</span>
        <span class="n">calories</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">calories</span><span class="o">;</span>
        <span class="n">fat</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">fat</span><span class="o">;</span>
        <span class="n">sodium</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">sodium</span><span class="o">;</span>
        <span class="n">carbohydrate</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">carbohydrate</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>이렇게 빌더를 만들면 객체의 필수값을 필수로 지정함과 동시에 <code class="language-plaintext highlighter-rouge">immutable</code>한 객체를 만들 수 있다.</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">NutritionFacts</span> <span class="n">build</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Builder</span><span class="o">(</span><span class="mi">100</span><span class="o">,</span> <span class="mi">10</span><span class="o">)</span>
                <span class="o">.</span><span class="na">fat</span><span class="o">(</span><span class="mi">25</span><span class="o">)</span>
                <span class="o">.</span><span class="na">calories</span><span class="o">(</span><span class="mi">300</span><span class="o">)</span>
                <span class="o">.</span><span class="na">sodium</span><span class="o">(</span><span class="mi">100</span><span class="o">)</span>
                <span class="o">.</span><span class="na">carbohydrate</span><span class="o">(</span><span class="mi">100</span><span class="o">)</span>
                <span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<ul>
  <li>
    <p>메서드 체이닝을하여 점층적 생성자보다 클라이언트 코드를 읽기가 훨씬 간결하고, 자바빈즈보다 훨씬 안전하다.</p>
  </li>
  <li>
    <p>그러나 빌더를 위해 너무 많은 코드를 작성해야함.
<br />(Lombok을 통해 빌더 자동생성 가능)</p>
  </li>
  <li>
    <p><b>그러나 Lombok을 통해 자동생성되는 빌더는 빈 아규먼트 생성자를 생성하여 필수값을 지정할 수 없음</b></p>
  </li>
</ul>

<h3 id="자바빈">자바빈</h3>
<p>(주로 GUI에서) 재사용 가능한 소프트웨어 컴포넌트를 위한 규약이라고 생각하면 됨<br /></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">java.beans</code> 패키지 안에 있는 모든 것</li>
  <li>그 중에서도 자바빈이 지켜야 할 규약
    <ul>
      <li>아규먼트 없는 기본 생성자</li>
      <li>getter와 setter 메소드 이름 규약</li>
      <li>Serializable 인터페이스 구현</li>
    </ul>
  </li>
  <li>JPA나 스프링에서는 리플렉션을 사용하여 객체를 조회하거나 설정하기 때문에 자바빈즈 규약을 지켜 getter와 setter를 생성하는 것이 중요</li>
</ul>]]></content><author><name>HyeonSeo Jang</name><email>jang8195@naver.com</email></author><category term="이펙티브" /><category term="자바" /><summary type="html"><![CDATA[아이템 2. 생성자에 매개변수가 많다면 빌더를 고려하라.]]></summary></entry><entry><title type="html">이펙티브 자바[아이템1]</title><link href="https://jang-go.github.io/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-%EC%95%84%EC%9D%B4%ED%85%9C1/" rel="alternate" type="text/html" title="이펙티브 자바[아이템1]" /><published>2024-10-12T13:10:06+00:00</published><updated>2024-10-12T13:10:06+00:00</updated><id>https://jang-go.github.io/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C%20%EC%9E%90%EB%B0%94%5B%EC%95%84%EC%9D%B4%ED%85%9C1%5D</id><content type="html" xml:base="https://jang-go.github.io/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-%EC%95%84%EC%9D%B4%ED%85%9C1/"><![CDATA[<h2 id="아이템-1-생성자-대신-정적-팩터리-메서드를-고려하라">아이템 1. 생성자 대신 정적 팩터리 메서드를 고려하라.</h2>
<!--more-->
<p>이번 아이템의 제목은 생성자로 객체를 생성하는 것과 더불어 static 객체 생성 메서드를 고려하라는 것이다. 왜 그럴까?</p>

<h3 id="장점1-이름을-가질-수-있다">장점1. 이름을 가질 수 있다.</h3>
<p><b>기본적으로 자바에서는 시그니처가 동일한 생성자를 두 개 이상 만들 수 없다.<b></b></b></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Order</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="kt">boolean</span> <span class="n">prime</span><span class="o">;</span>

    <span class="kd">private</span> <span class="kt">boolean</span> <span class="n">urgent</span><span class="o">;</span>

    <span class="kd">private</span> <span class="nc">Product</span> <span class="n">product</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">Order</span><span class="o">(</span><span class="nc">Product</span> <span class="n">product</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">prime</span><span class="o">){</span>
        <span class="k">this</span><span class="o">.</span><span class="na">product</span> <span class="o">=</span> <span class="n">product</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">prime</span> <span class="o">=</span> <span class="n">prime</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="kd">public</span> <span class="nf">Order</span><span class="o">(</span><span class="nc">Product</span> <span class="n">product</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">urgent</span><span class="o">){</span>
        <span class="k">this</span><span class="o">.</span><span class="na">product</span> <span class="o">=</span> <span class="n">product</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">urgent</span> <span class="o">=</span> <span class="n">urgent</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<ul>
  <li>urgent와 prime필드가 둘다 <code class="language-plaintext highlighter-rouge">boolean</code>타입으로 동일하여 생성자 시그니처가 동일하다.</li>
  <li>따라서 위 코드는 컴파일 에러가 발생한다.</li>
  <li>또한 생성자는 이름이 없기 때문에 어떤 객체가 만들어지는지 파악하기 불리하다.</li>
</ul>

<p>위와 같은 이유로 정적 팩토리 메서드를 만들어 객체 생성을 분리하면 아래와 같다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Order</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="kt">boolean</span> <span class="n">prime</span><span class="o">;</span>

    <span class="kd">private</span> <span class="kt">boolean</span> <span class="n">urgent</span><span class="o">;</span>

    <span class="kd">private</span> <span class="nc">Product</span> <span class="n">product</span><span class="o">;</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="nc">Order</span> <span class="nf">primeOrder</span><span class="o">(</span><span class="nc">Product</span> <span class="n">product</span><span class="o">){</span>
        <span class="nc">Order</span> <span class="n">order</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Order</span><span class="o">();</span>
        <span class="n">order</span><span class="o">.</span><span class="na">prime</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
        <span class="n">order</span><span class="o">.</span><span class="na">product</span> <span class="o">=</span> <span class="n">product</span><span class="o">;</span>
        <span class="k">return</span> <span class="n">order</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="nc">Order</span> <span class="nf">urgentOrder</span><span class="o">(</span><span class="nc">Product</span> <span class="n">product</span><span class="o">){</span>
        <span class="nc">Order</span> <span class="n">order</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Order</span><span class="o">();</span>
        <span class="n">order</span><span class="o">.</span><span class="na">urgent</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
        <span class="n">order</span><span class="o">.</span><span class="na">product</span> <span class="o">=</span> <span class="n">product</span><span class="o">;</span>
        <span class="k">return</span> <span class="n">order</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<ul>
  <li>prime, urgent 필드를 각각 지정하여 생성하는  정적 팩터리 메서드를 만들었다.</li>
  <li>따라서 생성자의 시그니처가 동일한 경우 객체 생성에 용이하다.</li>
  <li>메서드 이름으로 어떤 객체가 만들어지는지 알아보기 좋다.</li>
</ul>

<h3 id="장점2-호출될-때마다-인스턴스를-새로-생성하지-않아도-된다">장점2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.</h3>
<p><b> 자바의 생성자는 매번 호출될 때마다 새로운 인스턴스를 만든다.<br /></b></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Settings</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="kt">boolean</span> <span class="n">useAutoSteering</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kt">boolean</span> <span class="n">useABS</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">Difficulty</span> <span class="n">difficulty</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="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="k">new</span> <span class="nc">Settings</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="k">new</span> <span class="nc">Settings</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="k">new</span> <span class="nc">Settings</span><span class="o">());</span>
    <span class="o">}</span>
<span class="o">}</span>

<span class="c1">// 출력</span>
<span class="n">chap1</span><span class="o">.</span><span class="na">item1</span><span class="o">.</span><span class="na">Settings</span><span class="err">@</span><span class="mi">776</span><span class="n">ec8df</span>
<span class="n">chap1</span><span class="o">.</span><span class="na">item1</span><span class="o">.</span><span class="na">Settings</span><span class="err">@</span><span class="mi">4</span><span class="n">eec7777</span>
<span class="n">chap1</span><span class="o">.</span><span class="na">item1</span><span class="o">.</span><span class="na">Settings</span><span class="err">@</span><span class="mi">3</span><span class="n">b07d329</span>
</code></pre></div></div>
<ul>
  <li>인스턴스가 만들어지는 것을 통제할 수 없다.</li>
  <li>즉, 어디서든 인스턴스가 생성되는걸 막지 못한다.</li>
</ul>

<p>여기서 정적 팩터리 메서드와 private 생성자로 무분별한 객체 생성을 방지할 수 있다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Settings</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="kt">boolean</span> <span class="n">useAutoSteering</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kt">boolean</span> <span class="n">useABS</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">Difficulty</span> <span class="n">difficulty</span><span class="o">;</span>

    <span class="kd">private</span> <span class="nf">Settings</span><span class="o">(){}</span>

    <span class="kd">private</span> <span class="kd">final</span> <span class="kd">static</span> <span class="nc">Settings</span> <span class="no">SETTINGS</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Settings</span><span class="o">();</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="nc">Settings</span> <span class="nf">newInstance</span><span class="o">(){</span>
        <span class="k">return</span> <span class="no">SETTINGS</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<ul>
  <li>생성자를 public으로 열어두는 순간부터 인스턴스를 컨트롤할 수 없다.</li>
  <li>이제 다른 클래스에서는 정적 팩터리 메서드를 통하여만 인스턴스를 받아갈 수 있다.</li>
</ul>

<p>아래는 <code class="language-plaintext highlighter-rouge">Boolean.valueOf()</code> 메서드의 구현 코드이다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">...</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Boolean</span> <span class="no">TRUE</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Boolean</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Boolean</span> <span class="no">FALSE</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Boolean</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
<span class="o">...</span>
<span class="nd">@IntrinsicCandidate</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">Boolean</span> <span class="nf">valueOf</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">b</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="o">(</span><span class="n">b</span> <span class="o">?</span> <span class="no">TRUE</span> <span class="o">:</span> <span class="no">FALSE</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<ul>
  <li>Boolean 클래스의 valueOf() 메서드 또한 파라미터에 따라서 미리 정의된 TRUE, FALSE 상수를 리턴한다.</li>
  <li>정적 팩터리 메서드를 이용하면 파라미터에 따라 다른 객체를 리턴하기 좋다.</li>
</ul>

<h3 id="장점34--반환-타입의-하위-타입-객체를-반환할-수-있는-능력이-있다-입력-매개변수에-따라-매번-다른-클래스의-객체를-반환할-수-있다">장점3,4 . 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.</h3>
<p>생성자를 이용하면 해당하는 클래스의 인스턴스만 생성할 수 있다.<br />
<b>그러나 정적 팩터리 메서드는 인터페이스를 리턴할 수 있으며 즉, 인터페이스의 하위 타입도 리턴할 수 있다는 뜻이다.</b>
<br />
<b>따라서 유연하게 객체를 생성할 수 있는 장점이 있다.</b></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">HelloService</span> <span class="o">{</span>
    <span class="nc">String</span> <span class="nf">hello</span><span class="o">();</span>

    <span class="kd">static</span> <span class="nc">HelloService</span> <span class="nf">of</span><span class="o">(</span><span class="nc">String</span> <span class="n">lang</span><span class="o">){</span>
        <span class="k">if</span><span class="o">(</span><span class="n">lang</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="s">"ko"</span><span class="o">)){</span>
            <span class="k">return</span> <span class="k">new</span> <span class="nf">KoreanHelloService</span><span class="o">();</span>
        <span class="o">}</span><span class="k">else</span><span class="o">{</span>
            <span class="k">return</span> <span class="k">new</span> <span class="nf">EnglishHelloService</span><span class="o">();</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>

</code></pre></div></div>
<ul>
  <li>자바 8이후, 인터페이스에도 정적 메서드를 만들 수 있다.</li>
  <li>따라서 xxxFactory클래스를 따로 만들 필요는 없다.</li>
</ul>

<p>이후 아래와 같이 사용할 수 있다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HelloServiceFactory</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">HelloService</span> <span class="n">eng</span> <span class="o">=</span> <span class="nc">HelloService</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"eng"</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">print</span><span class="o">(</span><span class="n">eng</span><span class="o">.</span><span class="na">hello</span><span class="o">());</span>
    <span class="o">}</span>
<span class="o">}</span>

<span class="c1">// 출력</span>
<span class="n">hello</span>
</code></pre></div></div>

<h3 id="장점-5-정적-팩터리-메서드를-작성하는-시점에는-반환할-객체의-클래스가-존재하지-않아도-된다">장점 5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다</h3>
<p>아래와 같이 인터페이스만 있고 구현체가 존재하지 않는다고 가정해보자.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">HelloService</span> <span class="o">{</span>
    <span class="nc">String</span> <span class="nf">hello</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>이후 아래 코드를보자.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HelloServiceFactory</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">ServiceLoader</span><span class="o">&lt;</span><span class="nc">HelloService</span><span class="o">&gt;</span> <span class="n">loader</span> <span class="o">=</span> <span class="nc">ServiceLoader</span><span class="o">.</span><span class="na">load</span><span class="o">(</span><span class="nc">HelloService</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
        <span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">HelloService</span><span class="o">&gt;</span> <span class="n">helloServiceOptional</span> <span class="o">=</span> <span class="n">loader</span><span class="o">.</span><span class="na">findFirst</span><span class="o">();</span>
        <span class="n">helloServiceOptional</span><span class="o">.</span><span class="na">ifPresent</span><span class="o">(</span> <span class="n">h</span> <span class="o">-&gt;</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">h</span><span class="o">.</span><span class="na">hello</span><span class="o">());</span>
        <span class="o">});</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<ul>
  <li>자바에서는 <code class="language-plaintext highlighter-rouge">ServiceLoader.load()</code>라는 classpath내에 있는 구현체를 불러오는 정적 메서드를 제공한다.</li>
  <li><code class="language-plaintext highlighter-rouge">ServiceLoader</code>는 ServiceProvider 프레임워크의 자바 기본 구현체이다.</li>
  <li>구현체를 직접 만들어서 쓸수도 있지만 <b>위의 코드는 인터페이스 기반의 코드이므로 의존성이 생기지 않는다</b></li>
</ul>

<h3 id="단점-1-상속을-하려면-public이나-protected-생성하기-필요하니-정적-팩터리-메서드만-제공하면-하위-클래스를-만들-수-없다">단점 1. 상속을 하려면 public이나 protected 생성하기 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.</h3>
<p>다시 위의 코드를 가져와보자.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Settings</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="kt">boolean</span> <span class="n">useAutoSteering</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kt">boolean</span> <span class="n">useABS</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">Difficulty</span> <span class="n">difficulty</span><span class="o">;</span>

    <span class="kd">private</span> <span class="nf">Settings</span><span class="o">(){}</span>

    <span class="kd">private</span> <span class="kd">final</span> <span class="kd">static</span> <span class="nc">Settings</span> <span class="no">SETTINGS</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Settings</span><span class="o">();</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="nc">Settings</span> <span class="nf">newInstance</span><span class="o">(){</span>
        <span class="k">return</span> <span class="no">SETTINGS</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<ul>
  <li>상속을 하면 부모의 기본생성자가 먼저 호출된다. 그러나 지금 Settings의 기본생성자는 private이다.</li>
  <li>따라서 상속이 불가능하다.</li>
  <li>아래 코드와 같이 Delegation하여 우회적으로 사용하는 것이 장점이라고 보는 견해도 존재한다.
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">AdvencedSettings</span> <span class="o">{</span>
  <span class="nc">Settings</span> <span class="n">settings</span><span class="o">;</span>
  <span class="c1">// Settings의 기능 이용가능</span>
<span class="o">}</span>
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="단점-2-정적-팩터리-메서드는-프로그래머가-찾기-어렵다">단점 2. 정적 팩터리 메서드는 프로그래머가 찾기 어렵다</h3>
<p><code class="language-plaintext highlighter-rouge">javadoc</code>을 만들게 되면 <code class="language-plaintext highlighter-rouge">Constructors</code>와 <code class="language-plaintext highlighter-rouge">Method</code>가 따로 표시된다.<br />
이때 메소드가 많아진다면 어떤 메소드가 인스턴스를 만드는 메소드인지 구분하기 힘들것이다.<br />
따라서 책에서는 흔히 사용하는 NamingPattern을 이용해서 메서드 이름을 구분짓자고 제안한다.</p>
<ul>
  <li>of(), getInstance(), newInstance() 등</li>
</ul>

<h3 id="열거-타입enum">열거 타입(Enum)</h3>
<ul>
  <li>상수 목록을 담을 수 있는 데이터 타입</li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">enum</span> <span class="nc">OrderStatus</span> <span class="o">{</span>
    <span class="no">PREPARING</span><span class="o">,</span> <span class="no">SHIPPED</span><span class="o">,</span> <span class="no">DELIVERING</span><span class="o">,</span> <span class="no">DELIVERED</span>
<span class="o">}</span>
</code></pre></div></div>

<ul>
  <li>일반적으로 주문에는 주문상태가 포함이 된다.</li>
  <li>OrderStatus를 enum으로 두어 상태값을 제한할 수 있다.</li>
</ul>

<p>만약 주문상태를 나타내는 enum이 없다면, String, int 혹은 상수로 주문의 상태를 표현했을 것이다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 0 - 주문 받음</span>
<span class="c1">// 1 - 준비중</span>
<span class="c1">// 2 - 배송중</span>
<span class="c1">// 3 - 배송완료</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">status</span><span class="o">;</span>
</code></pre></div></div>
<ul>
  <li>만약 status에 0,1,2,3 을 제외한 다른 값이 들어오면 안된다.</li>
  <li>따라서 저 필드값을 검증하는 코드가 추가적으로 필요하다.</li>
</ul>

<p>따라서 enum을 사용할 때의 장점은 아래와 같다.</p>
<ul>
  <li>특정한 변수가 가질 수 있는 값을 제한할 수 있다. <b>타입-세이프티 (Type-Safety)</b>를 보장할 수 
있다.</li>
  <li><b>각각의 상수 인스턴스 딱 하나만 만들어진다.</b></li>
</ul>

<h4 id="enum-관련-정보">enum 관련 정보</h4>
<ol>
  <li><code class="language-plaintext highlighter-rouge">values()</code> : enum 상수 value를 모아둔 배열을 리턴해주는 메서드</li>
  <li>enum도 생성자 및 메서드, 필드를 가질 수 있다.
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">enum</span> <span class="nc">OrderStatus</span> <span class="o">{</span>
 <span class="no">PREPARING</span><span class="o">(</span><span class="mi">0</span><span class="o">),</span> <span class="no">SHIPPED</span><span class="o">(</span><span class="mi">1</span><span class="o">),</span> <span class="no">DELIVERING</span><span class="o">(</span><span class="mi">2</span><span class="o">),</span> <span class="no">DELIVERED</span><span class="o">(</span><span class="mi">3</span><span class="o">);</span>

 <span class="kd">private</span> <span class="kt">int</span> <span class="n">n</span><span class="o">;</span>

 <span class="nc">OrderStatus</span><span class="o">(</span><span class="kt">int</span> <span class="n">n</span><span class="o">)</span> <span class="o">{</span>
     <span class="k">this</span><span class="o">.</span><span class="na">n</span> <span class="o">=</span> <span class="n">n</span><span class="o">;</span>
 <span class="o">}</span>
<span class="o">}</span>
</code></pre></div>    </div>
  </li>
  <li>enum 필드간의 비교는 <code class="language-plaintext highlighter-rouge">==</code>을 사용할 것을 권장.
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Order</span> <span class="n">order</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Order</span><span class="o">();</span>
<span class="k">if</span><span class="o">(</span><span class="n">order</span><span class="o">.</span><span class="na">orderStatus</span> <span class="o">==</span> <span class="nc">OrderStatus</span><span class="o">.</span><span class="na">PREPARING</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">"preparing"</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div>    </div>
    <p>이런 상황에서 처음 orderStatus의 상태는 null이다. 만약 equals를 사용하여 비교하면
<code class="language-plaintext highlighter-rouge">NullPointerException</code>이 발생할 것이다.</p>
  </li>
  <li>enum을 key로 사용하는 컬렉션으로는 <code class="language-plaintext highlighter-rouge">EnumSet</code>, <code class="language-plaintext highlighter-rouge">EnumMap</code>을 사용하는 것이 좋다.
    <ul>
      <li>내부적으로 배열을 통해 구현되어 성능적으로 더 우수함</li>
    </ul>
  </li>
</ol>

<h3 id="플라이웨이트-패턴">플라이웨이트 패턴</h3>
<ul>
  <li>객체를 가볍게 만들어 메모리 사용을 줄이는 패턴</li>
  <li>자주 변하는 속성(또는 외적인 속성)과 변하지 않는 속성(또는 내적인 속성)을 분리하고 재사용하여 메모리를 줄일 수 있다.</li>
</ul>

<p>같은 객체가 빈번하게 사용된다면, 어딘가에 저장 혹은 캐싱하여
재사용하는 방향이 좋을 것임.</p>]]></content><author><name>HyeonSeo Jang</name><email>jang8195@naver.com</email></author><category term="이펙티브" /><category term="자바" /><summary type="html"><![CDATA[아이템 1. 생성자 대신 정적 팩터리 메서드를 고려하라.]]></summary></entry><entry><title type="html">커스텀 정렬조건 만들기</title><link href="https://jang-go.github.io/%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%A0%95%EB%A0%AC%EC%A1%B0%EA%B1%B4/" rel="alternate" type="text/html" title="커스텀 정렬조건 만들기" /><published>2024-09-10T02:10:06+00:00</published><updated>2024-09-10T02:10:06+00:00</updated><id>https://jang-go.github.io/%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%A0%95%EB%A0%AC%EC%A1%B0%EA%B1%B4</id><content type="html" xml:base="https://jang-go.github.io/%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%A0%95%EB%A0%AC%EC%A1%B0%EA%B1%B4/"><![CDATA[<p>코딩테스트 문제를 풀다보면 배열 혹은 컬렉션을 정렬해야하는 상황이 매우 많다.<br />
<!--more--></p>

<p>직접 구현하자니 반복문 때문에 코드의 복잡성이 증가하고 가독성이 떨어졌다. 그럴때마다 자동적으로 <code class="language-plaintext highlighter-rouge">Arrays.sort()</code> 혹은 <code class="language-plaintext highlighter-rouge">Collections.sort()</code>를 사용하는 경우가 많았다.<br />
하지만 <b>정렬조건이 달라진다면</b> 위와 같은 방법은 무용지물이 된다.<br />
오늘은 자바에서 제공하는 <code class="language-plaintext highlighter-rouge">sort</code> 메서드에 대하여 살펴보고 배열과 컬렉션에 정렬조건을 지정하는 방법을 살펴보려고 한다.</p>

<h2 id="arrayssort">Arrays.sort()</h2>
<p>자바에 기본적으로 포함되는 <code class="language-plaintext highlighter-rouge">java.base</code>모듈에는 <code class="language-plaintext highlighter-rouge">java.util</code>패키지가 있고 이 패키지 내부에 <code class="language-plaintext highlighter-rouge">Arrays</code>라는 클래스가 존재한다.<br />
클래스 이름과 맞게 배열과 관련된 다양한 메서드를 제공한다. 자바api 문서에서 제공하는 정렬관련 메서드의 종류를 살펴보자.
<img src="/assets/자바/sort.png" alt="정렬" /></p>
<ul>
  <li>모든 기본타입 배열을 파라미터로 받도록 오버로딩 되어있다. 따라서 모든 기본타입 배열을 <code class="language-plaintext highlighter-rouge">Arrays.sort()</code>메서드에 제공할 수 있다.</li>
  <li>2, 3번째 파라미터로 시작인덱스, 끝 인덱스를 주어 정렬 범위를 설정할 수 있다.</li>
  <li>기본적으로 모든 배열은 <code class="language-plaintext highlighter-rouge">ascending order</code>로 정렬을 수행한다.</li>
  <li><code class="language-plaintext highlighter-rouge">Comparator</code>객체를 마지막 파라미터로 받는 sort의 경우 comparator에서 특정한 방식대로 정렬을 수행한다.</li>
</ul>

<p>따라서 <code class="language-plaintext highlighter-rouge">Comparator</code>를 어떻게 구현하냐에 따라서 우리는 정렬방식을 자유자재로 설정할 수 있는 것이다.<br />
<code class="language-plaintext highlighter-rouge">Comparator</code>를 알아보기 전에 sort메서드의 동작 방식부터 알아보자.</p>

<h3 id="arrayssort-동작방식">Arrays.sort() 동작방식</h3>
<p><code class="language-plaintext highlighter-rouge">sort()</code> 메서드는 기본타입과 참조타입에 대하여 서로 다른 알고리즘을 사용한다.</p>
<h4 id="기본타입">기본타입</h4>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
 * Sorting methods. Note that all public "sort" methods take the
 * same form: performing argument checks if necessary, and then
 * expanding arguments into those required for the internal
 * implementation methods residing in other package-private
 * classes (except for legacyMergeSort, included in this class).
 */</span>

<span class="cm">/**
 * Sorts the specified array into ascending numerical order.
 *
 * @implNote The sorting algorithm is a Dual-Pivot Quicksort
 * by Vladimir Yaroslavskiy, Jon Bentley, and Joshua Bloch. This algorithm
 * offers O(n log(n)) performance on all data sets, and is typically
 * faster than traditional (one-pivot) Quicksort implementations.
 *
 * @param a the array to be sorted
 */</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">sort</span><span class="o">(</span><span class="kt">int</span><span class="o">[]</span> <span class="n">a</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">DualPivotQuicksort</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="n">a</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="n">a</span><span class="o">.</span><span class="na">length</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>모든 기본타입 배열의 sort에서는 <code class="language-plaintext highlighter-rouge">Dual-Pivot QuickSort</code>알고리즘을 사용하여 정렬한다.<br />
implNote에 따라 <code class="language-plaintext highlighter-rouge">Dual-Pivot QuickSort</code>는 O(n log(n))의 시간복잡도를 제공하며 one-pivot Quicksort에 비하여 더욱 빠르다고 한다.
<br /><br />
퀵정렬같은 경우는 기본적으로 피벗을 정하여 피벗보다 값이 큰부분, 작은부분으로 나누어 재귀적으로 정렬을 수행한다.<br />
그러나 <code class="language-plaintext highlighter-rouge">Dual-Pivot QuickSort</code>는 피벗을 두개를 가지며 피벗보다 작은부분, 피벗과 피벗의 사이부분, 피벗보다 큰 부분 3가지로 나뉘어 각 부분에서 정렬이 재귀적으로 수행된다.<br />
만일 피벗을 잘못 정하게 될 경우에는 배열이 비효율적으로 분할되어 O(n^2)의 시간복잡도를 가지게 된다.</p>

<h4 id="참조타입">참조타입</h4>
<p>참조타입 배열을 정렬할 때는 <code class="language-plaintext highlighter-rouge">TimSort</code>알고리즘을 사용한다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
 * Sorts the specified array of objects into ascending order, according to
 * the natural ordering of its elements. The array must be mutually
 * comparable, i.e., for any pair of elements e1 and e2 in the array,
 * e1.compareTo(e2) must not throw a ClassCastException.
 *
 * @param a the array to be sorted
 */</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">sort</span><span class="o">(</span><span class="nc">Object</span><span class="o">[]</span> <span class="n">a</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="nc">LegacyMergeSort</span><span class="o">.</span><span class="na">userRequested</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">legacyMergeSort</span><span class="o">(</span><span class="n">a</span><span class="o">);</span>
    <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
        <span class="nc">TimSort</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="n">a</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">TimSort</code>는 <strong>합병 정렬(Merge Sort)</strong>과 <strong>삽입 정렬(Insertion Sort)</strong>의 장점을 결합하여 설계된 알고리즘으로, 다음과 같은 특징을 가진다.</p>

<ul>
  <li>stable: TimSort는 안정적이므로, 동일한 값을 가진 요소들의 순서는 정렬 후에도 유지된다.</li>
  <li>부분적으로 정렬된 데이터에 최적화: 입력 배열이 이미 부분적으로 정렬된 경우, 성능이 크게 향상된다. 이미 거의 정렬된 데이터가 들어오면, TimSort는 그 데이터에서 적은 비교 횟수로 빠르게 정렬할 수 있다.</li>
  <li>최악의 경우에도 O(n log n): 최악의 경우에도 O(n log n)의 시간 복잡도를 보장한다. 이는 기본적인 합병 정렬과 동일한 성능이다.</li>
</ul>

<h2 id="comparator">Comparator</h2>
<p>위에서 말한대로 <code class="language-plaintext highlighter-rouge">Comparator</code>를 사용하여 커스텀 정렬조건을 지정할 수 있다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@FunctionalInterface</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">Comparator</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;{</span>
    <span class="kt">int</span> <span class="nf">compare</span><span class="o">(</span><span class="no">T</span> <span class="n">o1</span><span class="o">,</span> <span class="no">T</span> <span class="n">o2</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<ul>
  <li>Comparator는 인터페이스로 <code class="language-plaintext highlighter-rouge">compare()</code>라는 추상메소드 하나만 가지는 FunctionalInterface이다.</li>
  <li><code class="language-plaintext highlighter-rouge">compare()</code> 메소드는 T타입의 두 객체를 비교하여 정렬조건을 설정한다.</li>
  <li>함수형 인터페이스이기 때문에 람다식으로도 작성할 수 있다.</li>
</ul>

<p>예시 코드를 작성해보며 알아보자.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SortPractice</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">Integer</span><span class="o">[]</span> <span class="n">arr</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Integer</span><span class="o">[]{</span><span class="mi">3</span><span class="o">,</span><span class="mi">1</span><span class="o">,</span><span class="mi">8</span><span class="o">,</span><span class="mi">16</span><span class="o">,</span><span class="mi">78</span><span class="o">};</span>
        <span class="nc">Arrays</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="n">arr</span><span class="o">,</span> <span class="k">new</span> <span class="nc">MyComparator</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="nc">Arrays</span><span class="o">.</span><span class="na">toString</span><span class="o">(</span><span class="n">arr</span><span class="o">));</span>
    <span class="o">}</span>
    <span class="kd">static</span> <span class="kd">class</span> <span class="nc">MyComparator</span> <span class="kd">implements</span> <span class="nc">Comparator</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;{</span>
        <span class="nd">@Override</span>
        <span class="kd">public</span> <span class="kt">int</span> <span class="nf">compare</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">o1</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">o2</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="n">o1</span><span class="o">-</span><span class="n">o2</span><span class="o">;</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>

<span class="c1">// 출력</span>
<span class="o">[</span><span class="mi">1</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">8</span><span class="o">,</span> <span class="mi">16</span><span class="o">,</span> <span class="mi">78</span><span class="o">]</span>
</code></pre></div></div>
<ul>
  <li>compare 메소드의 o1, o2로 배열의 두 요소씩 비교된다.</li>
</ul>

<h4 id="compare-메서드의-반환값에-따른-결과">compare() 메서드의 반환값에 따른 결과</h4>
<ol>
  <li>o1 &lt; o2(음수) : o1이 o2보다 작을 시 두 요소는 그대로 두고 다음 비교로 넘어간다. 즉, o1이 앞에 위치한다.</li>
  <li>o1 &gt; o2(양수) : o1이 o2보다 클 시 두 요소의 위치를 교환한다. 즉, o1이 뒤로가고 o2가 앞으로 온다.</li>
  <li>o1 == o2 : 두 요소가 같을 시 두 요소의 순서는 그대로 유지된다.</li>
</ol>

<p>따라서 compare 메소드를 적절히 정의하여 정렬 조건을 설정하면 된다.</p>
<h4 id="내림차순">내림차순</h4>
<p>단순히 compare 메소드의 반환값을 수정해주면 된다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">static</span> <span class="kd">class</span> <span class="nc">MyComparator</span> <span class="kd">implements</span> <span class="nc">Comparator</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;{</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">int</span> <span class="nf">compare</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">o1</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">o2</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">o2</span><span class="o">-</span><span class="n">o1</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>

<span class="c1">// 출력</span>
<span class="o">[</span><span class="mi">78</span><span class="o">,</span> <span class="mi">16</span><span class="o">,</span> <span class="mi">8</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">1</span><span class="o">]</span>
</code></pre></div></div>
<h4 id="collectionssort에-적용하기">Collections.sort()에 적용하기</h4>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SortPractice</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">List</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;(</span><span class="nc">Arrays</span><span class="o">.</span><span class="na">asList</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span><span class="mi">8</span><span class="o">,</span><span class="mi">17</span><span class="o">,</span><span class="mi">81</span><span class="o">));</span>
        <span class="nc">Collections</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="n">list</span><span class="o">,</span> <span class="o">(</span><span class="n">o1</span><span class="o">,</span> <span class="n">o2</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">o2</span><span class="o">-</span><span class="n">o1</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">list</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
<span class="c1">// 출력</span>
<span class="o">[</span><span class="mi">81</span><span class="o">,</span> <span class="mi">17</span><span class="o">,</span> <span class="mi">8</span><span class="o">,</span> <span class="mi">1</span><span class="o">]</span>
</code></pre></div></div>
<ul>
  <li>마찬가지로 <code class="language-plaintext highlighter-rouge">Collections.sort()</code>메서드의 두번째 인자에 Comparator를 넘겨주면 된다.</li>
  <li>이번에는 람다식을 이용하여 Comparator를 구현하였다. 함수형 인터페이스이기 때문에 가능하다.</li>
</ul>

<h4 id="복잡한-정렬조건-설정하기">복잡한 정렬조건 설정하기</h4>
<p>상황을 가정해보자. 만약 학생리스트가 있고 학생은 번호, 키, 나이 필드를 가진다고 생각해보자.<br />
이때 학생을 나이를 기준으로 오름차순 정렬하되, 나이가 같으면 키를 기준으로 내림차순 정렬해보자.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SortPractice</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">List</span><span class="o">&lt;</span><span class="nc">Student</span><span class="o">&gt;</span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;();</span>
        <span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">Student</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">173</span><span class="o">,</span> <span class="mi">24</span><span class="o">));</span>
        <span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">Student</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">191</span><span class="o">,</span> <span class="mi">24</span><span class="o">));</span>
        <span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">Student</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="mi">181</span><span class="o">,</span> <span class="mi">21</span><span class="o">));</span>
        <span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">Student</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="mi">158</span><span class="o">,</span> <span class="mi">21</span><span class="o">));</span>
        <span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">Student</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="mi">168</span><span class="o">,</span> <span class="mi">28</span><span class="o">));</span>

        <span class="nc">Collections</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="n">list</span><span class="o">,</span> <span class="k">new</span> <span class="nc">Comparator</span><span class="o">&lt;</span><span class="nc">Student</span><span class="o">&gt;()</span> <span class="o">{</span>
            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="kt">int</span> <span class="nf">compare</span><span class="o">(</span><span class="nc">Student</span> <span class="n">o1</span><span class="o">,</span> <span class="nc">Student</span> <span class="n">o2</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">if</span><span class="o">(</span><span class="n">o1</span><span class="o">.</span><span class="na">age</span><span class="o">==</span><span class="n">o2</span><span class="o">.</span><span class="na">age</span><span class="o">)</span> <span class="k">return</span> <span class="n">o2</span><span class="o">.</span><span class="na">height</span><span class="o">-</span><span class="n">o1</span><span class="o">.</span><span class="na">height</span><span class="o">;</span>
                <span class="k">else</span> <span class="k">return</span> <span class="n">o1</span><span class="o">.</span><span class="na">age</span><span class="o">-</span><span class="n">o2</span><span class="o">.</span><span class="na">age</span><span class="o">;</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="n">list</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="kd">static</span> <span class="kd">class</span> <span class="nc">Student</span><span class="o">{</span>
        <span class="kt">int</span> <span class="n">number</span><span class="o">;</span>
        <span class="kt">int</span> <span class="n">height</span><span class="o">;</span>
        <span class="kt">int</span> <span class="n">age</span><span class="o">;</span>

        <span class="kd">public</span> <span class="nf">Student</span><span class="o">(</span><span class="kt">int</span> <span class="n">number</span><span class="o">,</span> <span class="kt">int</span> <span class="n">height</span><span class="o">,</span> <span class="kt">int</span> <span class="n">age</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">this</span><span class="o">.</span><span class="na">number</span> <span class="o">=</span> <span class="n">number</span><span class="o">;</span>
            <span class="k">this</span><span class="o">.</span><span class="na">height</span> <span class="o">=</span> <span class="n">height</span><span class="o">;</span>
            <span class="k">this</span><span class="o">.</span><span class="na">age</span> <span class="o">=</span> <span class="n">age</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="nd">@Override</span>
        <span class="kd">public</span> <span class="nc">String</span> <span class="nf">toString</span><span class="o">()</span> <span class="o">{</span>
            <span class="k">return</span> <span class="s">"Student{"</span> <span class="o">+</span>
                    <span class="s">"number="</span> <span class="o">+</span> <span class="n">number</span> <span class="o">+</span>
                    <span class="s">", height="</span> <span class="o">+</span> <span class="n">height</span> <span class="o">+</span>
                    <span class="s">", age="</span> <span class="o">+</span> <span class="n">age</span> <span class="o">+</span>
                    <span class="sc">'}'</span><span class="o">;</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>
<span class="c1">// 출력</span>
<span class="o">[</span><span class="nc">Student</span><span class="o">{</span><span class="n">number</span><span class="o">=</span><span class="mi">3</span><span class="o">,</span> <span class="n">height</span><span class="o">=</span><span class="mi">181</span><span class="o">,</span> <span class="n">age</span><span class="o">=</span><span class="mi">21</span><span class="o">},</span> <span class="nc">Student</span><span class="o">{</span><span class="n">number</span><span class="o">=</span><span class="mi">4</span><span class="o">,</span> <span class="n">height</span><span class="o">=</span><span class="mi">158</span><span class="o">,</span> <span class="n">age</span><span class="o">=</span><span class="mi">21</span><span class="o">},</span> <span class="nc">Student</span><span class="o">{</span><span class="n">number</span><span class="o">=</span><span class="mi">2</span><span class="o">,</span> <span class="n">height</span><span class="o">=</span><span class="mi">191</span><span class="o">,</span> <span class="n">age</span><span class="o">=</span><span class="mi">24</span><span class="o">},</span> <span class="nc">Student</span><span class="o">{</span><span class="n">number</span><span class="o">=</span><span class="mi">1</span><span class="o">,</span> <span class="n">height</span><span class="o">=</span><span class="mi">173</span><span class="o">,</span> <span class="n">age</span><span class="o">=</span><span class="mi">24</span><span class="o">},</span> <span class="nc">Student</span><span class="o">{</span><span class="n">number</span><span class="o">=</span><span class="mi">5</span><span class="o">,</span> <span class="n">height</span><span class="o">=</span><span class="mi">168</span><span class="o">,</span> <span class="n">age</span><span class="o">=</span><span class="mi">28</span><span class="o">}]</span>
</code></pre></div></div>
<p>이처럼 <code class="language-plaintext highlighter-rouge">Comparator</code>의 구현을 변경하여 정렬조건을 설정할 수 있음을 알아보았다.</p>]]></content><author><name>HyeonSeo Jang</name><email>jang8195@naver.com</email></author><category term="자바" /><summary type="html"><![CDATA[코딩테스트 문제를 풀다보면 배열 혹은 컬렉션을 정렬해야하는 상황이 매우 많다.]]></summary></entry></feed>