<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>gwjeon.log</title>
        <link>https://velog.io/</link>
        <description>ansuzh</description>
        <lastBuildDate>Mon, 14 Feb 2022 13:25:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>gwjeon.log</title>
            <url>https://images.velog.io/images/won-developer/profile/b9c71fdb-5e7f-43ec-989e-d3c4380f2bb9/075A1BC9-2675-45D9-8551-E1F41A2AA1D6.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. gwjeon.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/won-developer" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[로그인 구현 - 쿠키와 세션]]></title>
            <link>https://velog.io/@won-developer/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84-%EC%BF%A0%ED%82%A4%EC%99%80-%EC%84%B8%EC%85%98-n3wwrtip</link>
            <guid>https://velog.io/@won-developer/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84-%EC%BF%A0%ED%82%A4%EC%99%80-%EC%84%B8%EC%85%98-n3wwrtip</guid>
            <pubDate>Mon, 14 Feb 2022 13:25:54 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/won-developer/post/9d4b3828-8128-46a7-99db-6ef9c5e6de23/image.png" alt=""></p>
<hr>
<blockquote>
<p>학습참조
<a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard">인프런-스프링 MVC 2편 백엔드 웹 개발 활용 기술(김영한님)</a></p>
</blockquote>
<hr>
<p>쿠키와 세션을 이용한 간단한 로그인을 구현해보자! 우선 쿠키와 세션이 무엇인지에 대해 간단하게 알아보자.</p>
<h1 id="✅-쿠키와-세션">✅ 쿠키와 세션</h1>
<h2 id="✔-쿠키cookie">✔ 쿠키(Cookie)</h2>
<p>쿠키란 웹을 사용하는 유저들에게 편리함을 제공하거나, 이를 이용해 정보를 수집할 수 있는 브라우저에 저장하게 되는 작은 텍스트이다. 
예를 들어 &#39;Hello&#39;라는 ID를 가지는 유저가 로그인후 잠시 사이트를 닫은후 다시 재접속하여도 로그인 상태를 유지하게끔 하는데, 이는 바로 쿠키를 이용한 것이다. 
웹사이트에 접속하면 브라우저는 서버로 요청을 보내는데 서버에서는 해당 요청에 대한 응답에 쿠키를 만들어 브라우저로 보낸다. 해당 브라우저는 응답받은 쿠키를 쿠키저장소에 보관하게 되며, 이후 다시 유저가 웹사이트에 접속하게 되면 쿠키저장소에서 이전에 서버에서 발급해준 쿠키정보를 서버로 요청을 보내게 되는데, 서버에서는 이 쿠키값을 확인하여 재접속이더라도 계속 로그인 상태를 유지할수 있게끔 한다.</p>
<p><img src="https://images.velog.io/images/won-developer/post/4e041109-8226-4652-95b3-6687edbe1fa2/image.png" alt=""></p>
<h2 id="✔-세션session">✔ 세션(Session)</h2>
<p>세션이란 쿠키의 보안 이슈에 대해 이를 방지하고자 나온 개념이다. 쿠키만을 이용해서 로그인 또는 사용자를 식별하려면 매우 심각한 보안이슈가 일어난다. 쿠키에는 다음과 같은 여러가지 보안이슈가 있다.</p>
<h3 id="🚩-쿠키의-보안-문제">🚩 쿠키의 보안 문제</h3>
<ul>
<li>쿠키 값은 임의로 변경할 수 있다.<ul>
<li>웹브라우저의 개발자모드에서 쿠키를 변경할 수 있는데, 클라이언트가 쿠키를 강제로 변경하면 다른 사용자인 것처럼 사용할 수 있다.</li>
</ul>
</li>
<li>쿠키에 보관된 정보는 훔쳐갈 수 있다.<ul>
<li>만약 쿠키에 개인정보나, 신용카드 정보가 있다면? 이 정보는 브라우저의 쿠키저장소에 저장되고 네트워크 요청마다 서버로 전달된다. 나의 LOCAL PC의 쿠키가 털릴수도 있고, 네트워크 전송구간에서도 해커에게 털릴수가 있다.</li>
<li>만약 해커가 사용자A의 쿠키를 탈취하여 자신의 브라우저에 쿠키를 입력후 접속하게 된다면 사용자A인 마냥 서비스를 이용할 수 있다.</li>
</ul>
</li>
<li>해커가 쿠키를 한번 훔쳐가면 평생 사용할 수 있다.<ul>
<li>서버가 보관하고 있는 해당 쿠키에 대한 정보를 지우지 않는 이상은 무한대로 악의적인 요청을 할 수도 있다.</li>
</ul>
</li>
</ul>
<p>위와 같이 쿠키는 직접 추가, 변경이 가능할 뿐더러 해커가 탈취해가기도 매우 쉬운 환경에 놓여있다. 만약 사용자의 민감한 개인정보를 쿠키에 담을경우 심각한 보안문제가 발생할 것이다. 세션은 이를 위한 대안이다.</p>
<h3 id="🚩-세션을-이용한-대안">🚩 세션을 이용한 대안</h3>
<ul>
<li>쿠키에는 중요한 정보를 보관하지 않고, 서버에서는 예측 불가능한 임의의 토큰(랜덤값)을 쿠키로 생성하여 브라우저에게 응답하여야 한다. 여기서 예측 불가능이란 숫자 1다음은 2인것처럼 이러한 예측이 불가능 해야한다. 만약 예측이 가능할 경우 해커가 값을 예측하여 쿠키를 변경후 악의적인 접근을 시도 할 수 있다.</li>
<li>서버에서는 예측 불가능한 임의의 토큰(랜덤값)을 쿠키로 생성하여 클라이언트에게 응답하고 서버에서는 해당 임의의 토큰값을 가지는 쿠키를 전달받아 사용자의 정보를 매핑하여 관리한다.</li>
<li>만약 해커가 임의의 토큰(랜덤값)을 가지는 쿠키를 털어간다고 해도 일정한 시간이 지나면 사용할 수 없도록 서버에서는 해당 토큰값에 대해 유효시간을 설정하여 해당 유효시간이 지날경우 토큰정보를 삭제하고, 또한 해킹이 의심되는 경우 서버에서 해당 토큰을 강제로 제거한다.</li>
</ul>
<p>이와 같이 사용자의 중요한 정보는 서버에서 관리하고 임의의 토큰(랜덤값)을 쿠키로 만들어 브라우저에게 응답후, 이 쿠키값을 가지고 클라이언트와 서버의 연결을 유지하는 방법을 세션이라고 한다.
<img src="https://images.velog.io/images/won-developer/post/854202a9-9d44-4e9d-8cd1-409b5cd99627/image.png" alt=""></p>
<p>여기까지 쿠키와 세션을 간단히 알아보았고 이제 구현으로 들어가보자.</p>
<hr>
<h1 id="✅-실전-ㄱㄱ">✅ 실전 ㄱㄱ</h1>
<p>직접 Session을 관리하는 SessionManager와 Servlet에서 제공하는 Session을 사용하여 두가지 방식으로 구현을 해볼 것이다. 해당 구현의 소스코드는 <a href="https://github.com/gwjeondev/loginTest">https://github.com/gwjeondev/loginTest</a> 에서 확인할 수 있다!</p>
<p>디렉토리는 다음과 같다.
<img src="https://images.velog.io/images/won-developer/post/76635b4f-6cbd-44b4-8f95-a1742cae0563/image.png" alt=""></p>
<h2 id="✔-사전-요구사항">✔ 사전 요구사항</h2>
<ul>
<li>아이디를 hong-gildong, 패스워드를 1234로 가지는 회원을 미리 등록함</li>
<li>localhost:8080에 접속시 session 체크후 로그인화면으로 이동 또는 home화면으로 이동<ul>
<li>로그인 화면
<img src="https://images.velog.io/images/won-developer/post/292888d6-7c5a-413c-89b8-17c173edd04f/image.png" alt=""></li>
<li>home 화면
<img src="https://images.velog.io/images/won-developer/post/2456941e-d240-4ca5-87c8-5495f8b0a96f/image.png" alt=""></li>
</ul>
</li>
<li>View: Thymeleaf</li>
<li>Server: Spring</li>
</ul>
<hr>
<h2 id="✔-sessionmanager">✔ SessionManager</h2>
<h3 id="🚩-로그인처리후-session-발급">🚩 로그인처리후 Session 발급</h3>
<p><img src="https://images.velog.io/images/won-developer/post/d605d8bb-7298-4188-88c4-a4b6025772b1/image.png" alt="">
localhost:8080 접속시 로그인 페이지가 나온다. 로그인 ID와 비밀번호를 입력하여 접속하자. 회원 정보는 사전에 등록해두었다. 전송버튼 클릭시 아래 Controller로 요청된다.</p>
<h4 id="controller">Controller</h4>
<pre><code class="language-java">@PostMapping(&quot;/login&quot;)
public String login(@ModelAttribute Member member, HttpServletResponse response) {
  Member loginMember = loginService.login(member.getMemberId(), member.getPassword()); //(1)

  if(loginMember == null) { //(2)
      return &quot;redirect:/&quot;;
  }

  sessionManager.createSession(loginMember.getMemberId(), response); //(3)
  return &quot;redirect:/&quot;; //(4)
}</code></pre>
<ul>
<li><p>(1): 입력된 회원ID와 Password를 가지는 회원이 있는지 확인한다. 올바른 정보인경우 Member객체가 반환되고, 아니라면 null이 반환된다.</p>
</li>
<li><p>(2): null일 경우 올바른 정보가 입력되지 않았으므로 /로 redirect 시킨다.</p>
</li>
<li><p>(3): 요청받은 정보가 올바른 회원정보임이 확인되었으니, 세션을 생성한다. MemberId와 response를 argument로 sessionManager.createSession를 호출한다.</p>
<h4 id="sessionmanager">SessionManager</h4>
<pre><code class="language-java">public void createSession(String value, HttpServletResponse response) {
    String token = UUID.randomUUID().toString(); //(1)
    store.put(token, value); //(2)
    Cookie cookie = new Cookie(SessionConst.sessionId, token); //(3)
    response.addCookie(cookie); //(4)
}</code></pre>
<ul>
<li><p>(1): 예측불가한 임의의 token값을 생성한다. UUID.randomUUID()는 중복될 확률은 불가능하다고 봐도 무방하고 자세한건 따로 알아보자.</p>
</li>
<li><p>(2): (1)에서 생성한 token값을 Key로, Parameter로 전달받은 memberId를 Value로 Store Map(세션 저장소)에 put한다.</p>
</li>
<li><p>(3): SessionConst.sessionId는 미리 interface로 생성해둔 상수이다. SessionConst.sessionId를 Key로, (1)에서 생성한 token을 Value로 하여 Cookie를 생성한다.</p>
<pre><code class="language-java">public interface SessionConst {
  String sessionId = &quot;LOGIN_MEMBER&quot;;
}</code></pre>
</li>
<li><p>(4): response에 Cookie를 등록한다.</p>
</li>
</ul>
</li>
<li><p>(4): 정상적으로 세션저장소에 회원정보를 등록하고, response에도 쿠키에 세션정보를 실어 등록하였으므로 /으로 redirect한다.</p>
</li>
</ul>
<hr>
<h4 id="controller-1">Controller</h4>
<pre><code class="language-java">@GetMapping(&quot;/&quot;)
public String home(HttpServletRequest request, Model model) {
    String memberId = sessionManager.getSession(request); //(1)
    if(memberId == null) { //(2)
        return &quot;login&quot;;
    }
    Optional&lt;Member&gt; findMemberOptional = memberRepository.findByMemberId(memberId); //(3)
    Member member = findMemberOptional.orElse(null); //(4)
    if(member == null) { //(4)
        return &quot;login&quot;;
    }
    model.addAttribute(&quot;member&quot;, member); //(5)
    return &quot;home&quot;; //(5)
}</code></pre>
<p>URL /요청을 처리하는 Controller이다.</p>
<ul>
<li><p>(1): 세션정보를 가져오기 위하여 request를 argument로 sessionManager.getSession를 호출한다.</p>
<h4 id="sessionmanager-1">SessionManager</h4>
<pre><code class="language-java">public String getSession(HttpServletRequest request) {
    Cookie sessionCookie = findCookie(request); //(1)
    if(sessionCookie == null) { //(2)
        return null;
    }
    return store.get(sessionCookie.getValue()); //(3)
}</code></pre>
<ul>
<li><p>(1): request요청의 Cookie에 서버에서 관리되는 쿠키정보가 있는지 찾는다. reqeust를 argument로 findCookie를 호출한다.</p>
<pre><code class="language-java">public Cookie findCookie(HttpServletRequest request) {
    //request 요청에 cookie가 없을 경우 null
    if(request.getCookies() == null) { //(1)
        return null;
    }
    return Arrays.stream(request.getCookies()) //(2)
            .filter(cookie -&gt; cookie.getName().equals(SessionConst.sessionId))
            .findFirst()
            .orElse(null);
}</code></pre>
<ul>
<li>(1): request 요청에 Cookie가 없을 경우 null을 return 한다.</li>
<li>(2): request 요청에 Cookie중 서버에서 관리하는 Session Key인 상수<code>SessionConst.sessionId</code> 즉 <code>LOGIN_MEMBER</code>가 있는지 찾고 있다면 해당 Cookie를 return 한다.</li>
</ul>
</li>
<li><p>(2): (1)에서 유효한 쿠키를 찾지못하고 return 받은 sessionCookie가 null이라면 null을 return 한다.</p>
</li>
<li><p>(3): 유효한 쿠키를 찾았을 경우 Cookie의 Value(token값)을 통하여 Store Map(세션저장소)에서 memberId를 찾아온다.</p>
</li>
</ul>
</li>
<li><p>(2): (1)에서 유효한 Session을 찾지 못한 경우 mebmerId는 null이 되며, login 페이지로 이동시킨다.</p>
</li>
<li><p>(3): (1)에서 유효한 Session을 통해 정상적인 memberId를 반환받았을 경우 memberRepository에서 member정보를 찾는다.</p>
</li>
<li><p>(4): memberId를 통하여 member를 찾지 못했을 경우 member는 null이 되며, login 페이지로 이동시킨다.</p>
</li>
<li><p>(5): model에 member를 추가하여 home 페이지로 이동시킨다.</p>
</li>
</ul>
<hr>
<p>아래는 모든 과정이 정상적으로 마무리되고, home페이지로 이동한 화면이다.
<img src="https://images.velog.io/images/won-developer/post/0925640c-05b7-49e2-863b-3828bf8c51c8/image.png" alt=""></p>
<p>정상적으로 login된 home화면을 확인할 수 있다.</p>
<p><img src="https://images.velog.io/images/won-developer/post/d22d3c02-1567-4604-904a-21c3931c7289/image.png" alt="">
F12 개발자모드로 접속해 등록된 Cookie를 확인해보자! 앞으로 다시 사이트에 접속하더라도 이 Cookie에 등록된 임의의 Token값을 통해 로그인상태를 유지할 것이다.</p>
<hr>
<h3 id="🚩-로그아웃을-통한-session-무효화-처리">🚩 로그아웃을 통한 Session 무효화 처리</h3>
<p>로그아웃을 통해 서버의 세션저장소에 등록된 정보를 삭제해보자.
<img src="https://images.velog.io/images/won-developer/post/0925640c-05b7-49e2-863b-3828bf8c51c8/image.png" alt="">
home 화면에서 로그아웃 버튼을 클릭하면 /logout url로 요청된다.</p>
<h4 id="controller-2">Controller</h4>
<pre><code class="language-java">@PostMapping(&quot;logout&quot;)
public String logout(HttpServletRequest request) {
    sessionManager.sessionExpire(request); //(1)
    return &quot;redirect:/&quot;; //(2)
}</code></pre>
<ul>
<li><p>(1): 세션정보를 삭제하기 위하여 request를 argument로 sessionManager.sessionExpire를 호출한다.</p>
<h4 id="sessionmanager-2">SessionManager</h4>
<pre><code class="language-java">public void sessionExpire(HttpServletRequest request) {
    Cookie sessionCookie = findCookie(request); //(1)
    if(sessionCookie != null) { //(2)
        store.remove(sessionCookie.getValue()); //(2)
    }
}</code></pre>
<ul>
<li>(1): request요청의 Cookie에 서버에서 관리되는 쿠키정보가 있는지 찾는다. reqeust를 argument로 findCookie를 호출한다. findCookie 메서드에 대해서는 위에서 설명했으니 참고하자.</li>
<li>(2): 만약 유효한 sessionCookie를 찾았을경우 Cookie의 Value(token값)을 통하여 store Map(세션저장소)에서 세션을 삭제한다.</li>
</ul>
</li>
</ul>
<ul>
<li>(2): /으로 redirect 한다. /요청을 처리하는 Controller에서는 다시 reqeust요청에 Cookie가 있는지 확인후 세션이 삭제되었으므로 최종적으로 login페이지로 이동할 것이다.</li>
</ul>
<hr>
<p>지금까지 SessionManager를 직접 만들어 처리하는 방식에 대해 알아보았고 로그아웃이 정상적으로 진행되어 세션정보가 삭제되었다면 다시 localhost:8080으로 접속하여도 로그인상태를 유지에 실패할 것이다. </p>
<p>지금까지 성공적으로 쿠키와 세션을 이용하여 로그인을 구현하였는데, 한가지 문제점이 있다. 세션정보를 서버에서 삭제하는 일은 사용자가 로그아웃 버튼을 눌렀을때만 발생한다. 일반적으로 사용자가 웹사이트를 떠날때 브라우저를 종료하지 로그아웃 버튼을 직접 클릭하는 경우는 드물다. 이렇게 된다면 해당 사용자에 대한 세션정보는 서버에 계속 유지될것이고 또한 만약 해커가 브라우저의 쿠키에서 세션Key를 탈취할 경우 보안문제로 이어질수 있다. </p>
<p>즉 이러한 문제를 해결하기 위해선 시간 간격을 두어 얼마의 시간만큼 해당 세션정보를 가지는 request 요청이 없을 경우, 서버의 세션정보를 지워야한다. 예시로 A사용자가 30분간 웹사이트를 이용하지 않는 경우 서버의 세션정보를 삭제 해버리는 것이다. 말로 들었을땐 어려워 보이는 구현이지만 Servlet에서 제공하는 Session기능을 이용할 경우 매우 편리하게 구현할 수 있다. 이뿐만 아니라 지금까지 SessionManager를 통해 구현했던 쿠키와 세션기능을 코드 몇줄로 매우 편리하게 축약할 수 있다.</p>
<p>지금부터 Servlet이 제공하는 Session을 알아보자.</p>
<hr>
<h2 id="✔-servlet-session">✔ Servlet Session</h2>
<h3 id="🚩-로그인처리후-session-발급-1">🚩 로그인처리후 Session 발급</h3>
<p><img src="https://images.velog.io/images/won-developer/post/d605d8bb-7298-4188-88c4-a4b6025772b1/image.png" alt="">
localhost:8080 접속시 로그인 페이지가 나온다. 로그인 ID와 비밀번호를 입력하여 접속하자. 회원 정보는 사전에 등록해두었다. 전송버튼 클릭시 아래 Controller로 요청된다.</p>
<p>Controller를 확인하기 전에 reqeust.getSession()이라는 메서드가 사용될것인데, 어떠한 기능을 수행하는지 정리하고 가자. request.getSession()는 세션을 생성한다. </p>
<ul>
<li>argument로는 true또는 false가 오는데, 생략하면 default는 true이다. </li>
<li>true일 경우 세션이 없다면 세션을 생성해서 return하고 세션이 있다면 세션을 return한다.</li>
<li>false일 경우 세션이 있다면 세션을 return하고 없다면 null을 return한다.</li>
</ul>
<h4 id="controller-3">Controller</h4>
<pre><code class="language-java">@PostMapping(&quot;/login&quot;)
public String login(@ModelAttribute Member member, HttpServletRequest request) {
    Member loginMember = loginService.login(member.getMemberId(), member.getPassword()); //(1)
    if(loginMember == null) { //(2)
        return &quot;redirect:/&quot;; //(2)
    }
    HttpSession session = request.getSession(); //(3)
    session.setAttribute(SessionConst.sessionId, loginMember.getMemberId()); //(4)
    return &quot;redirect:/&quot;; //(4)
}</code></pre>
<ul>
<li>(1): 입력된 회원ID와 Password를 가지는 회원이 있는지 확인한다. 올바른 정보인경우 Member객체가 반환되고, 아니라면 null이 반환된다.</li>
<li>(2): null일 경우 올바른 정보가 입력되지 않았으므로 /로 redirect 시킨다.</li>
<li>(3): 요청받은 정보가 올바른 회원정보임이 확인되었으니, request.getSession()으로 세션을 생성한다. login을 요청하는 시점에는 세션이 없다고 판단되니 argument를 default로 놔둔다.(true)</li>
<li>(4): (3)에서 생성된 session에 세션정보를 추가한다. 즉 세션저장소에 사용자를 식별할 수 있는 Key 상수 <code>SessionConst.sessionId</code> 즉 <code>LOGIN_MEMBER</code>와 사용자의 memberId를 저장한후 /으로 redirect 한다.</li>
</ul>
<p><img src="https://images.velog.io/images/won-developer/post/e8328f5d-fb3b-49c3-9597-06ddb3adc0c9/image.png" alt="">
앞에서는 SessionManager에서 Cookie를 직접 new로 생성하여 response.addCookie()를 통해 Cookie정보를 넘겨줬다면 Servlet에서 제공하는 Session은 3번 4번의 과정이 이루어지게 될 경우 Servlet이 자체적으로 Cookie에 정보를 넘겨준다. F12 개발자모드로 쿠키를 확인해보자. <code>JSESSIONID</code>는 Servlet에서 default로 넣어주는 Name이다.</p>
<p><img src="https://images.velog.io/images/won-developer/post/17cc1208-257b-4cdd-9239-94a36c564207/image.png" alt="">
그리고 지금까지 문제없이 진행됐다면 아마 url의 주소를 봤을때 <a href="http://localhost:8080/;jsessionid=855423ECD5AF593C588A9ED7ABF55CFD">http://localhost:8080/;jsessionid=855423ECD5AF593C588A9ED7ABF55CFD</a> 라고 입력이 되어있을 것이다. 자세히 보면 jsessionid=855423ECD5AF593C588A9ED7ABF55CFD는 서버에서 발급해준 Cookie와 같다. 이는 Servlet에서 Cookie를 사용하지 못하는 환경에 대비해(브라우저가 쿠키를 사용하지 못하거나, 기타 다른 이유로 인하여)자동으로 url에 해당 정보를 실어주는데 일반적으로 쿠키를 사용하지 못하는 환경은 없다고 봐도 무방하니.. 해당 기능을 꺼주도록 하자. 스프링부트에서는 간단하게 해당 기능을 제공한다. application.properties에 다음내용을 추가하자.
<code>server.servlet.session.tracking-modes=cookie</code>
위 내용을 추가후 다시 서버를 시작하고 접속하면 정상적으로 url이 나오는것을 확인할 수 있다.</p>
<p>이제 /으로 redirect 시켰으니 /의 요청을 처리하는 Controller를 확인해보자.</p>
<hr>
<h4 id="controller-4">Controller</h4>
<pre><code class="language-java">@GetMapping(&quot;/&quot;)
public String home(HttpServletRequest request, Model model) {
    HttpSession session = request.getSession(false); //(1)
    if(session == null) { //(2)
        return &quot;login&quot;; //(2)
    }
    String memberId = (String)session.getAttribute(SessionConst.sessionId); //(3)
    Optional&lt;Member&gt; findMemberOptional = memberRepository.findByMemberId(memberId); //(4)
    Member member = findMemberOptional.orElse(null); //(5)
    if(member == null) { //(5)
        return &quot;login&quot;; //(5)
    }
    model.addAttribute(&quot;member&quot;, member); //(6)
    return &quot;home&quot;; //(6)
}</code></pre>
<ul>
<li>(1): /으로 요청이고 세션이 없어도 문제없으므로 request.getSession(false)로 세션을 가져오고 argument가 false이므로 세션이 있으면 세션을, 없다면 null을 return 한다.</li>
<li>(2): 생성된 세션이 없다면 login 페이지로 이동한다.</li>
<li>(3): 생성된 세션이 있을경우 세션을 생성할 때 Key로 등록했던 상수 <code>SessionConst.sessionId</code> 즉 <code>LOGIN_MEMBER</code>를 Attribute로 등록했던 mebmerId를 찾는다.</li>
<li>(4): memberId로 memberRepository에서 회원이 있는지 찾는다.</li>
<li>(5): 만약 회원을 찾지 못했을 경우 null이 될것이고 login 페이지로 이동한다.</li>
<li>(6): 정상적으로 회원을 찾았다면 model에 member를 추가하고 home 페이지로 이동한다.</li>
</ul>
<hr>
<p>아래는 모든 과정이 정상적으로 마무리되고, home페이지로 이동한 화면이다.
<img src="https://images.velog.io/images/won-developer/post/0925640c-05b7-49e2-863b-3828bf8c51c8/image.png" alt=""></p>
<p>정상적으로 login된 home화면을 확인할 수 있다.</p>
<hr>
<h3 id="🚩-로그아웃을-통한-session-무효화-처리-1">🚩 로그아웃을 통한 Session 무효화 처리</h3>
<p>로그아웃을 통해 서버의 세션저장소에 등록된 정보를 삭제해보자.
<img src="https://images.velog.io/images/won-developer/post/0925640c-05b7-49e2-863b-3828bf8c51c8/image.png" alt="">
home 화면에서 로그아웃 버튼을 클릭하면 /logout url로 요청된다.</p>
<h4 id="controller-5">Controller</h4>
<pre><code class="language-java">@PostMapping(&quot;/logout&quot;)
public String logout(HttpServletRequest request) {
    HttpSession session = request.getSession(false); //(1)
    if(session == null) { //(2)
        return &quot;redirect:/&quot;; //(2)
    }
    session.invalidate(); //(3)
    return &quot;redirect:/&quot;; //(3)
}</code></pre>
<ul>
<li>(1): logout으로 요청이고 세션이 없어도 문제없으므로 request.getSession(false)로 세션을 가져오고 argument가 false이므로 세션이 있으면 세션을, 없다면 null을 return 한다.</li>
<li>(2): session이 없어 null을 return 받았다면 /으로 redirect 한다.</li>
<li>(3): session을 return 받았다면 session.invalidate()로 세션을 지우고 /으로 redirect 한다. /요청을 처리하는 Controller에서는 세션을 확인후 세션정보가 없으니 login 페이지로 이동시킬 것이다.</li>
</ul>
<h3 id="🚩-session-유효시간-관리하기">🚩 Session 유효시간 관리하기</h3>
<p>스프링부트에서는 간단하게 해당기능을 제공한다. application.properties에 다음 내용을 추가하자. 
<code>server.servlet.session.timeout=1800</code> 1800초(30분)라는 의미이다.
위 내용을 추가하게 되면 30분간 Session정보를 가지는 request 요청이 없을경우 자동으로 서버에서 세션을 삭제한다. </p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[메시지와 국제화]]></title>
            <link>https://velog.io/@won-developer/%EB%A9%94%EC%8B%9C%EC%A7%80%EC%99%80-%EA%B5%AD%EC%A0%9C%ED%99%94</link>
            <guid>https://velog.io/@won-developer/%EB%A9%94%EC%8B%9C%EC%A7%80%EC%99%80-%EA%B5%AD%EC%A0%9C%ED%99%94</guid>
            <pubDate>Fri, 19 Nov 2021 13:48:05 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/won-developer/post/3439996d-732b-4d06-8346-183bd15c6c0c/spring.PNG" alt=""></p>
<hr>
<blockquote>
<p>학습참조
<a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard">인프런-스프링 MVC 2편 백엔드 웹 개발 활용 기술(김영한님)</a></p>
</blockquote>
<hr>
<h2 id="✅-메시지-기능이란">✅ 메시지 기능이란?</h2>
<p>예를 들어 3개의 HTML 파일을 가지는 웹페이지가 있다고 가정해보자. 각자의 HTML 파일의 Header 부분에는 &#39;어서오세요.&#39;라는 문자가 들어가 있다. 이후 고객의 요구로 인하여 이 &#39;어서오세요.&#39;라는 문자를 &#39;반갑습니다.&#39; 로 변경해야 한다면 HTML 3개의 파일의 Header 부분을 모두 수정해주어야 한다. 현재 웹페이지에는 3개의 HTML 파일만 있으니 손쉽게 수정할 수 있겠지만, 만약 이 웹페이지가 수백개의 HTML 파일을 가지고 있다면 개발자는 수백개의 HTML 파일을 일일이 다 클릭하여 Header를 수정해야 할 것이다. 이와 같은 문제를 손쉽게 해결하기 위하여 Spring에서는 메시지 기능을 제공한다.</p>
<hr>
<h2 id="✅-메시지-기능-사용하기">✅ 메시지 기능 사용하기</h2>
<h3 id="✔-messagesproperties-파일">✔ messages.properties 파일</h3>
<p>메시지를 공통으로 등록하여 사용하기 위해서는 messages.properties 파일을 생성하여야 한다. 기본 경로는 resource 아래에 생성하자. </p>
<h4 id="디렉토리-구조">디렉토리 구조</h4>
<p><img src="https://images.velog.io/images/won-developer/post/e87f0662-5de1-4caf-969e-782554f15f53/image.png" alt=""></p>
<p>해당 경로에 파일을 생성한다음 사용할 message를 선언해주어야 한다.</p>
<pre><code class="language-java">page.header = 어서오세요.
page.headerParam = 어서오세요. {0}</code></pre>
<p>를 가지는 messages.properties 파일을 생성 하였다. 이후에 이 page.header, page.headerParam를 가지고 메시지 기능을 사용할 것이다. <code>page.headerParam = 어서오세요. {0}</code>와 같이 {0}부분을 파라미터로도 사용할 수 있다.</p>
<h3 id="✔-applicationproperties에-속성-지정">✔ application.properties에 속성 지정</h3>
<p><code>spring.messages.basename=messages</code> 라는 속성을 등록한다. messages라는 이름을 가지는 properties 파일을 default로 사용하겠다는 의미이다. 하지만 이 속성을 등록하지 않아도 동작한다. Spring Boot를 사용한다면 자동으로 등록을 해준다. 이외에도 messages 관련 속성들이 있는데 그것은 Spring 매뉴얼을 참고하자.</p>
<h3 id="✔-messagesource를-bean으로-등록하자">✔ MessageSource를 Bean으로 등록하자.</h3>
<pre><code class="language-java">@Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasenames(&quot;messages&quot;, &quot;errors&quot;);
    messageSource.setDefaultEncoding(&quot;utf-8&quot;);
    return messageSource;
}</code></pre>
<p>기본적으로 Spring의 메시지 기능을 활용하기 위해선 MessageSource 인터페이스를 타입으로 가지는 Bean을 생성해야 한다. 하지만 우리는 이것을 할 필요가 없다. 이 역시 Spring Boot를 사용하면 부트가 다 알아서 빈으로 등록하고 설정을 해준다.</p>
<h3 id="✔-spring에서-messagesource-사용하기">✔ Spring에서 MessageSource 사용하기</h3>
<p>앞서 Spring Boot가 자동으로 MessageSource를 Bean으로 등록해 준다고 했다. 그렇다면 등록된 MessageSource를 이용해보자.</p>
<pre><code class="language-java">@Autowired
MessageSource ms; //(1)

void helloMessage() {
    String result1 = ms.getMessage(&quot;page.header&quot;, null, null); //(2)
    String result2 = ms.getMessage(&quot;page.headerParam&quot;, new Object[]{&quot;Jay Park.&quot;}, null); //(2)
    System.out.println(&quot;result1 = &quot; + result1); //(3)
    System.out.println(&quot;result2 = &quot; + result2); //(3)
}</code></pre>
<ul>
<li>(1): MessageSource를 필드주입 받는다.</li>
<li>(2): argument가 총 3개인데, 첫번째는 messages.properties에 등록된 key값, 두번째는 key value에 parameter를 사용하면 그에 대입될 argument값으로 타입은 배열타입으로 선언한다. 세번째는 Locale 값이다. Locale은 국제화에 필요한부분으로 추후에 설명한다. 여기서는 값이 null이면 default로 지정된 messages.properties를 사용한다.</li>
<li>(3): <code>result1 = 어서오세요.</code> ,  <code>result2 = 어서오세요. Jay Park.</code>가 출력된다.</li>
</ul>
<h3 id="✔-타임리프에서-사용하기">✔ 타임리프에서 사용하기</h3>
<p>사실 메시지 기능은 서버단 보다는 화면단에서 주로 사용한다고 한다. 타임리프에서는 이 메시지 기능을 편리하게 사용할 수 있도록 제공한다.</p>
<pre><code class="language-html">&lt;h2 th:text=&quot;#{page.header}&quot;&gt;Header&lt;/h2&gt; &lt;!-- (1) --&gt;
&lt;h2 th:text=&quot;#{page.header(&#39;Jay Park.&#39;)}&quot;&gt;Header.Param&lt;/h2&gt; &lt;!-- (2) --&gt;</code></pre>
<p>간단하게 #키워드를 사용하여 구현할 수 있다.</p>
<ul>
<li>(1): <code>어서오세요.</code>로 치환.</li>
<li>(2): <code>어서오세요. Jay Park.</code>로 치환.</li>
</ul>
<p>여기까지 학습이 되었다면 <code>어서오세요.</code> header 문자를 가지는 HTML 파일이 수십개든 수백개든 수천개든 messages.properties 파일의 page.header만 변경해준다면 매우 간단하게 문자를 변경할 수 있다.</p>
<hr>
<h2 id="✅-국제화">✅ 국제화</h2>
<p>국제화란 메시지 기능에서 한발 더 나아가 접속되는 브라우저 언어 환경에 따라 다른 메시지를 보여준다. 즉 접속하는 브라우저의 언어가 영어(en)라면 미리 만들어놓은 영어 메시지를 보여주고, 한글(ko)이라면 미리 만들어놓은 한글 메시지를 보여준다.</p>
<h3 id="✔-국제화-적용-messages_enproperties-파일-생성">✔ 국제화 적용, messages_en.properties 파일 생성</h3>
<p>영어로 메시지를 사용하기 위하여 messages_en.properties 파일을 생성하자.</p>
<pre><code class="language-java">page.header = hello.
page.headerParam = hello. {0}</code></pre>
<p>이후 브라우저의 언어 옵션을 영어로 변경후 접속해보자. 크롬의 경우 설정 &gt; 고급 &gt; 언어 &gt; 영어 속성을 가장 위로 이동시키면 언어 옵션이 변경된다.</p>
<h4 id="한국어로-지정시-http-header-accept-language">한국어로 지정시 HTTP Header accept-language</h4>
<p><img src="https://images.velog.io/images/won-developer/post/aaca10b8-de7a-4ccf-b5aa-f517516a1628/image.png" alt=""></p>
<h4 id="영어로-지정-http-header-accept-language">영어로 지정 HTTP Header accept-language</h4>
<p><img src="https://images.velog.io/images/won-developer/post/caace5c4-999a-4d14-9d1c-6c598672269d/image.png" alt=""></p>
<p>이렇게 해당 옵션을 변경해주게 되면 해당 페이지로 요청시 HTTP Header에 accept-language의 값으로 en이 최우선 순위로 넘어오게 된다. Spring은 이 accept-language를 확인하여 Locale을 지정한다. 이후 Spring에서 지정한 이 Locale를 확인하여 메시지를 사용할 properties 파일을 선택하게 된다. 
물론 Spring Boot가 <code>spring.messages.basename=messages</code> 와 같이 default 속성을 messages로 지정하기 때문에 1순위가 ko일때는 messages_ko.properties가 없으므로 messages.properties가 선택된다. 1순위가 en일 경우 messages_en.properties가 선택될 것이다.</p>
<h3 id="✔-localeresolver">✔ LocaleResolver</h3>
<p>단순히 브라우저의 언어에 따른 HTTP Header accept-language를 기준으로 Locale가 지정되는 것이 아닌 쿠키나 세션을 통해서도 Locale를 지정할 수 있다. 글로벌한 웹페이지를 접속해보면 각 지역에 맞게 언어를 선택하는 것을 볼수있는데, 이와 같은 경우라고 할수 있다.</p>
<h4 id="ikea-웹페이지">IKEA 웹페이지</h4>
<p><img src="https://images.velog.io/images/won-developer/post/00943dc3-18ce-4599-820c-93faeb2863a0/image.png" alt=""></p>
<p>Spring은 이 Locale를 방식을 변경할 수 있도록 LocaleResolver 인터페이스를 제공하는데, Spring Boot는 기본적으로 accept-language를 활용하는 AcceptHeaderLocaleResolver를 default로 사용한다. 만약 accept-language를 활용하지 않고, 쿠키나 세션등을 활용하여 Locale를 변경하려면 LocaleResolver의 구현체를 변경하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[중복된 model 요소 해결하는 특별한 ModelAttribute]]></title>
            <link>https://velog.io/@won-developer/%ED%8A%B9%EB%B3%84%ED%95%9C-ModelAttribute</link>
            <guid>https://velog.io/@won-developer/%ED%8A%B9%EB%B3%84%ED%95%9C-ModelAttribute</guid>
            <pubDate>Tue, 16 Nov 2021 11:11:24 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/won-developer/post/e3ada5e0-a864-4fb1-9ac6-d05849601abe/spring.PNG" alt=""></p>
<hr>
<blockquote>
<p>학습참조
<a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard">인프런-스프링 MVC 2편 백엔드 웹 개발 활용 기술(김영한님)</a></p>
</blockquote>
<hr>
<p>스프링에서는 컨트롤러 마다 중복된 데이터를 model에 담는 상황을 효율적으로 처리하기 위하여 다음과 같은 기능을 지원한다.
예로 두개의 컨트롤러에서 중복된 데이터를 model에 담는다고 가정해보자.</p>
<h3 id="controller">Controller</h3>
<pre><code class="language-java">public class controller {

    @GetMapping(&quot;/add&quot;)
    public String add(Model model) {
        Map&lt;String, String&gt; regions = new LinkedHashMap&lt;&gt;();
        regions.put(&quot;SEOUL&quot;, &quot;서울&quot;);
        regions.put(&quot;BUSAN&quot;, &quot;부산&quot;);
        regions.put(&quot;JEJU&quot;, &quot;제주&quot;);
        model.addAttribute(&quot;regions&quot;, regions);
        return &quot;add&quot;;
    }

    @GetMapping(&quot;/results&quot;)
    public String results(Model model) {
        Map&lt;String, String&gt; regions = new LinkedHashMap&lt;&gt;();
        regions.put(&quot;SEOUL&quot;, &quot;서울&quot;);
        regions.put(&quot;BUSAN&quot;, &quot;부산&quot;);
        regions.put(&quot;JEJU&quot;, &quot;제주&quot;);
        model.addAttribute(&quot;regions&quot;, regions);
        return &quot;results&quot;;
    }
}</code></pre>
<p>위 코드는 SEOUL, BUSAN, JEJU를 Key로 가지는 Map regions을 model에 담아서 반환한다. add와 results 모두 중복된 코드를 가지고 있다. 물론, 해당 부분만 Method로 뽑아내서 사용해도 무방하지만 Spring에서는 특별한 <code>ModelAttribute</code> 기능을 지원한다.</p>
<pre><code class="language-java">public class controller {

    @ModelAttribute(&quot;regions&quot;)
    public Map&lt;String, String&gt; regions() {
        Map&lt;String, String&gt; regions = new LinkedHashMap&lt;&gt;();
        regions.put(&quot;SEOUL&quot;, &quot;서울&quot;);
        regions.put(&quot;BUSAN&quot;, &quot;부산&quot;);
        regions.put(&quot;JEJU&quot;, &quot;제주&quot;);

        return regions;
    }
}</code></pre>
<p><code>@ModelAttribute</code> 애노테이션을 이용하여 다음과 같이 Method를 만든다면 controller를 호출하는 모든 요청에 return 데이터 <code>regions</code>이 <code>model</code>에 담기게 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring과 Thymeleaf CheckBox]]></title>
            <link>https://velog.io/@won-developer/Spring%EA%B3%BC-Thymeleaf-CheckBox</link>
            <guid>https://velog.io/@won-developer/Spring%EA%B3%BC-Thymeleaf-CheckBox</guid>
            <pubDate>Mon, 15 Nov 2021 12:52:33 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/won-developer/post/c5fbefbb-6dd9-4e4a-9237-faa0fd1910e1/thymeleaf.PNG" alt=""></p>
<hr>
<blockquote>
<p>학습참조
<a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard">인프런-스프링 MVC 2편 백엔드 웹 개발 활용 기술(김영한님)</a></p>
</blockquote>
<hr>
<p>스프링과 타임리프는 일반적인 HTML 코드의 form 방식에 대한 문제점을 개선하고자 다음과 같은 방식을 제공한다.
앞서, 이 포스팅은 다음과 같은 Item 객체와 Controller를 사용한다고 정의한다.</p>
<h4 id="⭐-item">⭐ Item</h4>
<pre><code class="language-java">public class Item {
    private Boolean open;
}</code></pre>
<h4 id="⭐-controller">⭐ Controller</h4>
<pre><code class="language-java">
@GetMapping(&quot;...&quot;)
public String ...(@ModelAttribute Item item) {
    model.addAttribute(&quot;item&quot;, new Item());
    return &quot;...&quot;;
}

@PostMapping(&quot;...&quot;)
public String ...(@ModelAttribute Item item) {
    log.info(&quot;Item.open={}&quot;, open);
    return &quot;redirect:....&quot;;
}</code></pre>
<hr>
<h2 id="✅-일반적인-html-코드로-checkbox를-form에-담아-전송했을때">✅ 일반적인 HTML 코드로 checkBox를 form에 담아 전송했을때</h2>
<h4 id="⭐-html">⭐ HTML</h4>
<pre><code class="language-html">&lt;form action method=&quot;post&quot;&gt;
&lt;input type=&quot;checkbox&quot; id=&quot;open&quot; name=&quot;open&quot;&gt;
&lt;/form&gt;</code></pre>
<p>위와 같은 상황일때 checkbox가 checked 상태로 전송 되었을때는 <code>Item.open=true</code>log가 출력이 될 것이다. 실제 전송 데이터는 <code>...?open=on</code>과 같이 전송 되겠지만, 스프링 타입 컨버터가 Item 객체의 open 타입이 Boolean이면 true로 변환해준다. 일반적으로 위와 같은 상황일때는 문제 될 것이 없다.</p>
<p>하지만 일반적인 HTML 코드는 checked 상태가 아닐 경우에는 open 필드 자체가 서버로 전송되지 않는다. 만약 check를 하지 않고 폼 전송을 하였을 경우에는 <code>Item.open=null</code> log가 출력될 것이다.</p>
<h3 id="✔-hidden-사용">✔ hidden 사용</h3>
<p>스프링에서는 이와 같은 문제점을 개선하기 위하여 다음과 같이 약간의 트릭을 사용하여 체크 해제를 인식할 수 있다.</p>
<h4 id="⭐-html-1">⭐ HTML</h4>
<pre><code class="language-html">&lt;form action method=&quot;post&quot;&gt;
&lt;input type=&quot;checkbox&quot; id=&quot;open&quot; name=&quot;open&quot;&gt;
&lt;input type=&quot;hidden&quot; name=&quot;_open&quot; value=&quot;on&quot;&gt; &lt;!-- 히든 필드 추가 --&gt;
&lt;/form&gt;</code></pre>
<p>위와 같이 input hidden 타입을 추가하고 기존 checkBox의 name에 언더스코어( _ )를 추가한 태그를 생성한후 전송한다면 check가 되어 있지 않은 상태에서도 false를 받을 수 있다.</p>
<p>스프링에서는 위와 같은 경우를 다음과 같이 해석한다.</p>
<h5 id="checked-상태인-경우">checked 상태인 경우</h5>
<ol>
<li><code>...?open=on&amp;_open=on</code>와 같은 parameter로 전송되는데 open이 전송되었으면 _open은 무시하고 타입 컨버터가 <code>true</code>로 변환하여 Item 객체에 저장한다.<h5 id="checked-상태가-아닌-경우">checked 상태가 아닌 경우</h5>
</li>
<li><code>...?_open=on</code>와 같은 parameter로 전송되는데 open이 parameter에 없고 _open만 있으니 타입 컨버터가 <code>false</code>로 변환하여 Item 객체에 저장한다.</li>
</ol>
<hr>
<h2 id="✅-타임리프를-사용-했을때-마법의-thobject-thfield">✅ 타임리프를 사용 했을때 마법의 th:object, th:field</h2>
<p>앞서 알아본 상황을 타임리프를 적용할시 더욱 효과적으로 개선할 수 있다.</p>
<h4 id="⭐-html-2">⭐ HTML</h4>
<pre><code class="language-html">&lt;form action method=&quot;post&quot; th:object=&quot;${item}&quot;&gt; &lt;!-- (1) --&gt;
&lt;input type=&quot;checkbox&quot; th:field=&quot;*{open}&quot;&gt; &lt;!-- (2) --&gt;
&lt;!-- &lt;input type=&quot;checkbox&quot; id=&quot;open&quot; name=&quot;open&quot;&gt; --&gt;
&lt;!-- &lt;input type=&quot;hidden&quot; name=&quot;_open&quot; value=&quot;on&quot;&gt; --&gt; &lt;!-- 히든 필드 추가 --&gt;
&lt;/form&gt;</code></pre>
<p>위와 같이 2가지 문법을 추가한다.</p>
<ul>
<li>(1): th:object를 사용하여 커맨드 객체를 추가한다. 커맨드 객체란 스프링과 타임리프의 통합기능을 구현한 객체이며 form 태그에서 사용될 data들의 객체이다. </li>
<li>(2): checkbox 태그에 th:field를 사용한다. <code>*{open}</code>이라고 입력할 경우 커맨드 객체의 <code>${item.open}</code>를 입력한 것과 같은 의미이며 축약하여 <code>*{open}</code>라고 입력했다.</li>
</ul>
<p>다음과 같이 작성한 다음 타임리프를 통해 랜더링후 HTML 코드를 확인해보면 다음과 같은 결과를 확인할 수 있다.</p>
<pre><code class="language-html">&lt;!--&lt;input type=&quot;checkbox&quot; id=&quot;open&quot; name=&quot;open&quot;&gt;--&gt;
&lt;input type=&quot;checkbox&quot; id=&quot;open1&quot; name=&quot;open&quot; value=&quot;true&quot;&gt;&lt;input type=&quot;hidden&quot; name=&quot;_open&quot; value=&quot;on&quot;/&gt;
&lt;!--&lt;input type=&quot;hidden&quot; name=&quot;_open&quot; value=&quot;on&quot;&gt;--&gt; &lt;!-- 히든 필드 추가 --&gt;</code></pre>
<p>잘 보면 hidden 타입 태그가 추가된 것을 볼 수 있다. 타임리프가 랜더링하면서 스스로 hidden 타입 태그를 추가하여 랜더링한다. 앞서 hidden 타입 태그를 추가한 경우와 같은 결과를 보여준다. 또한 id와 name까지 자동으로 생성 해준다. th:field를 사용함으로써 많은 부분을 편리하게 축약할 수 있다.</p>
<h3 id="✔-checked-속성">✔ checked 속성</h3>
<p>또한 일반적인 HTML checkbox 태그는 다음과 같이 값에 상관 없이 checked라는 속성을 가지고 있을 경우 무조건 checked 상태가 된다.</p>
<pre><code class="language-html">&lt;input type=&quot;checkbox&quot; checked=&quot;checked&quot;&gt;
&lt;input type=&quot;checkbox&quot; checked&gt;</code></pre>
<p>위 두가지 라인 모두 check 상태가 된다. 때문에 개발자가 if문을 통해 어떠한 값에 의하여 checked 속성을 넣을지, 뺄지에 대한 로직을 추가로 넣어줘야 한다. 개발자로써는 굉장히 번거로운 일이다.</p>
<p>하지만 이러한 경우도 타임리프에서 지원하는 th:field를 사용한다면 true와 false를 이용한 check 상태를 쉽게 관리할 수 있다. 아래는 form 태그에 속하는 input이 아닌 경우라고 가정하고 th:object를 가지는 커맨드 객체가 없으니 <code>*{...}</code>문법이 아닌 일반적인 <code>${...}</code>문법을 사용했다.</p>
<pre><code class="language-html">&lt;input type=&quot;checkbox&quot; th:field=&quot;${item.open}&quot;&gt;</code></pre>
<p>위와 같이 코드를 구성한다면 타임리프를 통해 랜더링된후 확인할 수 있는 HTML 코드는 다음과 같다.</p>
<pre><code class="language-html">&lt;input type=&quot;checkbox&quot; id=&quot;open1&quot; name=&quot;open&quot; value=&quot;true&quot; checked=&quot;checked&quot;&gt;</code></pre>
<p>th:field를 이용했기 때문에 id와 name, value가 자동적으로 추가됐고, <code>${item.open}</code>의 값이 true이냐 false냐에 따라서 checked를 넣고 빼고를 처리 해준다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Thymeleaf 기본기능 정복하기[2편]]]></title>
            <link>https://velog.io/@won-developer/Thymeleaf-%EA%B8%B0%EB%B3%B8%EA%B8%B0%EB%8A%A5-%EC%A0%95%EB%B3%B5%ED%95%98%EA%B8%B02%ED%8E%B8</link>
            <guid>https://velog.io/@won-developer/Thymeleaf-%EA%B8%B0%EB%B3%B8%EA%B8%B0%EB%8A%A5-%EC%A0%95%EB%B3%B5%ED%95%98%EA%B8%B02%ED%8E%B8</guid>
            <pubDate>Mon, 15 Nov 2021 12:51:45 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/won-developer/post/6d36bc17-0148-497c-902f-da835871f5db/thymeleaf.PNG" alt=""></p>
<hr>
<blockquote>
<ul>
<li><a href="https://www.thymeleaf.org/">공식 사이트</a></li>
</ul>
</blockquote>
<ul>
<li><a href="https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html">공식 메뉴얼 - 기본기능</a></li>
<li><a href="https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html">공식 메뉴얼 - 스프링 통합</a></li>
</ul>
<hr>
<blockquote>
<p>학습참조
<a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard">인프런-스프링 MVC 2편 백엔드 웹 개발 활용 기술(김영한님)</a></p>
</blockquote>
<hr>
<p>이 글을 작성하고 난 뒤에는 내가 나중에 타임리프를 사용할 때에, 문법이 잘 기억이 나지 않을 때 이 글만 보아도 기본적인 타임리프 기능들을 사용할 수 있었으면 한다.</p>
<hr>
<h2 id="✅-다루는-기능들">✅ 다루는 기능들</h2>
<h5 id="🚩-연산">🚩 연산</h5>
<h5 id="🚩-속성값-설정">🚩 속성값 설정</h5>
<h5 id="🚩-반복---theach">🚩 반복 - th:each</h5>
<h5 id="🚩-조건---thif-thunless-thswitch">🚩 조건 - th:if, th:unless, th:switch</h5>
<h5 id="🚩-주석">🚩 주석</h5>
<h5 id="🚩-블록">🚩 블록</h5>
<h5 id="🚩-javascript-인라인-사용">🚩 JavaScript 인라인 사용</h5>
<h5 id="🚩-레이아웃-템플릿">🚩 레이아웃 템플릿</h5>
<hr>
<h2 id="✅-연산">✅ 연산</h2>
<p>타임리프 연산은 일반적인 프로그래밍 연산과 크게 다르지 않다. 
하지만 HTML 안에서 사용하는 것이기 때문에 HTML 엔티티를 사용하는 부분을 주의 해야한다.
HTML 엔티티가 사용되는 것은 대부분이 비교 연산임으로 이때는 다음과 같은 문법을 사용할 것을 권장한다.</p>
<h3 id="✔-비교-연산-대체-키워드">✔ 비교 연산 대체 키워드</h3>
<ul>
<li><code>&gt;</code> (gt)</li>
<li><code>&lt;</code> (lt)</li>
<li><code>&gt;=</code> (ge)</li>
<li><code>&lt;=</code> (le)</li>
<li><code>!</code>(not)</li>
<li><code>==</code> (eq)</li>
<li><code>!=</code> (neq, ne)</li>
</ul>
<p>물론 &lt; , &gt; 에 대한 엔티티만 조심한다면 ==, !, !=, &gt;=, &lt;= 와 같은 연산은 그냥 사용해도 무방하다.</p>
<hr>
<h3 id="✔-삼항-연산자">✔ 삼항 연산자</h3>
<p>타임리프에서는 Elvis 연산자를 지원하는데 예를 들어 서버로 부터 data라는 객체를 전달받았다고 가정 해보자.</p>
<pre><code class="language-html">&lt;span th:text=&quot;${data} ? ${data} : &#39;데이터가 없습니다.&#39;&quot;&gt;&lt;/span&gt; &lt;!-- (1) --&gt;
&lt;span th:text=&quot;${data} ?: &#39;데이터가 없습니다.&#39;&quot;&gt;&lt;/span&gt; &lt;!-- (2) --&gt;</code></pre>
<ul>
<li>(1): 일반적으로 사용하는 삼항 연산자이다. <code>data</code> 변수가 참이면 <code>data</code>를, 거짓이라면 <code>데이터가 없습니다.</code>를 출력한다.</li>
<li>(2): Elvis 연산자를 사용한 것으로 동작은 (1)과 동일하게 동작하며 참일때의 부분이 생략되어 있다.</li>
</ul>
<hr>
<h3 id="✔-no-operation">✔ No-Operation</h3>
<p>No-Operation이란 <code>&#39;_&#39;</code> 언더스코어(밑줄)을 사용하여 마치 타임리프가 실행되지 않는 것처럼 동작하게끔 한다.</p>
<pre><code class="language-html">&lt;span th:text=&quot;${data} ?: &#39;데이터가 없습니다.&#39;&quot;&gt;&lt;/span&gt; &lt;!-- (1) --&gt;
&lt;span th:text=&quot;${data} ?: _&quot;&gt;데이터가 없습니다.&lt;/span&gt; &lt;!-- (2) --&gt;</code></pre>
<p>위 코드는 서로 다르지만 같은 결과를 가진다.</p>
<ul>
<li>(1): 위에서 설명한 Elvis 연산자이다.</li>
<li>(2): Elvis 연산자에서 <code>데이터가 없습니다.</code> 문자가 아닌 <code>&#39;_&#39;</code> 언더스코어(밑줄)을 사용했다. 타임리프를 문법을 사용하지 않고 HTML 내용을 그대로 사용하겠다는 의미이다. 즉 <code>data</code>가 거짓이라면 span 내에 삽입된 <code>데이터가 없습니다.</code>가 출력된다.</li>
</ul>
<p>이렇듯 No-Operation을 적절히 잘 확용 한다면 HTML의 내용을 그대로 사용할 수 있다.</p>
<hr>
<h2 id="✅-속성값-설정">✅ 속성값 설정</h2>
<p>타임리프에서도 HTML 속성을 동적으로 설정이 가능한데 Tag에 <code>th:*</code> 속성을 지정하는 방식으로 사용한다. 속성을 적용하면 기존 속성이 대체되고 없다면 새로 만든다.</p>
<p>속성을 새로이 설정하는 방법과 속성을 추가하는 방법, checked 처리를 하는 방법을 알아보자.</p>
<h3 id="✔-속성-설정">✔ 속성 설정</h3>
<pre><code class="language-html">&lt;input type=&quot;text&quot; name=&quot;mock&quot; th:name=&quot;userA&quot; /&gt;</code></pre>
<p>name 속성 mock이 타임리프로 렌더링 되며 userA로 변경된다.</p>
<hr>
<h3 id="✔-속성-추가">✔ 속성 추가</h3>
<pre><code class="language-html">&lt;input type=&quot;text&quot; class=&quot;text&quot; th:attrappend=&quot;class=&#39;large&#39;&quot; /&gt; &lt;!-- (1) --&gt; 
&lt;!-- @실제 렌더링시... &lt;input type=&quot;text&quot; class=&quot;textlarge&quot; /&gt; --&gt;
&lt;input type=&quot;text&quot; class=&quot;text&quot; th:attrprepend=&quot;class=&#39;large &#39;&quot; /&gt; &lt;!-- (2) --&gt;
&lt;!-- @실제 렌더링시... &lt;input type=&quot;text&quot; class=&quot;large text&quot; /&gt; --&gt;
&lt;input type=&quot;text&quot; class=&quot;text&quot; th:classappend=&quot;large&quot; /&gt; &lt;!-- (3) --&gt;
&lt;!-- @실제 렌더링시... &lt;input type=&quot;text&quot; class=&quot;text large&quot; /&gt; --&gt;</code></pre>
<ul>
<li>(1): attrappend는 해당 속성의 마지막에 속성을 추가하며 class 속성의 마지막에 large를 추가한다. 실제 렌더링시 아래 주석과 같이 생성된다. 공백이 없으므로 기존의 text와 large가 붙어서 나오게 된다.</li>
<li>(2): attrprepend는 해당 속성의 첫번째에 속성을 추가하며 class 속성의 첫번째에 large를 추가한다. 실제 렌더링시 아래 주석과 같이 생성된다. 공백이 있으므로 large와 text 2개의 class 선택자를 가진다.</li>
<li>(3): classappend는 class 속성의 마지막에 속성을 추가하며 class 속성의 마지막에 large를 추가한다. 실제 렌더링시 아래 주석과 같이 생성된다. attrappend가 아닌 클래스를 추가하는 append로써 공백이 없어도 large와 text 2개의 class 선택자를 가진다.</li>
</ul>
<hr>
<h3 id="✔-checked-처리">✔ checked 처리</h3>
<pre><code class="language-html">&lt;input type=&quot;checkbox&quot; name=&quot;active&quot; th:checked=&quot;true&quot; /&gt; &lt;!-- (1) --&gt;
&lt;input type=&quot;checkbox&quot; name=&quot;active&quot; th:checked=&quot;false&quot; /&gt; &lt;!-- (2) --&gt; 
&lt;input type=&quot;checkbox&quot; name=&quot;active&quot; checked=&quot;false&quot; /&gt; &lt;!-- (3) --&gt;</code></pre>
<p>HTML은 checked 속성만 들어가 있어도 내부의 값을 상관하지 않고 체크상태로 표시하는데 타임리프에서는 true와 false로 이를 제어할 수 있다.</p>
<ul>
<li>(1): check된 상태.</li>
<li>(2): check되지 않은 상태.</li>
<li>(3): check된 상태, HTML에서는 checked 속성만 들어가 있어도 내부의 값을 상관하지 않고 체크상태로 표시한다.</li>
</ul>
<hr>
<h2 id="✅-반복---theach">✅ 반복 - th:each</h2>
<p>모든 템플릿에 반복이 존재하듯 타임리프에서도 반복을 수행할 수 있다. 반복문을 수행하는 타임리프 키워드는 다음과 같다.
<code>th:each=&quot;item : ${items}&quot;</code></p>
<h3 id="✔-3개의-user가-있는-list를-반복하는-출력">✔ 3개의 User가 있는 List를 반복하는 출력</h3>
<h4 id="⭐-controller">⭐ controller</h4>
<pre><code class="language-java">@GetMapping(&quot;...&quot;)
public String ...(Model model) {
    addUser(model);
    return &quot;...&quot;;
}

private void addUser(Model model) {
    List&lt;User&gt; list = new ArrayList&lt;&gt;();
    list.add(new User(&quot;userA&quot;, 10));
    list.add(new User(&quot;userB&quot;, 20));
    list.add(new User(&quot;userC&quot;, 30));

    model.addAttribute(&quot;users&quot;, list);
}

@Data
static class User {
    private String username;
    private int age;

    public User(String username, int age) {
        this.username = username;
        this.age = age;
    }
}</code></pre>
<p>userA, userB, userC 총 3개의 User를 가지는 list가 model에 담겨 전달되었다.</p>
<h4 id="⭐-theach-수행">⭐ th:each 수행</h4>
<pre><code class="language-html">&lt;table border=&quot;1&quot;&gt;
    &lt;tr&gt;
        &lt;th&gt;username&lt;/th&gt;
        &lt;th&gt;age&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr th:each=&quot;user : ${users}&quot;&gt;
        &lt;td th:text=&quot;${user.username}&quot;&gt;username&lt;/td&gt;
        &lt;td th:text=&quot;${user.age}&quot;&gt;0&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;</code></pre>
<p>위 <code>th:each</code>문은 java의 다음 코드와 동일하다.</p>
<pre><code class="language-java">for(User user : users) {
system.out.println(user.getUsername());
system.out.println(user.getAge());
}</code></pre>
<p>즉 tr Tag를 <code>users</code>의 길이만큼 loop를 돌면서 생성하면서 내부에 td Tag를 생성한다. 실제 렌더된 결과는 다음과 같다.</p>
<h5 id="출력">출력</h5>
<p><img src="https://images.velog.io/images/won-developer/post/df50dc42-524e-47ed-b233-5c78c30a553c/image.png" alt=""></p>
<hr>
<h3 id="✔-반복중-사용-가능한-반복상태-값">✔ 반복중 사용 가능한 반복상태 값</h3>
<p>반복중 현재 몇번째 index가 반복되고 있는지, 그 index의 count는 몇개인지, 반복되는 List(컬렉션 등)의 총 사이즈는 얼마인지, 현재 짝수 반복인지, 홀수 반복인지 기타등 편리한 기능을 제공한다.</p>
<ul>
<li>index : 0부터 시작하는 값</li>
<li>count : 1부터 시작하는 값</li>
<li>size : 전체 사이즈</li>
<li>even , odd : 홀수, 짝수 여부 ( boolean )</li>
<li>first , last : 처음, 마지막 여부 ( boolean )</li>
<li>current : 현재 객체<h4 id="⭐-theach-수행-1">⭐ th:each 수행</h4>
<pre><code class="language-html">&lt;table border=&quot;1&quot;&gt;
  &lt;tr&gt;
      &lt;th&gt;count&lt;/th&gt;
      &lt;th&gt;username&lt;/th&gt;
      &lt;th&gt;age&lt;/th&gt;
      &lt;th&gt;etc&lt;/th&gt;
  &lt;/tr&gt;
  &lt;tr th:each=&quot;user, userStat : ${users}&quot;&gt; &lt;!-- userStat 생략 가능 --&gt;
      &lt;td th:text=&quot;${userStat.count}&quot;&gt;username&lt;/td&gt;
      &lt;td th:text=&quot;${user.username}&quot;&gt;username&lt;/td&gt;
      &lt;td th:text=&quot;${user.age}&quot;&gt;0&lt;/td&gt;
      &lt;td&gt;
          index = &lt;span th:text=&quot;${userStat.index}&quot;&gt;&lt;/span&gt;
          count = &lt;span th:text=&quot;${userStat.count}&quot;&gt;&lt;/span&gt;
          size = &lt;span th:text=&quot;${userStat.size}&quot;&gt;&lt;/span&gt;
          even? = &lt;span th:text=&quot;${userStat.even}&quot;&gt;&lt;/span&gt;
          odd? = &lt;span th:text=&quot;${userStat.odd}&quot;&gt;&lt;/span&gt;
          first? = &lt;span th:text=&quot;${userStat.first}&quot;&gt;&lt;/span&gt;
          last? = &lt;span th:text=&quot;${userStat.last}&quot;&gt;&lt;/span&gt;
          current = &lt;span th:text=&quot;${userStat.current}&quot;&gt;&lt;/span&gt;
      &lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;</code></pre>
<code>userStat</code>라는 두번째 파라미터를 지정하여 위와 같이 반복중에 필요한 상태를 확인할 수 있다. <h5 id="출력-1">출력</h5>
<img src="https://images.velog.io/images/won-developer/post/6b856e8f-cb00-4d95-ab45-01d5aea5f3ad/image.png" alt=""></li>
</ul>
<p>사실 userStat를 생략할 수 있다. 기본적으로 타임리프에서 user + &#39;Stat&#39; 자동으로 만들어주기 때문이다. 다음과 같이 작성하여도 userStat를 사용했을때 문제없이 동작한다.</p>
<pre><code class="language-html">&lt;tr th:each=&quot;user : ${users}&quot;&gt;</code></pre>
<hr>
<h2 id="✅-조건---thif-thunless-thswitch">✅ 조건 - th:if, th:unless, th:switch</h2>
<p>타임리프에서는 3가지 조건부 평가가 가능하다.</p>
<h4 id="⭐-controller-1">⭐ controller</h4>
<pre><code class="language-java">@GetMapping(&quot;...&quot;)
public String ...(Model model) {
    addUser(model);
    return &quot;...&quot;;
}

private void addUser(Model model) {
    List&lt;User&gt; list = new ArrayList&lt;&gt;();
    list.add(new User(&quot;userA&quot;, 10));
    list.add(new User(&quot;userB&quot;, 20));
    list.add(new User(&quot;userC&quot;, 30));

    model.addAttribute(&quot;users&quot;, list);
}

@Data
static class User {
    private String username;
    private int age;

    public User(String username, int age) {
        this.username = username;
        this.age = age;
    }
}</code></pre>
<h3 id="✔-thif-thunless">✔ th:if, th:unless</h3>
<pre><code class="language-html">&lt;table border=&quot;1&quot;&gt;
    &lt;tr&gt;
        &lt;th&gt;count&lt;/th&gt;
        &lt;th&gt;username&lt;/th&gt;
        &lt;th&gt;age&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr th:each=&quot;user, userStat : ${users}&quot;&gt;
        &lt;td th:text=&quot;${userStat.count}&quot;&gt;1&lt;/td&gt;
        &lt;td th:text=&quot;${user.username}&quot;&gt;username&lt;/td&gt;
        &lt;td&gt;
            &lt;span th:text=&quot;${user.age}&quot;&gt;0&lt;/span&gt;
            &lt;span th:text=&quot;&#39;미성년자&#39;&quot; th:if=&quot;${user.age lt 20}&quot;&gt;&lt;/span&gt; &lt;!-- (1) --&gt;
            &lt;span th:text=&quot;&#39;미성년자&#39;&quot; th:unless=&quot;${user.age ge 20}&quot;&gt;&lt;/span&gt; &lt;!-- (2) --&gt;
        &lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;</code></pre>
<ul>
<li>(1): th:if는 말 그대로 조건이 참일때 수행하며, 만약 조건이 거짓일 시 span 자체도 생성되지 않는다.</li>
<li>(2): th:unless는 if의 반대로 not을 의미한다. <code>if(!(true))</code>와 같은 의미이다. 해당 조건이 거짓일 때만 수행하며, 만약 조건이 참이라면 span 자체도 생성되지 않는다.<h5 id="출력-2">출력</h5>
<img src="https://images.velog.io/images/won-developer/post/e82c592a-998e-4fa1-bf14-8793f03b1313/image.png" alt=""></li>
</ul>
<hr>
<h3 id="✔-thswitch">✔ th:switch</h3>
<pre><code class="language-html">&lt;table border=&quot;1&quot;&gt;
    &lt;tr&gt;
        &lt;th&gt;count&lt;/th&gt;
        &lt;th&gt;username&lt;/th&gt;
        &lt;th&gt;age&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr th:each=&quot;user, userStat : ${users}&quot;&gt;
        &lt;td th:text=&quot;${userStat.count}&quot;&gt;1&lt;/td&gt;
        &lt;td th:text=&quot;${user.username}&quot;&gt;username&lt;/td&gt;
        &lt;td th:switch=&quot;${user.age}&quot;&gt;  &lt;!-- (1) --&gt;
            &lt;span th:case=&quot;10&quot;&gt;10살&lt;/span&gt;  &lt;!-- (2) --&gt;
            &lt;span th:case=&quot;20&quot;&gt;20살&lt;/span&gt;
            &lt;span th:case=&quot;*&quot;&gt;기타&lt;/span&gt; &lt;!-- (3) --&gt;
        &lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;</code></pre>
<ul>
<li>(1): 일반적인 switch문과 동일하게 <code>${user.age}</code>의 값과 맞는 값을 찾는 switch문 이다.</li>
<li>(2): <code>${user.age}</code>의 값이 10일 경우 span 태그를 생성한다.</li>
<li>(3): java switch에서 default를 의미하며 <code>*</code>로 표시한다. <code>${user.age}</code>의 값이 10,20이 아니라면 해당 span 태그가 생성될 것이다.<h5 id="출력-3">출력</h5>
<img src="https://images.velog.io/images/won-developer/post/9aba5b09-a628-4011-b282-9de84849648c/image.png" alt=""></li>
</ul>
<hr>
<h2 id="✅-주석">✅ 주석</h2>
<p>타임리프에서 사용가능한 주석은 3가지 타입이 있다.</p>
<h3 id="✔-표준-html-주석">✔ 표준 HTML 주석</h3>
<pre><code class="language-html">&lt;!-- &lt;span&gt;표준 HTML 주석&lt;/span&gt; --&gt;</code></pre>
<p>표준 HTML 주석은 타임리프가 렌더링 하지 않고, 그대로 남겨둔다.</p>
<h3 id="✔-타임리프-파서-주석">✔ 타임리프 파서 주석</h3>
<pre><code class="language-html">&lt;!--/* &lt;span&gt;표준 HTML 주석&lt;/span&gt; */--&gt;</code></pre>
<p>타임리프 파서 주석은 타임리프의 진짜 주석이다. 타임리프 렌더링에서 주석 부분을 제거한다.</p>
<h3 id="✔-타임리프-프로토타입-주석">✔ 타임리프 프로토타입 주석</h3>
<pre><code class="language-html">&lt;!--/*/ &lt;span&gt;표준 HTML 주석&lt;/span&gt; /*/--&gt;</code></pre>
<p>타임리프 프로토타입은 약간 특이한데, HTML 주석에 약간의 구문을 더했다. HTML 파일을 웹 브라우저에서 그대로 열어보면 <code>&lt;!-- --&gt;</code>을 포함하는 HTML 주석이기 때문에 이 부분이 웹 브라우저가 렌더링하지 않는다. 타임리프 렌더링을 거치면 이 부분이 정상 렌더링 된다. HTML 파일을 그대로 열어보면 주석처리가 되지만, 타임리프를 통해 렌더링 한 경우에만 출력된다.</p>
<hr>
<h2 id="✅-블록">✅ 블록</h2>
<p>타임리프 블록 <code>&lt;th:block&gt;</code>은 HTML 태그가 아닌 타임리프의 유일한 자체 태그이다. 특정한 HTML 태그에 for loop를 지정하는것이 아닌 특정 구역에 대한 loop를 돌기 위하여 사용한다.</p>
<h4 id="⭐-controller-2">⭐ controller</h4>
<pre><code class="language-java">@GetMapping(&quot;...&quot;)
public String ...(Model model) {
    addUser(model);
    return &quot;...&quot;;
}

private void addUser(Model model) {
    List&lt;User&gt; list = new ArrayList&lt;&gt;();
    list.add(new User(&quot;userA&quot;, 10));
    list.add(new User(&quot;userB&quot;, 20));
    list.add(new User(&quot;userC&quot;, 30));

    model.addAttribute(&quot;users&quot;, list);
}

@Data
static class User {
    private String username;
    private int age;

    public User(String username, int age) {
        this.username = username;
        this.age = age;
    }
}</code></pre>
<h3 id="✔-thblock">✔ th:block</h3>
<pre><code class="language-html">&lt;th:block th:each=&quot;user : ${users}&quot;&gt;
    &lt;div&gt;
        사용자 이름 &lt;span th:text=&quot;${user.username}&quot;&gt;&lt;/span&gt;
        사용자 나이 &lt;span th:text=&quot;${user.age}&quot;&gt;&lt;/span&gt;
    &lt;/div&gt;
    &lt;div&gt;
        요약 &lt;span th:text=&quot;|${user.username} / ${user.age}|&quot;&gt;&lt;/span&gt;
    &lt;/div&gt;
&lt;/th:block&gt;</code></pre>
<h5 id="출력-4">출력</h5>
<pre><code class="language-html">사용자 이름1 userA 사용자 나이1 10
요약 userA / 10
사용자 이름1 userB 사용자 나이1 20
요약 userB / 20
사용자 이름1 userC 사용자 나이1 30
요약 userC / 30</code></pre>
<p>div 2개에 대한 loop를 돌기 위하여 th:block 이란 특수한 타임리프 태그를 만들어 반복하도록 했다. 사실 위 코드는 아래와 유사하다.</p>
<pre><code class="language-html">&lt;div th:each=&quot;user : ${users}&quot;&gt;
    &lt;div&gt;
        사용자 이름 &lt;span th:text=&quot;${user.username}&quot;&gt;&lt;/span&gt;
        사용자 나이 &lt;span th:text=&quot;${user.age}&quot;&gt;&lt;/span&gt;
    &lt;/div&gt;
    &lt;div&gt;
        요약 &lt;span th:text=&quot;|${user.username} / ${user.age}|&quot;&gt;&lt;/span&gt;
    &lt;/div&gt;
&lt;/div&gt;</code></pre>
<p>위와 같이 바깥 loop에 th:block이 아닌 div로 사용하여도 똑같은 결과를 낼 수 있지만 HTML 태그가 추가로 필요한 다른 점이 존재한다. 가능하면 태그에 for loop를 넣는것을 추천하지만 해결하기 힘든 특별한 반복이 필요한 경우에 사용하도록 하자.</p>
<hr>
<h2 id="✅-javascript-인라인-사용">✅ JavaScript 인라인 사용</h2>
<p>타임리프 HTML파일에서 자바스크립트 사용시 편리하게 사용할 수 있는 인라인 기능을 제공한다. 인라인 기능을 사용하고자 할때는 <code>&lt;script th:inline=&quot;javascript&quot;&gt;&lt;/script&gt;</code> 처럼 선언해주어야 한다.</p>
<ul>
<li>일반적인 자바스크립트 사용 선언: <code>&lt;script&gt;&lt;/script&gt;</code></li>
<li>타임리프 자바스크립트 인라인 사용 선언: <code>&lt;script th:inline=&quot;javascript&quot;&gt;&lt;/script&gt;</code><h4 id="⭐-controller-3">⭐ controller</h4>
<pre><code class="language-java">@GetMapping(&quot;...&quot;)
public String ...(Model model) {
  addUser(model);
  return &quot;...&quot;;
}
</code></pre>
</li>
</ul>
<p>private void addUser(Model model) {
    List<User> list = new ArrayList&lt;&gt;();
    list.add(new User(&quot;userA&quot;, 10));
    list.add(new User(&quot;userB&quot;, 20));
    list.add(new User(&quot;userC&quot;, 30));</p>
<pre><code>model.addAttribute(&quot;users&quot;, list);</code></pre><p>}</p>
<p>@Data
static class User {
    private String username;
    private int age;</p>
<pre><code>public User(String username, int age) {
    this.username = username;
    this.age = age;
}</code></pre><p>}</p>
<pre><code>### ✔ 일반적인 자바스크립트 사용
```html
&lt;!-- 자바스크립트 인라인 사용 전 --&gt;
&lt;script&gt;
 var username = [[${user.username}]];
 var age = [[${user.age}]];
 //자바스크립트 내추럴 템플릿
 var username2 = /*[[${user.username}]]*/ &quot;test username&quot;;
 //객체
 var user = [[${user}]];
&lt;/script&gt;</code></pre><p>타임리프에서 컨텐츠를 출력하는 [[...]] 문법을 사용한다면 자바스크립트에서도 model의 데이터를 가질 수 있다.</p>
<h4 id="⭐-결과">⭐ 결과</h4>
<pre><code class="language-html">&lt;script&gt;
var username = userA; //(1) Uncaught ReferenceError: userA is not defined
var age = 10;
//자바스크립트 내추럴 템플릿
var username2 = /*userA*/ &quot;test username&quot;; //(2)
//객체
var user = BasicController.User(username=userA, age=10); //(3)
&lt;/script&gt;</code></pre>
<ul>
<li>(1): 앞서 [[..]] 문법을 통해 user.username을 username 변수에 저장하고자 했지만 <code>Uncaught ReferenceError: userA is not defined</code> 에러가 발생한다. 이유는 userA는 문자열이므로 &#39; &#39; 또는 &quot; &quot;로 감싸야 하지만 타임리프의 컨텐츠 출력 문법에서는 user.username 값인 userA를 그대로 저장하려 했으므로 에러가 발생한다. 이러한 문제를 해결하기 위해서는  <code>var username = &#39;[[${user.username}]]&#39;;</code>로 선언해야 한다.</li>
<li>(2): 타임리프를 사용하기 되면 자바스크립트 역시 내추럴 템플릿을 지원하게끔 활용할 수 있다. 하지만 일반적인 자바스크립트 사용에서는 앞서 주석처리 된 부분은 무시되고 &quot;test username&quot;이 그대로 username2 변수에 저장된다.</li>
<li>(3): 원하던 결과는 user 객체를 자바스크립트 문법으로 사용하길 원했으나, user객체가 toString()된 결과이다. </li>
</ul>
<p><strong>위 3가지 문제를 인라인 자바스크립트를 사용함으로써 해결할 수 있다.</strong></p>
<hr>
<h3 id="✔-인라인-자바스크립트-사용">✔ 인라인 자바스크립트 사용</h3>
<pre><code class="language-html">&lt;!-- 자바스크립트 인라인 사용 후 --&gt;
&lt;script th:inline=&quot;javascript&quot;&gt;
 var username = [[${user.username}]];
 var age = [[${user.age}]];
 //자바스크립트 내추럴 템플릿
 var username2 = /*[[${user.username}]]*/ &quot;test username&quot;;
 //객체
 var user = [[${user}]];
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>script 태그안에 <code>th:inline=&quot;javascript&quot;</code>를 추가한다.</p>
<h4 id="⭐-결과-1">⭐ 결과</h4>
<pre><code class="language-html">&lt;script&gt;
var username = &quot;userA&quot;; //(1)
var age = 10;
//자바스크립트 내추럴 템플릿
var username2 = &quot;userA&quot;; //(2)
//객체
var user = {&quot;username&quot;:&quot;userA&quot;,&quot;age&quot;:10}; //(3)
&lt;/script&gt;</code></pre>
<ul>
<li>(1): user.username 값 userA가 자동으로 문자열로 변환되어 저장된다.</li>
<li>(2): 실제 작성한 <code>var username2 = /*[[${user.username}]]*/ &quot;test username&quot;;</code> 코드에서 타임리프에서 렌더링시  <code>/*[[${user.username}]]*/</code> 부분이 <code>&quot;test username&quot;</code>부분과 치환되어 출력된다. 이렇듯 소스파일을 그대로 열었을 경우 내추럴한 결과를 유지하지만 타임리프 렌더링시 값이 치환되어 내추럴 템플릿을 가능하게끔 한다.</li>
<li>(3): 자동으로 json을 파싱하여 자바스크립트에서 사용가능한 객체로 만들어 저장된다.</li>
</ul>
<h4 id="⭐-인라인-자바스크립트-theach-사용">⭐ 인라인 자바스크립트 th:each 사용</h4>
<p>인라인 자바크스립트 사용에서 model로 부터 전달받은 데이터를 타임리프의 th:each를 통하여 특별한 반복을 수행할 수 있다.</p>
<pre><code class="language-html">&lt;!-- 자바스크립트 인라인 each --&gt;
&lt;script th:inline=&quot;javascript&quot;&gt;
 [# th:each=&quot;user, stat : ${users}&quot;] //(1)
 var user[[${stat.count}]] = [[${user}]]; //(2)
 [/]
&lt;/script&gt;</code></pre>
<ul>
<li>(1): 기존의 html body내에서 사용하던 타임리프 th:each와 유사하게 사용할 수 있다.</li>
<li>(2): stat.count를 이용하여 매 반복마다 동적인 변수 이름 user1, user2, user3 을 생성할 수 있으며 해당 변수에 user 객체를 저장한다.<h5 id="결과">결과</h5>
<pre><code class="language-html">&lt;script&gt;
var user1 = {&quot;username&quot;:&quot;userA&quot;,&quot;age&quot;:10};
var user2 = {&quot;username&quot;:&quot;userB&quot;,&quot;age&quot;:20};
var user3 = {&quot;username&quot;:&quot;userC&quot;,&quot;age&quot;:30};
&lt;/script&gt;</code></pre>
매 반복마다 동적인 변수 이름을 생성하고 각 객체들이 변수에 저장 된다.</li>
<li>1회전: var user1 = {&quot;username&quot;:&quot;userA&quot;,&quot;age&quot;:10};</li>
<li>2회전: var user2 = {&quot;username&quot;:&quot;userB&quot;,&quot;age&quot;:20};</li>
<li>3회전: var user3 = {&quot;username&quot;:&quot;userC&quot;,&quot;age&quot;:30};</li>
</ul>
<hr>
<h2 id="✅-레이아웃-템플릿">✅ 레이아웃 템플릿</h2>
<p>타임리프에서는 각 페이지마자 공통되는, 예를들어 head, footer, 네비게이션바 등, HTML 태그들을 효율적으로 관리하기 위하여 템플릿 조각과 레이아웃 기능을 지원한다.</p>
<h3 id="✔-템플릿-조각">✔ 템플릿 조각</h3>
<h4 id="⭐-templatefragmentfooterhtml">⭐ template/fragment/footer.html</h4>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;

&lt;body&gt;

&lt;!-- 템플릿 조각으로 사용하고자 할때는 th:fragment=&quot;...&quot; 이름을 선언 해준다. --&gt;
&lt;footer th:fragment=&quot;copy&quot;&gt; &lt;!-- (1) --&gt;
  푸터 자리 입니다.
&lt;/footer&gt;

&lt;footer th:fragment=&quot;copyParam (param1, param2)&quot;&gt; &lt;!-- (2) --&gt;
  &lt;p&gt;파라미터 자리 입니다.&lt;/p&gt;
  &lt;p th:text=&quot;${param1}&quot;&gt;&lt;/p&gt;
  &lt;p th:text=&quot;${param2}&quot;&gt;&lt;/p&gt;
&lt;/footer&gt;

&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h4 id="⭐-templatefragmentmainhtml">⭐ template/fragment/main.html</h4>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;head&gt;
  &lt;meta charset=&quot;UTF-8&quot;&gt;
  &lt;title&gt;Title&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;부분 포함&lt;/h1&gt;
&lt;h2&gt;부분 포함 insert&lt;/h2&gt;
&lt;!-- insert: div안에 fragment name copy 삽입 --&gt;
&lt;div th:insert=&quot;~{template/fragment/footer :: copy}&quot;&gt;&lt;/div&gt; &lt;!-- (3) --&gt;
&lt;h2&gt;부분 포함 replace&lt;/h2&gt;
&lt;!-- replace: div와 fragment name copy와 교체, 즉 div가 사라짐 --&gt;
&lt;div th:replace=&quot;~{template/fragment/footer :: copy}&quot;&gt;&lt;/div&gt; &lt;!-- (4) --&gt;
&lt;h2&gt;부분 포함 단순 표현식&lt;/h2&gt;
&lt;!-- ~{...} 를 사용하는 것이 원칙이지만 템플릿 조각을 사용하는 코드가 단순하면 이 부분을 생략할 수 있다. --&gt;
&lt;div th:replace=&quot;template/fragment/footer :: copy&quot;&gt;&lt;/div&gt;
&lt;h1&gt;파라미터 사용&lt;/h1&gt;
&lt;!-- 파라미터 사용: div와 fragment name copyParam와 교체하며 파라미터 사용 가능 --&gt;
&lt;div th:replace=&quot;~{template/fragment/footer :: copyParam (&#39;데이터1&#39;, &#39;데이터2&#39;)}&quot;&gt;&lt;/div&gt; &lt;!-- (5) --&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<ul>
<li>(1): footer tag에 <code>th:fragment=&quot;copy&quot;</code>를 사용함으로써 조각 이름을 copy로 지정하였다.</li>
<li>(2): fragment를 메서드처럼 파라미터로도 구현할 수 있다. 조각 이름은 copyParam이며 param1, param2 파라미터가 있다.</li>
<li>(3): <code>th:insert</code>는 해당 div안에 tag를 삽입하겠다는 의미이며, 삽입하려는 태그는 <code>footer.html</code>에 fragment의 이름이 copy인 footer tag이다.</li>
<li>(4): <code>th:replace</code>는 해당 div를 fragment와 교체 하겠다는 의미이며, 교체하려는 태그는 <code>footer.html</code>에 fragment의 이름이 copy인 footer tag이다.</li>
<li>(5): 파라미터를 사용하므로 <code>&#39;데이터1&#39;, &#39;데이터2&#39;</code>가 footer.html의 copyParam fragment 파라미터로 넘어가서 출력된다.</li>
</ul>
<h4 id="⭐-출력-html">⭐ 출력 HTML</h4>
<pre><code class="language-html">
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;meta charset=&quot;UTF-8&quot;&gt;
  &lt;title&gt;Title&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;부분 포함&lt;/h1&gt;
&lt;h2&gt;부분 포함 insert&lt;/h2&gt;
&lt;!-- insert: div안에 fragment name copy 삽입 --&gt;
&lt;div&gt;&lt;footer&gt;
  푸터 자리 입니다.
&lt;/footer&gt;&lt;/div&gt;
&lt;h2&gt;부분 포함 replace&lt;/h2&gt;
&lt;!-- replace: div와 fragment name copy와 교체, 즉 div가 사라짐 --&gt;
&lt;footer&gt;
  푸터 자리 입니다.
&lt;/footer&gt;
&lt;h2&gt;부분 포함 단순 표현식&lt;/h2&gt;
&lt;!-- ~{...} 를 사용하는 것이 원칙이지만 템플릿 조각을 사용하는 코드가 단순하면 이 부분을 생략할 수 있다. --&gt;
&lt;footer&gt;
  푸터 자리 입니다.
&lt;/footer&gt;
&lt;h1&gt;파라미터 사용&lt;/h1&gt;
&lt;!-- 파라미터 사용: div와 fragment name copyParam와 교체하며 파라미터 사용 가능 --&gt;
&lt;footer&gt;
  &lt;p&gt;파라미터 자리 입니다.&lt;/p&gt;
  &lt;p&gt;데이터1&lt;/p&gt;
  &lt;p&gt;데이터2&lt;/p&gt;
&lt;/footer&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<hr>
<h3 id="✔-템플릿-레이아웃-head-삽입">✔ 템플릿 레이아웃 <code>&lt;head&gt;</code> 삽입</h3>
<p>각 페이지마다 공통된 <code>&lt;head&gt;</code>를 사용할 수 있도록 구성 해보자.</p>
<h4 id="⭐-templatelayoutbasehtml">⭐ template/layout/base.html</h4>
<pre><code class="language-html">&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;head th:fragment=&quot;common_header(title,links)&quot;&gt; &lt;!-- (1) --&gt;

  &lt;!--매개변수로 전달받은 title을 replace 한다.--&gt;
  &lt;title th:replace=&quot;${title}&quot;&gt;레이아웃 타이틀&lt;/title&gt; &lt;!-- (2) --&gt;

  &lt;!-- 공통 --&gt;
  &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; media=&quot;all&quot; th:href=&quot;@{/css/awesomeapp.css}&quot;&gt;
  &lt;link rel=&quot;shortcut icon&quot; th:href=&quot;@{/images/favicon.ico}&quot;&gt;
  &lt;script type=&quot;text/javascript&quot; th:src=&quot;@{/sh/scripts/codebase.js}&quot;&gt;&lt;/script&gt;

  &lt;!-- 추가 --&gt;
  &lt;!--매개변수로 전달받은 title를 replace 한다.--&gt;
  &lt;th:block th:replace=&quot;${links}&quot; /&gt; &lt;!-- (3) --&gt;
&lt;/head&gt;</code></pre>
<h4 id="⭐-templatelayoutlayoutmainhtml">⭐ template/layout/layoutMain.html</h4>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;!-- template/layout/base.html의 common_header를 replace로 사용하고 title와 link tag를 매개변수로 넘긴다. --&gt;
&lt;head th:replace=&quot;template/layout/base :: common_header(~{::title},~{::link})&quot;&gt; &lt;!-- (4) --&gt;
    &lt;title&gt;메인 타이틀&lt;/title&gt;
    &lt;link rel=&quot;stylesheet&quot; th:href=&quot;@{/css/bootstrap.min.css}&quot;&gt;
    &lt;link rel=&quot;stylesheet&quot; th:href=&quot;@{/themes/smoothness/jquery-ui.css}&quot;&gt;
&lt;/head&gt;
&lt;body&gt;
메인 컨텐츠
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<ul>
<li>(1): 파라미터 타입의 head tag fragment이다.</li>
<li>(2): (1)에서 파라미터로 전달받은 title tag와 교체된다.</li>
<li>(3): (1)에서 파라미터로 잔달받은 link tag와 교체된다.</li>
<li>(4): head tag를 base.html의 common_header이름을 가지는 fragment와 교체(th:replace) 하겠다는 의미이며, <code>~{::title}, ~{::link}</code>은 title,link tag를 파라미터로 넘기겠다는 의미이다. 즉 <code>layoutMain.html</code>의 head tag는 <code>base.html</code>의 head tag로 교체되며, 단 title,link tag들은 파라미터로 넘겨서 <code>base.html</code>의 head tag에서 (2), (3)과 교체된다.</li>
</ul>
<h4 id="⭐-출력-html-1">⭐ 출력 HTML</h4>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;!-- template/layout/base.html의 common_header를 replace로 사용하고 title와 link tag를 매개변수로 넘긴다. --&gt;
&lt;head&gt;

  &lt;!--매개변수로 전달받은 title을 replace 한다.--&gt;
  &lt;title&gt;메인 타이틀&lt;/title&gt;

  &lt;!-- 공통 --&gt;
  &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; media=&quot;all&quot; href=&quot;/css/awesomeapp.css&quot;&gt;
  &lt;link rel=&quot;shortcut icon&quot; href=&quot;/images/favicon.ico&quot;&gt;
  &lt;script type=&quot;text/javascript&quot; src=&quot;/sh/scripts/codebase.js&quot;&gt;&lt;/script&gt;

  &lt;!-- 추가 --&gt;
  &lt;!--매개변수로 전달받은 title를 replace 한다.--&gt;
  &lt;link rel=&quot;stylesheet&quot; href=&quot;/css/bootstrap.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;/themes/smoothness/jquery-ui.css&quot;&gt;
&lt;/head&gt;
&lt;body&gt;
메인 컨텐츠
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<hr>
<h3 id="✔-템플릿-레이아웃-html-삽입">✔ 템플릿 레이아웃 <code>&lt;html&gt;</code> 삽입</h3>
<p>모든 레이아웃을 가지는 html 파일을 사용하고 내부의 title과 body만 교체할 수 있도록 구성해보자.</p>
<h4 id="⭐-templatelayoutlayoutfilehtml">⭐ template/layout/layoutFile.html</h4>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html th:fragment=&quot;layout (title, content)&quot; xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt; &lt;!-- (1) --&gt;
&lt;head&gt;
    &lt;title th:replace=&quot;${title}&quot;&gt;레이아웃 타이틀&lt;/title&gt; &lt;!-- (2) --&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;레이아웃 H1&lt;/h1&gt;
&lt;div th:replace=&quot;${content}&quot;&gt; &lt;!-- (3) --&gt;
    &lt;p&gt;레이아웃 컨텐츠&lt;/p&gt;
&lt;/div&gt;
&lt;footer&gt;
    레이아웃 푸터
&lt;/footer&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h4 id="⭐-templatelayoutlayoutextendmainhtml">⭐ template/layout/layoutExtendMain.html</h4>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html th:replace=&quot;~{template/layoutExtend/layoutFile :: layout(~{::title},~{::section})}&quot; xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt; &lt;!-- (4) --&gt;
&lt;head&gt;
    &lt;title&gt;메인 페이지 타이틀&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;section&gt;
    &lt;p&gt;메인 페이지 컨텐츠&lt;/p&gt;
    &lt;div&gt;메인 페이지 포함 내용&lt;/div&gt;
&lt;/section&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<ul>
<li>(1): 파라미터 타입의 html tag fragment이다. 즉 html 자체를 교체한다.</li>
<li>(2): (1)에서 파라미터로 전달받은 title tag와 교체된다.</li>
<li>(3): (1)에서 파라미터로 잔달받은 section tag와 교체된다.</li>
<li>(4): html tag를 layoutFile.html의 layout이름을 가지는 fragment와 교체(th:replace) 하겠다는 의미이며, <code>~{::title}, ~{::section}</code>은 title,section tag를 파라미터로 넘기겠다는 의미이다. 즉 <code>layoutExtendMain.html</code>의 html tag는 <code>layoutFile.html</code>의 html tag로 교체되며, 단 title,section tag들은 파라미터로 넘겨서 <code>layoutFile.html</code>에서 (2), (3) tag와 교체된다.</li>
</ul>
<h4 id="⭐-출력-html-2">⭐ 출력 HTML</h4>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;메인 페이지 타이틀&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;레이아웃 H1&lt;/h1&gt;
&lt;section&gt;
    &lt;p&gt;메인 페이지 컨텐츠&lt;/p&gt;
    &lt;div&gt;메인 페이지 포함 내용&lt;/div&gt;
&lt;/section&gt;
&lt;footer&gt;
    레이아웃 푸터
&lt;/footer&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[Thymeleaf 기본기능 정복하기[1편]]]></title>
            <link>https://velog.io/@won-developer/Thymeleaf-%EA%B8%B0%EB%B3%B8%EA%B8%B0%EB%8A%A5-%EC%A0%95%EB%B3%B5%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@won-developer/Thymeleaf-%EA%B8%B0%EB%B3%B8%EA%B8%B0%EB%8A%A5-%EC%A0%95%EB%B3%B5%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 01 Nov 2021 11:54:52 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/won-developer/post/99386fee-fe87-4b53-8249-268f90e0d0b5/thymeleaf.PNG" alt=""></p>
<hr>
<blockquote>
<ul>
<li><a href="https://www.thymeleaf.org/">공식 사이트</a></li>
</ul>
</blockquote>
<ul>
<li><a href="https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html">공식 메뉴얼 - 기본기능</a></li>
<li><a href="https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html">공식 메뉴얼 - 스프링 통합</a></li>
</ul>
<hr>
<blockquote>
<p>학습참조
<a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard">인프런-스프링 MVC 2편 백엔드 웹 개발 활용 기술(김영한님)</a></p>
</blockquote>
<hr>
<p>이 글을 작성하고 난 뒤에는 내가 나중에 타임리프를 사용할 때에, 문법이 잘 기억이 나지 않을 때 이 글만 보아도 기본적인 타임리프 기능들을 사용할 수 있었으면 한다.</p>
<hr>
<h2 id="✅-다루는-기능들">✅ 다루는 기능들</h2>
<h5 id="🚩-thymeleaf타임리프란">🚩 Thymeleaf(타임리프)란?</h5>
<h5 id="🚩-텍스트-출력하기---text----utext--">🚩 텍스트 출력하기 - text / [[...]] , utext / [(...)]</h5>
<h5 id="🚩-변수---springel">🚩 변수 - SpringEL</h5>
<h5 id="🚩-타임리프-기본-객체">🚩 타임리프 기본 객체</h5>
<h5 id="🚩-유틸리티-객체">🚩 유틸리티 객체</h5>
<h5 id="🚩-url">🚩 Url</h5>
<h5 id="🚩-리터럴literals">🚩 리터럴(Literals)</h5>
<hr>
<h2 id="✅-thymeleaf타임리프란">✅ Thymeleaf(타임리프)란?</h2>
<h3 id="✔-서버사이드-렌더링server-side-rendering-ssr">✔ 서버사이드 렌더링(Server Side Rendering, SSR)</h3>
<p> 클라이언트 사이드 렌더링(Client Side Rendering, CSR)이 아닌 서버에서 동적으로 HTML을 만들어 렌더링하는 할수있는 템플릿</p>
<h3 id="✔-네츄럴-템플릿natural-templates">✔ 네츄럴 템플릿(Natural templates)</h3>
<ul>
<li>순수 HTML을 유지하는 템플릿.</li>
<li>타임리프로 작성된 파일은 기본 HTML 구조를 유지하기 때문에 파일을 직접 열어도, 서버를 통해 렌더링 하더라도 일관된 HTML 구조를 유지한다.</li>
<li>JSP를 포함한 다른 템플릿들은 동적으로 렌더링 하기위하여 여러 기능들을 사용하는데 이 과정에서 HTML 구조가 뒤죽박죽 섞이기 때문에 정상적인 HTML 구조를 유지하기가 어렵다.</li>
<li>이렇게 순수 HTML을 그대로 유지하며 템플릿을 사용할 수 있는 타임리프의 특징을 네츄럴 팀플릿 이라한다.<h3 id="✔-스프링-통합-지원">✔ 스프링 통합 지원</h3>
스프링에서는 공식으로 타임리프를 사용할 것을 권장하며 스프링의 다양한 기능을 편리하게 사용할 수 있게끔 제공한다.<h3 id="✔-thymeleaf타임리프-사용선언">✔ Thymeleaf(타임리프) 사용선언</h3>
최상단 HTML 코드에 다음과 같이 선언한다.<pre><code class="language-html">&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;</code></pre>
</li>
</ul>
<hr>
<h2 id="✅-텍스트-출력하기---text----utext--">✅ 텍스트 출력하기 - text / [[...]] , utext / [(...)]</h2>
<h4 id="⭐-controller">⭐ controller</h4>
<pre><code class="language-java">@GetMapping(&quot;...&quot;)
public String ...(Model model) {
    model.addAttribute(&quot;data&quot;, &quot;Hello Spring!&quot;);
    return &quot;...&quot;;
}
</code></pre>
<h3 id="✔-thtext--">✔ th:text / [[...]]</h3>
<pre><code class="language-html">&lt;ul&gt;
    &lt;li&gt;th:text 사용 : &lt;span th:text=&quot;${data}&quot;&gt;&lt;/span&gt;&lt;/li&gt;
    &lt;li&gt;컨텐츠 안에서 직접 출력하기 : [[${data}]]&lt;/li&gt;
&lt;/ul&gt;</code></pre>
<h5 id="출력">출력</h5>
<ul>
<li>th:text 사용 : Hello Spring!</li>
<li>컨텐츠 안에서 직접 출력하기 : Hello Spring!</li>
</ul>
<hr>
<h4 id="⭐-html-엔티티">⭐ HTML 엔티티</h4>
<p>스프링에서 Model에 담아 넘겨줄때부터 직접 <code>&lt;b&gt;&lt;/b&gt;</code> 코드를 작성하여 넘겨주어 보자.</p>
<pre><code class="language-java">@GetMapping(&quot;...&quot;)
public String ...(Model model) {
    model.addAttribute(&quot;data&quot;, &quot;Hello &lt;b&gt;Spring!&lt;/b&gt;&quot;);
    return &quot;...&quot;;
}</code></pre>
<p>이렇게 하면 개발자가 의도한 바로는 &quot;Spring!&quot;이 강조 처리된 &quot;Hello <strong>Spring!</strong>&quot;이 출력되어야 하겠지만 사실은 <code>Hello &lt;b&gt;Spring!&lt;b&gt;</code>가 출력된다. 웹브라우저는 &#39;&lt;&#39; 를 HTML 태그의 시작으로 인식한다. 따라서 &#39;&lt;&#39;를 HTML 태그가 아닌 단순히 문자로 표현할 수 있는 방법이 필요한데 이것을 HTML 엔티티라 한다. 그리고 이렇게 HTML에서 사용하는 특수문자를 HTML 엔티티로 변경하는 것을 이스케이프(escape)라 하는데 타임리프는 기본적으로 th:text, [[...]]을 통해 이스케이프(escape)를 제공한다. </p>
<h5 id="html-엔티티는-다음과-같으며-이외에도-수많은-엔티티가-있다">HTML 엔티티는 다음과 같으며 이외에도 수많은 엔티티가 있다.</h5>
<ul>
<li>공백 : <code>&amp;nbsp;</code></li>
<li>&quot;&lt;&quot; : <code>&amp;lt;</code></li>
<li>&quot;&gt;&quot; : <code>&amp;gt;</code></li>
</ul>
<p>그렇다면 이스케이프(escape)를 지원하지 않으려면 어떻게 해야할까? 이 역시 타임리프에서 th:utext, [(...)]를 통해 지원한다.</p>
<hr>
<h3 id="✔-thutext--">✔ th:utext / [(...)]</h3>
<p>th:utext와 [(...)]을 통해 이스케이프 되지 않은 기능을 제공한다.</p>
<pre><code class="language-html">&lt;ul&gt;
    &lt;li&gt;th:text 사용 : &lt;span th:utext=&quot;${data}&quot;&gt;&lt;/span&gt;&lt;/li&gt;
    &lt;li&gt;컨텐츠 안에서 직접 출력하기 : [(${data})]&lt;/li&gt;
&lt;/ul&gt;</code></pre>
<h5 id="출력-1">출력</h5>
<ul>
<li>th:text 사용 : Hello <strong>Spring!</strong></li>
<li>컨텐츠 안에서 직접 출력하기 : Hello <strong>Spring!</strong></li>
</ul>
<p>이렇게 한다면 개발자가 의도한 대로 &quot;Hello <strong>Spring!</strong>&quot;이 출력되는 것을 확인할 수 있다.</p>
<hr>
<h2 id="✅-변수---springel">✅ 변수 - SpringEL</h2>
<p>타임리프에서 변수를 사용할 때는 변수 표현식을 사용한다. 변수 표현식은 다음과 같다. &quot;<code>${...}</code>&quot;
그리고 이 변수 표현식에는 SpringEL이라는 스프링이 제공하는 표현식을 사용할 수 있다.</p>
<h4 id="⭐-controller-1">⭐ controller</h4>
<pre><code class="language-java">@GetMapping(&quot;...&quot;)
public String ...(Model model) {

    User userA = new User(&quot;userA&quot;);

    List&lt;User&gt; list = new ArrayList&lt;&gt;();
    list.add(userA);

    Map&lt;String, Object&gt; map = new Map&lt;&gt;();
    map.put(&quot;userA&quot;, userA);

    model.addAttribute(&quot;userObject&quot;, user);
    model.addAttribute(&quot;userList&quot;, list);
    model.addAttribute(&quot;userMap&quot;, map);

    return &quot;...&quot;;
}

class User {
    private String name;

    public User(String name) {
        this.name = name;
    }
}</code></pre>
<h3 id="✔-springel-표현식">✔ SpringEL 표현식</h3>
<h4 id="⭐-object">⭐ Object</h4>
<pre><code class="language-html">&lt;ul&gt;
    &lt;li&gt;${userObject.name} = &lt;span th:utext=&quot;${userObject.name}&quot;&gt;&lt;/span&gt;&lt;/li&gt;
     &lt;li&gt;${userObject.[&#39;name&#39;]} = &lt;span th:utext=&quot;&gt;${userObject.[&#39;name&#39;]}&quot;&gt;&lt;/span&gt;&lt;/li&gt;
     &lt;li&gt;${userObject.getName()} = &lt;span th:utext=&quot;${userObject.getName()}&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</code></pre>
<h5 id="출력-2">출력</h5>
<ul>
<li>${userObject.name} = userA</li>
<li>${userObject.[&#39;name&#39;]} = userA</li>
<li>${userObject.getName()} = userA</li>
</ul>
<hr>
<h4 id="⭐-list">⭐ List</h4>
<pre><code class="language-html">&lt;ul&gt;
    &lt;li&gt;${userList[0].name} = &lt;span th:utext=&quot;${userList[0].name}&quot;&gt;&lt;/span&gt;&lt;/li&gt;
     &lt;li&gt;${userList[0].[&#39;name&#39;]} = &lt;span th:utext=&quot;&gt;${userList[0].[&#39;name&#39;]}&quot;&gt;&lt;/span&gt;&lt;/li&gt;
     &lt;li&gt;${userList[0].getName()} = &lt;span th:utext=&quot;${userList[0].getName()}&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</code></pre>
<h5 id="출력-3">출력</h5>
<ul>
<li>${userList[0].name} = userA</li>
<li>${userList[0].[&#39;name&#39;]} = userA</li>
<li>${userList[0].getName()} = userA</li>
</ul>
<hr>
<h4 id="⭐-map">⭐ Map</h4>
<pre><code class="language-html">&lt;ul&gt;
    &lt;li&gt;${userMap[&#39;userA&#39;].name} = &lt;span th:utext=&quot;${userMap[&#39;userA&#39;].name}&quot;&gt;&lt;/span&gt;&lt;/li&gt;
     &lt;li&gt;${userMap[&#39;userA&#39;].[&#39;name&#39;]} = &lt;span th:utext=&quot;&gt;${userMap[&#39;userA&#39;].[&#39;name&#39;]}&quot;&gt;&lt;/span&gt;&lt;/li&gt;
     &lt;li&gt;${userMap[&#39;userA&#39;].getName()} = &lt;span th:utext=&quot;${userMap[&#39;userA&#39;].getName()}&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</code></pre>
<h5 id="출력-4">출력</h5>
<ul>
<li>${userMap[&#39;userA&#39;].name} = userA</li>
<li>${userMap[&#39;userA&#39;].[&#39;name&#39;]} = userA</li>
<li>${userMap[&#39;userA&#39;].getName()} = userA</li>
</ul>
<hr>
<h3 id="✔-지역변수">✔ 지역변수</h3>
<p>타임리프 내에서도 지역변수를 선언하여 사용할 수 있으며 선언한 태그안에서만 사용가능 하다.</p>
<pre><code class="language-html">&lt;div th:with=&quot;person=${users[0]}&quot;&gt; &lt;!-- (1) --&gt;
 &lt;p&gt;사람의 이름은 &lt;span th:text=&quot;${person.name}&quot;&gt;&lt;/span&gt;&lt;/p&gt; &lt;!-- (2) --&gt;
&lt;/div&gt;</code></pre>
<ul>
<li>(1): th:with를 이용하여 변수명이 <code>person</code>이고 <code>users[0]</code>을 값으로 가지는 지역변수 선언.</li>
<li>(2): <code>person</code>이 <code>users[0]</code>을 가지고 있으므로 변수표현식 <code>${person.name}</code>을 통하여 출력.</li>
<li>해당 <code>person</code> 변수는 div 내에서만 사용가능.<h5 id="출력-5">출력</h5>
<code>사람의 이름은 userA</code></li>
</ul>
<hr>
<h2 id="✅-타임리프-기본-객체">✅ 타임리프 기본 객체</h2>
<p>타임리프는 다음과 같은 기본 객체들을 제공한다.</p>
<h3 id="✔-기본객체">✔ 기본객체</h3>
<ul>
<li><code>${#request}</code></li>
<li><code>${#response}</code></li>
<li><code>${#session}</code></li>
<li><code>${#servletContext}</code></li>
<li><code>${#locale}</code></li>
</ul>
<p><code>#request</code>는 <code>HttpServletRequest</code> 객체에 접근하듯 <code>reqeust.geParameter(&quot;...&quot;)</code>와 같이 접근해야 하지만 이러한 점을 해결하기 위한 편의 객체를 제공한다.</p>
<hr>
<h3 id="✔-편의객체">✔ 편의객체</h3>
<h4 id="⭐-params">⭐ params</h4>
<pre><code class="language-html">&lt;p th:text=&quot;${params.username}&quot;&gt;username&lt;/p&gt;</code></pre>
<p>http 호출시 <code>http://....?username=gwjeon</code>로 접속되었다면 <code>username</code>이 parameter에 존재하기 때문에 출력 결과는 <code>gwjeon</code> 이 된다.</p>
<hr>
<h4 id="⭐-session">⭐ session</h4>
<h5 id="controller">Controller</h5>
<pre><code class="language-java">@GetMapping(&quot;...&quot;)
public String ...(HttpSession session) {
     session.setAttribute(&quot;sessionData&quot;, &quot;Hello Spring&quot;);
     return &quot;...&quot;;
}</code></pre>
<pre><code class="language-html">&lt;p th:text=&quot;${session.sessionData}&quot;&gt;sessionData&lt;/p&gt;</code></pre>
<p>세션 접근도 지원한다. 컨트롤러에서 세션에 <code>sessionData</code>를 key로 한 <code>Hello Spring</code> 문자열을 저장하였다. 출력 결과는 <code>Hello Spring</code>이 된다.</p>
<hr>
<h4 id="⭐-스프링-빈-접근">⭐ 스프링 빈 접근</h4>
<p>스프링 빈에 접근하는것도 지원한다. </p>
<pre><code class="language-java">@Component(&quot;helloBean&quot;)
class HelloBean {
    public String hello(String data) {
        return &quot;Hello &quot; + data;
    }
}</code></pre>
<p>다음과 같은 스프링 빈이 있다고 가정 했을때 </p>
<pre><code class="language-html">&lt;p th:text=&quot;${@helloBean.hello(&#39;Hello Srping&#39;)}&quot;&gt;springBean&lt;/p&gt;</code></pre>
<p>와 같이 한다면 출력 결과는 <code>Hello Spring</code>이 된다. <code>@helloBean</code>은 빈이름이 되며 첫글자는 소문자로 변경되어 표기하고 <code>.hello</code>를 통해 빈에 등록된 메서드에 Hello Spring 문자열을 argument로 전달한다.</p>
<hr>
<h2 id="✅-유틸리티-객체">✅ 유틸리티 객체</h2>
<p>타임리프에서 지원하는 유틸리 객체는 다음과 같다.</p>
<ul>
<li><code>#message</code> : 메시지, 국제화 처리</li>
<li><code>#uris</code> : URI 이스케이프 지원</li>
<li><code>#dates</code> : java.util.Date 서식 지원</li>
<li><code>#calendars</code> : java.util.Calendar 서식 지원</li>
<li><code>#temporals</code> : 자바8 날짜 서식 지원</li>
<li><code>#numbers</code> : 숫자 서식 지원</li>
<li><code>#strings</code> : 문자 관련 편의 기능</li>
<li><code>#objects</code> : 객체 관련 기능 제공</li>
<li><code>#bools</code> : boolean 관련 기능 제공</li>
<li><code>#arrays</code> : 배열 관련 기능 제공</li>
<li><code>#lists , #sets , #maps</code> : 컬렉션 관련 기능 제공</li>
<li><code>#ids</code> : 아이디 처리 관련 기능 제공</li>
</ul>
<p>워낙 종류가 많으니 어떠한 것이 있는지만 기억하고 사용하기 전 <a href="https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#appendix-b-expression-utility-objects">매뉴얼</a>을 참고하자.</p>
<h3 id="✔-java8-날짜-객체">✔ JAVA8 날짜 객체</h3>
<p>타임리프에서 JAVA8 날짜인 LocalDate , LocalDateTime , Instant 를 사용하려면 추가 라이브러리 <code>thymeleaf-extras-java8time</code>가 필요하며 스프링 부트 타임리프를 사용하면 해당 라이브러리가 자동으로 추가된다. JAVA8 날짜 유틸리티 객체는 <code>#temporals</code> 이다.</p>
<hr>
<h2 id="✅-url">✅ Url</h2>
<p>타임리프에서는 기본적으로 Url은 모두 <code>@{}</code>안에 정의 되어야 한다.</p>
<h4 id="⭐-controller-2">⭐ controller</h4>
<pre><code class="language-java">@GetMapping(&quot;...&quot;)
public String ...(Model model) {
    model.addAttribute(&quot;param1&quot;, &quot;data1&quot;);
    model.addAttribute(&quot;param2&quot;, &quot;data2&quot;);

    return &quot;...&quot;;
}</code></pre>
<h3 id="✔-여러가지-url-타입">✔ 여러가지 url 타입</h3>
<pre><code class="language-html">&lt;ul&gt;
    &lt;li&gt;&lt;a th:href=&quot;@{/hello}&quot;&gt;basic url&lt;/a&gt;&lt;/li&gt; &lt;!-- (1) --&gt;
    &lt;li&gt;&lt;a th:href=&quot;@{/hello(param1=${param1}, param2=${param2})}&quot;&gt;hello query param&lt;/a&gt;&lt;/li&gt; &lt;!-- (2) --&gt;
    &lt;li&gt;&lt;a th:href=&quot;@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}&quot;&gt;path variable&lt;/a&gt;&lt;/li&gt; &lt;!-- (3) --&gt;
    &lt;li&gt;&lt;a th:href=&quot;@{/hello/{param1}(param1=${param1}, param2=${param2})}&quot;&gt;path variable + query parameter&lt;/a&gt;&lt;/li&gt; &lt;!-- (4) --&gt;
&lt;/ul&gt;</code></pre>
<p>기본적으로 url 구조를 먼저 작성하고 뒤에 ()괄호안에 동적으로 들어갈 value를 할당하는 구조이다.</p>
<ul>
<li>(1): 기본 url 타입으로 <code>/hello</code>로 이동한다.</li>
<li>(2): parameter 타입으로 <code>/hello?param1=data1&amp;param=data2</code>로 이동한다.</li>
<li>(3): path variable 타입으로 <code>/hello/{...}/{...}</code>로 이동한다.</li>
<li>(4): parameter와 path variable을 같이 사용 하는것으로 <code>/hello/{...}?param2=data2</code>로 이동한다. 이 경우는 url상에 variable인 param1까지만 할당되고 param2는 해당되는것이 없음으로 자동으로 parameter로 할당된다.</li>
</ul>
<hr>
<h2 id="✅-리터럴literals">✅ 리터럴(Literals)</h2>
<p>타임리프에는 다음과 같은 리터럴이 있다.</p>
<ul>
<li>문자</li>
<li>숫자</li>
<li>boolean</li>
<li>null<h3 id="✔-문자-리터럴">✔ 문자 리터럴</h3>
타임리프 문자 리터럴은 항상 <code>&#39;...&#39;</code>  작은 따옴표로 감싸야 한다.</li>
</ul>
<h4 id="⭐-공백-없이-쭉-이어지는-문자인-경우">⭐ 공백 없이 쭉 이어지는 문자인 경우</h4>
<pre><code class="language-html">&lt;span th:text=&quot;hello&quot;&gt;&lt;/span&gt;</code></pre>
<p>위 처럼 공백 없이 쭉 이어지는 문자라면 작은 따옴표를 생략할 수 있다.</p>
<h4 id="⭐-공백이-있는-문자인-경우">⭐ 공백이 있는 문자인 경우</h4>
<pre><code class="language-html">&lt;span th:text=&quot;hello spring!&quot;&gt;&lt;/span&gt; &lt;!-- (1) --&gt;
&lt;span th:text=&quot;&#39;hello spring!&#39;&quot;&gt;&lt;/span&gt; &lt;!-- (2) --&gt;</code></pre>
<ul>
<li>(1): 공백이 있는 문자의 경우는 에러가 발생한다.</li>
<li>(2): 공백이 있을 경우 작은 따옴표로 감싸야 한다.</li>
</ul>
<hr>
<h3 id="✔-리터럴-대체literal-substitutions">✔ 리터럴 대체(Literal substitutions)</h3>
<p>하지만 매번 이렇게 리터럴을 작은 따옴표를 통하여 작성하는 것은 비효율적이다.
리터럴 대체문법을 사용하면 매우 간편하게 해결할 수 있다.</p>
<pre><code class="language-html">&lt;span th:text=&quot;|hello spring!|&quot;&gt;&lt;/span&gt;</code></pre>
<p>위 처럼 문자를 <code>|...|</code>으로 감싸주면 작은 따옴표로 감싸지 않아도 된다. 여기서 의문이 든다. 
<code>&quot;&#39;hello spring!&#39;&quot;</code>이나 <code>&quot;|hello spring!|&quot;</code>이나 똑같이 문자를 감싸야 하는건 동일한데 도대체 무엇이 다른것인가?</p>
<p>리터럴 대체문법은 마치 JavaScript의 리터럴 템플릿 문법과 같다. </p>
<p>예를들어 다음과 같은 controller가 있다고 가정하자.</p>
<h4 id="⭐-controller-3">⭐ controller</h4>
<pre><code class="language-java">@GetMapping(&quot;...&quot;)
public String ...(Model model) {
    model.addAttribute(&quot;data&quot;, &quot;Spring!&quot;);
    return &quot;...&quot;;
}</code></pre>
<h4 id="⭐-리터럴-대체-문법을-써야하는-이유">⭐ 리터럴 대체 문법을 써야하는 이유</h4>
<pre><code class="language-html">&lt;span th:text=&quot;&#39;hello &#39; + ${data}&quot;&gt;&lt;/span&gt; &lt;!-- (1) --&gt;
&lt;span th:text=&quot;|hello ${data}|&quot;&gt;&lt;/span&gt; &lt;!-- (2) --&gt;</code></pre>
<p>똑같은 hello Spring! 이라는 문자를 출력하는 내용이지만 2가지 방법으로 갈린다. </p>
<ul>
<li>(1): 모델로부터 전달받은 data를 출력하기 위해선 + 연산자를 통해야 하며 공백을 위하여 <code>hello</code> 문자 뒤에 공백을 추가했다.</li>
<li>(2): 리터럴 대체 문법을 통하여 <code>|...|</code> 로 감싸기만 했을 뿐인데 마치 문자를 사용하듯 편리하다. <code>|...|</code>안에 있는 공백을 포함한 문자들을 출력하고 <code>${data}</code> 변수부분은 <code>Spring!</code>로 치환된다.</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[request queryParamater를 읽는 여러가지 방법]]></title>
            <link>https://velog.io/@won-developer/request-queryParamater%EB%A5%BC-%EC%9D%BD%EB%8A%94-%EC%97%AC%EB%9F%AC%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@won-developer/request-queryParamater%EB%A5%BC-%EC%9D%BD%EB%8A%94-%EC%97%AC%EB%9F%AC%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Sat, 23 Oct 2021 05:41:01 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/won-developer/post/27b76193-88ab-450d-991d-e4c0324f9992/spring.PNG" alt=""></p>
<hr>
<blockquote>
<p>학습참조
<a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard">인프런-스프링 MVC 1편 백엔드 웹 개발 핵심 기술(김영한님)</a></p>
</blockquote>
<hr>
<h2 id="✅-servlet-방식">✅ Servlet 방식</h2>
<pre><code class="language-java">    public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String username = request.getParameter(&quot;username&quot;);
        int age = Integer.parseInt(request.getParameter(&quot;age&quot;));

        log.info(&quot;username={} age={}&quot;, username, age);

        response.getWriter().write(&quot;ok&quot;);
    }</code></pre>
<ul>
<li>가장 기본적인 서블릿 방식의 request.getParameter를 사용한 방식.</li>
</ul>
<hr>
<h2 id="✅-requestparam-애노테이션-방식">✅ @RequestParam 애노테이션 방식</h2>
<h3 id="☑-가장-기본적인-형태">☑ 가장 기본적인 형태</h3>
<pre><code class="language-java">    public String requestParamV2(
            @RequestParam(&quot;username&quot;) String userName,
            @RequestParam(&quot;age&quot;) int userAge) {

        log.info(&quot;username={} age={}&quot;, userName, userAge);
        return &quot;ok&quot;;
    }</code></pre>
<h3 id="☑-애노테이션-requestname을-생략한-형태">☑ 애노테이션 @Request(name=&quot;&quot;)을 생략한 형태</h3>
<pre><code class="language-java">    public String requestParamV3(
            @RequestParam String username,
            @RequestParam int age) {

        log.info(&quot;username={} age={}&quot;, username, age);
        return &quot;ok&quot;;
    }</code></pre>
<ul>
<li>위와 같이 생략 할 경우 변수이름이 paramter 이름과 동일 해야함.</li>
</ul>
<h3 id="☑-애노테이션-자체를-생략한-형태">☑ 애노테이션 자체를 생략한 형태</h3>
<pre><code class="language-java">    public String requestParamV4(String username, int age) {

        log.info(&quot;username={} age={}&quot;, username, age);
        return &quot;ok&quot;;
    }</code></pre>
<ul>
<li>parameter value type이 primitive 형태(int, long, float, double 등) 인 경우 생략 가능.</li>
</ul>
<h3 id="☑-required-옵션">☑ required 옵션</h3>
<pre><code class="language-java">    public String requestParamRequired(
            @RequestParam(required = true) String username,
            @RequestParam(required = false) Integer age) {

        log.info(&quot;username={} age={}&quot;, username, age);
        return &quot;ok&quot;;
    }</code></pre>
<ul>
<li>default는 true이며 생략가능하고 true일 경우 반드시 해당 parameter가 담긴상태로 요청해야 한다. 이를 어길 경우 Bad Request 400 error 발생함.</li>
<li>parameter의 value가 빈 문자일 경우 null이 아닌 공백(&quot;&quot;)이 적용됨.</li>
<li>...../username= 와 같은 요청이 들어왔을 경우 username은 &quot;&quot;이며 age는 null. int타입은 null을 가질수 없으므로 Integer 타입으로 선언되어야 한다. </li>
</ul>
<h3 id="☑-defaultvalue-옵션">☑ defaultValue 옵션</h3>
<pre><code class="language-java">    public String requestParamDefault(
            @RequestParam(defaultValue = &quot;guest&quot;) String username,
            @RequestParam(defaultValue = &quot;-1&quot;) int age) {

        log.info(&quot;username={} age={}&quot;, username, age);
        return &quot;ok&quot;;
    }</code></pre>
<ul>
<li>parameter가 지정되지 않아도 자동으로 default 값을 가지며 username은 &quot;geust&quot;, age는 -1 가 됨.</li>
</ul>
<h3 id="☑-map을-이용한-모든-parameter-가져오기">☑ Map을 이용한 모든 parameter 가져오기</h3>
<pre><code class="language-java">    public String requestParamMap(@RequestParam Map&lt;String, Object&gt; paramMap){

        log.info(&quot;username={} age={}&quot;, paramMap.get(&quot;username&quot;), paramMap.get(&quot;age&quot;));
        return &quot;ok&quot;;
    }</code></pre>
<ul>
<li>요청받은 모든 parameter들을 map에 저장하고 get을 통해 가져옴.</li>
</ul>
<h3 id="☑-multivaluemap을-이용한-중복된-parameter명-처리">☑ MultiValueMap을 이용한 중복된 parameter명 처리</h3>
<pre><code class="language-java">    public String requestParamMap(@RequestParam MultiValueMap&lt;String, Object&gt; paramMap){
        //MultiValueMap={key=[value1, value2, ...], key=[value1, value2, ...]}
        log.info(&quot;MultiValueMap={}&quot;, paramMap);
        return &quot;ok&quot;;
    }</code></pre>
<ul>
<li>parameter name이 중복 될 경우 사용 할 수 있음.</li>
<li>MultiValueMap={key=[value1, value2, ...], key=[value1, value2, ...]} 와 같은 형태가 됨.</li>
</ul>
<hr>
<h2 id="✅-modelattribute를-이용한-만들어진-객체로-받기">✅ @ModelAttribute를 이용한 만들어진 객체로 받기</h2>
<h3 id="☑-예시를-위한-hellodata-객체">☑ 예시를 위한 HelloData 객체</h3>
<pre><code class="language-java">import lombok.Data;

@Data //@Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgConstructor 자동 적용
public class HelloData {
    private String username;
    private int age;
}</code></pre>
<ul>
<li>paramter를 전달받을 객체를 HelloData 객체 정의.</li>
</ul>
<h3 id="☑-가장-기본적인-형태-1">☑ 가장 기본적인 형태</h3>
<pre><code class="language-java">    public String modelAttributeV1(@ModelAttribute HelloData helloData) {
        log.info(&quot;helloData={}&quot;, helloData);

        return &quot;ok&quot;;
    }</code></pre>
<ul>
<li>parameter에 @ModelAttribute 애노테이션 넣어줌으로써 스프링이 자동으로 HelloData 객체를 생성하고 요청받은 Data를 주입함.</li>
</ul>
<h3 id="☑-modelattribute-애노테이션-생략-형태">☑ @ModelAttribute 애노테이션 생략 형태</h3>
<pre><code class="language-java">    public String modelAttributeV2(HelloData helloData) {
        log.info(&quot;helloData={}&quot;, helloData);

        return &quot;ok&quot;;
    }</code></pre>
<ul>
<li>애노테이션이 생략되어도 가능하다.</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 빈 타입이 중복 될 때 해결책]]></title>
            <link>https://velog.io/@won-developer/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88-%EC%9D%B4%EB%A6%84%EC%9D%B4-%EC%A4%91%EB%B3%B5-%EB%90%A0-%EB%95%8C-%ED%95%B4%EA%B2%B0%EC%B1%85</link>
            <guid>https://velog.io/@won-developer/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88-%EC%9D%B4%EB%A6%84%EC%9D%B4-%EC%A4%91%EB%B3%B5-%EB%90%A0-%EB%95%8C-%ED%95%B4%EA%B2%B0%EC%B1%85</guid>
            <pubDate>Sat, 02 Oct 2021 05:38:56 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/won-developer/post/11902aed-0030-4f01-a2de-9b1d972abfde/spring.PNG" alt=""></p>
<hr>
<blockquote>
<p>학습참조
<a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard">인프런-스프링 핵심 원리 기본편(김영한님)</a></p>
</blockquote>
<hr>
<h2 id="✅-중복되는-예시">✅ 중복되는 예시</h2>
<p>스프링의 빈 이름이 중복 되는 경우는 다음과 같다.</p>
<h4 id="✔-orderserviceimpl">✔ OrderServiceImpl</h4>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderService{

    private final DiscountPolicy discountPolicy;

    @Autowired //의존관계 자동 주입
    public OrderServiceImpl(DiscountPolicy discountPolicy) { //(1)
        this.discountPolicy = discountPolicy;
    }
}</code></pre>
<h4 id="✔-fixdiscountpolicy">✔ FixDiscountPolicy</h4>
<pre><code class="language-java">@Component
public class FixDiscountPolicy implements DiscountPolicy{ } //(2)</code></pre>
<h4 id="✔-ratediscountpolicy">✔ RateDiscountPolicy</h4>
<pre><code class="language-java">@Component
public class RateDiscountPolicy implements DiscountPolicy{ } //(2)</code></pre>
<p>위 소스코드의 상황은 다음과 같다.</p>
<ul>
<li>주문서비스를 구현하며 상황에 따라 적용되는 주문 정책이 달리 적용된다.</li>
<li>OrderService 인터페이스를 구현하는 OrderServiceImpl 구현체</li>
<li>DiscountPolicy 인터페이스를 구현하는 매번 Fix된 고정값을 할인해주는 FixDiscountPolicy 구현체</li>
<li>DiscountPolicy 인터페이스를 구현하는 상황에 따라 정해진 비율로 할인해주는 RateDiscountPolicy 구현체</li>
</ul>
<hr>
<h3 id="✔-문제점-발생">✔ 문제점 발생</h3>
<ul>
<li>(1) : @Autowired 자동 의존성 주입시 빈 조회 대상이 타입을 기준으로 조회를 하기 때문에 위 예제에서는 DiscountPolicy 타입을 대상으로 빈을 조회함.</li>
<li>(2) : 하지만 실제로 DiscountPolicy 타입을 구현하고 있는 빈은 FixDiscountPolicy, RateDiscountPolicy 2개가 되어 <strong>&#39;중복된 타입의 빈이 2개가 있다&#39;며 에러가 발생한다.</strong></li>
</ul>
<hr>
<h2 id="✅-문제점-해결을-위한-3가지-방안">✅ 문제점 해결을 위한 3가지 방안</h2>
<p>문제점 해결을 위한 3가지 방안은 다음과 같다.</p>
<ul>
<li>필드명 또는 Parameter명 변경으로 문제점 해결</li>
<li>@Qualifier Annotation</li>
<li>@Primary Annotation</li>
</ul>
<hr>
<h3 id="✔-필드명-또는-parameter명-변경으로-문제점-해결">✔ 필드명 또는 Parameter명 변경으로 문제점 해결</h3>
<p>@Autowired 자동 주입시 다음과 같은 순서로 이루어 진다.</p>
<ul>
<li><ol>
<li>동일한 데이터 타입을 가지는 빈을 찾아낸다.</li>
</ol>
</li>
<li><ol start="2">
<li><p>빈이 2개 이상일 경우 필드명 또는 Parameter 이름으로 빈 이름을 찾아낸다.</p>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderService{

private final DiscountPolicy discountPolicy;

@Autowired //의존관계 자동 주입
public OrderServiceImpl(DiscountPolicy rateDiscountPolicy) { //(1)
   this.discountPolicy = discountPolicy;
}
}</code></pre>
</li>
</ol>
</li>
<li><p>(1) : DiscountPolicy 타입을 가지는 빈이 fixDiscountPolicy, rateDiscountPolicy 2개이므로 Parameter 이름으로 빈을 찾는다. 즉 스프링 컨테이너에 rateDiscountPolicy라는 빈이 있으므로 rateDiscountPolicy 빈이 주입된다.</p>
</li>
</ul>
<hr>
<h3 id="✔-qualifier-annotation">✔ @Qualifier Annotation</h3>
<p>@Qualifier은 다음과 같은 순서로 이루어 진다.</p>
<ul>
<li><ol>
<li>주입시 @Qualifier가 있다면 @Qualifier로 생성된 같은 빈 별칭을 찾는다. </li>
</ol>
</li>
<li><ol start="2">
<li>만약 별칭으로 찾지 못할 경우 별칭이름으로 빈을 추가로 찾는다.</li>
</ol>
</li>
</ul>
<hr>
<h4 id="✔-ratediscountpolicy-1">✔ RateDiscountPolicy</h4>
<pre><code class="language-java">@Component
@Qualifier(&quot;mainDiscountPolicy&quot;) //(1)
public class RateDiscountPolicy implements DiscountPolicy{ }</code></pre>
<h4 id="✔-orderserviceimpl-1">✔ OrderServiceImpl</h4>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderService{

    private final DiscountPolicy discountPolicy;

    @Autowired //의존관계 자동 주입
    public OrderServiceImpl(@Qualifier(&quot;mainDiscountPolicy&quot;) DiscountPolicy rateDiscountPolicy) { //(2)
        this.discountPolicy = discountPolicy;
    }
}</code></pre>
<ul>
<li>(1) : rateDiscountPolicy 빈의 선언부에 @Qualifier Annoatation을 선언하고 옵션값으로 mainDiscountPolicy를 지정. 빈의 별칭을 지정한 것.</li>
<li>(2) : 의존 관계 주입시 @Qualifier mainDiscountPolicy가 등록된 빈을 찾는다. 만약 없을 경우 mainDiscountPolicy 이름을 가지는 빈을 찾아 주입한다.</li>
<li><strong>하지만.. 프로그램이 점점 복잡하고 분석이 어려워지는 코드는 좋은 설계가 아니다. 따라서 @Qualifier를 사용할 것이라면 정확하게 @Qualifier를 사용하여 찾는 용도로만 사용하는게 좋다.</strong></li>
<li><strong>@Autowired가 아닌 @Bean의 수동 등록 방법으로도 동일하게 사용 할 수 있다.</strong></li>
</ul>
<hr>
<h3 id="✔-primary-annotation">✔ @Primary Annotation</h3>
<h4 id="✔-ratediscountpolicy-2">✔ RateDiscountPolicy</h4>
<pre><code class="language-java">@Component
@Primary (2)
public class RateDiscountPolicy implements DiscountPolicy{ }</code></pre>
<h4 id="✔-orderserviceimpl-2">✔ OrderServiceImpl</h4>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderService{

    private final DiscountPolicy discountPolicy;

    @Autowired //의존관계 자동 주입
    public OrderServiceImpl(DiscountPolicy rateDiscountPolicy) { (1)
        this.discountPolicy = discountPolicy;
    }
}</code></pre>
<ul>
<li>(1) : 타입이 중복되는 빈이 있을 경우 </li>
<li>(2) : @Primary Annotation이 붙은 빈이 최우선이 되므로 중복되는 빈은 무시한다. 즉 fixDiscountPolicy 빈은 무시되고 rateDiscountPolicy빈이 주입된다.</li>
</ul>
<hr>
<h2 id="✅-primary와-qualifier-우선순위">✅ @Primary와 @Qualifier 우선순위</h2>
<p>@Primary 는 기본값 처럼 동작하는 것이고, @Qualifier 는 매우 상세하게 동작한다. 이런 경우 어떤 것이 우선권을 가져갈까? 스프링은 자동보다는 수동이, 넒은 범위의 선택권 보다는 좁은 범위의 선택권이 우선순위가 높다. 따라서 여기서도 @Qualifier 가 우선권이 높다.</p>
<h3 id="✔-활용">✔ 활용</h3>
<p>코드에서 자주 사용하는 메인 데이터베이스의 커넥션을 획득하는 스프링 빈이 있고, 코드에서 특별한 기능으로 가끔 사용하는 서브 데이터베이스의 커넥션을 획득하는 스프링 빈이 있다고 생각해보자. 메인 데이터베이스의 커넥션을 획득하는 스프링 빈은 @Primary 를 적용해서 조회하는 곳에서 @Qualifier 지정 없이 편리하게 조회하고, 서브 데이터베이스 커넥션 빈을 획득할 때는 @Qualifier 를 지정해서 명시적으로 획득 하는 방식으로 사용하면 코드를 깔끔하게 유지할 수 있다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링의 다양한 의존관계 주입 방법]]></title>
            <link>https://velog.io/@won-developer/%EC%8A%A4%ED%94%84%EB%A7%81%EC%9D%98-%EB%8B%A4%EC%96%91%ED%95%9C-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84-%EC%A3%BC%EC%9E%85-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@won-developer/%EC%8A%A4%ED%94%84%EB%A7%81%EC%9D%98-%EB%8B%A4%EC%96%91%ED%95%9C-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84-%EC%A3%BC%EC%9E%85-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Thu, 30 Sep 2021 12:46:59 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/won-developer/post/a4c00dfa-efe1-45e7-9542-e5a4789eb165/spring.PNG" alt=""></p>
<hr>
<blockquote>
<p>학습참조
<a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard">인프런-스프링 핵심 원리 기본편(김영한님)</a></p>
</blockquote>
<hr>
<p>의존관계 주입은 크게 4가지 방법이 있다.</p>
<ul>
<li>생성자 주입</li>
<li>수정자 주입(Setter 주입)</li>
<li>필드 주입</li>
<li>일반 메서드 주입</li>
</ul>
<hr>
<h2 id="✅-생성자-주입">✅ 생성자 주입</h2>
<pre><code class="language-java">@Component
public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;

    @Autowired // 생성자 의존관계 주입
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}</code></pre>
<ul>
<li>이름 그대로 생성자를 통해 의존 관계를 주입 받는 방식이다.</li>
<li>스프링이 MemberServiceImpl를 스프링 컨테이너에 등록하는 과정에서 생성자를 호출할 때 생성자에 @Autowired Annotation이 있다면 매개변수에 해당하는 타입의 빈을 스프링 컨테이너에서 꺼내와 자동으로 주입한다.</li>
<li>생성자를 호출하는 시점에 딱 1번만 호출되는 것이 보장된다.</li>
<li>불변, 필수적인 의존관계에 사용한다.</li>
<li><strong>※ 생성자가 딱 1개만 있으면 @Autowired를 생략해도 자동 주입된다.</strong> *물론 스프링 빈에만 해당함.</li>
</ul>
<hr>
<h3 id="✔-생성자-주입의-비밀">✔ 생성자 주입의 비밀(?)</h3>
<p>사실 생성자 주입과 다른 방식의 의존관계 주입과는 다른점이 있다. 생성자 주입은 컨테이너에 등록하는 과정과 의존관계 주입이 동시에 일어나지만 다른 방식의 의존관계 주입은 컨테이너에 등록하는 행위와 의존관계를 주입하는 행위가 나누어져 있다. </p>
<p>생성자 주입은 스프링이 Class를 스프링 컨테이너에 등록하면서 스프링이 아무리 마술사처럼 멋진 기능을 수행한다 하여도 어쨌든간에 스프링도 자바를 기반으로 하기에 <strong>자바 문법상 자동으로 생성자를 호출해야 함으로</strong> 이때 @Autowired Annotation이 있으면 Parameter에 해당하는 데이터 타입의 빈이 존재한다면 꺼내서 주입시키고 존재하지 않는다면 해당 데이터 타입의 Class를 찾아서 컨테이너에 등록후 의존관계를 주입한다. </p>
<hr>
<h2 id="✅-수정자-주입setter-주입">✅ 수정자 주입(setter 주입)</h2>
<pre><code class="language-java">@Component
public class MemberServiceImpl implements MemberService {

    private MemberRepository memberRepository;

    @Autowired
    public void setMemberRepository(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}
</code></pre>
<ul>
<li>setter를 이용한 의존관계를 주입하는 방식이다.</li>
<li>수정자 주입은 스프링이 MemberServiceImpl를 스프링 컨테이너에 등록한 후 @Autowired Annotation이 있다면 의존관계 주입을 수행한다. </li>
<li>선택, 변경 가능성이 있는 의존관계에 사용한다.</li>
<li><strong>※ 선택적으로 의존관계를 주입하고 싶다면(회원 등급에 따른 정책 변경 이라던지...), 만약 MemberRepository가 스프링 컨테이너에 등록이 안되어 있다면? @Autowired(required = false) 옵션을 이용하자. 그렇지 않다면 에러가 발생.</strong></li>
</ul>
<hr>
<h2 id="✅-필드-주입">✅ 필드 주입</h2>
<pre><code class="language-java">public class MemberServiceImpl implements MemberService {

    @Autowired private final MemberRepository memberRepository;
}</code></pre>
<ul>
<li>필드 선언 앞에 @Auotowired Annotation을 넣음으로써 필드에 바로 주입하는 방식이다.</li>
<li>코드가 간결해서 많은 개발자들을 유혹하지만, <strong>외부에서 변경이 불가능 하기에 테스트가 어려운 치명적인 단점이 있음.</strong></li>
<li>외부에서 의존관계를 주입해주는 DI 프레임워크가 없기 때문에 아무것도 할 수 없음.</li>
<li>가능하면 사용을 자제하되 다음과 같은 경우에는 사용해도 무방함.<ul>
<li>애플리케이션의 실제 코드와 관계 없는 테스트 코드</li>
<li>스프링 설정을 목적으로 하는 @Configuration Annotation 같은 경우에만 특별한 용도로 사용</li>
</ul>
</li>
</ul>
<hr>
<h2 id="✅-일반-메서드-주입">✅ 일반 메서드 주입</h2>
<pre><code class="language-java">@Component
public class MemberServiceImpl implements MemberService {

    private MemberRepository memberRepository;

    @Autowired
    public void injectionInit(MemberRepository memberRepository) {
            this.memberRepository = memberRepository;
    }
}</code></pre>
<ul>
<li>일반 메서드를 통해 주입하는 방식이다.</li>
<li>한번에 여러 필드를 주입 할 수 있다.</li>
<li>사실 수정자 주입(setter 주입)과 별 다를바 없이 동일하다고 봐도 무방하다.</li>
<li><strong>생성자 주입과 수정자 주입으로도 충분함으로 사용하지 않는다.</strong></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[좋은 객체 지향 설계를 위한 5가지 원칙 - SOLID]]></title>
            <link>https://velog.io/@won-developer/SOLID</link>
            <guid>https://velog.io/@won-developer/SOLID</guid>
            <pubDate>Sun, 19 Sep 2021 06:47:32 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/won-developer/post/79208b96-bc67-4501-92bd-dde6a02ecb4c/image.png" alt=""></p>
<hr>
<blockquote>
<p>학습참조
<a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard">인프런-스프링 핵심 원리 기본편(김영한님)</a></p>
</blockquote>
<hr>
<h2 id="✅-solid란">✅ SOLID란?</h2>
<p>SOLID란 좋은 객체지향 소프트웨어 설계를 위한 5가지 원칙이다. 5가지 원칙은 다음과 같다.</p>
<ul>
<li>SRP: 단일 책임 원칙(Single responsibility principle)</li>
<li>OCP: 개방-폐쇄 원칙(Open/closed principle)</li>
<li>LSP: 리스코프 치환 원칙(Liskov substitution principle)</li>
<li>ISP: 인터페이스 분리 원칙(Interface segregation principle)</li>
<li>DIP: 의존관계 역전 원칙(Dependency inversion principle)</li>
</ul>
<hr>
<h2 id="✅-srp-단일-책임-원칙single-responsibility-principle">✅ SRP 단일 책임 원칙(Single responsibility principle)</h2>
<blockquote>
<p>단일 책임 원칙(single responsibility principle)이란 모든 클래스는 하나의 책임만 가지며, 클래스는 그 책임을 완전히 캡슐화해야 함을 일컫는다. 클래스가 제공하는 모든 기능은 이 책임과 주의 깊게 부합해야 한다. <a href="https://ko.wikipedia.org/wiki/%EB%8B%A8%EC%9D%BC_%EC%B1%85%EC%9E%84_%EC%9B%90%EC%B9%99">[위키백과]</a></p>
</blockquote>
<ul>
<li>한 클래스는 하나의 책임만 가져야 한다.</li>
<li>하나의 책임이라는 기준은 모호함.<ul>
<li>클 수 있고, 작을 수 있다.</li>
<li>문맥과 상황에 따라 다르다.</li>
</ul>
</li>
<li>원칙을 잘 지켰느냐의 중요한 기준은 변경임. 변경이 있을 때 파급 효과가 적으면 단일 책임의 원칙을 잘 따른 것임.</li>
<li>예시로 UI 변경시 View단 외에도 여러 애플리케이션 코드들에 영향을 끼친다면 단일 책임의 원칙을 잘 지키지 못한 것임.</li>
<li>설계시 기능과 모듈 단위로 계층을 나누는 것(예를 들어 MVC패턴) 역시 이러한 책임 원칙을 지키기 위한 것임.</li>
</ul>
<hr>
<h2 id="✅-ocp-개방-폐쇄-원칙openclosed-principle">✅ OCP 개방-폐쇄 원칙(Open/closed principle)</h2>
<blockquote>
<p>개방-폐쇄 원칙(OCP, Open-Closed Principle)은 &#39;소프트웨어 개체(클래스, 모듈, 함수 등등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다&#39;는 프로그래밍 원칙이다. <a href="https://ko.wikipedia.org/wiki/%EA%B0%9C%EB%B0%A9-%ED%8F%90%EC%87%84_%EC%9B%90%EC%B9%99">[위키백과]</a></p>
</blockquote>
<ul>
<li><p>가장 중요시 되는 원칙.</p>
</li>
<li><p>소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.</p>
<ul>
<li>어떻게 코드의 변경없이 확장이 가능한가?</li>
<li>다형성을 활용하면 가능 </li>
</ul>
<blockquote>
<ul>
<li>참조: <a href="https://velog.io/@won-developer/JAVA-%EB%8B%A4%ED%98%95%EC%84%B1%EC%9D%B4%EB%9E%80">[JAVA] 다형성이란?</a><ul>
<li>Taxi의 차량이 확장되어도 확장된 차량 소스코드만 새로이 생기는 것이지 TaxiDriver 입장에서 기존 코드에는 변경이 없다.</li>
<li>하지만 실질적으로 Main 함수에 인스턴스를 초기화하는 부분에서 다른 차량의 인스턴스로 교체 해주어야 함으로 변경이 일어남. OCP 원칙을 위반한 것.</li>
<li>분명 다형성을 사용했지만 원칙을 재대로 지킬 수 없음.</li>
<li>이를 해결 하기 위해선 객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요함.</li>
<li>그 역할을 스프링과 같은 프레임워크에서 가능하게끔 만들어 줌. (Dependency Injection과 같은..)</li>
</ul>
</li>
</ul>
</blockquote>
</li>
</ul>
<hr>
<h2 id="✅-lsp-리스코프-치환-원칙liskov-substitution-principle">✅ LSP 리스코프 치환 원칙(Liskov substitution principle)</h2>
<blockquote>
<p> 컴퓨터 프로그램에서 자료형 S가 자료형 T의 하위형이라면 필요한 프로그램의 속성(정확성, 수행하는 업무 등)의 변경 없이 자료형 T의 객체를 자료형 S의 객체로 교체(치환)할 수 있어야 한다는 원칙이다. <a href="https://ko.wikipedia.org/wiki/%EB%A6%AC%EC%8A%A4%EC%BD%94%ED%94%84_%EC%B9%98%ED%99%98_%EC%9B%90%EC%B9%99">[위키백과]</a></p>
</blockquote>
<ul>
<li>프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.</li>
<li>다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것, 다형성을 지원하기 위한 원칙, 인터페이스를 구현한 구현체는 믿고 사용하려면 이 원칙이 필요하다.</li>
<li>단순히 컴파일에 성공하는 것을 넘어선다는 이야기</li>
<li>자동차 인터페이스의 엑셀은 앞으로 가라는 기능, 뒤로 가게 구현하면 LSP를 위반한다.</li>
<li>즉 인터페이스 설계에서 부터 인터페이스를 구현하고자 하는 구현체는 인터페이스에서 요구하는 규약을 잘 지켜야 한다.</li>
</ul>
<hr>
<h2 id="✅-isp-인터페이스-분리-원칙interface-segregation-principle">✅ ISP 인터페이스 분리 원칙(Interface segregation principle)</h2>
<blockquote>
<p>인터페이스 분리 원칙은 클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다는 원칙이다. 인터페이스 분리 원칙은 큰 덩어리의 인터페이스들을 구체적이고 작은 단위들로 분리시킴으로써 클라이언트들이 꼭 필요한 메서드들만 이용할 수 있게 한다. 이와 같은 작은 단위들을 역할 인터페이스라고도 부른다. 인터페이스 분리 원칙을 통해 시스템의 내부 의존성을 약화시켜 리팩토링, 수정, 재배포를 쉽게 할 수 있다. <a href="https://ko.wikipedia.org/wiki/%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4_%EB%B6%84%EB%A6%AC_%EC%9B%90%EC%B9%99">[위키백과]</a></p>
</blockquote>
<ul>
<li>특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.</li>
<li>자동차 인터페이스 -&gt; 운전 인터페이스, 정비 인터페이스로 분리.</li>
<li>사용자 클라이언트 -&gt; 운전자 클라이언트, 정비사 클라이언트로 분리.</li>
<li>분리하면 정비 인터페이스 자체가 변해도 운전자 클라이언트에 영향을 주지 않음.</li>
<li>인터페이스가 명확해지고, 대체 가능성이 높아진다.</li>
<li>즉 만약 어떠한 기능을 구현하기 위한 인터페이스가 존재한다면 가능한 그 안에서도 기능 별로 분리가 가능 한 만큼 분리 하는것이 좋다는 얘기임.</li>
</ul>
<hr>
<h2 id="✅-dip-의존관계-역전-원칙dependency-inversion-principle">✅ DIP 의존관계 역전 원칙(Dependency inversion principle)</h2>
<blockquote>
<p>의존관계 역전 원칙은 소프트웨어 모듈들을 분리하는 특정 형식을 지칭한다. 이 원칙을 따르면, 상위 계층(정책 결정)이 하위 계층(세부 사항)에 의존하는 전통적인 의존관계를 반전(역전)시킴으로써 상위 계층이 하위 계층의 구현으로부터 독립되게 할 수 있다. 이 원칙은 다음과 같은 내용을 담고 있다.
첫째, 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.
둘째, 추상화는 세부 사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다.
이 원칙은 &#39;상위와 하위 객체 모두가 동일한 추상화에 의존해야 한다&#39;는 객체 지향적 설계의 대원칙을 제공한다. <a href="https://ko.wikipedia.org/wiki/%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84_%EC%97%AD%EC%A0%84_%EC%9B%90%EC%B9%99">[위키백과]</a></p>
</blockquote>
<ul>
<li>프로그래머는 &quot;추상화에 의존해야지, 구체화에 의존하면 안된다.&quot; 의존성 주입은 이 원칙을 따르는 방법 중 하나임.</li>
<li>쉽게 이야기해서 클라이언트는 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻.</li>
<li>다형성과 깊은 관계가 있는데 클라이언트는 인터페이스에만 의존하면 되지, 구현체에는 의존 할 필요가 없다는 것.<blockquote>
<ul>
<li>참조: <a href="https://velog.io/@won-developer/JAVA-%EB%8B%A4%ED%98%95%EC%84%B1%EC%9D%B4%EB%9E%80">[JAVA] 다형성이란?</a><ul>
<li>TaxiDriver(클라이언트)는 Taxi라는 인터페이스에만 의존하면 되지, 그 Taxi가 어떤 차인지, 아반떼인지 소나타인지 그랜저인지의 구현체에 대해서는 중요하지 않음. </li>
<li>TaxiDriver는 아반떼만 운전을 해보았기 때문에 아반떼만을 Taxi로 사용 할 수 있어서는 안된다는 것임.</li>
<li>TaxiDriver는 만약 아반떼가 고장 났을 경우 언제든지 다른 차량으로 교체하여 운전 할 수 있어야 함.</li>
<li>키보드와 같은 입력장치도 마찬가지, 키보드가 고장 났을 경우 언제든지 다른 키보드로 교체할 수 있어야 함.</li>
<li>하지만 실질적으로 Main 함수에 인스턴스를 초기화하는 부분에서 다른 차량의 인스턴스로 교체 해주어야 함으로 이는 Taxi 인터페이스와 Avente, Sonata, Grandeur의 구현체에 모두 의존하고 있는 것임.(추상화에도 의존하고 구체화에도 의존) 즉 DIP를 위반함.</li>
<li>분명 다형성을 사용했지만 원칙을 재대로 지킬 수 없음.</li>
<li>이를 해결 하기 위해선 객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요함.</li>
<li>그 역할을 스프링과 같은 프레임워크에서 가능하게끔 만들어 줌. (Dependency Injection과 같은..)</li>
</ul>
</li>
</ul>
</blockquote>
</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[다형성이란?]]></title>
            <link>https://velog.io/@won-developer/JAVA-%EB%8B%A4%ED%98%95%EC%84%B1%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@won-developer/JAVA-%EB%8B%A4%ED%98%95%EC%84%B1%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Sun, 19 Sep 2021 04:52:44 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/won-developer/post/c43d3e6f-03d5-41dc-8ce1-355a8c881413/1_feg3Ii110jswahXwg18Q-g.jpeg" alt=""></p>
<hr>
<blockquote>
<p>학습참조
<a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard">인프런-스프링 핵심 원리 기본편(김영한님)</a></p>
</blockquote>
<hr>
<h2 id="✅-다형성이란">✅ 다형성이란?</h2>
<p>다형성이란 여러개의 형태를 가질 수 있다는 의미이다.</p>
<blockquote>
<p>프로그램 언어의 다형성(多形性, polymorphism; 폴리모피즘)은 그 프로그래밍 언어의 자료형 체계의 성질을 나타내는 것으로, <strong>프로그램 언어의 각 요소들(상수, 변수, 식, 오브젝트, 함수, 메소드 등)이 다양한 자료형(type)에 속하는 것이 허가되는 성질을 가리킨다.</strong> 반댓말은 단형성(monomorphism)으로, 프로그램 언어의 각 요소가 한가지 형태만 가지는 성질을 가리킨다. <a href="https://ko.wikipedia.org/wiki/%EB%8B%A4%ED%98%95%EC%84%B1_(%EC%BB%B4%ED%93%A8%ED%84%B0_%EA%B3%BC%ED%95%99)">[위키백과]</a></p>
</blockquote>
<p>주목해야 할 부분은 프로그램 언어의 각 요소들이 다양한 자료형에 속하는 것이 허가 되는 성질을 가르킨다는 것이다. 프로그래밍에서 다형성이란 <strong>하나의 부모타입 참조변수가 여러 자식 타입의 인스턴스를 가질 수 있는것</strong>을 말하며 객체지향 프로그래밍에서 가장 중요시 되는 핵심이 바로 다형성이다.</p>
<hr>
<h2 id="✅-다형성의-특징">✅ 다형성의 특징</h2>
<ul>
<li>하나의 부모타입이 여러 자식타입을 가질 수 있다.</li>
<li>유연하고 변경에 용이하다.</li>
<li>확장성이 뛰어나다.</li>
</ul>
<hr>
<h2 id="✅-실세계에-비유한-다형성taxi">✅ 실세계에 비유한 다형성(Taxi)</h2>
<ul>
<li>역할과 구현으로 구분한다.</li>
<li>역할은 인터페이스이며 구현은 클래스이다.</li>
<li>역할은 Taxi가 되며, 구현은 아반떼, 소나타, 그랜저가 된다.</li>
<li>Taxi Driver는 Taxi의 차가 아반떼이던, 소나타, 그랜저 차량의 종류와 상관없이 업무를 수행 할 수 있다.</li>
<li>Taxi Driver는 대상의 역할(Taxi 인터페이스)만 알면 됨.</li>
<li>Taxi Driver는 구현 대상(Avante, Sonata, Grandeur 클래스)의 내부 구조를 알 필요가 없음.</li>
<li>Taxi Driver는 구현 대상의 내부 구조가 변경되어도 영향을 받지 않음.</li>
<li>Taxi Driver는 구현 대상 자체를 변경하여도 영항을 받지 않음.</li>
</ul>
<p><img src="https://images.velog.io/images/won-developer/post/df62aae5-b8d7-40f4-8b7e-e767e87fe1ff/image.png" alt=""></p>
<hr>
<h3 id="✔-소스코드로-비교">✔ 소스코드로 비교</h3>
<ul>
<li><p>Main</p>
<pre><code class="language-java">public class Main {
  public static void main(String[] args) {
      TaxiDriver taxiDriver = new TaxiDriver(new Avante());
      taxiDriver.run(); // --&gt; output: 100km까지 걸리는 시간은 8초
      taxiDriver.stop(); // --&gt; output: 100km에서 0km까지 걸리는 시간은 5초
      taxiDriver.guestDropOff(); // --&gt; output: 손님 하차
  }
}</code></pre>
</li>
<li><p>Taxi 인터페이스</p>
<pre><code class="language-java">public interface Taxi {
  public void stepOnAccelerator();
  public void stepOnBrake();
}</code></pre>
</li>
<li><p>아반떼 구현 클래스</p>
<pre><code class="language-java">public class Avante implements Taxi {
  @Override
  public void stepOnAccelerator() {
      System.out.println(&quot;100km까지 걸리는 시간은 8초&quot;);
  }

  @Override
  public void stepOnBrake() {
      System.out.println(&quot;100km에서 0km까지 걸리는 시간은 5초&quot;);
  }
}</code></pre>
</li>
<li><p>소나타 구현 클래스</p>
<pre><code class="language-java">public class Sonata implements Taxi {
  @Override
  public void stepOnAccelerator() {
      System.out.println(&quot;100km까지 걸리는 시간은 6초&quot;);
  }

  @Override
  public void stepOnBrake() {
      System.out.println(&quot;100km에서 0km까지 걸리는 시간은 4초&quot;);
  }
}
</code></pre>
</li>
</ul>
<pre><code>- 그랜저 구현 클래스
```java
public class Grandeur implements Taxi {
    @Override
    public void stepOnAccelerator() {
        System.out.println(&quot;100km까지 걸리는 시간은 5초&quot;);
    }

    @Override
    public void stepOnBrake() {
        System.out.println(&quot;100km에서 0km까지 걸리는 시간은 3초&quot;);
    }
}</code></pre><ul>
<li><p>Taxi Driver 클래스</p>
<pre><code class="language-java">public class TaxiDriver {
  private Taxi taxiCar;

  public TaxiDriver(Taxi taxiCar) {
      this.taxiCar = taxiCar;
  }

  // 달리다
  public void run() {
      taxiCar.stepOnAccelerator();
  }

  // 멈추다
  public void stop() {
      taxiCar.stepOnBrake();
  }

  public void guestDropOff() {
      System.out.println(&quot;손님 하차&quot;);
  }
}</code></pre>
<p>택시기사는 택시의 차 종류가 무엇이던 간에 운전을 할 수 있고 택시 업무를 수행 할 수 있다. 즉 택시가 무엇으로 바뀌던지 간에 유연하고 변경에 용이하게 대응 할 수 있다. 왜냐하면 택시기사는 기본적으로 아반떼나, 소나타, 그랜저의 구현체에 의존성을 가지는것이 아닌 Taxi 인터페이스라는 역할에 의존성을 가지기 때문이다. </p>
</li>
</ul>
<p>만약 차량이 고장날 경우에 대비하여 택시가 아반떼가 아닌 소나타나 그랜저로 바뀌어야 한다면 택시기사는 아반떼가 아닌 소나타나 그랜저를 운전하여 택시 업무를 수행하면 그만이다. 이 역시 소스코드에서도 쉽게 대응 가능하다. Main 함수에서 아래와 같은 수정만 이루어 지면 된다.</p>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) {
        //TaxiDriver taxiDriver = new TaxiDriver(new Avante());
        //TaxiDriver taxiDriver = new TaxiDriver(new Sonata());
        TaxiDriver taxiDriver = new TaxiDriver(new Grandeur());
        taxiDriver.run();
        taxiDriver.stop();
        taxiDriver.guestDropOff();
    }
}</code></pre>
<p>TaxiDriver의 생성자 함수로 인스턴스만 Avante의 구현체가 아닌 Sonata나 Grandeur을 넣어주면 된다. 이렇게 하면 TaxiDriver의 생성자에서 </p>
<pre><code class="language-java">public class TaxiDriver {
    private Taxi taxiCar;

    public TaxiDriver(Taxi taxiCar) {
        this.taxiCar = taxiCar;
    }</code></pre>
<p>다음과 같이 Taxi 인터페이스를 Parameter로 전달 받아 taxiCar 멤버 변수에 할당한다. 앞서 Avante와 Sonata, Grandeur 구현 클래스는 Taxi 인터페이스를 구현(상속)하고 있기 때문에 이와 같은 행위가 가능하다. 아까 위에서 설명했듯 다형성이란 하나의 부모타입 참조변수가 여러 자식 타입의 인스턴스를 가질 수 있다. 즉 Avante와 Sonata, Grandeur 구현 클래스의 부모타입이 Taxi 인터페이스가 되는 것이다. </p>
<p>위에서 택시 차량이 아반떼에서 소나타로 변경되고, 그랜저로 변경 될 때 이러한 다형성의 특성을 이용하여 택시 차량이 바뀌어도 다른것은 전<del>~</del>혀 손댈 필요 없고 TaxiDriver 객체의 Taxi만 변경 해주면 되니 이 얼마나 편리하고 유연하고 변경에 용이한가? 심지어 Taxi 인터페이스를 상속받아 벤츠, BMW, 아우디, 테슬라등 어떠한 자동차던지 만들어 낼 수 있다. 이 얼마나 확장성이 좋은가? </p>
<hr>
<h3 id="✔-또-다른-실세계와-비유한-다형성의-예시">✔ 또 다른 실세계와 비유한 다형성의 예시</h3>
<ul>
<li>키보드와 마우스, 모니터, 프린터등 주변기기들</li>
<li>운동기구(운동기구가 뭐든 간에 운동을 하는것은 변함 없음)</li>
<li>정렬 알고리즘(퀵,선택,삽입등 정렬 알고리즘은 다양하지만 정렬을 한다는것은 변함 없음)</li>
<li>기타 등등...</li>
</ul>
<hr>
<h2 id="✅-정리하며">✅ 정리하며</h2>
<p>구현(상속) 받는 부모타입의 참조변수를 통해 자식의 인스턴스를 가짐으로써 유연하고 변경에 용이한 프로그래밍을 구현 할 수 있다. 택시기사 입장에서는 택시 영업을 할 수 있는 면허와 자동차만 있으면 차가 무엇이든 간에 언제든지 택시 업무를 수행 할 수 있는 것이다. 차가 바뀌던 말던간에 자동차는 엑셀을 밟으면 출발하고 브레이크를 밟으면 멈추는 기능은 모두가 동일하기 때문에 택시기사가 운전을 할 수 있다는 사실은 변함이 없기에 전혀 상관이 없다. 이것이 바로 다형성의 강력함이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[정리해보는 면접 준비]]></title>
            <link>https://velog.io/@won-developer/%EC%A0%95%EB%A6%AC%ED%95%B4%EB%B3%B4%EB%8A%94-%EA%B8%B0%EC%88%A0%EB%A9%B4%EC%A0%91-%EC%A4%80%EB%B9%84</link>
            <guid>https://velog.io/@won-developer/%EC%A0%95%EB%A6%AC%ED%95%B4%EB%B3%B4%EB%8A%94-%EA%B8%B0%EC%88%A0%EB%A9%B4%EC%A0%91-%EC%A4%80%EB%B9%84</guid>
            <pubDate>Sat, 14 Aug 2021 05:34:09 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/won-developer/post/7ca71b61-5384-4ca4-9c4c-919ce8df3d84/image.jpg" alt=""></p>
<hr>
<p>최근 면접준비로 혼자 끄적여보는.. 복습하며 끄적이는거니 그냥 막 적어본다.</p>
<h3 id="👉-객체지향">👉 객체지향</h3>
<p>객체지향 프로그래밍이란 <strong>프로그래밍에서 필요한 데이터를 추상화(Class)시키고 그것을 대상으로 상태(멤버변수), 행위(메서드)를 가지는 객체를 만들어 그 객체들 간에 유기적인 상호작용을 통해 프로그램을 구성하는 프로그래밍 방법</strong>이다. 
어떠한 문제를 해결하는 프로그램을 작성한다고 가정했을때 그 해당 문제를 추상화하고 여러개의 작업단위로 나누어 프로그램을 작성한다. 여기서 해당 문제를 추상화하는것은 Class가 되고 여러개의 작업단위로 나누는것을 객체(인스턴스)라고 한다. 
<strong>쉽게 설명해 Class는 설계도, 객체는 설계도를 통해 만들어진 실체라고 보자.</strong> </p>
<p>제<del>~</del>일 많이 쓰이는 예시를 봐볼까?</p>
<p>나는 강아지, 고양이, 햄스터를 키우는 동물원을 만들고 싶다. 그럴려면 동물을 만들어야 겠지? 이것을 객체지향이 아닌 절차지향으로 본다면.. </p>
<pre><code class="language-java">// 강아지
String dogName = &quot;멍멍이&quot;; // 강아지 이름
int dogAge = 12; // 강아지 나이
String dogSay = &quot;멍멍&quot;; // 강아지는 짖을때 멍멍한다.

// 고양이
String catName = &quot;야옹이&quot;; // 동물 이름
int catAge = 5; // 고양이 나이
String catSay = &quot;멍멍&quot;; // 강아지는 짖을때 야옹한다.

// 햄스터
String hamsterName = &quot;햄딩이&quot;; // 동물 이름
int hamsterAge = 7; // 햄스터 나이
String hamster = &quot;꺅꺅&quot;; // 햄스터는 짖을때 끼웃끼웃한다.</code></pre>
<p>뭔가 불편하고 지저분해 보이는데? 심지어 강아지가 2마리이면 어떡하지?</p>
<pre><code class="language-java">// 강아지
String dogName1 = &quot;멍멍이&quot;; // 강아지 이름
int dogAge1 = 12; // 강아지 나이
String dogSay1 = &quot;멍멍&quot;; // 강아지는 짖을때 멍멍한다.

String dogName2 = &quot;멍멍이2호&quot;; // 강아지 이름
int dogAge2 = 13; // 강아지 나이
String dogSay2 = &quot;멍멍&quot;; // 강아지는 짖을때 멍멍한다.</code></pre>
<p>이렇게 해줘야하나..? 겁나 불편하네... 머리를 좀써서 객체지향으로 짜보자.
<img src="https://images.velog.io/images/won-developer/post/b0a74f5c-a61b-4b0a-a38e-7841d8c5f4f5/image.png" alt="">
추상화란 간단하게 중요한 특징을 찾아내어 표현한것을 말한다. 동물을 추상화 해보자. 동물은 나이도 있을거고.. 이름도 있을거야. 또 짖을수도 있을거야. 동물마다 짖는 소리는 다르겠지? 이걸 이용해서 동물 Class를 만들어보자.</p>
<pre><code class="language-java">class Animal {
    private int age;
    private String name;
    public Animal(int age, String name) {
        this.age = age;
        this.name = name;
    }
    public void say() {
        System.out.print(&quot;저는&quot; + age + &quot;살이고 &quot; + &quot;이름은 &quot; + name + &quot;에요. &quot;);
    };
}</code></pre>
<p>Animal 클래스는 나이, 이름을 멤버변수로, 생성자함수에서 해당 변수에 값을 저장한다. 또한 나이와 이름을 말하는 say()메서드도 만들었다.</p>
<p>그럼 이제 동물 Class를 만들었으니.. 얘내를 가지고 강아지,고양이,햄스터 클래스를 만들어보자.</p>
<pre><code class="language-java">class Dog extends Animal {
    public Dog(int age, String name) {
        super(age, name);
    }
    public void say() {
        super.say();
        System.out.println(&quot;멍멍&quot;);
    }
} 

class Cat extends Animal {
    public Cat(int age, String name) {
        super(age, name);
    }
    public void say() {
        super.say();
        System.out.println(&quot;야옹&quot;);
    }
} 

class Hamster extends Animal {
    public Hamster(int age, String name) {
        super(age, name);
    }
    public void say() {
        super.say();
        System.out.println(&quot;꺅꺅&quot;);
    }
} </code></pre>
<p>Dog 클래스를 봐보자. 생성자 함수를 통해 super키워드로 부모클래스의 생성자를 호출하여 age와 name을 저장했다(age와 name은 private 제어자로 직접 접근할수 없다). 또한 상속받은 메서드 say를 오버라이딩하여 재정의했다. super 키워드를 통해 부모의 say 메서드를 호출하고 나서 강아지는 &quot;멍멍&quot;하고 짖는다. </p>
<p>이제 동물 Class를 상속받아 자식 Class Dog, Cat, Hamster를 만들었다. 위에서 설명했는데 Class는 설계도, 객체는 실체라고 하였다. Dog, Cat, Hamster 설계도를 만들었으니.. 이제 실체를 만들어볼 차례이다.</p>
<pre><code class="language-java">public static void main(String[] args) {
    Dog dog = new Dog(12,&quot;멍멍이&quot;);
    dog.say(); //--&gt; output: 저는12살이고 이름은 멍멍이에요. 멍멍
}</code></pre>
<p>메인함수에서 dog 객체를 만들었다. 나이는 12살, 이름은 멍멍이라고 했다. dog.say() 메서드를 이용해서 강아지한테 짖으라고 명령을 내리면 멍멍하고 짖을수 있다.</p>
<p>다른 동물들도 마저 만들어보자.</p>
<pre><code class="language-java">Cat cat = new Cat(5,&quot;야옹이&quot;);
cat.say(); // --&gt; output : 저는5살이고 이름은 야옹이에요. 야옹

Hamster hamster = new Hamster(7,&quot;햄띵이&quot;);
hamster.say(); // --&gt; output : 저는7살이고 이름은 햄띵이에요. 꺅꺅</code></pre>
<p>다른 동물들도 똑같은 결과가 나오는것을 알수 있다!</p>
<p>그럼 이제 강아지를 3마리로 늘려보자.</p>
<pre><code class="language-java">Dog[] dog = new Dog[3];
dog[0] = new Dog(12,&quot;멍멍이&quot;);
dog[1] = new Dog(13,&quot;멍멍이2호&quot;);
dog[2] = new Dog(14,&quot;멍멍이3호&quot;);

for(Dog dogs : dog) {
    dogs.say();
}
// --&gt; output:
/* 저는12살이고 이름은 멍멍이에요. 멍멍
저는13살이고 이름은 멍멍이2호에요. 멍멍
저는14살이고 이름은 멍멍이3호에요. 멍멍 */</code></pre>
<p>겁나 기깔나고 간단하고 멋지다. 객체지향을 이용하지 않았다면 아마 아래와 같은 코드일것이다.</p>
<pre><code class="language-java">String dogName1 = &quot;멍멍이&quot;; // 강아지 이름
int dogAge1 = 12; // 강아지 나이
String dogSay1 = &quot;멍멍&quot;; // 강아지는 짖을때 멍멍한다.

String dogName2 = &quot;멍멍이2호&quot;; // 강아지 이름
int dogAge2 = 13; // 강아지 나이
String dogSay2 = &quot;멍멍&quot;; // 강아지는 짖을때 멍멍한다.

String dogName3 = &quot;멍멍이3호&quot;; // 강아지 이름
int dogAge3 = 13; // 강아지 나이
String dogSay3 = &quot;멍멍&quot;; // 강아지는 짖을때 멍멍한다.

System.out.println(&quot;저는&quot; + dogAge1 + &quot;살이고 &quot; + &quot;이름은 &quot; + dogName1 + &quot;에요. &quot; + dogSay1);
System.out.println(&quot;저는&quot; + dogAge2 + &quot;살이고 &quot; + &quot;이름은 &quot; + dogName2 + &quot;에요. &quot; + dogSay2);
System.out.println(&quot;저는&quot; + dogAge3 + &quot;살이고 &quot; + &quot;이름은 &quot; + dogName3 + &quot;에요. &quot; + dogSay3);</code></pre>
<p>멋지지가 않고 가장 중요한건.. 코드가 중복되고 효율성이 떨어지는것 같다.</p>
<p>이것처럼 프로그램의 모든 동작단위를 클래스와 객체로 나누어 프로그램을 구성하여 코드의 재사용성을 높이는 방식을 객체지향 프로그래밍이라고 한다.</p>
<p>✔ 클래스가 뭔가요?</p>
<ul>
<li>연관되어 있는 변수와 메서드들의 집합</li>
<li>구현할 대상을 추상화 해놓은 것</li>
<li>객체를 만들어 내기위한 설계도</li>
</ul>
<p>✔ 객체란 뭔가요?</p>
<ul>
<li>추상화된 구현할 대상으로부터 실체로 구현된 것</li>
<li>클래스로부터 만들어진 실체</li>
</ul>
<p>✔ 객체지향 프로그래밍의 장점은 뭔가요?</p>
<ul>
<li>코드의 재사용성</li>
<li>유지보수 용이</li>
<li>대형 프로젝트에 적합</li>
</ul>
<hr>
<h3 id="👉-멀티스레드">👉 멀티스레드</h3>
<p>기본적으로 프로그램은 단일스레드를 제공한다. 예를 들어 운전을 할때 운전자가 운전말고 다른 행동을 할수있는가? 아니다. 운전자는 운전만 해야지.. 다른 행동을 했다간 큰 사고로 이어지겠지. 프로그램도 똑같다. 시작과 종료까지 하나의 일밖에 하지 못한다. 하지만 멀티스레드를 사용하게 되면 여러가지의 일을 동시에 수행할 수 있다. 예로 TCP/IP로 구현한 채팅프로그램이 있다고 가정하자. 기본적으로 TCP/IP는 하나의 소켓으로 통신하는 일대일 통신방식이다. 때문에 서버는 하나이지만 클라이언트, 즉 접속자가 여러명일 경우 다중 응답을 동시에 해주어야 할 때 멀티스레드를 사용하여 채팅프로그램을 구현할 수 있다.</p>
<hr>
<h3 id="👉-제네릭">👉 제네릭</h3>
<p><strong>제네릭이란 객체, 인스턴스가 생성될 때 클래스 내부에서 사용할 데이터 타입을 파라미터로 전달하여 결정하는것을 말한다.</strong> 제네릭을 선언하는 방법은 다음과 같다.</p>
<pre><code class="language-java">public class GnrTest&lt;T&gt; {
    T num;
    public GnrTest(T num) {
        this.num = num;
    }
    public T getNum() {
        return num;
    }
}</code></pre>
<pre><code class="language-java">public static void main(String[] args) {

    GnrTest&lt;Integer&gt; gt1 = new GnrTest&lt;Integer&gt;(3);
    System.out.println(gt1.getNum());

    GnrTest&lt;Double&gt; gt2 = new GnrTest&lt;Double&gt;(3.5);
    System.out.println(gt2.getNum());
}</code></pre>
<p>클래스 명 옆에 &lt;&gt; 기호로 안에 별명을 만들어준다. 보통 T라는 것으로 약속하여 사용한다. 객체 선언부에서 결정할 타입을 &lt;&gt;안에 전달한다. 여기서 주의할 점은 제네릭의 파라미터로는 참조 타입만 올수 있다. 즉 primitive type인 int, double char와 같은 테입을 불가하다. 대신 Wrapper 타입인 Integer, Double와 같은 타입으로 대체한다.</p>
<p>위 예제와 같이 비슷한 기능을 구현하는 하나의 클래스로 여러개의 데이터타입을 이용함으로써 재사용성이 증가한다.</p>
<pre><code class="language-java">// ArrayList str = new ArrayList(); 좋지않은 방법이다.
ArrayList&lt;String&gt; str = new ArrayList&lt;String&gt;();
str.add(&quot;hello&quot;);
System.out.println(str.get(0)); // --&gt; output: hello

ArrayList&lt;Double&gt; num = new ArrayList&lt;Double&gt;();
num.add(3.5);
System.out.println(num.get(0)); // --&gt; output: 3.5</code></pre>
<p>예로 Java의 Collection중에 하나인 ArrayList는 객체 선언시 타입을 제네릭으로 선언하지 않으면 데이터 타입에 상관없이 모든 자료형을 저장 할수 있다. 어떻게 보면 정말 편하구나라고 생각할 수 있지만 이것은 좋지않은 선택이다. 만약 제네릭을 선언않고 ArrayList를 사용할 경우 해당 Index에 값을 저장하고 불러올때 마다 형변환이 일어나게 된다. int형 값을 저장할 경우 int 타입에서 Object 타입으로 변환하여 저장되고, 불러 올 경우 다시 Object 타입을 int 타입으로 변환하게 된다. 이는 성능 저하를 불러올 뿐만 아니라 데이터 타입에 대한 검증을 매우 엄격하게 하는 Java에서 어울리지 않는다. 때문에 ArrayList 선언시 제네릭으로 타입을 명시해주어 사용하는것이 성능 향상에 도움을 줄것이다.</p>
<hr>
<h3 id="👉-다형성">👉 다형성</h3>
<p>다형성이란 사전적으로 <strong>다양한 형태의 성질을 가진다</strong>라는 의미이며,  **부모타입의 참조변수로 자식 타입의 객체를 가질수 있는것이다. 반대로 생각해본다면? 하나의 객체가 여러개의 데이터타입을 가질수 있게 된다.</p>
<pre><code class="language-java">class Fruit {}

class Apple extends Fruit {}

class Banana extends Fruit {}

public class MainClass {
    // 매개변수의 Fruit은 부모 타입임으로 자식타입(Apple,Banana)을 가질수도 있다.
    public static void whatKindFruits(Fruit fruit) {
        if(fruit instanceof Apple) { 
            System.out.println(&quot;Apple&quot;);
        } else if (fruit instanceof Banana) {
            System.out.println(&quot;Banana&quot;);
        }
        // instanceof는 특정 객체가 특정클래스의 인스턴스인지를 묻는 키워드이다.
    }

    public static void main(String[] args) {
        Apple apple = new Apple(); // 자기 자신을 참조변수으로 Apple 객체
            Banana banana = new Banana(); // 자기 자신을 참조변수으로 Banana 객체
        Fruit parentTypeApple = new Apple(); // 부모 타입을 참조변수로 가지는 Apple 인스턴스
        Fruit parentTypeBanana = new Banana(); // 부모 타입을 참조변수로 가지는 Banana 인스턴스

        whatKindFruits(parentTypeApple); // --&gt; output: Apple
        whatKindFruits(parentTypeBanana); // --&gt; output: Banana
    }
}</code></pre>
<p>위 코드를 보면 Fruit 클래스를 상속받는 자식클래스 Apple과 Banana가 있는데 Main 함수에서 Fruit 부모클래스의 데이터 타입을 참조변수로 하여 자식타입의 객체를 생성한다. Apple, Banana 클래스는 자기 자신을 데이터타입으로 사용할수도 있고 부모인 Fruit를 데이터타입으로 사용할 수도 있다. 그냥 이해가 어려울경우.. 가장 이해가 쉽게 아래 코드를 보자.</p>
<pre><code class="language-java">    public static void main(String[] args) {
        Object obj1 = 1;
        Object obj2 = &quot;String&quot;;
        Object obj3 = 23.5;

        System.out.println(obj1 instanceof Integer); // --&gt; output: true
        System.out.println(obj2 instanceof String); // --&gt; output: true
        System.out.println(obj3 instanceof Double); // --&gt; output: true
    }</code></pre>
<p>자바의 최상위 클래스는 Object이다. 자바의 모든 객체는 Object 타입을 부모로 가질수 있다. 아래 출력문에서 instanceof 키워드를 이용해 obj1,obj2,obj3이 특정 클래스의 인스턴스인지 비교했다. 모두가 true로 출력된다. 이렇게 보니 간.단.하.네?ㅡ.ㅡ</p>
<hr>
<h3 id="👉-추상클래스와-인터페이스-차이">👉 추상클래스와 인터페이스 차이</h3>
<p>추상클래스와 인터페이스는 선언만 있고 구현이 없는 클래스이다. 해서 자기 자신이 스스로 객체를 생성할 수 없고 자식클래스에서 추상클래스와 인터페이스를 상속받아야만 사용이 가능하다. 
추상클래스는 일반 멤버변수와 메서드를 포함할 수 있지만 인터페이스는 모든 변수는 상수이고 모든 메서드는 반드시 추상메서드여야 한다. 추상클래스는 단일상속만 가능하며 인터페이스는 다중구현이 가능하다. 공통점은 추상클래스와 인터페이스를 상속받는 자식클래스는 추상메서드를 모두 오버라이딩하여 정의하여야 한다는점이다. </p>
<h4 id="-추상클래스의-용도">* 추상클래스의 용도</h4>
<p><strong>추상클래스는 상속관계에 있는 클래스중 반드시 구현해야 하는 메서드의 형식을 미리 구현해놓은 것이다.</strong> 즉 추상클래스는 상속관계에 의미를 둔다. 일반적인 상속관계에서 자식클래스들 마다 달라져야 할 속성이 있을때 추상클래스로 선언하여 자식클래스에서 추상메서드를 정의하는 것이다. 즉 동물에 비교 했을때 개, 고양이가 있다고 가정하고 서로 동물이라는점은 일치하지만 짖을때 개는 &#39;멍멍&#39;, 고양이는 &#39;야옹&#39;, 까마귀는 &#39;꺅꺅&#39;이라는 다른점을 생각하면 될것이다.</p>
<h4 id="-인터페이스의-용도">* 인터페이스의 용도</h4>
<p>인터페이스의 목적은 위 처럼 동물에 비교 했을때 상속관계처럼 공통된 부분(모든 동물은 짖는다)을 <strong>상속받는것이 아닌 필요에따라 기능들을 결합하는것이다.</strong> 예를 들어 개와 고양이는 달릴수있고 까마귀는 하늘을 날수 있다. 그렇다면 개와 고양이는 달리는 인터페이스를 구현하고 까마귀는 하늘을 나는 인터페이스를 구현할수 있을것이다.</p>
<p><strong>추상클래스는 상속받고자 하는 자식클래스에서 상속관계에 해당되는 메서드를 강제로 구현하게끔 하고 인터페이스는 상속관계가 아닌 클래스가 요구하는 특성에 따라 메서드의 정의를 강제하게끔 한다. 즉 추상클래스는 물려받는것, 인터페이스는 장착하는것으로 이해하자.</strong></p>
<p>위의 얘기한 동물을 예시로 코드를 만들어보자. </p>
<p><img src="https://images.velog.io/images/won-developer/post/877de898-6826-4749-b138-c40413beae26/image.png" alt=""></p>
<h5 id="추상클래스">추상클래스</h5>
<pre><code class="language-java">abstract class Animal {
    abstract void say();
}</code></pre>
<p>동물은 모두 짖는다는 요소를 가지고 있다.</p>
<h5 id="인터페이스">인터페이스</h5>
<pre><code class="language-java">interface Runnable {
    void run();
}

interface Flyable {
    void fly();
}</code></pre>
<p>개와 고양이는 달릴수 있고 까마귀는 하늘을 날수있다.</p>
<h5 id="자식-클래스">자식 클래스</h5>
<pre><code class="language-java">class Dog extends Animal implements Runnable {
    @Override
    void say() {
        System.out.println(&quot;개는 멍멍하고 짖어요&quot;);
    }
    @Override
    public void run() {
        System.out.println(&quot;나는 10초에 50미터를 달려요&quot;);
    }
}

class Cat extends Animal implements Runnable {
    @Override
    void say() {
        System.out.println(&quot;고양이는 야옹하고 짖어요&quot;);
    }
    @Override
    public void run() {
        System.out.println(&quot;나는 10초에 70미터를 달려요&quot;);
    }
}

class Crow extends Animal implements Flyable {
    @Override
    void say() {
        System.out.println(&quot;까마귀는 깍깍하고 짖어요&quot;);
    }
    @Override
    public void fly() {
        System.out.println(&quot;나는 10초에 50미터를 날아요&quot;);
    }
}</code></pre>
<p>추상 클래스를 상속받아 Dog와 Cat, Crow의 자식클래스를 생성했고 각 자식클래스들은 say 추상메서드를 구현하고 있다. 또한 Dog와 Cat은 Runable이라는 인터페이스를 구현하고 있고, Crow는 Flyable이라는 인터페이스를 구현하고 있다.</p>
<hr>
<h3 id="👉-메모리-영역의-구조">👉 메모리 영역의 구조</h3>
<p><img src="https://images.velog.io/images/won-developer/post/df2e22d0-46e0-4e0c-bb50-3bc94cdc7e5e/image.png" alt=""></p>
<hr>
<h3 id="👉-스택영역과-힙영역">👉 스택영역과 힙영역</h3>
<p>C, C++이 아닌 Java와 C#과 같은 객체지향언어는 메모리를 가비지컬렉터가 스스로 관리 해주는데 그렇기 때문에 이 스택영역과 힙영역에 대한 부분을 다루는것이 중요치않게 여겨지는 경우가 꽤 있는것 같다. 스택영역과 힙영역에 대해 정리해보자. 
기본적으로 Java는 Primitive type인 int, char, double과 같은 기본 자료형외에 모든 자료형은 객체이며 참조형이다. 기본 자료형은 Stack 영역에 저장되고, 참조형은 Heap영역에 저장된다라는것을 알고가자.</p>
<p><img src="https://images.velog.io/images/won-developer/post/ad6d2314-03a3-4cb8-b108-573e64560cdf/image.png" alt=""></p>
<h4 id="stack">Stack</h4>
<ul>
<li>기본적으로 Stack영역은 매개변수와 지역변수, 그리고 참조타입의 위치(주소)를 가르킬 변수로 할당된다. </li>
<li>또한 Stack영역은 스레드마다 각자의 영역을 제공한다.</li>
</ul>
<h4 id="heap">Heap</h4>
<ul>
<li>Heap영역은 동적할당의 영역, 참조타입(객체)이 저장된다.</li>
<li>모든 객체는 사용하기 전에 new 키워드를 사용해 인스턴스를 메모리 동적할당(Heap 영역)을 해주어야한다. </li>
</ul>
<p>다음 코드를 보자.</p>
<pre><code class="language-java">    public static void addAryList(ArrayList&lt;String&gt; arrlist) {
        int value2 = 3;
        arylist.add(&quot; world&quot;);
    }

    public static void main(String[] args) {
        ArrayList&lt;String&gt; str = new ArrayList&lt;String&gt;();

        int value1 = 1; 
        str.add(&quot;hello&quot;);
        for(String _str : str) {
            System.out.println(_str); 
        } --&gt; output: hello

        addAryList(str);

        for(String _str : str) {
            System.out.print(_str);
        } --&gt; output: hello world
    }</code></pre>
<p>동작 과정은 다음과 같다.
<img src="https://images.velog.io/images/won-developer/post/835ce87f-18a3-4deb-bbaa-e988faf7f7c0/image.png" alt=""></p>
<ol>
<li>먼저 처음으로 Stack 영역에 str이라는 참조타입의 위치(주소)를 가르킬 변수가 할당된다.</li>
<li>Heap영역에는 참조 객체인 ArrayList가 할당된다. </li>
<li>Stack영역에 str는 Heap영역의 ArrayList를 가르킨다.</li>
</ol>
<hr>
<p><img src="https://images.velog.io/images/won-developer/post/ac2fbd5f-e825-45cb-a54c-d7dd0d72f3fa/image.png" alt="">
4. 이제 Main함수에 value1 변수가 stack영역에 할당된다. int형이므로 값 그 자체가 영역에 할당된다.</p>
<hr>
<p><img src="https://images.velog.io/images/won-developer/post/f079c7cf-b344-4104-8d70-36972966080c/image.png" alt="">
5. Stack 영역의 str에 &quot;hello&quot;라는 문자열을 add한다. 내부적으로 str이 가르키는 Heap영역의 ArrayList에 &quot;hello&quot;라는 문자열이 추가된다.</p>
<hr>
<p><img src="https://images.velog.io/images/won-developer/post/c2b4ccf4-2e24-4cb4-8019-4918bf11c19b/image.png" alt="">
6. for loop를 돌며 ArrayList str을 출력하여 hello이 출력되었다.
7. addAryList 함수를 호출함으로써 기존 Stack영역에 있던 str과 value1은 scope에서 벗어나 사용할 수 없다.
8. addAryList 함수의 매개변수 arrlist가 Stack영역에 할당되고 Main함수의 str을 인자로 전달받았음으로 Heap영역에 ArrayList를 가르킨다.
9. addAryList 함수의 value2 변수가 stack영역에 할당된다. int형이므로 값 그자체가 영역에 할당된다.</p>
<hr>
<p><img src="https://images.velog.io/images/won-developer/post/a22ff19f-f9e8-4d40-8d96-86a2151d3179/image.png" alt="">
10. Stack 영역의 arrlist에 &quot; world&quot;라는 문자열을 add한다. 내부적으로 arrlist이 가르키는 Heap영역의 ArryList에 &quot; world&quot;라는 문자열이 추가된다.</p>
<hr>
<p><img src="https://images.velog.io/images/won-developer/post/0dda66e7-de09-4938-9d4a-f02c1bcc2315/image.png" alt="">
11. addAryList의 함수가 종료되었음으로 이전에 존재하던 Stack 영역의 arrlist와 value2는 pop되어 삭제된다. 
12. 다시 Main함수의 scope로 돌아왔기 때문에 str와 value1이 사용 가능상태로 돌아왔다. 
13. for loop를 돌며 ArrsyList str을 출력해보니 hello world가 출력된다. 앞서 addAryList 함수에서 arrlist변수로 Main함수 스코프의 str과 같은 Heap영역의 ArrayList를 참조했기 때문이다.</p>
<hr>
<h3 id="👉-http">👉 HTTP</h3>
<p>HTTP란 HyperText transfer Protocol의 약자로 클라이언트와 서버간에 상호작용을 위한 통신 프로토콜이다. 프로토콜은 규약이며 약속이다. 상호작용 한다는것은 클라이언트와 서버간에 통신을 한다는 것을 의미한다. 때문에 웹에서 운영되는 모든 프로그램은 이 HTTP 규약을 따라야한다. 클라이언트가 서버로 정보를 요청하는것을 Request라고 하며 서버가 클라이언트로부터 요청받은 정보를 응답하는것을 Response라고 한다.
<img src="https://images.velog.io/images/won-developer/post/ae025a68-7294-4913-9428-444a36f9ddd7/image.png" alt=""></p>
<hr>
<h3 id="👉-resi-api">👉 RESI API</h3>
<p>REST API란 클라이언트에서 서버로 정보를 요청하는데에 있어서 널리 쓰이는 형식이다. 이 형식을 지키게 되면 클라이언트에서 서버로 요청하는 정보를 URL만 보고도 추론이 가능하다. 다음과 같은 4가지 요청을 서버로 보낼때를 예로 들어보자.</p>
<ul>
<li>학원반 정보를 요청</li>
<li>학원 학생들의 정보를 요청</li>
<li>학원 선생님의 정보를 요청</li>
</ul>
<p>위와 같은 조건들이 존재할 때 RESTful하지 못한 경우는 다음과 같다.</p>
<ul>
<li>http://학원/1 : 학원 반 정보를 요청</li>
<li>http://학원/hello : 학원 학생들의 정보를 요청</li>
<li>http://학원/bye : 학원 선생님들의 정보를 요청</li>
</ul>
<p>URL만 봐서는 도저히 이게 무엇을 요청하는거지? 이 URL로 요청했을때 어떠한 응답이 서버로부터 올까? 감이 잡히질 않는다.
물론 이렇게 애플리케이션을 만들어도 문제될것은 없다. 프로그램에 문제가 없으니. 단 자신이 혼자 개발하고 혼자 유지보수하며 다른 앱으로 API 정보를 제공하지 않을거라면 말이다.</p>
<p>그렇다면 REST API의 규칙을 잘 갖춘 요청을 보자.</p>
<ul>
<li>http://학원/class/3 : 학원 반 정보를 요청</li>
<li>http://학원/student : 학원 학생들의 정보를 요청</li>
<li>http://학원/teacher : 학원 선생님들의 정보를 요청</li>
</ul>
<p>딱봐도 이 URL이 무엇을 요청하는지 알수있다. 첫번째는 class의 3반을 요청하는것이고 두번째는 student 학생의 정보, 세번째는 teacher 선생들의 정보를 요청하는것이다. </p>
<p>개발자들 사이에서는 이러한 규칙들을 잘 지켜진것을 보고 RESTful하다고 한다.
물론 이런 URL의 자원을 예측 가능하도록 만드는것도 중요하지만 서버로에게 보내는 요청에 따라 HTTP 메서드 역시 잘 맞춰 사용해야 할것이다. HTTP 메서드의 종류는 다양하지만 대표적으로 가장 많이 쓰이는 종류는 다음과 같다.</p>
<ul>
<li>GET: URL에서 가진 정보를 조회하기 위한 요청 주로 페이지를 읽어오고 검색 요청을 수행</li>
<li>POST: 새로운 정보를 추가 하기 위한 요청</li>
<li>PUT &amp; PATCH : 정보를 수정하기 위한 요청, 정보 전체를 변경 할때는 PUT 메서드를 사용하고 일부를 변경 할때는 PATCH 메서드를 사용한다.</li>
<li>DELETE : 정보를 삭제하는 요청</li>
</ul>
<p>최종적으로 정리하자면 REST API란? *<em>HTTP 요청을 보낼때 어떠한 URL 자원과 어떠한 메소드 요청을 사용할지 개발자들의 사이에서 널리 지켜지는 약속이다. *</em></p>
<hr>
<h3 id="👉-델리게이트">👉 델리게이트</h3>
<h4 id="기본적인-개념과-선언방식">기본적인 개념과 선언방식</h4>
<p>델리게이트는 C#에서 사용되는 개념이다. 데이터 반환형과 파라메터를 포함한 델리게이트 자료형을 선언하고 델리게이트에 등록하려는 메서드 역시 델리게이트 자료형과 동일한 형태를 가지게끔 선언한다.</p>
<pre><code class="language-java">    class Program
    {
        delegate void MyDeleGate(); // 델리게이트 자료형 선언 반환형은 void이며 parameter는 없다.

        static void printTest() // 델리게이트에 등록할 메서드 선언. 반환형은 void이며 parameter는 없다.
        {
            Console.WriteLine(&quot;Hello World&quot;);
        }
        static void Main(string[] args)
        {
            MyDeleGate del = new MyDeleGate(printTest); // 델리게이트에 printTest 메서드를 등록한다.
            del(); // --&gt; output: Hello World
        }</code></pre>
<p>간단하게 설명하자면 델리게이트는 메서드를 대신해서 담는 변수이다. 의미적으로는 대리인이라고도 한다. 위 코드에서는 반환형이 없고 parameter가 없는 델리게이트를 MyDeleGate라는 이름으로 생성했다. printTest함수 역시 델리게이트 자료형과 동일한 형태로 선언했다. Main 함수에서 MyDeleGate 자료형으로 del이라는 델리게이트(대리자) 변수를 만들었으며 del변수에 printTest 메서드를 등록했다. del함수를 호출하면 printTest를 호출한것과 같은 출력결과를 확인할 수 있다.</p>
<h4 id="델리게이트의-사용예시">델리게이트의 사용예시</h4>
<p>또한 델리게이트는 메서드의 매개변수로도 사용할 수 있다(메서드를 담는 변수이기 때문). 이러한 것을 콜백함수라고 하는데 델리게이트와 메서드의 Prototype이 일치하면 모든 메서드를 등록할 수 있다는것을 이용해 코드의 재사용성을 높일 수 있다. </p>
<p>다음은 내림차순과, 오름차순을 하는 예시이다.</p>
<pre><code class="language-java">class Program
    {
        delegate Boolean SortCompare(int num1, int num2); // 델리게이트 자료형 선언

        static void sortFunc(int[] nums, SortCompare sort)  // 정렬 기능 처리 메서드
        {
            for (int i= 0; i &lt; nums.Length; i++) 
            { 
                for(int j=i+1; j&lt;nums.Length; j++)
                {
                    if (sort(nums[i],nums[j]) == true)
                    {
                        int temp = nums[j]; // 순서 바꾸기
                        nums[j] = nums[i];
                        nums[i] = temp;
                    }
                }
            }

            foreach(int i in nums) Console.Write(i + &quot; &quot;); // 정렬된 배열 출력
        }

        static Boolean downSortCompare(int num1, int num2) // 내림차순 비교
        {
            return (num1 - num2) &gt; 0 ? false : true;
        }

        static Boolean upSortCompare(int num1, int num2) // 오름차순 비교
        {
            return (num1 - num2) &gt; 0 ? true : false;
        }

        static void Main(string[] args)
        {
            int[] nums = new int[5] { 3,2,5,1,4 }; // 정렬되지 않은 배열

            SortCompare sortCompare = new SortCompare(upSortCompare); // 델리게이트에 오름차순 비교 메서드 등록
            sortFunc(nums, sortCompare); // 정렬 기능 처리 메서드에 정렬되지 않은 배열과 델리게이트 메서드를 인자로 호출

            Console.WriteLine();

            sortCompare = new SortCompare(downSortCompare); // 델리게이트에 내림차순 비교 메서드 등록
            sortFunc(nums, sortCompare); // 정렬 기능 처리 메서드에 정렬되지 않은 배열과 델리게이트 메서드를 인자로 호출
        }
    }</code></pre>
<p> sortFunc은 정렬 기능을 처리하는 메서드로, 오름차순으로 정렬할 것인지, 내림차순으로 정렬할 것인지는 메서드를 호출하는 시점에서 결정된다. 즉 메서드 호출시 내림차순이냐 오름차순이냐에 따라 다른 메서드를 델리게이트에 등록하여 호출한다.</p>
<hr>
<h3 id="👉-spasingle-page-application">👉 SPA(Single Page Application)</h3>
<p>SPA란 Single Page Application의 약자로 서버로 부터 완전한 페이지를 불러오는것이 아닌 현재의 페이지를 동적으로 다시 작성하는 기법이다. 즉 View부분을 담당하는 HTML, CSS, JavaScript는 모두 애플리케이션에 위치하고 서버는 클라이언트가 요청하는 정보만 응답함으로써 애플리케이션 측에서 응답받은 정보만 동적으로 교체하는 방식이다. 대표적인 프레임워크로는 React, Vue, Anguler가 있다.</p>
<hr>
<h3 id="👉-가비지컬렉션">👉 가비지컬렉션</h3>
<p>가비지컬렉션이란 프로그램이 런타임중 동적할당한 메모리영역에 대해 사용하지 않는 메모리영역을 찾아 자원을 해제 시킨다.
C,C++에서는 이러한 메모리의 할당부터 해제까지 개발자가 해주어야 하지만 Java와 C#에서는 가비지컬렉션이 스스로 처리한다.</p>
<hr>
<h3 id="👉-index">👉 INDEX</h3>
<p>인덱스란 추가적인 저장 공간을 활용하여 데이터베이스 테이블의 검색 속도를 향상시키기 위한 자료구조이다.</p>
<hr>
<h3 id="👉-dml-dcl-ddl-tcl">👉 DML DCL DDL TCL</h3>
<h4 id="dmldata-manipulation-language">DML(Data Manipulation Language)</h4>
<ul>
<li>SELECT: 데이터 검색</li>
<li>INSERT: 데이터 삽입</li>
<li>UPDATE: 데이터 수정</li>
<li>DELETE: 데이터 삭제<h4 id="ddldata-definition-language">DDL(Data Definition Language)</h4>
</li>
<li>CREATE: 테이블 생성</li>
<li>ALTER: 테이블 수정</li>
<li>DROP: 테이블 삭제</li>
<li>TRUNCATE: 테이블 초기화<h4 id="dcldata-control-language">DCL(Data Control Language)</h4>
</li>
<li>GRANT: 권한 부여</li>
<li>REVOKE: 권한 회수<h4 id="tcltransaction-control-language">TCL(Transaction Control Language)</h4>
</li>
<li>COMMIT: 트랜잭션의 성공</li>
<li>ROLLBACK: 트랜잭션 실패로 이전 작업으로 되돌림</li>
<li>SAVEPOINT: 트랜잭션 실패시 이전 작업으로 되돌리기 위한 Point 지점</li>
</ul>
<hr>
<h3 id="👉-join">👉 JOIN</h3>
<ul>
<li>INNER JOIN: 공통적인 부분만 SELECT(교집합)
<img src="https://images.velog.io/images/won-developer/post/d1a9f197-f722-435b-b6c0-d639b77240b9/image.png" alt=""></li>
<li>LEFT OUTER JOIN: A테이블이 기준이된, A테이블의 정보는 모두 출력하되 B테이블은 조건이 만족하는 정보만 출력
<img src="https://images.velog.io/images/won-developer/post/b32c678a-939c-4926-a89b-ab9d489a8399/image.png" alt=""></li>
<li>RIGHT OUTER JOIN: B테이블이 기준이된, B테이블의 정보는 모두 출력하되 A테이블은 조건이 만족하는 정보만 출력
<img src="https://images.velog.io/images/won-developer/post/ffbb6c67-c228-47c4-a2aa-f5dbadea238a/image.png" alt=""></li>
<li>FULL OUTER JOIN : A테이블, B테이블 모두 출력(합집합)
<img src="https://images.velog.io/images/won-developer/post/4bebf05e-9c65-481e-a95d-0fcf9713f496/image.png" alt=""></li>
</ul>
<hr>
<h3 id="👉-링크드리스트-스택-트리-큐-정렬-힙-해쉬테이블">👉 링크드리스트, 스택, 트리, 큐, 정렬, 힙, 해쉬테이블</h3>
<ul>
<li>링크드리스트: 각각의 데이터가 노드로 연결되어 구성된 자료구조. 배열과 유사하나 삽입,삭제에서 유리하다. 배열은 삽입,삭제시 전,후로 있는 인덱스들을 모두 밀거나 당겨줘야 하지만 링크드리스트는 삽입,삭제시 앞,뒤로 가르키는 노드의 위치만 변경해주면 된다.</li>
<li>스택: 후입선출 구조로 나중에 들어온것이 가장 먼저 처리된다.</li>
<li>큐: 선입선출 구조로 가장 먼저 들어온것이 가장 먼저 처리된다.</li>
<li>트리: 그래프의 한 종류로 계층구조의 노드로 이루어진 자료구조이며 트리의 형태를 가지게끔 탐색한다.</li>
<li>정렬: </li>
<li>힙: </li>
<li>해쉬테이블: Key와 Value를 가지며 Key의 중복을 허용하지 않고 순서를 보장하지 않는다. hash함수라는 함수로 부터 내부적인 로직을 거쳐 Key값이 index로 변경되며 이 index를 통하여 Value가 저장된다. hash함수와 Key만 있으면 index를 바로 알아낼 수 있으므로 탐색속도가 매우 빠르다.</li>
</ul>
<hr>
<h3 id="👉-c의-컬렉션제네릭">👉 C#의 컬렉션(제네릭)</h3>
<p>C#의 컬렉션으로 ArrayList, Queue, Stack, HashTable등이 있지만 이 자료형은 Object 타입을 사용하여 데이터를 관리하기 때문에 박싱/언박싱의 이슈가 있어 현재는 사용하지 않는 추세이고 제네릭 타입으로 사용할 수 있는 컬렉션을 사용한다.</p>
<ul>
<li>List&lt;&gt;: 배열의 크기를 정적으로 변경가능한 자료형, 순서를 보장한다.</li>
<li>Dictionary&lt;,&gt;: Key과 Value를 가지는 자료형, Key는 유일해야 한다.</li>
<li>HashSet&lt;&gt;: 자료의 순서를 보장하지 않으며 중복을 허용하지 않는다. Hash 알고리즘을 사용함으로 탐색속도가 매우 빠르다.</li>
<li>Queue&lt;&gt;: 선입선출(FIFO) 구조의 자료형</li>
<li>Stack&lt;&gt;: 후입선출(LIFO) 구조의 자료형</li>
</ul>
<h3 id="👉-java의-컬렉션">👉 Java의 컬렉션</h3>
<h4 id="set-interface">Set Interface</h4>
<p>요소의 저장 순서를 유지하지 않고 같은 요소의 중복 저장을 허용하지 않음.</p>
<ul>
<li>HashSet: Set 인터페이스에서 가장 많이 사용됨. Hash 알고리즘을 사용하여 탐색속도가 매우 빠름. 내부적으로 HashMap <pre><code>     인스턴스를 이용하여 요소를 저장함.</code></pre></li>
<li>TreeSet: 데이터가 정렬된 상태로 저장되는 이진 검색 트리의 형태로 요소를 저장함. 데이터를 추가하거나 제거하는 등의              동작 시간이 매우 빠름.</li>
</ul>
<h4 id="list-interface">List Interface</h4>
<p>배열과 유사한 구조이며 크기를 정적으로 변경가능함.</p>
<ul>
<li>LinkedList: 다음노드의 주소를 기억하고 있는 List로 삽입, 삭제시에 장점이 있지만 인덱스를 찾을때 첫번째 노드부터 차례로 탐색해야 함으로 탐색이 느림.</li>
<li>ArrayList: 배열과 동일한 구조를 가지고 있으며 인덱스를 기준으로 하기 때문에 삽입, 삭제시 LinkedList에 비해 불리하지만 인덱스를 기반으로 검색하므로 탐색이 빠르다. 동기화 기능 옵션이 존재한다.</li>
<li>Vector: ArrayList와 동일하지만 Vector는 동기화된 메서드로 구성되어 있어 멀티 스레드가 동시에 이 메소드를 실행할 수 없음. 현재는 사용하지 않는 추세이며 ArrayList를 주로 사용함.</li>
<li>Queue: 선입선출(FIFO) 구조의 자료형</li>
<li>Stack: 후입선출(LIFO) 구조의 자료형</li>
</ul>
<h4 id="map-interface">Map Interface</h4>
<p>Key와 Value를 가지는 자료형임. 요소의 저장순서를 유지하지 않고 키는 중복을 허용하지 않지만 값의 중복은 허용함.</p>
<ul>
<li>HashTable: 사용하지 않는 추세이며 HashMap과 대부분의 기능이 동일함. HashMap을 사용하자.</li>
<li>HashMap: HashMap 인터페이스에서 가장 많이 사용되는 클래스중 하나. Hash 알고리즘을 사용하여 탐색속도가 매우 빠름.</li>
<li>TreeMap: 데이터가 정렬된 상태로 저장되는 이진 검색 트리의 형태로 요소를 저장함. 데이터를 추가하거나 제거하는 등의              동작 시간이 매우 빠름.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[API 란?]]></title>
            <link>https://velog.io/@won-developer/API-%EB%9E%80</link>
            <guid>https://velog.io/@won-developer/API-%EB%9E%80</guid>
            <pubDate>Fri, 13 Mar 2020 06:27:48 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/won-developer/post/31793528-f6f0-405c-a8f5-2f81e723a3e8/api-defined-as-application-program-interface.jpeg" alt=""></p>
<hr>
<p>API란 도대체 무엇인가? API에 대한 나의 생각을 문서화 해보려한다. 
API에 대해 알아 보기 전에 우선 UI에 대해 알아보자!</p>
<hr>
<h2 id="✅-uiuser-interface">✅ UI(User Interface)</h2>
<blockquote>
<p>사용자 인터페이스 또는 유저 인터페이스는 사람과 사물 또는 시스템, 특히 기계, 컴퓨터 프로그램 등 사이에서 의사소통을 할 수 있도록 일시적 또는 영구적인 접근을 목적으로 만들어진 물리적, 가상적 매개체를 뜻한다. 사용자 인터페이스는 사람들이 컴퓨터와 상호 작용하는 시스템이다. <a href="https://ko.wikipedia.org/wiki/%EC%82%AC%EC%9A%A9%EC%9E%90_%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4">[위키백과]</a></p>
</blockquote>
<p>UI는 User Interface의 약자이다. 여기서 Interface란 무엇일까? 서로 다른 두 물체간의 접점, 상호작용을 말한다. 즉 UI란 사용자와 시스템간의 인터페이스를 말한다. 예를 들어 우리가 핸드폰을 사용할때 홈 버튼을 눌러 잠금을 풀고 화면을 터치하여 작동을 한다. 여기서 홈 버튼도 UI가 되고 화면 자체도 UI가 될 수 있다. 우리가 컴퓨터를 사용할 때 무엇을 가지고 컴퓨터와 상호작용 하는가? 키보드가 될 수도 있고, 마우스가 될 수도 있다. 이것 역시 UI이다.</p>
<hr>
<h2 id="✅-apiapplication-programming-interface">✅ API(Application Programming Interface)</h2>
<blockquote>
<p>API(Application Programming Interface 애플리케이션 프로그래밍 인터페이스, 응용 프로그램 프로그래밍 인터페이스)는 응용 프로그램에서 사용할 수 있도록, 운영 체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만든 인터페이스를 뜻한다. <a href="https://ko.wikipedia.org/wiki/API">[위키백과]</a></p>
</blockquote>
<p>응용 프로그램에서 사용할 수 있도록, 운영체제나 프로그래밍 언어가 제공하는 기능을 제어 할 수 있게 만든 인터페이스를 뜻한다.. 말이 어렵다. 쉽게 풀어보자. API란 Application Programming Interface의 약자이다. 위에서 Interface란 서로 다른 두물체간의 접점, 상호작용을 말한다고 했다. 그렇다면 Application Programming은 뭘까? 응용프로그램을 뜻한다. 즉 API란 내가 만들 응용프로그램에서 사용 할 수 있도록 기타 다른 응용프로그램들을 제어 할 수 있게 하는 것을 말한다.</p>
<hr>
<h3 id="✔-api의-용도">✔ API의 용도</h3>
<p>그렇다면 우리는 왜 API를 사용하는가? API를 사용하면 우리가 제어 하고자 하는 시스템의 환경을 정확히 알지 못하더라도 손 쉽게 제어가 가능하다. UI가 사용자와 시스템의 인터페이스 였다면 API는 시스템과 시스템간의 인터페이스라고 할 수 있다. 여기서 시스템이란 간단한 프로그램이 될 수도 있고 웹브라우저가 될 수도, 운영체제가 될 수도, 하드웨어가 될 수도 있다.</p>
<hr>
<h2 id="✅-사용-예시">✅ 사용 예시</h2>
<h3 id="✔-브라우저를-제어하는-api">✔ 브라우저를 제어하는 API</h3>
<p>우리가 자바스크립트를 통하여 브라우저를 제어 할때 가장 간단한 예로 alert이 있다. alert은 웹 브라우저에 경고창을 발생시키는 역할을 한다. 이 역시 API이다. 자바스크립트를 통해 브라우저를 제어 할 수 있게 한다. 여기서 한가지 의문이 들지 않나? 그렇다면 어떻게 alert라는 간단한 것 하나 만으로 브라우저에서 경고창을 발생 시킬 수 있을까? 우리가 이 alert 을 이용하여 브라우저에 경고창을 발생 시켰을때 이 브라우저에서 경고창을 어떻게 생성했는지 구성환경은 어떤지 아는가? 경고창의 크기는 얼마인지, 글씨체는 사이즈는 어떻게 되는지, 구성 버튼은 어떻게 만들었는지.. 아마 모를것이다. 또한 브라우저 종류마다 구성 환경이 다 다를 것이다. </p>
<p>이제 위에서 말한 문장을 한번 더 보자!</p>
<p>&quot;API를 사용하면 우리가 제어 하고자 하는 시스템의 환경을 정확히 알지 못하더라도 손 쉽게 제어가 가능하다.&quot; 우리는 우리가 제어 하고자하는 경고창의 구성환경을 정확히 알지 못하더라도 손쉽게 제어가 가능하다. 즉 이 역시 시스템(JS)과 시스템(Browser)간 의 인터페이스 인 것이다. </p>
<hr>
<h3 id="✔-수에-대한-연산-처리-api">✔ 수에 대한 연산 처리 API</h3>
<p>또 이번엔 자바스크립트에서만 생각 해보자. 예를 들어 자바스크립트에서 랜덤한 수를 구하려한다. 이를 위해서는 Math객체를 이용해야 하는데이 역시 API다. 이해가 되는가? JS와 Math객체 간의 인터페이스가 이루어 지는 것이다. (물론 Math 그자체는 JS.) 즉 Math객체를 이용하여 수에 대한 연산을 제어 할수 있게끔 하는 것이다. 이 와 같은 경우를 JavaScript API라고 할 수 있겠다.</p>
<hr>
<h3 id="✔-google-font-api">✔ Google Font API</h3>
<p>더 쉽게 우리가 일반적으로 말하는 API에 대해 생각 해보자. 대표적인 API로 Google Font API가 있다. Google Font API는 구글에서 제공해주는 폰트들을 우리의 HTML 문서에서 제어 할 수 있게끔 해준다. 위에서 말한 내용이 기억이 나는가? &quot;즉 API란 내가 만들 응용프로그램에서 사용 할 수 있도록 기타 다른 응용프로그램들을 제어 할 수 있게 하는 것을 말한다.&quot;</p>
<p>위 말을 바꿔보자. &quot;Google Font API란 내가 만들 응용프로그램에서 사용 할 수 있도록 Google 에서 제공하는 Font들을 제어 할 수 있게 하는 것을 말한다.&quot; 라고 해석 할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Node.js & mongoose 페이징 기능 구현하기]]></title>
            <link>https://velog.io/@won-developer/Node.js-mongoose-%ED%8E%98%EC%9D%B4%EC%A7%95-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@won-developer/Node.js-mongoose-%ED%8E%98%EC%9D%B4%EC%A7%95-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 06 Mar 2020 05:02:10 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/won-developer/post/47f5530f-dd79-4430-bf33-5916aec7b33c/nodeandmongoosepaging.png" alt=""></p>
<hr>
<h2 id="✅-완성-모습">✅ 완성 모습</h2>
<p><img src="https://images.velog.io/images/won-developer/post/ce550539-fd0e-4fae-bb20-6250ff51926d/geunwonboard.png" alt=""></p>
<p>이 페이지는 maxPost, maxPage 5로 구성 된 페이지이다. 아래 설명은 10으로 가정한다.
<a href="https://my-first-board.herokuapp.com/">https://my-first-board.herokuapp.com/</a></p>
<hr>
<h2 id="✅-학습-목표">✅ 학습 목표</h2>
<ul>
<li>페이징 기능을 구현할 수 있다.</li>
<li>내부적으로 사용되는 변수의 기능을 이해할 수 있다.</li>
<li>noSQL 방식에서의 페이징 기법을 이해할 수 있다.</li>
</ul>
<hr>
<h2 id="✅-사용-기술">✅ 사용 기술</h2>
<ul>
<li>Node.js</li>
<li>Mongoose</li>
<li>Pug</li>
</ul>
<hr>
<h2 id="✅-구현-하기">✅ 구현 하기</h2>
<h3 id="✔-컨트롤러-만들기">✔ 컨트롤러 만들기</h3>
<pre><code class="language-javascript">export const pagingController = async (req, res) =&gt; {
  const { page } = req.query; // (1)
  try {
    const totalPost = await Post.countDocuments({}); // (2)
    if (!totalPost) { // (3)
      throw Error();
    }
    let {
      startPage,
      endPage,
      hidePost,
      maxPost,
      totalPage,
      currentPage
    } = paging(page, totalPost); // (4)
    const board = await Post.find({}) // (5)
      .sort({ createAt: -1 })
      .skip(hidePost)
      .limit(maxPost);
    res.render(&quot;home&quot;, { // (6)
      board,
      currentPage,
      startPage,
      endPage,
      maxPost,
      totalPage,
    });
  } catch (error) {
    res.render(&quot;home&quot;, { board: [] }); // (7)
  } 
};</code></pre>
<h4 id="✔-설명">✔ 설명</h4>
<ul>
<li>(1) : req.query 객체를 통해서 URL의 쿼리스트링을 가져옴. ex) http://페이징구현/?page=1</li>
<li>(2) : 현재 DB에 존재하는 모든 Post(게시물)의 총 개수를 가져옴.</li>
<li>(3) : 만약 총 개시물이 없을 경우 Error를 던져 (7)번으로 분기함.</li>
<li>(4) : paging에 필요한 변수들을 만들기 위해 page와 totalPost를 아규먼트로 함수 호출하여 ES6 비구조화 할당으로 리턴 받는다.</li>
<li>(5) : db에서 (4)에서 받아온 변수를 토대로 필요한 게시물들을 받아오자. 여기서 mongoose의 sort와 skip, limit를 쓰고 있는데 sort는 내림차순으로 정렬하겠다는 것이고, skip은 제외할 게시물, limit은 제외할 게시물을 제외하고 그 다음부터 찾을 게시물이다. 즉 hidePost가 20이고 limit가 5라면 21~25 게시물을 받아온다.</li>
<li>(6) : 받아온 db 데이터와 화면에서 렌더링 하기 위해 필요한 변수들을 템플릿으로 전달한다.</li>
<li>(7) : (3)번에서 에러로 분기 했을 경우는 게시물이 없는 경우이다. 빈 board 객체를 템플릿으로 전달한다.</li>
</ul>
<hr>
<h3 id="✔-paging-함수">✔ paging 함수</h3>
<pre><code class="language-javascript">const paging = (page, totalPost) =&gt; {
  const maxPost = 10; // (1)
  const maxPage = 10; // (2)
  let currentPage = page ? parseInt(page) : 1; // (3)
  const hidePost = page === 1 ? 0 : (page - 1) * maxPost; // (4)
  const totalPage = Math.ceil(totalPost / maxPost); // (5)

  if (currentPage &gt; totalPage) { // (6)
    currentPage = totalPage;
  }

  const startPage = Math.floor(((currentPage - 1) / maxPage)) * maxPage + 1; // (7)
  let endPage = startPage + maxPage - 1; // (8)

  if (endPage &gt; totalPage) { // (9)
    endPage = totalPage;
  }

  return { startPage, endPage, hidePost, maxPost, totalPage, currentPage }; // (10)
};

export default paging;</code></pre>
<ul>
<li>(1) maxPost : 한 페이지에 표시 하고자하는 최대 게시물의 수</li>
<li>(2) maxPage : 한 페이지에 표시 하고자하는 최대 페이지의 수</li>
<li>(3) currentPage : 현재 페이지, 만약 쿼리스트링으로 현재 페이지(page)를 받아 왔을 경우 parseInt 메서드를 통해 정수화하고 아닐 경우 1</li>
<li>(4) hidePost : DB에서 게시물을 불러올때 제외하고 불러올 게시물의 수 만약 page가 1일 경우 제외 할 게시물이 없음으로 0, 있을 경우 -1을 하여 maxPost를 곱해준다. -1을 하는 이유는 예를 들어 3페이지를 출력 할 경우 1페이지는 1<del>10의 게시물, 2페이지는 11</del>20의 게시물, 3페이지는 21~30의 게시물을 출력 해야하는데 -1을 해주지 않으면 31부터 40의 게시물을 skip 하게 된다.</li>
<li>(5) totalPage : 총 페이징 해야 할 페이지의 수 totalPost(DB에 존재하는 게시물의 수) / maxPost를 하면 간단히 구할 수 있다. Math.ceil 메서드를 통해 나머지가 생길 경우를 대비한다. Math.ceil 메서드는 나머지를 무시하고 올림한다. floor을 쓰지 않고 ceil을 쓰는 이유는 만약 floor을 썼을 경우 totalPost가 33이면 33 / 5는 6이 된다.  33개의 게시물을 표시 하기 위해선 총 7개의 페이지가 필요하다.</li>
<li>(6) : 화면 UI를 통하지 않고 URL에서 직접 접근 하는 경우가 생길 수 있다. 그럴 경우를 대비해서 currentPage(현재 페이지)가 totalPage(총 페이지)보다 클 경우 currentPage를 마지막 페이지(totalPage의 개수가 마지막 페이지가 된다)로 바꾸어 준다.</li>
<li>(7) startPage : 화면에 표시할 페이지의 시작 번호를 구한다. 예를 들어 현재 3페이지일 경우 startPage는 1이 나와야 한다. currentPage에서 -1을 빼주고 maxPage와 나눈다. -1을 하는 이유는 만약 현재 페이지가 20페이지일 경우 startPage는 11이 되어야 한다. -1을 해주지 않으면 startPage가 21페이지가 되어버린다. 그러고나서 maxPage를 곱해주고 + 1을 해준다. </li>
<li>(8) endPage : 화면에 표시할 페이지의 마지막 번호를 구한다. startPage는 이시점에서 이미 결정이 되어 있기 때문에 그냥 maxPage를 더한 다음 -1 해주면 된다. 만약 startPage가 11 일경우 endPage는 20 이여야 한다. 11 + 10 - 1 하면 20이 된다.</li>
<li>(9) : endPage가 totalPage보다 큰 경우 예를 들어 totalPage의 개수는 36일때 startPage는 31일 것이다. 이때 endPage는 40이 될터인데 총 page는 36개이면 충분하다. 이것을 방지하기 위하여 치환 해준다.</li>
<li>(10) : 이제 페이징을 하기 위한 변수는 모두 생성했으니 객체로 리턴한다.</li>
</ul>
<hr>
<h3 id="✔-템플릿-만들기">✔ 템플릿 만들기</h3>
<pre><code class="language-javascript">if startPage &gt; maxPost // (1)
    a.prev_btn.pageBtn(href=`${routes.home}?page=${startPage-1}`)
        i.fas.fa-angle-double-left
else // (2)
    a.prev_btn.pageBtn.disabled(href=&quot;javascript:void(0);&quot;)
        i.fas.fa-angle-double-left
-for (let i = startPage; i &lt;= endPage ; i++) // (3)
    if i === currentPage // (4)
        a.pageBtn.current(href=`${routes.home}?page=${i}`)
            span=i
    else
        a.pageBtn(href=`${routes.home}?page=${i}`)
            span=i       
if endPage &lt; totalPage // (5)
    a.next_btn.pageBtn(href=`${routes.home}?page=${endPage+1}`)
        i.fas.fa-angle-double-right
else // (6)
    a.next_btn.pageBtn.disabled(href=&quot;javascript:void(0);&quot;)
        i.fas.fa-angle-double-right</code></pre>
<h4 id="✔-화면-구성">✔ 화면 구성</h4>
<ul>
<li>다음 페이지 버튼으로 넘어갈 수 있다. (만약 현재 페이지가 15일 경우 21페이지로) 조건을 만족하지 못 할 경우 disiable 처리한다. (만약 위치한 페이지가 페이징할 마지막 페이지들에 위치할 경우)</li>
<li>이전 페이지 버튼으로 넘어갈 수 있다. (만약 현재 페이지가 15일 경우 10페이지로) 조건을 만족하지 못 할 경우 disiable 처리한다. (만약 위치한 페이지가 페이징할 첫번째 페이지들에 위치할 경우)</li>
<li>현재 페이지는 .current 선택자를 이용하여 다른 색으로 표시한다.<h4 id="✔-설명-1">✔ 설명</h4>
</li>
<li>(1) : startPage가 maxPost 보다 크다는 것은 현재 페이지가 페이징할 첫번째 페이지가 아님을 의미한다.(11이상) 이전페이지 버튼을 활성화 한다.</li>
<li>(2) : 조건을 만족하지 못 할 경우 비활성화 한다.</li>
<li>(3) : startPage부터 endPage까지 반복하며 페이지 버튼을 출력한다.</li>
<li>(4) : 현제 페이지의 경우 다른 색상으로 표시한다.</li>
<li>(5) : endPage가 totalPage보다 작다는 것은 현재 페이지가 페이징할 마지막 페이지가 아님을 의미한다. 다음페이지 버튼을 활성화 한다.</li>
<li>(6) : 조건을 만족하지 못 할 경우 비활성화 한다.</li>
</ul>
<p>끄읏!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[두 정수의 사이의 합]]></title>
            <link>https://velog.io/@won-developer/%EB%91%90-%EC%A0%95%EC%88%98%EC%9D%98-%EC%82%AC%EC%9D%B4%EC%9D%98-%ED%95%A9</link>
            <guid>https://velog.io/@won-developer/%EB%91%90-%EC%A0%95%EC%88%98%EC%9D%98-%EC%82%AC%EC%9D%B4%EC%9D%98-%ED%95%A9</guid>
            <pubDate>Sun, 09 Feb 2020 13:46:01 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/won-developer/post/26dd3353-2898-42a6-a233-10181c55c9d9/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98.png" alt=""></p>
<hr>
<h2 id="✅-문제-설명">✅ 문제 설명</h2>
<p>두 정수 a, b가 주어졌을 때 a와 b 사이에 속한 모든 정수의 합을 리턴하는 함수, solution을 완성하세요.
예를 들어 a = 3, b = 5인 경우, 3 + 4 + 5 = 12이므로 12를 리턴합니다.</p>
<hr>
<h2 id="✅-제한-조건">✅ 제한 조건</h2>
<p>a와 b가 같은 경우는 둘 중 아무 수나 리턴하세요.
a와 b는 -10,000,000 이상 10,000,000 이하인 정수입니다.
a와 b의 대소관계는 정해져있지 않습니다.</p>
<h3 id="✔-입출력-예">✔ 입출력 예</h3>
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
<th>return</th>
</tr>
</thead>
<tbody><tr>
<td>3</td>
<td>5</td>
<td>12</td>
</tr>
<tr>
<td>3</td>
<td>3</td>
<td>3</td>
</tr>
<tr>
<td>5</td>
<td>3</td>
<td>12</td>
</tr>
<tr>
<td>- - -</td>
<td></td>
<td></td>
</tr>
<tr>
<td>## ✅ 풀이</td>
<td></td>
<td></td>
</tr>
<tr>
<td>```javascript</td>
<td></td>
<td></td>
</tr>
<tr>
<td>function solution(a, b) {</td>
<td></td>
<td></td>
</tr>
<tr>
<td>let sum = 0;</td>
<td></td>
<td></td>
</tr>
<tr>
<td>if(a&gt;b) [a, b] = [b, a]; // 1</td>
<td></td>
<td></td>
</tr>
<tr>
<td>for(let i=a;i&lt;=b;i++){ // 2</td>
<td></td>
<td></td>
</tr>
<tr>
<td>sum += i;</td>
<td></td>
<td></td>
</tr>
<tr>
<td>}</td>
<td></td>
<td></td>
</tr>
<tr>
<td>return sum;</td>
<td></td>
<td></td>
</tr>
<tr>
<td>}</td>
<td></td>
<td></td>
</tr>
<tr>
<td>```</td>
<td></td>
<td></td>
</tr>
<tr>
<td>- 1: 대소관계를 맞추기 위해서 조건 검사후 변수 값 교환</td>
<td></td>
<td></td>
</tr>
<tr>
<td>- 2: a가 b와 작거나 같을때까지 1씩 증가하며 sum 에 값 저장</td>
<td></td>
<td></td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[같은 숫자는 싫어]]></title>
            <link>https://velog.io/@won-developer/%EA%B0%99%EC%9D%80-%EC%88%AB%EC%9E%90%EB%8A%94-%EC%8B%AB%EC%96%B4</link>
            <guid>https://velog.io/@won-developer/%EA%B0%99%EC%9D%80-%EC%88%AB%EC%9E%90%EB%8A%94-%EC%8B%AB%EC%96%B4</guid>
            <pubDate>Sun, 09 Feb 2020 13:27:05 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/won-developer/post/deb30c3c-0a5c-4f03-9693-6fd9b5cdde75/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98.png" alt=""></p>
<hr>
<h2 id="✅-문제-설명">✅ 문제 설명</h2>
<p>배열 arr가 주어집니다. 배열 arr의 각 원소는 숫자 0부터 9까지로 이루어져 있습니다. 이때, 배열 arr에서 연속적으로 나타나는 숫자는 하나만 남기고 전부 제거하려고 합니다. 단, 제거된 후 남은 수들을 반환할 때는 배열 arr의 원소들의 순서를 유지해야 합니다. 예를 들면, arr = [1, 1, 3, 3, 0, 1, 1] 이면 [1, 3, 0, 1]을 return 합니다. arr = [4, 4, 4, 3, 3] 이면 [4, 3] 을 return 합니다. 배열 arr에서 연속적으로 나타나는 숫자는 제거하고 남은 수들을 return 하는 solution 함수를 완성해 주세요.</p>
<hr>
<h2 id="✅-제한사항">✅ 제한사항</h2>
<p>배열 arr의 크기 : 1,000,000 이하의 자연수
배열 arr의 원소의 크기 : 0보다 크거나 같고 9보다 작거나 같은 정수</p>
<h3 id="✔-입출력-예">✔ 입출력 예</h3>
<table>
<thead>
<tr>
<th>arr</th>
<th>answer</th>
</tr>
</thead>
<tbody><tr>
<td>[1,1,3,3,0,1,1]</td>
<td>[1,3,0,1]</td>
</tr>
<tr>
<td>[4,4,4,3,3]</td>
<td>[4,3]</td>
</tr>
<tr>
<td>- - -</td>
<td></td>
</tr>
<tr>
<td>## ✅ 풀이</td>
<td></td>
</tr>
<tr>
<td>```javascript</td>
<td></td>
</tr>
<tr>
<td>function solution(arr) {</td>
<td></td>
</tr>
<tr>
<td>return arr.filter((c,i,a)=&gt; c !== a[i+1]); // 1</td>
<td></td>
</tr>
<tr>
<td>}</td>
<td></td>
</tr>
<tr>
<td>```</td>
<td></td>
</tr>
<tr>
<td>- 1: 연속적으로 나타나는 숫자들을 제거 함으로 filter 메서드를 이용해서 자신과 자신의 다음 요소가 다를때만 반환 하도록 함.</td>
<td></td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[Immer.js과 불변성 유지]]></title>
            <link>https://velog.io/@won-developer/Immer.js%EA%B3%BC-%EB%B6%88%EB%B3%80%EC%84%B1-%EC%9C%A0%EC%A7%80</link>
            <guid>https://velog.io/@won-developer/Immer.js%EA%B3%BC-%EB%B6%88%EB%B3%80%EC%84%B1-%EC%9C%A0%EC%A7%80</guid>
            <pubDate>Sun, 09 Feb 2020 06:27:38 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/won-developer/post/d007ac4c-51a4-476e-82c2-727a96de65a4/react.png" alt=""></p>
<h2 id="불변성이란">불변성이란?</h2>
<p>검색해 보면 &#39;변하지 않는 성질&#39; 이라 정의하고 있음. JS에서 불변성이란 &#39;기존의 값을 그대로 유지하면서 새로운 값을 추가 하는것&#39;을 말함.</p>
<h3 id="불변성의-예">불변성의 예</h3>
<pre><code class="language-javascript">const a = [];
const b = a;
console.log(a === b); // true

const c = [...a];
console.log(a === c); // false</code></pre>
<ul>
<li>b는 배열a의 가르키고 있음. 그럼으로 둘은 동일한 배열임.</li>
<li>c는 배열a의 값을 전개연산자를 통해 복사 했음 둘은 다른 배열임.</li>
</ul>
<h3 id="react에서의-활용">React에서의 활용</h3>
<pre><code class="language-javascript">const [state, setState] = useState(
  {
    1:1, 
    2:2
  }
);

setState({
  ...state, // 기존의 값 복사
  3: 3
})</code></pre>
<ul>
<li>기존 값을 유지하면서 새로운 값을 추가함.</li>
</ul>
<h4 id="-전개연산자의-얕은-복사shallow-copy">... 전개연산자의 얕은 복사(shallow copy)</h4>
<p>전개연산자를 통하여 객체나 배열의 값을 복사할때는 얕은 복사를 하게 됨. 즉 완전히 새로 복사 하는게 아니라 가장 바깥쪽에 위치한 값만 복사 됨. </p>
<pre><code class="language-javascript">const a = [{1:1, 2:2}];
const b = [...a];

console.log(a === b); // false // a와 b는 다른배열.
console.log(a[0] === b[0]); // true // 요소들은 같은 요소임. 얕은 복사 했기 때문.

b[0] = { // 값을 새로 할당
  ...b[0]
}

console.log(a[0] === b[0]); // false // 이제 다른 요소.</code></pre>
<ul>
<li>구조의 깊이가 깊어질수록 전개연산자를 통한 불변성 유지가 힘들어짐.</li>
</ul>
<h2 id="immerjs">Immer.js</h2>
<p>위의 문제를 해결하기 위해 나온 라이브러리가 바로 Immer.js 임. 상태를 업데이트할때 불변성을 관리해줌.</p>
<ul>
<li>설치 : yarn add immer 또는 npm i immer</li>
</ul>
<pre><code class="language-javascript">import produce from &quot;immer&quot;;

const [state, setState] = useState(
  {
    1:1, 
    2:2
  }
);

setState(produce(state, draft =&gt; {
  draft[3] = 3;
}))</code></pre>
<ul>
<li>setState의 인자로 produce 함수를 넣어줌. produce 함수의 첫번째 인자로 변경하고자 하는 것을 넣어주고 두번째 인자는 콜백함수임. 즉 draft가 첫번째 인자로 넣은 변경하고자 하는 객체나 배열이 됨. 함수 안에서 새로 만들고 싶은 값을 작업 해주면 됨.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[가운데 글자 가져오기]]></title>
            <link>https://velog.io/@won-developer/%EA%B0%80%EC%9A%B4%EB%8D%B0-%EA%B8%80%EC%9E%90-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0</link>
            <guid>https://velog.io/@won-developer/%EA%B0%80%EC%9A%B4%EB%8D%B0-%EA%B8%80%EC%9E%90-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0</guid>
            <pubDate>Sat, 08 Feb 2020 14:04:23 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/won-developer/post/20ccee3c-5a0d-4427-8415-0107a1bf92e3/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98.png" alt=""></p>
<hr>
<h2 id="✅-문제-설명">✅ 문제 설명</h2>
<p>단어 s의 가운데 글자를 반환하는 함수, solution을 만들어 보세요. 단어의 길이가 짝수라면 가운데 두글자를 반환하면 됩니다.</p>
<h2 id="✅-제한사항">✅ 제한사항</h2>
<p>s는 길이가 1 이상, 100이하인 스트링입니다.</p>
<h3 id="✔-입출력-예">✔ 입출력 예</h3>
<table>
<thead>
<tr>
<th>s</th>
<th>return</th>
</tr>
</thead>
<tbody><tr>
<td>abcde</td>
<td>c</td>
</tr>
</tbody></table>
<h2 id="✅-풀이">✅ 풀이</h2>
<pre><code class="language-javascript">function solution(s) {
    const div = s.length % 2; // 1
    const point = Math.floor(s.length/2); // 2
    return (div ? s[point] : s.substring(point-1,point+1)); // 3
}</code></pre>
<ul>
<li>1: 문자열 s의 길이를 2로 나눈 나머지 짝수인지 홀수인지 구분</li>
<li>2: s의 길이가 홀수일 경우 실수임으로 Math객체의 floor메서드로 정수로 바꿔줌.</li>
<li>3: div가 true일 경우 홀수, false일 경우 짝수임.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2016년]]></title>
            <link>https://velog.io/@won-developer/2016%EB%85%84</link>
            <guid>https://velog.io/@won-developer/2016%EB%85%84</guid>
            <pubDate>Sat, 08 Feb 2020 13:55:28 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/won-developer/post/98de17ce-77d7-4469-a33d-d40a234dfa88/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98.png" alt=""></p>
<hr>
<h2 id="✅-문제-설명">✅ 문제 설명</h2>
<p>2016년 1월 1일은 금요일입니다. 2016년 a월 b일은 무슨 요일일까요? 두 수 a ,b를 입력받아 2016년 a월 b일이 무슨 요일인지 리턴하는 함수, solution을 완성하세요. 요일의 이름은 일요일부터 토요일까지 각각     SUN,MON,TUE,WED,THU,FRI,SAT 입니다. 예를 들어 a=5, b=24라면 5월 24일은 화요일이므로 문자열 TUE를 반환하세요.</p>
<hr>
<h2 id="✅-제한-조건">✅ 제한 조건</h2>
<p>2016년은 윤년입니다.
2016년 a월 b일은 실제로 있는 날입니다. (13월 26일이나 2월 45일같은 날짜는 주어지지 않습니다)</p>
<h3 id="✔-입출력-예">✔ 입출력 예</h3>
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>5</td>
<td>24</td>
<td>TUE</td>
</tr>
<tr>
<td>---</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h2 id="✅-풀이">✅ 풀이</h2>
<pre><code class="language-javascript">function solution(a, b) {
  let day = [&quot;SUN&quot;, &quot;MON&quot;, &quot;TUE&quot;, &quot;WED&quot;, &quot;THU&quot;, &quot;FRI&quot;, &quot;SAT&quot;]; // 3
  let date = new Date(`2016/${a}/${b}`).getDay(); // 1
  return day[date]; // 2
}</code></pre>
<ul>
<li>1: date 객체 생성자로 매개변수 a와 b를 템플릿 리터럴로 넣어준다음 리턴된 객체에 getDay 메서드를 통해 해당 요일의 정수값을 반환 받는다. 일요일 0부터 시작하며 토요일은 6이다.</li>
<li>2: 3에서 선언해준 day 배열의 index로 date 변수를 넣어 리턴한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[K번째 수]]></title>
            <link>https://velog.io/@won-developer/K%EB%B2%88%EC%A7%B8-%EC%88%98</link>
            <guid>https://velog.io/@won-developer/K%EB%B2%88%EC%A7%B8-%EC%88%98</guid>
            <pubDate>Thu, 06 Feb 2020 14:21:38 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/won-developer/post/f1501251-e77d-470c-814e-6b7a161fb282/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98.png" alt=""></p>
<hr>
<h2 id="✅-문제-설명">✅ 문제 설명</h2>
<p>배열 array의 i번째 숫자부터 j번째 숫자까지 자르고 정렬했을 때, k번째에 있는 수를 구하려 합니다. 예를 들어 array가 [1, 5, 2, 6, 3, 7, 4], i = 2, j = 5, k = 3이라면 array의 2번째부터 5번째까지 자르면 [5, 2, 6, 3]입니다. 1에서 나온 배열을 정렬하면 [2, 3, 5, 6]입니다. 2에서 나온 배열의 3번째 숫자는 5입니다. 배열 array, [i, j, k]를 원소로 가진 2차원 배열 commands가 매개변수로 주어질 때, commands의 모든 원소에 대해 앞서 설명한 연산을 적용했을 때 나온 결과를 배열에 담아 return 하도록 solution 함수를 작성해주세요.</p>
<hr>
<h2 id="✅-제한사항">✅ 제한사항</h2>
<p>array의 길이는 1 이상 100 이하입니다.
array의 각 원소는 1 이상 100 이하입니다.
commands의 길이는 1 이상 50 이하입니다.
commands의 각 원소는 길이가 3입니다.</p>
<h3 id="✔-입출력-예">✔ 입출력 예</h3>
<table>
<thead>
<tr>
<th>array</th>
<th>commands</th>
<th>return</th>
</tr>
</thead>
<tbody><tr>
<td>[1, 5, 2, 6, 3, 7, 4]</td>
<td>[[2, 5, 3], [4, 4, 1], [1, 7, 3]]</td>
<td>[5, 6, 3]</td>
</tr>
</tbody></table>
<h3 id="✔-입출력-예-설명">✔ 입출력 예 설명</h3>
<p>[1, 5, 2, 6, 3, 7, 4]를 2번째부터 5번째까지 자른 후 정렬합니다. [2, 3, 5, 6]의 세 번째 숫자는 5입니다.
[1, 5, 2, 6, 3, 7, 4]를 4번째부터 4번째까지 자른 후 정렬합니다. [6]의 첫 번째 숫자는 6입니다.
[1, 5, 2, 6, 3, 7, 4]를 1번째부터 7번째까지 자릅니다. [1, 2, 3, 4, 5, 6, 7]의 세 번째 숫자는 3입니다.</p>
<hr>
<h2 id="✅-풀이">✅ 풀이</h2>
<h3 id="✔-1번째-풀이">✔ 1번째 풀이</h3>
<pre><code class="language-javascript">function solution(array, commands) {
    let answer = [];
    for(let i=0;i&lt;commands.length;i++){
        answer.push(array.slice(commands[i][0]-1,commands[i][1]).sort((a,b)=&gt;a-b)[commands[i][2]-1]);
    }
    return answer;
}</code></pre>
<p>아직 완전 초짜다 보니까 문제 풀때 무슨 메서드를 써야 효율적인지 잘 안떠오른다.. 그래서 for문 돌려보고 아! 이걸로도 되겠구나 할때가 있다. map이 생각났다.</p>
<h3 id="✔-2번째-풀이">✔ 2번째 풀이</h3>
<pre><code class="language-javascript">function solution(array, commands) {
    return commands.map(i=&gt;{ // 1
        return array.slice(i[0]-1,i[1]).sort((a,b)=&gt;a-b)[i[2]-1];  // 2
    });
}</code></pre>
<ul>
<li>1: 2차원 배열 commands를 map을 통해 순회한다.</li>
<li>2: 각 인덱스에는 자를 길이(i[0]-1 ~[1])와 리턴 할 수의 인덱스(i[2]-1)가 있다. array를 자른다. 리턴된 배열을 sort로 정렬한다. i[2]-1 번째 요소의 값을 리턴한다.</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>