<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="https://rokwonk.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://rokwonk.github.io/" rel="alternate" type="text/html" /><updated>2024-04-03T19:34:49+09:00</updated><id>https://rokwonk.github.io/feed.xml</id><title type="html">Rok.log</title><subtitle>log log Blog</subtitle><author><name>RokWon K</name></author><entry><title type="html">문자열 응답 시 공통응답형식이 적용되지 않는 문제 개선하기</title><link href="https://rokwonk.github.io/project/apply-string-response-to-dto/" rel="alternate" type="text/html" title="문자열 응답 시 공통응답형식이 적용되지 않는 문제 개선하기" /><published>2024-04-03T00:00:00+09:00</published><updated>2024-04-03T00:00:00+09:00</updated><id>https://rokwonk.github.io/project/%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%9D%91%EB%8B%B5-%EC%8B%9C-%EA%B3%B5%ED%86%B5%EC%9D%91%EB%8B%B5%ED%98%95%EC%8B%9D%EC%9D%B4-%EC%A0%81%EC%9A%A9%EB%90%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EB%AC%B8%EC%A0%9C-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0</id><content type="html" xml:base="https://rokwonk.github.io/project/apply-string-response-to-dto/"><![CDATA[<p><br /></p>

<p><a href="https://rokwonk.github.io/project/apply-common-response/">이전 포스팅(ResponseBodyAdvice로 공통응답형식 적용기)</a>에서 만든 전역 공통응답형식 적용 덕분에 수월하게 API를 개발할 수 있었다. 하지만 얼마가지 않아 한 가지 문제를 인지할 수 있었는데, 바로 <strong>Controller에서 문자열 응답시 공통응답형식의 적용이 불가능하다는 점이었다.</strong></p>

<p>의도한대로라면 적어도 다음과 같은 응답을 받아야 하지만 예외 발생 응답이 돌아왔다.</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
	</span><span class="err">code:</span><span class="w"> </span><span class="mi">200</span><span class="p">,</span><span class="w">
	</span><span class="err">message:</span><span class="w"> </span><span class="s2">"Success"</span><span class="p">,</span><span class="w">
	</span><span class="err">data:</span><span class="w"> </span><span class="s2">"문자열 응답"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>이번 포스트에서는 해당 문제를 해결하기 위해 고군분투한 이야기를 담아봤다.</p>

<p>처음 문제부터 해결방법을 찾기, 해결한 줄 알았으나 추가적인 이슈들과 그에 대한 해결방안까지 한 포스트에 담아 내용이 꽤 방대하다. 때문에 어떤 순서로 진행되는지 미리 알면 좋을 것 같아 먼저 목차별 의미를 정리해 둔다.</p>
<ol>
  <li><a href="#문제---문자열에-공통응답형식-적용이-불가능한-이유">(문제찾기) - 문자열에 공통응답형식 적용이 불가능한 이유</a></li>
  <li><a href="#handleradapter-탐색하며-방법-찾고-구현하기">(솔루션) - HandlerAdapter 탐색하며 방법 찾고 구현하기</a></li>
  <li><a href="">(이슈1)</a></li>
  <li><a href="">(이슈2)</a></li>
  <li><a href="">(마무리) - 정리</a></li>
</ol>

<p><br /></p>

<h2 id="문제---문자열에-공통응답형식-적용이-불가능한-이유">문제 - 문자열에 공통응답형식 적용이 불가능한 이유</h2>
<p>우선 예외가 발생한 이유부터 알아보자. 기존에 작성한 <code class="language-plaintext highlighter-rouge">ResposneBodyAdvice</code>코드를 보면 Controller에서 어떤값을 리턴해도 advice 코드가 실행되도록 만들었으며, 이를 <code class="language-plaintext highlighter-rouge">SuccessResponse</code>로 래핑하였다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RestControllerAdvice</span>
<span class="kd">class</span> <span class="nc">ResponseAdviceHandler</span><span class="p">:</span> <span class="nc">ResponseBodyAdvice</span><span class="p">&lt;</span><span class="nc">Any</span><span class="p">&gt;</span> <span class="p">{</span>  
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">supports</span><span class="p">(</span><span class="n">returnType</span><span class="p">:</span> <span class="nc">MethodParameter</span><span class="p">,</span> <span class="n">converterType</span><span class="p">:</span> <span class="nc">Class</span><span class="p">&lt;</span><span class="k">out</span> <span class="nc">HttpMessageConverter</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;&gt;):</span> <span class="nc">Boolean</span> <span class="p">{</span>  
        <span class="k">return</span> <span class="k">true</span>
    <span class="p">}</span>  
  
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">beforeBodyWrite</span><span class="p">(</span><span class="n">body</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?,</span> <span class="n">returnType</span><span class="p">:</span> <span class="nc">MethodParameter</span><span class="p">,</span> <span class="n">selectedContentType</span><span class="p">:</span> <span class="nc">MediaType</span><span class="p">,</span> <span class="n">selectedConverterType</span><span class="p">:</span> <span class="nc">Class</span><span class="p">&lt;</span><span class="k">out</span> <span class="nc">HttpMessageConverter</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;&gt;,</span> <span class="n">request</span><span class="p">:</span> <span class="nc">ServerHttpRequest</span><span class="p">,</span> <span class="n">response</span><span class="p">:</span> <span class="nc">ServerHttpResponse</span>  
    <span class="p">):</span> <span class="nc">Any</span><span class="p">?</span> <span class="p">{</span>  
	    <span class="c1">// ...</span>
        <span class="nc">SuccessResponse</span><span class="p">(</span><span class="n">data</span> <span class="p">=</span> <span class="n">body</span><span class="p">)</span>  
    <span class="p">}</span>  
<span class="p">}</span>
</code></pre></div></div>

<p>supports 메서드를 보면 converterType으로 <code class="language-plaintext highlighter-rouge">HttpMessageConverter</code>가 들어오는 것을 볼 수 있다. 다시말해, <strong>ResponseBodyAdvice가 실행되기 전에 HttpMessageConverter가 정해지는 것</strong>이다. 때문에 beforeBodyWrite 메서드에서 응답데이터를 수정하더라도 정해진 컨버터는 바뀌지 않는다.</p>

<p>여기서 핵심은 <strong>문자열과 객체를 처리하는 컨버터가 다르다는 것이다.</strong> 문자열은 <code class="language-plaintext highlighter-rouge">StringHttpMessageConverter</code>, 객체는 Jackson 기준 <code class="language-plaintext highlighter-rouge">MappingJackson2HttpMessageConverter</code>가 사용되어진다.</p>

<p>때문에 핸들러 메서드(Controller 메서드)에서 문자열을 리턴해 <code class="language-plaintext highlighter-rouge">StringHttpMessageConverter</code>로 정해진 상태에서 응답데이터를 객체로 변환시키다보니 컨버터가 제 기능을 하지 못해 예외가 발생하는 것이다.</p>

<p><strong>예외를 막고자 한다면 supports 메서드에서 객체 처리용 컨버터에만 적용되도록 바꾸면 된다.</strong> 하지만 핸들러 메서드에서 문자열 리턴시 공통응답형식이 적용되지 않고 문자열 그대로 클라이언트에게 응답될 것이다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">override</span> <span class="k">fun</span> <span class="nf">supports</span><span class="p">(</span><span class="n">returnType</span><span class="p">:</span> <span class="nc">MethodParameter</span><span class="p">,</span> <span class="n">converterType</span><span class="p">:</span> <span class="nc">Class</span><span class="p">&lt;</span><span class="k">out</span> <span class="nc">HttpMessageConverter</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;&gt;):</span> <span class="nc">Boolean</span> <span class="p">{</span>  
    <span class="k">return</span> <span class="nc">MappingJackson2HttpMessageConverter</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span><span class="p">.</span><span class="nf">isAssignableFrom</span><span class="p">(</span><span class="n">converterType</span><span class="p">)</span>  
<span class="p">}</span>
</code></pre></div></div>

<p>그렇다면 문자열 리턴시에도 공통응답형식을 적용할려면 어떻게 해야할까?</p>

<p><br /><br /></p>

<h2 id="handleradapter-탐색하며-방법-찾고-구현하기">HandlerAdapter 탐색하며 방법 찾고 구현하기</h2>
<p>쉽게 생각해보면 <strong>MessageConverter가 정해지기 전에 응답을 조작</strong>하면 될 것이다. 문제는 그 사이에 응답 코드를 제어할 수 있는 기능이 어떤 것이 있냐는 것이다. 방법을 찾아내기 위해 핸들러 메서드가 실행되는 코드를 디버깅해봤다.</p>

<p><br /></p>

<h3 id="핸들러-메서드-실행-전-설정">핸들러 메서드 실행 전 설정</h3>
<p>DispatcherServlet에서 HandlerAdapter를 통해 요청과 응답 처리 작업을 진행한다. 이 작업들은 <code class="language-plaintext highlighter-rouge">invokeHandlerMethod</code> 메서드에서 이뤄진다. 먼저, <strong>핸들러 메서드 실행 전에 요청, 응답 처리시 필요한 정보들을 셋팅</strong>한다.</p>

<p>눈여겨 볼 부분은 핸들러 어댑터가 가지는 <code class="language-plaintext highlighter-rouge">argumentResolvers</code>와 <code class="language-plaintext highlighter-rouge">returnValueHandlers</code>를 실행할 메서드에 그대로 셋팅한다는 것이다. 이 둘은 각각 <strong>요청에 작업 처리, 응답 작업 처리를 전담하는 역할</strong>을 맡는다.</p>

<p><code class="language-plaintext highlighter-rouge">invocableMethod.invocableMethod</code> 메서드에서 그 모습을 볼 수 있다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Nullable</span>  
<span class="k">protected</span> <span class="nc">ModelAndView</span> <span class="nf">invokeHandlerMethod</span><span class="p">(</span><span class="nc">HttpServletRequest</span> <span class="n">request</span><span class="p">,</span>  
      <span class="nc">HttpServletResponse</span> <span class="n">response</span><span class="p">,</span> <span class="nc">HandlerMethod</span> <span class="n">handlerMethod</span><span class="p">)</span> <span class="n">throws</span> <span class="nc">Exception</span> <span class="p">{</span>  
	<span class="c1">// ...</span>
	<span class="c1">// 핸들러메서드를 실행시킬 객체로 래핑</span>
	<span class="nc">ServletInvocableHandlerMethod</span> <span class="n">invocableMethod</span> <span class="p">=</span> <span class="nf">createInvocableHandlerMethod</span><span class="p">(</span><span class="n">handlerMethod</span><span class="p">);</span>  

	<span class="c1">// ArgumentResolver 등록(요청 처리 시 사용)</span>
	<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">argumentResolvers</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>  
	    <span class="n">invocableMethod</span><span class="p">.</span><span class="nf">setHandlerMethodArgumentResolvers</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">argumentResolvers</span><span class="p">);</span>  
	<span class="p">}</span>  
	<span class="c1">// ReturnValueHandler 등록(응답 처리 시 사용)</span>
	<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">returnValueHandlers</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>  
		<span class="n">invocableMethod</span><span class="p">.</span><span class="nf">setHandlerMethodReturnValueHandlers</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">returnValueHandlers</span><span class="p">);</span>  
	<span class="p">}</span>  
	<span class="c1">// ...</span>

	<span class="c1">// 실제 요청 및 응답 작업 실행</span>
	<span class="n">invocableMethod</span><span class="p">.</span><span class="nf">invokeAndHandle</span><span class="p">(</span><span class="n">webRequest</span><span class="p">,</span> <span class="n">mavContainer</span><span class="p">);</span>  
	<span class="c1">// ... </span>
<span class="p">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="핸들러-메서드-실행과-응답처리-로직">핸들러 메서드 실행과 응답처리 로직</h3>
<p><code class="language-plaintext highlighter-rouge">invokeAndHandle</code>메서드에서는 두 가지 핵심 메서드로 요청과 응답값을 처리한다.</p>
<ul>
  <li><strong>invokeForRequest 메서드</strong><br />
실행 전 설정에서 셋팅된 <code class="language-plaintext highlighter-rouge">argumentResolvers</code>들을 통해, 핸들러 메서드를 실행하는데 필요한 정보를 받아오고 실행시킨다. <strong>필자와 같은 개발자가 작성한 로직들이 수행되고 응답값을 받아온다.</strong>  요청처리에 대해 다루는 글은 아니니 자세한 설명은 생략하겠다. returnValue 변수에는 Controller 메서드가 리턴한 응답값 그 자체가 들어가 있다.</li>
  <li><strong>handleReturnValue 메서드</strong><br />
설정 시 넘겨준 <code class="language-plaintext highlighter-rouge">returnValueHandlers</code>를 이용해 <strong>응답 데이터를 처리</strong>한다. 자세하게 말자하면 <strong>응답데이터를 HttpMessage 형식에 맞춰 바꾸고 부가적인 작업들을 처리한다는 의미</strong>이다. 가장 처음 문제가 되었던 MessageConverter 선택과 ResponseBodyAdvice가 실행되는 것도 바로 이 부분이다.</li>
</ul>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="n">void</span> <span class="nf">invokeAndHandle</span><span class="p">(</span><span class="nc">ServletWebRequest</span> <span class="n">webRequest</span><span class="p">,</span> <span class="nc">ModelAndViewContainer</span> <span class="n">mavContainer</span><span class="p">,</span> <span class="nc">Object</span><span class="o">..</span><span class="p">.</span> <span class="n">providedArgs</span><span class="p">)</span> <span class="n">throws</span> <span class="nc">Exception</span> <span class="p">{</span>    
   <span class="nc">Object</span> <span class="n">returnValue</span> <span class="p">=</span> <span class="nf">invokeForRequest</span><span class="p">(</span><span class="n">webRequest</span><span class="p">,</span> <span class="n">mavContainer</span><span class="p">,</span> <span class="n">providedArgs</span><span class="p">);</span>  
   <span class="nf">setResponseStatus</span><span class="p">(</span><span class="n">webRequest</span><span class="p">);</span>  
	<span class="c1">// ...</span>
   <span class="k">try</span> <span class="p">{</span>  
      <span class="k">this</span><span class="p">.</span><span class="n">returnValueHandlers</span><span class="p">.</span><span class="nf">handleReturnValue</span><span class="p">(</span>  
            <span class="n">returnValue</span><span class="p">,</span> <span class="nf">getReturnValueType</span><span class="p">(</span><span class="n">returnValue</span><span class="p">),</span> <span class="n">mavContainer</span><span class="p">,</span> <span class="n">webRequest</span><span class="p">);</span>  
   <span class="c1">// ...</span>
   <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>여기가 가장 중요한 부분이다. returnValueHandlers는 <code class="language-plaintext highlighter-rouge">HandlerMethodReturnValueHandler</code>의 인터페이스를 구현한 구현체들의 모음집이다. 해당 인터페이스의 구현체들은 <strong>응답 처리 전략을 제공</strong>한다. 각각이 서로 다른 응답속성(ResponseBody, ModelAndView 등)을 처리하는 전략을 가지고 있다.</p>

<p><strong>핸들러 메서드에서 어떤 응답속성을 리턴하느냐 따라 이러한 전략들 중 하나가 선택</strong>된다. 예를 들어, @ResponseBody라면 <code class="language-plaintext highlighter-rouge">RequestResponseBodyMethodProcessor</code>가, ModelAndView라면 <code class="language-plaintext highlighter-rouge">ModelAndViewMethodReturnValueHandler</code>가 선택된다.</p>

<p>필자의 서버는 ResponseBody를 리턴하는 REST API 서버이기 때문에 <code class="language-plaintext highlighter-rouge">RequestResponseBodyMethodProcessor</code>가 선택된다.(@RestController의 메타어노테이션으로 @ResponseBody가 적용되어있다)</p>

<p><strong>RequestResponseBodyMethodProcessor에서는 모든 ResponseBody 응답데이터 처리가 이뤄진다.</strong>  해당 구현체 내부에서 MessageConverter를 순회하며 returnValueType으로 가능한 Converter를 선택하고 ResponseBodyAdvice를 적용, Converter로 JSON 직렬화하는 로직을 찾을 수 있었다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">protected</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="n">void</span> <span class="nf">writeWithMessageConverters</span><span class="p">(</span><span class="nd">@Nullable</span> <span class="nc">T</span> <span class="n">value</span><span class="p">,</span> <span class="nc">MethodParameter</span> <span class="n">returnType</span><span class="p">,</span>  
      <span class="nc">ServletServerHttpRequest</span> <span class="n">inputMessage</span><span class="p">,</span> <span class="nc">ServletServerHttpResponse</span> <span class="n">outputMessage</span><span class="p">)</span>  
      <span class="n">throws</span> <span class="nc">IOException</span><span class="p">,</span> <span class="nc">HttpMediaTypeNotAcceptableException</span><span class="p">,</span> <span class="nc">HttpMessageNotWritableException</span> <span class="p">{</span>  
      
	<span class="c1">// ...</span>
	<span class="c1">// 등록된 MessageConverter를 순회</span>
	<span class="k">for</span> <span class="p">(</span><span class="nc">HttpMessageConverter</span><span class="p">&lt;?&gt;</span> <span class="n">converter</span> <span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="n">messageConverters</span><span class="p">)</span> <span class="p">{</span>  
		<span class="nc">GenericHttpMessageConverter</span> <span class="n">genericConverter</span> <span class="p">=</span> <span class="p">(</span><span class="n">converter</span> <span class="n">instanceof</span> <span class="nc">GenericHttpMessageConverter</span> <span class="n">ghmc</span> <span class="p">?</span> <span class="n">ghmc</span> <span class="p">:</span> <span class="k">null</span><span class="p">);</span>  
		
		<span class="c1">// 해당 Converter로 처리 가능한지 확인</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">genericConverter</span> <span class="p">!=</span> <span class="k">null</span> <span class="p">?</span> <span class="p">((</span><span class="nc">GenericHttpMessageConverter</span><span class="p">)</span> <span class="n">converter</span><span class="p">).</span><span class="nf">canWrite</span><span class="p">(</span><span class="n">targetType</span><span class="p">,</span> <span class="n">valueType</span><span class="p">,</span> <span class="n">selectedMediaType</span><span class="p">)</span> <span class="p">:</span> <span class="n">converter</span><span class="p">.</span><span class="nf">canWrite</span><span class="p">(</span><span class="n">valueType</span><span class="p">,</span> <span class="n">selectedMediaType</span><span class="p">))</span> <span class="p">{</span>  
		
			<span class="c1">// 처리가능하다면 ResponseBodyAdvice 적용 후 결정된 Converter로 write</span>
			<span class="n">body</span> <span class="p">=</span> <span class="nf">getAdvice</span><span class="p">().</span><span class="nf">beforeBodyWrite</span><span class="p">(</span><span class="n">body</span><span class="p">,</span> <span class="n">returnType</span><span class="p">,</span> <span class="n">selectedMediaType</span><span class="p">,</span> <span class="p">(</span><span class="nc">Class</span><span class="p">&lt;?</span> <span class="err">extends</span> <span class="nc">HttpMessageConverter</span><span class="p">&lt;?&gt;&gt;)</span> <span class="n">converter</span><span class="p">.</span><span class="nf">getClass</span><span class="p">(),</span> <span class="n">inputMessage</span><span class="p">,</span> <span class="n">outputMessage</span><span class="p">);</span>  

			<span class="c1">// ... converter로 write하는 로직</span>
			<span class="k">return</span><span class="p">;</span>  
		<span class="p">}</span>  
	<span class="p">}</span>  
<span class="p">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="어디서-응답값을-조작할-수-있을까">어디서 응답값을 조작할 수 있을까?</h3>
<p>응답처리 흐름을 도식화하면 다음과 같다.</p>

<p class="image-caption" style="font-size: 14px;"><img src="https://github.com/kids-ground/adevspoon-backend/assets/52196792/8897032a-84c9-4f2f-9506-fb513f0209e2" alt="HandlerAdapter응답처리" class="align-center" style="width: 100%;" /><br />
HandlerAdapter 응답 처리 흐름</p>

<p>아쉽게도 응답 처리 과정 중 MessageConverter결정과 ResponseBodyAdvice 로직실행은 완전히 결합되어 있었고 Converter가 결정되기 전 개발자가 개입하여 응답을 제어할 부분은 보이지 않았다.</p>

<p>때문에 <strong>응답 처리 과정을 전담하는 ReturnValueHandler 자체를 재구성</strong>해보기로 한다.(WebMvc에서 이를 추가할 수 있도록 지원한다)</p>

<p><br /></p>

<h3 id="응답-처리-덮어쓰기---returnvaluehandler-구현">응답 처리 덮어쓰기 - ReturnValueHandler 구현</h3>
<p>ReturnValueHandler 구현을 위한 인터페이스를 살펴보자. support 메서드는 <strong>응답속성이 ResponseBody이며, returnValue의 Type이 String일때 처리하는 걸로 하면 문제없을 듯</strong>하다. 난감한 부분은 응답처리를 전담하는 로직을 어떻게 작성하냐는 것이다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">HandlerMethodReturnValueHandler</span> <span class="o">{</span>  
	<span class="c1">// returnValue의 타입을 보고 해당 ReturnValueHandler를 사용할 것인지</span>
	<span class="kt">boolean</span> <span class="nf">supportsReturnType</span><span class="o">(</span><span class="nc">MethodParameter</span> <span class="n">returnType</span><span class="o">);</span>  
	
	<span class="c1">// 실제 응답처리를 전담하는 로직이 들어감</span>
    <span class="kt">void</span> <span class="nf">handleReturnValue</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="nc">Object</span> <span class="n">returnValue</span><span class="o">,</span> <span class="nc">MethodParameter</span> <span class="n">returnType</span><span class="o">,</span>  
         <span class="nc">ModelAndViewContainer</span> <span class="n">mavContainer</span><span class="o">,</span> <span class="nc">NativeWebRequest</span> <span class="n">webRequest</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span><span class="o">;</span> 
<span class="o">}</span>
</code></pre></div></div>

<p>기존에 ResponseBody 처리를 담당하던 <code class="language-plaintext highlighter-rouge">RequestResponseBodyMethodProcessors</code>의 구현체는 어떻게 처리하는지 살펴보자. 아래코드를 보면 눈에 띄는 점은 <code class="language-plaintext highlighter-rouge">writeWithMessageConverters</code> 호출이다. 위에서 봤듯 <strong>응답처리 로직은 전부 해당 메서드에서 진행</strong>한다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span>  
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">handleReturnValue</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="nc">Object</span> <span class="n">returnValue</span><span class="o">,</span> <span class="nc">MethodParameter</span> <span class="n">returnType</span><span class="o">,</span>  
      <span class="nc">ModelAndViewContainer</span> <span class="n">mavContainer</span><span class="o">,</span> <span class="nc">NativeWebRequest</span> <span class="n">webRequest</span><span class="o">)</span>  
      <span class="kd">throws</span> <span class="nc">IOException</span><span class="o">,</span> <span class="nc">HttpMediaTypeNotAcceptableException</span><span class="o">,</span> <span class="nc">HttpMessageNotWritableException</span> <span class="o">{</span>  
    
	<span class="n">mavContainer</span><span class="o">.</span><span class="na">setRequestHandled</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>  
	<span class="nc">ServletServerHttpRequest</span> <span class="n">inputMessage</span> <span class="o">=</span> <span class="n">createInputMessage</span><span class="o">(</span><span class="n">webRequest</span><span class="o">);</span>  
	<span class="nc">ServletServerHttpResponse</span> <span class="n">outputMessage</span> <span class="o">=</span> <span class="n">createOutputMessage</span><span class="o">(</span><span class="n">webRequest</span><span class="o">);</span>  
	
	<span class="k">if</span> <span class="o">(</span><span class="n">returnValue</span> <span class="k">instanceof</span> <span class="nc">ProblemDetail</span> <span class="n">detail</span><span class="o">)</span> <span class="o">{</span>  
		<span class="n">outputMessage</span><span class="o">.</span><span class="na">setStatusCode</span><span class="o">(</span><span class="nc">HttpStatusCode</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">detail</span><span class="o">.</span><span class="na">getStatus</span><span class="o">()));</span>  
		<span class="k">if</span> <span class="o">(</span><span class="n">detail</span><span class="o">.</span><span class="na">getInstance</span><span class="o">()</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>  
			<span class="no">URI</span> <span class="n">path</span> <span class="o">=</span> <span class="no">URI</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">inputMessage</span><span class="o">.</span><span class="na">getServletRequest</span><span class="o">().</span><span class="na">getRequestURI</span><span class="o">());</span>  
			<span class="n">detail</span><span class="o">.</span><span class="na">setInstance</span><span class="o">(</span><span class="n">path</span><span class="o">);</span>  
		<span class="o">}</span>  
	<span class="o">}</span>  
	
	<span class="n">writeWithMessageConverters</span><span class="o">(</span><span class="n">returnValue</span><span class="o">,</span> <span class="n">returnType</span><span class="o">,</span> <span class="n">inputMessage</span><span class="o">,</span> <span class="n">outputMessage</span><span class="o">);</span>  
<span class="o">}</span>
</code></pre></div></div>

<p>여기까지 봤을때, <strong>returnValue와 returnType만 String에서 SuccessResponse로 수정</strong>하면 다른 것들은 <code class="language-plaintext highlighter-rouge">RequestResponseBodyMethodProcessors</code>에게 그대로 위임해도 될 것 같다는 생각이 들었다. 바로 구현해보자.</p>

<p>상당히 간단하다. delegate에는 기존의 <code class="language-plaintext highlighter-rouge">RequestResponseBodyMethodProcessors</code>을 주입받아서 <strong>returnValue만 수정해서 delegate에 넘겨주면 된다</strong>!(returnType은 수정하지 않아도 내부적으로 returnValue에서 타입을 추출해 사용한다.)</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">StringResponseBodyReturnValueHandler</span><span class="p">(</span>  
    <span class="k">private</span> <span class="kd">val</span> <span class="py">delegate</span><span class="p">:</span> <span class="nc">HandlerMethodReturnValueHandler</span>  
<span class="p">):</span> <span class="nc">HandlerMethodReturnValueHandler</span> <span class="p">{</span>  
	<span class="c1">// ...suports 메서드</span>
  
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">handleReturnValue</span><span class="p">(</span><span class="n">returnValue</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?,</span> <span class="n">returnType</span><span class="p">:</span> <span class="nc">MethodParameter</span><span class="p">,</span> <span class="n">mavContainer</span><span class="p">:</span> <span class="nc">ModelAndViewContainer</span><span class="p">,</span><span class="n">webRequest</span><span class="p">:</span> <span class="nc">NativeWebRequest</span><span class="p">)</span> <span class="p">{</span>
		<span class="kd">val</span> <span class="py">realReturnValue</span> <span class="p">=</span> <span class="nc">SuccessResponse</span><span class="p">(</span><span class="n">message</span> <span class="p">=</span> <span class="n">returnValue</span> <span class="k">as</span> <span class="nc">String</span><span class="p">,</span> <span class="n">data</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span>
        <span class="n">delegate</span><span class="p">.</span><span class="nf">handleReturnValue</span><span class="p">(</span><span class="n">realReturnValue</span><span class="p">,</span> <span class="n">returnType</span><span class="p">,</span> <span class="n">mavContainer</span><span class="p">,</span> <span class="n">webRequest</span><span class="p">)</span>  
    <span class="p">}</span>  
<span class="p">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="의존성-주입하고-returnvaluehandler-등록하기">의존성 주입하고 ReturnValueHandler 등록하기</h3>
<p>WebMvc의 기능을 확장하고자 할때 <code class="language-plaintext highlighter-rouge">WebMvcConfigurer</code> 인테페이스를 사용한다. 해당 인터페이스에서 returnValueHandler도 등록할 수 있게 메서드를 지원해준다.</p>

<p>returnValueHandler들은 빈으로 등록되는 것이 아니라 빈 주입을 하지 못한다. 따라서 등록시에 기존에 등록되어 있는 <code class="language-plaintext highlighter-rouge">RequestResponseBodyMethodProcessor</code>를 가져와 주입하기로 하자.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>  
<span class="kd">class</span> <span class="nc">WebMvcConfig</span><span class="p">:</span> <span class="nc">WebMvcConfigurer</span> <span class="p">{</span>
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">addReturnValueHandlers</span><span class="p">(</span><span class="n">handlers</span><span class="p">:</span> <span class="nc">MutableList</span><span class="p">&lt;</span><span class="nc">HandlerMethodReturnValueHandler</span><span class="p">&gt;)</span> <span class="p">{</span>  
        <span class="n">handlers</span><span class="p">.</span><span class="nf">firstOrNull</span> <span class="p">{</span> <span class="n">it</span> <span class="k">is</span> <span class="nc">RequestResponseBodyMethodProcessor</span> <span class="p">}</span>  
            <span class="o">?.</span><span class="nf">let</span> <span class="p">{</span> <span class="n">it</span> <span class="k">as</span> <span class="nc">RequestResponseBodyMethodProcessor</span> <span class="p">}</span>  
            <span class="o">?.</span><span class="nf">also</span> <span class="p">{</span>  
                <span class="n">handlers</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">StringResponseBodyReturnValueHandler</span><span class="p">(</span><span class="n">delegate</span> <span class="p">=</span> <span class="n">it</span><span class="p">))</span>  
            <span class="p">}</span>  
    <span class="p">}</span>  
<span class="p">}</span>
</code></pre></div></div>

<p>여기서 새로운 문제가 발생했다.. <strong>아래 사진에서처럼 메서드 인자로 들어오는 handlers가 빈 리스트라는 것</strong>이다. 이래서는 직접 만든 Handler에 의존성을 주입할 수가 없다.</p>

<p class="image-caption" style="font-size: 14px;"><img src="https://github.com/kids-ground/adevspoon-backend/assets/52196792/3d3563b1-a6d1-4844-8a09-7941d116bfc2" alt="handlers" class="align-center" style="width: 100%;" />비어있는 handlers</p>

<p><br /><br /></p>

<h2 id="커스텀-returnvaluehandler-적용-문제">커스텀 ReturnValueHandler 적용 문제</h2>
<p>addReturnValueHandlers 메서드의 handlers에 정보들이 들어가 있지 않는 이유는 간단하다. <strong>returnValueHandlers 들은 RequestMappingHandlerAdapter의 초기화 과정에서 생성되는데 그 이전에 WebMvcConfigurer가 수행</strong>되기 때문이다.</p>

<p>아래 초기화 과정을 코드를 보자. <code class="language-plaintext highlighter-rouge">InitializingBean</code>를 구현하였기에 afterPropertiesSet 메서드가 실행 되는데(빈 생성, 의존성 주입이 완료되고 실행) 정직하게 returnValueHandler들을 생성, 추가하는 현장을 목격할 수 있다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">RequestMappingHandlerAdapter</span> <span class="kd">extends</span> <span class="nc">AbstractHandlerMethodAdapter</span> <span class="kd">implements</span> <span class="nc">BeanFactoryAware</span><span class="o">,</span> <span class="nc">InitializingBean</span> <span class="o">{</span>
	<span class="nd">@Override</span>  
	<span class="kd">public</span> <span class="kt">void</span> <span class="nf">afterPropertiesSet</span><span class="o">()</span> <span class="o">{</span>  
		<span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">returnValueHandlers</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>  
			<span class="c1">// handler들 가져오기</span>
			<span class="nc">List</span><span class="o">&lt;</span><span class="nc">HandlerMethodReturnValueHandler</span><span class="o">&gt;</span> <span class="n">handlers</span> <span class="o">=</span> <span class="n">getDefaultReturnValueHandlers</span><span class="o">();</span>  
			<span class="k">this</span><span class="o">.</span><span class="na">returnValueHandlers</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HandlerMethodReturnValueHandlerComposite</span><span class="o">().</span><span class="na">addHandlers</span><span class="o">(</span><span class="n">handlers</span><span class="o">);</span>  
		<span class="o">}</span>  
		<span class="c1">// ... </span>
	<span class="o">}</span>
	
	<span class="kd">private</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">HandlerMethodReturnValueHandler</span><span class="o">&gt;</span> <span class="nf">getDefaultReturnValueHandlers</span><span class="o">()</span> <span class="o">{</span>  
		<span class="nc">List</span><span class="o">&lt;</span><span class="nc">HandlerMethodReturnValueHandler</span><span class="o">&gt;</span> <span class="n">handlers</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;(</span><span class="mi">20</span><span class="o">);</span>  
		
		<span class="n">handlers</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">ModelAndViewMethodReturnValueHandler</span><span class="o">());</span>  
		<span class="n">handlers</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">ModelMethodProcessor</span><span class="o">());</span>
		<span class="n">handlers</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">RequestResponseBodyMethodProcessor</span><span class="o">(</span><span class="n">getMessageConverters</span><span class="o">(),</span>  
      <span class="k">this</span><span class="o">.</span><span class="na">contentNegotiationManager</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">requestResponseBodyAdvice</span><span class="o">));</span>
		<span class="c1">// ... 등등 add </span>
		<span class="k">return</span> <span class="n">handlers</span><span class="o">;</span>  
	<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">WebMvcConfigurer</code>에서 handlers가 왜 비어있는지 알았다. 이들이 초기화 되는것이 HandlerAdapter가 생성될 때인데, <code class="language-plaintext highlighter-rouge">WebMvcConfigurer</code>는 이보다 먼저 실행되기 때문이다.</p>

<p>그렇다면, <strong>returnValueHandlers들을 초기화하는 HandlerAdapter가 생성되고 난 후에 HandlerAdapter를 직접 이용하면 되지 않을까 생각이 들었다.</strong></p>

<p><br /></p>

<h3 id="handleradapter내-returnvaluehandlers-직접-커스텀하기">HandlerAdapter내 returnValueHandlers 직접 커스텀하기</h3>
<p><code class="language-plaintext highlighter-rouge">RequestMappingHandlerAdapter</code>에서 returnValueHandlers를 직접 추가 설정할 수 있는 getter와 setter를 메서드가 존재한다. 정말 다행이다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="kd">class</span> <span class="nc">RequestMappingHandlerAdapter</span> <span class="n">extends</span> <span class="nc">AbstractHandlerMethodAdapter</span> <span class="n">implements</span> <span class="nc">BeanFactoryAware</span><span class="p">,</span> <span class="nc">InitializingBean</span> <span class="p">{</span>
	<span class="k">public</span> <span class="n">void</span> <span class="nf">setReturnValueHandlers</span><span class="p">(</span><span class="nd">@Nullable</span> <span class="nc">List</span><span class="p">&lt;</span><span class="nc">HandlerMethodReturnValueHandler</span><span class="p">&gt;</span> <span class="n">returnValueHandlers</span><span class="p">)</span> <span class="p">{</span>  
	   <span class="c1">// .. 추가하는 로직</span>
	<span class="p">}</span>  
	
	<span class="k">public</span> <span class="nc">List</span><span class="p">&lt;</span><span class="nc">HandlerMethodReturnValueHandler</span><span class="p">&gt;</span> <span class="nf">getReturnValueHandlers</span><span class="p">()</span> <span class="p">{</span>  
	   <span class="k">return</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">returnValueHandlers</span> <span class="p">!=</span> <span class="k">null</span> <span class="p">?</span> <span class="k">this</span><span class="p">.</span><span class="n">returnValueHandlers</span><span class="p">.</span><span class="nf">getHandlers</span><span class="p">()</span> <span class="p">:</span> <span class="k">null</span><span class="p">);</span>  
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>본격적으로 HandlerAdapter를 커스텀해보자. 설정 클래스를 만들고 <code class="language-plaintext highlighter-rouge">RequestMappingHandlerAdapter</code>를 주입받자. <strong>자연스럽게 주입될 해당 빈이 생성된 이후에 설정 클래스가 빈 객체화</strong>된다.</p>

<p>커스텀 ReturnValueHandler 을 추가하는 로직은 다음의 순서로 동작한다.</p>
<ol>
  <li><strong>HandlerAdapter에서 초기화한 returnValueHandlers 꺼내오기</strong></li>
  <li><strong>커스텀 객체에 주입할 의존성 객체 뽑기</strong></li>
  <li><strong>커스텀 객체 생성 및 추가</strong></li>
  <li><strong>HandlerAdapter에 새롭게 구성된 returnValueHandlers 셋팅</strong></li>
</ol>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>  
<span class="kd">class</span> <span class="nc">HandlerAdapterCustomConfig</span> <span class="p">(</span>  
    <span class="k">private</span> <span class="kd">val</span> <span class="py">handlerAdapter</span><span class="p">:</span> <span class="nc">RequestMappingHandlerAdapter</span><span class="p">,</span>  
<span class="p">)</span> <span class="p">{</span>  
    <span class="k">fun</span> <span class="nf">addStringResponseBodyReturnValueHandler</span><span class="p">()</span> <span class="p">{</span>  
        <span class="kd">val</span> <span class="py">newReturnValueHandlers</span> <span class="p">=</span> <span class="n">mutableListOf</span><span class="p">&lt;</span><span class="nc">HandlerMethodReturnValueHandler</span><span class="p">&gt;()</span>
	        <span class="p">.</span><span class="nf">also</span><span class="p">{</span>  
			    <span class="n">it</span><span class="p">.</span><span class="nf">addAll</span><span class="p">(</span><span class="n">handlerAdapter</span><span class="p">.</span><span class="n">returnValueHandlers</span> <span class="o">?:</span> <span class="nf">emptyList</span><span class="p">())</span>  
			<span class="p">}</span>  
		<span class="kd">val</span> <span class="py">responseBodyReturnHandler</span> <span class="p">=</span> <span class="n">handlerAdapter</span><span class="p">.</span><span class="n">returnValueHandlers</span>  
			    <span class="o">?.</span><span class="nf">firstOrNull</span> <span class="p">{</span> <span class="n">it</span> <span class="k">is</span> <span class="nc">RequestResponseBodyMethodProcessor</span> <span class="p">}</span>  
			    <span class="o">?:</span> <span class="k">return</span> 
		<span class="n">newReturnValueHandlers</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">StringResponseBodyReturnValueHandler</span><span class="p">(</span><span class="n">responseBodyReturnHandler</span><span class="p">))</span>  
		<span class="n">handlerAdapter</span><span class="p">.</span><span class="n">returnValueHandlers</span> <span class="p">=</span> <span class="n">newReturnValueHandlers</span>
    <span class="p">}</span>  
<span class="p">}</span>
</code></pre></div></div>

<p>마지막으로 필자가 작성한 로직은 <strong>빈 생성 후(의존성 주입 완료 후)에 자동으로 적용</strong>되어야한다. 따라서, 빈 라이프사이클 콜백을 통해 적용되도록 만들었다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>  
<span class="kd">class</span> <span class="nc">HandlerAdapterCustomConfig</span> <span class="p">(</span>  
    <span class="k">private</span> <span class="kd">val</span> <span class="py">handlerAdapter</span><span class="p">:</span> <span class="nc">RequestMappingHandlerAdapter</span><span class="p">,</span>  
<span class="p">)</span> <span class="p">{</span>  
    <span class="nd">@PostConstruct</span>
    <span class="k">fun</span> <span class="nf">postConstruct</span><span class="p">()</span> <span class="p">{</span>  
	    <span class="nf">addStringResponseBodyReturnValueHandler</span><span class="p">()</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">RequestMappingHandlerAdapter</code>에서는 <strong>클라이언트의 요청이 들어와 핸들러메서드가 실행될때, 가지고있는 returnValueHandlers를 사용하는 구조</strong>이기 때문에 큰 문제없이 커스텀 객체를 추가할 수 있었다.</p>

<p><br /></p>

<h3 id="실제로-사용되지-않는-문제">실제로 사용되지 않는 문제</h3>
<p>빠짐없이 코드를 작성했음에도 커스텀 ReturnValueHandler가 동작하지 않았다. 흠, 산 너머 산이다.</p>

<p><br /> <br /></p>

<h2 id="returnvaluehandler-적용순서-문제">ReturnValueHandler 적용순서 문제</h2>
<p>왜 적용되지 않는가? HandlerAdapter의 초기화 과정에서 보았듯이 List에 returnValueHandlers들을 만든다. 그리고 이들 중 <strong>하나가 선택되어 핸들러메서드의 응답을 처리</strong>한다.</p>

<p>returnValueHandlers 중 하나를 선택하는 로직을 찾아봤다. <strong>단순히 List에서 순회</strong>하면서 처리가능한 것을 발견하면 곧바로 적용하더라.</p>

<p>보통 Ordered 인터페이스를 구현해 우선순위를 정해주기도 하는데 단순한 List 순회여서 순간 당황스러웠다. 아무튼 기존 <code class="language-plaintext highlighter-rouge">RequestResponseBodyMethodProcessor</code>로 처리가 가능하기 때문에 커스텀 객체에게는 기회가 가지 않는 것이 문제였다.</p>

<p class="image-caption" style="font-size: 14px;"><img src="https://github.com/kids-ground/adevspoon-backend/assets/52196792/1909ab5a-9611-4f51-8a44-371aa6668ac1" alt="ReturnValueHandler 순서" class="align-center" style="width: 100%;" /> ReturnValueHandler 순회</p>

<p><br /></p>

<h3 id="적용-순서-바꾸기">적용 순서 바꾸기</h3>

<p>해당 문제의 해결법은 아주 단순하다. 커스텀 객체를 가장 앞에 추가하면 된다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>  
<span class="kd">class</span> <span class="nc">HandlerAdapterCustomConfig</span> <span class="p">(</span>  
    <span class="k">private</span> <span class="kd">val</span> <span class="py">handlerAdapter</span><span class="p">:</span> <span class="nc">RequestMappingHandlerAdapter</span><span class="p">,</span>  
<span class="p">)</span> <span class="p">{</span>  
	<span class="c1">// ...</span>
  
    <span class="c1">// 커스텀 ReturnValueHandler 의존성 해결, 최우선순위로 등록  </span>
    <span class="k">private</span> <span class="k">fun</span> <span class="nf">addStringResponseBodyReturnValueHandler</span><span class="p">()</span> <span class="p">{</span>  
        <span class="kd">val</span> <span class="py">newReturnValueHandlers</span> <span class="p">=</span> <span class="n">mutableListOf</span><span class="p">&lt;</span><span class="nc">HandlerMethodReturnValueHandler</span><span class="p">&gt;()</span>  
        <span class="kd">val</span> <span class="py">responseBodyReturnHandler</span> <span class="p">=</span> <span class="p">(</span><span class="n">handlerAdapter</span><span class="p">.</span><span class="n">returnValueHandlers</span>  
            <span class="o">?.</span><span class="nf">firstOrNull</span> <span class="p">{</span> <span class="n">it</span> <span class="k">is</span> <span class="nc">RequestResponseBodyMethodProcessor</span> <span class="p">}</span>  
            <span class="o">?:</span> <span class="k">return</span><span class="p">)</span>  
        <span class="c1">// 우선 추가</span>
        <span class="n">newReturnValueHandlers</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">StringResponseBodyReturnValueHandler</span><span class="p">(</span><span class="n">responseBodyReturnHandler</span><span class="p">))</span>  
        <span class="n">newReturnValueHandlers</span><span class="p">.</span><span class="nf">addAll</span><span class="p">(</span><span class="n">handlerAdapter</span><span class="p">.</span><span class="n">returnValueHandlers</span> <span class="o">?:</span> <span class="nf">emptyList</span><span class="p">())</span>  
        <span class="n">handlerAdapter</span><span class="p">.</span><span class="n">returnValueHandlers</span> <span class="p">=</span> <span class="n">newReturnValueHandlers</span>  
    <span class="p">}</span>  
<span class="p">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="기존-responsebodyadvice-수정--마무리">기존 ResponseBodyAdvice 수정 &amp; 마무리</h3>
<p>마지막으로 기존에 작성했던 ResponseBodyAdvice의 supports 메서드를 수정했다. <strong>커스텀 ReturnValueHandler 작성 당시 returnValue는 객체로 수정했지만 returnType은 따로 수정하지 않았다.</strong> 때문에 returnTyped은 String으로 유지된다. 이를 이용해서 ResponseBodyAdvice를 타지 않도록 만들어줬다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RestControllerAdvice</span><span class="p">(</span><span class="n">basePackages</span> <span class="p">=</span> <span class="p">[</span><span class="s">"com.adevspoon"</span><span class="p">])</span>  
<span class="kd">class</span> <span class="nc">SuccessResponseBodyAdvisor</span><span class="p">:</span> <span class="nc">ResponseBodyAdvice</span><span class="p">&lt;</span><span class="nc">Any</span><span class="p">&gt;</span> <span class="p">{</span>  
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">supports</span><span class="p">(</span><span class="n">returnType</span><span class="p">:</span> <span class="nc">MethodParameter</span><span class="p">,</span> <span class="n">converterType</span><span class="p">:</span> <span class="nc">Class</span><span class="p">&lt;</span><span class="k">out</span> <span class="nc">HttpMessageConverter</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;&gt;):</span> <span class="nc">Boolean</span> <span class="p">{</span>  
        <span class="k">return</span> <span class="n">returnType</span><span class="p">.</span><span class="n">parameterType</span> <span class="p">!=</span> <span class="nc">String</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span> <span class="p">&amp;&amp;</span>
        <span class="nc">MappingJackson2HttpMessageConverter</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span><span class="p">.</span><span class="nf">isAssignableFrom</span><span class="p">(</span><span class="n">converterType</span><span class="p">)</span>  
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>다행히 더 이상의 이슈없이 잘 동작했다!(String 반환 값이 data 필드가 아닌 message 필드에 넣는 것으로 ReturnValueHandler를 수정했다)</p>

<p class="image-caption" style="font-size: 14px;"><img src="https://github.com/kids-ground/adevspoon-backend/assets/52196792/8dc53769-99d8-416f-b323-a115d4ccc0d3" alt="String 요청" class="align-center" style="width: 100%;" />String 공통응답형식으로 처리</p>

<p><br /><br /></p>

<h2 id="마무리">마무리</h2>

<h3 id="정리">정리</h3>
<p><strong>문제</strong></p>
<ul>
  <li>Controller에서 String 반환시 MessageConvter가 String 처리용으로 정해져 공통응답객체로 변환하지 못함.</li>
</ul>

<p><strong>해결방법</strong></p>
<ul>
  <li>응답 데이터 처리를 전담하는 ReturnValueHandler를 직접 구현하여 응답 처리전 String을 공통응답객체로 변환하기</li>
  <li>기존 ReturnValueHandler를 활용하여 이후 과정은 위임하기</li>
</ul>

<p><strong>이슈 처리</strong></p>
<ol>
  <li>기존 ReturnValueHandler를 가져와 커스텀 객체 주입하기 위해서는 HandlerAdapter에 직접 접근해 커스텀해야한다.</li>
  <li>등록된 ReturnValueHandler들은 단순 순회하며 선택하기하기 때문에 커스텀 객체를 리스트 앞단에 추가해야한다.</li>
</ol>

<p><br /></p>

<h3 id="마치며">마치며</h3>
<p>기존에 등록된 빈을 커스텀 마이징까지 해야할 줄은 몰랐다. 다행히 결국 풀어냈고 순수하게 디버깅과 필자의 생각만으로 풀어냈다는 것에서 의미가 깊다.</p>

<p>HandlerAdapter를 여행하면서 WebMvc 기능들에 대해서도 여러모로 배울 수도 있었고 흥미로운 트러블 슈팅 과정이었다.ㅎ</p>

<p><br /></p>]]></content><author><name>RokWon K</name></author><category term="Project" /><summary type="html"><![CDATA[MessageConverter가 결정되어 문자열을 DTO객체로 바꾸지 못하는 문제! 어떻게 해결할 수 있을까]]></summary></entry><entry><title type="html">ResponseBodyAdvice로 공통 응답 형식 적용시키기</title><link href="https://rokwonk.github.io/project/apply-common-response/" rel="alternate" type="text/html" title="ResponseBodyAdvice로 공통 응답 형식 적용시키기" /><published>2024-03-31T00:00:00+09:00</published><updated>2024-03-31T00:00:00+09:00</updated><id>https://rokwonk.github.io/project/ResponseBodyAdvice%EB%A1%9C-%EA%B3%B5%ED%86%B5-%EC%9D%91%EB%8B%B5-%ED%98%95%EC%8B%9D-%EC%A0%81%EC%9A%A9%EC%8B%9C%ED%82%A4%EA%B8%B0</id><content type="html" xml:base="https://rokwonk.github.io/project/apply-common-response/"><![CDATA[<p><br /></p>

<p>‘개발 한 스푼’에서는 다음과 같은 형식으로 응답 Body를 보낸다.</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="err">성공</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">객체</span><span class="w"> </span><span class="err">리턴</span><span class="w">
</span><span class="p">{</span><span class="w">
	</span><span class="nl">"code"</span><span class="p">:</span><span class="w"> </span><span class="mi">200</span><span class="p">,</span><span class="w">
	</span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Success"</span><span class="p">,</span><span class="w">
	</span><span class="nl">"data"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">성공시</span><span class="w"> </span><span class="err">데이터</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="err">//</span><span class="w"> </span><span class="err">실패시</span><span class="w">
</span><span class="p">{</span><span class="w">
	</span><span class="nl">"code"</span><span class="p">:</span><span class="w"> </span><span class="mi">400</span><span class="err">_</span><span class="mi">001</span><span class="err">_</span><span class="mi">003</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">내부적으로</span><span class="w"> </span><span class="err">유지하는</span><span class="w"> </span><span class="err">실패코드</span><span class="w">
	</span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"뭔가 잘못되었습니다."</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>성공과 실패 시 같은 응답형식을 취하고 <code class="language-plaintext highlighter-rouge">code</code>와 <code class="language-plaintext highlighter-rouge">message</code>를 통해 보다 자세한 실패 이유를 알려주기 위함이다.</p>

<p>실패시에는 등록된 ExceptionResovler에서 공통으로 처리하지만 정상적인 응답 시에는 수 많은 Controller 메서드에서 <code class="language-plaintext highlighter-rouge">SuccessResponse(data = ResponseObject)</code>와 같이 매번 래핑해서 사용할 수는 없는 노릇이다. 잘못하고 작성을 빼먹으면 더 큰일이다.(물론 테스트 코드가 남아있지만)</p>

<p>Controller에서는 Controller의 일만을 수행하게 냅두고 <strong>공통형식은 Controller 밖에서 처리해보자.</strong></p>

<p><br /></p>

<h3 id="공통-응답-형식-어디서-처리할-수-있을까">공통 응답 형식, 어디서 처리할 수 있을까?</h3>
<p>가장 먼저 떠올랐던 것은 <code class="language-plaintext highlighter-rouge">Filter</code>와 <code class="language-plaintext highlighter-rouge">Interceptor</code>였다.</p>

<p><code class="language-plaintext highlighter-rouge">Filter</code>는 유저의 요청을 가장 먼저 맞이하고 응답 시에는 가장 마지막에 통과하는 스프링 부트의 게이트웨이이다. <strong>Filter에서 응답을 받았을때는 이미 응답 데이터가 모두 쓰여진 상태</strong>다. 이는 Controller에서 리턴한 객체가 JSON으로 직렬화가 끝난 상태란 뜻이다. 이 데이터를 수정하기 위해서는 Response 캐싱부터 역직렬화, 수정, 다시직렬화까지 해줘야할 것들이 많다.</p>

<p>또한 공식설정상 Spring Container 외부에서 수행하기 때문에 단순 응답 Body 조작에서 사용하기에는 적절치 못하다 판단했다.</p>

<p><code class="language-plaintext highlighter-rouge">Interceptor</code>의 경우에는 Spring Container 내에서 동작하지만 Controller에서 return값을 null로 넘기지 않는이상 <strong>응답 데이터를 조작할 수 없다.</strong></p>

<p>또 다른 방법이 없을까하여, 요청부터 응답까지의 흐름에 사고의 템포를 맞춰 고민하다 보니 <strong>HandlerAdapter 레벨에서 처리할 수 있는 방법</strong>이 없을까? 하는 생각이 들었다. 유레카! 역시나? <code class="language-plaintext highlighter-rouge">ResponseBodyAdvice</code>를 통해 Controller 응답값을 제어할 수 있는 방법이 존재하였다.(<del>GPT에게 물어봤다면 더 빨리 찾았을테지만!</del>)</p>

<p><br /></p>

<h3 id="responsebodyadvice">ResponseBodyAdvice</h3>
<p><code class="language-plaintext highlighter-rouge">ResponseBodyAdvice</code>는 인터페이스로 두가지 메서드를 필수로 구현해야한다. 첫번째는 해당 객체를 적용할 대상인지를 체크하는 <code class="language-plaintext highlighter-rouge">supports</code>이고 다른 하나는 body를 조작할 수 있는 <code class="language-plaintext highlighter-rouge">beforeBodyWrite</code> 메서드이다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">boolean</span> <span class="nf">supports</span><span class="p">(</span><span class="nc">MethodParameter</span> <span class="n">returnType</span><span class="p">,</span> <span class="nc">Class</span><span class="p">&lt;?</span> <span class="err">extends</span> <span class="nc">HttpMessageConverter</span><span class="p">&lt;?&gt;&gt;</span> <span class="n">converterType</span><span class="p">);</span>

<span class="nd">@Nullable</span>  
<span class="nc">T</span> <span class="nf">beforeBodyWrite</span><span class="p">(</span><span class="nd">@Nullable</span> <span class="nc">T</span> <span class="n">body</span><span class="p">,</span> <span class="nc">MethodParameter</span> <span class="n">returnType</span><span class="p">,</span> <span class="nc">MediaType</span> <span class="n">selectedContentType</span><span class="p">,</span>  
      <span class="nc">Class</span><span class="p">&lt;?</span> <span class="err">extends</span> <span class="nc">HttpMessageConverter</span><span class="p">&lt;?&gt;&gt;</span> <span class="n">selectedConverterType</span><span class="p">,</span>  
      <span class="nc">ServerHttpRequest</span> <span class="n">request</span><span class="p">,</span> <span class="nc">ServerHttpResponse</span> <span class="n">response</span><span class="p">);</span>
</code></pre></div></div>

<p>해당 인터페이스를 구현하고 <code class="language-plaintext highlighter-rouge">@ControllerAdvice</code> 어노테이션을 붙이면 자동으로 등록된다. 글로벌 예외처리 클래스에 사용하던 그 어노테이션과 똑같다. Controller와 AOP에서 익숙한 용어인 Advice의 합성어로 Controller에 적용되는 부가적인 기능이라고 생각하면 된다.</p>

<p>등록된 <code class="language-plaintext highlighter-rouge">ResponseBodyAdvice</code>는 HandlerAdapter에서 사용된다. Controller 메서드(핸들러메서드)에서 응답값을 리턴 받으면 해당 응답에 적용가능한지(supports 메서드 실행) 확인 후 적용한다.</p>

<p>정리하자면, FilterChain에서 <code class="language-plaintext highlighter-rouge">Filter</code>가, DispatcherServlet에서 <code class="language-plaintext highlighter-rouge">Interceptor</code>가, HandlerAdapter에서 <code class="language-plaintext highlighter-rouge">Request/ResponseBodyAdvice</code>가 요청시(응답시 역순) 처리되는 것이다. (BodyAdvice에 대한 보다 자세한 동작흐름은 다음 포스트에 작성되어있다.)</p>

<p>ResponseBodyAdvice를 사용하여 응답 데이터를 조작할 때 이점은 다음과 같다고 생각한다.</p>
<ol>
  <li><strong>Spring Container 내에서 응답 조작가능</strong></li>
  <li><strong>제공 기능과 필자의 의도가 매칭된다. -&gt; 가독성이 좋다.</strong></li>
</ol>

<p><br /></p>

<h3 id="responsebodyadvice로-공통-응답형식-적용하기">ResponseBodyAdvice로 공통 응답형식 적용하기</h3>
<p>공통 응답형식을 가지는 class를 먼저 정의해보자. Controller에서 리턴되는 응답값들은 모두 data 필드로 들어갈 것이다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">data class</span> <span class="nc">SuccessResponse</span><span class="p">(</span>  
    <span class="kd">val</span> <span class="py">code</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="mi">200</span><span class="p">,</span>  
    <span class="kd">val</span> <span class="py">message</span><span class="p">:</span> <span class="nc">String</span> <span class="p">=</span> <span class="s">"Success"</span><span class="p">,</span>  
    <span class="kd">val</span> <span class="py">data</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?</span>  
<span class="p">)</span>
</code></pre></div></div>

<p>이제 <code class="language-plaintext highlighter-rouge">ResponseBodyAdvice</code>를 정의하자. 우선 모든 응답에 대해 <code class="language-plaintext highlighter-rouge">SuccessResponse</code>로 래핑할 예정이기 때문에 supports 는 무조건 true로 지정한다.(어떤 문제가 벌어질지도 모른 채.. )</p>

<p><code class="language-plaintext highlighter-rouge">beforeBodyWrite</code>에서는 status가 성공인 경우에만 <code class="language-plaintext highlighter-rouge">SuccessResponse</code>로 래핑해준다. 사실 비즈니스 로직에서 실패의 경우, 예외를 던지는 케이스만 있기 때문에 status 확인 로직이 필요할까 고민했지만, 혹시나 명시적 실패를 200번 응답에 작성할 수도 있음을 고려하여 확인로직도 추가하였다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RestControllerAdvice</span>
<span class="kd">class</span> <span class="nc">ResponseAdviceHandler</span><span class="p">:</span> <span class="nc">ResponseBodyAdvice</span><span class="p">&lt;</span><span class="nc">Any</span><span class="p">&gt;</span> <span class="p">{</span>  
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">supports</span><span class="p">(</span><span class="n">returnType</span><span class="p">:</span> <span class="nc">MethodParameter</span><span class="p">,</span> <span class="n">converterType</span><span class="p">:</span> <span class="nc">Class</span><span class="p">&lt;</span><span class="k">out</span> <span class="nc">HttpMessageConverter</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;&gt;):</span> <span class="nc">Boolean</span> <span class="p">{</span>  
        <span class="k">return</span> <span class="k">true</span>
    <span class="p">}</span>  
  
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">beforeBodyWrite</span><span class="p">(</span>  
        <span class="n">body</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?,</span>  
        <span class="n">returnType</span><span class="p">:</span> <span class="nc">MethodParameter</span><span class="p">,</span>  
        <span class="n">selectedContentType</span><span class="p">:</span> <span class="nc">MediaType</span><span class="p">,</span>  
        <span class="n">selectedConverterType</span><span class="p">:</span> <span class="nc">Class</span><span class="p">&lt;</span><span class="k">out</span> <span class="nc">HttpMessageConverter</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;&gt;,</span>  
        <span class="n">request</span><span class="p">:</span> <span class="nc">ServerHttpRequest</span><span class="p">,</span>  
        <span class="n">response</span><span class="p">:</span> <span class="nc">ServerHttpResponse</span>  
    <span class="p">):</span> <span class="nc">Any</span><span class="p">?</span> <span class="p">{</span>  
        <span class="kd">val</span> <span class="py">servletResponse</span> <span class="p">=</span> <span class="p">(</span><span class="n">response</span> <span class="k">as</span> <span class="nc">ServletServerHttpResponse</span><span class="p">).</span><span class="n">servletResponse</span>  
        <span class="kd">val</span> <span class="py">status</span> <span class="p">=</span> <span class="n">servletResponse</span><span class="p">.</span><span class="n">status</span>  
        <span class="kd">val</span> <span class="py">resolve</span> <span class="p">=</span> <span class="nc">HttpStatus</span><span class="p">.</span><span class="nf">resolve</span><span class="p">(</span><span class="n">status</span><span class="p">)</span> <span class="o">?:</span> <span class="k">return</span> <span class="n">body</span>  
  
        <span class="k">return</span> <span class="k">if</span> <span class="p">(</span><span class="n">resolve</span><span class="p">.</span><span class="n">is2xxSuccessful</span><span class="p">)</span> <span class="p">{</span>  
            <span class="nc">SuccessResponse</span><span class="p">(</span><span class="n">data</span> <span class="p">=</span> <span class="n">body</span><span class="p">)</span>  
        <span class="p">}</span> <span class="k">else</span> <span class="n">body</span>  
    <span class="p">}</span>  
<span class="p">}</span>

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

<p>잘 작동한다!</p>

<p><br /></p>

<h3 id="예상치-못한-문제">예상치 못한 문제</h3>
<p>잘 동작하는 와중 Controller에서 객체가 아닌 문자열을 리턴하는 API를 만들었다. API를 테스트하는 도중 왠열 에러가 발생했다. 다음과 같은 에러 코드와 함께..</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>message: class com.*.*.*.SuccessResponse cannot be cast to class java.lang.String (com.*.*.*.SuccessResponse is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')
type: ClassCastException
</code></pre></div></div>

<p>문자열 리턴 후 공통응답형식을 적용하는 것으로 인해 발생하는 문제였다. 이에 대한 해결과정은 <a href="https://rokwonk.github.io/project/apply-string-response-to-dto/">다음 포스트</a>에서 작성한다.</p>

<p><br /></p>]]></content><author><name>RokWon K</name></author><category term="Project" /><summary type="html"><![CDATA[공통 응답 형식을 적용하고 싶은데 어떤 기능을 사용해야할까?]]></summary></entry><entry><title type="html">Body, Query, Entity에 포함된 Legacy Enum 효율적으로 관리하기(2) Query편</title><link href="https://rokwonk.github.io/project/legacy-enum-management-2/" rel="alternate" type="text/html" title="Body, Query, Entity에 포함된 Legacy Enum 효율적으로 관리하기(2) Query편" /><published>2024-03-29T00:00:00+09:00</published><updated>2024-03-29T00:00:00+09:00</updated><id>https://rokwonk.github.io/project/Legacy-Enum-%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9C%BC%EB%A1%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0-2-Query%ED%8E%B8</id><content type="html" xml:base="https://rokwonk.github.io/project/legacy-enum-management-2/"><![CDATA[<p><br /></p>

<p><a href="https://rokwonk.github.io/project/legacy-enum-management-1/">이전 포스팅</a>에서는 요청, 응답 Body에 포함된 Legacy Enum을 다뤘던 방법을 작성하였다.</p>

<p>아쉽게도, ‘개발 한 스푼’ 서비스에서는 쿼리 파라미터에도 Legacy Enum(소문자 값 사용)들이 사용되고 있다. 사실 소문자 값을 사용하는게 Legacy라 부르는게 맞는가 싶기도 하지만 어찌되었든 어플리케이션에서는 대문자형 Enum 값에 바인딩시켜 사용하고 싶다.</p>

<p>쿼리 문자열은 Jackson을 통해서 역/직렬화가 되는것이 아니기 때문에 이전 포스팅에서 만든 Serializer를 사용할 수 없다. 이번 포스팅에서는 RequestParam 또는 ModelAttribute에 포함된 Legacy Enum들을 객체지향적으로 다룬 방법을 공유해본다.</p>

<blockquote>
  <p>요청/응답 Body, Query, Entity 부분을 각각 포스팅을 나누어 작성하였습니다.</p>
  <ul>
    <li><a href="https://rokwonk.github.io/project/legacy-enum-management-1/">요청/응답 Body에 포함된 Legacy Enum 관리하기</a></li>
    <li><strong>(Now) Query에 포함된 Legacy Enum 관리하기</strong></li>
    <li><a href="https://rokwonk.github.io/project/legacy-enum-management-3/">Entity에서 DB에 사용중인 Legacy Enum 관리하기</a></li>
  </ul>
</blockquote>

<p><br /></p>

<h3 id="query가-객체-혹은-param-필드에-바인딩-되는-방법">Query가 객체 혹은 Param 필드에 바인딩 되는 방법</h3>
<p>Controller에서 <code class="language-plaintext highlighter-rouge">@RequestParam</code>로 특정 필드에 <code class="language-plaintext highlighter-rouge">@ModelAttribute</code>를 통해 객체로 쿼리 정보들을 받을 수 있다. 이때 우리는 각 필드에 String, Int와 같이 타입을 지정하는데 재밌게도 알아서 쿼리값들이 해당 타입으로 변환되어 주입된다.</p>

<p>이러한 동작이 가능한 이유는 WebMvc에서 Converter를 이용해 바인딩(DataBinder 이용) 작업이 이루어지기 때문이다. 내부적으로 가지고 있는 <strong>Converter 중 필드에 할당된 타입으로 전환시킬 수 있는 가장 구체적인 Converter를 골라 사용</strong>한다. 대부분의 <code class="language-plaintext highlighter-rouge">StringTo{기본타입}Converter</code>들은 기본적으로 등록되어 있다.</p>

<p>만약 다른 값으로 커스텀해서 바인딩 시키고자 한다면 직접 Converter를 작성해서 등록해줘야한다.</p>

<p><br /></p>

<h3 id="string을-enum으로-바꿔주는-기본-converter">String을 Enum으로 바꿔주는 기본 Converter</h3>
<p>Enum으로 Convert 해주는 기본적인 Converter는 <code class="language-plaintext highlighter-rouge">StringToEnumConverterFactory</code>이다. 일반적인 Converter와 ConverterFactory의 차이는 ConverterFactory는 추상적인 클래스타입을 받아 구체적인 클래스타입으로 체크하고 Converter를 만들어 준다. 즉, 여러가지 타입간 변환을 처리할 수 있다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 이 파일은 java 임</span>
<span class="kd">final</span> <span class="kd">class</span> <span class="nc">StringToEnumConverterFactory</span> <span class="kd">implements</span> <span class="nc">ConverterFactory</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Enum</span><span class="o">&gt;</span> <span class="o">{</span>  
  
   <span class="nd">@Override</span>  
   <span class="kd">public</span> <span class="o">&lt;</span><span class="no">T</span> <span class="kd">extends</span> <span class="nc">Enum</span><span class="o">&gt;</span> <span class="nc">Converter</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="no">T</span><span class="o">&gt;</span> <span class="nf">getConverter</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">targetType</span><span class="o">)</span> <span class="o">{</span>  
      <span class="k">return</span> <span class="k">new</span> <span class="nf">StringToEnum</span><span class="o">(</span><span class="nc">ConversionUtils</span><span class="o">.</span><span class="na">getEnumType</span><span class="o">(</span><span class="n">targetType</span><span class="o">));</span>  
   <span class="o">}</span>  

   <span class="kd">private</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">StringToEnum</span><span class="o">&lt;</span><span class="no">T</span> <span class="kd">extends</span> <span class="nc">Enum</span><span class="o">&gt;</span> <span class="kd">implements</span> <span class="nc">Converter</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="no">T</span><span class="o">&gt;</span> <span class="o">{</span>  
      <span class="kd">private</span> <span class="kd">final</span> <span class="nc">Class</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">enumType</span><span class="o">;</span>  
      
      <span class="nc">StringToEnum</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">enumType</span><span class="o">)</span> <span class="o">{</span>  
         <span class="k">this</span><span class="o">.</span><span class="na">enumType</span> <span class="o">=</span> <span class="n">enumType</span><span class="o">;</span>  
      <span class="o">}</span>  
      
      <span class="nd">@Override</span>  
      <span class="nd">@Nullable</span>
      <span class="kd">public</span> <span class="no">T</span> <span class="nf">convert</span><span class="o">(</span><span class="nc">String</span> <span class="n">source</span><span class="o">)</span> <span class="o">{</span>  
         <span class="k">if</span> <span class="o">(</span><span class="n">source</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span> <span class="o">{</span>  
            <span class="k">return</span> <span class="kc">null</span><span class="o">;</span>  
         <span class="o">}</span>  
         <span class="k">return</span> <span class="o">(</span><span class="no">T</span><span class="o">)</span> <span class="nc">Enum</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">enumType</span><span class="o">,</span> <span class="n">source</span><span class="o">.</span><span class="na">trim</span><span class="o">());</span>  
      <span class="o">}</span>  
   <span class="o">}</span>  
  
<span class="o">}</span>
</code></pre></div></div>

<p>필자 또한 다양한 Enum 클래스에 대한 처리가 필요하기에 Converter가 아닌 ConverterFactory를 이용하여 커스텀 Converter를 구현했다.</p>

<p><br /></p>

<h3 id="legacy용-converterfactory-작성하기">Legacy용 ConverterFactory 작성하기</h3>
<p><a href="https://rokwonk.github.io/project/legacy-enum-management-1/">Legacy Enum 관리하기 Body편</a>에서 작성한 것과 매커니즘은 거의 똑같다. 쿼리는 요청시에만 사용되므로 소문자 문자열을 대문자 Enum 값으로 바인딩하는 로직만 작성하면 된다.</p>

<p>마찬가지로 <code class="language-plaintext highlighter-rouge">LegacyDtoEnum</code> <strong>인터페이스 타입을 사용했다</strong>. 모든 Enum 타입에 적용하기에는 이후 Legacy가 아닌 Enum을 만들경우 매우 위험하다. 그렇다고 구체적인 하나의 Enum 타입에만 적용하기에는 같은 로직의 Converter를 여러개 작성해야한다. ConverterFactory와 인터페이스를 이용하면 효율적으로 필요한 부분에만 적용시킬 수 있다.</p>

<p>Converter가 처리할 수 있는 타입을 Enum 클래스 타입이자 LegacyDtoEnum을 상속받은 경우에만 가능하도록 만들었다. Factory에서 Converter를 만들때 구체적인 타입을 넘겨 enum 값들을 불러올 수 있도록 하고, Converter 로직은 소문자 문자열을 대문자로 바꿔 Enum 값과 매칭되는 값으로 선택되도록 만들었다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">StringToLegacyDtoEnumConverterFactory</span><span class="p">&lt;</span><span class="nc">F</span><span class="p">&gt;:</span> <span class="nc">ConverterFactory</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">,</span> <span class="nc">F</span><span class="p">&gt;</span> <span class="k">where</span> <span class="nc">F</span><span class="p">:</span> <span class="nc">Enum</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;,</span> <span class="nc">F</span><span class="p">:</span> <span class="nc">LegacyDtoEnum</span> <span class="p">{</span>  
    <span class="k">override</span> <span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span> <span class="p">:</span> <span class="nc">F</span><span class="p">&gt;</span> <span class="nf">getConverter</span><span class="p">(</span><span class="n">targetType</span><span class="p">:</span> <span class="nc">Class</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;):</span> <span class="nc">Converter</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">,</span> <span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>  
        <span class="k">return</span> <span class="nc">StringToEnumConverter</span><span class="p">(</span><span class="n">targetType</span><span class="p">)</span>  
    <span class="p">}</span>  
  
    <span class="nd">@Suppress</span><span class="p">(</span><span class="s">"UNCHECKED_CAST"</span><span class="p">)</span>  
    <span class="k">private</span> <span class="kd">class</span> <span class="nc">StringToEnumConverter</span><span class="p">&lt;</span><span class="nc">T</span> <span class="p">:</span> <span class="nc">Enum</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;?&gt;(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">enumType</span><span class="p">:</span> <span class="nc">Class</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;)</span> <span class="p">:</span> <span class="nc">Converter</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">,</span> <span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>  
        <span class="k">override</span> <span class="k">fun</span> <span class="nf">convert</span><span class="p">(</span><span class="n">source</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">T</span> <span class="p">{</span>  
            <span class="kd">val</span> <span class="py">value</span> <span class="p">=</span> <span class="n">source</span><span class="p">.</span><span class="nf">uppercase</span><span class="p">()</span>  
            <span class="kd">val</span> <span class="py">enumValue</span> <span class="p">=</span> <span class="p">(</span><span class="n">enumType</span> <span class="k">as</span> <span class="nc">Class</span><span class="p">&lt;</span><span class="k">out</span> <span class="nc">Enum</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;&gt;)</span>  
                <span class="p">.</span><span class="n">enumConstants</span>  
                <span class="p">.</span><span class="nf">firstOrNull</span> <span class="p">{</span> <span class="n">it</span><span class="p">.</span><span class="n">name</span> <span class="p">==</span> <span class="n">value</span> <span class="p">}</span>  
                <span class="o">?:</span> <span class="k">throw</span> <span class="nc">ApiInvalidEnumException</span><span class="p">()</span>  
            <span class="k">return</span> <span class="n">enumValue</span> <span class="k">as</span> <span class="nc">T</span>  
        <span class="p">}</span>  
    <span class="p">}</span>  
<span class="p">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="converter-적용하기">Converter 적용하기</h3>
<p>WebMvc 설정에 Converter를 전역적으로 등록할 수 있다. <code class="language-plaintext highlighter-rouge">WebMvcConfigurer</code> 인터페이스를 사용하면 스프링 부트가 기존에 제공하는 기능에 더해 추가적인 커스텀 기능을 추가할 수 있다.</p>

<p>먼저, RequestParam이나 ModelAttribute에 사용하는 Legacy Enum들에 <code class="language-plaintext highlighter-rouge">LegacyDtoEnum</code>를 상속시켜준다. Enum 클래스이자 해당 인터페이스를 상속한 타입반 Converter가 동작하도록 Converter를 만들때 제약조건을 걸어줬던 것을 기억할 것이다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">enum</span> <span class="kd">class</span> <span class="nc">AnswerSortType</span><span class="p">:</span> <span class="nc">LegacyDtoEnum</span> <span class="p">{</span>
    <span class="nc">NEWEST</span><span class="p">,</span>
    <span class="nc">OLDEST</span><span class="p">,</span>
    <span class="nc">BEST</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>아래와 같이 ConverterFactory를 등록하면 원하던대로 잘 동작한다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>  
<span class="kd">class</span> <span class="nc">WebMvcConfig</span><span class="p">:</span> <span class="nc">WebMvcConfigurer</span> <span class="p">{</span>  
	<span class="c1">// ...</span>
  
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">addFormatters</span><span class="p">(</span><span class="n">registry</span><span class="p">:</span> <span class="nc">FormatterRegistry</span><span class="p">)</span> <span class="p">{</span>  
        <span class="n">registry</span><span class="p">.</span><span class="nf">addConverterFactory</span><span class="p">(</span><span class="nc">StringToLegacyDtoEnumConverterFactory</span><span class="p">())</span>  
    <span class="p">}</span>  
<span class="p">}</span>
</code></pre></div></div>

<p>이제 요청/응답 Body, Query에서의 Legacy Enum을 처리할 수 있게 되었다. Enum에 인터페이스를 더해 가독성 좋고 확장성 좋게 Legacy를 다룰 수 있었다. 이제 DB에서 사용중인 Legacy Enum을 다루는 방법만 남았다.</p>

<p><br /></p>]]></content><author><name>RokWon K</name></author><category term="Project" /><summary type="html"><![CDATA[RequestParam, ModelAttribute 내 Legacy Enum들을 깔끔하고 효율적으로 관리해보자]]></summary></entry><entry><title type="html">Body, Query, Entity에 포함된 Legacy Enum 효율적으로 관리하기(3) Entity편</title><link href="https://rokwonk.github.io/project/legacy-enum-management-3/" rel="alternate" type="text/html" title="Body, Query, Entity에 포함된 Legacy Enum 효율적으로 관리하기(3) Entity편" /><published>2024-03-29T00:00:00+09:00</published><updated>2024-03-29T00:00:00+09:00</updated><id>https://rokwonk.github.io/project/Legacy-Enum-%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9C%BC%EB%A1%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0-3-Entity%ED%8E%B8</id><content type="html" xml:base="https://rokwonk.github.io/project/legacy-enum-management-3/"><![CDATA[<p><br /></p>

<p>요청/응답 Body와 Query에 담긴 Legacy Enum을 다루는데 성공하였다. 남은 건 DB에 존재하는 Enum 뿐.</p>

<p>과거의 실수로 MySQL DB의 컬럼정의가 mysql enum타입인 것이 몇 개 존재한다. 그리고 그 값들도 전부 소문자이다. 현재 마이그레이션 되는 프로젝트에서는 JPA를 사용하고 있어 JPA Entity와 DB의 테이블 정의를 일치시켜 줘야한다.</p>

<p>현재로서는 mysql enum 타입을 다른 타입으로 마이그레이션하기에는 너무 수고롭다. 우선은 DB를 직접 건드리지 않고 어플리케이션에서 Enum으로 처리하기로 결정했다.</p>

<p>코틀린의 Enum 값은 대문자 사용을 권장한다. 때문에 DB와 소통시 대문자 Enum &lt;-&gt; 소문자 문자열로 변환할 필요가 있다. 이를 다루기 위해 구현한 방법을 공유해본다.</p>

<blockquote>
  <p>Body, Query, Entity 부분을 각각 포스팅을 나누어 작성하였습니다.</p>
  <ul>
    <li><a href="https://rokwonk.github.io/project/legacy-enum-management-1/">요청/응답 Body에 포함된 Legacy Enum 관리하기</a></li>
    <li><a href="https://rokwonk.github.io/project/legacy-enum-management-2/">Query에 포함된 Legacy Enum 관리하기</a></li>
    <li><strong>(Now) Entity에서 DB에 사용중인 Legacy Enum 관리하기</strong></li>
  </ul>
</blockquote>

<p><br /></p>

<h3 id="jpa-entity에서-enum-사용시">JPA Entity에서 Enum 사용시</h3>
<p>JPA Entity에서 Enum을 사용을 한다면 다음과 같은 방식을 이용한다. <code class="language-plaintext highlighter-rouge">@Enumerated(EnumType.STRING)</code>을 통해 Enum 값이 그대로 문자열로 변환되어 DB값에 적용된다.</p>

<p><code class="language-plaintext highlighter-rouge">@Enumerated(EnumType.ORDINAL)</code> 사용할 수도 있지만 이는 Enum의 순서 값(1, 2)이 DB값으로 들어간다. 가독성도 확장성도 좋지 못해 많이 사용되지는 않는다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Entity</span>  
<span class="nd">@Table</span><span class="p">(</span><span class="n">name</span> <span class="p">=</span> <span class="s">"users"</span><span class="p">)</span>  
<span class="kd">class</span> <span class="nc">UserEntity</span><span class="p">(</span>  
    <span class="c1">// ...</span>
  
    <span class="nd">@Column</span><span class="p">(</span><span class="n">name</span> <span class="p">=</span> <span class="s">"status"</span><span class="p">)</span>
    <span class="nd">@Enumerated</span><span class="p">(</span><span class="nc">EnumType</span><span class="p">.</span><span class="nc">STRING</span><span class="p">)</span>
    <span class="kd">var</span> <span class="py">status</span><span class="p">:</span> <span class="nc">UserStatus</span> <span class="p">=</span> <span class="nc">UserStatus</span><span class="p">.</span><span class="nc">ACTIVE</span><span class="p">,</span>
    
    <span class="c1">// ...</span>
<span class="p">)</span>
</code></pre></div></div>

<p>‘개발 한 스푼’ 어플리케이션에서 문제는 mysql enum 타입을 사용하고 있으며 값이 소문자라는 것이다. mysql enum 타입의 경우 다음과 같이 컬럼정의를 직접 넣어줘야 문제가 발생하지 않는다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Column</span><span class="p">(</span><span class="n">name</span> <span class="p">=</span> <span class="s">"status"</span> <span class="p">,</span> <span class="n">columnDefinition</span><span class="p">=</span><span class="s">"ENUM('active','exit')"</span><span class="p">)</span>
<span class="kd">var</span> <span class="py">status</span><span class="p">:</span> <span class="nc">UserStatus</span> <span class="p">=</span> <span class="nc">UserStatus</span><span class="p">.</span><span class="nc">ACTIVE</span><span class="p">,</span>
</code></pre></div></div>

<p>아이러니하게 Enum 값을 소문자로 작성해도 DB에서 Entity로 변환시 대문자 Enum값을 찾는다. MySQL 버전에 따라 달라지는 것 같기도..? 그래서 잠깐이나마 다음과 같이 쓸 생각도 해봤다… 돌아가긴 하더라..</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">enum</span> <span class="kd">class</span> <span class="nc">UserStatus</span> <span class="p">{</span>  
    <span class="nc">ACTIVE</span><span class="p">,</span> <span class="nc">EXIT</span><span class="p">,</span> <span class="n">active</span><span class="p">,</span> <span class="n">exit</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="커스텀이-필요할때">커스텀이 필요할때</h3>
<p>방법을 강구하다 <code class="language-plaintext highlighter-rouge">AttributeConverter</code>의 존재를 알 수 있었다.(동시에 우아한 형제들의 테크블로그도 굴러 들어왔다.)</p>

<p><code class="language-plaintext highlighter-rouge">AttributeConverter</code> 인터페이스는 Entity의 속성과 DB 컬럼값 간 커스텀 변환로직을 만들 수 있도록 도와준다. X타입은 Entity 속성의 타입이며 Y타입은 DB 커럼의 타입이다. 해당 인터페이스 하나로 양방향 변환을 해결할 수 있다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="kd">interface</span> <span class="nc">AttributeConverter</span><span class="p">&lt;</span><span class="nc">X</span><span class="p">,</span><span class="nc">Y</span><span class="p">&gt;</span> <span class="p">{</span>
	<span class="k">public</span> <span class="nc">Y</span> <span class="nf">convertToDatabaseColumn</span> <span class="p">(</span><span class="nc">X</span> <span class="n">attribute</span><span class="p">);</span>
    <span class="k">public</span> <span class="nc">X</span> <span class="nf">convertToEntityAttribute</span> <span class="p">(</span><span class="nc">Y</span> <span class="n">dbData</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="legacy-enum-다루는-attributeconverter-작성하기">Legacy Enum 다루는 AttributeConverter 작성하기</h3>
<p>대소문자간 변환이 필요한 Legacy Enum들이 N개가 존재한다. 모든 Enum들에 대소문자로 변환하는 로직을 반복해서 작성할 수는 없는법! <a href="https://rokwonk.github.io/project/legacy-enum-management-1/">앞선 포스팅들</a>처럼 <strong>공통 로직 구현체를 재사용하는 방향</strong>으로 만들어보자.</p>

<p>우선 inteface를 만들어 Legacy Enum임을 표시하도록 하자. 필자의 로직에서 인터페이스를 만들지 않아도 상관없었다. 하지만 인터페이스로 제약조건을 걸어 Legacy가 아닌 다른 Enum에서 실수로 적용하는 일을 막기 위해 사용하기로 했다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">LegacyEntityEnum</span>
</code></pre></div></div>

<p>Legacy Enum을 처리하는 공통로직을 가진 <code class="language-plaintext highlighter-rouge">AttributeConverter</code>는 다음과 같이 작성했다. Entity에서 DB 컬럼으로 보낼땐 단순히 소문자로 바꿔 처리하면 되지만, DB 컬럼 값을 Enum에 매핑하기 위해서는 값을 순회하여 컬럼의 대문자와 같은 값을 사용한다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">abstract</span> <span class="kd">class</span> <span class="nc">LegacyEntityEnumConverter</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span>  
    <span class="k">private</span> <span class="kd">var</span> <span class="py">enumType</span><span class="p">:</span> <span class="nc">Class</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;,</span>  
    <span class="k">private</span> <span class="kd">var</span> <span class="py">nullable</span><span class="p">:</span> <span class="nc">Boolean</span> <span class="p">=</span> <span class="k">false</span>  
<span class="p">)</span> <span class="p">:</span> <span class="nc">AttributeConverter</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">?,</span> <span class="nc">String</span><span class="p">?&gt;</span> <span class="k">where</span> <span class="nc">T</span><span class="p">:</span> <span class="nc">Enum</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;,</span> <span class="nc">T</span><span class="p">:</span><span class="nc">LegacyEntityEnum</span> <span class="p">{</span>  
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">convertToDatabaseColumn</span><span class="p">(</span><span class="n">attribute</span><span class="p">:</span> <span class="nc">T</span><span class="p">?):</span> <span class="nc">String</span><span class="p">?</span> <span class="p">{</span>  
        <span class="k">if</span> <span class="p">(!</span><span class="n">nullable</span> <span class="p">&amp;&amp;</span> <span class="n">attribute</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>  
            <span class="k">throw</span> <span class="nc">DomainInvalidAttributeException</span><span class="p">(</span><span class="n">type</span> <span class="p">=</span> <span class="n">enumType</span><span class="p">.</span><span class="n">simpleName</span><span class="p">)</span>  
        <span class="p">}</span>  
        
        <span class="k">return</span> <span class="n">attribute</span><span class="o">?.</span><span class="n">name</span><span class="o">?.</span><span class="nf">lowercase</span><span class="p">()</span>  
    <span class="p">}</span>  
    
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">convertToEntityAttribute</span><span class="p">(</span><span class="n">dbData</span><span class="p">:</span> <span class="nc">String</span><span class="p">?):</span> <span class="nc">T</span><span class="p">?</span> <span class="p">{</span>  
        <span class="k">if</span> <span class="p">(!</span><span class="n">nullable</span> <span class="p">&amp;&amp;</span> <span class="n">dbData</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>  
            <span class="k">throw</span> <span class="nc">DomainInvalidAttributeException</span><span class="p">(</span><span class="n">type</span> <span class="p">=</span> <span class="n">enumType</span><span class="p">.</span><span class="n">simpleName</span><span class="p">)</span>  
        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">nullable</span> <span class="p">&amp;&amp;</span> <span class="n">dbData</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>  
            <span class="k">return</span> <span class="k">null</span>  
        <span class="p">}</span>  
        
        <span class="kd">val</span> <span class="py">data</span> <span class="p">=</span> <span class="n">dbData</span><span class="o">?.</span><span class="nf">uppercase</span><span class="p">()</span>  
        <span class="k">return</span> <span class="n">enumType</span><span class="p">.</span><span class="n">enumConstants</span>  
            <span class="p">.</span><span class="nf">firstOrNull</span> <span class="p">{</span> <span class="n">it</span><span class="p">.</span><span class="n">name</span> <span class="p">==</span> <span class="n">data</span> <span class="p">}</span>  
            <span class="o">?:</span> <span class="k">throw</span> <span class="nc">DomainInvalidAttributeException</span><span class="p">(</span><span class="n">type</span> <span class="p">=</span> <span class="n">enumType</span><span class="p">.</span><span class="n">simpleName</span><span class="p">)</span>  
    <span class="p">}</span>  
<span class="p">}</span>
</code></pre></div></div>

<p>아쉬운 점은 <code class="language-plaintext highlighter-rouge">AttributeConverter</code>를 사용시 구체적인 타입을 받아올 수 없다. 때문에 각 Legacy Enum에서 해당 Converter를 이용해 구현하면서 구체적인 타입(enumType)을 넘겨줘야 한다.</p>

<p>추가적으로 Enity의 속성이 null이 가능할 수 있기 때문에 nullable 이라는 파라미터도 추가하였다.</p>

<p><br /></p>

<h3 id="적용하기">적용하기</h3>
<p>각 Enum에서 해당 Converter를 이용하도록 한다. 구체적인 타입을 넘겨주고 @Converter 어노테이션과 autoApply 속성을 true로 두면 자동으로 등록되어 처리된다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">enum</span> <span class="kd">class</span> <span class="nc">UserStatus</span><span class="p">:</span> <span class="nc">LegacyEntityEnum</span> <span class="p">{</span>  
    <span class="nc">ACTIVE</span><span class="p">,</span> <span class="nc">EXIT</span><span class="p">;</span>  
  
    <span class="nd">@Converter</span><span class="p">(</span><span class="n">autoApply</span> <span class="p">=</span> <span class="k">true</span><span class="p">)</span>  
    <span class="kd">class</span> <span class="nc">LegacyConverter</span><span class="p">:</span> <span class="nc">LegacyEntityEnumConverter</span><span class="p">&lt;</span><span class="nc">UserStatus</span><span class="p">&gt;(</span><span class="nc">UserStatus</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span><span class="p">,</span> <span class="k">true</span><span class="p">)</span>  
<span class="p">}</span>
</code></pre></div></div>

<p>Enum마다 Converter를 작성해야하긴 하지만 같은 변환 로직을 또 다시 구현하지 않아도 된다는 점이 매력만점이다. 이것으로 인터페이스를 이용한 3종류(Body, Query, Entity)의 Legacy Enum을 다뤘던 경험공유를 마친다.</p>

<p><br /></p>

<h3 id="참고">참고</h3>
<p>우아한 형제들의 테크 블로그에서 많은, 대부분의 영감을 얻었다. 해당 블로그보다는 훨씬 쉬운 상황이라 구현하기에는 간편했던거 같다.</p>
<ul>
  <li><a href="https://techblog.woowahan.com/2600/">우아한형제들 - Legacy DB의 JPA Entity Mapping</a></li>
</ul>

<p><br /></p>]]></content><author><name>RokWon K</name></author><category term="Project" /><summary type="html"><![CDATA[Jpa Entity에서 DB에 사용중인 Legacy Enum들을 깔끔하고 효율적으로 관리해보자]]></summary></entry><entry><title type="html">Body, Query, Entity에 포함된 Legacy Enum 효율적으로 관리하기(1) Body편</title><link href="https://rokwonk.github.io/project/legacy-enum-management-1/" rel="alternate" type="text/html" title="Body, Query, Entity에 포함된 Legacy Enum 효율적으로 관리하기(1) Body편" /><published>2024-03-28T00:00:00+09:00</published><updated>2024-03-28T00:00:00+09:00</updated><id>https://rokwonk.github.io/project/Legacy-Enum-%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9C%BC%EB%A1%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0-1-Body%ED%8E%B8</id><content type="html" xml:base="https://rokwonk.github.io/project/legacy-enum-management-1/"><![CDATA[<p><br /></p>

<p>운영중이던 ‘개발 한 스푼’ 서비스의 API 서버를 서버리스 아키텍쳐에서 스프링 부트로 마이그레이션중이다.<br />
마이그레이션을 진행하면서 API 요청이나 응답 Body, Query 그리고 JPA Entity에 포함된 Enum들이 전부 소문자로 작성되어 있는 것이 문제가 되었다.</p>

<p>코틀린의 경우 Enum 값을 대문자로 작성하는 것을 권장하는데 기존 정보들만 소문자로 두기엔 뭔가 마음 한 편이 불편하고 그렇다고 모든 Enum의 값을 소문자로 쓸 수도 없는 노릇이다.</p>

<p>이러한 Legacy Enum들을 조금은 선언적이고 효율적으로 관리하기 위해서 필자가 적용한 방식을 공유해본다.</p>

<blockquote>
  <p>Body, Query, Entity 부분을 각각 포스팅을 나누어 작성하였습니다.</p>
  <ul>
    <li><strong>(Now) 요청/응답 Body에 포함된 Legacy Enum 관리하기</strong></li>
    <li><a href="https://rokwonk.github.io/project/legacy-enum-management-2/">Query에 포함된 Legacy Enum 관리하기</a></li>
    <li><a href="https://rokwonk.github.io/project/legacy-enum-management-3/">Entity에서 DB에 사용중인 Legacy Enum 관리하기</a></li>
  </ul>
</blockquote>

<p><br /></p>

<h3 id="기존-legacy-enum을-다룬-방법">기존 Legacy Enum을 다룬 방법</h3>
<p>REST API의 요청 body와 응답은 보통 JSON을 이용한다. 스프링 부트에서는 ObjectMapper(Jackson)를 통해서 요청 JSON을 정의한 객체로 역직렬화해주거나 응답객체를 JSON으로 직렬화시켜준다.</p>

<p>Jackson은 개발자가 직렬화/역직렬화 과정에 직접 참여하여 커스텀할 수 있는 기능을 제공해 주는데 직렬화시에는 <code class="language-plaintext highlighter-rouge">@JsonValue</code>를 역직렬화시에는 <code class="language-plaintext highlighter-rouge">@JsonCreator</code> 어노테이션을 사용하면 된다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 요청 객체</span>
<span class="kd">data class</span> <span class="nc">LoginRequest</span><span class="p">(</span>
	<span class="kd">val</span> <span class="py">token</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span>
	<span class="kd">val</span> <span class="py">type</span><span class="p">:</span> <span class="nc">LoginType</span><span class="p">,</span>
<span class="p">)</span>

<span class="c1">// type enum</span>
<span class="k">enum</span> <span class="kd">class</span> <span class="nc">LoginType</span><span class="p">(</span>
	<span class="nd">@JsonValue</span> <span class="kd">val</span> <span class="py">value</span><span class="p">:</span> <span class="nc">String</span>
<span class="p">)</span> <span class="p">{</span>
	<span class="nc">KAKAO</span><span class="p">(</span><span class="s">"kakao"</span><span class="p">),</span>
	<span class="nc">APPLE</span><span class="p">(</span><span class="s">"apple"</span><span class="p">);</span>
	
	<span class="k">companion</span> <span class="k">object</span> <span class="p">{</span>
		<span class="nd">@JvmStatic</span>
		<span class="nd">@JsonCreator</span>
		<span class="k">fun</span> <span class="nf">from</span><span class="p">(</span><span class="n">lower</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">=</span>
			<span class="nf">values</span><span class="p">().</span><span class="nf">find</span> <span class="p">{</span> <span class="n">it</span><span class="p">.</span><span class="n">value</span> <span class="p">==</span> <span class="n">lower</span> <span class="p">}</span>
			<span class="o">?:</span> <span class="k">throw</span> <span class="nc">IllegalArgumentException</span><span class="p">(</span><span class="s">"Unknown LoginType: $lower"</span><span class="p">)</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>하지만 Legacy Enum의 수가 많다면 Enum마다 해당 코드를 반복 작성해야하기 때문에 퍽 난감하다. 때문에 더 나은 방법을 찾다 인터페이스를 활용하여 처리해보았다.</p>

<p><br /></p>

<h3 id="legacy인-enum들-인터페이스로-전환-로직-처리하기">Legacy인 Enum들 인터페이스로 전환 로직 처리하기</h3>
<p>Legacy Enum들은 (외부)소문자 &lt;-&gt; 대문자(앱 내) 간 변환이라는 공통점을 가진다. 동일한 행동을 취한다면 하나로 묶어주는게 간편하다. 인터페이스를 만들고 해당 인터페이스를 상속하는 Enum들이 취해야하는 공통 로직을 따로 작성하면 좋을 것 같다.</p>

<p>우선, <code class="language-plaintext highlighter-rouge">LegacyDtoEnum</code> 이라는 인터페이스를 선언한다. 코틀린에서는 인터페이스나 클래스에 구현부가 없다면 <code class="language-plaintext highlighter-rouge">{ }</code>를 생략할 수 있다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">LegacyDtoEnum</span>
</code></pre></div></div>

<p>이후, Legacy Enum들에 해당 인터페이스를 상속받는다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">enum</span> <span class="kd">class</span> <span class="nc">LoginType</span><span class="p">:</span> <span class="nc">LegacyDtoEnum</span> <span class="p">{</span>  
    <span class="nc">KAKAO</span><span class="p">,</span>  
    <span class="nc">APPLE</span><span class="p">;</span>  
<span class="p">}</span>
</code></pre></div></div>

<p>이제 이 인터페이스를 구현하는 enum값들이 역/직렬화시 어떤 로직이 실행될지를 직접 정의해야 한다.</p>

<p><br /></p>

<h3 id="커스텀-serializer-deserializer-구현하기">커스텀 Serializer, Deserializer 구현하기</h3>
<p>Jackson에서는 역/직렬화를 처리하는 커스텀 클래스를 만들 수 있도록 지원해준다. <code class="language-plaintext highlighter-rouge">StdDeserializer</code>와 <code class="language-plaintext highlighter-rouge">StdSerializer</code>를 상속받아 각각 역직렬화, 직렬화하는 커스텀 로직을 작성하고 적용하면 된다.</p>

<p>적용 시에는 필드나 클래스에 <code class="language-plaintext highlighter-rouge">@JsonDeserialize(using = 적용할 커스텀 클래스타입)</code> 어노테이션을 붙여주면 된다. 필자는 커스텀 직렬화 로직을 적용할 Enum들이 <code class="language-plaintext highlighter-rouge">LegacyDtoEnum</code>을 상속 받도록 만들었기 때문에 해당 인터페이스에 작성하면 모든 Enum들에 적용된다. 깔끔하면서 선언적이라 가독성이 좋다! 만들면서 내심 기분이 좋았다.</p>

<p>역직렬화 시에는 소문자를 대문자 Enum 매핑하고 직렬화시에는 대문자 Enum을 소문자 문자열로 교체하는 로직을 작성하면 된다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">LegacyDtoEnumCombinedSerializer</span> <span class="p">{</span>  
	<span class="c1">// 역직렬화 커스텀 로직</span>
    <span class="kd">class</span> <span class="nc">LegacyDtoEnumDeserializer</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span><span class="n">vc</span><span class="p">:</span> <span class="nc">Class</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;?):</span> <span class="nc">StdDeserializer</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span><span class="n">vc</span><span class="p">),</span> <span class="nc">ContextualDeserializer</span> <span class="k">where</span> <span class="nc">T</span> <span class="p">:</span> <span class="nc">LegacyDtoEnum</span><span class="p">,</span> <span class="nc">T</span><span class="p">:</span> <span class="nc">Enum</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;</span> <span class="p">{</span>  
        <span class="k">constructor</span><span class="p">()</span> <span class="p">:</span> <span class="k">this</span><span class="p">(</span><span class="k">null</span><span class="p">)</span>  
  
        <span class="nd">@Suppress</span><span class="p">(</span><span class="s">"UNCHECKED_CAST"</span><span class="p">)</span>  
        <span class="k">override</span> <span class="k">fun</span> <span class="nf">deserialize</span><span class="p">(</span><span class="n">p</span><span class="p">:</span> <span class="nc">JsonParser</span><span class="p">,</span> <span class="n">ctxt</span><span class="p">:</span> <span class="nc">DeserializationContext</span><span class="p">):</span> <span class="nc">T</span> <span class="p">{</span>  
            <span class="kd">val</span> <span class="py">jsonNode</span><span class="p">:</span> <span class="nc">JsonNode</span> <span class="p">=</span> <span class="n">p</span><span class="p">.</span><span class="n">codec</span><span class="p">.</span><span class="nf">readTree</span><span class="p">(</span><span class="n">p</span><span class="p">)</span>  
            <span class="kd">val</span> <span class="py">value</span><span class="p">:</span> <span class="nc">String</span> <span class="p">=</span> <span class="n">jsonNode</span><span class="p">.</span><span class="nf">asText</span><span class="p">().</span><span class="nf">uppercase</span><span class="p">()</span>  
            <span class="kd">val</span> <span class="py">enumValue</span> <span class="p">=</span> <span class="p">(</span><span class="n">_valueClass</span> <span class="k">as</span> <span class="nc">Class</span><span class="p">&lt;</span><span class="k">out</span> <span class="nc">Enum</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;&gt;)</span>  
                <span class="p">.</span><span class="n">enumConstants</span>  
                <span class="p">.</span><span class="nf">firstOrNull</span> <span class="p">{</span> <span class="n">it</span><span class="p">.</span><span class="n">name</span> <span class="p">==</span> <span class="n">value</span> <span class="p">}</span>  
                <span class="o">?:</span> <span class="k">throw</span> <span class="nc">ApiInvalidEnumException</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">currentName</span><span class="p">)</span>  
  
            <span class="k">return</span> <span class="n">enumValue</span> <span class="k">as</span> <span class="nc">T</span>  
        <span class="p">}</span>  
  
        <span class="k">override</span> <span class="k">fun</span> <span class="nf">createContextual</span><span class="p">(</span><span class="n">ctxt</span><span class="p">:</span> <span class="nc">DeserializationContext</span><span class="p">,</span> <span class="n">property</span><span class="p">:</span> <span class="nc">BeanProperty</span><span class="p">):</span> <span class="nc">JsonDeserializer</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;</span> <span class="p">{</span>  
            <span class="k">return</span> <span class="nc">LegacyDtoEnumDeserializer</span><span class="p">(</span><span class="n">property</span><span class="p">.</span><span class="n">type</span><span class="p">.</span><span class="n">rawClass</span><span class="p">)</span>  
        <span class="p">}</span>  
    <span class="p">}</span>  

	<span class="c1">// 직렬화 커스텀</span>
    <span class="kd">class</span> <span class="nc">LegacyDtoEnumSerializer</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span><span class="n">vc</span><span class="p">:</span> <span class="nc">Class</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;?):</span> <span class="nc">StdSerializer</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span><span class="n">vc</span><span class="p">,</span> <span class="k">true</span><span class="p">),</span> <span class="nc">ContextualSerializer</span> <span class="k">where</span> <span class="nc">T</span> <span class="p">:</span> <span class="nc">LegacyDtoEnum</span><span class="p">,</span> <span class="nc">T</span><span class="p">:</span> <span class="nc">Enum</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;</span> <span class="p">{</span>  
        <span class="k">constructor</span><span class="p">()</span> <span class="p">:</span> <span class="k">this</span><span class="p">(</span><span class="k">null</span><span class="p">)</span>  
  
        <span class="k">override</span> <span class="k">fun</span> <span class="nf">serialize</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">,</span> <span class="n">gen</span><span class="p">:</span> <span class="nc">JsonGenerator</span><span class="p">,</span> <span class="n">provider</span><span class="p">:</span> <span class="nc">SerializerProvider</span><span class="p">)</span> <span class="p">{</span>  
            <span class="n">gen</span><span class="p">.</span><span class="nf">writeString</span><span class="p">(</span><span class="n">value</span><span class="p">.</span><span class="n">name</span><span class="p">.</span><span class="nf">lowercase</span><span class="p">())</span>  
        <span class="p">}</span>  
        
        <span class="k">override</span> <span class="k">fun</span> <span class="nf">createContextual</span><span class="p">(</span><span class="n">prov</span><span class="p">:</span> <span class="nc">SerializerProvider</span><span class="p">,</span> <span class="n">property</span><span class="p">:</span> <span class="nc">BeanProperty</span><span class="p">):</span> <span class="nc">JsonSerializer</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;</span> <span class="p">{</span>  
            <span class="k">return</span> <span class="nc">LegacyDtoEnumSerializer</span><span class="p">(</span><span class="n">property</span><span class="p">.</span><span class="n">type</span><span class="p">.</span><span class="n">rawClass</span><span class="p">)</span>  
        <span class="p">}</span>  
    <span class="p">}</span>  
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">LegacyDtoEnumDeserializer</code>와 <code class="language-plaintext highlighter-rouge">LegacyDtoEnumSerializer</code> 클래스가 각각 역직렬화, 직렬화 로직을 수행한다. 제네릭 T 타입이 <code class="language-plaintext highlighter-rouge">LegacyDtoEnum</code> 타입이자 Enum일 경우에만 사용가능하도록 제약을 걸었다.(<code class="language-plaintext highlighter-rouge">where T : LegacyDtoEnum, T: Enum&lt;*&gt;</code>)</p>

<p>한 가지 더 주목할 점은 <code class="language-plaintext highlighter-rouge">ContextualDeserializer</code> 인터페이스를 구현한다는 점이다. 특정한 타입이 아닌 추상화된 타입(인터페이스 &amp; Enum)을 이용하기 때문에 <code class="language-plaintext highlighter-rouge">_valueClass</code>에 타입이 들어가 있지 않다. 때문에 특정 타입을 재지정해 주어야 하는데 <code class="language-plaintext highlighter-rouge">ContextualDeserializer</code>를 상속받아 createContextual 메서드를 작성해주면 된다.</p>

<blockquote>
  <p><strong>JsonDeserializer와 StdDeserializer의 차이</strong></p>
  <ul>
    <li>JsonDeserializer는 ObjectMapper를 이용해 Json로 역질렬화하는 class. Docs에서는 커스텀 Deserializer를 작성한다면 이 클래스가 아닌 StdDeserializer를 상속받아 만들 것을 권장한다.</li>
    <li>StdDeserializer는 JsonDeserializer를 상속받았으며, 기본적으로 String으로부터 원시타입을 파싱할 수 있는 메서드들을 제공해준다.</li>
  </ul>
</blockquote>

<p><br /></p>

<h3 id="serializer-적용하기">Serializer 적용하기</h3>
<p>마지막으로 작성한 클래스를 적용이 필요한 필드나 클래스에 추가하면 된다. 특정 필드에 적용 시 다음과 같이 적용하면 되지만 현재 상황에서 이와 같이 작성하는 것은 Legacy Enum을 사용하는 모든 필드에 작성해야하므로 반복적인 작업일 뿐이다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">data class</span> <span class="nc">LoginRequest</span><span class="p">(</span>
	<span class="kd">val</span> <span class="py">token</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span>
	<span class="nd">@JsonDeserialize</span><span class="p">(</span><span class="n">using</span> <span class="p">=</span> <span class="nc">LegacyDtoEnumCombinedSerializer</span><span class="p">.</span><span class="nc">LegacyDtoEnumDeserializer</span><span class="o">::</span><span class="k">class</span><span class="p">)</span>  
	<span class="nd">@JsonSerialize</span><span class="p">(</span><span class="n">using</span> <span class="p">=</span> <span class="nc">LegacyDtoEnumCombinedSerializer</span><span class="p">.</span><span class="nc">LegacyDtoEnumSerializer</span><span class="o">::</span><span class="k">class</span><span class="p">)</span>  
	<span class="kd">val</span> <span class="py">type</span><span class="p">:</span> <span class="nc">LoginType</span><span class="p">,</span>
<span class="p">)</span>
</code></pre></div></div>

<p>필자는 <code class="language-plaintext highlighter-rouge">LegacyDtoEnum</code>을 상속한 모든 곳에 적용할 것이기 때문에 <code class="language-plaintext highlighter-rouge">LegacyDtoEnum</code>에 적용하였다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@JsonDeserialize</span><span class="p">(</span><span class="n">using</span> <span class="p">=</span> <span class="nc">LegacyDtoEnumCombinedSerializer</span><span class="p">.</span><span class="nc">LegacyDtoEnumDeserializer</span><span class="o">::</span><span class="k">class</span><span class="p">)</span>  
<span class="nd">@JsonSerialize</span><span class="p">(</span><span class="n">using</span> <span class="p">=</span> <span class="nc">LegacyDtoEnumCombinedSerializer</span><span class="p">.</span><span class="nc">LegacyDtoEnumSerializer</span><span class="o">::</span><span class="k">class</span><span class="p">)</span>  
<span class="kd">interface</span> <span class="nc">LegacyDtoEnum</span>
</code></pre></div></div>

<p>또 작성한 커스텀 클래스에 <code class="language-plaintext highlighter-rouge">@JsonComponent</code>를 적용하여 전역적으로 자동적용시킬 수도 있다. Jackson 모듈에 직접 등록되어 Jackson 동작시 해당 클래스로 처리할 수 있는 알맞은 타입들은 이를 이용해 역/직렬화 시켜준다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@JsonComponent</span>
<span class="kd">class</span> <span class="nc">LegacyDtoEnumCombinedSerializer</span> <span class="p">{</span>
	<span class="c1">// ...직렬화, 역직렬화 커스텀 클래스</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">@JsonComponent</code>를 통해서 적용시키면 좋을까 싶기도 했지만 인터페이스에 직접 적용하는게 보다 선언적이고 가독성이 좋아 해당 방식을 선택했다. 후에 각 Enum에서 <code class="language-plaintext highlighter-rouge">LegacyDtoEnum</code> 인터페이스를 보는 것만으로 동작흐름을 파악할 수 있을 거라 여겼고 <code class="language-plaintext highlighter-rouge">@JsonComponent</code> 를 통한 등록은 그 연결고리를 찾기가 쉽지 않을 것 같았다.</p>

<p>위 내용들을 전부 적용한 후 Legacy Enum을 비교해보면 확연히 깔끔하고 가독성이 좋아졌다는게 느껴진다. 이런 Legacy Enum들이 N개, 수십개가 있다면.. 설명하지 않아도 그 효능을 충분히 알 수 있을 것이다.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 적용 전</span>
<span class="k">enum</span> <span class="kd">class</span> <span class="nc">LoginType</span><span class="p">(</span>
	<span class="nd">@JsonValue</span> <span class="kd">val</span> <span class="py">value</span><span class="p">:</span> <span class="nc">String</span>
<span class="p">)</span> <span class="p">{</span>
	<span class="nc">KAKAO</span><span class="p">(</span><span class="s">"kakao"</span><span class="p">),</span>
	<span class="nc">APPLE</span><span class="p">(</span><span class="s">"apple"</span><span class="p">);</span>
	
	<span class="k">companion</span> <span class="k">object</span> <span class="p">{</span>
		<span class="nd">@JvmStatic</span>
		<span class="nd">@JsonCreator</span>
		<span class="k">fun</span> <span class="nf">from</span><span class="p">(</span><span class="n">lower</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">=</span>
			<span class="nf">values</span><span class="p">().</span><span class="nf">find</span> <span class="p">{</span> <span class="n">it</span><span class="p">.</span><span class="n">value</span> <span class="p">==</span> <span class="n">lower</span> <span class="p">}</span>
			<span class="o">?:</span> <span class="k">throw</span> <span class="nc">IllegalArgumentException</span><span class="p">(</span><span class="s">"Unknown LoginType: $lower"</span><span class="p">)</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="c1">// 인터페이스로 분리 &amp; 커스텀 로직 적용 후</span>
<span class="k">enum</span> <span class="kd">class</span> <span class="nc">LoginType</span><span class="p">:</span> <span class="nc">LegacyDtoEnum</span> <span class="p">{</span>  
    <span class="nc">KAKAO</span><span class="p">,</span>  
    <span class="nc">APPLE</span><span class="p">;</span>  
<span class="p">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="참고">참고</h3>
<ul>
  <li><a href="https://fasterxml.github.io/jackson-databind/javadoc/2.7/com/fasterxml/jackson/databind/deser/std/StdDeserializer.html">StdDeserializer</a></li>
  <li><a href="https://fasterxml.github.io/jackson-databind/javadoc/2.7/com/fasterxml/jackson/databind/JsonDeserializer.html">JsonDeserializer</a></li>
  <li><a href="https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/jackson/JsonComponent.html">JsonComponent</a></li>
  <li><a href="https://d2.naver.com/helloworld/0473330">Naver D2 - Jackson의 확장 구조를 파헤쳐 보자</a></li>
</ul>]]></content><author><name>RokWon K</name></author><category term="Project" /><summary type="html"><![CDATA[요청, 응답 Body 내 Legacy Enum들을 깔끔하고 효율적으로 관리해보자]]></summary></entry><entry><title type="html">Spring Boot DB통신(2) - 스프링 컨테이너는 어떻게 JPA를 제공하는가?</title><link href="https://rokwonk.github.io/spring/spring-boot-db-2/" rel="alternate" type="text/html" title="Spring Boot DB통신(2) - 스프링 컨테이너는 어떻게 JPA를 제공하는가?" /><published>2024-01-13T00:00:00+09:00</published><updated>2024-01-13T00:00:00+09:00</updated><id>https://rokwonk.github.io/spring/Spring-Boot-DB%ED%86%B5%EC%8B%A0-2-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-JPA%EB%A5%BC-%EC%A0%9C%EA%B3%B5%ED%95%98%EB%8A%94%EA%B0%80</id><content type="html" xml:base="https://rokwonk.github.io/spring/spring-boot-db-2/"><![CDATA[<p><br /></p>

<p>우리 개발자들이 JPA를 사용하기 위해 필요한 여러가지 구성요소들이 있다. 우리는 그 중 <code class="language-plaintext highlighter-rouge">EntityManager</code>를 주입 받고 이를 이용해 객체지향의 방식으로 DB와 소통한다. 우리가 편하게 <code class="language-plaintext highlighter-rouge">EntityManager</code>를 주입 받아서 사용할 때까지 스프링 컨테이너는 몇 가지 일을 수행하여 이를 효과적으로 사용할 수 있도록 돕는다.</p>

<p>먼저, JPA의 구성요소에 대해 살펴보고 <strong>이 구성요소들을 스프링 컨테이너가 어떻게 제공</strong>하는지 알아보자.</p>

<blockquote>
  <p><strong>❗️ 이 포스팅이 다루는 내용</strong><br />
<strong>스프링 컨테이너가 ‘개발자가 JPA를 사용하는데 필요한 것’을 어떻게 제공하는지에 포커스를 둔다.</strong> JPA의 사용과 관련된 내용은 초반에 JPA를 설명하기 위해 잠시 다루고 이후 거의 다루지 않는다.</p>
</blockquote>

<p><br /></p>

<h2 id="jpa와-구성요소">JPA와 구성요소</h2>
<p>JDBC가 편리한 데이터베이스 소통을 지원해주지만 실제로 어플리케이션을 개발할때 생산성이 떨어지는 문제점들이 존재한다. 어플리케이션이 추구하는 객체지향적인 로직과는 거리가 멀고, SQL 쿼리와 쿼리결과를 비선언적인 방식으로 직접 파싱해야 한다.</p>

<p>이러한 문제점들은 AOP, 여러가지 패턴들을 도입해서 어느정도 해결할 수 있지만 SQL 쿼리를 작성하는 것과 결과를 객체로 다시 매핑하는데 생길 수 있는 실수와 생산성 저하는 여전히 문제점으로 남는다.</p>

<p>ORM(Object-Relational Mapping)은 이러한 <strong>데이터베이스 통신과 객체 지향 프로그래밍 간의 간극을 줄이기 위해 생겨났다.</strong>  ORM을 사용하면 데이터베이스와의 상호작용을 객체지향적인 방식으로 이용할 수 있다.</p>

<p>JPA(Java Persistence API)는 이러한 ORM을 구현하기 위한 표준 인터페이스로 <strong>객체와 관계형 데이터베이스간의 불일치를 해소하고 객체로 데이터를 다룰 수 있도록 도와</strong>준다. JPA는 인터페이스(명세)로 다양한 구현체 프레임워크가 존재하는데 흔히 Hibernate를 사용한다.</p>

<p><br /></p>

<h3 id="jpa로-db와-통신하기">JPA로 DB와 통신하기</h3>
<p>JPA는 관계형 데이터베이스의 테이블과 객체를 매핑하며 객체지향의 방식으로 데이터베이스 통신을 도와준다. 관계형 데이터베이스에서의 테이블은 Entity라고 부르는 객체에 매핑한며 해당 객체와 JPA가 제공하는 <code class="language-plaintext highlighter-rouge">EntityManager</code>를 통해 객체지향적인 방식으로 데이터베이스와 통신을 한다.</p>

<p><code class="language-plaintext highlighter-rouge">EntityManager</code>는 <code class="language-plaintext highlighter-rouge">EntityManagerFactory</code>를 통해 만들어낸다. 이 둘은 이후에 이어서 설명하니 지금은 객체지향의 방식으로 DB와 통신하는 것이 EntityManager이고 이러한 EntityManager를 만들어내는 공장이 <code class="language-plaintext highlighter-rouge">EntityManagerFactory</code>라고만 이해해두자.</p>

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

    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">// EntityManagerFactory 생성, EntityManager 생성</span>
        <span class="nc">EntityManagerFactory</span> <span class="n">emf</span> <span class="o">=</span> <span class="nc">Persistence</span><span class="o">.</span><span class="na">createEntityManagerFactory</span><span class="o">(</span><span class="s">"myPersistenceUnit"</span><span class="o">);</span>
        <span class="nc">EntityManager</span> <span class="n">em</span> <span class="o">=</span> <span class="n">emf</span><span class="o">.</span><span class="na">createEntityManager</span><span class="o">();</span>

        <span class="c1">// 트랜잭션 시작</span>
        <span class="nc">EntityTransaction</span> <span class="n">tx</span> <span class="o">=</span> <span class="n">em</span><span class="o">.</span><span class="na">getTransaction</span><span class="o">();</span>
        <span class="n">tx</span><span class="o">.</span><span class="na">begin</span><span class="o">();</span>

        <span class="k">try</span> <span class="o">{</span>
            <span class="c1">// 데이터베이스에 저장할 엔티티 생성</span>
            <span class="nc">Member</span> <span class="n">member</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Member</span><span class="o">();</span>
            <span class="n">member</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="mi">1L</span><span class="o">);</span>
            <span class="n">member</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="s">"Rok"</span><span class="o">);</span>
            <span class="n">member</span><span class="o">.</span><span class="na">setAge</span><span class="o">(</span><span class="mi">25</span><span class="o">);</span>

            <span class="c1">// 엔티티를 영속화(Persistence Context에 추가)</span>
            <span class="n">em</span><span class="o">.</span><span class="na">persist</span><span class="o">(</span><span class="n">member</span><span class="o">);</span>
			
			<span class="c1">// Select 쿼리도 객체 이용</span>
	        <span class="nc">TypedQuery</span><span class="o">&lt;</span><span class="nc">Member</span><span class="o">&gt;</span> <span class="n">query</span> <span class="o">=</span> <span class="n">entityManager</span><span class="o">.</span><span class="na">createQuery</span><span class="o">(</span><span class="s">"SELECT m FROM Member m"</span><span class="o">,</span> <span class="nc">Member</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
	        <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Member</span><span class="o">&gt;</span> <span class="n">memberList</span> <span class="o">=</span> <span class="n">query</span><span class="o">.</span><span class="na">getResultList</span><span class="o">();</span>
	        
            <span class="n">tx</span><span class="o">.</span><span class="na">commit</span><span class="o">();</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="c1">// 예외 발생 시 롤백</span>
            <span class="n">tx</span><span class="o">.</span><span class="na">rollback</span><span class="o">();</span>
        <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
            <span class="c1">// EntityManager 종료</span>
            <span class="n">em</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
        <span class="o">}</span>

        <span class="c1">// EntityManagerFactory 종료</span>
        <span class="n">emf</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">EntityManagerFactory</code>로 <code class="language-plaintext highlighter-rouge">EntityManager</code>를 생성하고 이를 이용한다. <code class="language-plaintext highlighter-rouge">Member</code>는 Entity로 관계형 데이터베이스의 테이블과 매핑된다. create를 할때도 객체(Entity)를 사용하고 select 쿼리에서도 객체를 사용하는 것을 볼 수 있다.</p>

<p><code class="language-plaintext highlighter-rouge">EntityManager</code>는 이름에서 유추할 수 있듯이 Entity를 관리하며 내부적으로 JDBC API를 사용하여 데이터베이스와 통신을 한다. 또한  <strong>객체(Entity)와 관계형 데이터베이스 사이의 패러다임 불일치를 해결</strong>해준다. 덕분에 JPA 사용한다면 이 <code class="language-plaintext highlighter-rouge">EntityManager</code>가 제공하는 기능으로 대부분의 데이터베이스 통신을 객체지향적인 방식으로 사용할 수 있다.</p>

<blockquote>
  <p><strong>Hibernate에서 EntityManager</strong><br />
Hibernate에서는 EntityManager를 Session(EntityManager를 사용받은 인터페이스)이라는 이름으로 사용한다. EntityManagerFactory는 SessionFactory라는 이름으로 사용한다. 이들의 구현체는 이름 뒤에 Impl을 붙혀서 사용하는 것을 볼 수 있다.</p>
</blockquote>

<p class="image-caption" style="font-size: 14px;"><img src="https://github.com/RokwonK/RokwonK.github.io/assets/52196792/f54608c9-6c43-4d5f-9962-ca80e0b32095" alt="JPA" class="align-center" style="width: 90%;" /><br />
JPA EntityManager</p>

<p><br /></p>

<h3 id="entitymanager와-persistencecontext">EntityManager와 PersistenceContext</h3>
<p><code class="language-plaintext highlighter-rouge">EntityManager</code>에는 <code class="language-plaintext highlighter-rouge">PersistenceContext</code>(이하 영속성 컨텍스트)라는 공간이 존재한다. 이 공간은 Entity(RDB의 테이블과 매핑되는 객체)들의 상태를 저장하고 관리하는 컨테이너이다.</p>

<p>우리는 이 Entity로 데이터를 검색(select), 새로운 데이터를 생성(create), 기존의 데이터를 수정(update), 데이터를 삭제(delete) 하는데 <strong>영속성 컨텍스트는 이 Entity의 저장공간이자 상태를 추적하여 데이터베이스와 효율적으로 통신할 수 있도록 돕는다.</strong></p>

<p>영속성 컨텍스트의 존재로 다음과 같은 이점을 얻을 수 있다.</p>
<ol>
  <li><strong>쓰기 지연</strong>
    <ul>
      <li>Entity 저장(영속화)시에 곧바로 DB와 통신하지 않고 커밋 시(정확히는 flush)에 통신한다.</li>
      <li>이후에 Entity의 데이터가 변경될 수 있다. 만약 즉시 통신한다면 2번의 쿼리가 발생되는데 쓰기 지연으로 인해 통신을 1번으로 줄일 수 있다.</li>
    </ul>
  </li>
  <li><strong>변경 감지(더티 체킹)</strong>
    <ul>
      <li>Entity의 정보가 수정된다면 후에 커밋(정확히는 flush)시에 변경을 감지하고 수정 쿼리를 만든다.</li>
    </ul>
  </li>
  <li><strong>1차 캐시</strong>
    <ul>
      <li>만약 검색하고자하는 정보가 영속성 컨텍스트 내부에 있다면 이를 사용한다.</li>
    </ul>
  </li>
  <li><strong>지연로딩</strong>
    <ul>
      <li>Entity와 연관된 객체는 한 번에 불러오지 않고 사용시에 불러올 수 있는 지연로딩을 제공한다.</li>
    </ul>
  </li>
</ol>

<p><br /></p>

<h3 id="entitymanager를-생성하는-factory">EntityManager를 생성하는 Factory</h3>
<p><code class="language-plaintext highlighter-rouge">EntityManager</code>를 사용할때는 상황마다 개별로 생성해서 사용한다. 이유는 <code class="language-plaintext highlighter-rouge">EntityManager</code>는 <strong>쓰레드 안정성을 보장하지 않기 때문이다.</strong> 여러 쓰레드 하나를 공유해서 사용하면 내부에서 관리하는 Enitty에 동시성 문제가 생길 수 있다. 동시성 문제로 DB의 실제 데이터와의 일관성이 깨지면 매우 치명적일 것이다.</p>

<p>또한 하나의 <code class="language-plaintext highlighter-rouge">EntityManager</code>는 <strong>하나의 트랜잭션 범위를 담당</strong>한다. 커밋을 하거나 문제가 생겼을 때의 롤백을 하는 범위가 된다. 쓰레드간에 를 공유해서 사용한다면 커밋과 롤백의 범위를 지정하기 어려울 것이다.</p>

<p>이러한 문제로 필요할 때 새로운 <code class="language-plaintext highlighter-rouge">EntityManager</code>를 생성해서 사용하는데 이 <code class="language-plaintext highlighter-rouge">EntityManager</code>를 생성하는 객체가 <code class="language-plaintext highlighter-rouge">EntityManagerFactory</code>이다. 이 <strong>팩토리 객체는 생성 비용이 비싸고 Thread-safe하기 때문에 하나만 만들어서 공유하는 것이 좋다.</strong></p>

<p>우리가 직접 <code class="language-plaintext highlighter-rouge">EntityManagerFactory</code>를 생성하고 공유하는 과정을 구현하지는 않는다. 역시나 스프링 컨테이너가 알아서 만들어준다. 하지만 추후 이 팩토리를 직접 등록해야하는 경우도 있다.(DataSource가 2개 이상, 즉 2개 이상의 DB를 사용할때) 때문에 어떤 과정을 통해 등록되는지 알아두면 좋지 않을까한다.</p>

<p><br /></p>

<h2 id="jpa를-위한-컨테이너의-지원">JPA를 위한 컨테이너의 지원</h2>
<p><code class="language-plaintext highlighter-rouge">application.yml</code> 프로퍼티 파일에 datasource에 대한 설정만 있으면 <code class="language-plaintext highlighter-rouge">EntityManager</code>를 주입받아 사용할 수 있다. 스프링 컨테이너는 실행 시 <code class="language-plaintext highlighter-rouge">EntityManagerFactory</code>와 <code class="language-plaintext highlighter-rouge">EntityManager</code>를 셋팅하고 필요에 따라 상황마다 <code class="language-plaintext highlighter-rouge">EntityManager</code>를 만들어 준다.</p>

<p><br /></p>

<h3 id="entitymanagerfactory를-생성하는-팩토리-빈">EntityManagerFactory를 생성하는 팩토리 빈</h3>
<p><code class="language-plaintext highlighter-rouge">EntityManagerFactory</code>는 컨테이너 수준에서 싱글톤 빈으로 만들어 어플리케이션 전반에 하나의 객체를 공유한다. 다만, <code class="language-plaintext highlighter-rouge">EntityManagerFactory</code>는 그 자체가 빈으로 등록되는 것이 아닌 <strong>팩토리빈에 의해 만들어져 빈으로 등록</strong>된다.</p>

<blockquote>
  <p><strong>FactoryBean&lt; T &gt; 타입</strong><br />
스프링의 빈 종류에는 일반 Bean과 FactoryBean이 있다. FactoryBean은 <strong>스프링 컨테이너에서 T 타입 빈을 생성하는 방법을 제어하고 빈의 초기화 및 설정을 담당한다</strong>. 여러가지 외부 의존성, 외부 설정을 연결해야하는 등 복잡한 빈 설정 및 커스터마이징을 해야하는 경우에 FactoryBean을 구현하여 특정 Bean을 만들어낸다.</p>
</blockquote>

<p><code class="language-plaintext highlighter-rouge">EntityManagerFactory</code>는 <code class="language-plaintext highlighter-rouge">LocalContainerEntityManagerFactoryBean</code>이라는 팩토리빈에 의해 싱글톤 빈으로 생성된다. <code class="language-plaintext highlighter-rouge">LocalContainerEntityManagerFactoryBean</code>은 <strong>DataSource, Entity 정보를 받아 Vendor사(Hibernate 등)에 맞는 <code class="language-plaintext highlighter-rouge">EntityManagerFactory</code>를 생성</strong>한다. 가장 많이 쓰이는 Hibernate의 구현체는 <code class="language-plaintext highlighter-rouge">SessionFactoryImpl</code>이다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">LocalContainerEntityManagerFactoryBean</span> <span class="kd">extends</span> <span class="nc">AbstractEntityManagerFactoryBean</span> <span class="kd">implements</span> <span class="nc">ResourceLoaderAware</span><span class="o">,</span> <span class="nc">LoadTimeWeaverAware</span> <span class="o">{</span>

	<span class="c1">// Entity 정보 셋팅</span>
	<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setManagedTypes</span><span class="o">(</span><span class="nc">PersistenceManagedTypes</span> <span class="n">managedTypes</span><span class="o">)</span> <span class="o">{</span>
		<span class="k">this</span><span class="o">.</span><span class="na">internalPersistenceUnitManager</span><span class="o">.</span><span class="na">setManagedTypes</span><span class="o">(</span><span class="n">managedTypes</span><span class="o">);</span>  
	<span class="o">}</span>
	
	<span class="c1">// DataSource 셋팅</span>
	<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setDataSource</span><span class="o">(</span><span class="nc">DataSource</span> <span class="n">dataSource</span><span class="o">)</span> <span class="o">{</span>  
	   <span class="k">this</span><span class="o">.</span><span class="na">internalPersistenceUnitManager</span><span class="o">.</span><span class="na">setDataSourceLookup</span><span class="o">(</span><span class="k">new</span> <span class="nc">SingleDataSourceLookup</span><span class="o">(</span><span class="n">dataSource</span><span class="o">));</span>  
	   <span class="k">this</span><span class="o">.</span><span class="na">internalPersistenceUnitManager</span><span class="o">.</span><span class="na">setDefaultDataSource</span><span class="o">(</span><span class="n">dataSource</span><span class="o">);</span>  
	<span class="o">}</span>
	
	<span class="nd">@Override</span>  
	<span class="kd">protected</span> <span class="nc">EntityManagerFactory</span> <span class="nf">createNativeEntityManagerFactory</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">PersistenceException</span> <span class="o">{</span>  
		<span class="c1">// ... EntityManagerFactory 생성</span>
	<span class="o">}</span>

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

<p>여기서 볼 수 있듯이 한 <code class="language-plaintext highlighter-rouge">EntityManagerFactory</code>당 하나의 DataSource와 연결되어 있기 때문에 만약 DB를 2개 이상 사용한다면 스프링 부트를 사용한다고 해도 <code class="language-plaintext highlighter-rouge">LocalContainerEntityManagerFactoryBean</code>를 따로 빈으로 등록해줘야한다.</p>

<p><br /></p>

<h3 id="공유-entitymanager-생성과-의존성-주입">공유 EntityManager 생성과 의존성 주입</h3>
<p>JPA를 사용할 때, <code class="language-plaintext highlighter-rouge">@PersistenceUnit</code>, <code class="language-plaintext highlighter-rouge">@PersistenceContext</code> 어노테이션을 이용해 EntityManagerFactory와 EntityManager를 주입받는다. 이 두 어노테이션은 어플리케이션 생성 중 <code class="language-plaintext highlighter-rouge">PersistenceAnnotationBeanPostProcessor</code> 빈후처리기에 의해 처리된다.</p>

<p><code class="language-plaintext highlighter-rouge">@PersistencUnit</code> 어노테이션이 달리 필드에는 <code class="language-plaintext highlighter-rouge">LocalContainerEntityManagerFactoryBean</code>로 생성된 공유 EntityManagerFactory(Hibernate에서 SessionFactoryImpl)를 주입한다.</p>

<p><code class="language-plaintext highlighter-rouge">@PersistenceContext</code> 어노테이션에는 EntityManager(SessionImpl)를 주입하는데 여기서 <strong>특이한 점은 SeesionImpl의 프록시 객체를 생성하여 주입한다는 것</strong>이다. 해당 빈후처리기에서는 이 프록시 객체를 공유 EntityManager로 하여 모든 필드에 이 공유 프록시 객체를 주입한다.</p>

<p><code class="language-plaintext highlighter-rouge">PersistenceAnnotationBeanPostProcessor</code>의 내부 코드를 보면 먼저, 필드가 <code class="language-plaintext highlighter-rouge">@PersistenceContext</code> 어노테이션을 가지는지 확인하고 <code class="language-plaintext highlighter-rouge">SharedEntityManagerCreator</code> 객체의 createSharedEntityManager 메서드를 통해 프록시 객체를 생성한다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* SharedEntityManagerCreator.java */</span>

<span class="kd">public</span> <span class="kd">static</span> <span class="nc">EntityManager</span> <span class="nf">createSharedEntityManager</span><span class="o">(</span><span class="nc">EntityManagerFactory</span> <span class="n">emf</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">Map</span><span class="o">&lt;?,</span> <span class="o">?&gt;</span> <span class="n">properties</span><span class="o">,</span>  
      <span class="kt">boolean</span> <span class="n">synchronizedWithTransaction</span><span class="o">,</span> <span class="nc">Class</span><span class="o">&lt;?&gt;...</span> <span class="n">entityManagerInterfaces</span><span class="o">)</span> <span class="o">{</span>  
   <span class="c1">// ...</span>
   <span class="c1">// 프록시 객체 생성</span>
   <span class="k">return</span> <span class="o">(</span><span class="nc">EntityManager</span><span class="o">)</span> <span class="nc">Proxy</span><span class="o">.</span><span class="na">newProxyInstance</span><span class="o">(</span>  
         <span class="o">(</span><span class="n">cl</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">cl</span> <span class="o">:</span> <span class="nc">SharedEntityManagerCreator</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getClassLoader</span><span class="o">()),</span>  
         <span class="n">ifcs</span><span class="o">,</span> <span class="k">new</span> <span class="nc">SharedEntityManagerInvocationHandler</span><span class="o">(</span><span class="n">emf</span><span class="o">,</span> <span class="n">properties</span><span class="o">,</span> <span class="n">synchronizedWithTransaction</span><span class="o">));</span>  
<span class="o">}</span>

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

<p><code class="language-plaintext highlighter-rouge">EntityManager</code>는 트랜잭션별로 다른 EntityManager를 생성해야한다. 하지만 객체가 주입되는 시점에서 어떤 트랜잭션을 사용하게 될지 모른다. 때문에 <strong>프록시 객체를 두고 해당 객체를 호출할때 현재 트랜잭션에서 사용되고 있는 EntityManager를 리턴하는 형식</strong>이다.</p>

<p>정리해보자면,</p>
<ol>
  <li>빈후처리기가 동작하는 단계에서 <code class="language-plaintext highlighter-rouge">PersistenceAnnotationBeanPostProcessor</code> 후처리기가 동작</li>
  <li><code class="language-plaintext highlighter-rouge">@PersistencUnit</code>, <code class="language-plaintext highlighter-rouge">@PersistenceContext</code> 어노테이션 필드에 값을 주입함</li>
  <li><code class="language-plaintext highlighter-rouge">@PersistenceContext</code> 경우엔 공유 프록시 <code class="language-plaintext highlighter-rouge">EntityManager</code>를 만들어 주입</li>
  <li>주입받은 <code class="language-plaintext highlighter-rouge">EntityManager</code>를 사용할때 내부에서 상황(트랜잭션)에 맞는 실제 <code class="language-plaintext highlighter-rouge">EntityManager</code>를 리턴한다.</li>
</ol>

<blockquote>
  <p><strong>트랜잭션별 EntityManager</strong><br />
트랜잭션 내에서 어떤 <code class="language-plaintext highlighter-rouge">EntityManager</code>가 사용되고 있는지 어떻게 알고 리턴할 수 있는 걸까? 이는 트랜잭션 동기화와 관련된 내용으로 다음 포스팅에서 설명할 예정이다.</p>
</blockquote>

<p><br /></p>

<h3 id="주입-받은-entitymanager-사용하기">주입 받은 EntityManager 사용하기</h3>
<p><code class="language-plaintext highlighter-rouge">LocalContainerEntityManagerFactoryBean</code> 팩토리빈을 통해 공유 <code class="language-plaintext highlighter-rouge">EntityManagerFactory</code>를 만들어 내고 이를 이용해서 모든 <code class="language-plaintext highlighter-rouge">@PersistenceContext</code> 어노테이션이 달린 <code class="language-plaintext highlighter-rouge">EntityManager</code> 필드에 공유 프록시 <code class="language-plaintext highlighter-rouge">EntityManager</code>를 주입하는 것까지 살펴보았다.</p>

<p>이제 DB통신을 하기 위해 이 <code class="language-plaintext highlighter-rouge">EntityManager</code>를 사용해보자. createQuery 메서드를 이용하여 쿼리를 만들면 프록시 객체를 만들때 넘겨준 <code class="language-plaintext highlighter-rouge">SharedEntityManagerInvocationHandler</code> 객체의 <code class="language-plaintext highlighter-rouge">invoke</code> 함수를 호출한다.</p>

<p class="image-caption" style="font-size: 14px;"><img src="https://github.com/RokwonK/RokwonK.github.io/assets/52196792/e24b2689-6b25-478b-ae0b-7e516671f41b" alt="invoke 함수 호출" class="align-center" style="width: 100%;" /><br />
SharedEntityManagerInvocationHandler invoke 함수</p>

<p>이 invoke 함수는 내부에서 <code class="language-plaintext highlighter-rouge">EntityManagerFactoryUtils</code>를 통해서 현재 트랜잭션에서 사용중인 실제 <code class="language-plaintext highlighter-rouge">EntityManager</code>를 가져와서 메서드를 처리하는 것을 볼 수 있다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* SharedEntityManagerInvocationHandler 클래스 */</span>

<span class="nd">@Override</span>  
<span class="nd">@Nullable</span>  
<span class="kd">public</span> <span class="nc">Object</span> <span class="nf">invoke</span><span class="o">(</span><span class="nc">Object</span> <span class="n">proxy</span><span class="o">,</span> <span class="nc">Method</span> <span class="n">method</span><span class="o">,</span> <span class="nc">Object</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Throwable</span> <span class="o">{</span>  
	<span class="c1">// ...</span>
	
	<span class="c1">// 현재 트랜잭션에서 사용하는 EntityManager 가져오기</span>
	<span class="nc">EntityManager</span> <span class="n">target</span> <span class="o">=</span> <span class="nc">EntityManagerFactoryUtils</span><span class="o">.</span><span class="na">doGetTransactionalEntityManager</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">targetFactory</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">properties</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">synchronizedWithTransaction</span><span class="o">);</span>  
	
	<span class="c1">// ...</span>
   
	<span class="k">try</span> <span class="o">{</span>  
		<span class="c1">// 메서드(createQuery) 실행</span>
		<span class="nc">Object</span> <span class="n">result</span> <span class="o">=</span> <span class="n">method</span><span class="o">.</span><span class="na">invoke</span><span class="o">(</span><span class="n">target</span><span class="o">,</span> <span class="n">args</span><span class="o">);</span>  
		<span class="c1">// ...</span>
		<span class="k">return</span> <span class="n">result</span><span class="o">;</span>  
	<span class="o">}</span>  
<span class="o">}</span>
</code></pre></div></div>

<p>여기서 적절한 <code class="language-plaintext highlighter-rouge">target</code>을 가져오기 위해 트랜잭션 동기화가 필요한데 이는 다음 포스팅에서 더 자세하게 알아보자.</p>

<p><br /></p>

<h3 id="정리">정리</h3>
<p>스프링 부트 어플리케이션을 실행하게 되면 어떤 과정을 거쳐서 우리가 사용하는 JPA가 셋팅되는지 정리해보자.</p>

<ol>
  <li>스프링 어플리케이션을 실행하면 AutoConfiguration을 통해 <code class="language-plaintext highlighter-rouge">JpaBaseConfiguration</code>이 빈으로 등록됨</li>
  <li>해당 Configuration에서 <code class="language-plaintext highlighter-rouge">LocalContainerEntityManagerFactoryBean</code> 빈을 생성함</li>
  <li>빈 생성 이후 빈후처리기가 동작 - <code class="language-plaintext highlighter-rouge">PersistenceAnnotationBeanPostProcessor</code> 동작</li>
  <li>Persistence 어노테이션들을 읽고 주입
    <ul>
      <li>이때 <code class="language-plaintext highlighter-rouge">@PersistenceContext</code>에는 공유 프록시 <code class="language-plaintext highlighter-rouge">EntityManager</code> 객체가 주입됨</li>
      <li>Spring Data JPA를 사용할때 JpaRepository의 구현체인 SimpleJpaRepository에서도 <code class="language-plaintext highlighter-rouge">EntityManager</code>가 존재하는데 여기에도 이 프록시 객체가 주입됨</li>
    </ul>
  </li>
  <li>스프링 어플리케이션 구동 완료</li>
  <li>요청이 들어오고 트랜잭션 시작, 프록시 <code class="language-plaintext highlighter-rouge">EntityManager</code>를 통한 DB 통신 시작</li>
  <li>내부에서 invoke 함수 호출
    <ul>
      <li><code class="language-plaintext highlighter-rouge">EntityManagerFactoryUtils</code>를 통해 현재 트랜잭션에서 사용중인 <code class="language-plaintext highlighter-rouge">EntityManager</code> 불러옴</li>
      <li>실제 <code class="language-plaintext highlighter-rouge">EntityManager</code>를 통해 DB 통신</li>
    </ul>
  </li>
  <li>트랜잭션 완료 및 요청에 대한 응답 완료</li>
</ol>]]></content><author><name>RokWon K</name></author><category term="Spring" /><summary type="html"><![CDATA[JPA에서 사용되는 구성요소들과 이 구성요소들을 스프링 컨테이너는 어떻게 제공하는걸까?]]></summary></entry><entry><title type="html">Spring Boot DB통신(1) - JDBC와 DataSource의 역할</title><link href="https://rokwonk.github.io/spring/spring-boot-db-1/" rel="alternate" type="text/html" title="Spring Boot DB통신(1) - JDBC와 DataSource의 역할" /><published>2024-01-10T00:00:00+09:00</published><updated>2024-01-10T00:00:00+09:00</updated><id>https://rokwonk.github.io/spring/Spring-Boot-DB%ED%86%B5%EC%8B%A0-1-JDBC%EC%99%80-DataSource%EC%9D%98-%EC%97%AD%ED%95%A0</id><content type="html" xml:base="https://rokwonk.github.io/spring/spring-boot-db-1/"><![CDATA[<p><br /></p>

<p>스프링은 데이터베이스와의 상호작용을 위한 여러가지 기술들을 제공해 주어 편리하게 개발을 할 수 있도록 도와준다. 워낙 간편하게 코드를 작성할 수 있도록 도와주다보니 간편한 로직들을 막 작성해도 알아서 잘 돌아가더라..</p>

<p>비교적 복잡한 로직을 작성할때도 대부분은 블로깅을 통해 쉽게 구현이 가능해 내부동작을 이해하지 못하고 사용하는 경우도 많았다. 매번 궁금증이 들기도 했지만 프로젝트의 완성이 우선이다보니 호기심은 구석에 던져놓는 경우가 다반사였다.</p>

<p>근래 코드를 다시 읽던 중에 과거의 들었던 궁금증이 다시 떠올라 이번 기회에 모두 정리하기로 마음먹었다. 데이터베이스 통신의 기본이 되는 JDBC부터 하나씩 살펴보자.</p>

<p><br /></p>

<h2 id="jdbc">JDBC</h2>
<p>JDBC(Java Database Connectivity)는 자바에서 데이터베이스와 상호작용할 수 있게 도와주는 표준 API이다. 추상화된 JDBC API를 이용해 우리는 데이터베이스와 연결하고 SQL을 실행 및 결과를 받을 수 있다. <strong>이런 JDBC의 가장 큰 장점은 통일된 인터페이스를 이용해 다양한 데이터베이스와의 상호작용을 동일한 방식한 방식으로 통신할 수 있도록 도와준다는 것이다.</strong></p>

<p>Oracle, MySQL 등 각 데이터베이스마다 프로토콜(SQL전달 및 응답처리)이 조금씩 다르다. 때문에 통일된 인터페이스를 제공하지 않는다면 각 데이터베이스에 의존된 코드를 작성할 수 밖에 없다. JDBC 덕분에 개발자는 데이터베이스 종류에 따른 의존도를 줄일 수 있다.</p>

<p><br /></p>

<h3 id="driver---db연결-추상화">Driver - DB연결 추상화</h3>
<p>JDBC는 <code class="language-plaintext highlighter-rouge">Driver</code>라는 인터페이스를 통해 데이터베이스 연결을 추상화한다. 각 데이터베이스용 Driver를 상속받는 구현체가 존재하고 개발자는 DriverManager 클래스를 통해 커넥션을 얻어 데이터베이스와 상호작용할 수 있다.</p>

<p class="image-caption" style="font-size: 14px;"><img src="https://github.com/RokwonK/RokwonK.github.io/assets/52196792/149631c6-6221-4ec4-839c-0604759c7c38" alt="JDBC" class="align-center" style="width: 50%;" /><br />
JDBC</p>

<p>통신할 데이터베이스에 맞는 Driver를 다운받기만 하면 DriverManager에서 이를 자동으로 인식하고 Connection을 받아올 수 있다. 아래 코드는 JDBC를 이용하여 MySQL 데이터베이스와 통신하는 과정이다.</p>

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

    <span class="c1">// 데이터베이스 연결 정보</span>
    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">JDBC_URL</span> <span class="o">=</span> <span class="s">"jdbc:mysql://localhost:3306/your_database_name"</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">USERNAME</span> <span class="o">=</span> <span class="s">"your_username"</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">PASSWORD</span> <span class="o">=</span> <span class="s">"your_password"</span><span class="o">;</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Connection</span> <span class="n">connection</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
        <span class="nc">PreparedStatement</span> <span class="n">preparedStatement</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
        <span class="nc">ResultSet</span> <span class="n">resultSet</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>

        <span class="k">try</span> <span class="o">{</span>
            <span class="c1">// 1. 데이터베이스 연결 생성</span>
            <span class="n">connection</span> <span class="o">=</span> <span class="nc">DriverManager</span><span class="o">.</span><span class="na">getConnection</span><span class="o">(</span><span class="no">JDBC_URL</span><span class="o">,</span> <span class="no">USERNAME</span><span class="o">,</span> <span class="no">PASSWORD</span><span class="o">);</span>

            <span class="c1">// 2. SQL 쿼리 준비</span>
            <span class="nc">String</span> <span class="n">sqlQuery</span> <span class="o">=</span> <span class="s">"SELECT * FROM your_table_name WHERE condition_column = ?"</span><span class="o">;</span>
            <span class="n">preparedStatement</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span><span class="n">sqlQuery</span><span class="o">);</span>
            <span class="n">preparedStatement</span><span class="o">.</span><span class="na">setString</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="s">"some_value"</span><span class="o">);</span>

            <span class="c1">// 3. SQL 실행 및 결과 얻기</span>
            <span class="n">resultSet</span> <span class="o">=</span> <span class="n">preparedStatement</span><span class="o">.</span><span class="na">executeQuery</span><span class="o">();</span>

            <span class="c1">// 4. 결과 처리</span>
            <span class="k">while</span> <span class="o">(</span><span class="n">resultSet</span><span class="o">.</span><span class="na">next</span><span class="o">())</span> <span class="o">{</span>
                <span class="c1">// 결과 데이터 읽기</span>
                <span class="kt">int</span> <span class="n">id</span> <span class="o">=</span> <span class="n">resultSet</span><span class="o">.</span><span class="na">getInt</span><span class="o">(</span><span class="s">"id"</span><span class="o">);</span>
                <span class="nc">String</span> <span class="n">name</span> <span class="o">=</span> <span class="n">resultSet</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"name"</span><span class="o">);</span>
                <span class="c1">// 필요한 작업 수행</span>
                <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"ID: "</span> <span class="o">+</span> <span class="n">id</span> <span class="o">+</span> <span class="s">", Name: "</span> <span class="o">+</span> <span class="n">name</span><span class="o">);</span>
            <span class="o">}</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">ClassNotFoundException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">SQLException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
        <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
            <span class="c1">// 자원 해제</span>
            <span class="k">try</span> <span class="o">{</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">resultSet</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                    <span class="n">resultSet</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
                <span class="o">}</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">preparedStatement</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                    <span class="n">preparedStatement</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
                <span class="o">}</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">connection</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                    <span class="n">connection</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
                <span class="o">}</span>
            <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">SQLException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
                <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
            <span class="o">}</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>

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

<ol>
  <li><code class="language-plaintext highlighter-rouge">Connection</code> 생성 - DriverManager를 통해 DB연결을 생성한다.</li>
  <li><code class="language-plaintext highlighter-rouge">Statement</code> 작성 - 실행할 쿼리를 준비한다.</li>
  <li><code class="language-plaintext highlighter-rouge">Statement</code> 실행 - 쿼리를 실행하여 DB를 통해 결과를 얻어온다.</li>
  <li><code class="language-plaintext highlighter-rouge">ResultSet</code> 읽기 - 쿼리결과는 ResultSet에 담기며 이를 읽어 필요한 작업을 수행한다.</li>
</ol>

<p>모든 작업이 끝난후에는 사용하던 자원을 할당 해제시켜준다.</p>

<p>JDBC 덕분에 데이터베이스와의 의존성은 확실히 줄어들었지만 보다시피 한 번의 통신에 코드에 양이 방대하다. 연결 설정, 자원반납 부분에서 보일러 플레이트 코드들이 보이고 결과를 처리하는 부분에서도 자료형, 데이터 검증의 필요성이 느껴진다. 무엇보다도 <strong>DB와 상호작용이 필요할 때마다 커텍션을 생성하는데 이에 대한 관리가 필요</strong>해 보인다.</p>

<p><br /></p>

<h2 id="datasource">DataSource</h2>
<p>JDBC API에서 DB와 통신이 필요할 때 <code class="language-plaintext highlighter-rouge">DriverManager</code>를 통해 커넥션을 얻어왔다. 이 방식은 요청시 마다 커넥션을 생성하는데 이런 경우에 몇 문제점이 존재한다.</p>
<ol>
  <li>DB와의 커넥션은 그 생성 비용이 크다.</li>
  <li>무분별한 커넥션 생성은 데이터베이스에 큰 부하를 줄 수 있다.</li>
  <li>필요하다면 timeout과 같은 설정을 할 수 있다. 매 커넥션마다 이러한 커넥션 설정을 하는 것은 비효율적이다.</li>
</ol>

<p>이러한 문제 때문에 <strong>데이터베이스와의 커넥션을 관리하는 계층</strong>을 하나 두는데 이 계층이 바로 <code class="language-plaintext highlighter-rouge">DataSource</code>이다. <code class="language-plaintext highlighter-rouge">DataSource</code> 또한 추상화된 계층으로 필요에 따라 다른 커넥션을 관리하는 방식을 사용할 수 있다. 대표적인 구현체로는 DriverManagerDataSource와 HikariDataSource가 있다.</p>

<p><br /></p>

<h3 id="hikaridatasource와--connection-pool">HikariDataSource와  Connection Pool</h3>
<p><code class="language-plaintext highlighter-rouge">DriverManagerDataSource</code>는 기존 <code class="language-plaintext highlighter-rouge">DriverManager</code>에 비해 커넥션에 대한 설정을 중앙 관리해준다. 테스트용도로만 사용한다고 하니 참고만 해두자.</p>

<p><code class="language-plaintext highlighter-rouge">HikariDataSource</code>는 Spring Boot에서 기본적으로 사용하는 DataSource이다. 이 DataSource의 가장 큰 매력으로는 <strong>Connection Pool</strong>이 있다.(줄여서 CP, HikariCP 라고 부른다)</p>

<p>Connection Pool은 어플리케이션이 실행될 때, <strong>미리 설정된 갯수만큼의 커넥션을 만들어 저장해 두는 공간</strong>이다. 만약 어플리케이션에서 커넥션을 사용하고자 한다면 이 Connection Pool에서 커넥션을 꺼내 사용한 뒤 할당을 해제하는게 아니라 Connection Pool에 반납한다. 반납한 커넥션은 다른 요청에서 재사용된다. Connection Pool 덕분에 커넥션 생성 비용을 줄일 수 있다.</p>

<p class="image-caption" style="font-size: 14px;"><img src="https://github.com/RokwonK/RokwonK.github.io/assets/52196792/5a945227-2754-4b2c-a614-5b0fc5b80dd4" alt="Connection Pool" class="align-center" style="width: 90%;" /><br />
Connection Pool</p>

<p>실제로 <code class="language-plaintext highlighter-rouge">HikraiDataSource</code> 내부에서는 <code class="language-plaintext highlighter-rouge">HikariPool</code>을 유지하고 이 내부에서 커넥션을 꺼내서 사용하는 걸 볼 수 있다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HikariDataSource</span> <span class="kd">extends</span> <span class="nc">HikariConfig</span> <span class="kd">implements</span> <span class="nc">DataSource</span><span class="o">,</span> <span class="nc">Closeable</span>  
<span class="o">{</span>  
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">HikariPool</span> <span class="n">fastPathPool</span><span class="o">;</span>  
   <span class="kd">private</span> <span class="kd">volatile</span> <span class="nc">HikariPool</span> <span class="n">pool</span><span class="o">;</span>  
  
   <span class="kd">public</span> <span class="nf">HikariDataSource</span><span class="o">(</span><span class="nc">HikariConfig</span> <span class="n">configuration</span><span class="o">)</span> <span class="o">{</span>  
      <span class="c1">// 커넥션 풀 생성</span>
      <span class="n">pool</span> <span class="o">=</span> <span class="n">fastPathPool</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HikariPool</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>  
   <span class="o">}</span>  
  
   <span class="nd">@Override</span>  
   <span class="kd">public</span> <span class="nc">Connection</span> <span class="nf">getConnection</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">SQLException</span>  
   <span class="o">{</span>  
      <span class="c1">// 커넥션 풀에서 커넥션 가져오기</span>
      <span class="k">if</span> <span class="o">(</span><span class="n">fastPathPool</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>  
         <span class="k">return</span> <span class="n">fastPathPool</span><span class="o">.</span><span class="na">getConnection</span><span class="o">();</span>  
      <span class="o">}</span>  
      
      <span class="nc">HikariPool</span> <span class="n">result</span> <span class="o">=</span> <span class="n">pool</span><span class="o">;</span>  
      <span class="c1">// ...</span>
  
      <span class="k">return</span> <span class="n">result</span><span class="o">.</span><span class="na">getConnection</span><span class="o">();</span>  
   <span class="o">}</span>
   <span class="c1">// ...</span>
<span class="o">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">HikraiDataSource</code>는 Connection Pool 외에도 여러가지 기능으로 커넥션을 관리해준다.</p>
<ol>
  <li>Connection Pool의 커넥션을 모두 사용했을 경우 <strong>추가적인 커넥션 생성 가능</strong></li>
  <li>요청이 많아 사용할 수 있는 커넥션이 존재하지 않는 경우 <strong>대기하는 큐 존재</strong></li>
  <li>요청 <strong>timeout 관리</strong></li>
  <li>커넥션은 DB와의 연결 유효시간(maxLifeTime)이 존재. <strong>커넥션 만료 및 새로운 커넥션 생성</strong>으로 Connection Pool 유지</li>
</ol>

<p>사용방법이 크게 달라지지는 않지만 DataSource를 이용한 DB 통신 코드도 봐보자. DataSource에 대한 설정은 생략했다.</p>

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

    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">DataSource</span> <span class="n">dataSource</span><span class="o">;</span>

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

    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">fetchDataFromDatabase</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">try</span> <span class="o">(</span><span class="nc">Connection</span> <span class="n">connection</span> <span class="o">=</span> <span class="n">dataSource</span><span class="o">.</span><span class="na">getConnection</span><span class="o">();</span>
             <span class="nc">Statement</span> <span class="n">statement</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="na">createStatement</span><span class="o">();</span>
             <span class="nc">ResultSet</span> <span class="n">resultSet</span> <span class="o">=</span> <span class="n">statement</span><span class="o">.</span><span class="na">executeQuery</span><span class="o">(</span><span class="s">"SELECT * FROM your_table_name"</span><span class="o">))</span> <span class="o">{</span>

            <span class="k">while</span> <span class="o">(</span><span class="n">resultSet</span><span class="o">.</span><span class="na">next</span><span class="o">())</span> <span class="o">{</span>
                <span class="kt">int</span> <span class="n">id</span> <span class="o">=</span> <span class="n">resultSet</span><span class="o">.</span><span class="na">getInt</span><span class="o">(</span><span class="s">"id"</span><span class="o">);</span>
                <span class="nc">String</span> <span class="n">name</span> <span class="o">=</span> <span class="n">resultSet</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"name"</span><span class="o">);</span>
                <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"ID: "</span> <span class="o">+</span> <span class="n">id</span> <span class="o">+</span> <span class="s">", Name: "</span> <span class="o">+</span> <span class="n">name</span><span class="o">);</span>
            <span class="o">}</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">SQLException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">getConnection()</code> 메서드에서 내부적으로는 커넥션을 재사용한다. <code class="language-plaintext highlighter-rouge">DriverManager</code>를 사용할때와 사용방법은 똑같다. 어디서 커넥션을 받아오는지만 달라졌으며, 내부적으로는 커넥션을 관리를 제공해줄 뿐이다.</p>

<p>자원 할당 해제하는 부분만 Java7부터 지원되는 <code class="language-plaintext highlighter-rouge">try-with-resources</code>를 이용하여 간결하게 표현하였다.</p>

<blockquote>
  <p><strong>try-with-resources</strong><br />
<code class="language-plaintext highlighter-rouge">AutoClosable</code> 인터페이스를 구현한 객체들을 try 구문이 끝난후 close() 메서드를 호출해준다. Connection, Statement, ResultSet 모두 <code class="language-plaintext highlighter-rouge">AutoClosable</code>를 구현한다.</p>
</blockquote>

<p><br /></p>

<h3 id="datasource의-connection은-proxy-객체">DataSource의 Connection은 Proxy 객체</h3>
<p>위 코드에서 재미있는 점은 DataSource로 받은 Connection을 <code class="language-plaintext highlighter-rouge">try-with-resources</code>로 자원 할당 해제한다는 것이다. try 구문이 종료되면 <code class="language-plaintext highlighter-rouge">close()</code> 메서드가 실행되고 <code class="language-plaintext highlighter-rouge">Connection</code>을 해제하는 것이다.</p>

<p>그런데 이상하다. 지금까지 살펴본바 <code class="language-plaintext highlighter-rouge">Connection</code>은 자원할당해제가 아닌 Connection Pool로 자원반납이 이루어져야 한다. 사실 DataSource로 받아온 <code class="language-plaintext highlighter-rouge">Connection</code>은 Proxy 객체이다. 때문에 <strong>Proxy 객체의 close() 메서드를 호출하고 이 메서드 내부에서 자원반납이 이루어진다.</strong></p>

<p>코드 내부를 살펴보자. 위에서 살펴보았듯 DataSource(HikariDataSoure)로 <code class="language-plaintext highlighter-rouge">getConnection()</code> 메서드를 호출하면 <code class="language-plaintext highlighter-rouge">HikariPool</code>로부터 커넥션을 받아온다. <code class="language-plaintext highlighter-rouge">HikariPool</code>의 <code class="language-plaintext highlighter-rouge">getConnection()</code> 메서드를 보면 아래와 같다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nc">Connection</span> <span class="nf">getConnection</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">SQLException</span> <span class="o">{</span>  
   <span class="k">return</span> <span class="nf">getConnection</span><span class="o">(</span><span class="n">connectionTimeout</span><span class="o">);</span>  
<span class="o">}</span>  
  
<span class="kd">public</span> <span class="nc">Connection</span> <span class="nf">getConnection</span><span class="o">(</span><span class="kd">final</span> <span class="kt">long</span> <span class="n">hardTimeout</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">SQLException</span> <span class="o">{</span>  
   <span class="c1">// ... 코드 생략</span>
   <span class="k">try</span> <span class="o">{</span>  
      <span class="kt">var</span> <span class="n">timeout</span> <span class="o">=</span> <span class="n">hardTimeout</span><span class="o">;</span>  
      <span class="k">do</span> <span class="o">{</span>  
         <span class="kt">var</span> <span class="n">poolEntry</span> <span class="o">=</span> <span class="n">connectionBag</span><span class="o">.</span><span class="na">borrow</span><span class="o">(</span><span class="n">timeout</span><span class="o">,</span> <span class="no">MILLISECONDS</span><span class="o">);</span>
         <span class="c1">// ...</span>
         <span class="k">return</span> <span class="n">poolEntry</span><span class="o">.</span><span class="na">createProxyConnection</span><span class="o">(</span><span class="n">leakTaskFactory</span><span class="o">.</span><span class="na">schedule</span><span class="o">(</span><span class="n">poolEntry</span><span class="o">));</span>  
      <span class="o">}</span> <span class="k">while</span> <span class="o">(</span><span class="n">timeout</span> <span class="o">&gt;</span> <span class="mi">0L</span><span class="o">);</span>  
 
   <span class="o">}</span>  
<span class="o">}</span>
</code></pre></div></div>

<p>poolEntry는 Connection Pool에 관리되는 요소이며, 실제 Connection을 가지고 있는 객체이다. <code class="language-plaintext highlighter-rouge">createProxyConnection</code>이라는 이름답게 poolEntry를 통해 <code class="language-plaintext highlighter-rouge">Connection</code> 프록시 객체를 만들어 리턴한다. 이름도 <code class="language-plaintext highlighter-rouge">HikariProxyConnection</code>으로 아주 직관적이다.</p>

<p><img src="https://github.com/RokwonK/RokwonK.github.io/assets/52196792/78239b68-9aee-418b-82e3-4477e6d147e4" alt="HikariProxyConnection" /></p>

<p>그럼 이제 <code class="language-plaintext highlighter-rouge">HikariProxyConnection</code>의 <code class="language-plaintext highlighter-rouge">close()</code> 메서드가 실제로 자원해제가 아닌 Connection Pool 커넥션을 반납하는지 살펴보자. <code class="language-plaintext highlighter-rouge">HikariProxyConnection</code>은 <code class="language-plaintext highlighter-rouge">ProxyConnection</code>을 상속받고 있는데 . 이 클래스에  <code class="language-plaintext highlighter-rouge">close()</code> 메서드가 구현되어 있다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ProxyConnection.java</span>

<span class="nd">@Override</span>  
<span class="kd">public</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">close</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">SQLException</span>  
<span class="o">{</span>  
   <span class="k">if</span> <span class="o">(</span><span class="n">delegate</span> <span class="o">!=</span> <span class="nc">ClosedConnection</span><span class="o">.</span><span class="na">CLOSED_CONNECTION</span><span class="o">)</span> <span class="o">{</span>  
      <span class="k">try</span> <span class="o">{</span>  
      <span class="c1">// ...</span>
      <span class="o">}</span>  
      <span class="k">catch</span> <span class="o">(</span><span class="nc">SQLException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>  
         <span class="c1">// ... </span>
      <span class="o">}</span>  
      <span class="k">finally</span> <span class="o">{</span>  
         <span class="n">delegate</span> <span class="o">=</span> <span class="nc">ClosedConnection</span><span class="o">.</span><span class="na">CLOSED_CONNECTION</span><span class="o">;</span>  
         <span class="n">poolEntry</span><span class="o">.</span><span class="na">recycle</span><span class="o">();</span>  
      <span class="o">}</span>  
   <span class="o">}</span>  
<span class="o">}</span>
</code></pre></div></div>

<p>HikariPool(Connection Pool)에 관리되고 있는 poolEntry(내부적으로 Connection을 가지고 있음)의 <code class="language-plaintext highlighter-rouge">recycle()</code> 메서드를 통해 Connection을 재활용하고 있다는 것을 알 수 있다.</p>

<p><br /></p>

<h3 id="정리jdbc와-datasource">정리(JDBC와 DataSource)</h3>
<p>지금까지 <code class="language-plaintext highlighter-rouge">JDBC</code>와 <code class="language-plaintext highlighter-rouge">DataSource</code> 내용을 정리해보자.</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">JDBC</code>는 <strong>다양한 데이터베이스를 일관된 방식으로 접근할 수 있도록 도와</strong>준다.
    <ul>
      <li>Dirver라는 인터페이스가 DB 연결에 대한 추상체이다.</li>
      <li>각 DB에 맞는 Driver 구현체 라이브러리를 다운받기만 하면 자동으로 인식하고 적용된다.</li>
    </ul>
  </li>
  <li>개발자는 <code class="language-plaintext highlighter-rouge">DriverManager</code>를 통해 DB와 커넥션을 맺고 이를 사용할 수 있다.
    <ul>
      <li>하지만 DB와의 커넥션은 직접적으로 생성하고 이용하는 것은 굉장히 민감한 문제이다.</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">DataSource</code>라는 <strong>커넥션 관리 레이어를 두고 이를 활용하는 것이 효과적</strong>이다.</li>
  <li><code class="language-plaintext highlighter-rouge">HikariDataSource</code>는 <code class="language-plaintext highlighter-rouge">DataSource</code>의 구현체로 <strong>커넥션 풀링으로 효율적으로 커넥션을 사용할 수 있도록 돕는다.</strong> 이 DataSource는 기본으로 적용되는 구현체이며 여러가지로 커넥션 관리를 지원한다.</li>
  <li>따라서, 우리는 <code class="language-plaintext highlighter-rouge">DataSource</code> 타입으로 의존성을 주입받아 이를 통해 커넥션을 받아와 데이터베이스와 상호작용을 진행하면 된다.
    <ul>
      <li>받아온 Connection은 Proxy객체로 close 시 Connection Pool로 반납이 이루어진다.</li>
    </ul>
  </li>
</ol>

<p class="image-caption" style="font-size: 14px;"><img src="https://github.com/RokwonK/RokwonK.github.io/assets/52196792/7dbb3b48-a24c-4566-8454-63f6163e1226" alt="Datasource" class="align-center" style="width: 70%;" /><br />
DataSource</p>]]></content><author><name>RokWon K</name></author><category term="Spring" /><summary type="html"><![CDATA[JDBC와 DataSource은 왜 필요할까? 커넥션과 커넥션을 회수하는 방법]]></summary></entry><entry><title type="html">Spring Boot Deep Dive(4) - refresh (빈 구성 단계)</title><link href="https://rokwonk.github.io/spring/spring-boot-deep-dive-4/" rel="alternate" type="text/html" title="Spring Boot Deep Dive(4) - refresh (빈 구성 단계)" /><published>2024-01-03T00:00:00+09:00</published><updated>2024-01-03T00:00:00+09:00</updated><id>https://rokwonk.github.io/spring/Spring-Boot-Deep-Dive-4-SpringApplication-refresh-%EA%B3%BC%EC%A0%95</id><content type="html" xml:base="https://rokwonk.github.io/spring/spring-boot-deep-dive-4/"><![CDATA[<p><br /></p>

<p>main 함수에서 <code class="language-plaintext highlighter-rouge">SpringApplication</code>의 static 메서드 run을 실행하면 SpringApplication 객체가 필요한 데이터를 초기화하면서 생성된다. 이후에는 인스턴스 <code class="language-plaintext highlighter-rouge">run</code> 메서드를 실행하는데 환경변수 설정 및 실행환경인 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>을 생성한다. <code class="language-plaintext highlighter-rouge">ApplicationContext</code>는 스프링 어플리케이션에서 Spring Container, DI Container라 불리며 스프링의 객체인 Bean에 대한 관리를 담당한다.</p>

<p><a href="https://rokwonk.github.io/spring/spring-boot-deep-dive-3/">SpringApplication 실행과정</a>에서 ApplicationContext의 refresh 메서드를 통해 빈들이 구성된다고 언급하였는데 이번 포스팅에서는 어떤 과정이 일어나는지 자세하게 알아보자.</p>

<p>여담으로 이번 글은 <em>‘Spring Boot Deep Dive 시리즈’</em> 를 포스팅하기 시작한 이유이기도 하다. <strong>‘컴포넌트 스캔과 자동구성은 대체 어디서 동작하는거지?’</strong> 라는 궁금증에서 시작해서 소스코드를 들여다보다가 드디어 기존의 궁금증을 해소할 수 있었다. 만약 필자와 같은 의문을 가졌던 분들이 계시다면 이번 포스팅을 통해 궁금증을 조금이나마 해소할 수 있으면 좋겠다.</p>

<blockquote>
  <p>Spring Boot 3.2.1 버전을 기준으로 작성되었습니다.</p>
</blockquote>

<p><br /></p>

<h2 id="refresh-코드">Refresh 코드</h2>
<p><code class="language-plaintext highlighter-rouge">SpringApplication</code> 실행(run) 과정 중 afterContext 메서드는 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>의 refresh() 메서드를 호출한다. <a href="https://rokwonk.github.io/spring/spring-boot-deep-dive-3/"><code class="language-plaintext highlighter-rouge">SpringApplication</code>의 실행과정</a>과 <a href="https://rokwonk.github.io/spring/spring-boot-deep-dive-2/"><code class="language-plaintext highlighter-rouge">ApplicaitonContext</code>의 상속관계</a>에 대한 자세한 내용은 각 링크에 작성해두었다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* SpringApplication.java */</span>
<span class="kd">public</span> <span class="nc">ConfigurableApplicationContext</span> <span class="nf">run</span><span class="o">(</span><span class="nc">String</span><span class="o">...</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
	<span class="c1">// ...</span>
	<span class="k">try</span> <span class="o">{</span>
		<span class="c1">// ...</span>
		<span class="n">refreshContext</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>
	<span class="o">}</span>
	<span class="c1">// ...</span>
<span class="o">}</span>

<span class="kd">private</span> <span class="kt">void</span> <span class="nf">refreshContext</span><span class="o">(</span><span class="nc">ConfigurableApplicationContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>  
	<span class="c1">// ...</span>
	<span class="n">refresh</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>  
<span class="o">}</span>

<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">refresh</span><span class="o">(</span><span class="nc">ConfigurableApplicationContext</span> <span class="n">applicationContext</span><span class="o">)</span> <span class="o">{</span>  
	<span class="c1">// refresh 실행!</span>
	<span class="n">applicationContext</span><span class="o">.</span><span class="na">refresh</span><span class="o">();</span>  
<span class="o">}</span>
</code></pre></div></div>

<p>refresh() 메서드는 상속구조 중 추상 클래스인 <code class="language-plaintext highlighter-rouge">AbstractApplicationContext</code>에 <strong>템플릿 메서드 패턴의 형태로 구현</strong>되어 있다. 일련의 단계가 순차적으로 실행되며 일부 메서드들은 서브 클래스에서 재정의하여 자체적인 동작을 추가한다. 주석과 함께 소스코드를 훑어보자면 아래와 같다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* AbstractApplicationContext.java */</span>

<span class="nd">@Override</span>  
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">refresh</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">BeansException</span><span class="o">,</span> <span class="nc">IllegalStateException</span> <span class="o">{</span>  
	<span class="k">this</span><span class="o">.</span><span class="na">startupShutdownLock</span><span class="o">.</span><span class="na">lock</span><span class="o">();</span>  
	<span class="k">try</span> <span class="o">{</span>  
		<span class="c1">// refresh 과정 동안만 StartupShutdown 쓰레드를 현재 쓰레드로 설정</span>
		<span class="k">this</span><span class="o">.</span><span class="na">startupShutdownThread</span> <span class="o">=</span> <span class="nc">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">();</span>  
		
		<span class="nc">StartupStep</span> <span class="n">contextRefresh</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">applicationStartup</span><span class="o">.</span><span class="na">start</span><span class="o">(</span><span class="s">"spring.context.refresh"</span><span class="o">);</span>  
		
		<span class="c1">// Refresh 준비</span>
		<span class="n">prepareRefresh</span><span class="o">();</span>  
		
		<span class="c1">// Beanfactory 가져오기</span>
		<span class="nc">ConfigurableListableBeanFactory</span> <span class="n">beanFactory</span> <span class="o">=</span> <span class="n">obtainFreshBeanFactory</span><span class="o">();</span>  
		
		<span class="c1">// BeanFactory 준비(BeanFactory 설정)</span>
		<span class="n">prepareBeanFactory</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">);</span>  
	
		<span class="k">try</span> <span class="o">{</span>  
			<span class="c1">// (서브 클래스 구현) 설정 완료 후 필요로직 호출</span>
			<span class="n">postProcessBeanFactory</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">);</span>  
			
			<span class="nc">StartupStep</span> <span class="n">beanPostProcess</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">applicationStartup</span><span class="o">.</span><span class="na">start</span><span class="o">(</span><span class="s">"spring.context.beans.post-process"</span><span class="o">);</span>  
			
			<span class="c1">// BeanFactoryPostProcessor들 실행</span>
			<span class="n">invokeBeanFactoryPostProcessors</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">);</span>  
			
			<span class="c1">// BeanPostProcessor 등록</span>
			<span class="n">registerBeanPostProcessors</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">);</span>
			<span class="n">beanPostProcess</span><span class="o">.</span><span class="na">end</span><span class="o">();</span>  
			
			<span class="c1">// Initialize message source &amp; ApplicationEventMulticaster</span>
			<span class="n">initMessageSource</span><span class="o">();</span>  
			<span class="n">initApplicationEventMulticaster</span><span class="o">();</span>  
			
			<span class="c1">// (서브 클래스 구현) 각 클래스에서 처리할 특별한 빈 구성</span>
			<span class="n">onRefresh</span><span class="o">();</span>  
			
			<span class="c1">// Check for listener beans and register them.  </span>
			<span class="n">registerListeners</span><span class="o">();</span>  
			
			<span class="c1">// 모든 빈이 생성된다.</span>
			<span class="n">finishBeanFactoryInitialization</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">);</span>  
			
			<span class="c1">// 마무리</span>
			<span class="n">finishRefresh</span><span class="o">();</span>
		<span class="o">}</span>  
		<span class="k">catch</span> <span class="o">(</span><span class="nc">RuntimeException</span> <span class="o">|</span> <span class="nc">Error</span> <span class="n">ex</span> <span class="o">)</span> <span class="o">{</span>  
			<span class="c1">// ...</span>
		<span class="o">}</span>  
		<span class="k">finally</span> <span class="o">{</span>  
			<span class="n">contextRefresh</span><span class="o">.</span><span class="na">end</span><span class="o">();</span>  
		<span class="o">}</span>  
	<span class="o">}</span>  
	<span class="k">finally</span> <span class="o">{</span>  
		<span class="k">this</span><span class="o">.</span><span class="na">startupShutdownThread</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>  
		<span class="k">this</span><span class="o">.</span><span class="na">startupShutdownLock</span><span class="o">.</span><span class="na">unlock</span><span class="o">();</span>  
	<span class="o">}</span>  
<span class="o">}</span>
</code></pre></div></div>

<p>여러가지 단계를 거치는데 여기서 빈팩토리와 빈 등록과 관련된 단계만 추려서 살펴보자.</p>
<ol>
  <li><strong>prepareRefresh</strong>
    <ul>
      <li>본격적인 Refresh에 앞서 준비하는 단계</li>
    </ul>
  </li>
  <li><strong>prepareBeanFactory</strong>
    <ul>
      <li>BeanFactory 환경 설정</li>
    </ul>
  </li>
  <li><strong>postProcessBeanFactory</strong>
    <ul>
      <li>BeanFactory 준비완료 후 각 서브클래스 필요에 의해 재정의한 로직들 실행</li>
    </ul>
  </li>
  <li><strong>invokeBeanFactoryPostProcessors</strong>
    <ul>
      <li>BeanFactoryPostProcessor 들 실행</li>
      <li>모든 빈의 정의를 등록하는 과정</li>
    </ul>
  </li>
  <li><strong>registerBeanPostProcessors</strong>
    <ul>
      <li>BeanPostProcessor 들 등록</li>
    </ul>
  </li>
  <li><strong>onRefresh</strong>
    <ul>
      <li>빈 정의가 등록된 이후 각 클래스에서 필요에 따라 재정의한 로직들 실행</li>
    </ul>
  </li>
  <li><strong>finishBeanFactoryInitialization</strong>
    <ul>
      <li>등록된 빈들을 객체화하는 단계</li>
    </ul>
  </li>
</ol>

<p><br /></p>

<h3 id="refresh-준비-단계">Refresh 준비 단계</h3>
<p>크게 살펴볼 코드는 없다. <code class="language-plaintext highlighter-rouge">ApplicationContext</code>가 현재 실행 중임을 의미하는 <code class="language-plaintext highlighter-rouge">active</code> 변수를 true로 변경한다. 이후 <code class="language-plaintext highlighter-rouge">initPropertySources</code>를 호출하는데 현재 이 메서드는 비어있다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* AbstractApplicationContext.java */</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">prepareRefresh</span><span class="o">()</span> <span class="o">{</span>  
	<span class="c1">// Switch to active.  </span>
	<span class="k">this</span><span class="o">.</span><span class="na">startupDate</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>  
	<span class="k">this</span><span class="o">.</span><span class="na">closed</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>  
	<span class="k">this</span><span class="o">.</span><span class="na">active</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>  
	
	<span class="k">if</span> <span class="o">(</span><span class="n">logger</span><span class="o">.</span><span class="na">isDebugEnabled</span><span class="o">())</span> <span class="o">{</span>  
		<span class="k">if</span> <span class="o">(</span><span class="n">logger</span><span class="o">.</span><span class="na">isTraceEnabled</span><span class="o">())</span> <span class="o">{</span>  
			<span class="n">logger</span><span class="o">.</span><span class="na">trace</span><span class="o">(</span><span class="s">"Refreshing "</span> <span class="o">+</span> <span class="k">this</span><span class="o">);</span>  
		<span class="o">}</span>  
		<span class="k">else</span> <span class="o">{</span>  
			<span class="n">logger</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"Refreshing "</span> <span class="o">+</span> <span class="n">getDisplayName</span><span class="o">());</span>  
		<span class="o">}</span>  
	<span class="o">}</span>  
	
	<span class="c1">// Initialize any placeholder property sources in the context environment.  </span>
	<span class="n">initPropertySources</span><span class="o">();</span>  
	
	<span class="c1">// ...</span>
<span class="o">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="beanfactory-설정">BeanFactory 설정</h3>
<p>실제 기능을 담당하는 내부적인 <code class="language-plaintext highlighter-rouge">BeanFactory</code>에 필요한 정보를 설정하는 단계이다.</p>
<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">ClassLoader</code>와 같은 필요한 빈 등록</strong></li>
  <li><strong><code class="language-plaintext highlighter-rouge">Aware</code> 인터페이스 타입의 의존성 주입을 무시하도록 설정</strong></li>
  <li><strong>몇 타입들에 대한 구현체 빈을 현재 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>로 등록</strong></li>
</ol>

<p><code class="language-plaintext highlighter-rouge">CloassLoader</code> 등은 이미 사용되고 있는 객체로 등록하고, 대부분의 <code class="language-plaintext highlighter-rouge">Aware</code> 인터페이스들을 무시하도록 설정한다. Aware 인터페이스는 이를 구현한 클래스들에게 특정 클래스가 객체화되면 메서드를 실행시켜주는 역할이다. 이를 통해 의존성을 주입받았는데 현재는 다른 주입방식들을 더 많이 사용하여 Ignore 설정을 해주는 듯하다.</p>

<p><code class="language-plaintext highlighter-rouge">ResourceLoader</code>, <code class="language-plaintext highlighter-rouge">ApplicationEventPublisher</code>, <code class="language-plaintext highlighter-rouge">ApplicationContext</code> 타입에 대한 구현체를 직접 등록해준다. 개발을 하다보면 <code class="language-plaintext highlighter-rouge">ResourceLoader</code>를 사용할 때나 <code class="language-plaintext highlighter-rouge">ApplicationEventPublisher</code>을 사용할때 해당 타입으로 의존성을 주입받는데 이것들은 사실 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>를 사용하는 것과 같다. <a href="https://rokwonk.github.io/spring/spring-boot-deep-dive-2/">ApplicationContext 상속관계</a>에서 살펴본 것과 같이 <code class="language-plaintext highlighter-rouge">AbstractApplicationContext</code>는 <code class="language-plaintext highlighter-rouge">ResourceLoader</code>를 상속받고 있으며 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>는 <code class="language-plaintext highlighter-rouge">ApplicationEventPublisher</code>를 받기 때문에 이런 설정이 가능하다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* AbstractApplicationContext.java */</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">prepareBeanFactory</span><span class="o">(</span><span class="nc">ConfigurableListableBeanFactory</span> <span class="n">beanFactory</span><span class="o">)</span> <span class="o">{</span> 
	<span class="c1">// 필요한 빈들 직접 등록</span>
	<span class="n">beanFactory</span><span class="o">.</span><span class="na">setBeanClassLoader</span><span class="o">(</span><span class="n">getClassLoader</span><span class="o">());</span>  
	<span class="n">beanFactory</span><span class="o">.</span><span class="na">setBeanExpressionResolver</span><span class="o">(</span><span class="k">new</span> <span class="nc">StandardBeanExpressionResolver</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">.</span><span class="na">getBeanClassLoader</span><span class="o">()));</span>  
	<span class="n">beanFactory</span><span class="o">.</span><span class="na">addPropertyEditorRegistrar</span><span class="o">(</span><span class="k">new</span> <span class="nc">ResourceEditorRegistrar</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">getEnvironment</span><span class="o">()));</span>  
	
	<span class="c1">// Aware 인터페이스들 무시</span>
	<span class="n">beanFactory</span><span class="o">.</span><span class="na">addBeanPostProcessor</span><span class="o">(</span><span class="k">new</span> <span class="nc">ApplicationContextAwareProcessor</span><span class="o">(</span><span class="k">this</span><span class="o">));</span>  
	<span class="n">beanFactory</span><span class="o">.</span><span class="na">ignoreDependencyInterface</span><span class="o">(</span><span class="nc">EnvironmentAware</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>  
	<span class="n">beanFactory</span><span class="o">.</span><span class="na">ignoreDependencyInterface</span><span class="o">(</span><span class="nc">EmbeddedValueResolverAware</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>  
	<span class="n">beanFactory</span><span class="o">.</span><span class="na">ignoreDependencyInterface</span><span class="o">(</span><span class="nc">ResourceLoaderAware</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>  
	<span class="n">beanFactory</span><span class="o">.</span><span class="na">ignoreDependencyInterface</span><span class="o">(</span><span class="nc">ApplicationEventPublisherAware</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> 
	<span class="n">beanFactory</span><span class="o">.</span><span class="na">ignoreDependencyInterface</span><span class="o">(</span><span class="nc">MessageSourceAware</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>  
	<span class="n">beanFactory</span><span class="o">.</span><span class="na">ignoreDependencyInterface</span><span class="o">(</span><span class="nc">ApplicationContextAware</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>  
	<span class="n">beanFactory</span><span class="o">.</span><span class="na">ignoreDependencyInterface</span><span class="o">(</span><span class="nc">ApplicationStartupAware</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>  
	
	<span class="c1">// Dependency 직접 설정</span>
	<span class="n">beanFactory</span><span class="o">.</span><span class="na">registerResolvableDependency</span><span class="o">(</span><span class="nc">ResourceLoader</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="k">this</span><span class="o">);</span>  
	<span class="n">beanFactory</span><span class="o">.</span><span class="na">registerResolvableDependency</span><span class="o">(</span><span class="nc">ApplicationEventPublisher</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="k">this</span><span class="o">);</span>  
	<span class="n">beanFactory</span><span class="o">.</span><span class="na">registerResolvableDependency</span><span class="o">(</span><span class="nc">ApplicationContext</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="k">this</span><span class="o">);</span>  
	
	<span class="c1">// ... </span>
<span class="o">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="beanfactory-준비-이후-서브클래스-로직-실행">BeanFactory 준비 이후 서브클래스 로직 실행</h3>
<p><code class="language-plaintext highlighter-rouge">postProcessBeanFactory</code> 메서드는 자식 클래스들에서 재정의되어있다. <code class="language-plaintext highlighter-rouge">ServletWebServerApplicationContext</code>에서 Web과 관련된 로직들이 실행된다. 유틸클래스에 값 셋팅 및 <code class="language-plaintext highlighter-rouge">Aware</code> 인터페이스에 대한 설정이 들어간다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* ServletWebServerApplicationContext */</span>

<span class="nd">@Override</span>  
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">postProcessBeanFactory</span><span class="o">(</span><span class="nc">ConfigurableListableBeanFactory</span> <span class="n">beanFactory</span><span class="o">)</span> <span class="o">{</span>  
	<span class="n">beanFactory</span><span class="o">.</span><span class="na">addBeanPostProcessor</span><span class="o">(</span><span class="k">new</span> <span class="nc">WebApplicationContextServletContextAwareProcessor</span><span class="o">(</span><span class="k">this</span><span class="o">));</span>  
	<span class="n">beanFactory</span><span class="o">.</span><span class="na">ignoreDependencyInterface</span><span class="o">(</span><span class="nc">ServletContextAware</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>  
	<span class="n">registerWebApplicationScopes</span><span class="o">();</span>  
<span class="o">}</span>

<span class="kd">private</span> <span class="kt">void</span> <span class="nf">registerWebApplicationScopes</span><span class="o">()</span> <span class="o">{</span>  
	<span class="nc">ExistingWebApplicationScopes</span> <span class="n">existingScopes</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ExistingWebApplicationScopes</span><span class="o">(</span><span class="n">getBeanFactory</span><span class="o">());</span>  
	<span class="nc">WebApplicationContextUtils</span><span class="o">.</span><span class="na">registerWebApplicationScopes</span><span class="o">(</span><span class="n">getBeanFactory</span><span class="o">());</span>  
	<span class="n">existingScopes</span><span class="o">.</span><span class="na">restore</span><span class="o">();</span>  
<span class="o">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="빈-정의-파싱-및-등록">빈 정의 파싱 및 등록</h3>
<p><code class="language-plaintext highlighter-rouge">invokeBeanFactoryPostProcessors</code> 메서드에서 대부분의 빈들의 <code class="language-plaintext highlighter-rouge">BeanDefinition</code>이 등록된다. 고대하던 <strong>컴포넌트 스캔 및 자동 구성 빈들이 등록되는 과정</strong>이다. 뿐만 아니라 빈 정의를 조작하기도 하는데 예를 들어 <code class="language-plaintext highlighter-rouge">@Value</code> 어노테이션이 달린 필드에 환경설정 값을 넣어준다.</p>

<p>코드를 보면 아주 간단하다. 모든 <code class="language-plaintext highlighter-rouge">BeanFactoryPostProcessor</code>에 대한 실행을 <code class="language-plaintext highlighter-rouge">PostProcessorRegistrationDelegate</code>에 위임한다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* ServletWebServerApplicationContext */</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">invokeBeanFactoryPostProcessors</span><span class="o">(</span><span class="nc">ConfigurableListableBeanFactory</span> <span class="n">beanFactory</span><span class="o">)</span> <span class="o">{</span>  
	<span class="nc">PostProcessorRegistrationDelegate</span><span class="o">.</span><span class="na">invokeBeanFactoryPostProcessors</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">,</span> <span class="n">getBeanFactoryPostProcessors</span><span class="o">());</span>  
	
	<span class="c1">// ...</span>
<span class="o">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">PostProcessorRegistrationDelegate</code> 의 정적 메서드 <code class="language-plaintext highlighter-rouge">invokeBeanFactoryPostProcessors</code>에서는 모든 <code class="language-plaintext highlighter-rouge">BeanFactoryPostProcessor</code>가 실행된다. 우선순위에 따라 순서대로 실행되는데 우선순위를 정리하자면 다음과 같다.</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">BeanDefinitionRegistryPostProcessor</code> (<code class="language-plaintext highlighter-rouge">BeanFactoryPostProcessor</code> 상속받음)
    <ul>
      <li>(1순위) 이 중 <code class="language-plaintext highlighter-rouge">PriorityOrderd</code>를 구현한 클래스</li>
      <li>(2순위) 그 다음 <code class="language-plaintext highlighter-rouge">Ordered</code>를 구현한 클래스</li>
      <li>(3순위) 나머지</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">BeanFactoryPostProcessor</code>
    <ul>
      <li>(4순위) 이 중 <code class="language-plaintext highlighter-rouge">PriorityOrderd</code>를 구현한 클래스</li>
      <li>(5순위) 그 다음 <code class="language-plaintext highlighter-rouge">Ordered</code>를 구현한 클래스</li>
      <li>(6순위) 나머지</li>
    </ul>
  </li>
</ol>

<p>아래 코드는 1순위(<code class="language-plaintext highlighter-rouge">BeanDefinitionRegistryPostProcessor</code>, <code class="language-plaintext highlighter-rouge">PriorityOrderd</code>를 구현한 클래스) 후처리기를 실행하는 코드이다.</p>

<p>필자가 가장 궁금해했던 컴포넌트 스캔과 자동 구성 빈들의 <code class="language-plaintext highlighter-rouge">BeanDefinition</code>을 등록하는 일을 담당하는 클래스도 1순위로 처리되는 후처리기인 <code class="language-plaintext highlighter-rouge">ConfigurationClassPostProcessor</code>라는 클래스이다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* PostProcessorRegistrationDelegate */</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">invokeBeanFactoryPostProcessors</span><span class="o">(</span>  
      <span class="nc">ConfigurableListableBeanFactory</span> <span class="n">beanFactory</span><span class="o">,</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">BeanFactoryPostProcessor</span><span class="o">&gt;</span> <span class="n">beanFactoryPostProcessors</span><span class="o">)</span> <span class="o">{</span>
	
	<span class="nc">Set</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">processedBeans</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashSet</span><span class="o">&lt;&gt;();</span>

	<span class="k">if</span> <span class="o">(</span><span class="n">beanFactory</span> <span class="k">instanceof</span> <span class="nc">BeanDefinitionRegistry</span> <span class="n">registry</span><span class="o">)</span> <span class="o">{</span>  
		<span class="nc">List</span><span class="o">&lt;</span><span class="nc">BeanFactoryPostProcessor</span><span class="o">&gt;</span> <span class="n">regularPostProcessors</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;();</span> 
		<span class="nc">List</span><span class="o">&lt;</span><span class="nc">BeanDefinitionRegistryPostProcessor</span><span class="o">&gt;</span> <span class="n">registryProcessors</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;();</span>
		
		<span class="c1">// ...</span>
		
		<span class="nc">List</span><span class="o">&lt;</span><span class="nc">BeanDefinitionRegistryPostProcessor</span><span class="o">&gt;</span> <span class="n">currentRegistryProcessors</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;();</span>

		<span class="nc">String</span><span class="o">[]</span> <span class="n">postProcessorNames</span> <span class="o">=</span> <span class="n">beanFactory</span><span class="o">.</span><span class="na">getBeanNamesForType</span><span class="o">(</span><span class="nc">BeanDefinitionRegistryPostProcessor</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="kc">true</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span> 
		
		<span class="c1">// 1. PriorityOrdered를 구현한 클래스(1순위)만 currentRegistryProcessors에 담기</span>
		<span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">ppName</span> <span class="o">:</span> <span class="n">postProcessorNames</span><span class="o">)</span> <span class="o">{</span>  
			<span class="k">if</span> <span class="o">(</span><span class="n">beanFactory</span><span class="o">.</span><span class="na">isTypeMatch</span><span class="o">(</span><span class="n">ppName</span><span class="o">,</span> <span class="nc">PriorityOrdered</span><span class="o">.</span><span class="na">class</span><span class="o">))</span> <span class="o">{</span>  
				<span class="c1">// 2. ConfigurationClassPostProcessor가 추가됨</span>
				<span class="n">currentRegistryProcessors</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="n">ppName</span><span class="o">,</span> <span class="nc">BeanDefinitionRegistryPostProcessor</span><span class="o">.</span><span class="na">class</span><span class="o">));</span>  
				<span class="n">processedBeans</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">ppName</span><span class="o">);</span>  
			<span class="o">}</span>  
		<span class="o">}</span>
		
		<span class="n">sortPostProcessors</span><span class="o">(</span><span class="n">currentRegistryProcessors</span><span class="o">,</span> <span class="n">beanFactory</span><span class="o">);</span>  
		<span class="n">registryProcessors</span><span class="o">.</span><span class="na">addAll</span><span class="o">(</span><span class="n">currentRegistryProcessors</span><span class="o">);</span>  
		
		<span class="c1">// 3. ConfigurationClassPostProcessor가 실행됨</span>
		<span class="n">invokeBeanDefinitionRegistryPostProcessors</span><span class="o">(</span><span class="n">currentRegistryProcessors</span><span class="o">,</span> <span class="n">registry</span><span class="o">,</span> <span class="n">beanFactory</span><span class="o">.</span><span class="na">getApplicationStartup</span><span class="o">());</span>  
		
		<span class="n">currentRegistryProcessors</span><span class="o">.</span><span class="na">clear</span><span class="o">();</span>  
		
		<span class="c1">// 우선순위에 따라 나머지 BeanFactoryPostProcessor 실행</span>
		<span class="c1">// .. </span>
	<span class="o">}</span>
<span class="o">}</span>

<span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">invokeBeanDefinitionRegistryPostProcessors</span><span class="o">(</span>  
      <span class="nc">Collection</span><span class="o">&lt;?</span> <span class="kd">extends</span> <span class="nc">BeanDefinitionRegistryPostProcessor</span><span class="o">&gt;</span> <span class="n">postProcessors</span><span class="o">,</span> <span class="nc">BeanDefinitionRegistry</span> <span class="n">registry</span><span class="o">,</span> <span class="nc">ApplicationStartup</span> <span class="n">applicationStartup</span><span class="o">)</span> <span class="o">{</span>  
	
	<span class="k">for</span> <span class="o">(</span><span class="nc">BeanDefinitionRegistryPostProcessor</span> <span class="n">postProcessor</span> <span class="o">:</span> <span class="n">postProcessors</span><span class="o">)</span> <span class="o">{</span>  
		<span class="nc">StartupStep</span> <span class="n">postProcessBeanDefRegistry</span> <span class="o">=</span> <span class="n">applicationStartup</span><span class="o">.</span><span class="na">start</span><span class="o">(</span><span class="s">"spring.context.beandef-registry.post-process"</span><span class="o">)</span>  
			<span class="o">.</span><span class="na">tag</span><span class="o">(</span><span class="s">"postProcessor"</span><span class="o">,</span> <span class="nl">postProcessor:</span><span class="o">:</span><span class="n">toString</span><span class="o">);</span>  
		<span class="c1">// 4. 메서드 실행</span>
		<span class="n">postProcessor</span><span class="o">.</span><span class="na">postProcessBeanDefinitionRegistry</span><span class="o">(</span><span class="n">registry</span><span class="o">);</span>  
		<span class="n">postProcessBeanDefRegistry</span><span class="o">.</span><span class="na">end</span><span class="o">();</span>  
	<span class="o">}</span>  
<span class="o">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">ConfigurationClassPostProcessor</code>의 <code class="language-plaintext highlighter-rouge">postProcessBeanDefinitionRegistry(registry)</code> 메서드를 살펴보자. 이 메서드에서는 처음에 메인어플리케이션을 구한 다음 후 다음 3단계로 모든 빈 정의를 등록한다.</p>
<ol>
  <li>parser 생성</li>
  <li>parser를 이용한 메인 어플리케이션 파싱</li>
  <li>reader를 생성하고 reader를 통해 파싱된 <code class="language-plaintext highlighter-rouge">ConfigurationClass</code>들을 <code class="language-plaintext highlighter-rouge">BeanDefinition</code>으로 만들어 로드시킨다.</li>
</ol>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* ConfigurationClassPostProcessor */</span>

<span class="kd">public</span> <span class="kt">void</span> <span class="nf">processConfigBeanDefinitions</span><span class="o">(</span><span class="nc">BeanDefinitionRegistry</span> <span class="n">registry</span><span class="o">)</span> <span class="o">{</span>

	<span class="c1">// configCandidates (mainApplication만 남음)</span>
	<span class="c1">// ...</span>

	<span class="c1">// 1. parser 생성</span>
	<span class="nc">ConfigurationClassParser</span> <span class="n">parser</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ConfigurationClassParser</span><span class="o">(</span>  
	      <span class="k">this</span><span class="o">.</span><span class="na">metadataReaderFactory</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">problemReporter</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">environment</span><span class="o">,</span>  
	      <span class="k">this</span><span class="o">.</span><span class="na">resourceLoader</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">componentScanBeanNameGenerator</span><span class="o">,</span> <span class="n">registry</span><span class="o">);</span>  
	  
	<span class="nc">Set</span><span class="o">&lt;</span><span class="nc">BeanDefinitionHolder</span><span class="o">&gt;</span> <span class="n">candidates</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LinkedHashSet</span><span class="o">&lt;&gt;(</span><span class="n">configCandidates</span><span class="o">);</span>  
	<span class="nc">Set</span><span class="o">&lt;</span><span class="nc">ConfigurationClass</span><span class="o">&gt;</span> <span class="n">alreadyParsed</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashSet</span><span class="o">&lt;&gt;(</span><span class="n">configCandidates</span><span class="o">.</span><span class="na">size</span><span class="o">());</span>  
	
	<span class="k">do</span> <span class="o">{</span>  
		<span class="nc">StartupStep</span> <span class="n">processConfig</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">applicationStartup</span><span class="o">.</span><span class="na">start</span><span class="o">(</span><span class="s">"spring.context.config-classes.parse"</span><span class="o">);</span>  
		
		<span class="c1">// 2. parsing</span>
		<span class="n">parser</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">candidates</span><span class="o">);</span>  
		<span class="n">parser</span><span class="o">.</span><span class="na">validate</span><span class="o">();</span>  
		
		<span class="nc">Set</span><span class="o">&lt;</span><span class="nc">ConfigurationClass</span><span class="o">&gt;</span> <span class="n">configClasses</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LinkedHashSet</span><span class="o">&lt;&gt;(</span><span class="n">parser</span><span class="o">.</span><span class="na">getConfigurationClasses</span><span class="o">());</span>  
		<span class="n">configClasses</span><span class="o">.</span><span class="na">removeAll</span><span class="o">(</span><span class="n">alreadyParsed</span><span class="o">);</span>  
		
		<span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">reader</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>  
		  <span class="k">this</span><span class="o">.</span><span class="na">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ConfigurationClassBeanDefinitionReader</span><span class="o">(</span>  
				<span class="n">registry</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">sourceExtractor</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">resourceLoader</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">environment</span><span class="o">,</span>  
				<span class="k">this</span><span class="o">.</span><span class="na">importBeanNameGenerator</span><span class="o">,</span> <span class="n">parser</span><span class="o">.</span><span class="na">getImportRegistry</span><span class="o">());</span>  
		<span class="o">}</span>  
		
		<span class="c1">// 3. reader를 통한 빈정의들을 로드시키기</span>
		<span class="k">this</span><span class="o">.</span><span class="na">reader</span><span class="o">.</span><span class="na">loadBeanDefinitions</span><span class="o">(</span><span class="n">configClasses</span><span class="o">);</span>  
		
		<span class="c1">// ...</span>
	<span class="o">}</span>  
	<span class="k">while</span> <span class="o">(!</span><span class="n">candidates</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>

<p>여기서 parser 역할을 하는 <code class="language-plaintext highlighter-rouge">ConfigurationClassParser</code>를 좀 더 살펴보자. 객체를 생성하는 생성자를 보면 드디어 필자의 호기심을 풀어줄 귀한 존재가 보인다. 바로 <code class="language-plaintext highlighter-rouge">ComponentScanAnnotationParser</code>이다. 이름부터가 <em>‘나 컴포넌트스캔 파서야~ ‘</em> 라고 존재감을 뿜뿜하고 있지 않은가.</p>

<p>유레카~ 소리지르고 싶지만 마음을 진정시키고 차분하게 조금 더 살펴보자. <code class="language-plaintext highlighter-rouge">ConfigurationClassParser</code>를 생성한 후에 <code class="language-plaintext highlighter-rouge">mainApplication</code>(configCandidates)를 인자로 <code class="language-plaintext highlighter-rouge">parse</code> 메서드를 실행한다.</p>

<p><code class="language-plaintext highlighter-rouge">parse</code>메서드에서는 크게 두 가지 메서드가 실행된다. <strong>컴포넌트 스캔을 통해 개발자가 정의한 빈 클래스를 긁는 <code class="language-plaintext highlighter-rouge">parse(metadata, beanName)</code>와 <code class="language-plaintext highlighter-rouge">AutoConfigurationImportSelector</code>를 통해 자동 구성 빈 클래스를 가져오는 <code class="language-plaintext highlighter-rouge">deferredImportSelectorHandler</code>가 실행</strong>된다.</p>

<p>해당 메서드들의 내부는 상당히 복잡한데 어노테이션 <code class="language-plaintext highlighter-rouge">@Configuration</code>, <code class="language-plaintext highlighter-rouge">@Component</code>을 기준으로 클래스들을 가져오는 것이다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">ConfigurationClassParser</span> <span class="o">{</span>
	<span class="c1">// ...</span>
	
	<span class="kd">private</span> <span class="kd">final</span> <span class="nc">DeferredImportSelectorHandler</span> <span class="n">deferredImportSelectorHandler</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DeferredImportSelectorHandler</span><span class="o">();</span>
	
	<span class="kd">public</span> <span class="nf">ConfigurationClassParser</span><span class="o">(</span><span class="nc">MetadataReaderFactory</span> <span class="n">metadataReaderFactory</span><span class="o">,</span> 
	<span class="nc">ProblemReporter</span> <span class="n">problemReporter</span><span class="o">,</span> <span class="nc">Environment</span> <span class="n">environment</span><span class="o">,</span> <span class="nc">ResourceLoader</span> <span class="n">resourceLoader</span><span class="o">,</span>  
	  <span class="nc">BeanNameGenerator</span> <span class="n">componentScanBeanNameGenerator</span><span class="o">,</span> <span class="nc">BeanDefinitionRegistry</span> <span class="n">registry</span><span class="o">)</span> <span class="o">{</span>  
	
		<span class="k">this</span><span class="o">.</span><span class="na">metadataReaderFactory</span> <span class="o">=</span> <span class="n">metadataReaderFactory</span><span class="o">;</span>  
		<span class="k">this</span><span class="o">.</span><span class="na">problemReporter</span> <span class="o">=</span> <span class="n">problemReporter</span><span class="o">;</span>  
		<span class="k">this</span><span class="o">.</span><span class="na">environment</span> <span class="o">=</span> <span class="n">environment</span><span class="o">;</span>  
		<span class="k">this</span><span class="o">.</span><span class="na">resourceLoader</span> <span class="o">=</span> <span class="n">resourceLoader</span><span class="o">;</span>  
		<span class="k">this</span><span class="o">.</span><span class="na">propertySourceRegistry</span> <span class="o">=</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">environment</span> <span class="k">instanceof</span> <span class="nc">ConfigurableEnvironment</span> <span class="n">ce</span> <span class="o">?</span>  
			 <span class="k">new</span> <span class="nf">PropertySourceRegistry</span><span class="o">(</span><span class="k">new</span> <span class="nc">PropertySourceProcessor</span><span class="o">(</span><span class="n">ce</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">resourceLoader</span><span class="o">))</span> <span class="o">:</span> <span class="kc">null</span><span class="o">);</span>  
		<span class="k">this</span><span class="o">.</span><span class="na">registry</span> <span class="o">=</span> <span class="n">registry</span><span class="o">;</span>  
		
		<span class="c1">// 드디어 찾았다 요놈</span>
		<span class="k">this</span><span class="o">.</span><span class="na">componentScanParser</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ComponentScanAnnotationParser</span><span class="o">(</span>  
			 <span class="n">environment</span><span class="o">,</span> <span class="n">resourceLoader</span><span class="o">,</span> <span class="n">componentScanBeanNameGenerator</span><span class="o">,</span> <span class="n">registry</span><span class="o">);</span>  
		<span class="k">this</span><span class="o">.</span><span class="na">conditionEvaluator</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ConditionEvaluator</span><span class="o">(</span><span class="n">registry</span><span class="o">,</span> <span class="n">environment</span><span class="o">,</span> <span class="n">resourceLoader</span><span class="o">);</span>  
	<span class="o">}</span>
	
	
	<span class="kd">public</span> <span class="kt">void</span> <span class="nf">parse</span><span class="o">(</span><span class="nc">Set</span><span class="o">&lt;</span><span class="nc">BeanDefinitionHolder</span><span class="o">&gt;</span> <span class="n">configCandidates</span><span class="o">)</span> <span class="o">{</span>  
		<span class="k">for</span> <span class="o">(</span><span class="nc">BeanDefinitionHolder</span> <span class="n">holder</span> <span class="o">:</span> <span class="n">configCandidates</span><span class="o">)</span> <span class="o">{</span>  
			<span class="nc">BeanDefinition</span> <span class="n">bd</span> <span class="o">=</span> <span class="n">holder</span><span class="o">.</span><span class="na">getBeanDefinition</span><span class="o">();</span>  
			<span class="k">try</span> <span class="o">{</span>  
				<span class="k">if</span> <span class="o">(</span><span class="n">bd</span> <span class="k">instanceof</span> <span class="nc">AnnotatedBeanDefinition</span> <span class="n">annotatedBeanDef</span><span class="o">)</span> <span class="o">{</span>  
					<span class="c1">// 내부적으로 componentScanParser 로 우리가 선언한 빈들을 등록</span>
					<span class="n">parse</span><span class="o">(</span><span class="n">annotatedBeanDef</span><span class="o">.</span><span class="na">getMetadata</span><span class="o">(),</span> <span class="n">holder</span><span class="o">.</span><span class="na">getBeanName</span><span class="o">());</span>
				<span class="o">}</span>  
				<span class="c1">// ... 다른 pasre 방법</span>
			<span class="o">}</span>  
			<span class="k">catch</span> <span class="o">(</span><span class="nc">BeanDefinitionStoreException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>  
				<span class="k">throw</span> <span class="n">ex</span><span class="o">;</span>  
			<span class="o">}</span>  
			<span class="k">catch</span> <span class="o">(</span><span class="nc">Throwable</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>  
				<span class="k">throw</span> <span class="k">new</span> <span class="nf">BeanDefinitionStoreException</span><span class="o">(</span>  
			   <span class="s">"Failed to parse configuration class ["</span> <span class="o">+</span> <span class="n">bd</span><span class="o">.</span><span class="na">getBeanClassName</span><span class="o">()</span> <span class="o">+</span> <span class="s">"]"</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span>  
			<span class="o">}</span>  
		<span class="o">}</span>  
		
		<span class="c1">// 얘는 AutoConfiguration을 처리하는 하는 친구</span>
		<span class="k">this</span><span class="o">.</span><span class="na">deferredImportSelectorHandler</span><span class="o">.</span><span class="na">process</span><span class="o">();</span>  
	<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">BeanFactoryPostProcesor</code>을 실행하는 이 과정을 짧게 정리하자면 다음과 같이 진행된다.</p>

<ul>
  <li>우선순위에 따라 후처리기들을 실행
    <ul>
      <li>1순위로 <code class="language-plaintext highlighter-rouge">ConfigurationClassPostProcessor</code> 실행
        <ul>
          <li><code class="language-plaintext highlighter-rouge">ConfigurationClassParser</code>를 생성하여 빈 클래스 parse
            <ul>
              <li><code class="language-plaintext highlighter-rouge">ComponentScanAnnotationParser</code>를 통한 컴포넌트 스캔으로 빈들을 불러옴</li>
              <li><code class="language-plaintext highlighter-rouge">DeferredImportSelectorHandler</code>를 통해 <code class="language-plaintext highlighter-rouge">AutoConfigurationImportSelector</code>를 읽어와 AutoConfiguration 빈 클래스들을 불러옴</li>
            </ul>
          </li>
          <li><code class="language-plaintext highlighter-rouge">ConfigurationClassBeanDefinitionReader</code>를 통한 파싱한 클래스들을<code class="language-plaintext highlighter-rouge">BeanDefinition</code>로 등록</li>
        </ul>
      </li>
      <li>(추가) 4순위로 <code class="language-plaintext highlighter-rouge">PropertySourcesPlaceholderConfigurer</code>가 실행된다.
        <ul>
          <li><code class="language-plaintext highlighter-rouge">PropertySourcesPropertyResolver</code>를 이용하여 <code class="language-plaintext highlighter-rouge">@Value</code>에 환경설정 값들을 추가한다.</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p><br /></p>

<h3 id="beanpostprocessor-등록">BeanPostProcessor 등록</h3>
<p><code class="language-plaintext highlighter-rouge">BeanPostProcessor</code>는 빈 생성 이후 초기화 전후로 진행할 로직들이 들어간다. 빈 생성은 refresh의 마지막 단계에서 이루어진다.</p>

<p><code class="language-plaintext highlighter-rouge">BeanFactoryPostProcessor</code>를 실행할 때 사용했던 <code class="language-plaintext highlighter-rouge">PostProcessorRegistrationDelegate</code>에게 <code class="language-plaintext highlighter-rouge">BeanPostProcessor</code> 등록을 위임한다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* AbstractApplicationContext */</span>

<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">registerBeanPostProcessors</span><span class="o">(</span><span class="nc">ConfigurableListableBeanFactory</span> <span class="n">beanFactory</span><span class="o">)</span> <span class="o">{</span>  
	<span class="nc">PostProcessorRegistrationDelegate</span><span class="o">.</span><span class="na">registerBeanPostProcessors</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">,</span> <span class="k">this</span><span class="o">);</span>  
<span class="o">}</span>
</code></pre></div></div>

<p>위 메서드 내부에서는 <code class="language-plaintext highlighter-rouge">PostProcessorRegistrationDelegate</code>의 메서드 <code class="language-plaintext highlighter-rouge">invokeBeanFactoryPostProcessors</code>와 마찬가지로 우선순위(PriorityOrdered 타입 - Ordered 타입 - 나머지 순)를 나누어 순위대로 <code class="language-plaintext highlighter-rouge">BeanPostProcessor</code>를 등록한다.</p>

<p>대표적으로 다음과 같은 <code class="language-plaintext highlighter-rouge">BeanPostProcessor</code>가 등록된다.</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">AutowiredAnnotationBeanPostProcessor</code>
    <ul>
      <li><code class="language-plaintext highlighter-rouge">@Autowired</code> 어노테이션을 처리하여 <strong>의존성 주입처리</strong></li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">PersistenceAnnotationBeanPostProcessor</code>
    <ul>
      <li>JPA 사용 시 적용되는 것으로 <strong>EntityManager 주입</strong>을 위한 <code class="language-plaintext highlighter-rouge">@PersistenceContext</code> 어노테이션 처리</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">ConfigurationPropertiesBeanPostProcessor</code>
    <ul>
      <li><code class="language-plaintext highlighter-rouge">@ConfigurationProperties</code> 어노테이션을 처리하여 <code class="language-plaintext highlighter-rouge">PropertySource</code> 바인딩</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">AnnotationAwareAspectJAutoProxyCreator</code>
    <ul>
      <li><code class="language-plaintext highlighter-rouge">@AspectJ</code>뿐만 아니라 <code class="language-plaintext highlighter-rouge">@Aspect</code>도 처리하여 <strong>프록시 빈을 생성</strong></li>
      <li>내부적으로 <code class="language-plaintext highlighter-rouge">ProxyFactory</code>를 통해 <code class="language-plaintext highlighter-rouge">ObjenesisCglibAopProxy</code>, <code class="language-plaintext highlighter-rouge">JdkDynamicAopProxy</code>를 이용해 프록시 객체를 만들어 낸다.</li>
    </ul>
  </li>
</ul>

<p><br /></p>

<h3 id="빈-정의-등록-후-서브클래스-로직-실행">빈 정의 등록 후 서브클래스 로직 실행</h3>
<p><code class="language-plaintext highlighter-rouge">onRefresh</code> 메서드는 서브클래스인 <code class="language-plaintext highlighter-rouge">ServletWebServerApplicationContext</code>에 재정의 되어 있다. <code class="language-plaintext highlighter-rouge">createWebServer()</code>라는 이름답게 웹서버를 생성하는 메서드이다. 해당 메서드 내부에서는 <code class="language-plaintext highlighter-rouge">getWebServer()</code>로 Tomcat등의 설정한 웹서버가 만들지면서 동시에 <code class="language-plaintext highlighter-rouge">ServletContext</code>(서블릿 실행환경)도 띄워진다. 이때 <code class="language-plaintext highlighter-rouge">ServletContextInitializer</code>라는 콜백 함수를 넘겨서 <code class="language-plaintext highlighter-rouge">ServletContext</code>를 생성되면 멤버 변수에 <code class="language-plaintext highlighter-rouge">ServletContext</code>를 저장한다.(해당 변수는 <code class="language-plaintext highlighter-rouge">ServletWebServerApplicationContext</code>의 부모 클래스인 <code class="language-plaintext highlighter-rouge">GenericWebApplicationContext</code>에 존재한다.)</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* ServletWebServerApplicationContext.java */</span>

<span class="nd">@Override</span>  
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onRefresh</span><span class="o">()</span> <span class="o">{</span>  
	<span class="kd">super</span><span class="o">.</span><span class="na">onRefresh</span><span class="o">();</span>  
	<span class="k">try</span> <span class="o">{</span>  
		<span class="n">createWebServer</span><span class="o">();</span>  
	<span class="o">}</span>  
	<span class="k">catch</span> <span class="o">(</span><span class="nc">Throwable</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>  
		<span class="k">throw</span> <span class="k">new</span> <span class="nf">ApplicationContextException</span><span class="o">(</span><span class="s">"Unable to start web server"</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span>  
	<span class="o">}</span>  
<span class="o">}</span>

<span class="kd">private</span> <span class="kt">void</span> <span class="nf">createWebServer</span><span class="o">()</span> <span class="o">{</span>  
	<span class="nc">WebServer</span> <span class="n">webServer</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">webServer</span><span class="o">;</span>  
	<span class="nc">ServletContext</span> <span class="n">servletContext</span> <span class="o">=</span> <span class="n">getServletContext</span><span class="o">();</span>  
	<span class="k">if</span> <span class="o">(</span><span class="n">webServer</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">servletContext</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>  
	
		<span class="nc">ServletWebServerFactory</span> <span class="n">factory</span> <span class="o">=</span> <span class="n">getWebServerFactory</span><span class="o">();</span>  
		
		<span class="c1">// WeberServer 생성 및 콜백함수로 ServletContext 생성</span>
		<span class="k">this</span><span class="o">.</span><span class="na">webServer</span> <span class="o">=</span> <span class="n">factory</span><span class="o">.</span><span class="na">getWebServer</span><span class="o">(</span><span class="n">getSelfInitializer</span><span class="o">());</span>  
		
		<span class="n">getBeanFactory</span><span class="o">().</span><span class="na">registerSingleton</span><span class="o">(</span><span class="s">"webServerGracefulShutdown"</span><span class="o">,</span>  
			<span class="k">new</span> <span class="nf">WebServerGracefulShutdownLifecycle</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">webServer</span><span class="o">));</span>  
		<span class="n">getBeanFactory</span><span class="o">().</span><span class="na">registerSingleton</span><span class="o">(</span><span class="s">"webServerStartStop"</span><span class="o">,</span>  
			<span class="k">new</span> <span class="nf">WebServerStartStopLifecycle</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">webServer</span><span class="o">));</span>  
	<span class="o">}</span>  
	<span class="k">else</span> <span class="nf">if</span> <span class="o">(</span><span class="n">servletContext</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>  
		<span class="k">try</span> <span class="o">{</span>  
			<span class="n">getSelfInitializer</span><span class="o">().</span><span class="na">onStartup</span><span class="o">(</span><span class="n">servletContext</span><span class="o">);</span>  
		<span class="o">}</span>  
		<span class="k">catch</span> <span class="o">(</span><span class="nc">ServletException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>  
			<span class="k">throw</span> <span class="k">new</span> <span class="nf">ApplicationContextException</span><span class="o">(</span><span class="s">"Cannot initialize servlet context"</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span>  
		<span class="o">}</span>  
	<span class="o">}</span>  
	<span class="n">initPropertySources</span><span class="o">();</span>  
<span class="o">}</span>

<span class="kd">private</span> <span class="n">org</span><span class="o">.</span><span class="na">springframework</span><span class="o">.</span><span class="na">boot</span><span class="o">.</span><span class="na">web</span><span class="o">.</span><span class="na">servlet</span><span class="o">.</span><span class="na">ServletContextInitializer</span> <span class="nf">getSelfInitializer</span><span class="o">()</span> <span class="o">{</span>  
	<span class="k">return</span> <span class="k">this</span><span class="o">::</span><span class="n">selfInitialize</span><span class="o">;</span>  
<span class="o">}</span>  
  
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">selfInitialize</span><span class="o">(</span><span class="nc">ServletContext</span> <span class="n">servletContext</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">ServletException</span> <span class="o">{</span>
	<span class="c1">// 내부 멤버 변수에 servletContext 저장</span>
	<span class="n">prepareWebApplicationContext</span><span class="o">(</span><span class="n">servletContext</span><span class="o">);</span>  
	<span class="n">registerApplicationScope</span><span class="o">(</span><span class="n">servletContext</span><span class="o">);</span>  
	<span class="nc">WebApplicationContextUtils</span><span class="o">.</span><span class="na">registerEnvironmentBeans</span><span class="o">(</span><span class="n">getBeanFactory</span><span class="o">(),</span> <span class="n">servletContext</span><span class="o">);</span>  
	<span class="k">for</span> <span class="o">(</span><span class="nc">ServletContextInitializer</span> <span class="n">beans</span> <span class="o">:</span> <span class="n">getServletContextInitializerBeans</span><span class="o">())</span> <span class="o">{</span>  
		<span class="n">beans</span><span class="o">.</span><span class="na">onStartup</span><span class="o">(</span><span class="n">servletContext</span><span class="o">);</span>  
	<span class="o">}</span>  
<span class="o">}</span>

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

<p><br /></p>

<h3 id="빈-객체-생성">빈 객체 생성</h3>
<p>등록된 <code class="language-plaintext highlighter-rouge">BeanDefinition</code>을 이용해서 실제로 빈들을 생성하는 단계이다. 빈들이 생성될 뿐만 아니라 등록한<code class="language-plaintext highlighter-rouge">BeanPostProcessor</code>들을 실행하면서 필요한 어노테이션을 처리하고 의존성을 주입하며, 필요에 의해 프록시 빈들(@Trasactional, @Aspect 어노테이션 등이 달린 클래스 및 메서드들)도 생성된다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* ServletWebServerApplicationContext.java */</span>

<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">finishBeanFactoryInitialization</span><span class="o">(</span><span class="nc">ConfigurableListableBeanFactory</span> <span class="n">beanFactory</span><span class="o">)</span> <span class="o">{</span>  
	
	<span class="c1">// ...</span>
	
	<span class="n">beanFactory</span><span class="o">.</span><span class="na">preInstantiateSingletons</span><span class="o">();</span>  
<span class="o">}</span>
</code></pre></div></div>

<p>내부적으로 사용하는 <code class="language-plaintext highlighter-rouge">DefaultListableBeanFactory</code>의 <code class="language-plaintext highlighter-rouge">preInstantiateSingletons()</code>를 통해 이러한 과정들이 전부 실행된다. 결론적으로 <code class="language-plaintext highlighter-rouge">DefaultListableBeanFactory</code>가 상속 받는 클래스인 <code class="language-plaintext highlighter-rouge">AbstractAutowireCapableBeanFactory</code>의 <code class="language-plaintext highlighter-rouge">createBean</code> 메서드를 통해 빈 생성 및 <code class="language-plaintext highlighter-rouge">BeanFactoryPostProcessor</code>를 통한 의존성 주입, 프록시 빈 들이 만들어진다.</p>

<p>이 과정이 마무리되고 나서 사용했던 자원, 캐시들을 반납하며 refresh 과정을 마무리한다. 이후에는 <a href="https://rokwonk.github.io/spring/spring-boot-deep-dive-3/">SpringApplication 실행(run 과정)</a>에서 이야기한 Runner들을 실행하고 유저들의 요청을 받을 수 있는 상태가 된다.</p>

<p><br /></p>

<h3 id="마무리">마무리</h3>
<p>지금까지 스프링 부트 프로젝트가 어떤 과정을 거치며 실행되는지에 대해 큰 흐름을 기준으로 살펴보았다. 여러 과정을 거치면서 기존의 의문점들이 많이 풀렸을 뿐만 아니라 단계마다 사용되는 각 객체들이 어떤 역할을 하는지 찾아보면서 새롭게 알게된 기능이나 API들도 여럿 있었다.</p>

<p>생각 이상으로 배운 것들이 많아 앞으로도 개발을 하면서 동작과정에 궁금증이 생긴다면 Deep Dive 시리즈로 포스팅 해보고자 한다.</p>]]></content><author><name>RokWon K</name></author><category term="Spring" /><summary type="html"><![CDATA[컴포넌트 스캔과 자동구성 빈들이 실제로 등록되고 객체로 만들어지는 과정을 쫒아가보자]]></summary></entry><entry><title type="html">Spring Boot Deep Dive(3) - SpringApplication run 과정</title><link href="https://rokwonk.github.io/spring/spring-boot-deep-dive-3/" rel="alternate" type="text/html" title="Spring Boot Deep Dive(3) - SpringApplication run 과정" /><published>2024-01-01T00:00:00+09:00</published><updated>2024-01-01T00:00:00+09:00</updated><id>https://rokwonk.github.io/spring/Spring-Boot-Deep-Dive-3-SpringApplication-run-%EA%B3%BC%EC%A0%95</id><content type="html" xml:base="https://rokwonk.github.io/spring/spring-boot-deep-dive-3/"><![CDATA[<p><br /></p>

<p><a href="https://rokwonk.github.io/spring/spring-boot-deep-dive-1/">SpringApplication 초기화 과정</a>에서 <code class="language-plaintext highlighter-rouge">SpringApplication</code> 객체가 생성되면서 어떤 것들이 초기화 되는지 살펴보았다. <code class="language-plaintext highlighter-rouge">SpringApplication</code>은 인스턴스화를 후에 곧받로 인스턴스 <code class="language-plaintext highlighter-rouge">run</code> 메서드를 실행한다.</p>

<p><code class="language-plaintext highlighter-rouge">run</code> 메서드 내부에서는 단계별로 여러가지 일을 수행한다. (ApplicationContext이 수행하는 역할은 <a href="https://rokwonk.github.io/spring/spring-boot-deep-dive-2/">ApplicationContext 상속구조</a>를 확인해보자.) <code class="language-plaintext highlighter-rouge">run</code> 메서드 내부에서 수행하는 작업들을 소스코드를 통해 알아보자.</p>

<blockquote>
  <p>Spring Boot 3.2.1 버전을 기준으로 작성되었습니다.</p>
</blockquote>

<p><code class="language-plaintext highlighter-rouge">run</code> 메서드에는 다음의 로직들이 실행되는데 먼저 가볍게 주석으로 각 역할에 대해 설명해두었다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* SpringApplication.java */</span>
<span class="kd">public</span> <span class="nc">ConfigurableApplicationContext</span> <span class="nf">run</span><span class="o">(</span><span class="nc">String</span><span class="o">...</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>  
	<span class="c1">// 시간초 타이머</span>
	<span class="nc">Startup</span> <span class="n">startup</span> <span class="o">=</span> <span class="nc">Startup</span><span class="o">.</span><span class="na">create</span><span class="o">();</span>  

	<span class="c1">// ShutdownHook 준비</span>
	<span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">registerShutdownHook</span><span class="o">)</span> <span class="o">{</span>  
	  <span class="nc">SpringApplication</span><span class="o">.</span><span class="na">shutdownHook</span><span class="o">.</span><span class="na">enableShutdownHookAddition</span><span class="o">();</span>  
	<span class="o">}</span>  

	<span class="c1">// BootStrapContext 생성</span>
	<span class="nc">DefaultBootstrapContext</span> <span class="n">bootstrapContext</span> <span class="o">=</span> <span class="n">createBootstrapContext</span><span class="o">();</span>  
	<span class="nc">ConfigurableApplicationContext</span> <span class="n">context</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>  

	<span class="c1">// AWT Headless 관련 프로퍼티 설정</span>
	<span class="n">configureHeadlessProperty</span><span class="o">();</span>  

	<span class="c1">// 리스너 이벤트 뿌려주기</span>
	<span class="nc">SpringApplicationRunListeners</span> <span class="n">listeners</span> <span class="o">=</span> <span class="n">getRunListeners</span><span class="o">(</span><span class="n">args</span><span class="o">);</span>  
	<span class="n">listeners</span><span class="o">.</span><span class="na">starting</span><span class="o">(</span><span class="n">bootstrapContext</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">mainApplicationClass</span><span class="o">);</span>  
	
	<span class="k">try</span> <span class="o">{</span>  
		<span class="c1">// 시작 시 Argument, Environment 설정</span>
		<span class="nc">ApplicationArguments</span> <span class="n">applicationArguments</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DefaultApplicationArguments</span><span class="o">(</span><span class="n">args</span><span class="o">);</span>  
		<span class="nc">ConfigurableEnvironment</span> <span class="n">environment</span> <span class="o">=</span> <span class="n">prepareEnvironment</span><span class="o">(</span><span class="n">listeners</span><span class="o">,</span> <span class="n">bootstrapContext</span><span class="o">,</span> <span class="n">applicationArguments</span><span class="o">);</span>  
		
		<span class="c1">// 시작 시 배너 출력</span>
		<span class="nc">Banner</span> <span class="n">printedBanner</span> <span class="o">=</span> <span class="n">printBanner</span><span class="o">(</span><span class="n">environment</span><span class="o">);</span>  
		
		<span class="c1">// ApplicationContext 생성</span>
		<span class="n">context</span> <span class="o">=</span> <span class="n">createApplicationContext</span><span class="o">();</span>  
		
		<span class="c1">// Startup 셋팅</span>
		<span class="n">context</span><span class="o">.</span><span class="na">setApplicationStartup</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">applicationStartup</span><span class="o">);</span>  
		
		<span class="c1">// ApplicationContext 준비단계</span>
		<span class="n">prepareContext</span><span class="o">(</span><span class="n">bootstrapContext</span><span class="o">,</span> <span class="n">context</span><span class="o">,</span> <span class="n">environment</span><span class="o">,</span> <span class="n">listeners</span><span class="o">,</span> <span class="n">applicationArguments</span><span class="o">,</span> <span class="n">printedBanner</span><span class="o">);</span>  
		
		<span class="c1">// ApplicationContext refresh단계</span>
		<span class="n">refreshContext</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>  
		
		<span class="c1">// ApplicationContext refresh 이후</span>
		<span class="n">afterRefresh</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">applicationArguments</span><span class="o">);</span>  
		
		<span class="n">startup</span><span class="o">.</span><span class="na">started</span><span class="o">();</span>  
		<span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">logStartupInfo</span><span class="o">)</span> <span class="o">{</span>  
		 <span class="k">new</span> <span class="nf">StartupInfoLogger</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">mainApplicationClass</span><span class="o">).</span><span class="na">logStarted</span><span class="o">(</span><span class="n">getApplicationLog</span><span class="o">(),</span> <span class="n">startup</span><span class="o">);</span>  
		<span class="o">}</span>
		<span class="n">listeners</span><span class="o">.</span><span class="na">started</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">startup</span><span class="o">.</span><span class="na">timeTakenToStarted</span><span class="o">());</span>  
		
		<span class="c1">// ApplicationRunner, CommandLineRunner 실행</span>
		<span class="n">callRunners</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">applicationArguments</span><span class="o">);</span>  
	<span class="o">}</span>  
	<span class="k">catch</span> <span class="o">(</span><span class="nc">Throwable</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>  
		<span class="k">if</span> <span class="o">(</span><span class="n">ex</span> <span class="k">instanceof</span> <span class="nc">AbandonedRunException</span><span class="o">)</span> <span class="o">{</span>  
		 <span class="k">throw</span> <span class="n">ex</span><span class="o">;</span>  
		<span class="o">}</span>  
		<span class="n">handleRunFailure</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">ex</span><span class="o">,</span> <span class="n">listeners</span><span class="o">);</span>  
		<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="n">ex</span><span class="o">);</span>  
	<span class="o">}</span>  
	<span class="k">try</span> <span class="o">{</span>  
		<span class="k">if</span> <span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">isRunning</span><span class="o">())</span> <span class="o">{</span>  
		 <span class="n">listeners</span><span class="o">.</span><span class="na">ready</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">startup</span><span class="o">.</span><span class="na">ready</span><span class="o">());</span>  
		<span class="o">}</span>  
	<span class="o">}</span>  
	<span class="k">catch</span> <span class="o">(</span><span class="nc">Throwable</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>  
		<span class="k">if</span> <span class="o">(</span><span class="n">ex</span> <span class="k">instanceof</span> <span class="nc">AbandonedRunException</span><span class="o">)</span> <span class="o">{</span>  
		 <span class="k">throw</span> <span class="n">ex</span><span class="o">;</span>  
		<span class="o">}</span>  
		<span class="n">handleRunFailure</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">ex</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>  
		<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="n">ex</span><span class="o">);</span>  
	<span class="o">}</span>  
	<span class="k">return</span> <span class="n">context</span><span class="o">;</span>  
<span class="o">}</span>
</code></pre></div></div>

<p>핵심로직을 기준으로 그루핑시켜 보면 다음과 같다.</p>
<ol>
  <li><strong>사전 준비</strong>
    <ul>
      <li>실행시간 체크할 Startup</li>
      <li>ShutDownHook 설정</li>
      <li>BootStrapContext 생성</li>
      <li>AWT 설정 및 Run Listener 생성</li>
    </ul>
  </li>
  <li><strong>환경 설정 셋팅</strong>
    <ul>
      <li>실행될 때 넘긴 인자(Argument)와 우리가 작성 <code class="language-plaintext highlighter-rouge">applicaiton.properties</code>와 같은 환경설정 셋팅</li>
    </ul>
  </li>
  <li><strong><code class="language-plaintext highlighter-rouge">ApplicationContext</code> 생성 &amp; 실행</strong>
    <ul>
      <li><code class="language-plaintext highlighter-rouge">ApplicationContext</code> 생성</li>
      <li>Startup들 셋팅</li>
      <li>prepare</li>
      <li>refresh</li>
    </ul>
  </li>
  <li><strong>실행 완료 - 마무리</strong>
    <ul>
      <li>설정된 Runner들을(개발자가 설정함) 실행</li>
    </ul>
  </li>
</ol>

<p><br /></p>

<h2 id="사전-준비">사전 준비</h2>
<p>이 단계에서는 준비 단계이다. 본격적으로 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>을 실행하기 전 필요한 것들을 셋팅한다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* SpringApplication.java */</span>
<span class="kd">public</span> <span class="nc">ConfigurableApplicationContext</span> <span class="nf">run</span><span class="o">(</span><span class="nc">String</span><span class="o">...</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>  
	<span class="c1">// 1. 시간초 타이머</span>
	<span class="nc">Startup</span> <span class="n">startup</span> <span class="o">=</span> <span class="nc">Startup</span><span class="o">.</span><span class="na">create</span><span class="o">();</span>  

	<span class="c1">// 2. ShutdownHook 준비</span>
	<span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">registerShutdownHook</span><span class="o">)</span> <span class="o">{</span>  
	  <span class="nc">SpringApplication</span><span class="o">.</span><span class="na">shutdownHook</span><span class="o">.</span><span class="na">enableShutdownHookAddition</span><span class="o">();</span>  
	<span class="o">}</span>  

	<span class="c1">// 3. BootStrapContext 생성</span>
	<span class="nc">DefaultBootstrapContext</span> <span class="n">bootstrapContext</span> <span class="o">=</span> <span class="n">createBootstrapContext</span><span class="o">();</span>  
	<span class="nc">ConfigurableApplicationContext</span> <span class="n">context</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>  

	<span class="c1">// 4. AWT Headless 관련 프로퍼티 설정</span>
	<span class="n">configureHeadlessProperty</span><span class="o">();</span>  

	<span class="c1">// 5. 리스너 셋팅</span>
	<span class="nc">SpringApplicationRunListeners</span> <span class="n">listeners</span> <span class="o">=</span> <span class="n">getRunListeners</span><span class="o">(</span><span class="n">args</span><span class="o">);</span>  

	<span class="c1">// 6. staring 이벤트 호출</span>
	<span class="n">listeners</span><span class="o">.</span><span class="na">starting</span><span class="o">(</span><span class="n">bootstrapContext</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">mainApplicationClass</span><span class="o">);</span>  

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

<p><br /></p>

<h3 id="실행-시간-체크---startup-셋팅">실행 시간 체크 - Startup 셋팅</h3>
<p>처음 시작은 실행시간을 체크할 타이머 역할을 하는 Startup 셋팅하는 걸로 시작한다. 구현체를 보면 시간을 체크하기 위한 메서드들로만 이루어져 있다.</p>

<p><br /></p>

<h3 id="shutdownhook-사용-설정">ShutDownHook 사용 설정</h3>
<p>두 번째로 <code class="language-plaintext highlighter-rouge">ShutDownHook</code>을 준비하다. <code class="language-plaintext highlighter-rouge">registerShutdownHook</code>은 기본값으로 true로 설정되어 있기때문에 활성화된다.</p>

<blockquote>
  <p><strong>ShutDownHook</strong></p>

  <p>프로그램이 예기치 못하게 종료되는 상황에서 별도의 쓰레드를 통해 연결 종료, 및 자원 반납의 처리를 할 수 있도록 작업을 처리해주는 역할을 해준다.</p>
</blockquote>

<p><br /></p>

<h3 id="임시-컨텍스트-bootstrapcontext-생성">임시 컨텍스트 BootStrapContext 생성</h3>
<p>세 번째로 <code class="language-plaintext highlighter-rouge">BootStrapContext</code>를 생성하고 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>의 변수를 선언한다. <code class="language-plaintext highlighter-rouge">BootStrapContext</code>는 <code class="language-plaintext highlighter-rouge">ApplicationContext</code> 생성 전에 <strong>임시로 사용되는 Context이다. 어플리케이션 설정 및 환경설정 시에 사용</strong>된다. <code class="language-plaintext highlighter-rouge">SpringApplicationRunListeners</code>의 <code class="language-plaintext highlighter-rouge">starting</code>과 <code class="language-plaintext highlighter-rouge">environmentPrepared</code> 두 이벤트를 처리하는데 관여한다. 각 이벤트에 대해선 이벤트 발생 로직을 설명할 때 자세히 알아보도록하자.</p>

<p><code class="language-plaintext highlighter-rouge">createBootstrapContext()</code>를 통해 <code class="language-plaintext highlighter-rouge">BootStrapContext</code>을 생성하는데 이때 SpringApplication 생성자에서 초기화하였던 <code class="language-plaintext highlighter-rouge">bootstrapRegistryInitializers</code>들을 실행한다. <a href="https://rokwonk.github.io/spring/spring-boot-deep-dive-1/">SpringApplication 초기화 과정</a>에서 보았듯 현재 버전(3.2.1)에서는 <code class="language-plaintext highlighter-rouge">bootstrapRegistryInitializers</code>들은 1개도 존재하지 않는다. 레거시로 남아있는 듯하다.</p>

<p><br /></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* SpringApplication.java */</span>
<span class="kd">private</span> <span class="nc">DefaultBootstrapContext</span> <span class="nf">createBootstrapContext</span><span class="o">()</span> <span class="o">{</span>  
	<span class="nc">DefaultBootstrapContext</span> <span class="n">bootstrapContext</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DefaultBootstrapContext</span><span class="o">();</span>  
	<span class="k">this</span><span class="o">.</span><span class="na">bootstrapRegistryInitializers</span><span class="o">.</span><span class="na">forEach</span><span class="o">((</span><span class="n">initializer</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">initializer</span><span class="o">.</span><span class="na">initialize</span><span class="o">(</span><span class="n">bootstrapContext</span><span class="o">));</span>  
	<span class="k">return</span> <span class="n">bootstrapContext</span><span class="o">;</span>  
<span class="o">}</span>
</code></pre></div></div>

<p>이후에 생성할 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>를 담을 변수도 미리 선언해 준다.</p>

<p><br /></p>

<h3 id="그래픽-설정-끄기-awt-설정">그래픽 설정 끄기 AWT 설정</h3>
<p>네 번째로 AWT Headless 관련 프로퍼티를 설정한다. <code class="language-plaintext highlighter-rouge">java.awt.headless</code> 시스템 프로퍼티를 설정하는 코드만 들어가는데 설정된 기본값이 true이다. true면 AWT 기능을 비활성화된다.</p>

<blockquote>
  <p><strong>Java AWT(Advanced Window Toolkit)</strong></p>

  <p>AWT는 Java에 포함된 그래픽 사용자 인터페이서(GUI) 개발을 위한 라이브러리이다. 서버와 같이 GUI가 필요없는 환경에서는 이 기능을 비활성화하는 것이 자원을 절약 할 수 있다.</p>
</blockquote>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* SpringApplication.java */</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">configureHeadlessProperty</span><span class="o">()</span> <span class="o">{</span>  
	<span class="c1">// SYSTEM_PROPERTY_JAVA_AWT_HEADLESS == java.awt.headless</span>
	<span class="c1">// this.headless 초기값은 true로 설정되어 있음</span>
	<span class="nc">System</span><span class="o">.</span><span class="na">setProperty</span><span class="o">(</span><span class="no">SYSTEM_PROPERTY_JAVA_AWT_HEADLESS</span><span class="o">,</span>  
		 <span class="nc">System</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="no">SYSTEM_PROPERTY_JAVA_AWT_HEADLESS</span><span class="o">,</span> <span class="nc">Boolean</span><span class="o">.</span><span class="na">toString</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">headless</span><span class="o">)));</span>
<span class="o">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="이벤트-브로커-설정">이벤트 브로커 설정</h3>
<p>다섯 번째로 <code class="language-plaintext highlighter-rouge">SpringApplicationRunListeners</code>를 셋팅한다. Listeners는 각 단계마다 이벤트 메서드를 실행하는데 내부에 셋팅된 <code class="language-plaintext highlighter-rouge">SpringApplicationRunListener</code> 타입의 listener들을 실행한다. 현재 버전(3.2.1)을 기준으로는 <code class="language-plaintext highlighter-rouge">EventPublishingRunListener</code> 하나만 존재한다.</p>

<p><code class="language-plaintext highlighter-rouge">SpringApplicationRunListeners</code>를 셋팅하는 로직을 보면 <code class="language-plaintext highlighter-rouge">getSpringFactoriesInstances</code>를 통해 정의된 <code class="language-plaintext highlighter-rouge">SpringApplicationRunListener</code> 들을 가져온다. 해당 메서드는 <a href="https://rokwonk.github.io/spring/spring-boot-deep-dive-1/">SpringApplication 초기화 과정</a>에서 설명했듯이 <code class="language-plaintext highlighter-rouge">META-INF/spring.factories</code>에서 정의된 타입의 구현체 클래스를 가져와 인스턴스화 해준다.</p>

<p>마지막에는 모든 listener들을 하나의 클래스(<code class="language-plaintext highlighter-rouge">SpringApplicationRunListeners</code>)로 묶는다. <strong>이벤트 실행과 관련된 책임을 하나의 클래스로 캡슐화, 위임</strong>하는 모습을 볼 수 있다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* SpringApplication.java */</span>
<span class="kd">private</span> <span class="nc">SpringApplicationRunListeners</span> <span class="nf">getRunListeners</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>  
	<span class="nc">ArgumentResolver</span> <span class="n">argumentResolver</span> <span class="o">=</span> <span class="nc">ArgumentResolver</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="nc">SpringApplication</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="k">this</span><span class="o">);</span>  
	<span class="n">argumentResolver</span> <span class="o">=</span> <span class="n">argumentResolver</span><span class="o">.</span><span class="na">and</span><span class="o">(</span><span class="nc">String</span><span class="o">[].</span><span class="na">class</span><span class="o">,</span> <span class="n">args</span><span class="o">);</span>  
	
	<span class="c1">// META_INF/spring.factories 에 정의된 클래스 인스턴스화</span>
	<span class="c1">// 현재는 EventPublishingRunListener 하나만 존재</span>
	<span class="nc">List</span><span class="o">&lt;</span><span class="nc">SpringApplicationRunListener</span><span class="o">&gt;</span> <span class="n">listeners</span> <span class="o">=</span> <span class="n">getSpringFactoriesInstances</span><span class="o">(</span><span class="nc">SpringApplicationRunListener</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>  
		 <span class="n">argumentResolver</span><span class="o">);</span>  
	<span class="nc">SpringApplicationHook</span> <span class="n">hook</span> <span class="o">=</span> <span class="n">applicationHook</span><span class="o">.</span><span class="na">get</span><span class="o">();</span>  
	<span class="nc">SpringApplicationRunListener</span> <span class="n">hookListener</span> <span class="o">=</span> <span class="o">(</span><span class="n">hook</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">?</span> <span class="n">hook</span><span class="o">.</span><span class="na">getRunListener</span><span class="o">(</span><span class="k">this</span><span class="o">)</span> <span class="o">:</span> <span class="kc">null</span><span class="o">;</span>  
	<span class="k">if</span> <span class="o">(</span><span class="n">hookListener</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>  
	  <span class="n">listeners</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;(</span><span class="n">listeners</span><span class="o">);</span>  
	  <span class="n">listeners</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">hookListener</span><span class="o">);</span>  
	<span class="o">}</span>  
	<span class="k">return</span> <span class="k">new</span> <span class="nf">SpringApplicationRunListeners</span><span class="o">(</span><span class="n">logger</span><span class="o">,</span> <span class="n">listeners</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">applicationStartup</span><span class="o">);</span>
</code></pre></div></div>

<p>이번엔 한 층 더 들어가 <code class="language-plaintext highlighter-rouge">EventPublishingRunListener</code>의 내부를 살펴보면 <code class="language-plaintext highlighter-rouge">initialMulticaster</code>를 Has-A 관계로 가지고 있다. 이벤트 관련 메서드가 실행되면 이 멀티캐스터를 통해 이벤트와 관련된 <code class="language-plaintext highlighter-rouge">ApplicationListener</code>을 실행하는 형태이다.</p>

<p>일종의 <strong>Pub-Sub 패턴으로 각 단계별로 후처리 되어야 할 작업을 처리</strong>한다. 덕분에 느슨한 결합(Sub 쪽에 무엇인가 코드작업이 일어나도 Pub 쪽의 코드에는 전혀 영향을 끼치지 않는다.)을 가지고 단계별 후처리 작업들을 진행할 수 있다.</p>

<p>조금 흥미로운 점은 SpringApplication 생성 시 초기화했던 <code class="language-plaintext highlighter-rouge">ApplicationListener</code> 를 어디서 사용하나 했더니 <code class="language-plaintext highlighter-rouge">EventPublishingRunListener</code>가 사용하고 있었다. <code class="language-plaintext highlighter-rouge">initialMulticaster</code>가 각 이벤트에 맞는 <code class="language-plaintext highlighter-rouge">ApplicationListener</code>를 선별하여 실행시킨다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">EventPublishingRunListener</span> <span class="kd">implements</span> <span class="nc">SpringApplicationRunListener</span><span class="o">,</span> <span class="nc">Ordered</span> <span class="o">{</span>  
	<span class="kd">private</span> <span class="kd">final</span> <span class="nc">SpringApplication</span> <span class="n">application</span><span class="o">;</span>  
	<span class="kd">private</span> <span class="kd">final</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">;</span>  
	<span class="kd">private</span> <span class="kd">final</span> <span class="nc">SimpleApplicationEventMulticaster</span> <span class="n">initialMulticaster</span><span class="o">;</span> 
	
	<span class="nc">EventPublishingRunListener</span><span class="o">(</span><span class="nc">SpringApplication</span> <span class="n">application</span><span class="o">,</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>  
	  <span class="k">this</span><span class="o">.</span><span class="na">application</span> <span class="o">=</span> <span class="n">application</span><span class="o">;</span>  
	  <span class="k">this</span><span class="o">.</span><span class="na">args</span> <span class="o">=</span> <span class="n">args</span><span class="o">;</span>  
	  <span class="k">this</span><span class="o">.</span><span class="na">initialMulticaster</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SimpleApplicationEventMulticaster</span><span class="o">();</span>  
	<span class="o">}</span>
	
	<span class="nd">@Override</span>  
	<span class="kd">public</span> <span class="kt">void</span> <span class="nf">starting</span><span class="o">(</span><span class="nc">ConfigurableBootstrapContext</span> <span class="n">bootstrapContext</span><span class="o">)</span> <span class="o">{</span>  
	   <span class="n">multicastInitialEvent</span><span class="o">(</span><span class="k">new</span> <span class="nc">ApplicationStartingEvent</span><span class="o">(</span><span class="n">bootstrapContext</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">application</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">args</span><span class="o">));</span>  
	<span class="o">}</span>  

	<span class="c1">// 이벤트 메서드들 ...</span>
	
	<span class="c1">// mulitcaster를 이용하여 등록된 ApplicationListener를 실행시킨다.</span>
	<span class="kd">private</span> <span class="kt">void</span> <span class="nf">multicastInitialEvent</span><span class="o">(</span><span class="nc">ApplicationEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>  
	   <span class="n">refreshApplicationListeners</span><span class="o">();</span>  
	   <span class="k">this</span><span class="o">.</span><span class="na">initialMulticaster</span><span class="o">.</span><span class="na">multicastEvent</span><span class="o">(</span><span class="n">event</span><span class="o">);</span>  
	<span class="o">}</span>  
	  
	<span class="kd">private</span> <span class="kt">void</span> <span class="nf">refreshApplicationListeners</span><span class="o">()</span> <span class="o">{</span>
		<span class="k">this</span><span class="o">.</span><span class="na">application</span><span class="o">.</span><span class="na">getListeners</span><span class="o">().</span><span class="na">forEach</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">initialMulticaster</span><span class="o">::</span><span class="n">addApplicationListener</span><span class="o">);</span>  
	<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">SpringApplicationRunListeners</code>의 구조를 정리해서 보자면 다음과 같은 구조를 띈다. 중첩 Pub-Sub 구조라는게 흥미롭다.</p>

<p><img src="https://github.com/RokwonK/RokwonK.github.io/assets/52196792/e119dff7-8b1f-4cc2-89df-ec5dee20f058" alt="SpringApplicationRunListeners구조" /></p>

<p><br /></p>

<h3 id="starting-이벤트에서-벌어지는-일">starting 이벤트에서 벌어지는 일</h3>
<p>다시 돌아와서, 여섯번째로 방금 셋팅한 <code class="language-plaintext highlighter-rouge">SpringApplicationRunListeners</code>에 starting 이벤트를 발생시킨다. 이 이벤트로 최종적으로 실행되는 <code class="language-plaintext highlighter-rouge">ApplicationListener</code> 중 <code class="language-plaintext highlighter-rouge">BackgroundPreinitializer</code>은 <strong>시간이 오래걸리는 작업들을 백그라운드 스레드를 이용해 미리 초기화</strong>시킨다. 대표적으로 Validation, JDK, TomcatInitializer 등이 존재하다. TomcatInitializer에서는 쿠키 프로세서와 인증 되지 않은 유저 처리를 위한 프로세서를 초기화한다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">BackgroundPreinitializer</span> <span class="kd">implements</span> <span class="nc">ApplicationListener</span><span class="o">&lt;</span><span class="nc">SpringApplicationEvent</span><span class="o">&gt;,</span> <span class="nc">Ordered</span> <span class="o">{</span>

	<span class="c1">// 이 메서드 실행됨</span>
	<span class="nd">@Override</span>  
	<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onApplicationEvent</span><span class="o">(</span><span class="nc">SpringApplicationEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>  
		<span class="k">if</span> <span class="o">(</span><span class="n">event</span> <span class="k">instanceof</span> <span class="nc">ApplicationEnvironmentPreparedEvent</span>  
			 <span class="o">&amp;&amp;</span> <span class="n">preinitializationStarted</span><span class="o">.</span><span class="na">compareAndSet</span><span class="o">(</span><span class="kc">false</span><span class="o">,</span> <span class="kc">true</span><span class="o">))</span> <span class="o">{</span>  
			<span class="n">performPreinitialization</span><span class="o">();</span>  
		<span class="o">}</span>
	   <span class="c1">// ...</span>
	<span class="o">}</span>  
	  
	<span class="kd">private</span> <span class="kt">void</span> <span class="nf">performPreinitialization</span><span class="o">()</span> <span class="o">{</span>  
		<span class="k">try</span> <span class="o">{</span>
			<span class="nc">Thread</span> <span class="n">thread</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Thread</span><span class="o">(</span><span class="k">new</span> <span class="nc">Runnable</span><span class="o">()</span> <span class="o">{</span>  
				<span class="nd">@Override</span>  
				<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>  
					<span class="n">runSafely</span><span class="o">(</span><span class="k">new</span> <span class="nc">ConversionServiceInitializer</span><span class="o">());</span>  
					<span class="n">runSafely</span><span class="o">(</span><span class="k">new</span> <span class="nc">ValidationInitializer</span><span class="o">());</span>  
					<span class="k">if</span> <span class="o">(!</span><span class="n">runSafely</span><span class="o">(</span><span class="k">new</span> <span class="nc">MessageConverterInitializer</span><span class="o">()))</span> <span class="o">{</span>  
					   <span class="n">runSafely</span><span class="o">(</span><span class="k">new</span> <span class="nc">JacksonInitializer</span><span class="o">());</span>  
					<span class="o">}</span>  
					<span class="n">runSafely</span><span class="o">(</span><span class="k">new</span> <span class="nc">CharsetInitializer</span><span class="o">());</span>  
					<span class="n">runSafely</span><span class="o">(</span><span class="k">new</span> <span class="nc">TomcatInitializer</span><span class="o">());</span>  
					<span class="n">runSafely</span><span class="o">(</span><span class="k">new</span> <span class="nc">JdkInitializer</span><span class="o">());</span>  
					<span class="n">preinitializationComplete</span><span class="o">.</span><span class="na">countDown</span><span class="o">();</span>  
				<span class="o">}</span>  
					
				<span class="kt">boolean</span> <span class="nf">runSafely</span><span class="o">(</span><span class="nc">Runnable</span> <span class="n">runnable</span><span class="o">)</span> <span class="o">{</span>  
					<span class="k">try</span> <span class="o">{</span>  
					   <span class="n">runnable</span><span class="o">.</span><span class="na">run</span><span class="o">();</span>  
					   <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>  
					<span class="o">}</span>  
					<span class="k">catch</span> <span class="o">(</span><span class="nc">Throwable</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>  
					   <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>  
					<span class="o">}</span>  
				<span class="o">}</span>  
			<span class="o">},</span> <span class="s">"background-preinit"</span><span class="o">);</span>  
			<span class="n">thread</span><span class="o">.</span><span class="na">start</span><span class="o">();</span>  
		<span class="o">}</span>  
		<span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>  
		<span class="o">}</span>  
	<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="정리">정리</h3>
<p>이제 사전 준비가 완료되었다. 살펴볼게 많아 길어졌는데 정리해보면 다음과 같다.</p>
<ol>
  <li>타이머 설정</li>
  <li>ShutDownHook 설정
    <ul>
      <li>예기치 못한 오류로 어플리케이션 종료 시 자원 반납을 실행할 쓰레드 설정</li>
    </ul>
  </li>
  <li>임시 컨텍스트 생성
    <ul>
      <li><code class="language-plaintext highlighter-rouge">ApplicationContext</code> 생성 전 어플리케이션 설정과 환경설정을 담당할 <code class="language-plaintext highlighter-rouge">BootStrapContext</code> 생성</li>
    </ul>
  </li>
  <li>그래픽 설정 끄기
    <ul>
      <li>자원 절약을 위한 JAVA의 그래픽 라이브러리 <code class="language-plaintext highlighter-rouge">AWT</code> 설정 끄기</li>
    </ul>
  </li>
  <li>후 처리를 담당할 이벤트 브로커 생성
    <ul>
      <li><code class="language-plaintext highlighter-rouge">ApplicationContext</code>의 각 단계마다 후처리를 담당할 리스너들을 등록</li>
    </ul>
  </li>
  <li>백그라운드 쓰레드를 이용해 오래걸리는 작업 미리 처리
    <ul>
      <li>starting 메서드의 뒤에 존재하는 리스너들이 해당 작업을 처리</li>
      <li>흥미로운 중첩 Pub-Sub 구조</li>
    </ul>
  </li>
</ol>

<p><br /></p>

<h2 id="환경-설정값-셋팅">환경 설정값 셋팅</h2>
<p>본격적으로 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>를 구성하는 단계이다. 실행시 넘기는 Argument와 우리가 설정한 환경설정 값들을 불러와 준비시키는 단계이다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* SpringApplication.java */</span>
<span class="kd">public</span> <span class="nc">ConfigurableApplicationContext</span> <span class="nf">run</span><span class="o">(</span><span class="nc">String</span><span class="o">...</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>  

	<span class="c1">// ...</span>
	
	<span class="k">try</span> <span class="o">{</span>
		<span class="c1">// 1. 시작 시 Argument, Environment 설정</span>
		<span class="nc">ApplicationArguments</span> <span class="n">applicationArguments</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DefaultApplicationArguments</span><span class="o">(</span><span class="n">args</span><span class="o">);</span>  
		<span class="nc">ConfigurableEnvironment</span> <span class="n">environment</span> <span class="o">=</span> <span class="n">prepareEnvironment</span><span class="o">(</span><span class="n">listeners</span><span class="o">,</span> <span class="n">bootstrapContext</span><span class="o">,</span> <span class="n">applicationArguments</span><span class="o">);</span>  
		
		<span class="c1">// 2. 시작 시 배너 출력</span>
		<span class="nc">Banner</span> <span class="n">printedBanner</span> <span class="o">=</span> <span class="n">printBanner</span><span class="o">(</span><span class="n">environment</span><span class="o">);</span>  
		
		<span class="c1">// ...</span>
	<span class="o">}</span>
	<span class="c1">// ...</span>
<span class="o">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="환경설정environment--불러오기">환경설정(Environment ) 불러오기</h3>
<p>Argument와 통합해 Environment 준비하는 만들어주는 단계이다. Environment라 함은 <code class="language-plaintext highlighter-rouge">Profile</code>과 <code class="language-plaintext highlighter-rouge">PropertySource</code>들을 의미한다. 알다시피 Properties를 설정할 수 있는 방법이 굉장히 많다.</p>

<p><code class="language-plaintext highlighter-rouge">prepareEnvironment()</code>에서 <strong>모든 Properties들이 <code class="language-plaintext highlighter-rouge">PropertySource</code> 객체로 매핑된고 environment에 저장</strong>된다. SystemProperties, SystemEnvironment, ConfigurationProperties, ServletConfigInitParams, ServletContextInitPrams, 설정파일(application.yml 등)과 같은 것들이 <code class="language-plaintext highlighter-rouge">PropertySoucre</code>과 <code class="language-plaintext highlighter-rouge">Profile</code> 객체로 매핑된다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* SpringApplicaiton.java */</span>

<span class="kd">private</span> <span class="nc">ConfigurableEnvironment</span> <span class="nf">prepareEnvironment</span><span class="o">(</span><span class="nc">SpringApplicationRunListeners</span> <span class="n">listeners</span><span class="o">,</span> <span class="nc">DefaultBootstrapContext</span> <span class="n">bootstrapContext</span><span class="o">,</span> <span class="nc">ApplicationArguments</span> <span class="n">applicationArguments</span><span class="o">)</span> <span class="o">{</span>  

	<span class="c1">// 1. Create configure the environment</span>
	<span class="nc">ConfigurableEnvironment</span> <span class="n">environment</span> <span class="o">=</span> <span class="n">getOrCreateEnvironment</span><span class="o">();</span>  
	<span class="n">configureEnvironment</span><span class="o">(</span><span class="n">environment</span><span class="o">,</span> <span class="n">applicationArguments</span><span class="o">.</span><span class="na">getSourceArgs</span><span class="o">());</span>  
	<span class="nc">ConfigurationPropertySources</span><span class="o">.</span><span class="na">attach</span><span class="o">(</span><span class="n">environment</span><span class="o">);</span>
	
	<span class="c1">// 2. Listener에 의해 설정파일(application.yml)을 읽고 셋팅함</span>
	<span class="n">listeners</span><span class="o">.</span><span class="na">environmentPrepared</span><span class="o">(</span><span class="n">bootstrapContext</span><span class="o">,</span> <span class="n">environment</span><span class="o">);</span>
	<span class="nc">DefaultPropertiesPropertySource</span><span class="o">.</span><span class="na">moveToEnd</span><span class="o">(</span><span class="n">environment</span><span class="o">);</span>  
	<span class="nc">Assert</span><span class="o">.</span><span class="na">state</span><span class="o">(!</span><span class="n">environment</span><span class="o">.</span><span class="na">containsProperty</span><span class="o">(</span><span class="s">"spring.main.environment-prefix"</span><span class="o">),</span>  
		 <span class="s">"Environment prefix cannot be set via properties."</span><span class="o">);</span>  
		
	<span class="c1">// 3. SpringApplication에 바인딩</span>
	<span class="n">bindToSpringApplication</span><span class="o">(</span><span class="n">environment</span><span class="o">);</span>  
	<span class="k">if</span> <span class="o">(!</span><span class="k">this</span><span class="o">.</span><span class="na">isCustomEnvironment</span><span class="o">)</span> <span class="o">{</span>  
	  <span class="nc">EnvironmentConverter</span> <span class="n">environmentConverter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">EnvironmentConverter</span><span class="o">(</span><span class="n">getClassLoader</span><span class="o">());</span>  
	  <span class="n">environment</span> <span class="o">=</span> <span class="n">environmentConverter</span><span class="o">.</span><span class="na">convertEnvironmentIfNecessary</span><span class="o">(</span><span class="n">environment</span><span class="o">,</span> <span class="n">deduceEnvironmentClass</span><span class="o">());</span>  
	<span class="o">}</span>  
	<span class="nc">ConfigurationPropertySources</span><span class="o">.</span><span class="na">attach</span><span class="o">(</span><span class="n">environment</span><span class="o">);</span>  
	<span class="k">return</span> <span class="n">environment</span><span class="o">;</span>  
<span class="o">}</span>
</code></pre></div></div>

<p>처음 <code class="language-plaintext highlighter-rouge">getOrCreateEnvironment()</code> 메서드가 실행되면 ServletConfigInitParams, ServletContextInitPrams, SystemProperties,SystemEnvironment를 읽어 <code class="language-plaintext highlighter-rouge">PropertySource</code>로 만든다.</p>

<p>실제로 이 부분이 실행된 뒤에 environment에는 4가지 <code class="language-plaintext highlighter-rouge">PropertySource</code>가 셋팅되는 것을 볼 수 있다.</p>

<p><img src="https://github.com/RokwonK/RokwonK.github.io/assets/52196792/a78bbb61-398b-4ed0-bf2f-9d96867443f2" alt="getOrCreateEnvironment" /></p>

<p>다음으로 위에서 생성한 Listener를 이용해 <code class="language-plaintext highlighter-rouge">environpreared</code> 메서드를 발생시키게 되면 <code class="language-plaintext highlighter-rouge">EventPublishingRunListener</code>가 실행하는 <code class="language-plaintext highlighter-rouge">AppliationListener</code> 중 
<code class="language-plaintext highlighter-rouge">EnvironmentPostProcessorApplicationListener</code>이 실행이 된다.</p>

<p>여기서 총 7가지의 <code class="language-plaintext highlighter-rouge">EnvironmentPostProcessor</code>(후처리기)가 동작한다. 
<img src="https://github.com/RokwonK/RokwonK.github.io/assets/52196792/8b348c34-c416-4d95-b540-bc8433fe23e3" alt="EnvironmentPrepared이벤트로 실행되는 PostProcessor" /></p>

<p>이 후처리기들은 추가적인 <code class="language-plaintext highlighter-rouge">SystemProperties</code>들을 추가하고 <strong>우리가 설정한 설정파일 <code class="language-plaintext highlighter-rouge">application.yml</code>파일들을 불러와 <code class="language-plaintext highlighter-rouge">PropertySource</code>로 셋팅</strong>해준다. 이것들은 <code class="language-plaintext highlighter-rouge">OriginTrackedMapPropertySource</code>라는 구현체로 만들어진다. <code class="language-plaintext highlighter-rouge">environmentPrepared</code> 이벤트가 실행한 이후를 보면 필자가 작성한 설정파일 2개가 등록되어 있는 것을 볼 수 있다.</p>

<p><img src="https://github.com/RokwonK/RokwonK.github.io/assets/52196792/2dbc05ce-7f0e-4ec3-bf06-3b3de56bf64a" alt="EnvironmentPrepared이벤트 실행 이후" /></p>

<p>마지막으로 셋팅된 <code class="language-plaintext highlighter-rouge">Environment</code>를 바인딩시키며 마무리한다.</p>

<p><br /></p>

<h3 id="배너-출력">배너 출력</h3>
<p>스프링 부트 실행 시 커맨드 라인에 출력되는 화면을 출력하는 과정이다. 필요에 따라 출력 내용을 바꿀 수도 있다고 한다. 주요 내용은 아니므로 패스하겠다.</p>

<p><br /></p>

<h2 id="applicationcontext-생성-및-실행">ApplicationContext 생성 및 실행</h2>
<p>본격적으로 실행환경을 셋팅하는 단계이다. <code class="language-plaintext highlighter-rouge">WebServer</code>부터 Bean 구성까지 스프링 어플리케이션이 실행되기 위한 모든 과정이 여기서 일어난다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* SpringApplication.java */</span>
<span class="kd">public</span> <span class="nc">ConfigurableApplicationContext</span> <span class="nf">run</span><span class="o">(</span><span class="nc">String</span><span class="o">...</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>  
	<span class="c1">// ...</span>
	<span class="k">try</span> <span class="o">{</span> 
		<span class="c1">// ... </span>
		
		<span class="c1">// 1. ApplicationContext 생성</span>
		<span class="n">context</span> <span class="o">=</span> <span class="n">createApplicationContext</span><span class="o">();</span>  
		
		<span class="c1">// 2. Startup 셋팅</span>
		<span class="n">context</span><span class="o">.</span><span class="na">setApplicationStartup</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">applicationStartup</span><span class="o">);</span>  
		
		<span class="c1">// 3. ApplicationContext 준비단계</span>
		<span class="n">prepareContext</span><span class="o">(</span><span class="n">bootstrapContext</span><span class="o">,</span> <span class="n">context</span><span class="o">,</span> <span class="n">environment</span><span class="o">,</span> <span class="n">listeners</span><span class="o">,</span> <span class="n">applicationArguments</span><span class="o">,</span> <span class="n">printedBanner</span><span class="o">);</span>  
		
		<span class="c1">// 4. ApplicationContext refresh단계</span>
		<span class="n">refreshContext</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>  
		
		<span class="c1">// 5. ApplicationContext refresh 이후</span>
		<span class="n">afterRefresh</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">applicationArguments</span><span class="o">);</span>  
		
		<span class="c1">// ...</span>
	<span class="o">}</span>
	<span class="c1">// ...</span>
<span class="o">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="applicationcontext-생성">ApplicationContext 생성</h3>
<p>가장 먼저, <code class="language-plaintext highlighter-rouge">WebApplicationType</code>따라 맞는 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>를 생성한다. <a href="https://rokwonk.github.io/spring/spring-boot-deep-dive-2/">ApplicationContext 상속관계</a>에서 알아보았던 <strong>3가지 최종 구현체 중 하나를 생성</strong>한다. 지금은 Servlet을 이용한 웹 어플리케이션이므로 <code class="language-plaintext highlighter-rouge">AnnotationConfigServletWebServerApplicationContext</code>를 생성한다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* SpringApplication.java */</span>

<span class="c1">// DefaultApplicationContextFactory </span>
<span class="kd">private</span> <span class="nc">ApplicationContextFactory</span> <span class="n">applicationContextFactory</span> <span class="o">=</span> <span class="nc">ApplicationContextFactory</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">;</span>

<span class="kd">protected</span> <span class="nc">ConfigurableApplicationContext</span> <span class="nf">createApplicationContext</span><span class="o">()</span> <span class="o">{</span>  
   <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">applicationContextFactory</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">webApplicationType</span><span class="o">);</span>  
<span class="o">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="startup-셋팅">Startup 셋팅</h3>
<p>생성한 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>에 <code class="language-plaintext highlighter-rouge">ApplicationStartup</code>을 셋팅한다. <code class="language-plaintext highlighter-rouge">ApplicationStartup</code>은 각 단계(Step)으로 나누어 지표를 기록해주는 역할을 한다. 이후에 <code class="language-plaintext highlighter-rouge">ApplicationContext</code> refresh 단계에서 startup을 이용해 단계마다 태깅을 하는 것을 볼 수 있다.</p>

<p><br /></p>

<h3 id="applicationcontext-준비">ApplicationContext 준비</h3>
<p><code class="language-plaintext highlighter-rouge">ApplicationContext</code>을 본격적으로 실행하는 단계인 refresh 단계를 시작하기 전에 필요한 설정을 셋팅하는 단계이다.</p>
<ol>
  <li><strong>setEnvironment</strong>
    <ul>
      <li>생성한 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>에 <code class="language-plaintext highlighter-rouge">Environment</code> 를 셋팅한다.</li>
    </ul>
  </li>
  <li><strong>postProcessApplicationContext</strong>
    <ul>
      <li><code class="language-plaintext highlighter-rouge">ClassLoader</code>, <code class="language-plaintext highlighter-rouge">ResourceLoader</code> 등 필요한 자원을 셋팅</li>
    </ul>
  </li>
  <li><strong>addAotGeneratedInitializerIfNecessary</strong>
    <ul>
      <li>Spring boot 3.0 부터는 AOT를 사용하여 빌드시간을 단축할 수 있다. 만약 AOT를 사용한다면 그에 따른 Initializer를 셋팅한다.</li>
    </ul>
  </li>
  <li><strong>applyInitializers</strong>
    <ul>
      <li><code class="language-plaintext highlighter-rouge">SpringApplication</code> 생성 시 초기화했던 Initializer들을 적용한다.</li>
    </ul>
  </li>
  <li><strong>contextPrepared</strong>
    <ul>
      <li><code class="language-plaintext highlighter-rouge">ApplicationContext</code> 준비완료에 따른 후처리를 진행한다.</li>
    </ul>
  </li>
  <li><strong>bootstrapContext.close(context)</strong>
    <ul>
      <li><code class="language-plaintext highlighter-rouge">ApplicationContext</code>이 생성되어 필요없어진 <code class="language-plaintext highlighter-rouge">BootStrapContext</code>를 종료한다.</li>
    </ul>
  </li>
  <li><strong>싱글톤 빈 생성</strong>
    <ul>
      <li><code class="language-plaintext highlighter-rouge">SpringApplication</code>에서 등록할 수 있는 빈들을 등록, 생성한다.</li>
    </ul>
  </li>
  <li><strong>설정정보 셋팅</strong>
    <ul>
      <li>순환참조 허용, 빈 이름 중복 허용에 관한 셋팅을 한다.(기본값은 둘 다 불가능이다)</li>
    </ul>
  </li>
  <li><strong>BeanFactoryPostProcessor 등록</strong>
    <ul>
      <li>BeanFactory 설정이 완료된 후에 사용될 <code class="language-plaintext highlighter-rouge">BeanFactoryPostProcessor</code>를 등록한다.</li>
      <li>지연생성, <code class="language-plaintext highlighter-rouge">PropertySource</code> 의 우선순위를 정하는 후처리기를 등록하는 것 같다.</li>
    </ul>
  </li>
</ol>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* SpringApplication.java */</span>

<span class="kd">private</span> <span class="kt">void</span> <span class="nf">prepareContext</span><span class="o">(</span><span class="nc">DefaultBootstrapContext</span> <span class="n">bootstrapContext</span><span class="o">,</span> <span class="nc">ConfigurableApplicationContext</span> <span class="n">context</span><span class="o">,</span> <span class="nc">ConfigurableEnvironment</span> <span class="n">environment</span><span class="o">,</span> <span class="nc">SpringApplicationRunListeners</span> <span class="n">listeners</span><span class="o">,</span> <span class="nc">ApplicationArguments</span> <span class="n">applicationArguments</span><span class="o">,</span> <span class="nc">Banner</span> <span class="n">printedBanner</span><span class="o">)</span> <span class="o">{</span>
	<span class="c1">// 1. 환경설정 셋팅</span>
	<span class="n">context</span><span class="o">.</span><span class="na">setEnvironment</span><span class="o">(</span><span class="n">environment</span><span class="o">);</span>  
	
	<span class="c1">// 2. 주입하기 </span>
	<span class="n">postProcessApplicationContext</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>  

	<span class="c1">// 3. AOT 셋팅</span>
	<span class="n">addAotGeneratedInitializerIfNecessary</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">initializers</span><span class="o">);</span>  
	
	<span class="c1">// 4. Initializer 적용</span>
	<span class="n">applyInitializers</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>  
	
	<span class="c1">// 5. 준비완료 후처리</span>
	<span class="n">listeners</span><span class="o">.</span><span class="na">contextPrepared</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>  

	<span class="c1">// 6. 임시 Context 종료</span>
	<span class="n">bootstrapContext</span><span class="o">.</span><span class="na">close</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>  
	<span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">logStartupInfo</span><span class="o">)</span> <span class="o">{</span>  
		<span class="n">logStartupInfo</span><span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">getParent</span><span class="o">()</span> <span class="o">==</span> <span class="kc">null</span><span class="o">);</span>  
		<span class="n">logStartupProfileInfo</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>  
	<span class="o">}</span>  
	
	<span class="c1">// 7. 필요한 싱글톤 </span>
	<span class="nc">ConfigurableListableBeanFactory</span> <span class="n">beanFactory</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="na">getBeanFactory</span><span class="o">();</span>  
	<span class="n">beanFactory</span><span class="o">.</span><span class="na">registerSingleton</span><span class="o">(</span><span class="s">"springApplicationArguments"</span><span class="o">,</span> <span class="n">applicationArguments</span><span class="o">);</span>  
	<span class="k">if</span> <span class="o">(</span><span class="n">printedBanner</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>  
		<span class="n">beanFactory</span><span class="o">.</span><span class="na">registerSingleton</span><span class="o">(</span><span class="s">"springBootBanner"</span><span class="o">,</span> <span class="n">printedBanner</span><span class="o">);</span>  
	<span class="o">}</span>  
	
	<span class="c1">// 8. 설정정보 셋팅</span>
	<span class="k">if</span> <span class="o">(</span><span class="n">beanFactory</span> <span class="k">instanceof</span> <span class="nc">AbstractAutowireCapableBeanFactory</span> <span class="n">autowireCapableBeanFactory</span><span class="o">)</span> <span class="o">{</span>
			<span class="n">autowireCapableBeanFactory</span><span class="o">.</span><span class="na">setAllowCircularReferences</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">allowCircularReferences</span><span class="o">);</span>  
		<span class="k">if</span> <span class="o">(</span><span class="n">beanFactory</span> <span class="k">instanceof</span> <span class="nc">DefaultListableBeanFactory</span> <span class="n">listableBeanFactory</span><span class="o">)</span> <span class="o">{</span>
					<span class="n">listableBeanFactory</span><span class="o">.</span><span class="na">setAllowBeanDefinitionOverriding</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">allowBeanDefinitionOverriding</span><span class="o">);</span>  
		<span class="o">}</span>  
	<span class="o">}</span>  
	
	<span class="c1">// 9. BeanFactoryPostProcessor 등록</span>
	<span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">lazyInitialization</span><span class="o">)</span> <span class="o">{</span>  
		<span class="n">context</span><span class="o">.</span><span class="na">addBeanFactoryPostProcessor</span><span class="o">(</span><span class="k">new</span> <span class="nc">LazyInitializationBeanFactoryPostProcessor</span><span class="o">());</span>  
	<span class="o">}</span>  
	<span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">keepAlive</span><span class="o">)</span> <span class="o">{</span>  
		<span class="n">context</span><span class="o">.</span><span class="na">addApplicationListener</span><span class="o">(</span><span class="k">new</span> <span class="nc">KeepAlive</span><span class="o">());</span>  
	<span class="o">}</span>  
	<span class="n">context</span><span class="o">.</span><span class="na">addBeanFactoryPostProcessor</span><span class="o">(</span><span class="k">new</span> <span class="nc">PropertySourceOrderingBeanFactoryPostProcessor</span><span class="o">(</span><span class="n">context</span><span class="o">));</span>  
	
	<span class="c1">// ...</span>
<span class="o">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="빈-구성단계---refresh">빈 구성단계 - Refresh</h3>
<p>본격적으로 <strong>개발자들이 등록한 빈들과 자동구성될 빈들이 등록되고 인스턴스화 되는 단계</strong>이다. <code class="language-plaintext highlighter-rouge">ApplicationContext</code>의 refresh() 메서드를 통해 이 과정들이 차례대로 실행된다. 컴포넌트 스캔과 자동구성, 프록시 빈들이 구성되는 중요한 과정으로 그 과정이 상당히 길어 따로 포스팅하겠다.(이 과정이 메인단계라고 볼 수 있으며 여담이지만 이 과정이 궁금해 Deep Dive 시리즈를 작성하게 되었다.)</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* SpringApplication.java */</span>

<span class="kd">private</span> <span class="kt">void</span> <span class="nf">refreshContext</span><span class="o">(</span><span class="nc">ConfigurableApplicationContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>  
	<span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">registerShutdownHook</span><span class="o">)</span> <span class="o">{</span>  
		<span class="n">shutdownHook</span><span class="o">.</span><span class="na">registerApplicationContext</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>  
	<span class="o">}</span>  
	<span class="n">refresh</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>  
<span class="o">}</span>

<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">refresh</span><span class="o">(</span><span class="nc">ConfigurableApplicationContext</span> <span class="n">applicationContext</span><span class="o">)</span> <span class="o">{</span>  
	<span class="c1">// </span>
	<span class="n">applicationContext</span><span class="o">.</span><span class="na">refresh</span><span class="o">();</span>  
<span class="o">}</span>
</code></pre></div></div>

<p>마지막으로 <code class="language-plaintext highlighter-rouge">afterRefreshContext</code>를 통해 실행환경 구성을 마무리하는데 이 메서드는 현재는 비어 있다.</p>

<p><br /></p>

<h2 id="실행-완료">실행 완료</h2>
<p><code class="language-plaintext highlighter-rouge">ApplicationContext</code>가 실행되고 난 후의 프로세스들이다. 대표적으로 개발자가 등록한 Runner(<code class="language-plaintext highlighter-rouge">CommandLineRunner</code>, <code class="language-plaintext highlighter-rouge">ApplicationRunner</code>)를 실행해 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>가 완료되었음을 알린다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* SpringApplication.java */</span>
<span class="kd">public</span> <span class="nc">ConfigurableApplicationContext</span> <span class="nf">run</span><span class="o">(</span><span class="nc">String</span><span class="o">...</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>  
	<span class="c1">// ...</span>
	<span class="k">try</span> <span class="o">{</span> 
		<span class="c1">// ... </span>

		<span class="n">startup</span><span class="o">.</span><span class="na">started</span><span class="o">();</span>  
		<span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">logStartupInfo</span><span class="o">)</span> <span class="o">{</span>  
		 <span class="k">new</span> <span class="nf">StartupInfoLogger</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">mainApplicationClass</span><span class="o">).</span><span class="na">logStarted</span><span class="o">(</span><span class="n">getApplicationLog</span><span class="o">(),</span> <span class="n">startup</span><span class="o">);</span>  
		<span class="o">}</span>
		<span class="n">listeners</span><span class="o">.</span><span class="na">started</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">startup</span><span class="o">.</span><span class="na">timeTakenToStarted</span><span class="o">());</span>  
		
		<span class="c1">// ApplicationRunner, CommandLineRunner 실행</span>
		<span class="n">callRunners</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">applicationArguments</span><span class="o">);</span>  
	<span class="o">}</span>
	<span class="c1">// ...</span>
<span class="o">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="마무리">마무리</h3>
<p>디테일한 Bean 구성단계(refresh)를 제외하고 전체적인 SpringApplication의 실행을 살펴보았다. 코드를 잘 알지 못하는 상태에서 하나하나 뜯어봐야 하다보니 디버깅 능력이 날로 성장하는 것 같다. 물론 스프링 코드 자체가 깔끔하게 잘 짜여진 덕분이 크긴하다. 네이밍 뿐만 아니라 객체지향 패턴을 적절히 잘 사용하다보니 배울점도 많은 것 같다.</p>

<p>지금까지 살펴본 실행흐름을 중요한 기능을 중심으로 도식화한 그림으로 마무리한다.</p>

<p><img src="https://github.com/RokwonK/RokwonK.github.io/assets/52196792/bcbb76a3-09c7-4432-b246-117b62a87f15" alt="SpringApplication_run_실행" /></p>]]></content><author><name>RokWon K</name></author><category term="Spring" /><summary type="html"><![CDATA[SpringApplication 의 run 메서드는 어떤 과정을 거쳐 어플리케이션을 실행하는지 알아보자]]></summary></entry><entry><title type="html">Spring Boot Deep Dive(2) - ApplicationContext 상속관계</title><link href="https://rokwonk.github.io/spring/spring-boot-deep-dive-2/" rel="alternate" type="text/html" title="Spring Boot Deep Dive(2) - ApplicationContext 상속관계" /><published>2023-12-29T00:00:00+09:00</published><updated>2023-12-29T00:00:00+09:00</updated><id>https://rokwonk.github.io/spring/Spring-Boot-Deep-Dive-2-ApplicationContext-%EC%83%81%EC%86%8D%EA%B4%80%EA%B3%84</id><content type="html" xml:base="https://rokwonk.github.io/spring/spring-boot-deep-dive-2/"><![CDATA[<p><br /></p>

<p>스프링은 스프링 컨테이너(혹은 DI 컨테이너) 라고 부르는 핵심 기술을 중심으로 동작한다. 이 컨테이너는 빈이라고 부르는 스프링 객체들의 생명주기를 관리하고 빈 간의 의존성을 관리해준다.</p>

<p>스프링 컨테이너의 실체는 코드에서 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>의 구현체로 존재한다. 이 컨테이너는 <a href="https://rokwonk.github.io/spring/spring-boot-deep-dive-1/">앞선 포스팅(SpringApplication 객체 생성 과정)</a>에서 <code class="language-plaintext highlighter-rouge">SpringApplication</code>의 run 메서드를 통해 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>가 생성 및 실행된다고 잠깐 이야기 했었다.</p>

<p>이번 포스팅에서는 실제 웹 어플리케이션에서 동작하는 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>이 어떤 상속관계로 이루어져있는지, 각 서브클래스는 어떤 역하을 가지고 있는지를 소스코드를 통해 들여다보자.</p>

<blockquote class="important">
  <p>Spring Boot 3.2.1 버전을 기준으로 작성되었습니다.</p>
</blockquote>

<p><br /></p>

<h2 id="최종-applicationcontext-구현체">최종 ApplicationContext 구현체</h2>
<p>우리가 Spring Boot를 이용해 어플리케이션을 만들면 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>의 최종 구현체는 다음 3개 중 한 가지이다.</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">AnnotationConfigServletWebServerApplicationContext</code>
    <ul>
      <li><strong>전통적인 서블릿기반 Web Mvc 어플리케이션</strong></li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">AnnotationConfigReactiveWebServerApplicationContext</code>
    <ul>
      <li><strong>Reactive 기반 웹 어플리케이션</strong></li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">AnnotationConfigApplicationContext</code>
    <ul>
      <li>그 외</li>
    </ul>
  </li>
</ol>

<p><a href="https://rokwonk.github.io/spring/spring-boot-deep-dive-1/">이전 포스팅</a>에서 <code class="language-plaintext highlighter-rouge">SpringApplication</code> 생성자에서 초기화했던 <code class="language-plaintext highlighter-rouge">WebApplicationType</code>을 이용하여 셋 중 하나를 인스턴스화한다. 실제로 코드에서 어떤 구현체가 선택되어 만들어지는 지는 다음 포스팅(SpringApplication run 메서드 실행)에서 자세히 알아보도록 하자.</p>

<p>Servlet 기반 웹 어플리케이션의 구현체인 <code class="language-plaintext highlighter-rouge">AnnotationConfigServletWebServerApplicationContext</code>을 기준으로 상속관계를 들여다보자.</p>

<p><br /></p>

<h2 id="applicationcontext-상속-구조">ApplicationContext 상속 구조</h2>
<p>IntelliJ가 제공해주는 Diagram의 힘을 빌려 <code class="language-plaintext highlighter-rouge">AnnotationConfigServletWebServerApplicationContext</code>의 상속구조를 확인해보면 아래의 사진과 같다.</p>

<p><img src="https://github.com/RokwonK/RokwonK.github.io/assets/52196792/a5cb57ca-1aed-4dd1-8c23-9d57c72b84ba" alt="AnnotationConfigServletWebServerApplicationContext 전체 상속구조" /></p>

<p>여러모로 복합해 보인다. 때문에 소스코드를 뒤적거리며 나름대로 핵심 class와 Interface만을 추려보았다.</p>

<p><img src="https://github.com/RokwonK/RokwonK.github.io/assets/52196792/dcefdb0d-bf01-4250-a80b-2ccad894f77c" alt="ApplicationContext 핵심 Class &amp; Interface" /></p>

<p><code class="language-plaintext highlighter-rouge">AnnotationConfigServletWebServerApplicationContext</code>부터 거슬러 올라가면</p>
<ul>
  <li><strong>ServletWebServerApplicationContext</strong></li>
  <li><strong>GenericWebApplicationContext</strong></li>
  <li><strong>GenericApplicationContext</strong></li>
  <li><strong>AbstractApplicationContext</strong> (추상 클래스)</li>
</ul>

<p>까지 상속 받고 있다. 각 클래스마다 각 한 가지의 핵심 책임을 가지고 구현되고 있는 것을 볼 수 있다.(곧 살펴보도록 하자)</p>

<p><code class="language-plaintext highlighter-rouge">AbstractApplicationContext</code>가 상속받고 있는 <code class="language-plaintext highlighter-rouge">DefatulResourceLoader</code>는 Url, FileUrl, ClassPath에서 데이터를 받아올 수 있는 데이터로더이다.</p>

<p>클래스 상속구조에서의 최상단인 <code class="language-plaintext highlighter-rouge">AbstractApplicationContext</code> 에서는 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>(실행환경)로서 지녀야할 여러가지 설정, 역할이 정의된 인터페이스인 <code class="language-plaintext highlighter-rouge">CofigurationApplicationContext</code>를 구현하고 있다. 여기서 재밌었던 점은 <strong>하나의 인터페이스가 한 종류의 역할만을 가지고 필요에 의해 여러 인터페이스를 합쳐 새롭게 필요한 인터페이스를 만들고 있다는 것이다.</strong> ISP(인터페이스 분리 원칙)을 지키고 있음을 알 수 있다.</p>

<p>상속되는 각 인터페이스마다 어떤 역할을 가지고 있는지도 곧 알아보도록 하자. 위 사진에 나와있는 핵심 역할을 지닌 인터페이스를 정리하자면 다음과 같다.</p>
<ul>
  <li><strong>ConfigurableApplicationContext</strong></li>
  <li><strong>ApplicationContext</strong></li>
  <li><strong>ListableBeanFactory</strong></li>
  <li><strong>HierarchicalBeanFactory</strong></li>
  <li><strong>BeanFactory</strong></li>
</ul>

<p><br /></p>

<h2 id="applicationcontext의-역할">ApplicationContext의 역할</h2>

<h3 id="단일-빈-검색을-담당하는-beanfactory">단일 빈 검색을 담당하는 BeanFactory</h3>
<p>상속 구조의 가장 꼭대기에는 <code class="language-plaintext highlighter-rouge">BeanFactory</code>가 있다. 보통 Spring Container에 대해 이야기하면 나오는 그 <code class="language-plaintext highlighter-rouge">BeanFactory</code>가 맞다. 왜 그런 이야기가 나오느냐 하면 <code class="language-plaintext highlighter-rouge">BeanFactory</code>의 메서드를 살펴보면 잘 알 수 있다. <strong>단일 빈을 가져오거나 빈에 대한 정보를 확인할 수 있는 역할</strong>을 가지고 있다. 말 그대로 빈 공장이 가져야할 역할을 가지고 있다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">BeanFactory</span> <span class="o">{</span>  
    <span class="nc">String</span> <span class="no">FACTORY_BEAN_PREFIX</span> <span class="o">=</span> <span class="s">"&amp;"</span><span class="o">;</span>  

    <span class="nc">Object</span> <span class="nf">getBean</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">BeansException</span><span class="o">;</span>  
    <span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="no">T</span> <span class="nf">getBean</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="nc">Class</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">requiredType</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">BeansException</span><span class="o">;</span>  
    <span class="nc">Object</span> <span class="nf">getBean</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="nc">Object</span><span class="o">...</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">BeansException</span><span class="o">;</span>  
    <span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="no">T</span> <span class="nf">getBean</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">requiredType</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">BeansException</span><span class="o">;</span>  
    <span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="no">T</span> <span class="nf">getBean</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">requiredType</span><span class="o">,</span> <span class="nc">Object</span><span class="o">...</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">BeansException</span><span class="o">;</span>  
    <span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="nc">ObjectProvider</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="nf">getBeanProvider</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">requiredType</span><span class="o">);</span>  
    <span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="nc">ObjectProvider</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="nf">getBeanProvider</span><span class="o">(</span><span class="nc">ResolvableType</span> <span class="n">requiredType</span><span class="o">);</span>  
    <span class="kt">boolean</span> <span class="nf">containsBean</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">);</span>  
    <span class="kt">boolean</span> <span class="nf">isSingleton</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">NoSuchBeanDefinitionException</span><span class="o">;</span>  
    <span class="kt">boolean</span> <span class="nf">isPrototype</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">NoSuchBeanDefinitionException</span><span class="o">;</span>  
    <span class="kt">boolean</span> <span class="nf">isTypeMatch</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="nc">ResolvableType</span> <span class="n">typeToMatch</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">NoSuchBeanDefinitionException</span><span class="o">;</span>  
    <span class="kt">boolean</span> <span class="nf">isTypeMatch</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">typeToMatch</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">NoSuchBeanDefinitionException</span><span class="o">;</span>  
    <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">getType</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">NoSuchBeanDefinitionException</span><span class="o">;</span>  
    <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">getType</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">allowFactoryBeanInit</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">NoSuchBeanDefinitionException</span><span class="o">;</span>  
    <span class="nc">String</span><span class="o">[]</span> <span class="nf">getAliases</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">);</span>  
<span class="o">}</span>
</code></pre></div></div>

<p>BeanFactory는 일반적인 빈에 대한 정보를 가져올 수 있는 역할을 가지고 있다. 하지만 해당 역할만으로는 부족할 때가 있는다. 때문에 이를 보충하는 인터페이스가 대표적으로 2가지 존재하는데 <code class="language-plaintext highlighter-rouge">ListableBeanFactory</code>와 <code class="language-plaintext highlighter-rouge">HierarchicalBeanFactory</code>이다.</p>

<p><br /></p>

<h3 id="다중-빈-검색을-담당하는-listablebeanfactory">다중 빈 검색을 담당하는 ListableBeanFactory</h3>
<p><code class="language-plaintext highlighter-rouge">ListableBeanFactory</code>의 역할은 이름에서 알 수 있듯이 <strong>빈에 대한 정보를 List로 제공</strong>하는 것이다. 왜 굳이 List로 제공할 필요가 있을까? 우리가 개발할때도 그렇듯이 하나의 인터페이스 타입으로 여러가지 구현체를 만들 수 있다. 때문에 타입으로 불러올때 N개의 결과가 있을 수 있다. <code class="language-plaintext highlighter-rouge">ListableBeanFactory</code>는 이런 기능을 추가 확장시킨 인터페이스이다.</p>

<p>그런데 이 뿐만 Annotation으로 빈을 찾거나 빈의 Annotation을 찾을 수 있는 기능도 존재한다.(이건 Annotation 관련 인터페이스로 따로 분할하는게 좋지 않을까? 하는 생각도 든다.)</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">ListableBeanFactory</span> <span class="kd">extends</span> <span class="nc">BeanFactory</span> <span class="o">{</span>  
    <span class="kt">boolean</span> <span class="nf">containsBeanDefinition</span><span class="o">(</span><span class="nc">String</span> <span class="n">beanName</span><span class="o">);</span>  
    <span class="kt">int</span> <span class="nf">getBeanDefinitionCount</span><span class="o">();</span>  
    <span class="nc">String</span><span class="o">[]</span> <span class="nf">getBeanDefinitionNames</span><span class="o">();</span>  
    <span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="nc">ObjectProvider</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="nf">getBeanProvider</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">requiredType</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">allowEagerInit</span><span class="o">);</span>  
    <span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="nc">ObjectProvider</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="nf">getBeanProvider</span><span class="o">(</span><span class="nc">ResolvableType</span> <span class="n">requiredType</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">allowEagerInit</span><span class="o">);</span>  
    <span class="nc">String</span><span class="o">[]</span> <span class="nf">getBeanNamesForType</span><span class="o">(</span><span class="nc">ResolvableType</span> <span class="n">type</span><span class="o">);</span>  
    <span class="nc">String</span><span class="o">[]</span> <span class="nf">getBeanNamesForType</span><span class="o">(</span><span class="nc">ResolvableType</span> <span class="n">type</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">includeNonSingletons</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">allowEagerInit</span><span class="o">);</span>  
    <span class="nc">String</span><span class="o">[]</span> <span class="nf">getBeanNamesForType</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">type</span><span class="o">);</span>  
    <span class="nc">String</span><span class="o">[]</span> <span class="nf">getBeanNamesForType</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">type</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">includeNonSingletons</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">allowEagerInit</span><span class="o">);</span>  
    <span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="no">T</span><span class="o">&gt;</span> <span class="nf">getBeansOfType</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="nc">Class</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">type</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">BeansException</span><span class="o">;</span>
    <span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="no">T</span><span class="o">&gt;</span> <span class="nf">getBeansOfType</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="nc">Class</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">type</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">includeNonSingletons</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">allowEagerInit</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">BeansException</span><span class="o">;</span>  
    <span class="nc">String</span><span class="o">[]</span> <span class="nf">getBeanNamesForAnnotation</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;?</span> <span class="kd">extends</span> <span class="nc">Annotation</span><span class="o">&gt;</span> <span class="n">annotationType</span><span class="o">);</span>  
    <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="nf">getBeansWithAnnotation</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;?</span> <span class="kd">extends</span> <span class="nc">Annotation</span><span class="o">&gt;</span> <span class="n">annotationType</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">BeansException</span><span class="o">;</span>  
    <span class="o">&lt;</span><span class="no">A</span> <span class="kd">extends</span> <span class="nc">Annotation</span><span class="o">&gt;</span> <span class="no">A</span> <span class="nf">findAnnotationOnBean</span><span class="o">(</span><span class="nc">String</span> <span class="n">beanName</span><span class="o">,</span> <span class="nc">Class</span><span class="o">&lt;</span><span class="no">A</span><span class="o">&gt;</span> <span class="n">annotationType</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">NoSuchBeanDefinitionException</span><span class="o">;</span>  
    <span class="o">&lt;</span><span class="no">A</span> <span class="kd">extends</span> <span class="nc">Annotation</span><span class="o">&gt;</span> <span class="no">A</span> <span class="nf">findAnnotationOnBean</span><span class="o">(</span><span class="nc">String</span> <span class="n">beanName</span><span class="o">,</span> <span class="nc">Class</span><span class="o">&lt;</span><span class="no">A</span><span class="o">&gt;</span> <span class="n">annotationType</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">allowFactoryBeanInit</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">NoSuchBeanDefinitionException</span><span class="o">;</span>  
    <span class="o">&lt;</span><span class="no">A</span> <span class="kd">extends</span> <span class="nc">Annotation</span><span class="o">&gt;</span> <span class="nc">Set</span><span class="o">&lt;</span><span class="no">A</span><span class="o">&gt;</span> <span class="nf">findAllAnnotationsOnBean</span><span class="o">(</span><span class="nc">String</span> <span class="n">beanName</span><span class="o">,</span> <span class="nc">Class</span><span class="o">&lt;</span><span class="no">A</span><span class="o">&gt;</span> <span class="n">annotationType</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">allowFactoryBeanInit</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">NoSuchBeanDefinitionException</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="빈팩토리-간-연결---hierarchicalbeanfactory">빈팩토리 간 연결 - HierarchicalBeanFactory</h3>
<p><code class="language-plaintext highlighter-rouge">BeanFactory</code>의 또다른 확장 인터페이스인 <code class="language-plaintext highlighter-rouge">HierarchicalBeanFactory</code>는 <code class="language-plaintext highlighter-rouge">BeanFactory</code>간의 계층을 관리할 수 있는 역할을 가진다. 처음에는 의아해했다. <em>‘음? <code class="language-plaintext highlighter-rouge">BeanFactory</code> 간 계층 구조가 필요하다고? 하나의 어플리케이션에는 하나의 <code class="language-plaintext highlighter-rouge">BeanFactory</code>만 구동하는 것 아닌가?’</em> 라고 생각했는데 멀티 모듈과 같은 상황을 간과했다.</p>

<p>멀티 모듈은 모듈을 조합하여 하나의 어플리케이션 만들어 <code class="language-plaintext highlighter-rouge">BeanFactory</code>가 여러 개이다. 때문에 빈 관리의 효율을 위해 빈을 통합해서 관리할 필요가 있다. 때문에 외부에서 하나의 <code class="language-plaintext highlighter-rouge">BeanFactory</code>에 요청을 보내도 내부에서 연관되어 있는<code class="language-plaintext highlighter-rouge">BeanFactory</code>들에서 빈을 찾아 리턴하는 것이 좋을 것이다. 때문에 <code class="language-plaintext highlighter-rouge">BeanFactory</code>간에 부모관계(연관관계)를 맺어 둔다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">HierarchicalBeanFactory</span> <span class="kd">extends</span> <span class="nc">BeanFactory</span> <span class="o">{</span>  
    <span class="nc">BeanFactory</span> <span class="nf">getParentBeanFactory</span><span class="o">();</span>  
    <span class="kt">boolean</span> <span class="nf">containsLocalBean</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">);</span>    
<span class="o">}</span>
</code></pre></div></div>

<p>그런데 흥미로운 점이 있다. <code class="language-plaintext highlighter-rouge">HierarchicalBeanFactory</code>에는 연관된 <code class="language-plaintext highlighter-rouge">BeanFactory</code>를 get하는 메서드는 존재하지만 set하는 메서드는 존재하지 않는다. 그럼 누가 관계를 맺는 역할을 가진단 말인가?</p>

<p>빈팩토리 간 관계를 맺어주는 역할은 <code class="language-plaintext highlighter-rouge">ConfigurableApplicationContext</code>이 맡고 있다. 아무래도 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>(실행환경)의 구성을 설정하는 부분은 모두 <code class="language-plaintext highlighter-rouge">ConfigurableApplicationContext</code>의 역할이 가지는 듯하다.(ConfigurableApplicationContext에 대한 설명은 잠시 후에 이어한다.)</p>

<p>여기까지가 Bean 및 BeanFactory 관리를 위한 역할 정의를 위한 인터페이스들이었다면 이제는 본격적으로 실행환경을 위한 역할들이 등장한다.</p>

<p><br /></p>

<h3 id="실행환경---applicationcontext">실행환경 - ApplicationContext</h3>
<p><code class="language-plaintext highlighter-rouge">ListableBeanFactory</code>와 <code class="language-plaintext highlighter-rouge">HierarchicalBeanFactory</code>를 상속받는 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>는 실행환경이 마땅히 지녀야할 정보들을 가져오는 메서드들이 존재한다. 말 그대로 실행환경의 역할이다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">ApplicationContext</span> <span class="kd">extends</span> <span class="nc">EnvironmentCapable</span><span class="o">,</span> <span class="nc">ListableBeanFactory</span><span class="o">,</span> <span class="nc">HierarchicalBeanFactory</span><span class="o">,</span> <span class="nc">MessageSource</span><span class="o">,</span> <span class="nc">ApplicationEventPublisher</span><span class="o">,</span> <span class="nc">ResourcePatternResolver</span> <span class="o">{</span>  
    <span class="nc">String</span> <span class="nf">getId</span><span class="o">();</span>
    <span class="nc">String</span> <span class="nf">getApplicationName</span><span class="o">();</span>  
    <span class="nc">String</span> <span class="nf">getDisplayName</span><span class="o">();</span>
    <span class="kt">long</span> <span class="nf">getStartupDate</span><span class="o">();</span>
    <span class="nc">ApplicationContext</span> <span class="nf">getParent</span><span class="o">();</span>
    <span class="nc">AutowireCapableBeanFactory</span> <span class="nf">getAutowireCapableBeanFactory</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IllegalStateException</span><span class="o">;</span>  
<span class="o">}</span>
</code></pre></div></div>

<p>위 코드에서 특별한게 없어보이지만 한 가지 흥미로운 점이 있다. 바로 <code class="language-plaintext highlighter-rouge">getAutowireCapableBeanFactory()</code> 메스드를 통해 리턴되는 <code class="language-plaintext highlighter-rouge">AutowireCapableBeanFactory</code>이다. <code class="language-plaintext highlighter-rouge">ApplicationContext</code>가 곧 <code class="language-plaintext highlighter-rouge">BeanFactory</code>인데 이 새로운 타입의 <code class="language-plaintext highlighter-rouge">BeanFactory</code>는 대체 뭘까?</p>

<p>우리가 <strong>지금까지 알아본 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>는 실제로 빈을 관리하지 않는다.</strong> 외부적으로 빈을 관리하는 것처럼 보이지만 <strong>내부적으로 빈을 생성하고 빈 간의 의존성 주입을 실제로 수행하는 기능 담당 <code class="language-plaintext highlighter-rouge">BeanFactory</code>를  따로 가지고 있다.</strong>  이 포스팅의 타이틀에 있는 (BeanFactory는 사실 2개라고?)의 진실이 바로 이것이다.</p>

<p><code class="language-plaintext highlighter-rouge">ApplicationContext</code>는 외부에 빈 정보를 제공해주기 위해 정보제공용 <code class="language-plaintext highlighter-rouge">BeanFactory</code> 역할만을 담당하고 내부적으로 실제 빈 관리를 담당하는 <code class="language-plaintext highlighter-rouge">BeanFactory(AutowireCapableBeanFactory)</code> Has-A 관계로 가진다.(놀랍도록 역할이 잘 나뉘어져 있다.)</p>

<p><code class="language-plaintext highlighter-rouge">ApplicationContext</code> 클래스 상속관계를 살펴볼 때 어떤 클래스가 이 기능적 빈팩토리의 책임을 담당하는지 살펴보자.</p>

<p><br /></p>

<h3 id="실행환경에-대한-구성과-실행환경을-조작하는-configurableapplicationcontext">실행환경에 대한 구성과 실행환경을 조작하는 ConfigurableApplicationContext</h3>
<p><strong>실행환경 설정, 구성 대한 모든 역할</strong>은 <code class="language-plaintext highlighter-rouge">ConfigurableApplicationContext</code> 전부 가지고 있다. 앞서 설명한 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>의 3가지 최종 구현체 모두 이 인터페이스를 구현하고 있다.</p>

<p>여러가지 get, set 메서드들이 존재하는지 get 메서드들은 제외하고 set 메서드들 위주로 가져와봤다. 위에서 이야기한 <code class="language-plaintext highlighter-rouge">BeanFactory</code> 연관관계 설정을 위한 <code class="language-plaintext highlighter-rouge">setParent</code> 부터 <code class="language-plaintext highlighter-rouge">ApplicationListener</code> 설정도 보인다.</p>

<p>주목해서 볼만한 점은 <code class="language-plaintext highlighter-rouge">refresh</code> 메서드와 <code class="language-plaintext highlighter-rouge">addBeanFactoryPostProcessor</code> 메서드이다. <code class="language-plaintext highlighter-rouge">refresh</code> 메서드는 <code class="language-plaintext highlighter-rouge">ApplicationContext</code> 초기화 과정 중 refresh 단계에 해당되는데 이 단계에서 모든 빈들이 객체화 되고 구성된다. <code class="language-plaintext highlighter-rouge">addBeanFactoryPostProcessor</code>는 refresh 단계 중 실행되는 <code class="language-plaintext highlighter-rouge">BeanFactoryPostProcessor</code>를 추가하는 메서드이다.</p>

<p>refresh 단계에서 무슨 일들이 일어나는지에 대한 자세한 내용은 추후 refresh 단계 소스코드 Deep Dive 포스팅에서 알아본다.(컴포넌트 스캔과 자동구성이 바로 이 단계에서 일어난다. 이렇게 이야기하면 흥미가 생기겠지?ㅎㅎ)</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">ConfigurableApplicationContext</span> <span class="kd">extends</span> <span class="nc">ApplicationContext</span><span class="o">,</span> <span class="nc">Lifecycle</span><span class="o">,</span> <span class="nc">Closeable</span> <span class="o">{</span>  
    <span class="kt">void</span> <span class="nf">setParent</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="nc">ApplicationContext</span> <span class="n">parent</span><span class="o">);</span>
    <span class="kt">void</span> <span class="nf">setEnvironment</span><span class="o">(</span><span class="nc">ConfigurableEnvironment</span> <span class="n">environment</span><span class="o">);</span>  
    <span class="kt">void</span> <span class="nf">setApplicationStartup</span><span class="o">(</span><span class="nc">ApplicationStartup</span> <span class="n">applicationStartup</span><span class="o">);</span>
    <span class="kt">void</span> <span class="nf">addBeanFactoryPostProcessor</span><span class="o">(</span><span class="nc">BeanFactoryPostProcessor</span> <span class="n">postProcessor</span><span class="o">);</span>  
    <span class="kt">void</span> <span class="nf">addApplicationListener</span><span class="o">(</span><span class="nc">ApplicationListener</span><span class="o">&lt;?&gt;</span> <span class="n">listener</span><span class="o">);</span>  
    <span class="kt">void</span> <span class="nf">removeApplicationListener</span><span class="o">(</span><span class="nc">ApplicationListener</span><span class="o">&lt;?&gt;</span> <span class="n">listener</span><span class="o">);</span>
    <span class="kt">void</span> <span class="nf">setClassLoader</span><span class="o">(</span><span class="nc">ClassLoader</span> <span class="n">classLoader</span><span class="o">);</span>  
    <span class="kt">void</span> <span class="nf">addProtocolResolver</span><span class="o">(</span><span class="nc">ProtocolResolver</span> <span class="n">resolver</span><span class="o">);</span>  
    <span class="kt">void</span> <span class="nf">refresh</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">BeansException</span><span class="o">,</span> <span class="nc">IllegalStateException</span><span class="o">;</span>  
    <span class="kt">void</span> <span class="nf">registerShutdownHook</span><span class="o">();</span>  
    <span class="nc">ConfigurableListableBeanFactory</span> <span class="nf">getBeanFactory</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IllegalStateException</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="applicationcontext-역할-정리">ApplicationContext 역할 정리</h3>
<p>이렇게 <code class="language-plaintext highlighter-rouge">ApplicationContext</code>가 실행환경으로 동작 및 설정되기 위해 어떤 종류의 인터페이스들로 구성되어 있는지를 살펴보았다. 마지막으로 나름 위 내용들을 도식화 해보았다.</p>

<p><img src="https://github.com/RokwonK/RokwonK.github.io/assets/52196792/a239a155-39d6-4773-b674-771d8bf6b7af" alt="ApplicationContext 역할 정리 " /></p>

<p><br /> <br /></p>

<h2 id="상속받는-각-클래스들이-지니는-책임">상속받는 각 클래스들이 지니는 책임</h2>

<h3 id="annotationconfigservletwebserverapplicationcontext">AnnotationConfigServletWebServerApplicationContext</h3>
<p><code class="language-plaintext highlighter-rouge">AnnotationConfigServletWebServerApplicationContext</code>은 Web MVC에서 사용되는 <code class="language-plaintext highlighter-rouge">ApplicationContext</code> 최종구현체이다. <a href="https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/web/servlet/context/AnnotationConfigServletWebServerApplicationContext.html">Docs</a>에 따르면 <code class="language-plaintext highlighter-rouge">@Configuration</code>이나 <code class="language-plaintext highlighter-rouge">@Component</code>처럼 <strong>어노테이션 기반의 빈들을 등록하는 기능을 수행한다고 소개</strong>한다.</p>

<p>하지만 프로젝트 실행을 디버깅 해본 결과, 실제로 이 클래스에서 어노테이션을 스캔하는 로직들이 동작하는 걸 확인할 수는 없었다. 아무래도 개발자가 직접 사용하는 방식인 듯하다.</p>

<p><br /></p>

<h3 id="servletwebserverapplicationcontext">ServletWebServerApplicationContext</h3>
<p><code class="language-plaintext highlighter-rouge">AnnotationConfigServletWebServerApplicationContext</code>가 상속받고 있는 클래스로 <strong>내장 WebServer를 생성, 관리하는 기능을 수행하는 클래스</strong>이다.</p>

<p>실행 단계 중 refresh 단계에서 <code class="language-plaintext highlighter-rouge">createWebServer</code>메서드를 실행하여 WebServer를 생성, 셋팅한다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ServletWebServerApplicationContext</span> <span class="kd">extends</span> <span class="nc">GenericWebApplicationContext</span> <span class="kd">implements</span> <span class="nc">ConfigurableWebServerApplicationContext</span> <span class="o">{</span>  
    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Log</span> <span class="n">logger</span> <span class="o">=</span> <span class="nc">LogFactory</span><span class="o">.</span><span class="na">getLog</span><span class="o">(</span><span class="nc">ServletWebServerApplicationContext</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>  
    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">DISPATCHER_SERVLET_NAME</span> <span class="o">=</span> <span class="s">"dispatcherServlet"</span><span class="o">;</span>  
    <span class="kd">private</span> <span class="kd">volatile</span> <span class="nc">WebServer</span> <span class="n">webServer</span><span class="o">;</span>  
    <span class="kd">private</span> <span class="nc">ServletConfig</span> <span class="n">servletConfig</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">serverNamespace</span><span class="o">;</span>  

    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">createWebServer</span><span class="o">()</span> <span class="o">{</span>  
      <span class="c1">// ...</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="genericwebapplicationcontext">GenericWebApplicationContext</h3>
<p><code class="language-plaintext highlighter-rouge">GenericWebApplicationContext</code> 는 <strong>Servlet의 실행환경인 <code class="language-plaintext highlighter-rouge">ServletContext</code>을 관리</strong>한다.</p>

<p><code class="language-plaintext highlighter-rouge">WebServer</code> 위에서 <code class="language-plaintext highlighter-rouge">ServletContext</code>가 동작하므로 <code class="language-plaintext highlighter-rouge">WebServer</code>가 생성 셋팅될때 ServletContext도 생성되어 셋팅된다. 이러한 셋팅 로직은 <code class="language-plaintext highlighter-rouge">ServletWebServerApplicationContext</code>의 <code class="language-plaintext highlighter-rouge">createWebServer</code> 메서드 내 깊숙한 곳에 존재한다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">GenericWebApplicationContext</span> <span class="kd">extends</span> <span class="nc">GenericApplicationContext</span> <span class="kd">implements</span> <span class="nc">ConfigurableWebApplicationContext</span><span class="o">,</span> <span class="nc">ThemeSource</span> <span class="o">{</span>  

    <span class="nd">@Nullable</span>  
    <span class="kd">private</span> <span class="nc">ServletContext</span> <span class="n">servletContext</span><span class="o">;</span>  

    <span class="nd">@Nullable</span>  
    <span class="kd">private</span> <span class="nc">ThemeSource</span> <span class="n">themeSource</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="genericapplicationcontext">GenericApplicationContext</h3>
<p><code class="language-plaintext highlighter-rouge">GenericApplicationContext</code>는 Generic이라는 이름에서부터 유추할 수 있듯이 3종류의 최종 구현체가 모두에게 상속되는 클래스이다. 이 클래스가 바로 <strong>실제 기능적인 빈 생성,관리를 담당하는 내부 빈팩토리를 관리하는 클래스</strong>이다.</p>

<p>실제로 내부 메서드들도 대부분 내부적인 빈팩토리를 조작하는 메서드들로 구성되어 있다. 또 <code class="language-plaintext highlighter-rouge">BeanDefinitionRegistry</code> 인터페이스를 구현해 BeanDefinition 저장소임을 표방하고 있다.</p>

<p><code class="language-plaintext highlighter-rouge">DefaultListableBeanFactory</code> 가 내부 빈팩토리로 <code class="language-plaintext highlighter-rouge">ApplicationContext</code> 인터페이스에서 보았던 <code class="language-plaintext highlighter-rouge">AutoWireCapableBeanFactory</code>를 구현한 구현체이다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">GenericApplicationContext</span> <span class="kd">extends</span> <span class="nc">AbstractApplicationContext</span> <span class="kd">implements</span> <span class="nc">BeanDefinitionRegistry</span> <span class="o">{</span>  
    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">DefaultListableBeanFactory</span> <span class="n">beanFactory</span><span class="o">;</span>  

    <span class="nd">@Nullable</span>  
    <span class="kd">private</span> <span class="nc">ResourceLoader</span> <span class="n">resourceLoader</span><span class="o">;</span>  
    <span class="kd">private</span> <span class="kt">boolean</span> <span class="n">customClassLoader</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>  
    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">AtomicBoolean</span> <span class="n">refreshed</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">AtomicBoolean</span><span class="o">();</span>

    <span class="nd">@Override</span>  
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">registerBeanDefinition</span><span class="o">(</span><span class="nc">String</span> <span class="n">beanName</span><span class="o">,</span> <span class="nc">BeanDefinition</span> <span class="n">beanDefinition</span><span class="o">)</span>  
          <span class="kd">throws</span> <span class="nc">BeanDefinitionStoreException</span> <span class="o">{</span>  
        <span class="k">this</span><span class="o">.</span><span class="na">beanFactory</span><span class="o">.</span><span class="na">registerBeanDefinition</span><span class="o">(</span><span class="n">beanName</span><span class="o">,</span> <span class="n">beanDefinition</span><span class="o">);</span>  
    <span class="o">}</span>  
      
    <span class="nd">@Override</span>  
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">removeBeanDefinition</span><span class="o">(</span><span class="nc">String</span> <span class="n">beanName</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">NoSuchBeanDefinitionException</span> <span class="o">{</span>  
        <span class="k">this</span><span class="o">.</span><span class="na">beanFactory</span><span class="o">.</span><span class="na">removeBeanDefinition</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span>  
    <span class="o">}</span>  
      
    <span class="nd">@Override</span>  
    <span class="kd">public</span> <span class="nc">BeanDefinition</span> <span class="nf">getBeanDefinition</span><span class="o">(</span><span class="nc">String</span> <span class="n">beanName</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">NoSuchBeanDefinitionException</span> <span class="o">{</span>  
        <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">beanFactory</span><span class="o">.</span><span class="na">getBeanDefinition</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span>  
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="abstractapplicationcontext">AbstractApplicationContext</h3>
<p>마지막으로 <code class="language-plaintext highlighter-rouge">AbstractApplicationContext</code>이다. 이 클래스는 추상 클래스로 <strong><code class="language-plaintext highlighter-rouge">ApplicatioContext</code>로서 제 기능을 다하기 위한 기본적인 로직들이 구현</strong>되어 있다. 특히 refresh()와 같은 프로젝트 실행에 대한 로직들이 <strong>템플릿 메소드 패턴으로 되어 있어 서브클래스에서 이를 효과적으로 구현</strong>할 수 있도록 돕는다.</p>

<p>아래코드는 템플릿 메소드 패턴이 적용된 refresh 코드이다. 각 단계의 메서드들을 서브클래스들이 필요에 따라 오버라이딩해 필요한 로직을 작성할 수 있다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">AbstractApplicationContext</span> <span class="kd">extends</span> <span class="nc">DefaultResourceLoader</span> <span class="kd">implements</span> <span class="nc">ConfigurableApplicationContext</span> <span class="o">{</span>
	  <span class="c1">// ...</span>

    <span class="nd">@Override</span>  
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">refresh</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">BeansException</span><span class="o">,</span> <span class="nc">IllegalStateException</span> <span class="o">{</span>  
        <span class="k">this</span><span class="o">.</span><span class="na">startupShutdownLock</span><span class="o">.</span><span class="na">lock</span><span class="o">();</span>  
        <span class="k">try</span> <span class="o">{</span>  
          <span class="c1">// ...</span>

          <span class="c1">// 템플릿 메소드 패턴</span>
          <span class="n">prepareRefresh</span><span class="o">();</span>  
          <span class="nc">ConfigurableListableBeanFactory</span> <span class="n">beanFactory</span> <span class="o">=</span> <span class="n">obtainFreshBeanFactory</span><span class="o">();</span>  
          <span class="n">prepareBeanFactory</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">);</span>  
        
          <span class="k">try</span> <span class="o">{</span>  
              <span class="n">postProcessBeanFactory</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">);</span>  
              <span class="nc">StartupStep</span> <span class="n">beanPostProcess</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">applicationStartup</span><span class="o">.</span><span class="na">start</span><span class="o">(</span><span class="s">"spring.context.beans.post-process"</span><span class="o">);</span>  

              <span class="n">invokeBeanFactoryPostProcessors</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">);</span>   
              <span class="n">registerBeanPostProcessors</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">);</span>  
              <span class="n">beanPostProcess</span><span class="o">.</span><span class="na">end</span><span class="o">();</span>  

              <span class="n">initMessageSource</span><span class="o">();</span>  
              <span class="n">initApplicationEventMulticaster</span><span class="o">();</span>  
              <span class="n">onRefresh</span><span class="o">();</span>  
              <span class="n">registerListeners</span><span class="o">();</span>  
              <span class="n">finishBeanFactoryInitialization</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">);</span>  
              <span class="n">finishRefresh</span><span class="o">();</span>  
          <span class="o">}</span>  
          <span class="c1">// ...</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p><br /></p>

<h3 id="정리">정리</h3>
<p>복잡한 로직들을 이렇게 깔끔하고 단순하게 정리했다는게 놀랍다. 적절한 역할을 가지는 인터페이스로 나누고 각 클래스가 한 가지 책임만을 지니도록 깔끔한게 분리된 스프링 코드를 보니 새롭게 공부하는 것 같은 느낌이 든다.</p>

<p>마지막으로 지금까지 알아본 인터페이스들과 클래스들이 가지는 핵심 역할과 책임을 도식화해보았다.
<img src="https://github.com/RokwonK/RokwonK.github.io/assets/52196792/a6e3d218-e57e-48f5-aa01-725a545a99a8" alt="ApplicationContext상속구조" /></p>

<p>다음 포스팅은 다시 <code class="language-plaintext highlighter-rouge">SpringApplication</code> 실행으로 돌아가서 SpringApplication의 <code class="language-plaintext highlighter-rouge">run</code> 메서드에서 Application이 실행되는 과정의 코드를 살펴보도록 하자.</p>]]></content><author><name>RokWon K</name></author><category term="Spring" /><summary type="html"><![CDATA[Spring Container의 전신인 ApplcationContext는 어떤 역할과 책임을 가지는지 살펴보자]]></summary></entry></feed>