<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>꾸준히열심히하자</title>
    <link>https://dont-giveup-study.tistory.com/</link>
    <description>개발을 공부하기 위해 만든 블로그입니다.</description>
    <language>ko</language>
    <pubDate>Sat, 18 Jul 2026 03:46:14 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>꾸준한승준씨</managingEditor>
    <image>
      <title>꾸준히열심히하자</title>
      <url>https://tistory1.daumcdn.net/tistory/6846104/attach/38d85eec4162427db3962673997c4ef7</url>
      <link>https://dont-giveup-study.tistory.com</link>
    </image>
    <item>
      <title>[Spring] 쿠키/세션의 한계, 인터셉터로 극복하기</title>
      <link>https://dont-giveup-study.tistory.com/30</link>
      <description>&lt;h3&gt;1. 문제 제기 : 쿠키/세션만 활용했을때 치명적 한계&lt;/h3&gt;
&lt;p&gt;현재 기획중인 '학원 강사 주간 수업 현황 보고 API 서버' 에서는 인가된 강사만이 학생의 데이터를 조회 하고 등록할수있어야한다.&lt;br&gt;
초기에는 쿠키와 세션만을 활용해서 로그인을 구현하려고 했으나 문제가 발생했다. 사용자가 로그인을 하지않은 상태에서도 특정 URL을 치고 들어오면 막을 방법이 없다는것이다.&lt;br&gt;
이것을 막기위해서 코드내에 &lt;code&gt;session.getAttribute()&lt;/code&gt; 를 사용해서 로그인한 사용자인지 아닌지 로직을 모든 컨트롤러의 API 엔드포인트 마다 검증하는 로직을 추가해야한다.&lt;br&gt;
이는 코드중복을 낳고, 향후 유지보수 까지 불가능하게하는 최악의 설계이다.&lt;br&gt;
핵심 비즈니스 로직과 공통 관심사(인증)을 분리 해야만 했다.&lt;/p&gt;

&lt;h3&gt;2. 기술선택의 트레이드 오프: 서블릿 필터 vs 스프링 인터셉터&lt;/h3&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;서블릿 필터:&lt;/strong&gt; 서블릿 컨테이너(WAS) 단에서 동작하며 가장 앞단에서 요청을 걸러낸다. 이는 DispatcherServlet 을 호출하기도 전에 걸러내므로 효과가 좋아보이지만 로직 구현중 &lt;code&gt;chain.doFilter()&lt;/code&gt; 호출을 하나라도 빼먹는다면 다음 단계로 넘어가지않고 화면이 하얗게 멈춰버리는 위험이 생긴다.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;스프링 인터셉터:&lt;/strong&gt; 스프링 MVC 구조 내(DispatcherServlet 이후)에 동작한다, &lt;code&gt;preHandle&lt;/code&gt; 메서드에서 true 또는 false 만 반환하면 코드 흐름에서 직관적으로 제어할수있기에 개발 제약이 적다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;최종결정:&lt;/strong&gt; 로그인 인가 단계는 WAS단에서 걸려야할만큼은 무거운 작업이 아니고 개발 편의성과 안정성이 높은 스프링 인터셉터를 최종 아키텍쳐로 선정했다.&lt;/p&gt;

&lt;h3&gt;3. 아키택처 적용 계획 : Argument Resolver와 결합한 컨트롤러 최적화&lt;/h3&gt;
&lt;p&gt;인터셉터로 인증 방어선을 구축하더라도, 컨트롤러에서 현재 로그인한 강사의 정보를 꺼내 쓰려면 여전히 &lt;code&gt;HttpServletRequest&lt;/code&gt;를 뒤져 세션을 찾아야 하는 번거로움이 남는다. 이 문제를 해결하기 위해 향후 본격적인 프로젝트 구현 시 Argument Resolver를 적극 도입할 계획이다.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 기존의 지저분한 방식
public String registerReport(HttpServletRequest request) {
    HttpSession session = request.getSession(false);
    Instructor instructor = (Instructor) session.getAttribute(&quot;loginMember&quot;);
    // ...
}

// Argument Resolver 적용 후 구상하는 클린 코드
public String registerReport(@Login Instructor loginInstructor) {
    // ... 세션 탐색 코드가 완전히 사라짐
}&lt;/code&gt;&lt;/pre&gt;</description>
      <author>꾸준한승준씨</author>
      <guid isPermaLink="true">https://dont-giveup-study.tistory.com/30</guid>
      <comments>https://dont-giveup-study.tistory.com/30#entry30comment</comments>
      <pubDate>Thu, 11 Jun 2026 14:51:44 +0900</pubDate>
    </item>
    <item>
      <title>학원 관리 API 서버 로그인: 왜 쿠키 대신 세션을 선택하고 30분 타임아웃을 걸었는가?</title>
      <link>https://dont-giveup-study.tistory.com/29</link>
      <description>&lt;div style=&quot;font-family: 'Noto Sans KR', Arial, sans-serif; line-height: 1.8; color: #333333; max-width: 750px; margin: 0 auto; padding: 10px;&quot;&gt;
    
    &lt;div style=&quot;border-bottom: 3px solid #3b5998; padding-bottom: 15px; margin-bottom: 35px;&quot;&gt;
        &lt;h1 style=&quot;color: #2c3e50; font-size: 24px; font-weight: 700; margin-bottom: 8px; letter-spacing: -0.5px;&quot;&gt;
            학원 관리 API 서버 로그인: 왜 쿠키 대신 세션을 선택하고 30분 타임아웃을 걸었는가?
        &lt;/h1&gt;
        &lt;p style=&quot;color: #7f8c8d; font-size: 14px; margin: 0;&quot;&gt;
            Category: Backend Security / Architecture | Project: 학원 강사 관리 시스템
        &lt;/p&gt;
    &lt;/div&gt;

    &lt;div style=&quot;margin-bottom: 35px;&quot;&gt;
        &lt;h2 style=&quot;color: #2980b9; font-size: 19px; font-weight: 600; border-left: 5px solid #2980b9; padding-left: 12px; margin-bottom: 15px; letter-spacing: -0.3px;&quot;&gt;
            1. [문제 발생] 브라우저 쿠키의 치명적 보안 결함
        &lt;/h2&gt;
        &lt;p style=&quot;margin-bottom: 15px; font-size: 15px; text-align: left;&quot;&gt;
            F12를 누르면 쿠키를 클라이언트가 변경할수있다.&lt;br&gt;
            내가 만들 학원 강사 API 서버에서 A강사가 B강사의 주간 수업현황이 궁금하거나 악의적으로 사용하기위해&lt;br&gt;
            브라우저 쿠키의 보안 결함을 사용하여 해킹을 시도 할수있다.
        &lt;/p&gt;
    &lt;/div&gt;

    &lt;div style=&quot;margin-bottom: 35px;&quot;&gt;
        &lt;h2 style=&quot;color: #2980b9; font-size: 19px; font-weight: 600; border-left: 5px solid #2980b9; padding-left: 12px; margin-bottom: 15px; letter-spacing: -0.3px;&quot;&gt;
            2. [원인 분석 및 트레이드오프] 쿠키 vs 세션의 딜레마
        &lt;/h2&gt;
        &lt;div style=&quot;background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 6px; padding: 18px; margin: 20px 0;&quot;&gt;
            &lt;p style=&quot;margin-top: 0; margin-bottom: 15px; font-size: 15px; font-weight: bold; color: #495057;&quot;&gt;
                - 내가 이해하지 못했던 쿠키와 세션의 개념을 쉽게 풀어적은것
            &lt;/p&gt;
            &lt;p style=&quot;margin-bottom: 15px; font-size: 15px; text-align: left;&quot;&gt;
                쿠키는 클라이언트가 가지고 있는 주머니 이고 세션은 서버가 가지고 있는 금고이다 세션에서는 중요한 정보만을 가지고 있고 서버가 클라이언트에게 임의의 랜덤값(키)를 주면 클라이언트는 키를 쿠키안에 저장을 하고 클라이언트가 요청 할때 마다 랜덤키를 서버에게 보내 서버는 랜덤키를 확인한후 인증을 통과시켜준다.
            &lt;/p&gt;
            &lt;p style=&quot;margin-bottom: 15px; font-size: 15px; text-align: left;&quot;&gt;
                &lt;strong style=&quot;color: #d9534f;&quot;&gt;쿠키의 장점&lt;/strong&gt;은 서버가 관리하는것이 아닌 클라이언트가 관리하는것이기 때문에 속도가 빠르지만 위변조가 가능하다는것이 단점이고&lt;br&gt;
                &lt;strong style=&quot;color: #5cb85c;&quot;&gt;세션의 장점&lt;/strong&gt;은 중요한 정보를 서버가 관리 하고 그 중요한 정보에 접근하기 위한 인증키인 랜덤키를 클라이언트에게 전달하여 보안에 신경쓸 수 있다. 하지만 메모리를 차지하는 비율이 높아 너무 큰 데이터를 넣으면 안된다.
            &lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;

    &lt;div style=&quot;margin-bottom: 35px;&quot;&gt;
        &lt;h2 style=&quot;color: #2980b9; font-size: 19px; font-weight: 600; border-left: 5px solid #2980b9; padding-left: 12px; margin-bottom: 15px; letter-spacing: -0.3px;&quot;&gt;
            3. [해결 및 적용] 학원 관리 API 서버의 세션 타임아웃 설계
        &lt;/h2&gt;
        &lt;p style=&quot;margin-bottom: 15px; font-size: 15px; text-align: left;&quot;&gt;
            그래서 내 프로젝트인 학원 관리 API 서버에서는 세션을 사용할것이고&lt;br&gt;
            강사들이 매일 수업 하자마자 학생들의 수업현황을 적기 때문에 세션 타임아웃을 30분으로 설계할것이다.
        &lt;/p&gt;
        
        &lt;p style=&quot;font-weight: 600; color: #2c3e50; margin-bottom: 5px; font-size: 15px;&quot;&gt;세션 생성코드&lt;/p&gt;
        &lt;pre style=&quot;background-color: #2d2d2d; color: #ccc; padding: 15px; border-radius: 5px; font-family: Consolas, monospace; font-size: 14px; margin-bottom: 20px; overflow-x: auto;&quot;&gt;
// 세션이 있으면 세션 반환 , 없으면 신규 세션 생성
HttpSession session = request.getSession()
// 세션에 로그인 회원 정보 보관
session.setAttribute(Key,Value)
        &lt;/pre&gt;

        &lt;p style=&quot;font-weight: 600; color: #2c3e50; margin-bottom: 5px; font-size: 15px;&quot;&gt;타임 아웃 설계&lt;/p&gt;
        &lt;pre style=&quot;background-color: #2d2d2d; color: #ccc; padding: 15px; border-radius: 5px; font-family: Consolas, monospace; font-size: 14px; margin-bottom: 20px; overflow-x: auto;&quot;&gt;
server.servlet.session.timeout=180
        &lt;/pre&gt;
    &lt;/div&gt;

&lt;/div&gt;</description>
      <author>꾸준한승준씨</author>
      <guid isPermaLink="true">https://dont-giveup-study.tistory.com/29</guid>
      <comments>https://dont-giveup-study.tistory.com/29#entry29comment</comments>
      <pubDate>Mon, 8 Jun 2026 17:47:44 +0900</pubDate>
    </item>
    <item>
      <title>[Spring/Java] @Repository의 진짜 역할(예외 변환)과 Optional의 올바른 사용법</title>
      <link>https://dont-giveup-study.tistory.com/28</link>
      <description>&lt;div style=&quot;border: 1px solid #e1e4e8; border-radius: 8px; padding: 25px; background-color: #f8f9fa; font-family: 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;&quot;&gt;

    &lt;h3 style=&quot;color: #0366d6; margin-top: 0; border-bottom: 2px solid #0366d6; padding-bottom: 10px; font-size: 1.3em;&quot;&gt;
         ️ @Repository의 진짜 역할
    &lt;/h3&gt;
    &lt;p style=&quot;line-height: 1.7; color: #333; margin-bottom: 30px; font-size: 1.05em;&quot;&gt;
        &lt;code style=&quot;background-color: #e6f7ff; color: #005cc5; padding: 3px 6px; border-radius: 4px; font-size: 0.9em; font-weight: bold;&quot;&gt;@Repository&lt;/code&gt;는 DB에 접근하는 객체이니 &lt;strong&gt;스프링 관리 빈(Bean)&lt;/strong&gt;으로 등록하고 (실제로 내부에 &lt;code style=&quot;background-color: #f6f8fa; border: 1px solid #e1e4e8; padding: 2px 5px; border-radius: 3px; font-size: 0.9em;&quot;&gt;@Component&lt;/code&gt;를 포함하고 있다), &lt;br&gt;
        가장 중요한 역할인 &lt;strong&gt;복잡한 DB 전용 에러들을 스프링의 표준 에러(DataAccessException)로 바꿔 보내기 위해&lt;/strong&gt; 적어주는 애노테이션이다.
    &lt;/p&gt;

    &lt;h3 style=&quot;color: #28a745; margin-top: 0; border-bottom: 2px solid #28a745; padding-bottom: 10px; font-size: 1.3em;&quot;&gt;
          Optional: 내용물이 있을 수도, 없을 수도 있는 상자
    &lt;/h3&gt;
    &lt;p style=&quot;line-height: 1.7; color: #333; margin-bottom: 20px; font-size: 1.05em;&quot;&gt;
        &lt;code style=&quot;background-color: #dcffe4; color: #22863a; padding: 3px 6px; border-radius: 4px; font-size: 0.9em; font-weight: bold;&quot;&gt;Optional&lt;/code&gt;은 &lt;strong&gt;'내용물이 있을 수도 있고, 없을 수도 있는 택배 상자'&lt;/strong&gt; 같은 개념이라고 생각하면 이해하기 쉽다. &lt;br&gt;
        데이터가 없다고 무작정 &lt;code style=&quot;background-color: #ffeef0; color: #d73a49; padding: 3px 6px; border-radius: 4px; font-size: 0.9em;&quot;&gt;null&lt;/code&gt;을 던져서 에러(NullPointerException)를 발생시키는 대신, 안전하게 &lt;strong&gt;빈 상자&lt;/strong&gt;를 건네주는 역할을 한다.
    &lt;/p&gt;

    &lt;div style=&quot;background-color: #282c34; border-radius: 8px; padding: 20px; margin-top: 15px; box-shadow: inset 0 0 10px rgba(0,0,0,0.5); overflow-x: auto;&quot;&gt;
        &lt;pre style=&quot;margin: 0; font-family: 'Consolas', 'Monaco', monospace; font-size: 0.95em; line-height: 1.6; color: #abb2bf;&quot;&gt;
&lt;span style=&quot;color: #c678dd;&quot;&gt;public&lt;/span&gt; &lt;span style=&quot;color: #e5c07b;&quot;&gt;Optional&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #e5c07b;&quot;&gt;Member&lt;/span&gt;&amp;gt; &lt;span style=&quot;color: #61afef;&quot;&gt;findByLoginId&lt;/span&gt;(&lt;span style=&quot;color: #e5c07b;&quot;&gt;String&lt;/span&gt; &lt;span style=&quot;color: #e06c75;&quot;&gt;loginId&lt;/span&gt;) {
    &lt;span style=&quot;color: #e5c07b;&quot;&gt;List&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #e5c07b;&quot;&gt;Member&lt;/span&gt;&amp;gt; all = &lt;span style=&quot;color: #56b6c2;&quot;&gt;findAll&lt;/span&gt;();
    &lt;span style=&quot;color: #c678dd;&quot;&gt;for&lt;/span&gt; (&lt;span style=&quot;color: #e5c07b;&quot;&gt;Member&lt;/span&gt; m : all) {
        &lt;span style=&quot;color: #c678dd;&quot;&gt;if&lt;/span&gt; (m.&lt;span style=&quot;color: #56b6c2;&quot;&gt;getLoginId&lt;/span&gt;().&lt;span style=&quot;color: #56b6c2;&quot;&gt;equals&lt;/span&gt;(loginId)) {
            &lt;span style=&quot;color: #7f848e; font-style: italic;&quot;&gt;// 찾았을 때: 객체를 Optional 상자에 예쁘게 포장해서 반환&lt;/span&gt;
            &lt;span style=&quot;color: #c678dd;&quot;&gt;return&lt;/span&gt; &lt;span style=&quot;color: #e5c07b;&quot;&gt;Optional&lt;/span&gt;.&lt;span style=&quot;color: #56b6c2;&quot;&gt;of&lt;/span&gt;(m);
        }
    }
    &lt;span style=&quot;color: #7f848e; font-style: italic;&quot;&gt;// 못 찾았을 때: null 대신 '빈 상자'를 반환 (안전함)&lt;/span&gt;
    &lt;span style=&quot;color: #c678dd;&quot;&gt;return&lt;/span&gt; &lt;span style=&quot;color: #e5c07b;&quot;&gt;Optional&lt;/span&gt;.&lt;span style=&quot;color: #56b6c2;&quot;&gt;empty&lt;/span&gt;();
}
        &lt;/pre&gt;
    &lt;/div&gt;
&lt;/div&gt;</description>
      <author>꾸준한승준씨</author>
      <guid isPermaLink="true">https://dont-giveup-study.tistory.com/28</guid>
      <comments>https://dont-giveup-study.tistory.com/28#entry28comment</comments>
      <pubDate>Fri, 8 May 2026 23:27:18 +0900</pubDate>
    </item>
    <item>
      <title>스프링 검증 로직 진화 과정과 DTO 분리 전략</title>
      <link>https://dont-giveup-study.tistory.com/27</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2026-05-07-22-19-36.jpeg&quot; data-origin-width=&quot;2157&quot; data-origin-height=&quot;1668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsdmmL/dJMcaf7y3u6/XGK0sKSekjZHylpwZV3Agk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsdmmL/dJMcaf7y3u6/XGK0sKSekjZHylpwZV3Agk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsdmmL/dJMcaf7y3u6/XGK0sKSekjZHylpwZV3Agk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsdmmL%2FdJMcaf7y3u6%2FXGK0sKSekjZHylpwZV3Agk%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;2157&quot; height=&quot;1668&quot; data-filename=&quot;KakaoTalk_Photo_2026-05-07-22-19-36.jpeg&quot; data-origin-width=&quot;2157&quot; data-origin-height=&quot;1668&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <author>꾸준한승준씨</author>
      <guid isPermaLink="true">https://dont-giveup-study.tistory.com/27</guid>
      <comments>https://dont-giveup-study.tistory.com/27#entry27comment</comments>
      <pubDate>Thu, 7 May 2026 22:24:12 +0900</pubDate>
    </item>
    <item>
      <title>스프링 메시지 소스(MessageSource) 설정 오류 해결 및 rejectValue()와 FieldError의 차이점</title>
      <link>https://dont-giveup-study.tistory.com/26</link>
      <description>&lt;div style=&quot;border: 1px solid #e1e4e8; border-radius: 8px; padding: 20px; background-color: #f8f9fa; font-family: 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;&quot;&gt;
    &lt;h3 style=&quot;color: #0366d6; margin-top: 0; border-bottom: 2px solid #0366d6; padding-bottom: 10px; font-size: 1.3em;&quot;&gt;
          오늘의 의문점: rejectValue()는 어떻게 거절된 값을 알까?
    &lt;/h3&gt;
    &lt;p style=&quot;line-height: 1.6; color: #333; margin-bottom: 20px;&quot;&gt;
        &lt;code style=&quot;background-color: #ffeef0; color: #d73a49; padding: 2px 5px; border-radius: 3px; font-size: 0.9em;&quot;&gt;FieldError&lt;/code&gt; 객체를 직접 생성할 때는 인자로 &lt;strong&gt;rejectedValue(사용자가 잘못 입력한 값)&lt;/strong&gt;를 직접 넘겨주어야 했다.&lt;br&gt;
        하지만 &lt;code style=&quot;background-color: #e6f7ff; color: #005cc5; padding: 2px 5px; border-radius: 3px; font-size: 0.9em;&quot;&gt;BindingResult&lt;/code&gt;의 제공 메서드인 &lt;code style=&quot;background-color: #f6f8fa; border: 1px solid #e1e4e8; padding: 2px 5px; border-radius: 3px; font-size: 0.9em;&quot;&gt;rejectValue()&lt;/code&gt;를 사용할 때는 rejectedValue를 인자로 주지 않는데도, 화면에 잘못 입력한 값이 그대로 남아서 유지된다. 어떻게 이게 가능한 걸까?
    &lt;/p&gt;

    &lt;h3 style=&quot;color: #28a745; margin-top: 30px; border-bottom: 2px solid #28a745; padding-bottom: 10px; font-size: 1.3em;&quot;&gt;
          해소점: BindingResult의 타겟(Target) 인지 능력
    &lt;/h3&gt;
    &lt;p style=&quot;line-height: 1.6; color: #333;&quot;&gt;
        그 해답은 &lt;code style=&quot;background-color: #e6f7ff; color: #005cc5; padding: 2px 5px; border-radius: 3px; font-size: 0.9em;&quot;&gt;BindingResult&lt;/code&gt;의 위치와 역할에 있다. &lt;br&gt;
        BindingResult는 컨트롤러 메서드 파라미터에서 항상 검증할 타겟 객체(예: &lt;code style=&quot;background-color: #f6f8fa; border: 1px solid #e1e4e8; padding: 2px 5px; border-radius: 3px; font-size: 0.9em;&quot;&gt;@ModelAttribute Item item&lt;/code&gt;) &lt;strong&gt;바로 뒤에 선언&lt;/strong&gt;된다. 즉, BindingResult는 자신이 검증해야 할 &lt;strong&gt;Target 객체의 정보를 이미 내부에 품고 있다.&lt;/strong&gt;
    &lt;/p&gt;
    
    &lt;div style=&quot;background-color: #fff3cd; border-left: 4px solid #ffeeba; padding: 15px; margin: 15px 0; border-radius: 4px;&quot;&gt;
        &lt;strong style=&quot;color: #856404;&quot;&gt;  핵심 동작 원리&lt;/strong&gt;&lt;br&gt;
        &lt;span style=&quot;color: #666; font-size: 0.95em;&quot;&gt;
            &lt;code style=&quot;background-color: #fff; border: 1px solid #ccc; padding: 1px 4px; border-radius: 3px;&quot;&gt;rejectValue(&quot;필드명&quot;, &quot;오류 코드&quot;)&lt;/code&gt;를 호출하면, 스프링 내부에서 알아서 타겟 객체에 접근한다. 그리고 해당 필드의 현재 값을 가져와서 자동으로 &lt;code style=&quot;background-color: #fff; border: 1px solid #ccc; padding: 1px 4px; border-radius: 3px;&quot;&gt;FieldError&lt;/code&gt;의 rejectedValue로 넣어주는 마법을 부린다.
        &lt;/span&gt;
    &lt;/div&gt;

    &lt;ul style=&quot;line-height: 1.6; color: #555; font-size: 0.95em; margin-bottom: 0;&quot;&gt;
        &lt;li&gt;⚠️ &lt;strong&gt;주의할 점:&lt;/strong&gt; rejectValue의 두 번째 인자는 실제 보여줄 오류 메시지 텍스트가 아니라, &lt;code style=&quot;background-color: #f6f8fa; border: 1px solid #e1e4e8; padding: 2px 5px; border-radius: 3px;&quot;&gt;errors.properties&lt;/code&gt;에서 메시지를 찾기 위한 &lt;strong&gt;'오류 코드(Message Code)'&lt;/strong&gt;이다.&lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</description>
      <author>꾸준한승준씨</author>
      <guid isPermaLink="true">https://dont-giveup-study.tistory.com/26</guid>
      <comments>https://dont-giveup-study.tistory.com/26#entry26comment</comments>
      <pubDate>Wed, 6 May 2026 23:19:19 +0900</pubDate>
    </item>
    <item>
      <title>[Spring MVC 2] 검증(Validation) 심화: BindingResult와 데이터 유지의 비밀</title>
      <link>https://dont-giveup-study.tistory.com/25</link>
      <description>&lt;div style=&quot;font-family: 'Noto Sans KR', sans-serif; line-height: 1.8; color: #333;&quot;&gt;
  &lt;h2 style=&quot;color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px;&quot;&gt; ️ 스프링 검증(Validation) 2편: BindingResult 완벽 이해&lt;/h2&gt;

  &lt;p&gt;스프링에서 검증 오류를 처리할 때 가장 중요한 &lt;b&gt;BindingResult&lt;/b&gt;의 핵심 동작 원리와 데이터 유지 방법을 정리합니다.&lt;/p&gt;

  &lt;h3 style=&quot;color: #2980b9; border-left: 5px solid #2980b9; padding-left: 10px; margin-top: 30px;&quot;&gt;1. BindingResult의 '전담 마크' 원칙&lt;/h3&gt;
  &lt;p&gt;&lt;code&gt;BindingResult&lt;/code&gt;는 반드시 검증할 대상인 &lt;code&gt;@ModelAttribute&lt;/code&gt; &lt;b&gt;바로 다음 매개변수&lt;/b&gt;에 위치해야 합니다.&lt;/p&gt;
  
  &lt;div style=&quot;background-color: #f1f3f5; padding: 15px; border-radius: 8px; font-family: monospace;&quot;&gt;
    &lt;span style=&quot;color: #e74c3c;&quot;&gt;// ❌ 잘못된 예: student의 에러를 잡을 수 없음&lt;/span&gt;&lt;br&gt;
    public String add(@ModelAttribute Student s, @ModelAttribute ClassInfo c, BindingResult result) { ... }&lt;br&gt;&lt;br&gt;
    
    &lt;span style=&quot;color: #27ae60;&quot;&gt;// ⭕ 올바른 예: 각 객체마다 전담 바구니 배치&lt;/span&gt;&lt;br&gt;
    public String add(@ModelAttribute Student s, &lt;b&gt;BindingResult studentResult&lt;/b&gt;, &lt;br&gt;
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@ModelAttribute ClassInfo c, &lt;b&gt;BindingResult classInfoResult&lt;/b&gt;) { ... }
  &lt;/div&gt;
  &lt;p style=&quot;font-size: 0.9em; color: #666;&quot;&gt;* 이유: 스프링은 자신의 직전(왼쪽) 인자만 검증 대상으로 인식합니다.&lt;/p&gt;

  &lt;h3 style=&quot;color: #2980b9; border-left: 5px solid #2980b9; padding-left: 10px; margin-top: 30px;&quot;&gt;2. 400 에러 페이지 방어하기&lt;/h3&gt;
  &lt;p&gt;컨트롤러 매개변수에 &lt;code&gt;BindingResult&lt;/code&gt;가 없으면, 타입 미스매치(숫자 칸에 문자 입력 등) 발생 시 스프링은 즉시 예외를 던지고 &lt;b&gt;Whitelabel Error Page&lt;/b&gt;를 보여줍니다.&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;b&gt;BindingResult가 있으면:&lt;/b&gt; 일단 컨트롤러를 호출합니다! 대신 에러 내용을 바구니에 담아 넘겨줍니다.&lt;/li&gt;
    &lt;li&gt;덕분에 개발자는 &lt;code&gt;if (bindingResult.hasErrors())&lt;/code&gt;를 통해 에러를 인지하고 사용자에게 다시 입력 폼을 보여줄 수 있습니다.&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h3 style=&quot;color: #2980b9; border-left: 5px solid #2980b9; padding-left: 10px; margin-top: 30px;&quot;&gt;3. 사용자 입력 값 유지 (rejectedValue)&lt;/h3&gt;
  &lt;p&gt;검증에 실패했을 때, 사용자가 방금 입력한 잘못된 값이 사라지면 사용자 경험(UX)이 매우 나빠집니다. 이를 해결하기 위해 &lt;code&gt;FieldError&lt;/code&gt;의 &lt;b&gt;rejectedValue&lt;/b&gt;를 사용합니다.&lt;/p&gt;
  
  &lt;div style=&quot;background-color: #282c34; color: #abb2bf; padding: 15px; border-radius: 8px; font-family: monospace;&quot;&gt;
    new FieldError(&quot;item&quot;, &quot;price&quot;, &lt;b&gt;item.getPrice()&lt;/b&gt;, false, null, null, &quot;에러 메시지&quot;);
  &lt;/div&gt;
  &lt;p&gt;여기서 3번째 인자인 &lt;code&gt;item.getPrice()&lt;/code&gt;(거절된 값)가 &lt;code&gt;BindingResult&lt;/code&gt;에 보관되고, 타임리프의 &lt;code&gt;th:field&lt;/code&gt;는 모델 객체가 아닌 이 보관소에서 값을 우선적으로 찾아 화면에 다시 뿌려줍니다.&lt;/p&gt;

  &lt;hr style=&quot;border: 0; height: 1px; background: #eee; margin: 40px 0;&quot;&gt;
  &lt;p style=&quot;text-align: center; color: #7f8c8d;&quot;&gt;&lt;i&gt;&quot;기록은 기억보다 강하다. 오늘도 한 걸음 더 성장!&quot;&lt;/i&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <author>꾸준한승준씨</author>
      <guid isPermaLink="true">https://dont-giveup-study.tistory.com/25</guid>
      <comments>https://dont-giveup-study.tistory.com/25#entry25comment</comments>
      <pubDate>Thu, 23 Apr 2026 23:27:14 +0900</pubDate>
    </item>
    <item>
      <title>[Spring MVC 2] 19일 만의 복귀! 검증(Validation) 시작</title>
      <link>https://dont-giveup-study.tistory.com/24</link>
      <description>&lt;div style=&quot;font-family: 'Noto Sans KR', sans-serif; line-height: 1.6; color: #333;&quot;&gt;
  &lt;h2 style=&quot;color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 5px;&quot;&gt;  19일 만의 복귀: 스프링 검증(Validation) V1 핵심 정리&lt;/h2&gt;

  &lt;p style=&quot;font-size: 1.1em; color: #555;&quot;&gt;길었던 방황을 끝내고 다시 백엔드 공부의 시동을 켰습니다. 6월 학원 관리 시스템 프로젝트를 위해 꼭 필요한 &lt;b&gt;컨트롤러의 HTTP 요청 검증&lt;/b&gt; 뼈대를 다지는 시간입니다.&lt;/p&gt;

  &lt;br&gt;

  &lt;h3 style=&quot;color: #2980b9;&quot;&gt;1. 컨트롤러의 주요 역할&lt;/h3&gt;
  &lt;blockquote style=&quot;border-left: 4px solid #3498db; padding-left: 15px; margin-left: 0; background-color: #f8f9fa; padding: 10px;&quot;&gt;
    &lt;p style=&quot;margin: 0;&quot;&gt;&lt;b&gt;&quot;HTTP 요청이 정상인지 검증하는 것&quot;&lt;/b&gt;&lt;/p&gt;
  &lt;/blockquote&gt;
  &lt;p&gt;클라이언트가 보낸 데이터가 올바른지 가장 먼저 확인하는 문지기 역할입니다. 여기서 꼼꼼하게 불량 데이터를 걸러내야 안쪽에 있는 핵심 서비스(비즈니스 로직) 계층을 안전하게 보호할 수 있습니다.&lt;/p&gt;

  &lt;br&gt;

  &lt;h3 style=&quot;color: #2980b9;&quot;&gt;2. @ModelAttribute의 숨겨진 기능&lt;/h3&gt;
  &lt;p&gt;컨트롤러의 매개변수로 &lt;code&gt;@ModelAttribute&lt;/code&gt;를 사용하면, 파라미터를 객체에 바인딩해 주는 것뿐만 아니라 &lt;b&gt;Model에 객체를 자동으로 담아주는 역할&lt;/b&gt;까지 뒤에서 조용히 수행합니다.&lt;/p&gt;
  &lt;div style=&quot;background-color: #282c34; color: #abb2bf; padding: 15px; border-radius: 5px; font-family: monospace;&quot;&gt;
    @ModelAttribute Item item&lt;br&gt;
    &lt;br&gt;
    &lt;span style=&quot;color: #7f848e;&quot;&gt;// 위 코드는 아래 코드와 완전히 동일한 효과를 냅니다.&lt;/span&gt;&lt;br&gt;
    model.addAttribute(&quot;item&quot;, item);
  &lt;/div&gt;

  &lt;br&gt;

  &lt;h3 style=&quot;color: #e74c3c;&quot;&gt;3. GET 요청에서 빈 객체(new Item())를 넘겨주는 진짜 이유  &lt;/h3&gt;
  &lt;div style=&quot;background-color: #fdf6e3; padding: 15px; border-left: 4px solid #e67e22; border-radius: 3px;&quot;&gt;
    &lt;p&gt;&lt;b&gt;❌ 흔한 착각:&lt;/b&gt; &quot;리다이렉트해서 돌아왔을 때 데이터를 유지하기 위해서다?&quot;&lt;/p&gt;
    &lt;p&gt;&lt;b&gt;⭕ 팩트 체크:&lt;/b&gt;&lt;/p&gt;
    &lt;ul style=&quot;margin-bottom: 0;&quot;&gt;
        &lt;li&gt;화면을 그리는 &lt;b&gt;타임리프의 &lt;code&gt;th:object=&quot;${item}&quot;&lt;/code&gt;이 에러를 뱉지 않게 하기 위해&lt;/b&gt; 우선 빈 상자라도 쥐어주는 것입니다.&lt;/li&gt;
        &lt;li&gt;검증 실패 시 사용자가 입력한 값을 유지한 채 폼으로 다시 돌아가야 할 때, &lt;b&gt;리다이렉트를 하면 아예 새로운 요청이 되어 데이터가 싹 날아갑니다.&lt;/b&gt;&lt;/li&gt;
        &lt;li&gt;따라서 &lt;code&gt;return &quot;validation/v1/addForm&quot;;&lt;/code&gt; 처럼 &lt;b&gt;뷰 템플릿 이름만 그대로 반환(포워드)&lt;/b&gt;하여, Model에 담겨있는 기존 입력값들이 화면에 그대로 렌더링되어 유지되도록 하는 것이 핵심입니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;

  &lt;br&gt;

  &lt;h3 style=&quot;color: #2980b9;&quot;&gt;4. Safe Navigation Operator (?.)&lt;/h3&gt;
  &lt;p&gt;타임리프나 스프링 SpEL에서 참조 변수에 접근할 때 서버가 뻗는 대참사를 막아주는 아주 유용한 디테일입니다.&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;b&gt;일반 접근 (&lt;code&gt;참조변수.필드&lt;/code&gt;):&lt;/b&gt; 변수가 &lt;b&gt;null&lt;/b&gt;이면 여지없이 &lt;code&gt;NullPointerException&lt;/code&gt; 폭탄이 터집니다.&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;안전한 접근 (&lt;code&gt;참조변수?.필드&lt;/code&gt;):&lt;/b&gt; 변수가 &lt;b&gt;null&lt;/b&gt;인 경우 에러를 터뜨리지 않고 부드럽게 &lt;b&gt;null을 반환&lt;/b&gt;하고 넘어갑니다.&lt;/li&gt;
  &lt;/ul&gt;

  &lt;br&gt;
  &lt;hr style=&quot;border: 0; height: 1px; background: #ddd;&quot;&gt;
  &lt;p style=&quot;text-align: center; color: #7f8c8d; font-size: 0.9em;&quot;&gt;&lt;i&gt;어둠 속에서 다시 켠 작은 빛. 멈추지 말고 꾸준히 가보자!&lt;/i&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <author>꾸준한승준씨</author>
      <guid isPermaLink="true">https://dont-giveup-study.tistory.com/24</guid>
      <comments>https://dont-giveup-study.tistory.com/24#entry24comment</comments>
      <pubDate>Wed, 22 Apr 2026 22:48:24 +0900</pubDate>
    </item>
    <item>
      <title>[Troubleshooting] 타임리프 IllegalAccessException 에러 원인 분석과 public 접근 제어자의 중요성</title>
      <link>https://dont-giveup-study.tistory.com/22</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  [트러블슈팅 1] 타임리프 기본 객체 조회 에러 (Spring Boot 3.x)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 에러 메시지&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;code&gt;java.lang.IllegalArgumentException: The 'request','session','servletContext' and 'response' expression utility objects are no longer available by default...&lt;/code&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 발생 원인&lt;/b&gt;&lt;br /&gt;
과거 Spring Boot 2.x 버전에서는 타임리프 화면 단에서 &lt;code&gt;${session}&lt;/code&gt;, &lt;code&gt;${#request}&lt;/code&gt; 등을 통해 웹 기본 객체를 직접 조회할 수 있었습니다.&lt;br /&gt;
하지만 뷰(View) 템플릿이 백엔드의 깊은 객체인 session, request 등에 직접 접근하는 것은 &lt;b&gt;보안상 취약하며 객체지향 설계에도 어긋납니다.&lt;/b&gt; 따라서 Spring Boot 3.x (타임리프 3.1 이상)부터는 프레임워크 차원에서 이 직접적인 접근을 원천 차단했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 해결 방법 (실무 정석)&lt;/b&gt;&lt;br /&gt;
화면에서 직접 객체를 뒤지지 않고, &lt;b&gt;컨트롤러(Controller)에서 데이터를 꺼내어 Model에 담아 전달&lt;/b&gt;하는 방식을 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;HttpSession&lt;/code&gt;의 &lt;code&gt;getAttribute()&lt;/code&gt;로 데이터를 꺼낸 뒤, &lt;code&gt;Model&lt;/code&gt;의 &lt;code&gt;addAttribute()&lt;/code&gt;를 이용해 뷰(View)로 넘겨주어 해결했습니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;

&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  [트러블슈팅 2] 자바스크립트 인라인 객체 접근 권한 에러&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 에러 메시지 (Root Cause)&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;code&gt;java.lang.IllegalAccessException: ... cannot access a member of class ... with modifiers &quot;public&quot;&lt;/code&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 발생 원인&lt;/b&gt;&lt;br /&gt;
타임리프의 자바스크립트 인라인 기능(&lt;code&gt;&amp;lt;script th:inline=&quot;javascript&quot;&amp;gt;&lt;/code&gt;)을 사용하던 중 터진 에러입니다.&lt;br /&gt;
타임리프가 백엔드에서 넘겨준 커스텀 객체를 자바스크립트가 쓸 수 있도록 JSON 형태로 자동 변환(직렬화)하려 했으나, &lt;b&gt;해당 클래스의 접근 제어자가 &lt;code&gt;public&lt;/code&gt;이 아니어서&lt;/b&gt; 내부 데이터에 접근(Access)하지 못해 발생했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 해결 방법&lt;/b&gt;&lt;br /&gt;
해당 객체의 클래스 레벨에 &lt;b&gt;&lt;code&gt;public&lt;/code&gt;&lt;/b&gt; 키워드를 명시적으로 붙여주어 접근 권한을 열어주어 해결했습니다.&lt;/p&gt;

&lt;div style=&quot;background-color: #f8f9fa; padding: 15px; border-left: 4px solid #0052cc; margin-top: 15px;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  [CS 심화] 왜 객체에 public을 붙여줘야 할까?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;리플렉션(Reflection):&lt;/b&gt; 외부의 라이브러리나 프레임워크가 런타임에 내 코드의 객체를 분석하고 사용하는 기술입니다.&lt;/li&gt;
&lt;li&gt;타임리프나 Jackson 같은 라이브러리가 자바의 리플렉션을 활용해 객체의 데이터를 읽어갈 때, 해당 클래스와 &lt;code&gt;Getter&lt;/code&gt; 메서드가 반드시 &lt;b&gt;&lt;code&gt;public&lt;/code&gt;&lt;/b&gt;으로 개방되어 있어야만 데이터를 정상적으로 조회하고 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description>
      <author>꾸준한승준씨</author>
      <guid isPermaLink="true">https://dont-giveup-study.tistory.com/22</guid>
      <comments>https://dont-giveup-study.tistory.com/22#entry22comment</comments>
      <pubDate>Thu, 2 Apr 2026 19:08:29 +0900</pubDate>
    </item>
    <item>
      <title>(Spring) 순수 자바의 한계와 AppConfig의 등장: DI/IoC로 OCP, DIP 완벽 해결하기</title>
      <link>https://dont-giveup-study.tistory.com/21</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 순수 자바로만 코드를 작성했을 때의 문제점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순수 자바로 코드를 작성할 때, &lt;code&gt;OrderServiceImpl&lt;/code&gt; 클래스는 &lt;code&gt;DiscountPolicy&lt;/code&gt; 인터페이스에만 의존해야 합니다. 하지만 실제 코드를 보면 다음과 같은 문제가 발생합니다.&lt;/p&gt;

&lt;pre id=&quot;code_1710123456&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class OrderServiceImpl implements OrderService {
    // private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}&lt;/code&gt;&lt;/pre&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 &lt;code&gt;FixDiscountPolicy&lt;/code&gt; 또는 &lt;code&gt;RateDiscountPolicy&lt;/code&gt; 같은 &lt;b&gt;구현체 인스턴스에도 직접 의존&lt;/b&gt;하고 있는 상태입니다. 이는 &lt;b&gt;DIP(의존역전원칙) 위반&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 갑자기 할인 정책이 바뀌어 &lt;code&gt;RateDiscountPolicy&lt;/code&gt;로 변경해야 할 때, 클라이언트인 &lt;code&gt;OrderServiceImpl&lt;/code&gt;의 코드를 직접 수정해야 합니다. 이는 &lt;b&gt;OCP(개방폐쇄원칙) 위반&lt;/b&gt;입니다.&lt;/p&gt;

&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;

&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 공연 기획자의 등장 (AppConfig)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 하나의 공연을 애플리케이션이라고 예시를 들어보겠습니다. 여기서 역할(인터페이스)은 '로미오와 줄리엣'입니다.&lt;/p&gt;

&lt;blockquote data-ke-style=&quot;style1&quot;&gt;로미오 역할로 캐스팅된 강동원(구현체, 배우)이 있다고 가정해 보겠습니다. 강동원은 본인의 연기에만 집중하면 되지, 상대역인 줄리엣 역할로 누가 캐스팅될 것인지까지 신경 쓸 필요가 없습니다.&lt;br&gt;누가 캐스팅되어야 할지 결정하는 것은 &lt;b&gt;공연 기획자&lt;/b&gt;가 해야 할 일입니다.&lt;/blockquote&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 자바 코드에서도 이 '공연 기획자' 역할을 하는 클래스를 만들어야 합니다. 배우를 캐스팅하고(구현 객체를 생성하고) 연결해 주는 &lt;code&gt;AppConfig&lt;/code&gt; 클래스를 생성해 보겠습니다.&lt;/p&gt;

&lt;pre id=&quot;code_1710123457&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(), disCountPolicy());
    }

    @Bean
    public DisCountPolicy disCountPolicy() {
       //return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }

    @Bean
    public MemoryMemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
    &lt;li&gt;&lt;code&gt;@Configuration&lt;/code&gt; : AppConfig에 애플리케이션의 설정을 구성한다는 뜻입니다.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;@Bean&lt;/code&gt; : 스프링 컨테이너에 스프링 빈으로 등록합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 &lt;code&gt;AppConfig&lt;/code&gt;처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을 &lt;b&gt;DI(Dependency Injection) 컨테이너&lt;/b&gt;라고 합니다. 위 코드가 바로 스프링으로 전환한 코드입니다.&lt;/p&gt;

&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;

&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. DIP, OCP를 완벽히 준수한 클라이언트 코드&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;AppConfig&lt;/code&gt;를 도입한 후 &lt;code&gt;OrderServiceImpl&lt;/code&gt;의 코드가 어떻게 바뀌는지 살펴보겠습니다.&lt;/p&gt;

&lt;pre id=&quot;code_1710123458&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DisCountPolicy disCountPolicy;

    // 생성자를 통해 의존관계를 주입(DI) 받음
    public OrderServiceImpl(MemberRepository memberRepository, DisCountPolicy disCountPolicy) {
        this.memberRepository = memberRepository;
        this.disCountPolicy = disCountPolicy;
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;OrderServiceImpl&lt;/code&gt;은 더 이상 구체적인 클래스에 의존하지 않고 오직 인터페이스에만 의존합니다. 완벽히 &lt;b&gt;DIP, OCP를 지킨 코드&lt;/b&gt;라 할 수 있습니다.&lt;/p&gt;

&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;

&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 스프링 컨테이너 적용 (OrderApp)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 이를 실행하는 &lt;code&gt;OrderApp&lt;/code&gt; 클래스입니다.&lt;/p&gt;

&lt;pre id=&quot;code_1710123459&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class OrderApp {
    public static void main(String[] args) {
        // AppConfig appConfig = new AppConfig();
        // MemberService memberService = appConfig.memberService();
        // OrderService orderService = appConfig.orderService();
        
        ApplicationContext ap = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = ap.getBean(&quot;memberService&quot;, MemberService.class);
        OrderService orderService = ap.getBean(&quot;orderService&quot;, OrderService.class);

        Long memberId = 1L;
        Member member = new Member(memberId, &quot;memberA&quot;, Grade.VIP);
        memberService.join(member);
        
        Order order = orderService.createOrder(memberId, &quot;itemA&quot;, 10000);
        System.out.println(&quot;order = &quot; + order);
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;code&gt;ApplicationContext&lt;/code&gt;를 &lt;b&gt;스프링 컨테이너&lt;/b&gt;라고 합니다. 기존에는 개발자가 직접 &lt;code&gt;AppConfig&lt;/code&gt;를 생성해서 직접 객체를 조회했지만, 이제는 스프링 컨테이너를 통해서 스프링 빈(객체)을 찾아서 사용하게 됩니다.&lt;/p&gt;</description>
      <author>꾸준한승준씨</author>
      <guid isPermaLink="true">https://dont-giveup-study.tistory.com/21</guid>
      <comments>https://dont-giveup-study.tistory.com/21#entry21comment</comments>
      <pubDate>Wed, 11 Mar 2026 23:14:48 +0900</pubDate>
    </item>
    <item>
      <title>(Spring) 스프링(Spring)의 존재 이유: 객체 지향 프로그래밍과 SOLID 원칙</title>
      <link>https://dont-giveup-study.tistory.com/20</link>
      <description>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;style&gt;
        .post-container {
            font-family: 'Apple SD Gothic Neo', 'Noto Sans KR', sans-serif;
            line-height: 1.6;
            color: #333;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        h1 {
            color: #2c3e50;
            border-bottom: 2px solid #42b883;
            padding-bottom: 10px;
            margin-bottom: 30px;
        }
        h2 {
            color: #42b883;
            margin-top: 40px;
            border-left: 4px solid #42b883;
            padding-left: 10px;
        }
        h3 {
            color: #34495e;
            margin-top: 25px;
        }
        p {
            margin-bottom: 15px;
        }
        .highlight {
            background-color: #f1f8ff;
            padding: 15px;
            border-radius: 8px;
            margin: 20px 0;
            border: 1px solid #c8e1ff;
        }
        .solid-list {
            list-style-type: none;
            padding: 0;
        }
        .solid-list li {
            background: #f8f9fa;
            margin-bottom: 15px;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.05);
        }
        .solid-title {
            font-weight: bold;
            color: #2980b9;
            font-size: 1.1em;
            display: block;
            margin-bottom: 8px;
        }
        strong {
            color: #e74c3c;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div class=&quot;post-container&quot;&gt;
        &lt;h1&gt;  스프링 프레임워크와 객체 지향 설계의 핵심&lt;/h1&gt;

        &lt;h2&gt;1. 스프링과 스프링 부트 (Spring &amp; Spring Boot)&lt;/h2&gt;
        &lt;h3&gt;스프링 (Spring)&lt;/h3&gt;
        &lt;p&gt;스프링은 &lt;strong&gt;DI(Dependency Injection) 컨테이너 기술&lt;/strong&gt;을 중심으로 하는 강력한 프레임워크입니다. 핵심 기술인 스프링 프레임워크를 기반으로 다양한 생태계(Spring Boot, Spring Data 등)를 구성하고 있습니다.&lt;/p&gt;

        &lt;h3&gt;스프링 부트 (Spring Boot)&lt;/h3&gt;
        &lt;p&gt;스프링 부트는 복잡한 스프링을 &lt;strong&gt;편리하게 사용할 수 있도록 지원&lt;/strong&gt;하는 도구입니다.&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;복잡한 외부 라이브러리 버전을 크게 신경 쓰지 않아도 최적화된 조합을 제공합니다.&lt;/li&gt;
            &lt;li&gt;내장 톰캣(Tomcat) 서버 등을 제공하여 별도의 복잡한 설정 없이 애플리케이션을 실행할 수 있습니다.&lt;/li&gt;
        &lt;/ul&gt;

        &lt;h2&gt;2. 스프링의 핵심: 좋은 객체 지향 프로그래밍&lt;/h2&gt;
        &lt;div class=&quot;highlight&quot;&gt;
            &lt;p&gt;  &lt;strong&gt;스프링의 핵심 개념:&lt;/strong&gt; 스프링은 자바 기반의 프레임워크로, 개발자가 &lt;strong&gt;'좋은 객체 지향 애플리케이션'&lt;/strong&gt;을 개발할 수 있도록 도와주는 프레임워크입니다.&lt;/p&gt;
        &lt;/div&gt;
        
        &lt;h3&gt;좋은 객체 지향 프로그램이란?&lt;/h3&gt;
        &lt;ul&gt;
            &lt;li&gt;단순한 명령어의 나열이 아닌, &lt;strong&gt;객체들의 모임&lt;/strong&gt;이며 이 객체들끼리 메시지를 주고받으며 협력합니다.&lt;/li&gt;
            &lt;li&gt;프로그램이 &lt;strong&gt;유연하고 변경에 용이&lt;/strong&gt;하다는 특징을 가집니다.&lt;/li&gt;
        &lt;/ul&gt;

        &lt;h2&gt;3. 다형성 (Polymorphism): 객체 지향의 꽃&lt;/h2&gt;
        &lt;p&gt;유연하고 변경에 용이한 객체 지향 프로그램을 만들 때 &lt;strong&gt;다형성은 필수&lt;/strong&gt;입니다. 다형성은 애플리케이션의 &lt;strong&gt;'역할'과 '구현'을 분리&lt;/strong&gt;해 줍니다.&lt;/p&gt;
        
        &lt;p&gt;  &lt;strong&gt;자동차 비유:&lt;/strong&gt; 자동차 사용자(클라이언트)는 '자동차 인터페이스(역할)'의 사용법만 알면 됩니다. 내부 엔진이 어떻게 굴러가는지, K3인지 아반떼인지 구체적인 '구현체(클래스)'의 내부 구조를 알 필요가 없습니다.&lt;/p&gt;

        &lt;p&gt;&lt;strong&gt;다형성의 본질:&lt;/strong&gt; 클라이언트의 코드를 전혀 변경하지 않고도, 서버의 구현 기능을 유연하게 변경하고 확장할 수 있습니다.&lt;/p&gt;

        &lt;h2&gt;4. 객체 지향 설계의 핵심 원칙 (SOLID)&lt;/h2&gt;
        &lt;p&gt;좋은 객체 지향 설계를 위해 반드시 알아야 할 SOLID 원칙 중 핵심적인 4가지를 정리해 보았습니다. &lt;em&gt;(참고: SOLID는 원래 5가지 원칙이며, 여기서는 작성한 핵심 4가지를 다룹니다.)&lt;/em&gt;&lt;/p&gt;

        &lt;ul class=&quot;solid-list&quot;&gt;
            &lt;li&gt;
                &lt;span class=&quot;solid-title&quot;&gt;1. SRP (Single Responsibility Principle) : 단일 책임 원칙&lt;/span&gt;
                하나의 클래스는 &lt;strong&gt;단 하나의 책임&lt;/strong&gt;만 가져야 합니다. 클래스의 변경이 있을 때, 다른 곳에 미치는 파급 효과가 적다면 SRP를 잘 지킨 것입니다.
            &lt;/li&gt;
            &lt;li&gt;
                &lt;span class=&quot;solid-title&quot;&gt;2. OCP (Open-Closed Principle) : 개방-폐쇄 원칙&lt;/span&gt;
                소프트웨어 요소는 &lt;strong&gt;확장에는 열려 있으나 변경에는 닫혀 있어야&lt;/strong&gt; 합니다. 다형성을 활용하여 인터페이스(변경 X)를 두고, 새로운 구현체를 만들어(확장) 기능을 추가하는 방식입니다.
            &lt;/li&gt;
            &lt;li&gt;
                &lt;span class=&quot;solid-title&quot;&gt;3. LSP (Liskov Substitution Principle) : 리스코프 치환 원칙&lt;/span&gt;
                프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서, 언제나 하위 타입의 인스턴스로 교체할 수 있어야 합니다. 쉽게 말해, &lt;strong&gt;구현체는 인터페이스의 규약과 의도를 모두 지켜야 한다&lt;/strong&gt;는 뜻입니다.
            &lt;/li&gt;
            &lt;li&gt;
                &lt;span class=&quot;solid-title&quot;&gt;4. DIP (Dependency Inversion Principle) : 의존관계 역전 원칙&lt;/span&gt;
                프로그래머는 &lt;strong&gt;&quot;추상화에 의존해야지, 구체화에 의존하면 안 된다&quot;&lt;/strong&gt;는 원칙입니다. 즉, 클라이언트 코드가 구현 클래스에 의존하지 말고, 인터페이스에 의존해야 유연한 변경이 가능해집니다.
            &lt;/li&gt;
        &lt;/ul&gt;

        &lt;h2&gt;  결론: 스프링은 왜 필요한가?&lt;/h2&gt;
        &lt;p&gt;순수 자바 코드만으로는 다형성을 활용하더라도 OCP와 DIP 원칙을 완벽하게 지키기 어렵습니다. (클라이언트 코드의 변경이 불가피함)&lt;/p&gt;
        &lt;p&gt;&lt;strong&gt;스프링은 DI(의존성 주입)와 DI 컨테이너를 제공&lt;/strong&gt;함으로써, 이 문제를 해결합니다. 스프링을 활용하면 다형성과 더불어 &lt;strong&gt;OCP, DIP를 완벽하게 지키며 유연한 애플리케이션을 설계&lt;/strong&gt;할 수 있게 됩니다.&lt;/p&gt;
    &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</description>
      <author>꾸준한승준씨</author>
      <guid isPermaLink="true">https://dont-giveup-study.tistory.com/20</guid>
      <comments>https://dont-giveup-study.tistory.com/20#entry20comment</comments>
      <pubDate>Mon, 9 Mar 2026 22:59:46 +0900</pubDate>
    </item>
  </channel>
</rss>