<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>블로그 이름 뭐하지??</title>
    <link>https://potwings.tistory.com/</link>
    <description>다들 힘내 좋아

를 거꾸로 읽어보세요</description>
    <language>ko</language>
    <pubDate>Tue, 12 May 2026 00:24:45 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Potwings</managingEditor>
    <image>
      <title>블로그 이름 뭐하지??</title>
      <url>https://tistory1.daumcdn.net/tistory/4785042/attach/7bb8cce52b6e4c46a471e34fb0a49d14</url>
      <link>https://potwings.tistory.com</link>
    </image>
    <item>
      <title>CSRF 공격 방지 - SameSite 쿠키, CSRF 토큰</title>
      <link>https://potwings.tistory.com/73</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;인증과 관련하여 Spring Security에 대해 학습하던 중 CSRF라는 키워드에 갑자기 궁금증이 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에는 대부분의 백엔드 서버가&lt;b&gt; REST API 형식으로 데이터를 제공하여 CSRF에 대한 검증을 하지 않는다&lt;/b&gt; 하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1553&quot; data-origin-height=&quot;167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vhFvh/btsQmxUYtKE/lzvcU7lc1UzBGBYGVQ6iJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vhFvh/btsQmxUYtKE/lzvcU7lc1UzBGBYGVQ6iJK/img.png&quot; data-alt=&quot;REST API 서버에서 CSRF에 대한 검증이 disable 상태인 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vhFvh/btsQmxUYtKE/lzvcU7lc1UzBGBYGVQ6iJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvhFvh%2FbtsQmxUYtKE%2FlzvcU7lc1UzBGBYGVQ6iJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1553&quot; height=&quot;167&quot; data-origin-width=&quot;1553&quot; data-origin-height=&quot;167&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;REST API 서버에서 CSRF에 대한 검증이 disable 상태인 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면&lt;b&gt; 기존의 세션 방식으로 인증을 진행할 때는 왜 CSRF 공격에 대한 대비를 해야했을까??&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CSRF 공격이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cross-Site Request Forgery의 줄임말로 &lt;b&gt;사이트 간 요청 위조&lt;/b&gt;라는 의미이며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격자가 &lt;b&gt;인증된 사용자의 권한을 악용하여 사용자가 의도하지 않은 요청을 다른 사이트로 보내도록 하는 공격 기법&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;CSRF 공격 테스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 테스트를 위해&lt;b&gt; 공격 대상이 될 게시판 서비스&lt;/b&gt;를 생성하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2631&quot; data-origin-height=&quot;994&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/01UEn/btsQoCOjfev/Kinn3yKtsYFILtkU3AF5I0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/01UEn/btsQoCOjfev/Kinn3yKtsYFILtkU3AF5I0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/01UEn/btsQoCOjfev/Kinn3yKtsYFILtkU3AF5I0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F01UEn%2FbtsQoCOjfev%2FKinn3yKtsYFILtkU3AF5I0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2631&quot; height=&quot;994&quot; data-origin-width=&quot;2631&quot; data-origin-height=&quot;994&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;CSRF 공격을 시도하기 위해 피싱 페이지&lt;/b&gt;를 제작하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2336&quot; data-origin-height=&quot;872&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7814L/btsQTPTJA1x/KQuUx6P37K1WcufEJ3QH11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7814L/btsQTPTJA1x/KQuUx6P37K1WcufEJ3QH11/img.png&quot; data-alt=&quot;피싱 사이트 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7814L/btsQTPTJA1x/KQuUx6P37K1WcufEJ3QH11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7814L%2FbtsQTPTJA1x%2FKQuUx6P37K1WcufEJ3QH11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2336&quot; height=&quot;872&quot; data-origin-width=&quot;2336&quot; data-origin-height=&quot;872&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;피싱 사이트 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 둘은 서로 다른 서비스임을 가정하기 위해 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;게시판 서비스는 localhost&lt;/b&gt;&lt;/span&gt;로 접근,&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 피싱 사이트는 127.0.0.1&lt;/b&gt;&lt;/span&gt;로 접근하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 게시판 서비스에 로그인한 사용자가 &lt;b&gt;피싱 사이트에 접근할 경우 사용자의 인증 정보를 활용하여 공격자가 의도한대로 게시글이 등록&lt;/b&gt;되게 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 CSRF 공격을 두가지 방법으로 구현하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. form을 활용한 CSRF 공격&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 로딩 시 자동으로 &lt;b&gt;csrfForm을 submit&lt;/b&gt; 하도록 하여 사용자의 인증 정보를 활용하여 게시글을 작성하는 방식이다.&lt;/p&gt;
&lt;pre id=&quot;code_1758446147052&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- 방법 1: Form &amp;amp; iframe 활용 --&amp;gt;
&amp;lt;form id=&quot;csrfForm&quot; action=&quot;http://localhost:8080/board/new&quot; method=&quot;POST&quot; target=&quot;hiddenFrame&quot;&amp;gt;
  &amp;lt;input type=&quot;hidden&quot; name=&quot;title&quot; value=&quot;Form CSRF ATTACK&quot;&amp;gt;
  &amp;lt;input type=&quot;hidden&quot; name=&quot;writer&quot; value=&quot;hacker&quot;&amp;gt;
  &amp;lt;input type=&quot;hidden&quot; name=&quot;content&quot; value=&quot;게시판 중학생한테 털렸죠 ㅋㅋㅋㅋㅋ&quot;&amp;gt;
&amp;lt;/form&amp;gt;

&amp;lt;!-- iframe 추가 - 폼 제출 결과 여기로 이동 (사용자가 공격 사실 모르도록) --&amp;gt;
&amp;lt;iframe name=&quot;hiddenFrame&quot; style=&quot;display:none;&quot;&amp;gt;&amp;lt;/iframe&amp;gt;

&amp;lt;script&amp;gt;
  // 노출되지 않은 form에서 진행된 요청 전송
  document.getElementById('csrfForm').submit();
 &amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;form을 submit할 경우 자동으로 페이지 이동이 진행되는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hiddenFrame을 생성하여&lt;b&gt; 페이지 이동을 hiddenFrame에서 발생시켜&lt;/b&gt;&amp;nbsp; &lt;b&gt;&lt;b&gt;사용자가 알아차리지 못하도록&lt;/b&gt;&lt;/b&gt; 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2. ajax를 활용한 CSRF 공격&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 방법은 &lt;b&gt;ajax를 활용&lt;/b&gt;하여 페이지 로딩 시 게시글이 작성되도록 하는 방식이다.&lt;/p&gt;
&lt;pre id=&quot;code_1758446606051&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  &amp;lt;!-- 방법 2: ajax를 활용한 CSRF 공격 --&amp;gt;
  &amp;lt;!-- 요청 시 credentials: 'include' 옵션을 추가하여 쿠키(세션) 포함 --&amp;gt;
  function sendCSRF() {
    var formData = new FormData();
    formData.append('title', 'JS CSRF ATTACK');
    formData.append('writer', 'hacker');
    formData.append('content', '게시판 중학생한테 털렸죠 ㅋㅋㅋㅋㅋ');

    fetch('http://localhost:8080/board/new', {
      method: 'POST',
      body: formData,
      credentials: 'include',  // 쿠키(세션) 포함
    }).then((result) =&amp;gt; {
      document.getElementById('status').innerHTML = '&amp;lt;p style=&quot;color: green;&quot;&amp;gt;AJAX 요청 전송 완료&amp;lt;/p&amp;gt;';
    });
  }

  sendCSRF();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게시판 서비스에서 사용자의 인증된 세션 정보는 쿠키에 저장하고 있으므로 &lt;b&gt;ajax 요청 시 인증 정보도 같이 전달하기 위해&lt;/b&gt; &lt;b&gt;credentials: 'include'&lt;/b&gt; 옵션을 추가해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실행 결과&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2496&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EZjzO/btsQIWZV8DA/xrCGKKu1wedFPPNk2D6k9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EZjzO/btsQIWZV8DA/xrCGKKu1wedFPPNk2D6k9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EZjzO/btsQIWZV8DA/xrCGKKu1wedFPPNk2D6k9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEZjzO%2FbtsQIWZV8DA%2FxrCGKKu1wedFPPNk2D6k9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2496&quot; height=&quot;500&quot; data-origin-width=&quot;2496&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 공격자는 게시판 서비스에서 접근 권한이 없음에도 사용자 인증 정보를 활용하여 글을 등록할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그렇다면 이는 어떻게 방지할 수 있을까??&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;대처 방안&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 쿠키 SameSite 정책을 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 방법은 공격자의 사이트에서 &lt;b&gt;사용자 인증 정보를 활용할 수 없도록 하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위하여 우리는 사용자 인증 정보 &lt;b&gt;쿠키에 SameSite 속성을 추가&lt;/b&gt;하면 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;SameSite 속성이란?&lt;br /&gt;브라우저가 해당 쿠키를 어떤 상황에서 요청에 포함시킬지를 제어하는 정책&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SameSite 속성은 총 3종류가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. SameSite = None&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모든 cross-site 요청에도 쿠키를 전송&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제3자 서비스에서도 쿠키를 사용할 수 있기 때문에 위와 같은 &lt;b&gt;CSRF 공격에 취약&lt;/b&gt;하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. SameSite=Lax&amp;nbsp;(기본값)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교적 보안상 안전한 &lt;b&gt;GET 방식의 요청에서만 쿠키를 전송&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;POST&amp;nbsp;요청,&amp;nbsp;iframe,&amp;nbsp;이미지,&amp;nbsp;AJAX&amp;nbsp;등&amp;nbsp;cross-site&amp;nbsp;컨텍스트에서는&amp;nbsp;쿠키를&amp;nbsp;전송하지&amp;nbsp;않음.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chrome 80 버전부터는 별도의 정책이 수립되어있지 않은 경우 Lax를 기본값으로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt; (localhost의 경우 브라우저에서 테스트 환경으로 판단하여 이 정책에 포함되지 않았다) &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. SameSite=Strict&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 보수적인 설정으로 &lt;b&gt;같은 사이트 내에서의 요청에서만 해당 쿠키를 사용&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시에서 CSRF 공격은 iframe, AJAX 요청을 통해 진행되었으므로 &lt;b&gt;SameSite를 Lax로 설정하여 CSRF 공격을 예방&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1255&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z73KZ/btsQTNPakgy/7ScBlTBnx2HKwLLgQnYI61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z73KZ/btsQTNPakgy/7ScBlTBnx2HKwLLgQnYI61/img.png&quot; data-alt=&quot;SameSite Lax 설정 추가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z73KZ/btsQTNPakgy/7ScBlTBnx2HKwLLgQnYI61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ73KZ%2FbtsQTNPakgy%2F7ScBlTBnx2HKwLLgQnYI61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;511&quot; height=&quot;202&quot; data-origin-width=&quot;1255&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SameSite Lax 설정 추가&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1901&quot; data-origin-height=&quot;321&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bT6D1x/btsQSwHoGKB/4w3m49E3K0QLVPbozCcTk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bT6D1x/btsQSwHoGKB/4w3m49E3K0QLVPbozCcTk0/img.png&quot; data-alt=&quot;SameSite가 Lax로 설정된 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bT6D1x/btsQSwHoGKB/4w3m49E3K0QLVPbozCcTk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbT6D1x%2FbtsQSwHoGKB%2F4w3m49E3K0QLVPbozCcTk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1901&quot; height=&quot;321&quot; data-origin-width=&quot;1901&quot; data-origin-height=&quot;321&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SameSite가 Lax로 설정된 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증 정보 쿠키가 없으므로 &lt;b&gt;글이 등록되지 않고 login 페이지로 redirect&lt;/b&gt; 되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;1013&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQjxLt/btsQRGX3Eac/qxR8bRsnee8VcIjLHoGiC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQjxLt/btsQRGX3Eac/qxR8bRsnee8VcIjLHoGiC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQjxLt/btsQRGX3Eac/qxR8bRsnee8VcIjLHoGiC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQjxLt%2FbtsQRGX3Eac%2FqxR8bRsnee8VcIjLHoGiC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;568&quot; height=&quot;483&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;1013&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. CSRF 토큰 활용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 방법은 &lt;b&gt;CSRF 토큰을 활용&lt;/b&gt;하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CSRF 토큰이란&lt;/b&gt; 서버에서 암호화된 랜덤값을 생성 후 브라우저와 주고 받아 사용자의 의도에 따라 &lt;b&gt;정상적으로 발생된 요청인지 검증하는데 쓰이는 토큰&lt;/b&gt;값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글을 시작할 때 보았던 Spring Security의 CSRF 속성은 CSRF 토큰을 활용할지에 대한 설정값이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1553&quot; data-origin-height=&quot;167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vhFvh/btsQmxUYtKE/lzvcU7lc1UzBGBYGVQ6iJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vhFvh/btsQmxUYtKE/lzvcU7lc1UzBGBYGVQ6iJK/img.png&quot; data-alt=&quot;CSRF 토큰 사용 여부&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vhFvh/btsQmxUYtKE/lzvcU7lc1UzBGBYGVQ6iJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvhFvh%2FbtsQmxUYtKE%2FlzvcU7lc1UzBGBYGVQ6iJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1553&quot; height=&quot;167&quot; data-origin-width=&quot;1553&quot; data-origin-height=&quot;167&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CSRF 토큰 사용 여부&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 설정값을 disable 하지 않을 경우 POST 요청 시 CSRF 토큰이 같이 넘어왔는지 검증을 진행하고 &lt;b&gt;토큰이 없을 경우 요청은 거부된다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1328&quot; data-origin-height=&quot;355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cl1fv4/btsQ2pPGgcl/bkl8qhTNNHHubBU9kn1uUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cl1fv4/btsQ2pPGgcl/bkl8qhTNNHHubBU9kn1uUK/img.png&quot; data-alt=&quot;CSRF 토큰 없이 요청 할 경우 거부&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cl1fv4/btsQ2pPGgcl/bkl8qhTNNHHubBU9kn1uUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcl1fv4%2FbtsQ2pPGgcl%2Fbkl8qhTNNHHubBU9kn1uUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1328&quot; height=&quot;355&quot; data-origin-width=&quot;1328&quot; data-origin-height=&quot;355&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CSRF 토큰 없이 요청 할 경우 거부&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 form에 CSRF 토큰 값 추가하여 같이 전송해주면 정상적으로 요청이 처리된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1855&quot; data-origin-height=&quot;199&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blQf8s/btsQ4pAUTTA/LFayvcF8d4SxumPpdj7uV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blQf8s/btsQ4pAUTTA/LFayvcF8d4SxumPpdj7uV1/img.png&quot; data-alt=&quot;정상적으로 CSRF 토큰 값 추가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blQf8s/btsQ4pAUTTA/LFayvcF8d4SxumPpdj7uV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblQf8s%2FbtsQ4pAUTTA%2FLFayvcF8d4SxumPpdj7uV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1855&quot; height=&quot;199&quot; data-origin-width=&quot;1855&quot; data-origin-height=&quot;199&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;정상적으로 CSRF 토큰 값 추가&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hnnrk/btsQ2AQ1UeM/Jz3iRM1EyeaoylAF9kOn81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hnnrk/btsQ2AQ1UeM/Jz3iRM1EyeaoylAF9kOn81/img.png&quot; data-alt=&quot;정상적으로 요청이 처리된 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hnnrk/btsQ2AQ1UeM/Jz3iRM1EyeaoylAF9kOn81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHnnrk%2FbtsQ2AQ1UeM%2FJz3iRM1EyeaoylAF9kOn81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1316&quot; height=&quot;327&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;정상적으로 요청이 처리된 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;REST API에서 CSRF 토큰을 disable 하는 이유?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST API의 경우 &lt;b&gt;무상태(stateless)라 이전 요청에서 발생한 내용을 기억하지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠키나 세션에 인증 정보를 저장하지 않기 때문에 &lt;b&gt;브라우저에 저장된 인증 정보를 악용하는 CSRF 공격에 당할 위험이 적다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;Spring Security의 CSRF 토큰 설정값을 disable 할 수 있는 것이다.&lt;/b&gt;&lt;/p&gt;</description>
      <category>BackEnd/Spring</category>
      <author>Potwings</author>
      <guid isPermaLink="true">https://potwings.tistory.com/73</guid>
      <comments>https://potwings.tistory.com/73#entry73comment</comments>
      <pubDate>Tue, 7 Oct 2025 16:28:10 +0900</pubDate>
    </item>
    <item>
      <title>비개발자도 할 수 있는 구글 시트로 웹 앱 만들기</title>
      <link>https://potwings.tistory.com/72</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개발 동기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 비개발자인 친구가 회사에서 업무 부담을 줄이기 위해 기업들의 데이터를 각 기업의 담당자들이 직접 수정할 수 있도록 하고 싶어 방법을 찾고 있다는 이야기를 들어 시작하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 실제 어플리케이션을 개발하면 쉽게 제작할 수 있는 내용이나 아무래도 비개발자가 이를 운영하기 위한 인프라 및 유지보수를 진행하기엔 무리가 있다 판단하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러던 중 구글 스프레드 시트로 웹앱을 제작할 수 있다는 것을 알게되어 이를 활용하여 진행하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개발 과정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글 스프레드 시트의 앱 스크립트를 활용하여 개발을 진행하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프레드 시트의 [확장 프로그램] 메뉴에서 [Apps Script]에서 시작할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/9e3343b5-a6c5-4cf3-b00d-3476477696cb&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 초기 생성 시 아래와 같이 gs 파일를 마주하게 되는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/22de089a-9d21-40cc-a8cb-a6f03c113c2a&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gs 코드의 정확한 문법을 알지 못하지만 걱정할 필요 없다.&lt;b&gt; ChatGPT를 활용하면 되니까.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Apps Script 기본 파악&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 기본적인 동작 방식을 알아보기 위해 간단한 데이터로 기능만 동작할 수 있도록 구현해달라 요청하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/92cf0b09-d5d1-4517-b4fb-a535bf026b57&quot; alt=&quot;&quot; /&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/5448bd71-538d-41b3-89bb-016e4a99f32e&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;GPT가 작성해준 코드 확인 [클릭]&lt;/b&gt;&lt;/summary&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Code.gs&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function include(filename) {
  return HtmlService.createHtmlOutputFromFile(filename).getContent();
}

function loginUser(projectNumber, bizNumber) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(&quot;시트1&quot;);
  const data = sheet.getDataRange().getValues();

  for (let i = 1; i &amp;lt; data.length; i++) {
    if (data[i][0] == projectNumber &amp;amp;&amp;amp; data[i][1] == bizNumber) {
      return {
        success: true,
        name: data[i][2],
        phone: data[i][3],
        email: data[i][4],
        rowIndex: i + 1
      };
    }
  }

  return { success: false, message: &quot;일치하는 정보가 없습니다.&quot; };
}

function updateUser(rowIndex, name, phone, email) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(&quot;시트1&quot;);
  sheet.getRange(rowIndex, 3).setValue(name);
  sheet.getRange(rowIndex, 4).setValue(phone);
  sheet.getRange(rowIndex, 5).setValue(email);
  return { success: true };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Index.html&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;base target=&quot;_top&quot;&amp;gt;
    &amp;lt;style&amp;gt;
      body { font-family: Arial; margin: 20px; }
      input { margin-bottom: 10px; display: block; }
    &amp;lt;/style&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h2&amp;gt;로그인&amp;lt;/h2&amp;gt;
    &amp;lt;input type=&quot;text&quot; id=&quot;projectNumber&quot; placeholder=&quot;과제번호&quot;&amp;gt;
    &amp;lt;input type=&quot;text&quot; id=&quot;bizNumber&quot; placeholder=&quot;사업자번호&quot;&amp;gt;
    &amp;lt;button onclick=&quot;login()&quot;&amp;gt;로그인&amp;lt;/button&amp;gt;

    &amp;lt;div id=&quot;userInfo&quot; style=&quot;display:none;&quot;&amp;gt;
      &amp;lt;h3&amp;gt;내 정보&amp;lt;/h3&amp;gt;
      &amp;lt;input type=&quot;text&quot; id=&quot;name&quot; placeholder=&quot;담당자명&quot;&amp;gt;
      &amp;lt;input type=&quot;text&quot; id=&quot;phone&quot; placeholder=&quot;휴대폰번호&quot;&amp;gt;
      &amp;lt;input type=&quot;text&quot; id=&quot;email&quot; placeholder=&quot;이메일&quot;&amp;gt;
      &amp;lt;button onclick=&quot;update()&quot;&amp;gt;정보 수정&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;script&amp;gt;
      let currentRow = null;

      function login() {
        const projectNumber = document.getElementById('projectNumber').value;
        const bizNumber = document.getElementById('bizNumber').value;

        google.script.run.withSuccessHandler(function(response) {
          if (response.success) {
            currentRow = response.rowIndex;
            document.getElementById('userInfo').style.display = 'block';
            document.getElementById('name').value = response.name;
            document.getElementById('phone').value = response.phone;
            document.getElementById('email').value = response.email;
          } else {
            alert(response.message);
          }
        }).loginUser(projectNumber, bizNumber);
      }

      function update() {
        const name = document.getElementById('name').value;
        const phone = document.getElementById('phone').value;
        const email = document.getElementById('email').value;

        google.script.run.withSuccessHandler(function(res) {
          if (res.success) alert('정보가 수정되었습니다!');
        }).updateUser(currentRow, name, phone, email);
      }
    &amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 파일들을 내 프로젝트에 추가해 주고 정상적으로 동작할 수 있도록 이에 맞춰 스프레드 시트에 데이터를 추가해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/4d534016-85d5-40e7-81d7-adf25146da1c&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 우측 상단의 [배포] 버튼을 통하여 웹앱을 배포할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[배포] &amp;gt; [새 배포] &amp;gt; [유형 선택] 우측 톱니바퀴 &amp;gt; [웹 앱] 후 아래와 같이 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(설명은 개발자가 알아보기 위한 설명이라 내용 무관하다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/643cb18b-fe00-4210-82ac-dfaf57a22d88&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 액세스 허용을 요청하는 문구가 나온다면 허용을 진행해주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포를 완료한다면 아래와 같이 URL이 나오고 접속하면 HTML로 구현된 화면을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/7ccc6e5d-cbdc-49ea-a89b-2e2dd0cc4890&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/5f3eaee2-86a6-46d1-932f-62b6faabc82f&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 스프레드 시트에 등록된 정보를 입력하면 아래와 같아 정상적으로 조회/수정이 가능하며 시트에도 변경된 내용이 반영된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/deb0df59-758f-4b5e-86a8-9822630fa7cf&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/43d6346f-ebc5-491d-895a-454dd81fefbe&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;고도화 진행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 어느정도 어떻게 동작하는지에 대해 이해하였으니 정확한 요구사항을 전달하여 요청하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/156a89f2-6364-443f-890b-f3b437ae7640&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;GPT가 작성해준 코드 확인[클릭]&lt;/b&gt;&lt;/summary&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Code.gs&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;function loginUser(projectNumber, bizNumber) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(&quot;시트1&quot;);
  const data = sheet.getDataRange().getValues();

  for (let i = 1; i &amp;lt; data.length; i++) {
    if (data[i][4] == projectNumber &amp;amp;&amp;amp; data[i][5] == bizNumber) {
      // No 제외한 나머지 정보 (index 1부터)
      const userInfo = {
        연도: data[i][1],
        과제명: data[i][2],
        차수: data[i][3],
        과제번호: data[i][4],
        사업자번호: data[i][5],
        기업명: data[i][6],
        대표자명: data[i][7],
        대표자휴대폰번호: data[i][8],
        대표자이메일: data[i][9],
        담당자명직책: data[i][10],
        담당자휴대폰번호: data[i][11],
        담당자이메일: data[i][12],
      };

      return {
        success: true,
        data: userInfo,
        rowIndex: i + 1
      };
    }
  }

  return { success: false, message: &quot;일치하는 과제번호 및 사업자번호가 없습니다.&quot; };
}

function updateUser(rowIndex, updatedData) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(&quot;시트1&quot;);

  // No는 건너뛰고 2번째 열(B열)부터 M열까지 갱신
  const values = [
    updatedData.연도,
    updatedData.과제명,
    updatedData.차수,
    updatedData.과제번호,
    updatedData.사업자번호,
    updatedData.기업명,
    updatedData.대표자명,
    updatedData.대표자휴대폰번호,
    updatedData.대표자이메일,
    updatedData.담당자명직책,
    updatedData.담당자휴대폰번호,
    updatedData.담당자이메일,
  ];

  sheet.getRange(rowIndex, 2, 1, values.length).setValues([values]);

  return { success: true };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Index.html&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;base target=&quot;_top&quot;&amp;gt;
    &amp;lt;style&amp;gt;
      body {
        font-family: 'Segoe UI', sans-serif;
        background-color: #f0f2f5;
        padding: 40px;
      }
      .container {
        max-width: 500px;
        margin: auto;
        background: white;
        padding: 30px;
        border-radius: 12px;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
      }
      h2 {
        text-align: center;
        color: #333;
      }
      label {
        display: block;
        margin-top: 10px;
        font-weight: bold;
        color: #555;
      }
      input {
        width: 100%;
        padding: 10px;
        margin-top: 5px;
        border: 1px solid #ccc;
        border-radius: 6px;
        box-sizing: border-box;
      }
      button {
        width: 100%;
        margin-top: 20px;
        padding: 12px;
        background-color: #4CAF50;
        color: white;
        border: none;
        border-radius: 6px;
        font-size: 16px;
        cursor: pointer;
      }
      #userInfo {
        display: none;
        margin-top: 30px;
      }
    &amp;lt;/style&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div class=&quot;container&quot;&amp;gt;
      &amp;lt;h2&amp;gt;사용자 로그인&amp;lt;/h2&amp;gt;

      &amp;lt;label for=&quot;projectNumber&quot;&amp;gt;과제번호&amp;lt;/label&amp;gt;
      &amp;lt;input type=&quot;text&quot; id=&quot;projectNumber&quot; placeholder=&quot;예: PRJ-1234&quot;&amp;gt;

      &amp;lt;label for=&quot;bizNumber&quot;&amp;gt;사업자번호&amp;lt;/label&amp;gt;
      &amp;lt;input type=&quot;text&quot; id=&quot;bizNumber&quot; placeholder=&quot;예: 123-45-67890&quot;&amp;gt;

      &amp;lt;button onclick=&quot;login()&quot;&amp;gt;로그인&amp;lt;/button&amp;gt;

      &amp;lt;div id=&quot;userInfo&quot;&amp;gt;
        &amp;lt;hr&amp;gt;
        &amp;lt;h2&amp;gt;과제 정보&amp;lt;/h2&amp;gt;
        &amp;lt;div id=&quot;form-fields&quot;&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;button onclick=&quot;update()&quot;&amp;gt;정보 저장&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;script&amp;gt;
      let currentRow = null;
      let userData = {};

      function login() {
        const projectNumber = document.getElementById(&quot;projectNumber&quot;).value.trim();
        const bizNumber = document.getElementById(&quot;bizNumber&quot;).value.trim();

        if (!projectNumber || !bizNumber) {
          alert(&quot;과제번호와 사업자번호를 입력해주세요.&quot;);
          return;
        }

        google.script.run.withSuccessHandler(function (response) {
          if (response.success) {
            currentRow = response.rowIndex;
            userData = response.data;

            const form = document.getElementById(&quot;form-fields&quot;);
            form.innerHTML = &quot;&quot;;

            for (const key in userData) {
              const label = document.createElement(&quot;label&quot;);
              label.textContent = key;
              const input = document.createElement(&quot;input&quot;);
              input.id = key;
              input.value = userData[key];
              form.appendChild(label);
              form.appendChild(input);
            }

            document.getElementById(&quot;userInfo&quot;).style.display = &quot;block&quot;;
          } else {
            alert(response.message || &quot;로그인 실패&quot;);
          }
        }).loginUser(projectNumber, bizNumber);
      }

      function update() {
        const updated = {};
        for (const key in userData) {
          updated[key] = document.getElementById(key).value;
        }

        google.script.run.withSuccessHandler(function (res) {
          if (res.success) {
            alert(&quot;정보가 성공적으로 저장되었습니다.&quot;);
          } else {
            alert(&quot;수정 실패&quot;);
          }
        }).updateUser(currentRow, updated);
      }
    &amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPT가 작성해준 코드를 활용하여 기존 코드의 loginUser, updateUser 함수의 내용을 변경해주고 변경된 HTML을 적용해주고, 스프레드 시트의 내용도 이에 맞춰 변경해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/c37d2b0c-984f-4383-8a72-84cf615439ec&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정 사항이 반영되어 정상적으로 기능이 동작하는 것을 확인할 수 있엇다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/f1ddf1d8-9d26-44a4-b3fa-224f54b3915d&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추가 개선&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 사용자가 수정하는 데이터를 검증하고, 데이터 포맷에 대한 가이드를 추가하여 한번 더 개선을 진행하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/14017d36-6d02-4e0e-af45-022fd16240c6&quot; alt=&quot;&quot; /&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/d2a55b5b-11eb-4bbc-b025-45ac4437b372&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;최종 결과&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;code.gs&lt;/p&gt;
&lt;pre id=&quot;code_1743323042752&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function include(filename) {
  return HtmlService.createHtmlOutputFromFile(filename).getContent();
}

function loginUser(projectNumber, bizNumber) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(&quot;시트1&quot;);
  const data = sheet.getDataRange().getValues();

  for (let i = 1; i &amp;lt; data.length; i++) {
    if (data[i][4] == projectNumber &amp;amp;&amp;amp; data[i][5] == bizNumber) {
      // No 제외한 나머지 정보 (index 1부터)
      const userInfo = {
        &quot;연도&quot;: data[i][1],
        &quot;과제명&quot;: data[i][2],
        &quot;차수&quot;: data[i][3],
        &quot;과제 번호&quot;: data[i][4],
        &quot;사업자 번호&quot;: data[i][5],
        &quot;기업명&quot;: data[i][6],
        &quot;대표자명&quot;: data[i][7],
        &quot;대표자 휴대폰 번호&quot;: data[i][8],
        &quot;대표자 이메일&quot;: data[i][9],
        &quot;담당자명(직책)&quot;: data[i][10],
        &quot;담당자 휴대폰 번호&quot;: data[i][11],
        &quot;담당자 이메일&quot;: data[i][12],
      };

      return {
        success: true,
        data: userInfo,
        rowIndex: i + 1
      };
    }
  }

  return { success: false, message: &quot;일치하는 과제번호 및 사업자번호가 없습니다.&quot; };
}

function updateUser(rowIndex, updatedData) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(&quot;시트1&quot;);

  // 수정 가능한 필드만 업데이트 (8~13번째 열 = I~M 열)
  const editableValues = [
    updatedData[&quot;대표자 휴대폰 번호&quot;],
    updatedData[&quot;대표자 이메일&quot;],
    updatedData[&quot;담당자명(직책)&quot;],
    updatedData[&quot;담당자 휴대폰 번호&quot;],
    updatedData[&quot;담당자 이메일&quot;]
  ];


  // rowIndex 행의 I열(9번째 열)부터 5개 열을 덮어쓰기
  sheet.getRange(rowIndex, 9, 1, editableValues.length).setValues([editableValues]);

  return { success: true };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Index.html&lt;/p&gt;
&lt;pre id=&quot;code_1743323081710&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;base target=&quot;_top&quot;&amp;gt;
    &amp;lt;style&amp;gt;
      body {
        font-family: 'Segoe UI', sans-serif;
        background-color: #f0f2f5;
        padding: 40px;
      }
      .container {
        max-width: 500px;
        margin: auto;
        background: white;
        padding: 30px;
        border-radius: 12px;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
      }
      h2 {
        text-align: center;
        color: #333;
      }
      label {
        display: block;
        margin-top: 10px;
        font-weight: bold;
        color: #555;
      }
      input {
        width: 100%;
        padding: 10px;
        margin-top: 5px;
        border: 1px solid #ccc;
        border-radius: 6px;
        box-sizing: border-box;
      }
      button {
        width: 100%;
        margin-top: 20px;
        padding: 12px;
        background-color: #4CAF50;
        color: white;
        border: none;
        border-radius: 6px;
        font-size: 16px;
        cursor: pointer;
      }
      #userInfo {
        display: none;
        margin-top: 30px;
      }
    &amp;lt;/style&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div class=&quot;container&quot;&amp;gt;
      &amp;lt;div style=&quot;text-align: center;&quot;&amp;gt;
        &amp;lt;h3&amp;gt;사용자 로그인&amp;lt;/h3&amp;gt;
      &amp;lt;/div&amp;gt;

      &amp;lt;label for=&quot;projectNumber&quot;&amp;gt;과제번호&amp;lt;/label&amp;gt;
      &amp;lt;input type=&quot;text&quot; id=&quot;projectNumber&quot; placeholder=&quot;예 : 12345678&quot;&amp;gt;

      &amp;lt;label for=&quot;bizNumber&quot;&amp;gt;사업자번호&amp;lt;/label&amp;gt;
      &amp;lt;input type=&quot;text&quot; id=&quot;bizNumber&quot; placeholder=&quot;예: 123-45-67890&quot;&amp;gt;

      &amp;lt;button onclick=&quot;login()&quot;&amp;gt;로그인&amp;lt;/button&amp;gt;

      &amp;lt;div id=&quot;userInfo&quot;&amp;gt;
        &amp;lt;hr&amp;gt;
        &amp;lt;h2&amp;gt;과제 정보&amp;lt;/h2&amp;gt;
        &amp;lt;div id=&quot;form-fields&quot;&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;button onclick=&quot;update()&quot;&amp;gt;정보 저장&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;script&amp;gt;
      let currentRow = null;
      let userData = {};

      // 표시 순서와 일치하도록 배열로 순서 지정
      const fieldOrder = [
        &quot;연도&quot;,
        &quot;과제명&quot;,
        &quot;차수&quot;,
        &quot;과제 번호&quot;,
        &quot;사업자 번호&quot;,
        &quot;기업명&quot;,
        &quot;대표자명&quot;,
        &quot;대표자 휴대폰 번호&quot;,
        &quot;대표자 이메일&quot;,
        &quot;담당자명(직책)&quot;,
        &quot;담당자 휴대폰 번호&quot;,
        &quot;담당자 이메일&quot;
      ];

      const readOnlyFields = [
        &quot;연도&quot;,
        &quot;과제명&quot;,
        &quot;차수&quot;,
        &quot;과제 번호&quot;,
        &quot;사업자 번호&quot;,
        &quot;기업명&quot;,
        &quot;대표자명&quot;
      ];


      function login() {
        const projectNumber = document.getElementById(&quot;projectNumber&quot;).value.trim();
        const bizNumber = document.getElementById(&quot;bizNumber&quot;).value.trim();

        if (!projectNumber || !bizNumber) {
          alert(&quot;과제번호와 사업자번호를 입력해주세요.&quot;);
          return;
        }

        google.script.run.withSuccessHandler(function (response) {
          if (response.success) {
            currentRow = response.rowIndex;
            userData = response.data;

            const form = document.getElementById(&quot;form-fields&quot;);
            form.innerHTML = &quot;&quot;;

            fieldOrder.forEach((key) =&amp;gt; {
              const label = document.createElement(&quot;label&quot;);
              label.textContent = key;

              const input = document.createElement(&quot;input&quot;);
              input.id = key;
              input.value = userData[key] || &quot;&quot;;

              // 포맷 안내 텍스트
              const hint = document.createElement(&quot;small&quot;);
              hint.style.fontSize = &quot;12px&quot;;
              hint.style.color = &quot;#888&quot;;

              if (readOnlyFields.includes(key)) {
                input.setAttribute(&quot;readonly&quot;, true);
                input.style.backgroundColor = &quot;#f0f0f0&quot;;
              } else {
                // 유효성 포맷 힌트 표시
                if (key.includes(&quot;휴대폰&quot;)) {
                  hint.textContent = &quot;예: 010-1234-5678&quot;;
                } else if (key.includes(&quot;이메일&quot;)) {
                  hint.textContent = &quot;예: example@example.com&quot;;
                }
              }

              form.appendChild(label);
              form.appendChild(input);
              if (hint.textContent) form.appendChild(hint);
            });


            document.getElementById(&quot;userInfo&quot;).style.display = &quot;block&quot;;
          } else {
            alert(response.message || &quot;로그인 실패&quot;);
          }
        }).loginUser(projectNumber, bizNumber);
      }

      function update() {
        const updated = {};
        let isValid = true;
        let errorMessage = &quot;&quot;;

        const phoneRegex = /^\d{3}-\d{3,4}-\d{4}$/;
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

        fieldOrder.forEach((key) =&amp;gt; {
          const value = document.getElementById(key).value.trim();
          updated[key] = value;

          if (!readOnlyFields.includes(key)) {
            if (key.includes(&quot;휴대폰&quot;) &amp;amp;&amp;amp; value &amp;amp;&amp;amp; !phoneRegex.test(value)) {
              isValid = false;
              errorMessage = `${key} 형식이 올바르지 않습니다. (예: 010-1234-5678)`;
            }
            if (key.includes(&quot;이메일&quot;) &amp;amp;&amp;amp; value &amp;amp;&amp;amp; !emailRegex.test(value)) {
              isValid = false;
              errorMessage = `${key} 형식이 올바르지 않습니다. (예: example@example.com)`;
            }
          }
        });

        if (!isValid) {
          alert(errorMessage);
          return;
        }

        google.script.run.withSuccessHandler(function (res) {
          if (res.success) {
            alert(&quot;정보가 성공적으로 저장되었습니다.&quot;);
          } else {
            alert(&quot;수정 실패&quot;);
          }
        }).updateUser(currentRow, updated);
      }

    &amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; font-size: 1.44em; letter-spacing: -1px;&quot;&gt;화면&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;로그인&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/eb625aa9-46d6-4d6a-ac5b-b00b4a8c35b7&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;조회 및 수정&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/2c969900-1325-4492-a736-88880d046e4a&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;수정 시 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;데이터&lt;span&gt; &lt;/span&gt;&lt;/span&gt;검증&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/92abfe6c-3108-4d3e-a899-6e72edb63869&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github : &lt;a href=&quot;https://github.com/Potwings/Google-Sheet-WebApp&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/Potwings/Google-Sheet-WebApp&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1743323206945&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Potwings/Google-Sheet-WebApp: 구글 스프레드 시트를 활용하여 제작한 웹 앱&quot; data-og-description=&quot;구글 스프레드 시트를 활용하여 제작한 웹 앱. Contribute to Potwings/Google-Sheet-WebApp development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Potwings/Google-Sheet-WebApp&quot; data-og-url=&quot;https://github.com/Potwings/Google-Sheet-WebApp&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nHzAL/hyYvniSoig/S63xvskcwKUFCS2UxkavP0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/ccTthd/hyYxRJ6Jkx/UhkG5J34qwDdOBBjOPWEV1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Potwings/Google-Sheet-WebApp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Potwings/Google-Sheet-WebApp&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nHzAL/hyYvniSoig/S63xvskcwKUFCS2UxkavP0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/ccTthd/hyYxRJ6Jkx/UhkG5J34qwDdOBBjOPWEV1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Potwings/Google-Sheet-WebApp: 구글 스프레드 시트를 활용하여 제작한 웹 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;구글 스프레드 시트를 활용하여 제작한 웹 앱. Contribute to Potwings/Google-Sheet-WebApp development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>개발일지/side</category>
      <author>Potwings</author>
      <guid isPermaLink="true">https://potwings.tistory.com/72</guid>
      <comments>https://potwings.tistory.com/72#entry72comment</comments>
      <pubDate>Sun, 30 Mar 2025 17:35:00 +0900</pubDate>
    </item>
    <item>
      <title>SOLID 원칙이란?</title>
      <link>https://potwings.tistory.com/71</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;객체지향에서 소프트웨어를 &lt;b&gt;변경에 유연한 구조, 이해하기 쉬운 구조&lt;/b&gt;로 만들기 위한 5가지 원칙으로 각 원칙들의 앞글자를 따서 &lt;b&gt;SOLID 원칙&lt;/b&gt;이라 불린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;SRP - 단일 책임의 원칙&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하나의 클래스에서 하나의 책임만 가져야 한다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책임은 매우 주관적인 단어라 이해하기 조금 어려울 수 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 쉽게 표현하자면 &lt;b&gt;하나의 클래스의 변경은 오직 하나의 이유만으로 이루어져야 한다&lt;/b&gt;라고 말할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 SRP 원칙을 지키지 않을 경우 어떤 문제가 발생하는지 예시를 통해 알아보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;YG쇼핑몰에서는 회원가입을 했을 경우 가입 환영 이메일을, 주문을 완료했을 경우 주문 내역 알림 메일을 발송해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각 &lt;b&gt;sendWelcomeMail, sendOrderMail&lt;/b&gt; 메소드로 기능을 제공하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 다 메일을 발송해야 하므로 &lt;b&gt;sendMail을 공통으로 사용&lt;/b&gt;한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;    public class EmailSender {

      public void sendWelcomeMail() {
        // 가입 환영 메일 발송 - sendMail 메소드 활용
      }

      public void sendOrderMail() {
        // 주문 내역 알림 메일 발송 - sendMail 메소드 활용
      }

      public void sendMail() {
        // 이메일 발송 메소드
      }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 날 &lt;b&gt;sendOrderMail 메소드를 수정하는 과정에서 sendMail 메소드의 내용도 변경&lt;/b&gt;이 발생하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sendMail 메소드에 변경사항이 발생해 이에 맞춰 &lt;b&gt;sendWelcomeMail 메소드도 변경이 불가피해졌다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;sendWelcomeMail 기능&lt;/span&gt;에는&lt;/b&gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;변경사항이 없는데도 변경해야 하는 불필요한 작업이 발생&lt;/b&gt;&lt;/span&gt;한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 해당 클래스에 또 다른 기능들이 있었더라면 클래스의 변경이 발생함에 따라&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;기능이 수정되지 않은 메소드들도&amp;nbsp;불필요하게 컴파일이 발생&lt;/b&gt;&lt;/span&gt;하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 하나의 클래스에서 하나의 책임만 가지고 있도록 분리해두었더라면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주문 내역 알림 메일 발송 기능을 수정하였을 때 &lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;sendWelcomeMail&lt;span&gt; 메소드의&lt;/span&gt;&lt;/span&gt; 수정은 발생하지 않았을 것이고,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;불필요한 컴파일도 발생하지 않았을 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;OCP - 개방 - 폐쇄 원칙&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클래스는 확장에는 열려있어야하며 수정에는 닫혀있어야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확장에는 열려있어야 한다는 건 새로운 요구 사항이 발생할 경우 &lt;b&gt;유연하게 기능을 확장&lt;/b&gt;할 수 있어야 한다는 내용이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정에는 닫혀있어야 한다는 건 &lt;b&gt;새로운 기능을 추가 시 기존의 코드는 수정이 발생하면 안된다&lt;/b&gt; 라는 내용이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 어떻게 유연한 기능 추가가 가능하되 기존 코드의 수정이 발생하지 않도록 할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;우린 객체 지향의 특징 중 추상화, 다형성을 활용하여 쉽게 해결할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시를 통해 더 자세히 알아보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;YG쇼핑몰에서는 결제 수단을 추가하려고 한다,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 &lt;b&gt;신용카드, 계좌이체&lt;/b&gt;만 지원하였으나 이제 &lt;b&gt;카카오페이 결제도 추가&lt;/b&gt;하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 OCP를 고려하지 않고 작성한 코드를 보자&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;    // OCP 위반 예시
    // 결제 처리기
    public class PaymentProcessor {
        public void doPayment(String type, int amount) {
            if (type.equals(&quot;CREDIT_CARD&quot;)) {
                  // 신용카드 결제 처리
                processCreditCardPayment(amount);
            } else if (type.equals(&quot;BANK_TRANSFER&quot;)) {
                // 계좌이체 결제 처리
                processBankTransferPayment(amount);
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 코드가 작성되어 있다면 우리는 매번&amp;nbsp;&lt;b&gt;if문에 조건을 추가하여 결제 수단을 추가해야&lt;/b&gt; 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 되면 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;기능이 추가될 때마다 기존 코드의 변경이 발생&lt;/b&gt;&lt;/span&gt;하며 이는 곧 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;OCP를 위반&lt;/span&gt;&lt;/b&gt;하는 것이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 추상화와 다형성을 활용하여 OCP를 준수하도록 수정해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결제 수단의 공통되는 기능을 &lt;b&gt;인터페이스로 추출하여 추상화&lt;/b&gt;를 진행하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PaymentProcessor에서 각각의 &lt;b&gt;어떤 결제 수단 구현체든지 활용할 수 있도록 다형성&lt;/b&gt;을 적용할 것이다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;    // OCP 준수 예시
    // 결제 처리를 위한 인터페이스
    public interface PaymentStrategy {
        void processPayment(int amount);
    }

    // 신용카드 결제 구현체
    public class CreditCardPayment implements PaymentStrategy {
        @Override
        public void processPayment(int amount) {
            System.out.println(&quot;신용카드 결제: &quot; + amount + &quot;원&quot;);
        }
    }

    // 계좌이체 결제 구현체
    public class BankTransferPayment implements PaymentStrategy {
        @Override
        public void processPayment(int amount) {
            System.out.println(&quot;계좌이체: &quot; + amount + &quot;원&quot;);
        }
    }

    // 결제 처리기
    public class PaymentProcessor {
        private PaymentStrategy paymentStrategy;

        public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
            this.paymentStrategy = paymentStrategy;
        }

        public void processPayment(int amount) {
            paymentStrategy.processPayment(amount);
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 새로운 결제 수단을 추가하고 싶다면 기존 코드를 수정할 필요 없이 &lt;b&gt;PaymentStrategy 인터페이스를 구현하는 새로운 클래스만 만들면 된다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;    public class KakaoPayPayment implements PaymentStrategy {
        @Override
        public void processPayment(int amount) {
            System.out.println(&quot;카카오페이 결제: &quot; + amount + &quot;원&quot;);
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 결제 시스템을 구현해두면 우리는 &lt;b&gt;기존 코드의 수정 없이 새로운 결제 방식을 얼마든지 추가할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;LSP - 리스코프 치환 원칙&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자식 타입은 언제나 부모 타입으로 교체될 수 있어야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브 타입은 항상 부모 타입이 가지고 있는 기능들을 동일하게 수행할 수 있어야 한다는 말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 객체 지향의 특징 중 &lt;b&gt;다형성의 오버라이딩&lt;/b&gt;을 좀 더 잘 활용하기 위한 원칙으로 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 타입으로 선언한 변수에 자식 타입의 구현체를 할당한 후 메소드를 실행하면 오버라이딩한 메소드를 실행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 각각의 구현체들이 부모와 다른 기능을 수행하게 된다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 구현체를 변경 할 때마다 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;변경되는 구현체의 동작을 매번 확인해야하며 이는 매우 비효율적&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 우리는 LSP 원칙을 준수하여 이러한 비효율적인 일이 생기지 않도록 하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시를 통해 자세히 알아보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;YG쇼핑몰에서는 &lt;b&gt;일반 고객에게는 5%&lt;/b&gt;의 기본 할인을,&lt;b&gt; VIP 고객에게는 15%&lt;/b&gt;의 기본 할인을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 아래와 같이 코드를 작성하여 구현하였다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;    // LSP 위반 예시
    class Member {
            public void login() {
                // 공통 로그인 로직
            }

        public int discount(int price) {
          return (int)(price * 0.95); // 5% 할인
        }
    }

    class VIPMember extends Member {
        @Override
        public int discount(int price) {
          return (int)(price * 0.85); // 15% 할인
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 얼핏 보기에는 문제가 없어보이나, LSP 원칙을 위반하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;부모 타입인 Member 클래스에서 5%의 할인을 제공&lt;/b&gt;하고 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 여기서 &lt;b&gt;자식 타입인 VIPMember로 구현체가 교체될 경우 15%의 할인이 적용&lt;/b&gt;되며 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;기존과 기능이 달라진다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 구현체마다 기능이 다르므로 부모 타입에 있는 기능임에도 불구하고 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;구현체를 변경할 때마다 동일하게 동작하는지 확인&lt;/b&gt;&lt;/span&gt;이 필요하게 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 LSP를 준수하도록 기능을 수정해보자.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;    // LSP 준수 예시
    abstract class Member {

      boolean login() {
        // 공통 로그인 로직
      }

        // 고객마다 다른 할인율은 각 클래스에서 구현
      abstract int discount(int price);
    }

    class NormalMember extends Member {

      @Override
      int discount(int price) {
        return (int) (price * 0.95); // 5% 할인
      }
    }

    class VIPMember extends Member {

      @Override
      int discount(int price) {
        return (int) (price * 0.85); // 15% 할인
      }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공통되는 기능인 login은 부모 클래스인 Member에서 관리&lt;/b&gt;하되,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경우에 따라 다른 discount 메소드는 &lt;b&gt;각 회원 타입이 자신에게 맞는 할인 정책을 구현하여 사용&lt;/b&gt;하도록 수정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 할 경우 부모 타입인 Member를 자식 타입인 NormalMember와 VIPMember가 대체하더라도 &lt;b&gt;기존 부모 타입에서 제공하는 기능은 기존과 동일하게 동작한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;ISP - 인터페이스 분리의 원칙&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인터페이스의 동작을 고려하여 인터페이스를 잘 분리해야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언뜻보면 위에서 보았던 SRP와 유사하다 볼 수 있지만 조금은 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구현체에서 사용하지 메소드를 구현하면 안된다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 사용하지 않는 메소드가 인터페이스에 있어 구현해야 한다면 &lt;b&gt;인터페이스 분리를 통해 구현체에게 필요한 메소드만 제공&lt;/b&gt;해야 한다는 내용이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드를 통해 더 자세히 이해해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UserOperations라는 인터페이스&lt;/b&gt;를 생성하여 &lt;b&gt;유저들(판매자, 구매자, 관리자)들이 사용하는 기능들을 정의&lt;/b&gt;해두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 &lt;b&gt;구매자에 대해 구현&lt;/b&gt;을 예시로 들어보자&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;    // ISP 위반 예시
    interface UserOperations {
        void purchase();      // 구매자 기능
        void sell();         // 판매자 기능
        void manageUsers();  // 관리자 기능
    }

    // 구매자 클래스 구현
    class Buyer implements UserOperations {
        @Override
        public void purchase() {
            // 구매 로직
        }

        @Override
        public void sell() {
                // 구매자에게 불필요한 로직
            // Do nothing
        }

        @Override
        public void manageUsers() {
                // 구매자에게 불필요한 로직
            // Do nothing
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 하나의 인터페이스에서 유저의 모든 동작을 관리할 경우 위와 같이 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;구매자에게 불필요한 메소드까지도 구현&lt;/span&gt;&lt;/b&gt;을 해두어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서 sell이나 managerUsers 메소드가 변경사항이라도 생긴다면 Buyer는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;이 메소드를 사용하지도 않음에도 다시 빌드되거나 영향을 받을 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 ISP를 준수하도록 인터페이스를 분리해보자.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;    // ISP 준수 예시
    interface BuyerOperations {
        void purchase();
    }

    interface SellerOperations {
        void sell();
    }

    interface AdminOperations {
        void manageUsers();
    }

    // 구매자는 필요한 기능만 구현
    class Buyer implements BuyerOperations {
        @Override
        public void purchase() {
            // 구매 로직
        }
    }

    // 판매자는 필요한 기능만 구현
    class Seller implements SellerOperations {
        @Override
        public void sell() {
            // 판매 로직
        }
    }

    // 관리자는 필요한 기능만 구현
    class Admin implements AdminOperations {
        @Override
        public void manageUsers() {
            // 사용자 관리 로직
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 인터페이스를 분리하면 구현체들은 &lt;b&gt;자신에 유형에 맞는 기능만 구현&lt;/b&gt;하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 코드의 &lt;b&gt;가독성과 유지보수성이 향상&lt;/b&gt;된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;DIP - 의존성 역전의 원칙&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다른 클래스에 의존할 경우 추상화에 의존해야하며 구현체에 의존하면 안된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 하다보면 다른 클래스에 있는 기능이 필요할 때 다른 클래스를 참조(의존)하여 메소드를 불러올 일이 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 다른 클래스를 직접 참조하는 것이 아닌 그의&lt;b&gt; 추상화(추상 클래스 or 인터페이스)를 참조해야 한다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 구현체가 아닌 추상화를 참조해야할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;구현체는 변동성이 크기 &lt;/b&gt;때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 기능에 변경이 발생하여 다른 구현체를 참조해야하는 경우가 발생한다 생각해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 경우 구현체에 의존하고 있다면 우리는 다른 구현체를 바라보도록&lt;b&gt;&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;코드의 변경도 발생&lt;/span&gt;&lt;/b&gt;할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;허나 추상화에 의존하고 있다면 같은 &lt;b&gt;추상화를 구현하는 구현체로는 얼마든지 치환이 가능하다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결제 상황을 예시로 들어 확인해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 우리 쇼핑몰은 &lt;b&gt;신용카드 결제만 지원&lt;/b&gt;하여 결제를 담당하는 클래스인 PaymentProcessor를 아래와 같이 구현해두었다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;    // DIP 위반 예시
    // 신용카드 결제 구현체
    public class CreditCardPayment implements PaymentStrategy {
        @Override
        public void processPayment(int amount) {
            System.out.println(&quot;신용카드 결제: &quot; + amount + &quot;원&quot;);
        }
    }

    // 결제 처리기
    public class PaymentProcessor {
        // 신용카드 결제 구현체에 의존
        private CreditCardPayment paymentStrategy;

        public void setPaymentStrategy(CreditCardPayment paymentStrategy) {
            this.paymentStrategy = paymentStrategy;
        }

        public void processPayment(int amount) {
            paymentStrategy.processPayment(amount);
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서 계좌 이체를 통한 결제까지 지원하려한다면 계좌 결제 처리를 진행하는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;별도의 결제 처리기를 또 추가해야하는 작업이 발생할 것이다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;허나 구현체가 아닌 &lt;b&gt;인터페이스(추상화)에 의존&lt;/b&gt;을 하고 있었다면 어떻게 되었을까?&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;    // DIP 준수 예시
    // 결제 처리를 위한 인터페이스
    public interface PaymentStrategy {
        void processPayment(int amount);
    }

    // 신용카드 결제 구현체
    public class CreditCardPayment implements PaymentStrategy {
        @Override
        public void processPayment(int amount) {
            System.out.println(&quot;신용카드 결제: &quot; + amount + &quot;원&quot;);
        }
    }

    // 결제 처리기
    public class PaymentProcessor {
        private PaymentStrategy paymentStrategy;

        public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
            this.paymentStrategy = paymentStrategy;
        }

        public void processPayment(int amount) {
            paymentStrategy.processPayment(amount);
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 결제 처리기에서 인터페이스를 바라보고 있기 때문에 우리는 &lt;b&gt;해당 인터페이스를 구현하는 계좌 결제 구현체를 구현한 후 주입&lt;/b&gt;해주기만 한다면 무리없이 계좌 결제까지 처리할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;    // 계좌이체 결제 구현체
    public class BankTransferPayment implements PaymentStrategy {
        @Override
        public void processPayment(int amount) {
            System.out.println(&quot;계좌이체: &quot; + amount + &quot;원&quot;);
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고자료&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tech.kakaobank.com/posts/2411-solid-truth-or-myths-for-developers/&quot;&gt;https://tech.kakaobank.com/posts/2411-solid-truth-or-myths-for-developers/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/OOP-&quot;&gt;https://inpa.tistory.com/entry/OOP-&lt;/a&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%9D%98-5%EA%B0%80%EC%A7%80-%EC%9B%90%EC%B9%99-SOLID#%EB%8B%A8%EC%9D%BC_%EC%B1%85%EC%9E%84_%EC%9B%90%EC%B9%99_-_srp_single_responsibility_principle&quot;&gt; -객체-지향-설계의-5가지-원칙-SOLID#단일_책임_원칙_-_srp_single_responsibility_principle&lt;/a&gt;&lt;/p&gt;</description>
      <category>BackEnd/Java</category>
      <author>Potwings</author>
      <guid isPermaLink="true">https://potwings.tistory.com/71</guid>
      <comments>https://potwings.tistory.com/71#entry71comment</comments>
      <pubDate>Tue, 31 Dec 2024 23:57:11 +0900</pubDate>
    </item>
    <item>
      <title>AI의 두 번의 겨울 - XOR 문제, 기울기 소실 문제</title>
      <link>https://potwings.tistory.com/70</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;ChatGPT 등장 이후 지금까지 AI는 뜨거운 화두이다. 이전까지는 상상도하지 못했던 것들이 AI를 활용하여 가능해졌고 하루가 멀다하고 새로운 역할을 하는 AI는 출시되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;AI는 이떻게 이렇게 발전하게 되었을까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인공지능의 탄생&lt;/h2&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;초기의 인공신경망은 단순 on/off 기능의 인공 신경을 그물망 형태로 연결하면 뇌에서 동작하는 간단한 기능을 흉내낼 수 있다는 이론에서 시작되었다.이 신경망 내에서 반복적인 시그널이 발생할 때&lt;b&gt; 신경 세포들은 그 시그널을 기억하는 학습 효과&lt;/b&gt;가 있음을 바탕으로 최&lt;b&gt;초의 퍼셉트론이 탄생&lt;/b&gt;하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI의 첫번째 겨울&lt;/h2&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;허나 퍼셉트론을 통해 AND와 OR 같은 선형 분리가 가능한 문제는 해결할 수 있었지만 XOR 문제에는 적용할 수 없다는 것이 밝혀졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이로 인해 인공지능은 첫번째 겨울을 맞이하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;XOR 문제란?&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;선형 모델을 활용하여 OR, AND 문제는 해결할 수 있으나 XOR 문제를 해결할 수 없는 문제를 말한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1811&quot; data-origin-height=&quot;573&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biH0TR/btsKodgo2w2/QwDHFJlkTfqTmsgxXp2NK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biH0TR/btsKodgo2w2/QwDHFJlkTfqTmsgxXp2NK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biH0TR/btsKodgo2w2/QwDHFJlkTfqTmsgxXp2NK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiH0TR%2FbtsKodgo2w2%2FQwDHFJlkTfqTmsgxXp2NK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1811&quot; height=&quot;573&quot; data-origin-width=&quot;1811&quot; data-origin-height=&quot;573&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;해결 방안&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이후 발견된 XOR 문제의 한계를 극복하는 방법은 바로 좌표 평면 자체에 변화를 주는 것이다. 그러기 위해서는 두 개의 퍼셉트론을 한 번에 계산할 수 있어야 한다. &lt;b&gt;다층 퍼셉트론&lt;/b&gt;을 이용하면 &lt;b&gt;은닉층으로 두개의 퍼셉트론을 한번에 계산&lt;/b&gt;할 수 있게 되고 은닉층에서 공간을 외곡해 두 영역을 구분하는 직선을 구할 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI의 두번째 겨울&lt;/h2&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;다중 퍼셉트론과 역전파의 등장으로 XOR 문제가 해결되었으나 신경망이 적용될 수 있는 범위는 한정적이었다. 크고 복잡한 데이터를 다루려면 은닉층을 여러개 연결해야 하는데 이 둘만을 활용해서는 한계가 보이기 시작했다. 또 &lt;b&gt;기울기 소실 문제&lt;/b&gt;도 발생하게 되며 AI의 두번째 겨울이 시작된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;기울기 소실 문제란?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;역전파 과정에서&lt;b&gt; 출력층에서 멀어질 수록 기울기 값이 매우 작아지는 현상&lt;/b&gt;을 말한다. 이는 활성화 함수의 기울기와 관련이 깊은데 그 중 sigmoid함수를 예시로 들어 자세히 알아보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;아래는 sigmoid 함수의 그래프와 그의 도함수의 그래프 이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1565&quot; data-origin-height=&quot;855&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEunuw/btsKoqmit8G/JpQtKZMCVGKdE6iPqQYuZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEunuw/btsKoqmit8G/JpQtKZMCVGKdE6iPqQYuZ0/img.png&quot; data-alt=&quot;sigmoid 함수&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEunuw/btsKoqmit8G/JpQtKZMCVGKdE6iPqQYuZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEunuw%2FbtsKoqmit8G%2FJpQtKZMCVGKdE6iPqQYuZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;412&quot; height=&quot;225&quot; data-origin-width=&quot;1565&quot; data-origin-height=&quot;855&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;sigmoid 함수&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1748&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsIhdF/btsKoQE0efG/Baz6b0sQXuPxNDRnKdXkqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsIhdF/btsKoQE0efG/Baz6b0sQXuPxNDRnKdXkqK/img.png&quot; data-alt=&quot;sigmoid의 도함수&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsIhdF/btsKoQE0efG/Baz6b0sQXuPxNDRnKdXkqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsIhdF%2FbtsKoQE0efG%2FBaz6b0sQXuPxNDRnKdXkqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;525&quot; height=&quot;122&quot; data-origin-width=&quot;1748&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;sigmoid의 도함수&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;미분 그래프를 확인해보면 sigmoid 함수의 기울기가 최대 0.25이고 최소가 0으로 수렴한다. 역전파에서 입력층에 가까운 앞쪽의 layer로 갈수록 &lt;b&gt;기울기의 값은 거의 0에 가깝게 작아져 가중치 업데이트가 정상적으로 진행되지 않는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;해결방안&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이를 해결하기 위해 &lt;b&gt;ReLU 함수&lt;/b&gt;가 등장하였다. ReLU 함수와 그의 도함수 그래프는 아래와 같은데&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;367&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccOQdz/btsKoL4Xbu2/vbFjIXw8oUyKnoP0hRmrik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccOQdz/btsKoL4Xbu2/vbFjIXw8oUyKnoP0hRmrik/img.png&quot; data-alt=&quot;ReLU 함수 그래프&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccOQdz/btsKoL4Xbu2/vbFjIXw8oUyKnoP0hRmrik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccOQdz%2FbtsKoL4Xbu2%2FvbFjIXw8oUyKnoP0hRmrik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;472&quot; height=&quot;268&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;367&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ReLU 함수 그래프&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1mBjm/btsKo9YBLTw/vANyTQnXJYObDRZTEIKVB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1mBjm/btsKo9YBLTw/vANyTQnXJYObDRZTEIKVB0/img.png&quot; data-alt=&quot;ReLU의 도함수 그래프&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1mBjm/btsKo9YBLTw/vANyTQnXJYObDRZTEIKVB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1mBjm%2FbtsKo9YBLTw%2FvANyTQnXJYObDRZTEIKVB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;479&quot; height=&quot;173&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ReLU의 도함수 그래프&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;ReLU 함수를 활성화 함수로 사용&lt;/b&gt;하면 입력값이 0 이상인 경우 &lt;b&gt;기울기가 0이 되지 않으므로 기울기 소실 문제를 해결&lt;/b&gt;할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;현대의 딥러닝&lt;/h2&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이후 2000년대에 이르러 제프리 힌튼의 Deep Belief Network를 기반으로 비지도학습 방법이 가능해진다. 이로 인해&lt;b&gt; 딥러닝이라는 방법론이 인공신경망이라는 명칭을 대체하며&lt;/b&gt; 유일하게 여겨지는 방법론으로 칭해진다.&amp;nbsp;그 이후 Deep-CNN은 이미지 인식 성능 평가에서 2011년에는 26%의 인식 오류율을 4년만에 3.5%로 개선하는 놀라운 성과를 보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이런 가능성이 나타나자 구글이 딥마인드 사를 인수하며 시장에 뛰어들었고, &lt;b&gt;알파고를 개발하여 딥러닝이 많은 사람들에게 알려지게 되었다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;그 후 2017년 구글에서는 트랜스포머를 출시하는데, &lt;b&gt;트랜스포머는 자연어 처리 분야에서 혁신적인 변화&lt;/b&gt;를 가져오게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이와 관련된 내용은 아래 글에서 자세히 알아보도록 하자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;[작성 후 링크 추가 예정]&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;참고 자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;-&amp;nbsp;&lt;a href=&quot;https://www.samsungsds.com/kr/insights/091517_cx_cvp3.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.samsungsds.com/kr/insights/091517_cx_cvp3.html&lt;/a&gt;&lt;br /&gt;-&amp;nbsp;&lt;a href=&quot;https://www.letr.ai/ko/blog/story-20211029-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.letr.ai/ko/blog/story-20211029-1&lt;/a&gt;&lt;br /&gt;-&amp;nbsp;&lt;a href=&quot;https://www.letr.ai/ko/blog/story-20211105-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.letr.ai/ko/blog/story-20211105-1&lt;/a&gt;&lt;/p&gt;</description>
      <category>머신러닝</category>
      <author>Potwings</author>
      <guid isPermaLink="true">https://potwings.tistory.com/70</guid>
      <comments>https://potwings.tistory.com/70#entry70comment</comments>
      <pubDate>Tue, 29 Oct 2024 19:31:58 +0900</pubDate>
    </item>
    <item>
      <title>어노테이션은 사실 깡통이다.</title>
      <link>https://potwings.tistory.com/69</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이전글 :&amp;nbsp;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;a href=&quot;https://potwings.tistory.com/66&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://potwings.tistory.com/66&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1726885740806&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Spring MVC] Filter에서 파라미터 처리하는 방법&quot; data-og-description=&quot;Servlet의 Filter를 구현하는 Filter클래스에서 POST로 전달되는 Body에서 JSON을 불러와 처리해야할 일이 있었다.따라서 Filter에서 아래와 같이 Request에서 값을 불러왔다. @Override public void doFilter(ServletRequ&quot; data-og-host=&quot;potwings.tistory.com&quot; data-og-source-url=&quot;https://potwings.tistory.com/66&quot; data-og-url=&quot;https://potwings.tistory.com/66&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/yzOIp/hyW21au9nh/ZnKaU4e3przYE6wBzpsZMK/img.png?width=800&amp;amp;height=525&amp;amp;face=0_0_800_525,https://scrap.kakaocdn.net/dn/j4wcu/hyW6CmAMTK/7zXzSZpYL0ANLinUOAzrN0/img.png?width=800&amp;amp;height=525&amp;amp;face=0_0_800_525,https://scrap.kakaocdn.net/dn/18Hsl/hyW2RFH5gc/lrEgEXmle6Gy6Q3asJ3oZk/img.png?width=1793&amp;amp;height=756&amp;amp;face=0_0_1793_756&quot;&gt;&lt;a href=&quot;https://potwings.tistory.com/66&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://potwings.tistory.com/66&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/yzOIp/hyW21au9nh/ZnKaU4e3przYE6wBzpsZMK/img.png?width=800&amp;amp;height=525&amp;amp;face=0_0_800_525,https://scrap.kakaocdn.net/dn/j4wcu/hyW6CmAMTK/7zXzSZpYL0ANLinUOAzrN0/img.png?width=800&amp;amp;height=525&amp;amp;face=0_0_800_525,https://scrap.kakaocdn.net/dn/18Hsl/hyW2RFH5gc/lrEgEXmle6Gy6Q3asJ3oZk/img.png?width=1793&amp;amp;height=756&amp;amp;face=0_0_1793_756');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring MVC] Filter에서 파라미터 처리하는 방법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Servlet의 Filter를 구현하는 Filter클래스에서 POST로 전달되는 Body에서 JSON을 불러와 처리해야할 일이 있었다.따라서 Filter에서 아래와 같이 Request에서 값을 불러왔다. @Override public void doFilter(ServletRequ&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;potwings.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이전 글에서 Request에서 JSON으로 된 파라미터를 불러올 때 아래와 직접 코드 작성하여 파라미터를 받아왔었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;허나 평소에 Controller에서 JSON을 전달받을 때는 파라미터 앞에 @RequestBody를 추가해주는 것 만으로 불러올 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;따라서 &lt;b&gt;@RequestBody는 어떻게 처리&lt;/b&gt;를 해주길래 위와 같은 코드를 생략할 수 있는지 궁금해 &lt;b&gt;그 내부를 확인&lt;/b&gt;해보게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;근데 막상 열어보니 RequestBody에는 아무 내용이 없었다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1726908783034&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Target(ElementType.PARAMETER) // 적용 대상 - 파라미터
@Retention(RetentionPolicy.RUNTIME) // 컴파일러에 의해 class 파일에 기록 &amp;amp; 런타임에 유지
@Documented // javaDoc에 자동으로 기록
public @interface RequestBody {

	boolean required() default true;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #333333; text-align: start;&quot;&gt;그렇다면&amp;nbsp;내용이&amp;nbsp;아무것도&amp;nbsp;없음에도&amp;nbsp;불구하고&amp;nbsp;&lt;b&gt;@RequestBody는&amp;nbsp;어떻게&amp;nbsp;Body의&amp;nbsp;데이터를&amp;nbsp;불러와&amp;nbsp;처리해줄까?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@RequestBody는 단순 마킹 용도이다.&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이를 확인하기 위해 그 과정을 직접 디버깅 해보았다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;근데 생각보다 너무 복잡하니 궁금한 사람만 확인해보자&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;파라미터에 대한 처리를 진행하기 위해서 HandlerMethodArgumentResolverComposite의 &lt;b&gt;resolveArgument &lt;/b&gt;를 진행한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727001513410&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;  @Nullable
  public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter); // 사용할 resolver 탐색
    if (resolver == null) {
      throw new IllegalArgumentException(&quot;Unsupported parameter type [&quot; + parameter.getParameterType().getName() + &quot;]. supportsParameter should be called first.&quot;);
    } else {
      return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); // 파라미터 처리
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;여기서 &lt;b&gt;getArgumentResolver&lt;/b&gt;는 &lt;b&gt;해당 파라미터를 처리할 수 있는 &lt;/b&gt;&lt;span style=&quot;background-color: #fafafa; text-align: start;&quot;&gt;&lt;b&gt;resolver를 탐색&lt;/b&gt;하는 역할을 한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727001513412&quot; class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;  @Nullable
  private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
    if (result == null) {
      Iterator var3 = this.argumentResolvers.iterator();

      while(var3.hasNext()) {
        HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
        // 해당 resolver 지원 여부 확인
        if (resolver.supportsParameter(parameter)) {
          result = resolver;
          this.argumentResolverCache.put(parameter, resolver); // 캐싱 진행
          break;
        }
      }
    }

    return result;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 메소드에서는&amp;nbsp;&lt;b&gt;파라미터의 어노테이션을 확인하여 어떤 resolver를 통해 처리할지 결정&lt;/b&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;또 결정 후에는 캐싱 진행하여 이후의 요청에 대해선 좀 더 빠르게 처리할 수 있게 해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;RequestBody는 RequestResponseBodyMethodProcessor를 통하여 처리되는데 아래와 같이&lt;b&gt; 파라미터에 RequestBody가 있는지&lt;/b&gt;를 확인한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727001513414&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;  public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(RequestBody.class);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;사용할 resolver를 결정하였으면 이제 resolver의&lt;b&gt; resolveArgument&lt;/b&gt; 메소드를 진행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;우리는 RequestBody에 대해 확인하고 있으므로 그 resolver인 &lt;b&gt;RequestResponseBodyMethodProcessor의 resolveArgument&lt;/b&gt;메소드를 확인해보자&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727001513415&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;  public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    parameter = parameter.nestedIfOptional();
    // 파라미터에 대한 처리 진행
    Object arg = this.readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
    String name = Conventions.getVariableNameForParameter(parameter);
    
    //... 이하 생략
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; background-color: #fafafa; text-align: start;&quot;&gt;여기서는 파라미터들만 확인 후 실제 파라미터에 대한 처리는 readWithMessageConverters에서 진행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이제 실제 처리를 위해&amp;nbsp;&lt;b&gt;contentType(application/json)과 targetClass(변환할 자바 클래스)를 확인&lt;/b&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그리고 resolver를 찾을 때와 동일하게 &lt;b&gt;지원하는 converter를 결정 후 변환을 진행&lt;/b&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727001513416&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;  @Nullable
  protected &amp;lt;T&amp;gt; Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

	//... 생략

        HttpMessageConverter converter;
        Class converterType;
        GenericHttpMessageConverter genericConverter;
        while(true) {
          if (!var11.hasNext()) {
            break label94;
          }

          converter = (HttpMessageConverter)var11.next();
          converterType = converter.getClass();
          genericConverter = converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter)converter : null;
          // converter 확인
          if (genericConverter != null) {
            if (genericConverter.canRead(targetType, contextClass, contentType)) {
              break;
            }
          } else if (targetClass != null &amp;amp;&amp;amp; converter.canRead(targetClass, contentType)) {
            break;
          }
        }

        if (message.hasBody()) {
          HttpInputMessage msgToUse = this.getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
		  // Converter를 통한 변환 진행
          body = genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) : converter.read(targetClass, msgToUse);
          body = this.getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
        } else {
          body = this.getAdvice().handleEmptyBody((Object)null, message, parameter, targetType, converterType);
        }

		// ...생략
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이와 같은 처리를 통해서 @RequestBody는 전달받은 json을 자바 객체로 변환하여준다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;간단히 설명하자면 파라미터에 &lt;b&gt;@RequestBody가 존재하면 해당 파라미터는 변환이 필요하다 체크&lt;/b&gt;를 해둔 후&lt;b&gt; resolver를 통하여 데이터 변환을 진행&lt;/b&gt;해주는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;즉 @RequestBody는&lt;/b&gt;&lt;b&gt;&amp;nbsp;실제 처리를 하는 것이 아닌 &quot;이 파라미터 처리 부탁드려요~&quot; 하는 역할로 보면 되겠다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;그럼 다른 어노테이션은?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;java 공식문서의 어노테이션 부분을 확인해보면 (&lt;a href=&quot;https://docs.oracle.com/javase/tutorial/java/annotations/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/javase/tutorial/java/annotations/)&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tW51o/btsJHoiX0sa/9U61SrHM5rTCNbZrkP28M0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tW51o/btsJHoiX0sa/9U61SrHM5rTCNbZrkP28M0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tW51o/btsJHoiX0sa/9U61SrHM5rTCNbZrkP28M0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtW51o%2FbtsJHoiX0sa%2F9U61SrHM5rTCNbZrkP28M0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1320&quot; height=&quot;86&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left; font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&quot;어노테이션은 주석이 달려있는 코드에 직접적으로 영향을 미치지 않는다.&quot;&lt;/b&gt; 라고 나와있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left; font-family: 'Noto Sans Light';&quot;&gt;모든 어노테이션을 확인해보진 못했으나 lombok 같은 경우도 실제 동작하는 내용은 없었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nUbxo/btsJHAXXqt2/k8V878eAp1HMkxLo5lwVVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nUbxo/btsJHAXXqt2/k8V878eAp1HMkxLo5lwVVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nUbxo/btsJHAXXqt2/k8V878eAp1HMkxLo5lwVVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnUbxo%2FbtsJHAXXqt2%2Fk8V878eAp1HMkxLo5lwVVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;590&quot; height=&quot;358&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;어노테이션은 직접 동작하는 주체가 아닌 다른 주체(컴파일러 등등)에게 동작을 요청하는 지시자로 생각하면 되겠다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>BackEnd/Spring</category>
      <author>Potwings</author>
      <guid isPermaLink="true">https://potwings.tistory.com/69</guid>
      <comments>https://potwings.tistory.com/69#entry69comment</comments>
      <pubDate>Sun, 22 Sep 2024 19:48:17 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot main 2배 버그 팝니다@@@@@</title>
      <link>https://potwings.tistory.com/68</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;최근 cli로 된 어플리케이션을 개발하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;어플리케이션 실행 후 사용할 DB 정보를 &lt;/span&gt;입력을 통하여 받고 싶었으나 Spring의 경우 DataSource가 우선 생성되어야 어플리케이션이 실행될 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Spring 구동 이전의 main 메소드에서 DB 정보를 받아오도록 아래와 같이 진행하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726644501498&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  public static void main(String[] args) {
    try {

      br = new BufferedReader(new InputStreamReader(System.in));

      String dbUrl = validDBURL(); // IP PORT 연결 테스트
      String schema = validInputString(&quot;DB 스키마&quot;);
      jdbcUrl = &quot;jdbc:mysql://&quot; + dbUrl + &quot;/&quot; + schema;
      dbUser = validInputString(&quot;DB 계정&quot;);
      dbPassword = validInputString(&quot;DB 패스워드&quot;);

      new SpringApplicationBuilder(MainDoubleApplication.class)
          .sources(DataSourceConfig.class)
          .run(args);
    } catch (Exception e) {
      System.err.println(&quot;Error: &quot; + e.getMessage() + e);
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;허나 해당 어플리케이션을 실행해보니&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Main 메소드의 내용이 2번 실행&lt;/span&gt; 후 Spring이 실행&lt;/b&gt;되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2336&quot; data-origin-height=&quot;1343&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQyi2G/btsJDOB54OO/fubROkfLt3m8B4PRsuKzAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQyi2G/btsJDOB54OO/fubROkfLt3m8B4PRsuKzAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQyi2G/btsJDOB54OO/fubROkfLt3m8B4PRsuKzAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQyi2G%2FbtsJDOB54OO%2FfubROkfLt3m8B4PRsuKzAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2336&quot; height=&quot;1343&quot; data-origin-width=&quot;2336&quot; data-origin-height=&quot;1343&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색을 해보니 매번 아무생각없이 추가한 &lt;b&gt;spring-boot-devtools&lt;/b&gt;로 인해 main이 두번 실행되고 있던 것이었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1393&quot; data-origin-height=&quot;216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgA4LC/btsJDHiPE7G/vsoVBlyzFlLlrTnGtd3JPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgA4LC/btsJDHiPE7G/vsoVBlyzFlLlrTnGtd3JPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgA4LC/btsJDHiPE7G/vsoVBlyzFlLlrTnGtd3JPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgA4LC%2FbtsJDHiPE7G%2FvsoVBlyzFlLlrTnGtd3JPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1393&quot; height=&quot;216&quot; data-origin-width=&quot;1393&quot; data-origin-height=&quot;216&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 의존성을 제거하니 정상적으로 Main 메소드 한번 실행 후 Spring이 실행되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 spring-boot-devtools는 왜 main 메소드를 두번 실행할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이는 &lt;b&gt;devtools의 핵심 기능인 Automatic Restart 기능&lt;/b&gt;을 위해서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; ※ Automatic Restart&lt;span&gt; - 서비스 기동 중 코드 변경사항 반영&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;devtools 공식 문서를 보면 이렇게 나와있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/reference/using/devtools.html#using.devtools.restart.restart-vs-reload&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-boot/reference/using/devtools.html#using.devtools.restart.restart-vs-reload&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1163&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJfeKE/btsJDWGFoYk/7HKxfk5IjFqJIl2vqMQHkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJfeKE/btsJDWGFoYk/7HKxfk5IjFqJIl2vqMQHkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJfeKE/btsJDWGFoYk/7HKxfk5IjFqJIl2vqMQHkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJfeKE%2FbtsJDWGFoYk%2F7HKxfk5IjFqJIl2vqMQHkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1163&quot; height=&quot;277&quot; data-origin-width=&quot;1163&quot; data-origin-height=&quot;277&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면 아래와 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;devtools 사용 시 두 개의 클래스 로더를 사용하여 어플리케이션이 기동된다.&lt;br /&gt;base&amp;nbsp;클래스로더&amp;nbsp;-&amp;nbsp;변경이&amp;nbsp;발생하지&amp;nbsp;않는&amp;nbsp;클래스&amp;nbsp;관리&amp;nbsp;(ex.&amp;nbsp;외부&amp;nbsp;jar파일)&lt;br /&gt;restart&amp;nbsp;클래스로더&amp;nbsp;-&amp;nbsp;개발을&amp;nbsp;진행하면서&amp;nbsp;변경되는&amp;nbsp;클래스&amp;nbsp;관리&lt;br /&gt;&lt;br /&gt;이후 클래스 변경이 일어나면 기존 restart 클래스로더가 변경된 클래스를 반영한 후 소멸된다. 그 후 새 restart 클래스로더가 생성된다.&amp;nbsp;&lt;br /&gt;이렇게 하면 base 클래스로더에서 관리하는 변경사항이 발생하지 않는 클래스들은 다시 로딩하지 않으므로 restart 시간이 줄어든다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;따라서&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;빠른 Automatic Restart&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;&amp;nbsp;진행을 위해&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;&amp;nbsp;어플리케이션 기동 시 두 개의 클래스 로더를 생성&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;하고 이로 인해&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;&amp;nbsp;main 메소드가 두번 실행&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;되는 것이다.&lt;/span&gt; &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;main 메소드가 실행되는 것을 보기 위해 실행 시 &quot;Main Method Run&quot;을 출력하도록 해두었고&lt;/p&gt;
&lt;pre id=&quot;code_1726656383876&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootApplication
public class MainDoubleApplication {

  public static void main(String[] args) {
    System.out.println(&quot;================== Main Method Run ==================&quot;);
    new SpringApplicationBuilder(MainDoubleApplication.class)
        .run(args);
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Controller에서는 &quot;/test&quot; path호출 시 before change를 반환하도록 해두었다.&lt;/p&gt;
&lt;pre id=&quot;code_1726651092876&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
public class TestController {

  @GetMapping(&quot;/test&quot;)
    public String test() {
        return &quot;before change&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 실행 후 &quot;/test&quot; 호출 시는 결과는 아래와 같았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;326&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ebTczZ/btsJE2F7Rw2/uJCjCuqgIexNt9KWBHkAI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ebTczZ/btsJE2F7Rw2/uJCjCuqgIexNt9KWBHkAI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ebTczZ/btsJE2F7Rw2/uJCjCuqgIexNt9KWBHkAI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FebTczZ%2FbtsJE2F7Rw2%2FuJCjCuqgIexNt9KWBHkAI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;326&quot; height=&quot;100&quot; data-origin-width=&quot;326&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 Controller에서 반환하는 내용을 변경하고 기다려 보니 console 창에는 아래와 같은 로그가 발생하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2642&quot; data-origin-height=&quot;1240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmfVB6/btsJEOg25RE/Ak61Paogxh5M5a1s2jl3q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmfVB6/btsJEOg25RE/Ak61Paogxh5M5a1s2jl3q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmfVB6/btsJEOg25RE/Ak61Paogxh5M5a1s2jl3q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmfVB6%2FbtsJEOg25RE%2FAk61Paogxh5M5a1s2jl3q0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2642&quot; height=&quot;1240&quot; data-origin-width=&quot;2642&quot; data-origin-height=&quot;1240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Main Method Run이 출력되는 걸 보면 &lt;b&gt;새 restart 클래스로더를 생성하기 위해 main 메소드가 다시 실행&lt;/b&gt;되었다는 것을 알 수 있었고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 &quot;/test&quot;를 호출해보니 &lt;b&gt;Controller의 변경된 내용이 반영&lt;/b&gt;되어있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;305&quot; data-origin-height=&quot;103&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UnaLp/btsJDxHvigX/5gvDjaFsDkdjYzqg2FbuVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UnaLp/btsJDxHvigX/5gvDjaFsDkdjYzqg2FbuVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UnaLp/btsJDxHvigX/5gvDjaFsDkdjYzqg2FbuVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUnaLp%2FbtsJDxHvigX%2F5gvDjaFsDkdjYzqg2FbuVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;305&quot; height=&quot;103&quot; data-origin-width=&quot;305&quot; data-origin-height=&quot;103&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring-boot-devtools에서는&lt;b&gt; 빠른 Automatic Restart 진행을 위해&lt;/b&gt; 어플리케이션 기동 시 &lt;b&gt;두 개의 클래스 로더를 생성&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 클래스 로더 생성 시 main 메소드를 실행해야하고 이로 인해 main 메소드가 두 번 실행&lt;/b&gt;되는 것이다.&lt;/p&gt;</description>
      <category>BackEnd/Spring</category>
      <author>Potwings</author>
      <guid isPermaLink="true">https://potwings.tistory.com/68</guid>
      <comments>https://potwings.tistory.com/68#entry68comment</comments>
      <pubDate>Wed, 18 Sep 2024 21:21:11 +0900</pubDate>
    </item>
    <item>
      <title>텍스트 전처리(1) - 토큰화, 정제, 정규화</title>
      <link>https://potwings.tistory.com/67</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;토큰화&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;주어진 문장에서의미 부여가 가능한 단위를 찾는 것&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;단순 공백 기준으로 잘라낼 경우 아래와 같은 &lt;b&gt;이슈&lt;/b&gt;가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;단어나 숫자에 특수 문자가 들어가는 경우 존재 (ex. AT&amp;amp;T, $45.55)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;줄임말과 단어 내에 띄어쓰기가 있는 경우 존재 (ex. We&amp;rsquo;re, I&amp;rsquo;m)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이와 같은 이유로 인해 섬세한 알고리즘이 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;품사 태깅&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;단어가 어떤 품사로 사용되었는지 구분해놓는 것. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;토큰화 후 진행 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;어떤 품사로 쓰였는지에 따라 단어의 의미가 달라질 수 있어 진행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ex) fly - [동사 : 날다], [명사 : 파리]&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실습&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;영어 - NLTK 라이브러리 활용&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;from nltk.tokenize import word_tokenize
from nltk.tag import pos_tag

text = &quot;I'd like to stay at home all day long. I love my house.&quot;
tokenized_sentence = word_tokenize(text)

print('단어 토큰화 :',tokenized_sentence)
print('품사 태깅 :',pos_tag(tokenized_sentence))
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;## 실행 결과 ##
단어 토큰화 : ['I', &quot;'d&quot;, 'like', 'to', 'stay', 'at', 'home', 'all', 'day', 'long', '.', 'I', 'love', 'my', 'house', '.']
품사 태깅 : [('I', 'PRP'), (&quot;'d&quot;, 'MD'), ('like', 'VB'), ('to', 'TO'), ('stay', 'VB'), ('at', 'IN'), ('home', 'NN'), ('all', 'DT'), ('day', 'NN'), ('long', 'RB'), ('.', '.'), ('I', 'PRP'), ('love', 'VBP'), ('my', 'PRP$'), ('house', 'NN'), ('.', '.')]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;한글 토큰화와 그 문제점&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;한글화 진행 시에는 토큰화에 더 큰 문제점이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 한글은 조사, 어미 등을 붙여서 말을 만드는 교착어이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;예를 들어 한국어의 &amp;lsquo;그녀(she/her)&amp;rsquo;라는 주어나 목적어가 들어간 문장이 있다고 해보자. 이 경우 단어에 &amp;lsquo;그녀가&amp;rsquo;, &amp;lsquo;그녀에게&amp;rsquo;, &amp;lsquo;그녀를&amp;rsquo; 과 같이 다양한 조사가 붙을 것이다. 같은 단어임에도 서로 다른 조사가 붙어 다른 단어로 인식이 되면 자연어 처리가 힘들고 번거로워지는 경우가 많다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;따라서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;한국어 자연어 처리에선 조사를 분리&lt;/b&gt;해줄 필요가 있고 이를 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;형태소 기준 토큰화&lt;/b&gt;를 진행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 한국어는 띄어쓰기가 영어보다 잘 지켜지지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Tobeornottobethatisthequestion&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;영어는 띄어쓰기가 없으면 쉽게 알아보기 힘들다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;제가이렇게띄어쓰기를전혀하지않고글을썼다고하더라도글을이해할수있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;반면 한국어는 띄어쓰기가 되지 않아도 어느 정도 내용을 알아볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이로 인해 띄어쓰기가 무시되는 경우가 많아져&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;자연어 처리에 어려움을 겪는다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #333333; text-align: start;&quot;&gt;KoNLPy 라이브러리 활용하여 진행하며 &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;KoNLPy 형태소 분석기들은 공통적으로 아래 메소드 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;morphs : 형태소 추출&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;pos : 품사 태깅(Part-of-speech tagging)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;nouns : 명사 추출&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;from konlpy.tag import Okt

okt = Okt()
text = &quot;아 퇴근하고 싶다. 월요일부터 집에 가고 싶다니 완전 럭키비키잖아&quot;

# OKT 형태소 분석기 사용
print('OKT 형태소 분석 :',okt.morphs(text))
print('OKT 품사 태깅 :',okt.pos(text))
print('OKT 명사 추출 :',okt.nouns(text))
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;## 실행 결과 ##
OKT 형태소 분석 : ['아', '퇴근', '하고', '싶다', '.', '월요일', '부터', '집', '에', '가고', '싶다니', '완전', '럭키', '비키잖아']
OKT 품사 태깅 : [('아', 'Exclamation'), ('퇴근', 'Noun'), ('하고', 'Josa'), ('싶다', 'Verb'), ('.', 'Punctuation'), ('월요일', 'Noun'), ('부터', 'Josa'), ('집', 'Noun'), ('에', 'Josa'), ('가고', 'Verb'), ('싶다니', 'Verb'), ('완전', 'Noun'), ('럭키', 'Noun'), ('비키잖아', 'Verb')]
OKT 명사 추출 : ['퇴근', '월요일', '집', '완전', '럭키']
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;어떤 형태소 분석기를 사용하느냐에 따라 결과가 다르게 나온다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;필요 용도에 어떤 형태소 분석기가 가장 적절한지 판단하여 사용하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;from konlpy.tag import Kkma

kkma = Kkma()
text = &quot;아 퇴근하고 싶다. 월요일부터 집에 가고 싶다니 완전 럭키비키잖아&quot;

# 꼬꼬마 형태소 분석기 사용
print('꼬꼬마 형태소 분석 :',kkma.morphs(text))
print('꼬꼬마 품사 태깅 :',kkma.pos(text))
print('꼬꼬마 명사 추출 :',kkma.nouns(text))
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;## 실행 결과 ##
꼬꼬마 형태소 분석 : ['I', &quot;'&quot;, 'd', 'like', 'to', 'stay', 'at', 'home', 'all', 'day', 'long', '.', 'I', 'love', 'my', 'house', '.']
꼬꼬마 품사 태깅 : [('I', 'OL'), (&quot;'&quot;, 'SS'), ('d', 'OL'), ('like', 'OL'), ('to', 'OL'), ('stay', 'OL'), ('at', 'OL'), ('home', 'OL'), ('all', 'OL'), ('day', 'OL'), ('long', 'OL'), ('.', 'SF'), ('I', 'OL'), ('love', 'OL'), ('my', 'OL'), ('house', 'OL'), ('.', 'SF')]
꼬꼬마 명사 추출 : []
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;문장 토큰화&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;문장단위로 의미를 나누기 위해 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;영어 - NLTK의 from nltk.tokenize import sent_tokenize 로 진행&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;smalltalk&quot;&gt;&lt;code&gt;from nltk.tokenize import sent_tokenize

text = &quot;His barber kept his word. But keeping such a huge secret to himself was driving him crazy. Finally, the barber went up a mountain and almost to the edge of a cliff. He dug a hole in the midst of some reeds. He looked about, to make sure no one was near.&quot;
print('문장 토큰화1 :',sent_tokenize(text))
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;vbnet&quot;&gt;&lt;code&gt;## 실행 결과 ##
문장 토큰화1 : ['His barber kept his word.', 'But keeping such a huge secret to himself was driving him crazy.', 'Finally, the barber went up a mountain and almost to the edge of a cliff.', 'He dug a hole in the midst of some reeds.', 'He looked about, to make sure no one was near.']
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;한글 - KSS(Korean Sentence Splitter)를 추천&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;pip install kss # 패키지 설치

import kss

text = '딥 러닝 자연어 처리가 재미있기는 합니다. 그런데 문제는 영어보다 한국어로 할 때 너무 어렵습니다. 이제 해보면 알걸요?'
print('한국어 문장 토큰화 :',kss.split_sentences(text))
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;## 실행 결과 ##
한국어 문장 토큰화 : ['딥 러닝 자연어 처리가 재미있기는 합니다.', '그런데 문제는 영어보다 한국어로 할 때 너무 어렵습니다.', '이제 해보면 알걸요?']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정규화&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;표현 방법이 다른 단어들을 같은 단어로 통합하는 작업&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정규화 기법&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;규칙에 기반한 표기가 다른 단어들의 통합&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;같은 의미를 가지고 있으나 표기가 다른 단어들을 하나의 단어로 정규화할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ex) (US, USA), (uh-huh, uhhuh)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;대, 소문자 통합&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;대, 소문자를 통합할 경우 단어의 개수를 줄일 수 있다. 영어에서 대부분은 소문자로 작성되므로 소문자 변환 작업으로 이루어진다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;떄론 무작정 대문자와 소문자가 구분되어야하는 상황도 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;대안으로 문장의 맨앞의 단어만 대문자로 변경하고 나머지는 대,소문자 유지하는 방법이 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;표제어 추출&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단어들로부터 표제어(기본 사전형 단어)를 추출하는 행위 어간과 접사로 단어를 나누어 준다.&lt;br /&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;from nltk.stem import WordNetLemmatizer

lemmatizer = WordNetLemmatizer()

words = ['policy', 'doing', 'organization', 'have', 'going', 'love', 'lives', 'fly', 'dies', 'watched', 'has', 'starting']

print('표제어 추출 전 :',words)
print('표제어 추출 후 :',[lemmatizer.lemmatize(word) for word in words])

&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;## 실행 결과 ##
표제어 추출 전 : ['policy', 'doing', 'organization', 'have', 'going', 'love', 'lives', 'fly', 'dies', 'watched', 'has', 'starting']
표제어 추출 후 : ['policy', 'doing', 'organization', 'have', 'going', 'love', 'life', 'fly', 'dy', 'watched', 'ha', 'starting']
​&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;추출 결과에서의 dy와 ha처럼 의미를 알 수 없는 적절하지 못한 단어가 있다. 이런 경우 본래 단어의 품사 정보를 알아야 정확한 결과를 얻을 수 있다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;pre class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;print(lemmatizer.lemmatize('dies', 'v'))
print(lemmatizer.lemmatize('watched', 'v'))
print(lemmatizer.lemmatize('has', 'v'))&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;## 실행 결과 ##
die
watch
have&lt;/code&gt;&lt;/pre&gt;
&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;어간 추출&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;표제어 추출보다 일반적으로 속도가 빠르다. 그 중 &lt;b&gt;포터 어간 추출기&lt;/b&gt;는 정밀하게 설계되어 정확도가 높아 영어 자연어 처리에서 준수한 선택이다.그 외에도 NLTK에는 &lt;b&gt;랭커스터 스태머&lt;/b&gt; 알고리즘이 있다.&lt;/span&gt;&lt;/span&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;from nltk.stem import PorterStemmer
from nltk.stem import LancasterStemmer

porter_stemmer = PorterStemmer()
lancaster_stemmer = LancasterStemmer()

words = ['policy', 'doing', 'organization', 'have', 'going', 'love', 'lives', 'fly', 'dies', 'watched', 'has', 'starting']
print('어간 추출 전 :', words)
print('포터 스테머의 어간 추출 후:',[porter_stemmer.stem(w) for w in words])
print('랭커스터 스테머의 어간 추출 후:',[lancaster_stemmer.stem(w) for w in words])
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;## 실행 결과 ##
# 둘은 서로 다른 알고리즘을 사용하기 때문에 다른 결과가 나온다
어간 추출 전 : ['policy', 'doing', 'organization', 'have', 'going', 'love', 'lives', 'fly', 'dies', 'watched', 'has', 'starting']
포터 스테머의 어간 추출 후: ['polici', 'do', 'organ', 'have', 'go', 'love', 'live', 'fli', 'die', 'watch', 'ha', 'start']
랭커스터 스테머의 어간 추출 후: ['policy', 'doing', 'org', 'hav', 'going', 'lov', 'liv', 'fly', 'die', 'watch', 'has', 'start']
&lt;/code&gt;&lt;/pre&gt;
&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;허나 이런 규칙에 기반한 어간 추출은 제대로 된 일반화를 수행하지 못할 수도 있다.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ex) organization &amp;rarr; organ (완전히 다른 단어임에도 이와 같이 어간 추출이 된다)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정제&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;갖고 있는 말뭉치로부터 불필요한 데이터를 제거한다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정제 방식의 예시&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;등장 빈도가 적은 단어 제거&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;입력 받은 데이터 100000건 중에서 5번밖에 등장하지 않은 단어가 있다면 이는 직관적으로 분류에 도움이 되지 않는 데이터임을 알 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;길이가 짧은 단어 제거&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;영어에서는 길이가 짧은 단어의 대부분이 불용어라 제거하는 의미가 있다. 또 구두점들까지도 한번에 제거할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;허나 한국어에서는 크게 유효하지 않을 수 있다. 이는 영어와 한국어 단어에서 각 한 글자가 가진 의미의 크기가 다르기 때문이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;불용어 제거&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;큰 의미가 없는 단어(분석에 큰 도움이 되지 않음)를 제거한다.&lt;/span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;예시&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize 

example = &quot;Family is not an important thing. It's everything.&quot;
stop_words = set(stopwords.words('english')) 

word_tokens = word_tokenize(example)

result = []
for word in word_tokens: 
    if word not in stop_words: 
        result.append(word) 

print('불용어 제거 전 :',word_tokens) 
print('불용어 제거 후 :',result)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;## 실행 결과 ##
불용어 제거 전 : ['Family', 'is', 'not', 'an', 'important', 'thing', '.', 'It', &quot;'s&quot;, 'everything', '.']
불용어 제거 후 : ['Family', 'important', 'thing', '.', 'It', &quot;'s&quot;, 'everything', '.']
&lt;/code&gt;&lt;/pre&gt;
&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;한국어에서 불용어 제거 예시 [한국어 불용어 리스트 : &lt;a href=&quot;https://www.ranks.nl/stopwords/korean&quot;&gt;https://www.ranks.nl/stopwords/korean&lt;/a&gt;]&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;from konlpy.tag import Okt

okt = Okt()

example = &quot;고기를 아무렇게나 구우려고 하면 안 돼. 고기라고 다 같은 게 아니거든. 예컨대 삼겹살을 구울 때는 중요한 게 있지.&quot;
stop_words = &quot;를 아무렇게나 구 우려 고 안 돼 같은 게 구울 때 는&quot;

stop_words = set(stop_words.split(' '))
word_tokens = okt.morphs(example)

result = [word for word in word_tokens if not word in stop_words]

print('불용어 제거 전 :',word_tokens) 
print('불용어 제거 후 :',result)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;## 실행 결과 ##
불용어 제거 전 : ['고기', '를', '아무렇게나', '구', '우려', '고', '하면', '안', '돼', '.', '고기', '라고', '다', '같은', '게', '아니거든', '.', '예컨대', '삼겹살', '을', '구울', '때', '는', '중요한', '게', '있지', '.']
불용어 제거 후 : ['고기', '하면', '.', '고기', '라고', '다', '아니거든', '.', '예컨대', '삼겹살', '을', '중요한', '있지', '.']
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;참고 원문 :&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://wikidocs.net/21694&quot;&gt;https://wikidocs.net/21694&lt;/a&gt; &lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1725329978690&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;02. 텍스트 전처리(Text preprocessing)&quot; data-og-description=&quot;텍스트 전처리는 풀고자 하는 문제의 용도에 맞게 텍스트를 사전에 처리하는 작업입니다. 요리를 할 때 재료를 제대로 손질하지 않으면, 요리가 엉망이 되는 것처럼 텍스트에 제대로 전&amp;hellip;&quot; data-og-host=&quot;wikidocs.net&quot; data-og-source-url=&quot;https://wikidocs.net/21694&quot; data-og-url=&quot;https://wikidocs.net/21694&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bh8mbf/hyWVVuwYwf/zB4Ir9MkhMlj8bl86qMWTK/img.png?width=98&amp;amp;height=130&amp;amp;face=0_0_98_130&quot;&gt;&lt;a href=&quot;https://wikidocs.net/21694&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wikidocs.net/21694&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bh8mbf/hyWVVuwYwf/zB4Ir9MkhMlj8bl86qMWTK/img.png?width=98&amp;amp;height=130&amp;amp;face=0_0_98_130');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;02. 텍스트 전처리(Text preprocessing)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;텍스트 전처리는 풀고자 하는 문제의 용도에 맞게 텍스트를 사전에 처리하는 작업입니다. 요리를 할 때 재료를 제대로 손질하지 않으면, 요리가 엉망이 되는 것처럼 텍스트에 제대로 전&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wikidocs.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>머신러닝</category>
      <author>Potwings</author>
      <guid isPermaLink="true">https://potwings.tistory.com/67</guid>
      <comments>https://potwings.tistory.com/67#entry67comment</comments>
      <pubDate>Tue, 3 Sep 2024 10:03:04 +0900</pubDate>
    </item>
    <item>
      <title>[Spring MVC] Filter에서 파라미터 처리하는 방법</title>
      <link>https://potwings.tistory.com/66</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Servlet의 Filter를 구현하는 Filter클래스에서 POST로 전달되는 Body에서 JSON을 불러와 처리해야할 일이 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;따라서 Filter에서 아래와 같이 Request에서 값을 불러왔다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722427071843&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  
	ServletInputStream inputStream = request.getInputStream();
	ObjectMapper objectMapper = new ObjectMapper();
	PersonDTO dto = objectMapper.readValue(inputStream, PersonDTO.class);
	System.out.println(&quot;name: &quot; + dto.getName());
	chain.doFilter(request, response);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;허나 이 코드에는 문제가 있는데...&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Request의 Body는 InputStream&lt;/b&gt;으로 이루어져있어&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;한 번 데이터를 읽으면 다시 사용할 수 없다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; font-family: 'Noto Sans Light';&quot;&gt;이로 인해 Body의 값을 이미 Filter에서 한 번 사용하면&lt;b&gt;&amp;nbsp;Controller에서는&lt;/b&gt;&lt;b&gt;&amp;nbsp;정상적으로 값을 불러올 수 없었다&lt;/b&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1976&quot; data-origin-height=&quot;299&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnsdxS/btsIRnMxT9P/INgkVNIDPudocEhG1vJjI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnsdxS/btsIRnMxT9P/INgkVNIDPudocEhG1vJjI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnsdxS/btsIRnMxT9P/INgkVNIDPudocEhG1vJjI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnsdxS%2FbtsIRnMxT9P%2FINgkVNIDPudocEhG1vJjI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1976&quot; height=&quot;299&quot; data-origin-width=&quot;1976&quot; data-origin-height=&quot;299&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;letter-spacing: 0px; font-family: 'Noto Sans Light';&quot;&gt;이에 대한 해결법으로는&amp;nbsp;&lt;b&gt; Body의 값을 읽어와 저장해둔 후 여러번 사용&lt;/b&gt;하는 방법이 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;(참고 : &lt;a href=&quot;https://www.baeldung.com/spring-reading-httpservletrequest-multiple-times&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.baeldung.com/spring-reading-httpservletrequest-multiple-times&lt;/a&gt;)&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;letter-spacing: 0px; font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;HttpServletRequestWrapper을 상속받은 클래스를 구현&lt;/b&gt;하여 이 작업을 진행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;기존 Request의 Body는 한번밖에 못 불러오므로&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;해당 값을 불러와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;RequestWrapper의 클래스 변수(requestData)로 저장해둔 후 여러번 사용&lt;/b&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722509783037&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class RequestWrapper extends HttpServletRequestWrapper {

	//기존 Request의 Body 불러와 저장해두는 변수
	private String requestData;

	public RequestWrapper(HttpServletRequest request) throws IOException {
	  super(request);
	  requestData = requestDataByte(request);
	}

	private String requestDataByte(HttpServletRequest request) throws IOException {
	  ServletInputStream inputStream = request.getInputStream();
	  byte[] rawData = StreamUtils.copyToByteArray(inputStream); //Body 복제 진행
	  return new String(rawData);
	}

	@Override
	public ServletInputStream getInputStream() {
      // requestData에 있는 값 Stream으로 변환하여 return
	  ByteArrayInputStream inputStream = new ByteArrayInputStream(
	      this.requestData.getBytes(StandardCharsets.UTF_8));
	  return new ServletInputStream() {
	    @Override
	    public boolean isFinished() {
	      return inputStream.available() == 0;
	    }

	    @Override
	    public boolean isReady() {
	      return true;
	    }

	    @Override
	    public void setReadListener(ReadListener listener) {
	      throw new UnsupportedOperationException();
	    }

	    @Override
	    public int read() {
	      return inputStream.read();
	    }
	  };
	}

	@Override
	public BufferedReader getReader() {
	  return new BufferedReader(new InputStreamReader(this.getInputStream()));
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;또 Filter 작업 완료 후 이후에 Request를 사용하는 작업에서 RequestWrapper를 사용할 수 있도록&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;chain.doFilter() 시 기존 Request가 아닌 RequestWrapper를 전달해준다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1722510000874&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CommonFilter implements Filter {
	....

	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
	    FilterChain chain) throws IOException, ServletException {
	  // 필터 로직
	  HttpServletRequest httpRequest = (HttpServletRequest) request;
	  RequestWrapper requestWrapper = new RequestWrapper(httpRequest);
	  String method = httpRequest.getMethod();
	  try {
	    if (method.equals(&quot;POST&quot;)) {
	      ServletInputStream inputStream = requestWrapper.getInputStream();
	      ObjectMapper objectMapper = new ObjectMapper();
	      PersonDTO dto = objectMapper.readValue(inputStream, PersonDTO.class);
	      System.out.println(&quot;name: &quot; + dto.getName());
	    }
	  } catch (Exception e) {
	    e.printStackTrace();
	  }

	  chain.doFilter(requestWrapper, response); //requestWrapper를 전달
	}
...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #333333; text-align: start;&quot;&gt;이렇게하면 Filter와 Controller 모두에서 Request의 Body값을 불러와 사용할 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1062&quot; data-origin-height=&quot;180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mwAxd/btsISssPPFP/59CKKDMUZbUhbedSmaKlUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mwAxd/btsISssPPFP/59CKKDMUZbUhbedSmaKlUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mwAxd/btsISssPPFP/59CKKDMUZbUhbedSmaKlUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmwAxd%2FbtsISssPPFP%2F59CKKDMUZbUhbedSmaKlUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1062&quot; height=&quot;180&quot; data-origin-width=&quot;1062&quot; data-origin-height=&quot;180&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;작업 코드&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://github.com/Potwings/filter_want_request&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/Potwings/filter_want_request&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722515058762&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Potwings/filter_want_request: 필터는 request가 보고 싶다&quot; data-og-description=&quot;필터는 request가 보고 싶다. Contribute to Potwings/filter_want_request development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Potwings/filter_want_request&quot; data-og-url=&quot;https://github.com/Potwings/filter_want_request&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/udoyS/hyWGXFXsuO/eu6GNOYrC8qGIIRK3OdWuK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Potwings/filter_want_request&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Potwings/filter_want_request&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/udoyS/hyWGXFXsuO/eu6GNOYrC8qGIIRK3OdWuK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Potwings/filter_want_request: 필터는 request가 보고 싶다&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;필터는 request가 보고 싶다. Contribute to Potwings/filter_want_request development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>BackEnd/Spring</category>
      <author>Potwings</author>
      <guid isPermaLink="true">https://potwings.tistory.com/66</guid>
      <comments>https://potwings.tistory.com/66#entry66comment</comments>
      <pubDate>Thu, 1 Aug 2024 21:26:38 +0900</pubDate>
    </item>
    <item>
      <title>문자열 탐색 성능 개선</title>
      <link>https://potwings.tistory.com/64</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;필자는 웹메일 업체에서 스팸 차단 솔루션 개발을 담당하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;스팸 차단 솔루션의 특성상 관리자/사용자가 등록한 필터들을 활용한 많은 문자열 탐색이 일어난다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이&amp;nbsp;기능을&amp;nbsp;개선하면&amp;nbsp;솔루션의&amp;nbsp;성능이&amp;nbsp;크게&amp;nbsp;향상될&amp;nbsp;것이라고&amp;nbsp;판단하여&amp;nbsp;진행하게&amp;nbsp;되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;우선 결론부터 말하자면 패턴 매칭의&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; 성능 개선은 진행되지 못하였다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;허나 누군가가 나와 같은 고민을 하고 있을 경우 도움을 받을 수 있도록, 또 나와 같은 실수를 하지 않도록 하기 위해 기록하고자 한다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;시작 - 어떻게 개선하려 하였는가?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;우선 회사에서 열심히 놀고 있는 도중 우아한형제 블로그의 아래 글을 보게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/15764/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://techblog.woowahan.com/15764&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1715855678514&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;고르곤졸라는 되지만 고르곤 졸라는 안 돼! 배달의민족에서 금칙어를 관리하는 방법 | 우아한형&quot; data-og-description=&quot;{{item.name}} 안녕하세요! 셀러시스템팀에서 서버 개발을 하고 있는 김예빈이라고 합니다. 배달의민족에는 금칙어를 관리하는 &amp;quot;통합금칙어시스템&amp;quot;이라는 것이 있습니다. 금칙어란? 법 혹은 규칙으&quot; data-og-host=&quot;techblog.woowahan.com&quot; data-og-source-url=&quot;https://techblog.woowahan.com/15764/&quot; data-og-url=&quot;https://techblog.woowahan.com/15764/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/RnftG/hyV6ipmYTr/YCJXNdUqasq8qh86z2bXq1/img.png?width=4000&amp;amp;height=2088&amp;amp;face=0_0_4000_2088,https://scrap.kakaocdn.net/dn/hT49s/hyV6lsUrQb/fD0L59Q1DBMN8DDr711oI0/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/baMXOg/hyV572u7T3/wJQynf6ZjMHhl2Up5gmTHK/img.png?width=2494&amp;amp;height=1434&amp;amp;face=0_0_2494_1434&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/15764/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.woowahan.com/15764/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/RnftG/hyV6ipmYTr/YCJXNdUqasq8qh86z2bXq1/img.png?width=4000&amp;amp;height=2088&amp;amp;face=0_0_4000_2088,https://scrap.kakaocdn.net/dn/hT49s/hyV6lsUrQb/fD0L59Q1DBMN8DDr711oI0/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/baMXOg/hyV572u7T3/wJQynf6ZjMHhl2Up5gmTHK/img.png?width=2494&amp;amp;height=1434&amp;amp;face=0_0_2494_1434');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;고르곤졸라는 되지만 고르곤 졸라는 안 돼! 배달의민족에서 금칙어를 관리하는 방법 | 우아한형&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;{{item.name}} 안녕하세요! 셀러시스템팀에서 서버 개발을 하고 있는 김예빈이라고 합니다. 배달의민족에는 금칙어를 관리하는 &quot;통합금칙어시스템&quot;이라는 것이 있습니다. 금칙어란? 법 혹은 규칙으&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;techblog.woowahan.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;배민에서 금칙어 시스템을 어떻게 개선했는지에 대한 이야기였는데 그중 금칙어 찾아내기 부분의 내용을 보다 흠칫했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1445&quot; data-origin-height=&quot;956&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAr6CV/btsHqOxADFD/BShpm2QJEO45LpioyeKHt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAr6CV/btsHqOxADFD/BShpm2QJEO45LpioyeKHt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAr6CV/btsHqOxADFD/BShpm2QJEO45LpioyeKHt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAr6CV%2FbtsHqOxADFD%2FBShpm2QJEO45LpioyeKHt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;626&quot; height=&quot;414&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1445&quot; data-origin-height=&quot;956&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;현재 &lt;b&gt;우리 솔루션에서는 문자열 탐색은 String.contains(== String.indexOf)를 통하여 진행&lt;/b&gt;하고 있었고 성능이 좋지 않다는 것도 이미 알고 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;따라서 배민에서 진행한 내용을 참고하여 &lt;b&gt;아호코라식 알고리즘을 적용해 개선&lt;/b&gt;하고자 하였다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1차 시도 - 아호코라식 알고리즘&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;아호코라식 알고리즘이란?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;존재 여부를 확인할 문자열(이하 패턴)들을 Trie&lt;/b&gt;로 만들어둔 후&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;검사 대상 문자열(이하 원문)이 Trie를 따라가며 일치, 불일치 여부를 확인하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;배민 아호코라.jpeg&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tZ624/btsHCuY4zAY/sTWv0rb0CGmVuOLFJR0YV1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tZ624/btsHCuY4zAY/sTWv0rb0CGmVuOLFJR0YV1/img.jpg&quot; data-alt=&quot;출처 : https://techblog.woowahan.com/15764&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tZ624/btsHCuY4zAY/sTWv0rb0CGmVuOLFJR0YV1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtZ624%2FbtsHCuY4zAY%2FsTWv0rb0CGmVuOLFJR0YV1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1080&quot; data-filename=&quot;배민 아호코라.jpeg&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://techblog.woowahan.com/15764&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;기존 패턴 매칭&lt;/b&gt;의 경우 패턴 하나당 한 번씩 원문과 매칭을 진행하므로 시간복잡도가&lt;b&gt; O(m(n1+n2+n3...))&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;반면 &lt;b&gt;아호코라식&lt;/b&gt;으로 진행할 경우 원문을 생성해 둔 Trie에 한 번만 대입하면 되므로 &lt;b&gt;O(m+n1+n2+n3...)&lt;/b&gt;이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;마침 자바에 &lt;a href=&quot;https://mvnrepository.com/artifact/org.ahocorasick/ahocorasick&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;아호코라식 라이브러리&lt;/a&gt;가 존재하여 이를 활용하였고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;본문의 길이가 21397자인 메일 원문&lt;/b&gt;을 기준으로 &lt;b&gt;100개의 문자열을 포함&lt;/b&gt;여부에 대해 테스트해 보았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;기존(String contains)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;273&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgrySp/btsHHoZF21x/IvIMDEsJiMyjP3HyC8Eur0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgrySp/btsHHoZF21x/IvIMDEsJiMyjP3HyC8Eur0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgrySp/btsHHoZF21x/IvIMDEsJiMyjP3HyC8Eur0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgrySp%2FbtsHHoZF21x%2FIvIMDEsJiMyjP3HyC8Eur0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;809&quot; height=&quot;273&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;273&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;아호코라식&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;271&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/loY9O/btsHHfIGagw/l1aoBAhKI7BGRDKkK62tYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/loY9O/btsHHfIGagw/l1aoBAhKI7BGRDKkK62tYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/loY9O/btsHHfIGagw/l1aoBAhKI7BGRDKkK62tYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FloY9O%2FbtsHHfIGagw%2Fl1aoBAhKI7BGRDKkK62tYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;824&quot; height=&quot;271&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;271&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;기존의 방식은 36ms&lt;/b&gt;가 소요되었고 &lt;b&gt;아호코라식을 활용할 경우 5ms&lt;/b&gt;가 소요되어 &lt;b&gt;약 7배&lt;/b&gt;의 정도 성능이 더 좋았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;여기까지 테스트하고 이제 적용하려 하였으나......&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;성능 개선 작업을 진행하던 중 운영 측에서 사칭 메일을 탐지하지 못한다는 신고가 접수되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;필터링 성능은 당장 서비스에 영향을 주는 문제가 아니었던 반면, 사칭 메일 차단은 즉시 해결이 필요한 사안이라 판단하여 복합 조건 필터 기능 개발을 먼저 진행하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;복합 조건 필터&amp;nbsp;작업의 내용은 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;작업 전&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;각 필터당 하나의 조건으로 문자열 검사 진행 - &lt;b&gt;하나의 조건만 충족되면 해당 필터에 매칭&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ex) 1번 조건(제목에 Test가 포함되는 경우) =&amp;gt; 차단&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;작업 후&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;각 필터 당 여러 개의 조건으로 문자열 검사 진행 -&lt;b&gt; 필터에 있는 모든 조건이 충족해야 해당 필터에 매칭&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ex) 1번 조건(제목에 Test가 포함되는 경우), 2번 조건(아이디가 potwings로 시작되는 경우) =&amp;gt; 1, 2번 모두 충족해야 차단&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이전의&amp;nbsp;구조에서는&amp;nbsp;&lt;b&gt;필터 하나에 조건이 하나&lt;/b&gt; 있어, &lt;b&gt;메일이 하나의 조건에만 충족&lt;/b&gt;할 경우 &lt;b&gt;해당&amp;nbsp;조건을&amp;nbsp;가지고&amp;nbsp;있는&amp;nbsp;필터에&amp;nbsp;탐지된&amp;nbsp;것으로&amp;nbsp;처리&lt;/b&gt;하면&amp;nbsp;되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;따라서 &lt;b&gt;&quot;특정 문자를 포함하면&quot;인 조건들로만 Trie를 생성&lt;/b&gt;하면 되었다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;허나&amp;nbsp;변경된&amp;nbsp;구조에서는&amp;nbsp;&lt;b&gt;필터&amp;nbsp;하나에&amp;nbsp;여러 개의&amp;nbsp;조건&lt;/b&gt;이 있어, &lt;b&gt;특정 조건에 부합하는 경우에만 그다음 조건을 검사해야 한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;예시를 통하여 설명해 보자, 아래와 같이 세 가지 조건을 가진 필터가 있다고 생각해 보자&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 제목에 &quot;subject&quot;를 포함&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 아이디에 &quot;test&quot;와 일치&lt;br /&gt;3. 본문 &quot;naver.com&quot;을 포함&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;우선 제목에 대한 패턴들로 만들어&lt;b&gt; Trie를 통하여 1번 조건에 충족&lt;/b&gt;되었다 하자&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그럼 그 후 &lt;b&gt;2번 조건&lt;/b&gt;에 의해&amp;nbsp;&lt;b&gt;아이디에 대해서는 test와 일치&lt;/b&gt;하는지 확인해야 하고 그다음 &lt;b&gt;3번 조건&lt;/b&gt;으로 확인해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;따라서 &lt;b&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;본문 &quot;naver.com&quot;을 포함&lt;/span&gt; &lt;/b&gt;조건은 &lt;b&gt;1번, 2번 조건을 충족하는 경우에만 확인을 진행해야 하므로&lt;/b&gt;&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Trie에 포함시킬 수 없다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1번 조건들로만 Trie를 생성하면 어떨까? 도 생각하였으나.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1번 조건에 &quot;포함하면&quot;이 아닌 다른 조건들이 오는 경우가 많아 효율적이지 못하다 판단해 진행하지 않았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;위와 같은 이유들로 인해 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;아호코라식을 &lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;적용할 수 없게 되었다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2차 시도 - KMP 알고리즘&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;진행해야 하는 작업이 일대다 패턴 매칭이라 아호코라식보다는 성능이 좋지는 않으나&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;KMP 알고리즘&lt;/b&gt;을 활용하면 &lt;b&gt;일대일 매칭에서 String의 contains(브루트 포스)보다는 성능이 좋다&lt;/b&gt; 하여 고려해 보았다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;KMP 알고리즘이란?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; &lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;문자열 비교 시&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;효율적인 인덱스 조정을 통해&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;이미 비교했던 문자열을 최대한 다시 비교하지 않도록 하여&lt;/span&gt;&amp;nbsp;성능을 개선한 문자열 탐색 알고리즘&lt;/b&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;이다.&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;KMP 알고리즘의 자세한 내용은 아래 글에 따로 정리해 두었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://potwings.tistory.com/65&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://potwings.tistory.com/65&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1716981484592&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;KMP - 문자열 탐색 알고리즘 (By Java)&quot; data-og-description=&quot;문자열 탐색 성능 개선 중 KMP알고리즘을 사용하면 기존의 브루트포스 알고리즘보다 더 좋은 성능이 나온다는 이야기를 들었다.정확히 어떻게 성능을 더 빠르게 하는지가 궁금하여 자세한 내용&quot; data-og-host=&quot;potwings.tistory.com&quot; data-og-source-url=&quot;https://potwings.tistory.com/65&quot; data-og-url=&quot;https://potwings.tistory.com/65&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/FYhfL/hyWdfMKmMl/UsfxKYSBfHzgIAdF6mszdk/img.png?width=800&amp;amp;height=117&amp;amp;face=0_0_800_117,https://scrap.kakaocdn.net/dn/mNdHq/hyWdrl78rw/NxK64k3Pv3ceb6vfPm2uA1/img.png?width=800&amp;amp;height=117&amp;amp;face=0_0_800_117,https://scrap.kakaocdn.net/dn/tyEG3/hyWdeAk8iz/1XZKW8K51yNiEQWbFO1gBk/img.png?width=1793&amp;amp;height=756&amp;amp;face=0_0_1793_756&quot;&gt;&lt;a href=&quot;https://potwings.tistory.com/65&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://potwings.tistory.com/65&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/FYhfL/hyWdfMKmMl/UsfxKYSBfHzgIAdF6mszdk/img.png?width=800&amp;amp;height=117&amp;amp;face=0_0_800_117,https://scrap.kakaocdn.net/dn/mNdHq/hyWdrl78rw/NxK64k3Pv3ceb6vfPm2uA1/img.png?width=800&amp;amp;height=117&amp;amp;face=0_0_800_117,https://scrap.kakaocdn.net/dn/tyEG3/hyWdeAk8iz/1XZKW8K51yNiEQWbFO1gBk/img.png?width=1793&amp;amp;height=756&amp;amp;face=0_0_1793_756');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;KMP - 문자열 탐색 알고리즘 (By Java)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;문자열 탐색 성능 개선 중 KMP알고리즘을 사용하면 기존의 브루트포스 알고리즘보다 더 좋은 성능이 나온다는 이야기를 들었다.정확히 어떻게 성능을 더 빠르게 하는지가 궁금하여 자세한 내용&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;potwings.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;KMP 알고리즘은 자체의 index 조절 방식을 통하여 비교를 횟수를 줄이기 때문에 성능이 더 좋을 것이라 예상하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;허나 테스트 결과는 예상과 달리 String contains의 성능이 더 좋았다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;187&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DaTti/btsHIVIxTKK/nrGePwZaDESJkWysIe8VQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DaTti/btsHIVIxTKK/nrGePwZaDESJkWysIe8VQK/img.png&quot; data-alt=&quot;String contains보다 수행 시간이 무려 10배나 늘어났다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DaTti/btsHIVIxTKK/nrGePwZaDESJkWysIe8VQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDaTti%2FbtsHIVIxTKK%2FnrGePwZaDESJkWysIe8VQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;545&quot; height=&quot;187&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;187&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;String contains보다 수행 시간이 무려 10배나 늘어났다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;왜 그럴까?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 혹시 String의 contains는 브루트 포스가 아닌 성능이 더 좋은 방식을 사용할까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;아니다 &lt;b&gt;String의 contains(== indexOf)는 브루트포스 방식으로 비교&lt;/b&gt;하고 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1716460330088&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 브루트포스 방식으로 비교 진행중
public static int indexOf(byte[] value // 검사 대상 , int valueCount, byte[] str // 패턴, int strCount, int fromIndex) {
    byte first = str[0]; // 패턴의 처음 문자
    int max = (valueCount - strCount); // 둘의 길이 차이의 최대
    for (int i = fromIndex; i &amp;lt;= max; i++) {
        // Look for first character.
        if (value[i] != first) { // 패턴의 처음 문자와 일치하지 않는 경우 일치하는 문자가 나올 때까지 skip
            while (++i &amp;lt;= max &amp;amp;&amp;amp; value[i] != first); 
        }
        // 패턴의 처음 문자를 찾을 경우 다음 매칭 진행
        if (i &amp;lt;= max) {
            int j = i + 1; // 패턴의 두번째 문자부터 비교 진행
            int end = j + strCount - 1; // 패턴과 비교해야하는 부분 문자열 마지막 인덱스
            for (int k = 1; j &amp;lt; end &amp;amp;&amp;amp; value[j] == str[k]; j++, k++); // j,k를 증가시키며 비교 진행 불일치하면 중단
            if (j == end) {
                // 위의 반복문에서 패턴의 끝까지 비교가 완료되었을 경우 시작 인덱스 반환
                return i;
            }
            // 만일 도중에 불일치 발생했을 경우 다시 i를 증가시키며 비교 진행
        }
    }
    return -1;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. KMP 알고리즘은 존재 여부만 판단할 경우 오히려 성능이 안 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;만일 &lt;b&gt;존재 여부&lt;/b&gt;만 판단하면 &lt;b&gt;전체 문자열을 확인할 필요 없이 문자열 일치가 발생하면 탐색을 중단&lt;/b&gt;하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;허나 KMP의 경우 탐색을 진행하기 전 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;전체 문자열을 확인하여 lps 배열을 생성&lt;/span&gt;&lt;/b&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이때 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;불필요한 작업이 발생&lt;/span&gt;&lt;/b&gt;하기 때문에 오히려 contains보다 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;성능이 저하&lt;/span&gt;&lt;/b&gt;되는 것이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;허나 여기서 이상한 점이 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;원문이 패턴을 포함하고 있지 않은 경우&lt;/b&gt; &lt;b&gt;전체 탐색&lt;/b&gt;을 진행하므로 KMP가 성능이 더 좋아야 하는데도 &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;성능이 contains보다 떨어지는 경우가 발생&lt;/span&gt;하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그래서 원인을 더 찾아봤는데...&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;u&gt;&lt;b&gt;3. JIT 컴파일러가 String의 indexOf의 성능을 향상시켜준다.&lt;/b&gt;&lt;/u&gt;&lt;u&gt;&lt;b&gt;&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;JIT(Just-In&amp;nbsp;Time&amp;nbsp;Compiler) 컴파일러란?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;초기의 자바는 모든 바이트 코드를 실행 전 직접 기계어로 번역 후 실행해 성능이 떨어졌다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이를 개선하기 위해 &lt;b&gt;JIT 컴파일러&lt;/b&gt;가 등장하였고, &lt;b&gt;자주 쓰이는 메소드는 &lt;span style=&quot;color: #ee2323;&quot;&gt;바이트 코드를 번역해 실행하는 것이 아닌&lt;/span&gt; 기계어로 캐싱해 둔 코드를 실행&lt;/b&gt;하여 성능을 개선하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;String의 contains에서 사용하는 indexOf는 여러 메소드에서도 이미 불러와 사용하고 있는 상태였고, 그로 인하여 JIT&lt;b&gt; 컴파일러에 등록되어 있어 더 좋은 성능으로 실행&lt;/b&gt;될 수 있었던 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;마치며&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;비록 이번에는 성능 개선을 이루지는 못했지만, &lt;span style=&quot;font-family: 'Noto Sans Light'; color: #333333; text-align: start;&quot;&gt;생각으로만 가지고 있던 &lt;/span&gt;&quot;좋은 알고리즘을 활용하여 성능을 이렇게 개선할 수 있겠다&quot;는 것을 직접 시도해 볼 수 있는 좋은 경험이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;향후 다른 기능에 적절한 알고리즘을 접목시킬 기회가 있다면, 확실한 결과물을 남길 수 있기를 기대한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;추가로 글로 작성해 보니 작업할 땐 미처 생각하지 못했던 부분들에 대해 고려해 볼 수 있어 좀 더 많은 것들을 공부할 수 있었다. 앞으로도 꾸준히 기록하는 습관을 가져야겠다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발일지/main</category>
      <author>Potwings</author>
      <guid isPermaLink="true">https://potwings.tistory.com/64</guid>
      <comments>https://potwings.tistory.com/64#entry64comment</comments>
      <pubDate>Thu, 30 May 2024 22:01:05 +0900</pubDate>
    </item>
    <item>
      <title>KMP - 문자열 탐색 알고리즘 (By Java)</title>
      <link>https://potwings.tistory.com/65</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;문자열 탐색 성능 개선 중 KMP알고리즘을 사용하면 기존의 브루트포스 알고리즘보다 더 좋은 성능이 나온다는 이야기를 들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정확히 어떻게 성능을 더 빠르게 하는지가 궁금하여 자세한 내용을 정리해보고자 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 글은 아래 글을 참고하여 작성하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://bowbowbow.tistory.com/6&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://bowbowbow.tistory.com/6&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1716700447733&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;KMP : 문자열 검색 알고리즘&quot; data-og-description=&quot;문자열 검색이 뭐지? 워드프로세서를 사용할 때 찾기 기능을 사용한적 있을 겁니다. 브라우저에서도 Ctrl+F 단축키를 눌러 검색할 수 있습니다. 아래 이미지는 브라우저에서 &amp;quot;테이프&amp;quot;를 검색했을 &quot; data-og-host=&quot;bowbowbow.tistory.com&quot; data-og-source-url=&quot;https://bowbowbow.tistory.com/6&quot; data-og-url=&quot;https://bowbowbow.tistory.com/6&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/L3Q0L/hyV91vfnCZ/0kIV7V0ZBQTBhXOSGMiXZ1/img.png?width=728&amp;amp;height=690&amp;amp;face=0_0_728_690,https://scrap.kakaocdn.net/dn/dmdLfW/hyV91IOTuM/tYk9r1z0AHKZSLVIPttCsK/img.png?width=728&amp;amp;height=690&amp;amp;face=0_0_728_690,https://scrap.kakaocdn.net/dn/UylzF/hyWdj15d0k/aaSj3FwoAGuY4cAueh6nX1/img.png?width=728&amp;amp;height=690&amp;amp;face=0_0_728_690&quot;&gt;&lt;a href=&quot;https://bowbowbow.tistory.com/6&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bowbowbow.tistory.com/6&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/L3Q0L/hyV91vfnCZ/0kIV7V0ZBQTBhXOSGMiXZ1/img.png?width=728&amp;amp;height=690&amp;amp;face=0_0_728_690,https://scrap.kakaocdn.net/dn/dmdLfW/hyV91IOTuM/tYk9r1z0AHKZSLVIPttCsK/img.png?width=728&amp;amp;height=690&amp;amp;face=0_0_728_690,https://scrap.kakaocdn.net/dn/UylzF/hyWdj15d0k/aaSj3FwoAGuY4cAueh6nX1/img.png?width=728&amp;amp;height=690&amp;amp;face=0_0_728_690');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;KMP : 문자열 검색 알고리즘&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;문자열 검색이 뭐지? 워드프로세서를 사용할 때 찾기 기능을 사용한적 있을 겁니다. 브라우저에서도 Ctrl+F 단축키를 눌러 검색할 수 있습니다. 아래 이미지는 브라우저에서 &quot;테이프&quot;를 검색했을&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;bowbowbow.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;KMP 알고리즘이란?&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;문자열 비교 시 &lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;효율적인 인덱스 조정을 통해&lt;/span&gt;&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;이미 비교했던 문자열을 최대한 다시 비교하지 않도록 하여&lt;/span&gt;&amp;nbsp;성능을 개선한 문자열 탐색 알고리즘&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;더 자세한 설명을 위해 아래 예시를 통해 알아보자&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;우리는 &lt;b&gt;ABAABAABAABAB라는 텍스트에서 ABAABAB 패턴을 찾는 작업&lt;/b&gt;을 진행할 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;패턴(찾을 문자) : ABAABAB&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;텍스트(탐색 대상) : ABAABAABAABAB&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;브루트 포스 알고리즘(단순 문자열 탐색)을 활용한 문자열 탐색&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;KMP가 얼마나 효율적으로 동작하는지 확인하기에 위해 우선 브루트 포스 방식으로 문자열 탐색 진행 과정을 확인해보자&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;처음 비교를 시작하면 텍스트의 0번째 인덱스부터 순서대로 비교하다 아래와 같이 &lt;b&gt;6번째 인덱스에서 불일치가 발생&lt;/b&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 54px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;인덱스&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;6&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;7&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;8&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;9&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;10&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;11&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;12&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;텍스트&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;패턴&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그 다음번에는 &lt;b&gt;비교&amp;nbsp;시작 인덱스 +1&lt;/b&gt;한 &lt;b&gt;텍스트의&amp;nbsp;1번째 인덱스부터 비교를 진행&lt;/b&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 60px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;인덱스&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;1&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;6&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;7&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;8&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;9&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;10&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;11&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;12&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;텍스트&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;패턴&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;허나&amp;nbsp;&lt;/span&gt;&lt;b&gt;시작하자마자&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp;불일치&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;가 발생해 바로 &lt;b&gt;비교&lt;/b&gt;&amp;nbsp;&lt;b&gt;시작 인덱스 +1&lt;/b&gt; 하여&amp;nbsp;&lt;/span&gt;&lt;b&gt;텍스트의 2번 인덱스에 대한 비교를 진행&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;할 것이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 60px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;인덱스&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;6&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;7&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;8&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;9&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;10&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;11&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;12&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;텍스트&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #ee2323;&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;패턴&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #ee2323;&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;위와 같이 계속 비교 시작 인덱스를 1씩 증가시키면서 진행하다 &lt;b&gt;6번째 검사 진행 시 텍스트의 일치하는 부분을 찾게 된다&lt;/b&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 60px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;인덱스&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;6&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;7&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;8&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;9&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;10&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;11&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;12&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;텍스트&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;패턴&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;즉 텍스트에 패턴 존재여부를 확인하기 위해 &lt;b&gt;6번의 문자열 비교가 발생&lt;/b&gt;한 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;뿐만 아니라 &lt;span style=&quot;color: #ee2323;&quot;&gt;텍스트가 길어질 경우 비교 작업이 배로 많아지며 &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;b&gt;시간복잡도는 O(n*m) [n: 텍스트의 길이, m : 패턴의 길이]이다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;KMP 알고리즘을 활용한 문자열 탐색&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;KMP 알고리즘을 활용하여 문자열 탐색을 진행할 경우 두 단계로 나누어 진행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 패턴에 대한 접두사 접미사 최대 길이 배열(Longest Prefix Suffix 이하 lps) 배열을 생성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;lps배열은 패턴의 0~i까지의 부분 문자열에서&lt;/b&gt; &lt;b&gt;서로 일치하는 prefix, suffix 중 가장 긴 것의 길이&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: 'Noto Sans Light';&quot;&gt; (단 prefix와 suffix는 부분 문자열의 전체가 되면 안된다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;현재 패턴에서&lt;b&gt; i = 5일 경우&lt;/b&gt;를 확인해 보자, &lt;b&gt;부분 문자열은 ABAABA&lt;/b&gt;이고 prefix와 suffix 집합은 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;prefix : {A, AB, ABA, ABAA, ABAAB}&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;suffix : {A, BA, ABA, AABA, BAABA}&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;prefix와 suffix가 일치하는 값은 A, ABA이고&lt;/b&gt; 이 중 &lt;b&gt;가장 긴 것은 ABA&lt;/b&gt;이다. 따라서&lt;b&gt; lps[5] = 3&lt;/b&gt;이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;lps 배열의 전체는 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 39.7674%; height: 271px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;i&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;부분 문자열&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 20px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;lps[i]&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;AB&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; A&lt;/span&gt;B&lt;span style=&quot;color: #006dd7;&quot;&gt;A&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; A&lt;/span&gt;BA&lt;span style=&quot;color: #006dd7;&quot;&gt;A&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; AB&lt;/span&gt;A&lt;span style=&quot;color: #006dd7;&quot;&gt;AB&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; ABA&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;ABA&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;6&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;AB&lt;/span&gt;AAB&lt;span style=&quot;color: #006dd7;&quot;&gt;AB&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이제 생성한 배열을 활용하여 문자열 탐색을 해보자&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. lps 배열을 활용한 문자열 탐색&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;우선 처음은 브루트 포스와 동일하게 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;텍스트의 0번째 인덱스부터 &lt;/span&gt;비교를 진행한다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 60px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;인덱스&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;6&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;7&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;8&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;9&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;10&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;11&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;12&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;텍스트&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;패턴&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Light';&quot;&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: 'Noto Sans Light';&quot;&gt;B&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;첫 번째 매칭을 하는 과정에서 6번째에서 불일치가 발생하였다. 이를 통해&amp;nbsp;&lt;b&gt;5번째 인덱스까지는 문자열이 일치&lt;/b&gt;한다는 점도 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;여기서 1번에서 생성해 둔 lps 배열을 참고하면&amp;nbsp;&lt;b&gt;lps[5] = 3&lt;/b&gt;이므로 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;패턴의 0~5 인덱스의 부분 문자열에서 prefix와 suffix가 3개 일치&lt;/b&gt;한다는 것을 알 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1931&quot; data-origin-height=&quot;283&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drWWiT/btsHBqw1wzn/dNCNCn03u4H4umXKqNeCYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drWWiT/btsHBqw1wzn/dNCNCn03u4H4umXKqNeCYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drWWiT/btsHBqw1wzn/dNCNCn03u4H4umXKqNeCYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrWWiT%2FbtsHBqw1wzn%2FdNCNCn03u4H4umXKqNeCYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1931&quot; height=&quot;283&quot; data-origin-width=&quot;1931&quot; data-origin-height=&quot;283&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이를 활용하여 다음번 검사 시 &lt;b&gt;패턴 시작을 텍스트의 3번째 인덱스로 조정한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;또 &lt;b&gt;인덱스 3,4,5는 이미 패턴과 일치&lt;/b&gt;하다는 것을 알고 있으므로 &lt;b&gt;6번 인덱스부터 비교를 진행&lt;/b&gt;하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;허나 이번에도 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;패턴의 6번째 인덱스 즉 텍스트의 9번째 인덱스에서 불일치가 발생&lt;/span&gt;&lt;/b&gt;하였고, &lt;b&gt;이전과 마찬가지로 인덱스 조정&lt;/b&gt;을 진행할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1923&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcb9r3/btsHE9OCaJD/tkkE3oNiQnwznwJdXrDcFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcb9r3/btsHE9OCaJD/tkkE3oNiQnwznwJdXrDcFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcb9r3/btsHE9OCaJD/tkkE3oNiQnwznwJdXrDcFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcb9r3%2FbtsHE9OCaJD%2FtkkE3oNiQnwznwJdXrDcFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1923&quot; height=&quot;288&quot; data-origin-width=&quot;1923&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그 다음 패턴 시작을 텍스트의 6번째 인덱스로 조정하였고, 이번에는 &lt;b&gt;패턴과 문자열이 일치하는 부분을 찾게 되었다&lt;/b&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1925&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxuOK5/btsHFw3TJxp/op2iuRGjLEheR0K1L9aOvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxuOK5/btsHFw3TJxp/op2iuRGjLEheR0K1L9aOvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxuOK5/btsHFw3TJxp/op2iuRGjLEheR0K1L9aOvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdxuOK5%2FbtsHFw3TJxp%2Fop2iuRGjLEheR0K1L9aOvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1925&quot; height=&quot;280&quot; data-origin-width=&quot;1925&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;KMP 알고리즘을 활용하니 &lt;b&gt;3번 만에&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 해당 문자열을 찾을 수 있었고, &lt;/span&gt;&lt;b&gt;6번 걸렸던 브루트 포스&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;보다&amp;nbsp;&lt;/span&gt;&lt;b&gt;비교 횟수가 무려 절반이 줄었다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;또한, 효율적인 인덱스 조정으로 인해 문자열 비교 횟수가 감소하여 &lt;b&gt;시간 복잡도가 O(n + m)&lt;/b&gt;으로 개선된다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;자바 코드로의 구현&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이제 KMP 알고리즘을 자바 코드로 어떻게 구현하는지 확인해 보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;비교할 때와 동일하게 &lt;b&gt;패턴에&amp;nbsp;대한&amp;nbsp;lps배열을&amp;nbsp;생성&lt;/b&gt;,&lt;b&gt; lps&amp;nbsp;배열을&amp;nbsp;활용한&amp;nbsp;문자열&amp;nbsp;탐색&amp;nbsp;&lt;/b&gt;총 두 단계로 나뉜다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #333333; text-align: start;&quot;&gt;1. 패턴에 대한 lps배열 생성&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1716979899216&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; public static int[] makeLps(String pattern) {
    int n = pattern.length();
    int[] lps = new int[n];

    int idx = 0; // 비교할 prefix의 인덱스

    // 부분 문자열의 크기 증가시키며 비교 진행
    for (int i = 1; i &amp;lt; n; i++) {

      while (idx &amp;gt; 0 &amp;amp;&amp;amp; pattern.charAt(i) != pattern.charAt(idx)) {
        // 문자열이 연속 일치가 발생했을 때(idx&amp;gt;0), 연속적으로 더 일치하지 않으면 인덱스 조정
        /*
         * idx = lps[idx-1]으로 조정하는 이유
         * 문자열 탐색 시 lps 배열을 참고하여 인덱스 조정을 진행하는 것과 동일하게 생각하면된다
         * prefix는 패턴, 부분 문자열은 text라 생각하면 된다.
         */
        idx = lps[idx - 1];
      }

      if (pattern.charAt(i) == pattern.charAt(idx)) {
        idx++; // 문자열이 일치할 경우 연속으로 일치하는지 확인하기 위해 idx를 증가시킨다.
        lps[i] = idx; // 지금까지 일치한 개수 lps 배열에 저장
      }
    }
    return lps;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #333333; text-align: start;&quot;&gt;2. lps 배열을 활용한 문자열 탐색&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1716979859543&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  public static void kmp(String text, String pattern) {

    int[] lps = makeLps(pattern); // lps 배열 생성

    int idx = 0; // 비교할 패턴의 현재 인덱스
    
    // text의 인덱스를 증가시키며 비교 진행
    for (int i = 0; i &amp;lt; text.length(); i++) {
      while (idx &amp;gt; 0 &amp;amp;&amp;amp; text.charAt(i) != pattern.charAt(idx)) {
        // 패턴과 text 불일치 시 lps 배열을 활용하여 인덱스 조정
   		// 이전에 검사 시 패턴과 일치한 부분의 suffix를 prefix라 생각하고 인덱스 조정
        idx = lps[idx - 1]; 
      }

      if (text.charAt(i) == pattern.charAt(idx)) {
        // 글자가 일치할 경우
        if (idx == pattern.length() - 1) {
          // 패턴의 끝까지 비교한 경우
          System.out.println(&quot;패턴 시작 위치: &quot; + (i - idx + 1) + &quot;, &quot; + &quot;패턴 끝 위치: &quot; + (i + 1));
          idx = lps[idx]; // 다음 비교를 위하여 인덱스 조정
        } else {
          // 패턴의 끝이 아닌 경우 패턴의 다음 값 확인하기 위해 idx++
          idx++;
        }
      }
    }
  }&lt;/code&gt;&lt;/pre&gt;</description>
      <category>BackEnd/Java</category>
      <author>Potwings</author>
      <guid isPermaLink="true">https://potwings.tistory.com/65</guid>
      <comments>https://potwings.tistory.com/65#entry65comment</comments>
      <pubDate>Wed, 29 May 2024 20:17:33 +0900</pubDate>
    </item>
  </channel>
</rss>