<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>free_mill.log</title>
        <link>https://velog.io/</link>
        <description>금융 개발 전문가가 목표</description>
        <lastBuildDate>Thu, 20 Apr 2023 21:44:35 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>free_mill.log</title>
            <url>https://velog.velcdn.com/images/free_mill/profile/9b9f1e14-209d-45eb-aead-51461c745afd/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. free_mill.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/free_mill" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[빈 생명주기 콜백]]></title>
            <link>https://velog.io/@free_mill/%EB%B9%88-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0-%EC%BD%9C%EB%B0%B1</link>
            <guid>https://velog.io/@free_mill/%EB%B9%88-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0-%EC%BD%9C%EB%B0%B1</guid>
            <pubDate>Thu, 20 Apr 2023 21:44:35 GMT</pubDate>
            <description><![CDATA[<h3 id="빈-생명주기-콜백-시작">빈 생명주기 콜백 시작</h3>
<p>데이터베이스 커넥션 풀이나, 네트워크 소켓처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요하다.
이번 시간에는 스프링을 통해 이러한 초기화 작업과 종료 작업을 어떻게 진행하는지 예제로 알아보자.</p>
<p>간단하게 외부 네트워크에 미리 연결하는 객체를 하나 생성한다고 가정해보자. 실제로 네트워크에 연결하는 것은 아니고, 단순히 문자만 출력하도록 했다. 이 <code>NetworkClient</code>는 애플리케이션 시작 시점에 <code>connect()</code>를 호출해서 연결을 맺어두어야 하고, 애플리케이션이 종료되면 <code>disconnect()</code>를 호출해서 연결을 끊어야 한다.</p>
<p><strong>예제 코드, 테스트 하위에 생성</strong></p>
<pre><code class="language-java">public class NetworkClient{

    private String url;

    public NetworkClient(){
        System.out.println(&quot;생성자 호출, url = &quot; + url);
        connect();
        call(&quot;초기화 연결 메시지&quot;);
    }

    public void setUrl(String url){
        this.url = url;
    }

    //서비스 시작시 호출
    public void connect(){
        System.out.println(&quot;connect: &quot; + url);
    }

    public void call(String message){
        System.out.println(&quot;call: &quot; + &quot; message = &quot; + message);
    }

    //서비스 종료시 호출
    public void disconnect(){
        System.out.println(&quot;close: &quot; + url);
    }
}</code></pre>
<p><strong>스프링 환경설정과 실행</strong></p>
<pre><code class="language-java">public class BeanLifeCycleTest {

    @Test
    public void lifeCycleTest() {
           ConfigurableApplicationContext ac = new AnnotationConfigApplicatioinContext(LifeCycleConfig.class);

        NetworkCleint client = ac.getBean(NetworkClient.class);
           ac.close();
    }

    @Configuration
    static class LifeCycleConfig {
        @Bean
        public NetworkClient networkClient(){
            NetworkClient networkClient = new NetworkClient();
            networkClient.setUrl(&quot;http://hello-spring.dev&quot;);
            return networkClient;
        }
    }

}</code></pre>
<p>실행해보면 다음과 같은 이상한 결과가 나온다.</p>
<pre><code class="language-java">생성자 호출, url = null
connect: null
call: null message = 초기화 연결 메시지</code></pre>
<p>생성자 부분을 보면 url 정보 없이 connect가 호출되는 것을 확인할 수 있다.
너무 당연한 이야기지만 객체를 생성하는 단계에는 url이 없고, 객체를 생성한 다음에 외부에서 수정자 주입을 통해서<code>setUrl()</code>이 호출되어야 url이 존재하게 된다.</p>
<p>스프링 빈은 간단하게 다음과 같은 라이프사이클을 가진다.
<strong>객체 생성 -&gt; 의존관계 주입</strong></p>
<p>스프링 빈은 객체를 생성하고, 의존관계 주입이 다 끝난 다음에야 필요한 데이터를 사용할 수 있는 준비가 완료된다. 따라서 초기화 작업은 의존관계 주입이 모두 완료되고 난 다음에 호출해야 한다.
그런데 개발자가 의존관계 주입이 모두 완료된 시점을 어떻게 알 수 있을까?
<strong>스프링 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해서 초기화 시점을 알려주는 다양한 기능을 제공</strong>한다. 또한 <strong>스프링은 스프링 컨테이너가 종료되기 직전에 소멸 콜백</strong>을 준다. 따라서 안전하게 종료 작업을 진행할 수 있다.</p>
<p><strong>스프링 빈의 이벤트 라이프사이클</strong>
<strong>스프링 컨테이너 생성 ➢ 스프링 빈 생성 ➢ 의존관계 주입 ➢ 초기화 콜백 ➢ 소멸전 콜백 ➢ 스프링 종료</strong></p>
<ul>
<li><strong>초기화 콜백</strong>: 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출</li>
<li><strong>소멸전 골백</strong>: 빈이 소멸되기 직전에 호출</li>
</ul>
<p>스프링은 다양한 방식으로 생명주기 콜백을 지원한다.</p>
<blockquote>
<p><strong>참고: 객체의 생성과 초기화를 분리하자.</strong>
셍성자는 필수 정보(파라미터)를 받고, 메모리를 할당해서 객체를 생성하는 책임을 가진다. 반면에 초기화는 이렇게 생성된 값들을 활용해서 외부 커넥션을 연결하는 등 무거운 동작을 수행한다.
따라서 생성자 안에서 무거운 초기화 작업을 함께 하는 것 보다는 객체를 생성하는 부분과 초기화 하는 부분을 명확하게 나누는 것이 유지보수 관점에서 좋다. 물론 초기화 작업이 내부 값들만 약간 변경하는 정도로 단순한 경우에는 생성자에서 한번에 다 처리하는게 더 나을 수 있다.</p>
</blockquote>
<blockquote>
<p><strong>참고:</strong>
싱글톤 빈들은 스프링 컨테이너가 종료될 때 싱글톤 빈들도 함께 종료되기 때문에 스프링 컨테이너가 종료되기 직전에 소멸전 콜백이 일어난다. 뒤에서 설명하겠지만 싱글톤 처럼 컨테이너의 시작과 종료까지 생존하는 빈도 있지만, 생명주기가 짧은 빈들도 있는데 이 빈들은 컨테이너와 무관하게 해당 빈이 종료되기 직전에 소멸전 콜백이 일어난다. 자세한 내용은 스코프에서 알아보겠다.</p>
</blockquote>
<p><strong>스프링은 크게 3가지 방법으로만 빈 생명주기 콜백을 지원한다.</strong></p>
<ul>
<li>인터 페이스(InitializingBean, DisposalBean)</li>
<li>설정 정보에 초기화 메서드, 종료 메서드 지정</li>
<li>@PostConstruct, @PreDestroy 애노테이션 지원</li>
</ul>
<h3 id="인터페이스-initializingbean-disposalbean">인터페이스 InitializingBean, DisposalBean</h3>
<p><strong>코드를 바로 보자</strong></p>
<pre><code class="language-java">public class NetworkClient implements InitializingBean, DisposalBean{

    private String url;

    public NetworkClient(){
        System.out.println(&quot;생성자 호출, url = &quot; +url);
    }

     public void setUrl(String url){
        this.url = url;
    }

    //서비스 시작시 호출
    public void connect(){
        System.out.println(&quot;connect: &quot; + url);
    }

    public void call(String message){
        System.out.println(&quot;call: &quot; + url + &quot;message = &quot; + message);
    }

    //서비스 종료시 호출
    public void disConnect(){
        System.out.println(&quot;close + &quot; + url);
    }

    @Override
    public void afterPropertiesSet() throws Exception{
        connect();
        call(&quot;초기화 연결 메시지&quot;);
    }

    @Override
    public void destroy() throws Exception {
        disconnect();
    }
}</code></pre>
<ul>
<li><code>InitializingBean</code>은 <code>afterPropertiesSet()</code> 메서드로 초기화를 지원한다.</li>
<li><code>DisposableBean</code>은 <code>destroy()</code>메서드로 소멸을 지원한다.</li>
</ul>
<p><strong>출력결과</strong></p>
<pre><code>생성자 호출, url = null
NetworkClient.afterPropertiesSet
connect: http://hello-spring.dev
call: http://hello-spring.dev message = 초기화 연결 메시지
13:24:49.043 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing NetworkClient.destroy
close + http://hello-spring.dev</code></pre><ul>
<li>출력 결과를 보면 초기화 메서드가 주입 완료 후에 적절하게 호출 된 것을 확인할 수 있다.</li>
<li>그리고 스프링 컨테이너의 종료가 호출되자 소멸 메서드가 호출 된 것도 확인할 수 있다.</li>
</ul>
<p><strong>초기화, 소멸 인터페이스 단점</strong></p>
<ul>
<li>이 인터페이스는 스프링 전용 인터페이스이다. 해당 코드가 스프링 전용 인터페이스에 의존한다.</li>
<li>초기화, 소멸 메서드의 이름을 변경할 수 없다.</li>
<li>내가 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없다.</li>
</ul>
<blockquote>
<p>참고: 인터페이스를 사용하는 초기화, 종료 방법은 스프링 초창기에 나온 방법들이고, 지금은 다음의 더 나은 방법들이 있어서 거의 사용하지 않는다.</p>
</blockquote>
<h3 id="빈-등록-초기화-소멸-메서드-지정">빈 등록 초기화, 소멸 메서드 지정</h3>
<p>설정 정보에 <code>@Bean(initMethod = &quot;init&quot;, destroyMethod = &quot;close&quot;)</code>처럼 초기화, 소멸 메서드를 지정할 수 있다.</p>
<p><strong>설정 정보를 사용하도록 변경</strong></p>
<pre><code class="language-java">public class NetworkClient {
    private String url;

    public NetworkClient() {
        System.out.println(&quot;생성자 호출, url = &quot; + url); 
    }

    public void setUrl(String url) {
        this.url = url;
    }

    //서비스 시작시 호출
    public void connect() {
        System.out.println(&quot;connect: &quot; + url);
    }

    public void call(String message) {
        System.out.println(&quot;call: &quot; + url + &quot; message = &quot; + message);
        }

     //서비스 종료시 호출
    public void disConnect() {
       System.out.println(&quot;close + &quot; + url);
    }

    public void init() { 
        System.out.println(&quot;NetworkClient.init&quot;); 
         connect();
        call(&quot;초기화 연결 메시지&quot;);
    }

    public void close() {
        System.out.println(&quot;NetworkClient.close&quot;);
        disConnect();    
    }
}</code></pre>
<p><strong>설정 정보에 초기화 메서드 지정</strong></p>
<pre><code class="language-java">@Configuration
  static class LifeCycleConfig {
      @Bean(initMethod = &quot;init&quot;, destroyMethod = &quot;close&quot;)
      public NetworkClient networkClient() {
          NetworkClient networkClient = new NetworkClient();
          networkClient.setUrl(&quot;http://hello-spring.dev&quot;);
          return networkClient;
    } 
}</code></pre>
<p><strong>결과</strong></p>
<pre><code>생성자 호출, url = null
NetworkClient.init
connect: http://hello-spring.dev
call: http://hello-spring.dev message = 초기화 연결 메시지
13:33:10.029 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing NetworkClient.close
close + http://hello-spring.dev</code></pre><p><strong>설정 정보 사용 특징</strong></p>
<ul>
<li>메서드 이름을 자유롭게 줄 수 있다.</li>
<li>스프링 빈이 스프링 코드에 의존하지 않는다.</li>
<li>코드가 아니라 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부 라이브러이에도 초기화, 종료 메서드를 적용할 수 있다.</li>
</ul>
<p><strong>종료 메서드 추론</strong></p>
<ul>
<li><code>@Bean의 destroyMethod</code>속성에는 아주 특별한 기능이 있다.</li>
<li>라이브러리는 대부분 <code>close</code>, <code>shutdown</code>이라는 이름의 종료 메서드를 사용한다.</li>
<li><code>@Bean</code>의 <code>destroyMethod</code>는 기본값이 <code>(infered)</code>(추론)으로 등록되어 있다.</li>
<li>이 추론 기능은 <code>close</code>, <code>shutdown</code>라는 이름의 메서드를 자동으로 호출해준다. 이름 그대로 종료 메서드를 추론해서 호출해준다.</li>
<li>따라서 직접 스프링 빈으로 등록하면 종료 메서드를 자동으로 호출해준다. 이름 그대로 종료 메서드를 호출해준다.</li>
<li>따라서 직접 스프링 빈으로 등록하면 종료 메서드는 따로 적어주지 않아도 잘 동작한다.</li>
<li>추론 기능을 사용하기 싫으면 <code>destroyMethod=&quot;&quot;</code>처럼 빈 공백을 지정하면 된다.</li>
</ul>
<h3 id="애노테이션-postconstruct-predestroy">애노테이션 @PostConstruct, @PreDestroy</h3>
<p>우선 코드 먼저 보고 설명하겠다.</p>
<pre><code class="language-java">public class NetworkClient {
    private String url;

    public NetworkClient(){
         System.out.println(&quot;생성자 호출, url = &quot; +url);   
    }

    public void setUrl(String url){
        this.url = url;
    }

    //서비스 시작시 호출
     public void connect(){
        System.out.println(&quot;connect: &quot; + url); 
    }

    public void call(String message){
        System.out.println(&quot;call: &quot; + url + &quot;message = &quot; +message);
    }

    //서비스 종료시 호출
    public void disConnect(){
        System.out.println(&quot;close + &quot; + url);
    }

    @PostConstruct
    public void init(){
        System.out.println(&quot;NetworkClient.init&quot;);
        connect();
        call(&quot;초기화 연결 메시지&quot;);
    }

    @PreDestroy
    public void close(){
        System.out.println(&quot;NetworkClient.close&quot;);
        disconnect();
    }

}</code></pre>
<pre><code class="language-java">@Configuration
static class LifeCycleConfig{

    @Bean
    public NetworkClient networkClient(){
        NetworkClient networkClient = new NetworkClient();
        networkClient.setUrl(&quot;http://hello-spring.dev&quot;);
        return networkClient;
    }
}</code></pre>
<p><strong>실행결과</strong></p>
<pre><code class="language-java">생성자 호출, url = null
NetworkClient.init
connect: http://hello-spring.dev
call: http://hello-spring.message = 초기화 연결 메시지
21:20:22.299 [main] DEBUG
org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing NetworkClient.close
close + http://hello-spring.dev</code></pre>
<p><code>@PostConstruct</code>, <code>@PreDestroy</code>이 두 애노테이션을 사용하면 가장 편리하게 초기화와 종료를 실행할 수 있다.</p>
<p><strong>@PostConstruct, @PreDestory 애노테이션 특징</strong></p>
<ul>
<li>최신 스프링에서 가장 권장하는 방법이다.</li>
<li>애노테이션 하나만 붙이면 되므로 매우 편리하다.</li>
<li>패키지를 잘 보면 <code>javax.annotation.PostConstruct</code>이다. 스프링에 종속적인 기술이 아니라 JSR-250라는 자바 표준이다. 따라서 스프링이 아닌 다른 컨테이너에서도 동작한다.</li>
<li>컴포넌트 스캔과 잘 어울린다.</li>
<li>유일한 단점은 외부 라이브러리에는 적용하지 못한다는 것이다. 외부 라이브러리르를 초기화, 종료해야 하면 @Bean의 기능을 사용하자.</li>
</ul>
<p><strong>정리</strong></p>
<ul>
<li><strong>@PostConstruct, @PreDestroy 애노테이션을 사용하자.*</strong></li>
<li>코드를 고칠 수 없는 외부 라이브러리를 초기화, 종료해야 하면 <code>@Bean</code>의 <code>initMethod</code>, <code>destroyMethod</code>를 사용하자.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[의존관계 자동 주입]]></title>
            <link>https://velog.io/@free_mill/%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84-%EC%9E%90%EB%8F%99-%EC%A3%BC%EC%9E%85</link>
            <guid>https://velog.io/@free_mill/%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84-%EC%9E%90%EB%8F%99-%EC%A3%BC%EC%9E%85</guid>
            <pubDate>Sat, 15 Apr 2023 09:18:42 GMT</pubDate>
            <description><![CDATA[<h3 id="다양한-의존관계-주입-방법"><strong>다양한 의존관계 주입 방법</strong></h3>
<p>의존관계 주입은 크게 4가지 방법이 있다.</p>
<ul>
<li>생성자 주입</li>
<li>수정자 주입(setter 주입)</li>
<li>필드 주입</li>
<li>일반 메서드 주입</li>
</ul>
<p><strong>생성자 주입</strong></p>
<ul>
<li><p>이름 그대로 생성자를 통해서 의존 관계를 주입 받는 방법이다.</p>
</li>
<li><p>지금까지 우리가 진행했던 방법이 바로 생성자 주입이다.</p>
</li>
<li><p>특징</p>
<ul>
<li><p>생성자 호출시점에 딱 1번만 호출되는 것이 보장된다.</p>
</li>
<li><p><strong>불변, 필수</strong> 의존관계에 사용</p>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;

@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy){
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}
}</code></pre>
</li>
</ul>
</li>
<li><p><em>중요! 생성자가 딱 1개만 있으면 @Autowired를 생략해도 자동 주입 된다.*</em>물론 스프링 빈에만 해당한다.</p>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderService {
   private final MemberRepository memberRepository;
   private final DiscountPolicy discountPolicy;

   public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
       this.memberRepository = memberRepository;
       this.discountPolicy = discountPolicy;
   }
}</code></pre>
</li>
</ul>
<p><strong>수정자 주입(setter    주입)</strong></p>
<ul>
<li><p>setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법이다.</p>
</li>
<li><p>특징</p>
<ul>
<li><p><strong>선택, 변경</strong> 가능성이 있는 의존관계에 사용</p>
</li>
<li><p>자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용하는 방법이다.</p>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderSerivce{

private MemberRepository memberRepository;
private DiscountPolicy discountPoilcy;

@Autowired
public void setMemerRepository(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
}

@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy){
    this.discountPolicy = discountPolicy;
}
}</code></pre>
<blockquote>
<p>참고: <code>@Autowired</code>의 기본 동작은 주입할 대상이 없으면 오류가 발생한다. 주입할 대상이 없어도 동작하게 하려면 <code>@Autowired(required = false)</code>로 지정하면 된다.</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>참고: 자바빈 포로퍼티, 자바에서는 과거부터 필드의 값을 직접 변경하지 않고, setXxx, getXxx라는 메서드를 통해서 값을 읽거나 수정하는 규칙을 만들었는데, 그것이 자바빈 프로퍼티 규약이다. 더 자세한 내용이 굼긍하면 자바빈 프로퍼티로 검색해보자.</p>
</blockquote>
<p><strong>자바빈 프로퍼티 규약 예시</strong></p>
<pre><code class="language-java">class Data{
    private int age;

    public void setAge(int age){
        this.age = age;
    }

    public void getAge(int age){
        return age;
    }
}</code></pre>
<p><strong>필드 주입</strong></p>
<ul>
<li><p>이름 그대로 필드에 바로 주입하는 방법이다.</p>
</li>
<li><p>특징</p>
<ul>
<li><p>코드가 간결해서 많은 개발자들을 유혹하지만 외부에서 변경이 불가능해서 테스트하기 힘들다는 치명적인 단점이 있다.</p>
</li>
<li><p>DI 프레임워크가 없으면 아무것도 할 수 없다.</p>
</li>
<li><p>사용하지 말자</p>
<ul>
<li>애플리케이션의 실제 코드와 관계 없는 테스트 코드</li>
<li>스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 사용<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderSercice{
</code></pre>
</li>
</ul>
<p>@Autowired
private MemberRepository memberRepository;
@Autowired
private DiscountPoilcy discountPolicy;
}    </p>
<pre><code>&gt; 참고 : 순수한 자바 테스트 코드에는 당연히 @Autowired가 동작하지 않는다. ```@SpringBootTest```처럼 스프링 컨테이너를 테스트에 통합한 경우에만 가능하다.
</code></pre></li>
</ul>
</li>
</ul>
<blockquote>
<p>참고 : 다음 코드와 같이 <code>@Bean</code>에서 파라미터에 의존관계는 자동 주입된다. 수동 등록시 자동 등록된 빈의 의존관계가 필요할 때 문제를 해결할 수 있다.</p>
</blockquote>
<p><strong>일반 메서드 주입</strong></p>
<ul>
<li><p>일반 메서드를 통해서 주입 받을 수 있다.</p>
</li>
<li><p>특징</p>
<ul>
<li><p>한번에 여러 필드를 주입 받을 수 있다.</p>
</li>
<li><p>일반적으로 잘 사용하지 않는다.</p>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderService{
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;

@Autowired
public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy){
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}
}</code></pre>
<blockquote>
<p>참고 : 어쩌면 당연한 이야기지만 의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 빈이어야 동작한다. 스프링 빈이 아닌 <code>Member</code>같은 클래스에서 <code>@Autowired</code>코드를 적용해도 아무 기능도 동작하지 않는다.</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<h3 id="옵션처리"><strong>옵션처리</strong></h3>
<p>주입할 스프링 빈이 없어도 동작해야 할 때가 있다.
그런데 <code>@Autowired</code>만 사용하면 <code>required</code> 옵션의 기본값이 <code>true</code> 로 되어 있어서 자동 주입 대상이 없으면 오류가 발생한다.</p>
<p>자동 주입 대상을 옵션으로 처리하는 방법은 다음과 같다.</p>
<ul>
<li><code>@Autowired(required = true)</code>: 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출 안됨</li>
<li><code>org.springframework.lang.@Nullable</code>: 자동 주입할 대상이 없으면 null이 입력된다.</li>
<li><code>Optional&lt;&gt;</code>: 자동 주입할 대상이 없으면 <code>Optional.empty</code>가 입력된다.</li>
</ul>
<p>예제로 확인해보자.</p>
<pre><code class="language-java">//호출 안됨
@Autowired(required = false)
public void setNoBean1(Member member){
    System.out.println(&quot;setNoBean1 = &quot; + member);
}

//null 호출
@Autowired
public void setNoBean2(@Nullable Member member){
    System.out.println(&quot;setNoBean = &quot; + member);
}

@Optional.empty 호출
@Autowired(required = false)
public void setNoBean3(Optional&lt;Member&gt; member){
    System.out.println(&quot;setNoBean3 = &quot; + member);
}</code></pre>
<ul>
<li><strong>Member는 스프링 빈이 아니다.</strong></li>
<li><code>setNoBean1()</code>은 <code>@Autowired(required = false)</code>이므로 호출 자체가 안된다.</li>
</ul>
<p><strong>출력결과</strong></p>
<pre><code>setNoBean = null
setNoBean3 = OPtional.empty</code></pre><blockquote>
<p>참고: @Nullable, Optional은 스프링 전반에 걸쳐서 지원된다. 예를 들어서 생성자 자동 주입에서 특정 필드에만 사용해도 된다.</p>
</blockquote>
<h3 id="생성자-주입을-선택해라"><strong>생성자 주입을 선택해라!</strong></h3>
<p><strong>불변</strong></p>
<ul>
<li>대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다. 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다.(불변해야 한다.)</li>
<li>수정자 주입을 사용하면, setXxx 메서드를 public으로 열어두어야한다.</li>
<li>누군가 실수로 변경할 수도 있고, 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아니다.</li>
<li>생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없다. 따라서 불변하게 설계할 수 있다.</li>
</ul>
<p><strong>누락</strong>
프레임워크 없이 순수한 자바 코드를 단위 테스트 하는 경우에
다음과 같이 수정자 의존관계인 경우</p>
<pre><code class="language-java">public class OrderServiceImpl implements OrderSercice{
    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    @Autowired
    public void setMemberRepository(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Autowired
    public void setDiscountPolicy(DiscountPolicy discountPolicy){
        this.discountPolicy = discountPolicy;
    }
}    </code></pre>
<ul>
<li><code>@Autowired</code>가 프레임워크 안에서 동작할 때는 의존관계는 없으면 오류가 발생하지만, 지금은 프레임워크없이 순수한 자바 코드로만 단위 테스트를 수행하고 있다.</li>
</ul>
<p>이렇게 테스트를 수행하면 실행은 된다.</p>
<pre><code class="language-java">@Test
void createOrder(){
    OrderServiceImpl orderSerivce = new OrderServiceImpl();
    orderService.createOrder(1L, &quot;itemA&quot;, 10000);
}</code></pre>
<p>그런데 막상 실행 결과는 NPE(Null Point Exception)이 발생하는데, memberRepository, discountPolicy 모두 의존관계 주입이 누락되었기 때문이다.</p>
<p>생성자 주입을 사용하면 다음처럼 주입 데이터를 누락했을 때 컴파일 오류가 발생한다.
그리고 IDE에서 바로 어떤 값을 필수로 주입해야 하는지 알 수 있다.</p>
<pre><code class="language-java">@Test
void createOrder(){
    OrderServiceImpl orderService = new OrderServiceImpl();
    orderService.createOrder(1L, &quot;itemA&quot;, 10000);
}</code></pre>
<p><strong>final 키워드</strong>
생성자 주입을 사용하면 필드에 <code>final</code>키워드를 사용할 수 있다. 그래서 생성자에서 혹시라도 값이 설정되지 않은 오류를 컴파일 시점에 막아준다. 다음 코드를 보자.</p>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderService{
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowried
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy){
        this.memberRepository = memberRepository;
    }
}</code></pre>
<ul>
<li>잘보면 필수 필드인 <code>discountPolicy</code>에 값을 설정해야 하는데, 이 부분이 누락되었다. 자바는 컴파일 시점에 다음 오류를 발생시킨다.</li>
<li><code>java: variable discountPolicy might not have been initialized</code></li>
<li>기억하자! <strong>컴파일 오류는 세상에서 가장 빠르고, 좋은 오류다!</strong><blockquote>
<p><strong>참고:</strong> 수정자 주입을 포함한 나머지 주입 방식은 모두 생성자 이후에 호출되므로, 필드에 <code>final</code> 키워드를 사용할 수 없다. 오직 생성자 주입 방식만 <code>final</code> 키워드를 사용할 수 있다.</p>
</blockquote>
</li>
</ul>
<p><strong>정리</strong></p>
<ul>
<li>생성자 주입 방식을 선택하는 이유는 여러가지가 있지만, 프레임워크에 의존하지 않고, 순수한 자바 언어의 특징을 잘 살리는 방법이기도 하다.</li>
<li>기본으로 생성자 주입을 사용하고, 필수 값이 아닌 경우에는 수정자 주입 방식을 옵션으로 부여하면 된다. 생성자 주입과 수정자 주입을 동시에 사용할 수 있다.</li>
<li>항상 생성자 주입을 선택해라! 그리고 가끔 옵션이 필요하면 수정자 주입을 선택해라. 필드 주입은 사용하지 않는게 좋다.</li>
</ul>
<h3 id="롬복과-최신-트렌드"><strong>롬복과 최신 트렌드</strong></h3>
<p>막상 개발을 해보면, 대부분이 다 불변이고, 그래서 다음과 같이 필드에 final 키워드를 사용하게 된다.
그런데 생성자도 만들어야 하고, 주입 받은 값을 대입하는 코드도 만들어야 하고..
필드 주입처럼 좀 편리하게 사용하는 방법은 없을까?
역시 개발자는 귀찮은 것은 못참는다.
다음 기본 코드를 최적화해보자.</p>
<p><strong>기본 코드</strong></p>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderService{
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy){
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}</code></pre>
<ul>
<li>이제 롬복을 적용해보자. 롬복 라이브러리 적용 방법은 아래에 적어두었다.</li>
<li>롬복 라이브러리가 제공하는 <code>@RequiredArgsConstructor</code> 기능을 사용하면 final이 붙은 필드를 모아서 생성자를 자동으로 만들어준다.(다음 코드에는 보이지 않지만 실제 호출 가능하다.)</li>
<li>최종 결과는 다음과 같다! 정말 간결하다!</li>
</ul>
<p><strong>최종 결과 코드</strong></p>
<pre><code class="language-java">@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService{
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;
}</code></pre>
<p>이 최종결과 코드와 이전의 코드는 완전히 동일하다. 롬복이 자바의 애노테이션 프로세서라는 기능을 이용해서 컴파일 시점에 생성자를 자동으로 생성해준다. 실제 <code>class</code>를 열어보면 다음 코드가 추가되어 있는 것을 확인할 수 있다.</p>
<pre><code class="language-java">public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}</code></pre>
<p><strong>정리</strong>
최근에는 생성자를 딱 1개 두고, <code>@Autowired</code>를 생략하는 방법을 주로 사용한다. 여기에 Lombok라이브러리의 <code>@RequiredArgsConstructor</code>함께 사용하면 기능은 다 제공하면서, 코드는 깔끔하게 사용할 수 있다.</p>
<p><strong>롬복 라이브러리 적용 방법</strong>
<code>build.gradle</code>에 라이브러리 및 환경 추가</p>
<pre><code class="language-java">plugins {
    id &#39;org.springframework.boot&#39; version &#39;2.3.2.RELEASE`
    id &#39;io.spring.dependency-management&#39; version &#39;1.0.9.RELEASE&#39;
    id &#39;java&#39;
}

group = &#39;hello&#39;
version = &#39;0.0.1-SNAPSHOT&#39;
sourceCompatibility = &#39;11&#39;

//lombok 설정 추가 시작
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}
//lombok 설정 추가 끝

repositories {
    mavenCentral()
}

dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter&#39;

    //lombok 라이브러리 추가 시작
    compileOnly &#39;org.projectlombok:lombok&#39;
    annotationProcessor &#39;org.projectlombok:lombok&#39;

    testCompileOnly &#39;org.projectlombok:lombok&#39;
    testAnnotationProcessor &#39;org.projectlombok:lombok&#39;
    //lombok 라이브러리 추가 끝

    testImplementation(&#39;org.springframework.boot:spring-boot-starter-test&#39;) {
        exclude group: &#39;org.junit.vintage&#39;, module: &#39;junit-vintage-engine&#39;
    } 
}
test {
    useJUnitPlatform()
}</code></pre>
<ol>
<li>Preferences(윈도우 File ➢ Settings) ➢ plugin
➢ lombok 검색 설치 실행(재시작)</li>
<li>Preferences ➢ Annotation Processors 검색 ➢ Enable annotation processing 체크 (재시작)</li>
<li>임의의 테스트 클래스를 만들고 @Getter, @Setter 확인</li>
</ol>
<p><strong>조회 빈이 2개 이상 - 문제</strong>
<code>@Autowired</code>는 타입(Type)으로 조회한다.</p>
<pre><code class="language-java">@Autowired
private DiscountPolicy discountPolicy</code></pre>
<p>타입으로 조회하기 때문에, 마치 다음 코드와 유사하게 동작한다.(실제로는 더 많은 기능을 제공한다.)
<code>ac.getBean(DiscountPolicy.class)</code></p>
<p>스프링 빈 조회에서 학습했듯이 타입으로 조회하면 선택된 빈이 2개 이상일 때 문제가 발생한다.
<code>DiscountPolicy</code>의 하위 타입인 <code>FixDicountPolicy</code>, <code>RateDiscountPolicy</code> 둘다 스프링 빈으로 선언해보자.</p>
<pre><code class="language-java">@Component
public class FixDiscountPolicy implements DiscountPolicy {}</code></pre>
<pre><code class="language-java">public class RateDiscountPolicy implements DiscountPolicy {}</code></pre>
<p>그리고 이렇게 의존관계 자동 주입을 실행하면</p>
<pre><code class="language-java">@Autowired
private DiscountPolicy discountPolicy</code></pre>
<p><code>NoUniqueBeanDefinitionException</code>오류가 발생한다.</p>
<pre><code>NoUniqueBeanDefinitionException: No qualifying bean of type
  &#39;hello.core.discount.DiscountPolicy&#39; available: expected single matching bean
  but found 2: fixDiscountPolicy,rateDiscountPolicy</code></pre><p>오류 메시지가 친절하게도 하나의 빈을 기대했는데 <code>fixDiscountPolicy</code>, <code>rateDiscountPolicy</code> 2개가 발견되었다고 알려준다.</p>
<p>이때 하위 타입으로 지정할 수도 있지만, 하위 타입으로 지정하는 것은 DIP을 위배하고 유연성이 떨어진다. 그리고 이름만 다르고, 완전히 똑같은 스프링 빈이 2개 있을 때 해결이 안된다.
스프링 빈을 수동 등록해서 문제를 해결해도 되지만, 의존 관계 자동 주입에서 해결하는 여러 방법이 있다.</p>
<h3 id="autowired-필드명-qualifier-primary">@Autowired 필드명, @Qualifier, @Primary</h3>
<p>해결 방법을 하나씩 알아보자.</p>
<p>조회 대상 빈이 2개 이상일 때 해결 방법</p>
<ul>
<li>@Autowired 필드 명 매칭</li>
<li>@Qualifier ➣ @Qualifier끼리 매칭 ➣ 빈 이름 매칭</li>
<li>@Primary 사용</li>
</ul>
<p><strong>@Autowired 필드 명 매칭</strong>
<code>@Autowired</code>는 타입 매칭을 시도하고, 이때 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.</p>
<p><strong>기존 코드</strong></p>
<pre><code class="language-java">@Autowired
private DiscountPolicy discountPolicy;</code></pre>
<p><strong>필드 명을 이름으로 변경</strong></p>
<pre><code class="language-java">@Autowired
private DiscountPolicy rateDiscountPolicy;</code></pre>
<p>필드 명이 <code>rateDiscountPolicy</code>이므로 정상 주입뇐다.
<strong>필드 명 매칭은 먼저 타입 매칭을 시도하고 그 결과에 여러 빈이 있을 대 추가로 동작하는 기능이다.</strong></p>
<p><strong>@Autowired 매칭 정리</strong></p>
<ul>
<li><ol>
<li>타입 매칭</li>
</ol>
</li>
<li><ol start="2">
<li>타입 매칭의 결과가 2개 이상일 때 필드 명, 파라미터 명으로 빈 이름 매칭</li>
</ol>
</li>
</ul>
<p><strong>@Qualifier 사용</strong>
<code>@Qualifier</code>는 추가 구분자를 붙여주는 방법이다. 주입시 추가적인 방법을 제공하는 것이지 빈 이름을 변경하는 것은 아니다.
<strong>빈 등록시 @Qualifier를 붙여 준다.</strong></p>
<pre><code class="language-java">@Component
@Qualifier(&quot;mainDiscountPolicy&quot;)
public class RateDiscountPolicy implements DiscountPolicy{}</code></pre>
<pre><code class="language-java">@Component
@Qualifier(&quot;fixDiscountPolicy&quot;)
public class FixDiscountPolicy implements DiscountPolicy{}</code></pre>
<p><strong>주입시에 @Qualifier를 붙어주고 등록한 이름을 적어준다.</strong>
<strong>생성자 자동 주입 예시</strong></p>
<pre><code class="language-java">@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier(&quot;mainDiscountPolicy&quot;) DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}</code></pre>
<p><strong>수정자 자동 주입 예시</strong></p>
<pre><code class="language-java">@Autowired
public DiscountPolicy setDiscountPolicy(@Qualifier(&quot;mainDisountPolicy&quot;) DiscountPolicy discountPolicy){
    this.discountPolicy = discountPolicy;
}</code></pre>
<p><code>@Qualifier</code>로 주입할 때 <code>@Qualifier(&quot;mainDiscountPolicy&quot;)</code>를 못찾으면 어떻게 될까? 그러면 mainDiscountPolicy라는 이름의 스프링 빈을 추가로 찾는다. 하지만 경험상 <code>@Qualifier</code>는 <code>@Qualifier</code>를 찾는 용도로만 사용하는게 명확하고 좋다.</p>
<p>다음과 같이 직접 빈 등록시에도 @Qualifier를 동일하게 사용할 수 있다.</p>
<pre><code class="language-java">@Bean
@Qualifier(&quot;mainDiscountPolicy&quot;)
public DiscountPolicy discountPolicy(){
    return new ...
}</code></pre>
<p><strong>@Qualifier 정리</strong></p>
<ul>
<li><ol>
<li>@Qualifier끼리 매칭</li>
</ol>
</li>
<li><ol start="2">
<li>빈 이름 매칭</li>
</ol>
</li>
<li><ol start="3">
<li><code>NoSuchBeanDefinitionException</code>예외 발생</li>
</ol>
</li>
</ul>
<p><strong>@Primary 사용</strong>
<code>@Primary</code>는 우선순위를 정하는 방법이다. @Autowired 시에 여러 빈이 매칭되면 <code>@Primary</code>가 우선권을 가진다.</p>
<p><code>rateDiscountPolicy</code>가 우선권을 가지도록 하자.</p>
<pre><code class="language-java">@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}

@Component
public class FixDiscountPolicy implements DiscountPolicy {}</code></pre>
<p><strong>사용코드</strong></p>
<pre><code class="language-java">//생성자
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
    this.memberRepostiroy = membeRepository;
    this.discountPoilcy = discountPolicy;
}

//수정자
@Autowired
public DiscountPolicy setDiscountPolicy(DiscountPolicy discountPolicy){
    this.discountPolicy = discountPolicy;
}    </code></pre>
<p>코드를 실행해보면 문제 없이 <code>@Primary</code>가 잘 동작하는 것을 확인할 수 있다.</p>
<p>여기까지 보면 <code>@Primary</code>와 <code>@Qualifier</code>중에 어떤 것을 사용하면 좋을지 고민이 될 것이다.
<code>@Qualifier</code>의 단점은 주입 받을 때 다음과 같이 모든 코드에 <code>@Qualifier</code>를 붙여주어야 한다는 점이다.</p>
<pre><code class="language-java">@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier(&quot;mainDiscountPolicy&quot;) DiscountPolicy){
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}</code></pre>
<p>반면에 <code>@Primary</code>를 사용하면 이렇게 <code>@Qualifier</code>를 붙일 필요가 없다.</p>
<h3 id="애노테이션-직접-만들기"><strong>애노테이션 직접 만들기</strong></h3>
<p><code>@Qualifier(&quot;mainDiscountPolicy&quot;)</code> 이렇게 문자를 적으면 컴파일시 체크가 안된다. 다음과 같은 애노테이션을 만들어서 문제를 해결할 수 있다.</p>
<pre><code class="language-java">@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier(&quot;mainDiscountPolicy&quot;)
public @interface MainDiscountPolicy{
}</code></pre>
<pre><code class="language-java">@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {}</code></pre>
<pre><code class="language-java">//생성자 자동 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy){
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}

//수정자 자동 주입
public DiscountPolicy setDiscountPolicy(@MainDiscountPolicy DiscountPolicy discountPolicy){
    this.discountPolicy = discountPolicy;
}</code></pre>
<p>애노테이션에서는 상속이라는 개념이 없다. 이렇게 여러 애노테이션을 모아서 사용하는 기능은 스프링이 지원해주는 기능이다. @Qualifier 뿐만 아니라 다른 애노테이션들도 함께 조합해서 사용할 수 있다. 단적으로 @Autowired도 재정의 할 수 있다. 물론 스프링이 제공하는 기능을 뚜렷한 목적 없이 무분별하게 재정의 하는 것은 유지보수에 더 혼란만 가중할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 개체]]></title>
            <link>https://velog.io/@free_mill/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EA%B0%9C%EC%B2%B4</link>
            <guid>https://velog.io/@free_mill/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EA%B0%9C%EC%B2%B4</guid>
            <pubDate>Sun, 09 Apr 2023 05:42:26 GMT</pubDate>
            <description><![CDATA[<h3 id="1-데이터-베이스-개체">1. 데이터 베이스 개체</h3>
<p>데이터베이스에서는 테이블 외에 <strong>인덱스</strong>, <strong>뷰</strong>, <strong>스토어드 프로시저</strong>, 트리거, 함수, 커서 등의 개체가 필요하다.</p>
<h3 id="인덱스"><strong>인덱스</strong></h3>
<p>데이터를 조회할 때 테이블에 데이터가 적다면 결과가 금방나오지만 많아질수록 결과과 나오는 시간이 많이 소요된다. 인덱스는 이런 경우 결과가 나오는 시간을 대폭 줄여줄 수 있다.</p>
<p>인덱스란 책의 목차를 알려주는 &#39;찾아보기&#39;와 비슷한 개념이다.</p>
<p><strong>인덱스 실습</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/dd9f740b-d2e1-4055-8edb-823447d5b817/image.png" alt="">
위와같은 테이블 구조에서 아래와 같은 쿼리를 수행하면</p>
<pre><code class="language-sql">SELECT * FROM member WHERE member_name = &#39;아이유&#39;;</code></pre>
<p><img src="https://velog.velcdn.com/images/free_mill/post/b67465f0-04e1-44ee-9d20-0f176bd6ad6c/image.png" alt="">
결과가 잘 나온다.</p>
<p>여기서
<img src="https://velog.velcdn.com/images/free_mill/post/b0b868d8-67c0-417f-9858-856448e215b8/image.png" alt="">
이렇게 [Excution Plan(실행계획)] 탭을 클릭하면 Full Table Scan이라고 나온다. 이것을 해석하면 <strong>전체 테이블 검색</strong> 정도가 된다. 이러한 방법은 데이터가 매우 큰 경우 매우 오랜 시간이 소요된다.</p>
<p><strong>인덱스로 찾기</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/19989eab-19a6-45d1-9876-08bff8b08ed7/image.png" alt="">
<img src="https://velog.velcdn.com/images/free_mill/post/cf7f2b27-c6f0-46a1-bfa5-8b25720da7e1/image.png" alt="">
인덱스를 만들어 준다. SQL 마지막의 ON member(member_name)의 의미는 member 테이블의 member_name열에 인덱스를 지정하라는 의미이다.</p>
<p><img src="https://velog.velcdn.com/images/free_mill/post/c6bc3a66-7981-4337-84e4-0e5fca98db8e/image.png" alt="">
다시 쿼리문을 수행하고 [Excution Plan(실행계획)]을 확인하면
<img src="https://velog.velcdn.com/images/free_mill/post/462dfcdc-c6e3-46cf-967b-736b11d4d1d6/image.png" alt="">
<strong>Non-Unique Key Lookup</strong>이라고 나온다. 자세한 사항은 추구 더 알아보도록 한다.</p>
<p>인덱스에서 한 가지 더 주의해야 할 점은 인덱스 생성 여부에 따라 결과가 달라지지는 않는다는것 입니다. 즉 책의 내용을 찾을 때 찾아보기가 있으면 시간을 단축하는 효과가 있지만, 책의 찾아보기가 없어도 책의 첫 페이지부터 찾아야 하기 때문에 시간이 오래 걸릴 뿐 어차피 동일하게 찾을 수 있습니다.</p>
<h3 id="뷰"><strong>뷰</strong></h3>
<p>뷰는 테이블과 상당히 동일한 성격의 데이터베이스 개체입니다. 뷰를 활용하면 보안도 강화하고, SQL문도 간단하게 사용할 수 있습니다.</p>
<p><strong>뷰 개념 이해하기</strong>
뷰를 한마디로 정의하면 &#39;가상의 테이블&#39;이라고 할 수 있다.
일반 사용자 입장에서는 테이블과 뷰를 구분할 수 없다. 즉, 일반 사용자는 테이블과 동일하게 뷰를 취급하면 된다. 다만 뷰는 실제 데이터를 가지고 있지 않으며, 진짜 테이블에 링크된 개념이라고 생각하면 된다.</p>
<p>뷰는 윈도우즈 운영 체제의 &#39;바로 가기 아이콘&#39;과 비슷한 개념.</p>
<p>뷰는 바로가기와 비슷한 개념으로 실체는 없으며 테이블과 연결되어 있는 것뿐입니다.</p>
<p>그렇다면 뷰의 실체는 무엇일까? 뷰의 실체는 바로 SELECT문이다.</p>
<p><strong>뷰 실습</strong></p>
<pre><code class="language-sql">CREATE  VIEW member_view
AS
    SELECT * FROM member;</code></pre>
<p><img src="https://velog.velcdn.com/images/free_mill/post/9f0e7f14-5b22-477b-90ad-80b057200023/image.png" alt=""></p>
<p>회원 뷰에 접근해보자,</p>
<pre><code class="language-sql">select * from member_view;</code></pre>
<p><img src="https://velog.velcdn.com/images/free_mill/post/5f4147c1-2763-4f75-8366-bbf3df9f0637/image.png" alt="">
결과는 동일하다. 그렇다면 뷰를 사용하는 이유는 무엇일까?</p>
<p>그 이유는 크게 2가지이다.</p>
<ul>
<li>보안에 도움이 된다.</li>
<li>긴 SQL 문을 간략하게 만들 수 있다.</li>
</ul>
<h3 id="스토어드-프로시저"><strong>스토어드 프로시저</strong></h3>
<p>스토어드 프로시저를 통해 SQL 안에서도 일반 프로그래밍 언어처럼 코딩을 할 수 있다. 비록 일반 프로그래밍보다는 좀 불편하지만, 프로그래밍 로직을 작성할 수 있어서 때론 유용하게 사용된다.</p>
<p><strong>스토어드 프로시저 개념 이해하기</strong>
_스토어드 프로시저_란 MySQL에서 제공하는 프로그래밍 기능으로 여러개의 SQL문을 하나로 묶어서 편리하게 사용할 수 있다.</p>
<p><strong>스토어드 프로시저 실습하기</strong></p>
<pre><code class="language-sql">SELECT * FROM member WHERE member_name=&quot;나훈아&quot;;
SELECT * FROM product WHERE product_name = &#39;삼각김밥&#39;;</code></pre>
<p>두개의 sql문을 한번에 실행하는 프로시저를 만들어보자.</p>
<pre><code class="language-sql">DELIMITER //    -- DELIMITER// ~ DELIMITER;는 스토어드 프로시저를 묶어주는 약속
CREATE PROCEDURE myProc() -- myProc()은 프로시저의 이름이다.
BEGIN
    SELECT * FROM member WHERE member_name = &#39;나훈아&#39;;
    SELECT * FROM product WHERE product_name = &#39;삼각김밥&#39;;
END //
DELIMITER;</code></pre>
<p>프로시저 실행</p>
<pre><code class="language-sql">CALL myProc();</code></pre>
<p><img src="https://velog.velcdn.com/images/free_mill/post/a8fa4b8e-9f94-44b3-8bd1-7084a46d42dd/image.png" alt="">
result 탭이 2개 생성되고 정상 작동되는 것을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[컴포넌트 스캔]]></title>
            <link>https://velog.io/@free_mill/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%8A%A4%EC%BA%94</link>
            <guid>https://velog.io/@free_mill/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%8A%A4%EC%BA%94</guid>
            <pubDate>Sat, 08 Apr 2023 10:17:41 GMT</pubDate>
            <description><![CDATA[<p>출처. inflearn, 김영한 스프링 핵심 원리 - 기본편</p>
<h3 id="컴포넌트-스캔과-의존관계-자동-주입-시작하기">컴포넌트 스캔과 의존관계 자동 주입 시작하기</h3>
<ul>
<li>지금까지 스프링 빈을 등록할 때는 자바 코드의 @Bean이나 XML의 &lt;bean&gt; 등을 통해서 설정 정보에 직접 등록할 스프링 빈을 나열했다.</li>
<li>예제에서는 몇개가 안되었지만, 이렇게 등록해야 할 스프링 빈이 수십, 수백개가 되면 일일이 등록하기도 귀찮고, 성정 정보도 커지고, 누락하는 문제도 발생한다. </li>
<li>그래서 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공한다.</li>
<li>또 의존관계도 자동으로 주입하는 <code>@Autowired</code>라는 기능도 제공한다.</li>
</ul>
<p>코드로 컴포넌트 스캔과 의존과계 자동 주입을 알아보자.</p>
<p>먼저 기존 AppConfig.java는 과거 코드와 테스트를 유지하기 위해 남겨두고, 새로운 AutoAppConfig.java를 만들자.</p>
<p><strong>AutoAppConfig.java</strong></p>
<pre><code class="language-java">@Configuration
@ComponentScan(excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Configuration.class))
public class AutoAppConfig{

}</code></pre>
<ul>
<li>컴포넌트 스캔을 사용하려면 먼저 <code>@ComponentScan</code>을 설정 정보에 붙여주면 된다.</li>
<li>기존의 AppConfig와는 다르게 @Bean으로 등록한 클래스가 하나도 없다!</li>
</ul>
<blockquote>
<p><strong>참고</strong>: 컴포넌트 스캔을 사용하면 <code>@Configuration</code>이 붙은 설정 정보도 자동으로 등록되기 때문에, AppConfig, TestConfig 등 앞서 만들어두었던 설정 정보도 함께 등록되고, 실행되어 버린다. 그래서 <code>excludeFilters</code>을 이용해서 설정정보는 컴포넌트 스캔 대상에서 제외했다. 보통 설정 정보를 컴포넌트 스캔 대상에서 제외하지는 않지만, 기존 예제 코드를 최대한 남기고 유지하기 위해서 이 방법을 선택했다.</p>
</blockquote>
<p>컴포넌트 스캔은 이름 그대로 <code>@Component</code>애노테이션이 붙은 클래스를 스캔해서 스프링 빈으로 등록한다. <code>@Component</code>를 붙어주자.</p>
<blockquote>
<p>참고: <code>@Configuration</code>이 컴포넌트 스캔의 대상이 된 이유도 <code>@Configuration</code> 소스코드를 열어보면 <code>@Component</code> 애노테이션이 붙어있기 때문이다.</p>
</blockquote>
<p>이제 각 클래스가 컴포넌트 스캔의 대상이 되도록 <code>@Component</code> 애노테이션을 붙어주자.</p>
<p><strong>MemoryMemberRepository @Component 추가</strong></p>
<pre><code class="language-java">@Component
public class MemoryMemberRepository implements MemberRepository{}</code></pre>
<p><strong>RateDiscountPolicy @Component 추가</strong></p>
<pre><code class="language-java">@Component
public class RateDiscountPolicy implements DiscountPolicy {}</code></pre>
<p><strong>MemberServiceImpl @Component, @Autowired 추가</strong></p>
<pre><code class="language-java">@Component
public class MemberServiceImpl implements MemberService {
    private final MemberRepository MemberRepository;

     @Autowired
      public MemberServiceImpl(MemberRepository memberRepository){
          this.memberRepository = memberRepository;
  }
}</code></pre>
<ul>
<li>이전에 AppConfig에서는 <code>@Bean</code>으로 직접 설정 정보를 작성했고, 의존관계도 직접 명시했다. 이제는 이런 설정 정보 자체가 없기 때문에, 의존관계 주입도 이 클래스 안에서 해결해야 한다.</li>
<li><code>@Autowired</code>는 의존 관계를 자동으로 주입해준다. 자세한 룰은 조금 뒤에 설명하겠다.</li>
</ul>
<p><strong>OrderServiceImpl @Component, @Autowired 추가</strong></p>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderSercice{
    private final MemberRepository memberRepositoy;
      private final DiscountPolicy discountPolicy;

     @Autowired 
      public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy){
         this.memberRepository = memberRepositoty;
          this.discountPolicy = discountPolicy;
    }
}</code></pre>
<p><strong>AutoAppConfigTest.java</strong></p>
<pre><code class="language-java">public class AutoAppConfigTest{

  @Test
  void basicScan(){
      ApplicationContext ac = new AnnotationConfigAppilcationContext(AutoAppConfig.class);

    MemberService memberService = ac.getBean(MemberService.class);
    assertThat(memberService).isInstanceOf(MemberService.class);

  }
}</code></pre>
<ul>
<li><code>AnntationConfigApplicationContext</code> 를 사용하는 것은 기존과 동일한다.</li>
<li>설정 정보로 <code>AutoAppConfig</code> 클래스를 넘겨준다.</li>
<li>실행해보면 기존과 같이 잘 동작하는 것을 확인할 수 있다.</li>
</ul>
<p>로그를 잘 보면 컴포넌트 스캔이 잘 동작하는 것을 확인할 수 있다.</p>
<pre><code class="language-java">ClassPathBeanDefinitionScanner - identified canditate component class:
・・ RateDiscountPolicy.class
・・ MemberServiceImpl.class
・・ MemoryMemberRepository.class  
・・ OrderServiceImpl.class</code></pre>
<p>컴포넌트 스캔과 자동 의존관계 주입이 어떻게 동작하는지 그림으로 알아보자.</p>
<p><strong>1.ComponentScan</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/7ab10d6b-f3e2-45b2-ae56-24f3364d44c9/image.png" alt=""></p>
<ul>
<li><code>@ComponentScan</code>은 <code>@Component</code>가 붙은 모든 클래스를 스프링 빈으로 등록한다.</li>
<li>이떄 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용한다.<ul>
<li><strong>빈 이름 기본 전략</strong> : MemberServiceImpl 클래스 ➣ memberServiceImpl</li>
<li><strong>빈 이름 직접 지정</strong> : 만약 스프링 빈의 이름을 직접 지정하고 싶으면 <code>@Component(&quot;memberService2&quot;)</code> 이런식으로 이름을 부여하면 된다.</li>
</ul>
</li>
</ul>
<p><strong>2. @Autowired 의존관계 자동 주입</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/49d3f949-115d-4bf3-a009-37eb4408f382/image.png" alt=""></p>
<ul>
<li>생성자에 <code>@Autowired</code>를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다.</li>
<li>이때 기본 조회 전략은 타입이 같은 빈을 찾아서 주입한다.<ul>
<li><code>getBean(MemberRepository.class)</code>와 동일하다고 이해하면 된다.</li>
<li>더 자세한 내용은 뒤에서 설명한다.</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/free_mill/post/d9df0199-c23a-448e-9c8e-eeffe735ed23/image.png" alt=""></p>
<ul>
<li>생성자에 파라미터가 많아도 다 찾아서 자동으로 주입한다.</li>
</ul>
<h3 id="탐색-위치와-기본-스캔-대상">탐색 위치와 기본 스캔 대상</h3>
<p><strong>탐색할 패키지의 시작 위치 지정</strong>
모든 자바 클래스를 다 컴포넌트 스캔하면 시간이 오래 걸린다. 그래서 꼭 필요한 위치부터 탐색하도록 시작 위치를 지정할 수 있다.</p>
<pre><code class="language-java">@ComponentScan{
    basePackages = &quot;hello.core&quot;,
}</code></pre>
<ul>
<li><code>basePackages</code> 탐색할 패키지의 시작 위치를 지정한다. 이 패키지를 포함해서 하위 패키지를 모두 탐색한다.<ul>
<li><code>basePackagesg = {&quot;hello.core&quot;, &quot;hello.service&quot;}</code> 이렇게 여러 시작 위치를 지정할 수도 있다.</li>
</ul>
</li>
<li><code>basePackageClasses</code>: 지정한 클래스의 패키지를 탐색 시작 위치로 지정한다.</li>
<li>만약 지정하지 않으면 <code>@ComponentScan</code>이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.</li>
</ul>
<p><strong>권장하는 방법</strong>
개인적으로 즐겨 사용하는 방법은 패키지 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것이다. 최근 스프링 부트도 이 방법을 기본으로 제공한다.</p>
<p>예를 들어서 프로젝트가 다음과 같이 구조가 되어 있으면</p>
<ul>
<li><code>com.hello</code></li>
<li><code>com.hello.service</code></li>
<li><code>com.hello.repository</code></li>
</ul>
<p><code>com.hello</code> ▻ 프로젝트 시작 루트, 여기에 AppConfig같은 메인 설정 정보를 두고, @ComponentScan 애노테이션을 붙이고, <code>basePackages</code> 지정은 생략한다.</p>
<p>이렇게 하면 <code>com.hello</code>를 포함한 하위는 모두 자동으로 컴포넌트 스캔의 대상이 된다. 그리고 프로젝트 메인 설정 정보는 프로젝트를 대표하는 정보이기 때문에 프로젝트 시작 루트 위치에 두는 것이 좋다.
참고로 스프링 부트를 사용하면 스프링 부트의 대표 시작 정보인 <code>@SpringBootApplication</code>를 이 프로젝트 시작 루트 위치에 두는 것이 관례이다. (그리고 이 설정안에 바로 <code>@ComponentScan</code>이 들어있다!)</p>
<p><strong>컴포넌트 스캔 기본 대상</strong>
컴포넌트 스캔은 <code>@Component</code>뿐만 아니라 다음과 내용도 추가로 대상에 포함한다.</p>
<ul>
<li><code>@Component</code>: 컴포넌트 스캔에서 사용</li>
<li><code>@Controller</code>: 스프링 MVC 컨토를러에서 사용</li>
<li><code>@Service</code>: 스프링 비즈니스 로직에서 사용</li>
<li><code>@Repository</code>: 스프링 데이터 접근 계층에서 사용</li>
<li><code>@Configuration</code>: 스프링 설정 정보에서 사용</li>
</ul>
<p>해당 클래스의 소스 코드를 보면 <code>@Component</code>를 포함하고 있는 것을 알 수 있다.</p>
<pre><code class="language-java">  @Component
  public @interface Controller {
  }

  @Component
  public @interface Service {
  }

  @Component
  public @interface Configuration {
  }</code></pre>
<blockquote>
<p>참고: 사실 애노테이션에는 상속관계라는 것이 없다. 그래서 이렇게 애노테이션이 특정 애노테이션을 들고 있는 것을 인식할 수 있는 것은 자바 언어가 지원하는 기능은 아니고, 스프링이 지원하는 기능이다.</p>
</blockquote>
<p>컴포넌트 스캔의 용도 뿐만 아니라 다음 애노테이션이 있으면 스프링은 부가 기능을 수행한다.</p>
<ul>
<li><code>@Controller</code>: 스프링 MVC 컨트롤러로 인식</li>
<li><code>@Repository</code>: 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다.</li>
<li><code>@Configuration</code>: 앞서 보았듯이 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다.</li>
<li><code>@Service</code>: 사실 <code>@Service</code>는 특별할 처리를 하지 않는다. 대신 개발자들이 핵심 비즈니스 로직이 여기에 있겠구나라고 비즈니스 계층을 인식하는데 도움이 된다.</li>
</ul>
<blockquote>
<p>참고: <code>useDefaultFilters</code>옵션은 기본으로 켜져있는데, 이 옵션을 끄면 기본 스캔 대상들이 제외된다. 그냥 이런 옵션이 잏ㅆ구나 알고 넘어가자.</p>
</blockquote>
<h3 id="필터"><strong>필터</strong></h3>
<ul>
<li><code>includeFilters</code>: 컴포넌트 스캔 대상을 추가로 지정한다.</li>
<li><code>excludeFilters</code>: 컴포넌트 스캔에서 제외할 대상을 지정한다.</li>
</ul>
<p>빠르게 예제로 확인해보자.
<strong>모든 코드는 테스트 코드에 추가</strong></p>
<p><strong>컴포넌트 스캔 대상에 추가할 애노테이션</strong></p>
<pre><code class="language-java">@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponet{

}</code></pre>
<p><strong>컴포넌트 스캔 대상에서 제외할 애노테이션</strong></p>
<pre><code class="language-java">@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent{
}</code></pre>
<p><strong>컴포넌트 스캔 대상에 추가할 클래스</strong></p>
<pre><code class="language-java">@MyIncludeComponent
public class BeanA{
}</code></pre>
<ul>
<li><code>@MyIncludeComponent</code> 적용</li>
</ul>
<p><strong>설정 정보와 전체 테스트 코드</strong></p>
<pre><code class="language-java">public class ComponentFilterAppConfigTest{

    @Test
    void filterScan(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);

        BeanA beanA = ac.getBean(&quot;beanA&quot;, BeanA.class);
        assertThat(beanA).isNotNull();

        Assertions.assertThrows(
                NoSuchBeanDefinitionException.class,
                () -&gt; ac.getBean(&quot;beanB&quot;, BeanB.class);
    }

    @Configuration
    @ComponentScan(
            includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
            excludeFilters = @Filter(type = MyExcludeComponent.class)
            )
    static class ComponentFilterAppConfig{
    }
}</code></pre>
<pre><code class="language-java">@ComponentScan(
        includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
        excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
        )</code></pre>
<ul>
<li><code>includeFilters</code>에 <code>MyIncludeComponent</code> 애노테이션을 추가해서 BeanA가 스프링 빈에 등록된다.</li>
<li><code>excludeFilters</code>에 <code>MyExcludeComponent</code> 애노테이션을 추가해서 BeanB    는 스프링 빈에 등록되지 않는다.</li>
</ul>
<p><strong>FilterType 옵션</strong>
FilterType은 5가지 옵션이 있다.</p>
<ul>
<li>ANNOTATION: 기본값, 애노테이션을 인식해서 동작한다.<ul>
<li>ex) <code>org.example.SomeAnnotation</code></li>
</ul>
</li>
<li>ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작한다.<ul>
<li>ex) <code>org.example.SomeClass</code></li>
</ul>
</li>
<li>ASPECTJ: AsepectJ 패턴 사용<ul>
<li>ex) <code>org.\example\.Default.*</code></li>
</ul>
</li>
<li>CUSTOM: <code>TypeFilter</code> 이라는 인터페이스를 구현해서 처리<ul>
<li>ex) <code>org.example.MyTypeFilter</code></li>
</ul>
</li>
</ul>
<p>예를 들어서 BeanA도 빼고 싶으면 다음과 같이 추가하면 된다.</p>
<pre><code class="language-java">@ComponentScan(
    includeFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
        },
    excludeFilters = {
        @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = BeanA.class)
        }
    }</code></pre>
<blockquote>
<p>참고: <code>@Component</code>면 충분하기 때문에, <code>includeFilters</code>를 사용할 일은 거의 없다. <code>excludeFilters</code>는 여러가지 이유로 간혹 사용할 때가 있지만 많지는 않다.
특히 최근 스프링 부트는 컴포넌트 스캔을 기본으로 제공하는데, 개인적으로는 옵션을 변경하면서 사용하기보다는 스프링의 기본 설정에 최대한 맞추어 사용하는 것을 권장하고, 선호하는 편이다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[싱글톤 컨테이너]]></title>
            <link>https://velog.io/@free_mill/%EC%8B%B1%EA%B8%80%ED%86%A4-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88</link>
            <guid>https://velog.io/@free_mill/%EC%8B%B1%EA%B8%80%ED%86%A4-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88</guid>
            <pubDate>Sun, 02 Apr 2023 05:31:14 GMT</pubDate>
            <description><![CDATA[<p><em>출처. inflearn, 김영한 스프링 핵심 원리 - 기본편</em></p>
<h3 id="웹-애플리케이션과-싱글톤"><strong>웹 애플리케이션과 싱글톤</strong></h3>
<ul>
<li>스프링은 태생이 기업용 온라인 서비스 기술을 지원하기 위해 탄생했다.</li>
<li>대부분의 스프링 애플리케이션은 웹 애플리케이션이다.</li>
<li>웹 애플리케이션은 보통 여러 고객이 동시에 요청을 한다.
<img src="https://velog.velcdn.com/images/free_mill/post/7cce064f-8f0c-4762-b928-6c05e4d63973/image.png" alt=""></li>
</ul>
<p><strong>스프링 없는 순수한 DI 컨테이너 테스트</strong>`</p>
<pre><code class="language-java">public class SingletonTest {
    @Test
    @DisplayName(&quot;스프링 없는 순수한 DI 컨테이너&quot;)
    void pureContainer(){

        AppConfig appConfig = new AppConfig();

        //1.조회: 호출할 때 마다 객체를 생성
        MemberService memberService1 = appConfig.memberService();

        //2.조회: 호출할 때 마다 객체를 생성
        MemberService memberService2 = appConfig.memberService();

        //참조값이 다른 것을 확인
        System.out.println(&quot;memberService1 = &quot; + memberService1);
        System.out.println(&quot;memberService2 = &quot; + memberService2);

        //참조값이 다른 것을 확인
        assrertThat(memberService1).isNoSameAs(memberService2);
    }
}</code></pre>
<ul>
<li>우리가 만들었던 스프링 없는 순수한 DI 컨테이너인 AppConfig는 요청을 할 때마다 객체를 새로 생성한다.</li>
<li>고객 트래픽인 100이 나오면 초당 100개 객체가 생성되고 소멸된다! -&gt; 메모리 낭비가 심하다.</li>
<li>해결방안은 해당 객체가 딱 1개만 생성되고 공유하도록 설계하면 된다. -&gt; 싱글톤 패턴</li>
</ul>
<h3 id="싱글톤-패턴"><strong>싱글톤 패턴</strong></h3>
<ul>
<li>클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다.</li>
<li>그래서 객체 인스턴스를 2개 이상 생성하지 못하도록 막아야 한다.<ul>
<li>private 생성자를 사용해서 외부에서 임의로 new 키워드를 사용하지 못하도록 막아야 한다.</li>
</ul>
</li>
</ul>
<p>싱글톤 패턴을 적용한 예제 코드를 보자. <strong>main이 아닌 test 위치에 생성하자.</strong></p>
<pre><code class="language-java">public class SingletonService{
    //1. static 영역에 객체를 딱 1개만 생성해둔다.
    private static final SingletonService instance = new SingletonService();

    //2.public으로 열어서 객체 인스턴스가 필요하면 이 static메서드를 통해서만 조회하도록 허용한다.
    public static SingletonService getInstance(){
        return instance;
    }

    private SingletonService(){
    }

    public void logic(){
        System.out.println(&quot;싱글통 객체 로직 호출&quot;);
    }
}</code></pre>
<ul>
<li><ol>
<li>static 영엑에 객체 instance를 미리 하나 생성해서 올려둔다.</li>
</ol>
</li>
<li><ol start="2">
<li>이 객체 인스턴스가 필요하면 오직 <code>getInstance()</code>메서드를 통해서만 조회할 수 있다. 이 메서드를 호출하면 항상 같은 인스턴스를 반환한다.</li>
</ol>
</li>
<li><ol start="3">
<li>딱 1개의 객체 인스턴스만 존재하므로, 생성자를 private으로 막아서 혹시라도 외부에서 new 키워드로 객체 인스턴스가 생성되는 것을 막는다.</li>
</ol>
</li>
</ul>
<p>싱글톤 패턴을 사용하는 테스트 코드를 보자.</p>
<pre><code class="language-java">@Test
@DisplayName(&quot;싱글톤 패턴을 적용한 객체 사용&quot;)
public void singletonServiceTest(){

    //private으로 생성자를 막아두었다. 컴파일 오류가 발생한다.
    //new SingletonService();

    //1.조회: 호출할 때 마다 같은 객체를 반환
    SingletonService singletonService1 = SingletonService.getInstance();

    //2.조회: 호출할 때 마다 같은 객체를 반환
    SingletonService singletonService2 = SingletonService.getInstance();

    //참조값이 같은 것을 확인
    System.out.println(&quot;singletonService1 = &quot; + singletonService1);
    System.out.ptintln(&quot;singletonService2 = &quot; + singletonService2);


    //singletonService1 = singeltonService2
    assertThat(singletonService1).isSameAs(singletonService2);

    singletonService.logic();
}</code></pre>
<ul>
<li>private으로 new 키워드를 막아두었다.</li>
<li>호출할 때 마다 같은 객체 인스턴스를 반환하는 것을 확인할 수 있다.</li>
</ul>
<blockquote>
<p>참고: 싱글톤 패턴을 구현하는 방법은 여러가지가 있다. 여기서는 객체를 미리 생성해두는 가장 단순하고 안전한 방법을 선택했다.</p>
</blockquote>
<p>싱글톤 패턴을 적용하면 고객의 요청이 올 때 마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유해서 효율적으로 사용할 수 있다. 하지만 싱글톤 패턴은 다음과 같은 수 많은 문제점들을 가지고 있다.</p>
<p><strong>싱글톤 패턴 문제점</strong></p>
<ul>
<li>싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.</li>
<li>의존관계상 클라이언트가 구체 클래스에 의존한다. -&gt; DIP를 위반한다. </li>
<li>클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다. </li>
<li>테스트하기 어렵다.</li>
<li>내부 속성을 변경하거나 초기화 하기 어렵다. </li>
<li>private 생성자로 자식 클래스를 만들기 어렵다. </li>
<li>결론적으로 유연성이 떨어진다.</li>
<li>안티패턴으로 불리기도 한다.</li>
</ul>
<h3 id="싱글톤-컨테이너">싱글톤 컨테이너</h3>
<p>스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤(1개만 생성)으로 관리한다.
지금까지 우리가 학습한 스프링 빈이 바로 싱글톤으로 관리되는 빈이다.</p>
<p><strong>싱글톤 컨테이너</strong></p>
<ul>
<li>스프링 컨테이너는 싱글턴 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다.<ul>
<li>이전에 설명한 컨테이너 생성 과정을 자세히 보자. 컨테이너는 객체를 하나만 생성해서 관리한다.</li>
</ul>
</li>
<li>스프링 컨테이너는 싱글톤 컨테이너 역할을 한다. 이렇게 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라 한다.</li>
<li>스프링 컨테이너의 이런 기능 덕분에 싱글턴 패턴의 모든 단점을 해결하면서 객체를 싱글톤으로 유지할 수 있다.<ul>
<li>싱글톤 패턴을 위한 지저분한 코드가 들어가지 않아도 된다.</li>
<li>DIP, OCP, 테스트, private 생성자로 부터 자유롭게 싱글톤을 사용할 수 있다.</li>
</ul>
</li>
</ul>
<p><strong>스프링 컨테이너를 사용하는 테스트 코드</strong></p>
<pre><code class="language-java">@Test
@DisplayName(&quot;스프링 컨테이너와 싱글톤&quot;)
void springContainer(){
    ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    //1.조회 : 호출할 때 마다 같은 객체를 반환
    MemberService memberService1 = ac.getBean(&quot;memberService&quot;, MemberService.class);

    //2.조회 : 호출할 때 마다 같은 객체를 반환
    MemberService memberService2 = ac.getBean(&quot;memberService&quot;, MemberService.class);

    //참조값이 같은 것을 확인
    System.out.println(&quot;memberService1 = &quot; +memberService1);
    System.out.println(&quot;memberService2 = &quot; +memberService2);

    //memberService1 == memberService2
    assertThat(memberService1).isSameAs(memberService2);
}</code></pre>
<br/>

<p><strong>싱글톤 컨테이너 적용 후</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/c536cb5d-bdb2-4d73-a51f-85d58652e645/image.png" alt=""></p>
<ul>
<li>스프링 컨테이너 덕분에 고객의 요청이 올 때 마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유해서 효율적으로 재사용할 수 있다.</li>
</ul>
<blockquote>
<p>참고: 스프링의 기본 빈 등록 방식은 싱글톤이지만, 싱글톤 방식만 지원하는 것은 아니다. 요청할 때 마다 새로운 객체를 생성해서 반환하는 기능도 제공한다. 자세한 내용은 뒤에 빈 스코프에서 설명하겠다.</p>
</blockquote>
<h3 id="싱글톤-방식의-주의점"><strong>싱글톤 방식의 주의점</strong></h3>
<ul>
<li>싱글톤 패턴이든, 스프링 같은 싱글톤 컨테이너를 사용하든, 객체 인스턴스를 하나만 생성해서 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지(stateful)하게 설계하면 안된다.</li>
<li>무상태(stateless)로 설계해야 한다!<ul>
<li>특정 클라이언트에 의존적인 필드가 있으면 안된다.</li>
<li>특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다!</li>
<li>가급적 읽기만 가능해야 한다.</li>
<li>필드 대신에 자바에서 공유되지 않는, 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.</li>
</ul>
</li>
<li>스프링 빈의 필드에 공유 값을 설정하면 정말 큰 장애가 발생할 수 있다!!!</li>
</ul>
<p><strong>상태를 유지할 경우 발생하는 문제점 예시</strong></p>
<pre><code class="language-java">public class StatefulService {
    private int price; //상태를 유지하는 필드

    pubilc void order(String name, int price) {
    System.out.println(&quot;name = &quot; + &quot;price = &quot; + price);
    this.price = price;
}

    public int getPrice(){
        return price;
    }
}</code></pre>
<p><strong>상태를 유지할 경우 발생하는 문제점 예시</strong></p>
<pre><code class="language-java">public class StatefulServiceTest {
    @Test
    void statefulServiceSingleton(){
        Application ac = new AnnotationConfigApplicationContext(TestConfig.class);
        StatefulService statefulService1 = ac.getBean(&quot;statefulService&quot;, StatefulService.class);
        StatefulService statefulService2 = ac.getBean(&quot;statefulService&quot;, StatefulService.class);

        //ThreadA: A사용자 1000원 주문
        statefulService1.order(&quot;userA&quot;, 10000);
        //ThreadB: B사용자 2000원 주문
        statefulService2.order(&quot;userB&quot;, 20000);

        //ThreadA: A사용자 10000원 주문
        statefulService1.order(&quot;userA&quot;, 10000);
        //ThreadB: B사용자 20000원 주문
        statefulService2.order(&quot;userB&quot;, 20000);

        //ThreadA : 사용자A 10000원 주문
        int price = statefulService1.getPrice();
        //THreadA : 사용자A는 10000원을 기대했지만, 기대와 다르게 20000원 출력
        System.out.println(&quot;price = &quot; + price);

        Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
    }

    static class TestConfig {
        @Bean
        public StatefulService statfulService(){
            return new StatefulService();
        }
    }
}</code></pre>
<ul>
<li>최대한 단순히 설명하기 위해, 실제 쓰레드는 사용하지 않았다.</li>
<li>ThreadA가 사용자A 코드를 호출하고 ThreadB가 사용자B 코드를 호출한다 가정하자.</li>
<li><code>StatefulService</code>의 <code>price</code>필드는 공유되는 필드인데, 특정 클라이언트가 값을 변경한다.</li>
<li>사용자 A의 주문금액은 10000원이 되어야 하는데, 20000원이라는 결과가 나왔다.</li>
<li>실무에서 이런 경우를 종종 보는데, 이로인해 정말 해결하기 어려운 큰 문제들이 터진다.</li>
<li>진짜 공유필드는 조심해야 한다. 스프링 빈은 항상 무상태(stateless)로 설계하자.</li>
</ul>
<h3 id="configuration과-싱글톤"><strong>Configuration과 싱글톤</strong></h3>
<p>그런데 이상한점이 있다. 다음 AppConfig 코드를 보자.</p>
<pre><code class="language-java">@Configuration
public class AppConfig{
    @Bean
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }

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

    @Bean
    public MemberRepository memberRespotiroy(){
        return new MemoryMemberRepository();
    }
}</code></pre>
<ul>
<li>memberRepository 빈을 만드는 코드를 보면 <code>memberRepository()</code>를 호출한다.<ul>
<li>이 메서드를 호출하면 <code>new MemoryMemberRepository()</code>를 호출한다.</li>
</ul>
</li>
<li>orderService 빈을 만드는 코드도 동일하게 <code>memberRepository()</code>를 호출한다. <ul>
<li>이 메서드를 호출하면 <code>new MemoryMemberRepository()</code>를 호출한다.</li>
</ul>
</li>
</ul>
<p>결과적으로 각각 다르게 2개의 <code>MemoryMemberRepository</code>가 생성되면서 싱글톤이 깨지는 것 처럼 보인다. 스프링 컨테이너는 이 문제를 어떻게 해결할까?</p>
<p>직접 테스트 해보자.</p>
<p><strong>검증 용도의 코드 추가</strong></p>
<pre><code class="language-java">public class MemberServiceImpl implements MemberService {
    private final MemberRepository memberRepository;

    //테스트 용도
    public MemberRepository getMemberRepository(){
        return memberRepository;
    }

 public class OrderServiceImpl implements OrderService{
     private final MemberRepository memberRepositoy;

    //테스트 용도
    public MemberRepository getMemberRepository(){
        return memberRepository;
 }
}</code></pre>
<ul>
<li>테스트를 위해 MemberRepository를 조회할 수 있는 기능을 추가한다. 기능 검증을 위해 잠깐 사용하는것이니 인터페이스에 조회기능까지 추가하지는 말자.</li>
</ul>
<p><strong>테스트 코드</strong></p>
<pre><code class="language-java">public class ConfigurationTest {
    @Test
    void configurationTest(){
        AnnotationConfigApplicationContext(AppConfig.class);
        MemberServiceImpl memberService = ac.getBean(&quot;memberService&quot;, MemberServiceImpl.class);
        OrderServiceImpl orderService = ac.getBean(&quot;orderService&quot;, OrderServiceImpl.class);
        MemberRepository memberRepository = ac.getBean(&quot;memberRepository&quot;, MemberRepository.class);



        //모두 같은 인스턴스를 참고하고 있다.
        System.out.println(&quot;memberService -&gt; memberRepository = &quot; +
  memberService.getMemberRepository());
        System.out.println(&quot;orderService -&gt; memberRepository  = &quot; +
  orderService.getMemberRepository());
        System.out.println(&quot;memberRepository = &quot; + memberRepository);


        //모두 같은 인스턴스를 참고하고 있다.
        assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
        assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
    }
}</code></pre>
<ul>
<li>확인해보면 memberRepository 인스턴스는 모두 같은 인스턴스가 공유되어 사용된다.</li>
<li>AppConfig의 자바 코드를 보면 분명히 각각 2번 <code>new MemoryMemberRepository</code> 호출해서 다른 인스턴스가 생성되어야 하는데?</li>
<li>어떻게 된 일일까? 혹시 두 번 호출이 안되는 것일까? 실험을 통해 알아보자.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 핵심 원리_스프링 컨테이너와 스프링 빈]]></title>
            <link>https://velog.io/@free_mill/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC%EC%8A%A4%ED%94%84%EB%A7%81-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%99%80-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88</link>
            <guid>https://velog.io/@free_mill/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC%EC%8A%A4%ED%94%84%EB%A7%81-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%99%80-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88</guid>
            <pubDate>Sat, 01 Apr 2023 14:25:01 GMT</pubDate>
            <description><![CDATA[<p><em>출처. inflearn, 김영한 스프링 핵심 원리 - 기본편</em></p>
<p><strong>스프링 컨테이너 생성</strong>
스프링 컨테이너가 생성되는 과정을 알아보자.</p>
<pre><code class="language-java">//스프링 컨테이너 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);</code></pre>
<ul>
<li><code>ApplicationContext</code>를 스프링 컨테이너라 한다.</li>
<li><code>ApplicationContext</code>는 인터페이스이다.</li>
<li>스프링 컨테이너는 XML을 기반으로 만들 수 있고, 애노테이션 기반의 자바 설정 클래스로 만들 수 있다.</li>
<li>직전에 <code>AppConfig</code>를 사용했던 방식이 애노테이션 기반의 자바 설정 클래스로 스프링 컨테이너를 만든 것이다.</li>
<li>자바 설정 클래스를 기반으로 스프링 컨테이너(<code>ApplicationContext</code>)를 만들어보자.<ul>
<li><code>new AnnotationConfigApplicationConext(AppConfig.class);</code></li>
<li>이 클래스는 <code>ApplicationContext</code>인터페이스 구현체이다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>참고: 더 정확히는 스프링 컨테이너를 부를 때 <code>BeanFactory</code>, <code>ApplicationContext</code>로 구분해서 이야기한다. 이 부분은 뒤에서 설명하겠다. <code>BeanFactory</code>를 직접 사용하는 경우는 거의 없으므로 일반적으로 <code>ApplicationContext</code>를 스프링 컨테이너라 한다.</p>
</blockquote>
<p><strong>스프링 컨테이너의 생성 과정</strong>
S$
<strong>1.스프링 컨테이너 생성</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/121a71f7-7855-49c2-93ef-5dbf3625e8c2/image.png" alt=""></p>
<ul>
<li><code>new AnnotationConifgApplicationContext(AppConfig.class)</code></li>
<li>스프링 컨테이너를 생성할 때는 구성 정보를 지정해주어야 한다.</li>
<li>여기서는 <code>AppConfig.class</code>를 구성 정보로 지정했다.</li>
</ul>
<p><strong>2. 스프링 빈 등록</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/ead30849-6dd5-4e1f-9360-dab0142c85eb/image.png" alt=""></p>
<ul>
<li>스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 시용해서 스프링 빈을 등록한다.</li>
</ul>
<p><strong>빈 이름</strong></p>
<ul>
<li>빈 이름은 메서드 이름을 사용한다.</li>
<li>빈 이름을 직접 부여할 수도 있다.</li>
<li><code>@Bean(name = &quot;memberService2&quot;)</code></li>
</ul>
<blockquote>
<p><strong>주의: 빈 이름은 항상 다른 이름을 부여</strong>해야 한다.
같은 이름을 부여하면, 다른 빈이 무시되거나, 기존 빈을 덮어버리거나 설정에 따라 오류가 발생한다.</p>
</blockquote>
<p><strong>3. 스프링 빈 의존관계 설정 - 준비</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/734bfb10-f0a9-4783-bf5c-0bacc037c80f/image.png" alt=""></p>
<p><strong>4. 스프링 빈 의존관계 설정 - 완료</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/a0cc144d-0895-41d5-b846-24f39a4bdb9e/image.png" alt=""></p>
<ul>
<li>스프링 컨테이너는 설정 정보를 참고해서 의존관계 주입(DI)한다.</li>
<li>단순히 자바 코드를 호출하는 것 같지만, 차이가 있다. 이 차이는 뒤에 싱글통 컨테이너에서 설명한다.</li>
</ul>
<p><strong>참고</strong>
스프링 빈을 생성하고, 의존관계를 주입하는 단계가 나누어져 있다. 그런데 이렇게 자바 코드로 스프링 빈을 등록하면 생정자를 호출하면서 의존관계 주입도 한번에 처리된다. 여기서는 이해를 돕기 위해 개념적으로 나누어 설명했다. 자세한 내용은 의존관계 자동 주입에서 다시 설명하겠다.</p>
<p><strong>정리</strong>
스프링 컨테이너를 생성하고, 설정(구성) 정보를 참고해서 스프링 빈도 등록하고, 의존관계도 설정했다. 이제 스프링 컨테이너에서 데이터를 조회해보자.</p>
<p><strong>컨테이너에 등록된 모든 빈 조회</strong>
스프링 컨테이너에 실제 스프링 빈들이 잘 등록 되었는지 확인해보자.</p>
<pre><code class="language-java">class ApplicationContextText{

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName(&quot;모든 빈 출력하기&quot;)
    void findAllBean(){
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            Obeject bean = ac.getBean(beanDefinitionName);
            System.out.println(&quot;name=&quot; + beanDefinitionName + &quot; objects=&quot; + bean);
        }
    }

    @Test
    @DisplayName(&quot;애플리케이션 빈 출력하기&quot;)
    void findApplicationBean(){
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for(String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);

            //Role ROLE_APPLICATION: 직접 등록한 애플리케이션 빈
            //Role ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈
            if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
                Object bean = ac.getBean(beanDefinitionName);
                System.out.println(&quot;name=&quot; + beanDefinitionName + &quot; object=&quot; + bean);
}
        }
    }
}</code></pre>
<ul>
<li><p>모든 빈 출력하기</p>
<ul>
<li>실행하면 스프링에 등록된 모든 빈 정보를 출력할 수 있다.</li>
<li><code>ac.getBeanDefinitionNames()</code>: 스프링에 등록된 모든 빈 이름을 조회한다.</li>
<li><code>ac.getBean()</code>: 빈 이름으로 빈 객체(인스턴스)를 조회한다.</li>
</ul>
</li>
<li><p>애플리케이션 빈 출력하기</p>
<ul>
<li>스프링이 내부에서 사용하는 빈은 제외하고, 내가 등록한 빈만 출력해보자.</li>
<li>스프링 내부에서 사용하는 빈은 <code>getRole()</code>로 구분할 수 있다.<ul>
<li><code>Role_APPLICATION</code>: 일반적으로 사용자가 정의하는 빈</li>
<li><code>ROLE_INFRASTRUCTURE</code>: 스프링이 내부에서 사용하는 빈</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>스프링 빈 조회 - 기본</strong>
스프링 컨테이너에서 스프링 빈을 찾는 가장 기본적인 조회 방법</p>
<ul>
<li><code>ac.getBean(빈이름, 타입)</code></li>
<li><code>ac.getBean(타입)</code></li>
<li>조회 대상 스프링 빈이 없으면 예외 발생<ul>
<li><code>NoSuchBeanDefinitionException: No bean named &#39;xxx&#39; available</code></li>
</ul>
</li>
</ul>
<p><strong>예제 코드</strong></p>
<pre><code class="language-java">class ApllicationContextBasicFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName(&quot;빈 이름으로 조회&quot;)
    void findBeanByName(){
        MemberService memberService = ac.getBean(&quot;memberService&quot;, MemberService.class);

        assertThat(memberService).inInstanceOf(MemberServivceImpl.class);
    }

    @Test
       @DisplayName(&quot;이름 없이 타입으로 조회&quot;)
    void findBeanByType(){
        MemberService memberService = ac.getBean(MemberService.class);
        assertThat(memberService).isInstanceOf(MemberService.class);
    }

      @Test
    @DisplayName(&quot;구체 타입으로 조회&quot;)
    void findBeanByName2(){
        MemberServiceImpl memberService = ac.getBean(&quot;memberService&quot;, MemberServiceImpl.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

    @Test
    @DisplayName(&quot;빈 이름으로 조회x&quot;)
    void findByBeanNameX(){
        //ac.getBean(&quot;xxxx&quot;, MemberService.class);
        Assertions.assertThat(NoSuchBeanDefinition.calss, () -&gt; ac.getBean(&quot;xxxx&quot;, MemberService.class));
    }
}</code></pre>
<blockquote>
<p>참고 : 구체 타입으로 조회하면 변경시 유연성이 떨어진다.</p>
</blockquote>
<p><strong>스프링 빈 조회 - 동일한 타입이 둘 이상</strong></p>
<ul>
<li>타입으로 조회시 같은 타입의 스프링 빈이 둘 이상이면 오류가 발생한다. 이떄는 빈 이름을 지정하자.</li>
<li><code>ac.getBeanOfType()</code>을 사용하면 해당 타입의 모든 빈을 조회할 수 있다.</li>
</ul>
<p><strong>예제 코드</strong></p>
<pre><code class="language-java">class ApplicationContextBasicFindTest {
      AnnotationConfigApplicationContext ac = new
  AnnotationConfigApplicationContext(AppConfig.class);


@Test
@DisplayName(&quot;빈 이름으로 조회&quot;)
      void findBeanByName() {
          MemberService memberService = ac.getBean(&quot;memberService&quot;,
  MemberService.class);
          assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
} 

@Test
@DisplayName(&quot;이름 없이 타입만으로 조회&quot;) void findBeanByType() {
          MemberService memberService = ac.getBean(MemberService.class);
          assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
      }


@Test
@DisplayName(&quot;구체 타입으로 조회&quot;)
        void findBeanByName2() {
            MemberServiceImpl memberService = ac.getBean(&quot;memberService&quot;,
    MemberServiceImpl.class);
            assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
} 

@Test
@DisplayName(&quot;빈 이름으로 조회X&quot;) void findBeanByNameX() {
            //ac.getBean(&quot;xxxxx&quot;, MemberService.class);
            Assertions.assertThrows(NoSuchBeanDefinitionException.class, () -&gt;
    ac.getBean(&quot;xxxxx&quot;, MemberService.class));
} 

}</code></pre>
<blockquote>
<p>참고 : 구체 타입으로 조회하면 변경시 유연성이 떨어진다.</p>
</blockquote>
<p><strong>스프링 빈 조회 - 동일한 타입이 둘 이상</strong></p>
<ul>
<li>타입으로 조회시 같은 타입의 빈이 둘 이상이면 오류가 발생한다. 이때는 빈 이름을 지정하자.</li>
<li><code>ac.getBeanOfType()</code>을 사용하면 해당 탈입의 모든 빈을 조회할 수 있다.</li>
</ul>
<p><strong>예제 코드</strong></p>
<pre><code class="language-java">class ApplicationContextSameBeanFindTest {
    AnnotationConfigApplicationContext ac = new
AnnotationConfigApplicationContext(SameBeanConfig.class);

@Test
@DisplayName(&quot;타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다&quot;) void findBeanByTypeDuplicate() {
        //MemberRepository bean = ac.getBean(MemberRepository.class);
        assertThrows(NoUniqueBeanDefinitionException.class, () -&gt;
ac.getBean(MemberRepository.class));
} 


@Test
@DisplayName(&quot;타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다&quot;) void findBeanByName() {
        MemberRepository memberRepository = ac.getBean(&quot;memberRepository1&quot;,
MemberRepository.class);
        assertThat(memberRepository).isInstanceOf(MemberRepository.class);
    }

@Test
@DisplayName(&quot;특정 타입을 모두 조회하기&quot;)
    void findAllBeanByType() {
        Map&lt;String, MemberRepository&gt; beansOfType =
ac.getBeansOfType(MemberRepository.class);
        for (String key : beansOfType.keySet()) {
            System.out.println(&quot;key = &quot; + key + &quot; value = &quot; +
beansOfType.get(key));
        }
        System.out.println(&quot;beansOfType = &quot; + beansOfType);
        assertThat(beansOfType.size()).isEqualTo(2);
        }


 @Configuration
 static class SameBeanConfig {
          @Bean
          public MemberRepository memberRepository1() {
              return new MemoryMemberRepository();
          }
          @Bean
          public MemberRepository memberRepository2() {
              return new MemoryMemberRepository();
          }
    } 

}
</code></pre>
<br/>
<br/>

<p><strong>스프링 빈 조회 - 상속 관계</strong></p>
<ul>
<li>부모 타입으로 조회하면, 자식 타입도 함께 조회한다.</li>
<li>그래서 모든 자바 객체의 최고 부모인 <code>object</code>타입으로 조회하면, 모든 스프링 빈을 조회한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/free_mill/post/2edb7e2a-87ce-495c-9ffe-2c80de145178/image.png" alt=""></p>
<p><strong>예제 코드</strong></p>
<pre><code class="language-java">class ApplicationContextExtendsFindTest {

      AnnotationConfigApplicationContext ac = new
  AnnotationConfigApplicationContext(TestConfig.class);

@Test
@DisplayName(&quot;부모 타입으로 조회시, 자식이 둘 이상 있으면, 중복 오류가 발생한다&quot;) void findBeanByParentTypeDuplicate() {
          //DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
          assertThrows(NoUniqueBeanDefinitionException.class, () -&gt;
  ac.getBean(DiscountPolicy.class));
}


@Test
@DisplayName(&quot;부모 타입으로 조회시, 자식이 둘 이상 있으면, 빈 이름을 지정하면 된다&quot;)
void findBeanByParentTypeBeanName() {
        DiscountPolicy rateDiscountPolicy = ac.getBean(&quot;rateDiscountPolicy&quot;,
DiscountPolicy.class);
        assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
}

@Test
@DisplayName(&quot;특정 하위 타입으로 조회&quot;) void findBeanBySubType() {
        RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
        assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
    }

@Test
@DisplayName(&quot;부모 타입으로 모두 조회하기&quot;)
    void findAllBeanByParentType() {
        Map&lt;String, DiscountPolicy&gt; beansOfType =
ac.getBeansOfType(DiscountPolicy.class);
        assertThat(beansOfType.size()).isEqualTo(2);
        for (String key : beansOfType.keySet()) {
            System.out.println(&quot;key = &quot; + key + &quot; value=&quot; +
beansOfType.get(key));
} }


@Test
@DisplayName(&quot;부모 타입으로 모두 조회하기 - Object&quot;)
    void findAllBeanByObjectType() {
        Map&lt;String, Object&gt; beansOfType = ac.getBeansOfType(Object.class);
        for (String key : beansOfType.keySet()) {
            System.out.println(&quot;key = &quot; + key + &quot; value=&quot; +
beansOfType.get(key));
} }


@Configuration
static class TestConfig {
          @Bean
          public DiscountPolicy rateDiscountPolicy() {
              return new RateDiscountPolicy();
          }
          @Bean
          public DiscountPolicy fixDiscountPolicy() {
              return new FixDiscountPolicy();
          }
    } 
}</code></pre>
<br/>

<p><strong>BeanFactory의 ApplicationContext</strong>
beanFactory와 ApplicationContext에 대해서 알아보자.
<img src="https://velog.velcdn.com/images/free_mill/post/61343a5c-2999-4674-be9a-566f9716d273/image.png" alt=""></p>
<p><strong>BeanFactory</strong></p>
<ul>
<li>스프링 컨테이너의 최상위 인터페이스이다.</li>
<li>스프링 빈을 관리하고 조회하는 역할을 담당한다.</li>
<li><code>getBean()</code>을 제공한다.</li>
<li>지금까지 우리가 사용했던 대부분의 기능은 BeanFactory가 제공하는 기능이다.</li>
</ul>
<p><strong>ApplicationContext</strong></p>
<ul>
<li>BeanFactory 기능을 모두 상속받아서 제공한다.</li>
<li>빈을 관리하고 검색하는 기능은 BeanFacotry가 제공해주는데, 그러면 둘의 차이가 뭘까?</li>
<li>애플리케이션을 개발할 때는 빈을 관리하고 조회하는 기능은 물론이고, 수 많은 부가기능이 필요하다.</li>
</ul>
<p><strong>ApplicationContext가 제공하는 부가기능</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/f01761a9-e196-427a-b73c-01f093f684dc/image.png" alt=""></p>
<ul>
<li>메시지 소스를 활용한 국제화 기능<ul>
<li>예를 들어서 한국에서 들어오면 한국어로, 영어원에서 들어오면 영어로 출력</li>
</ul>
</li>
<li>환경 변수<ul>
<li>로컬, 개발, 운영등을 구분해서 처리</li>
</ul>
</li>
<li>애플리케이션 이벤트<ul>
<li>이벤트를 발행하고 구독하는 모델을 편리하게 지원</li>
</ul>
</li>
<li>편리한 리소스 조회<ul>
<li>파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회</li>
</ul>
</li>
</ul>
<p>정리</p>
<ul>
<li>ApplicationContext는 BeanFactory의 기능을 상속 받는다.</li>
<li>ApplicationContext는 빈 관리 기능 + 편리한 부가 기능을 제공한다.</li>
<li>BeanFactory를 직접 사용할 일은 거의 없다. 부가기능이 포함된 ApplicationContext를 사용한다.</li>
<li>BeanFactory나 ApplicationContext를 스프링 컨테이너라 한다.</li>
</ul>
<br/>

<p><strong>다양한 설정 형식 지원 - 자바 코드, XML</strong></p>
<ul>
<li>스프링 컨테이너는 다양한 형식의 설정 정보를 받아드릴 수 있게 유연하게 설계되어 있다.<ul>
<li>자바 코드, XML, Grovy 등등
<img src="https://velog.velcdn.com/images/free_mill/post/d94fa6fd-ade7-41b1-afe4-1105911c1107/image.png" alt=""></li>
</ul>
</li>
</ul>
<p><strong>애노테이션 기반 자바 코드 설정 사용</strong></p>
<ul>
<li>지금까지 했던 것이다.</li>
<li><code>new AnnotationConfigApplicationContext(AppConfig.class)</code></li>
<li><code>AnnotationConfigApplicationContext</code>클랫스를 사용하면서 자바 코드로된 설정 정보를 넘기면 된다.</li>
</ul>
<p><strong>XML 설정 적용</strong></p>
<ul>
<li>최근에는 스프링 부트를 많이 사용하면서 XML 기반의 설정은 잘 사용하지 않는다. 아직 많은 레거시 프로젝트 들이 XML로 되어 있고, 또 XML을 사용하면 컴파일 없이 빈 설정 정보를 변경할 수 있는 장점도 있으므로 한번쯤 배워두는 것도 괜찮다.</li>
<li><code>GenericXmlApplicationContext</code>를 사용하면 <code>xml</code>설정 파일을 넘기면 된다.</li>
</ul>
<p><strong>XmlAppConfig 사용 자바 코드</strong></p>
<pre><code class="language-java">public class XmlAppContext{

    @Test
    void xmlAppContext{
        ApplicationContext ac = new GenericXmlApplicationContext(&quot;appConfig.xml&quot;);

        MemberService memberService = ac.getBean(&quot;memberService&quot;, MemberService.class);

        assertThat(memberService).inInstanceOf(MemberService.class);
    }
}</code></pre>
<p><strong>xml 기반의 스프링 빈 설정 정보</strong>
<code>src/main/resources/appConfig.xml</code></p>
<pre><code class="language-java">
  &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
  &lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
         xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
         xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://
  www.springframework.org/schema/beans/spring-beans.xsd&quot;&gt;



&lt;bean id=&quot;memberService&quot; class=&quot;hello.core.member.MemberServiceImpl&quot;&gt;
          &lt;constructor-arg name=&quot;memberRepository&quot; ref=&quot;memberRepository&quot; /&gt;
&lt;/bean&gt;

&lt;bean id=&quot;memberRepository&quot; class=&quot;hello.core.member.MemoryMemberRepository&quot; /&gt;

&lt;bean id=&quot;orderService&quot; class=&quot;hello.core.order.OrderServiceImpl&quot;&gt;
    &lt;constructor-arg name=&quot;memberRepository&quot; ref=&quot;memberRepository&quot; /&gt;
    &lt;constructor-arg name=&quot;discountPolicy&quot; ref=&quot;discountPolicy&quot; /&gt;
 &lt;/bean&gt;

&lt;bean id=&quot;discountPolicy&quot; class=&quot;hello.core.discount.RateDiscountPolicy&quot; /&gt;
&lt;/beans&gt;</code></pre>
<ul>
<li>xml 기반의 <code>appConfig.xml</code> 스프링 설정 정보와 자바 코드로 된 <code>AppConfig.xml</code> 설정 정보를 비교해보면 거의 비슷하다는 것을 알 수 있다.</li>
<li>xml 기반으로 설정하는 것은 최근에 잘 사용하지 않으므로 이정도로 마무리 하고, 필요하면 스프링 공식 레퍼런스 문서를 확읺자.<ul>
<li><a href="https://spring.io/projects/spring-framework">https://spring.io/projects/spring-framework</a></li>
</ul>
</li>
</ul>
<p><strong>스프링 빈 설정 메타 정보 -BeanDefinition</strong></p>
<ul>
<li>스프링은 어떻게 이런 다양한 설정 형식을 지원하는 것일까?
그 중심에는 <code>BeanDefinition</code>이라는 추상화가 있다.</li>
<li>쉽게 이야기해서 <strong>역할과 구현을 개념적으로 나눈 것</strong>이다!<ul>
<li>XML을 읽어서 BeanDefinition을 만들면 된다.</li>
<li>자바 코드를 읽어서 BeanDefinition을 만들면 된다.</li>
<li>스프링 컨테이너는 자바 코드인지, XML인지 몰라도 된다. 오직 BeanDefinition만 알면 된다.</li>
<li><code>BeanDefinition</code>을 빈 설정 메타정보라 한다.</li>
<li><code>@Bean</code>, <code>&lt;bean&gt;</code> 당 각각 하나씩 메타 정보가 생성된다.</li>
</ul>
</li>
<li>스프링 컨테이너는 이 메타정보를 기반으로 스프링 빈을 생성한다.
<img src="https://velog.velcdn.com/images/free_mill/post/13da6b4c-d12f-4875-b3d3-5fb409a20391/image.png" alt=""></li>
</ul>
<p><strong>코드 레벨로 조금 더 깊이 있게 들어가보자</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/29a82aca-0178-475e-8f8a-224154d04022/image.png" alt=""></p>
<ul>
<li><code>AnnotationConfigApplicationContext</code>는 <code>AnnotatedBeanDefinitionReader</code>를 사용해서 <code>AppConfig.class</code>를 읽고 <code>BeanDefinition</code>을 생성한다.</li>
<li><code>GenericXmlApplicationContext</code>는 <code>XmlBeanDefinitionReader</code>를 사용해서 <code>appConfig.xml</code> 설정 정보를 읽고 <code>BeanDefinition</code>을 생성한다.</li>
<li>새로운 형식의 설정 정보가 추가되면, XxxBeanDefinitionReader를 만들어서 <code>BeanDefinition</code>을 생성하면 된다.</li>
</ul>
<p><strong>BeanDefinition 살펴보기</strong></p>
<p><strong>BeanDefinition 정보</strong></p>
<ul>
<li>BeanClassName: 생성할 빈의 클래스 명(자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)</li>
<li>factoryBeanName: 팩토리 역할의 빈을 사용할 겨우 이름 예)appConfig</li>
<li>factoryMethodName: 빈을 생성할 팩토리 메서드 지정, 예) memberService</li>
<li>Scope: 싱글톤(기본값)</li>
<li>lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때 까지 최대한 생성을 지연처리 하는지 여부</li>
<li>InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명</li>
<li>DestroyMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명</li>
<li>Constructor arguments, Properties: 의존관계 주입에서 사용한다.(자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음.)</li>
</ul>
<pre><code class="language-java">public class BeanDefinitionTest {
    AnnotationConfigApplication ac = new AnnotationConfigApplicationCotext(AppConfig.class);

    @Test
    @DisplayName(&quot;빈 설정 메타정보 확인&quot;)
    void findApplicationBean(){
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for(String beanDefinitionName : beanDefinitionNames){
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);

            if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
                System.out.println(&quot;beanDefinitionName&quot; + beanDefinitionName + &quot; beanDefinition =&quot; + beanDefinition);
                }
        }
    }
}</code></pre>
<p><strong>정리</strong></p>
<ul>
<li>BeanDefinitio을 직접 생성해서 스프링 컨테이너에 등록할 수 도 있다. 하지만 실무에서 BeanDefinition을 직접 정의하거나 사용할 일은 거의 없다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 핵심 원리_객체 지향 원리 적용]]></title>
            <link>https://velog.io/@free_mill/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%9B%90%EB%A6%AC-%EC%A0%81%EC%9A%A9</link>
            <guid>https://velog.io/@free_mill/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%9B%90%EB%A6%AC-%EC%A0%81%EC%9A%A9</guid>
            <pubDate>Sat, 25 Mar 2023 11:01:27 GMT</pubDate>
            <description><![CDATA[<p><em>출처. inflearn, 김영한 스프링 핵심 원리 - 기본편</em></p>
<p><strong>새로운 할인 정책 개발</strong>
서비스 오픈 직전에 할인 정책을 지금처럼 고정 금액 할인이 아니라 좀 더 합리적인 주문 금액당 할인하는 정률(%) 할인으로 변경하고 싶어요.</p>
<p><strong>RateDiscountPolicy 추가</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/3ecfb3f6-901c-4da6-8b28-83f202950fa4/image.png" alt=""></p>
<p><strong>RateDiscountPolicy 코드 추가</strong></p>
<pre><code class="language-java">public class RateDiscountPolicy implements DiscountPolicy {
    private int discountPercent = 10; //10% 할인

    @Override
    public int discount(Member member, int price){
        if(member.getGrade() == Grade.VIP){
            return price * discountPercent / 100;
        } else {
            return 0;
        }
    }
}</code></pre>
<p><strong>테스트 작성</strong></p>
<pre><code class="language-java">class RateDiscountPolicyTest {
    RateDiscountPolicy discountPolicy = new RateDiscountPolicy();

    @Test
    @DisplayName(&quot;VIP는 10% 할인이 적용되어야 한다.&quot;)
    void vip_o(){
        //given
        Member member = new Member(1L, &quot;memberVIP&quot;, Grade.VIP);

        //when
        int discount = discountPolicy.discount(member, 10000);

        //then
        Assertions.assertThat(discount).isEqualTo(1000);

    }

    @Test
    @DisplayName(&quot;VIP가 아니면 할인을 적용받지 않아야 한다.&quot;);
    void vip_x(){
    //given
    Member member = new Member(2L, &quot;memberBasic&quot;, Grade.BASIC);

    //when
    int discount = discountPolicy.discount(member, 10000);

    //then
    Assertions.assertThat(discount).isEqualTo(0);        
    }

}</code></pre>
<br/>
<br/>

<p><strong>새로운 할인 정책 적용과 문제점</strong>
방금 추가한 할인 정책을 적용해보자.</p>
<p>할인 정책을 애플리케이션에 적용해보자.</p>
<p>할인 정책을 변경려면 클라이언트인 <code>OrderServiceImpl</code> 코드를 고쳐야한다.</p>
<pre><code class="language-java">public class OrderServiceImpl implements OrderService {

    //private final DiscountPolicy discountPolicy = new FixDiscountPolicy();

    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}</code></pre>
<p><strong>문제점 발견</strong></p>
<ul>
<li>우리는 역할과 구현을 충실하게 분리했다. -&gt; OK</li>
<li>다형성도 활용하고, 인터체이스와 구현 객체를 분리했다. -&gt; OK</li>
<li>OCP, DIP 같은 객체지향 설계 원칙을 충실히 준수했다. 
(그렇게 보이지만 사실은 아니다.)</li>
<li>DIP: 주문서비스 클라이언트(<code>OrderServiceImpl</code>)는 <code>DiscountPolicy</code> 인터페이스에 의존하면서 DIP를 지킨 것 같은데?<ul>
<li>-&gt; 클래스 의존관계를 분석해 보자. 추상(인터페이스) 뿐만 아니라 <strong>구체(구현) 클래스에도 의존</strong> 하고 있다.<ul>
<li>추상(인터페이스) 의존 : <code>DiscountPolicy</code></li>
<li>구체(구현) 클래스 : <code>FixDiscountPolicy</code>, <code>RateDiscountPolicy</code> </li>
</ul>
</li>
</ul>
</li>
<li>OCP : 변경하지 않고 확장할 수 있다고 했는데!<ul>
<li><strong>-&gt; 지금 코드는 기능을 확장해서 변경하면, 클라이언트 코드에 영향을 준다!</strong> 따라서 <strong>OCP를 위반</strong>한다.</li>
</ul>
</li>
</ul>
<p><strong>왜 클라이언트 코드를 변경해야 할까?</strong>
클래스 다이어그램으로 의존관계를 분석해보자.</p>
<p><strong>기대했던 의존관계</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/00920917-7d2d-4d6c-ae93-716fd9cdc343/image.png" alt="">
지금까지 단순히 <code>DiscountPolicy</code> 인터페이스만 의존한다고 생각했다.</p>
<p><strong>실제 의존관계</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/b677c082-2a6d-4360-b1fe-0e4b2909d312/image.png" alt="">
잘보면 클라이언트인 <code>OrderServiceImpl</code>이 <code>DiscountPolicy</code> 인터페이스 뿐만 아니라 <code>FixDiscountPolicy</code>인 구체 클래스도 함께 의존하고 있다. 실제 코드를 보면 의존하고 있다! <strong>DIP 위반</strong></p>
<p><strong>정책 변경</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/c974afc5-6d94-4ccd-b988-e1ace039735a/image.png" alt="">
<strong>중요!</strong> : 그래서 <code>FixDiscountPoicy</code>를 <code>RateDiscountPoilcy</code>로 변경하는 순간 <code>OrderServiceImpl</code>의 소스 코드도 함께 변경해야 한다! <strong>OCP 위반</strong></p>
<p>어떻게 문제를 해결할 수 있을까?</p>
<ul>
<li>클라이언트 코드인 <code>OrderServiceImpl</code>은 <code>DiscountPolicy</code>의 인터페이스 뿐만 아니라 구체 클래소도 함께 의존한다.</li>
<li>그래서 구체 클래스를 변경할 때 클라이언트 코드도 함께 변경해야 한다.</li>
<li><strong>DIP 위반</strong> -&gt; 추상에만 의존하도록 변경(인터페이스에만 의존)</li>
<li>DIP를 위반하지 않도록 인터페이스만 의존하도록 의존관계를 변경하면 된다.</li>
</ul>
<p><strong>인터페이스에만 의존하도록 설계를 변경하자</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/dc15ffa9-2fc8-4f25-8b1d-8733ccb2a27a/image.png" alt=""></p>
<p><strong>인터페이스에만 의존하도록 코드 변경</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/042ac891-90ab-429b-a071-194b15353acd/image.png" alt=""></p>
<ul>
<li>인터페이스에만 의존하도록 설계와 코드를 변경했다.</li>
<li><strong>그런데 구현체가 없는데 어떻게 코드를 실행할 수 있을까?</strong></li>
<li>실제 실행을 해보면 NPE(null pointer exception)가 발생한다.</li>
</ul>
<p><strong>해결방안</strong></p>
<ul>
<li>이 문제를 해결하려면 누군가가 클라이언트인 <code>OrderServiceImpl</code>에 <code>DiscountPolicy</code> 의 구현 객체를 대신 생성하고 주입해주어야 한다.</li>
</ul>
<p><strong>관심사의 분리</strong></p>
<ul>
<li>애플리케이션을 하나의 공연이라고 생각해보자. 각각의 인터페이스를 배역(배우 역할)이라고 생각하자. 그런데!
실제 배역 맞는 배우를 선택하는 것은 누구?</li>
<li>로미오와 줄리엣 공연을 하면 로미오 역할을 누가 할지 줄리엣 역할을 누가 할지는 배우들이 정하는게 아니다. 이전 코드는 마치 로미오 역할(인터페이스)을 하는 레오나르도 디카프리오(구현체, 배우)가 줄리엣 역할(인터페이스)을 하는 여자 주인공(구현체, 배우)을 직접 초빙하는 것과 같다. 디카프리오는 공연도 해야하고 동시에 여자 주인공도 공연에 직접 초빙해야 하는 <strong>다양한 책임</strong>을 가지고 있다.</li>
</ul>
<p><strong>AppConfig 등장</strong></p>
<ul>
<li>애플리케이션의 전체 동작 방식을 구성(config)하기 위해, <strong>구현 객체를 생성</strong>하고, <strong>연결</strong>하는 책임을 가지는 별도의 설정 클래스를 만들자.</li>
</ul>
<p><strong>AppConfig</strong></p>
<pre><code class="language-java">public class AppConfig {
    public MemberService memberService(){
        return new MemberServiceImpl(new MemoryMemberRepository());
    }

    public OrderService orderService(){
        return new OrderServiceImpl(
                new MemoryMemberRepository(),
                new FixDiscountPolicy());
    }
}</code></pre>
<ul>
<li><p>AppConfig는 애플리케이션의 실제 동작에 필요한 <strong>구현 객체를 생성</strong>한다.</p>
<ul>
<li><code>MemberServiceImpl</code></li>
<li><code>MemoryMemberRepository</code></li>
<li><code>OrderServiceImpl</code></li>
<li><code>FixDiscountPolicy</code></li>
</ul>
</li>
<li><p>AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 <strong>생성자를 통해서 주입(연결)</strong> 해준다.</p>
<ul>
<li><code>MemberServiceImpl</code> -&gt; <code>MemoryMemberRepository</code></li>
<li><code>OrderServiceImpl</code> -&gt; <code>MemoryMemberRepository</code> , <code>FixDiscountPolicy</code><blockquote>
<p>참고 : 지금은 각 클래스에 생성자가 없어서 컴파일 오류가 발생한다. 바로 다음에 코드에서 생성자를 만든다.</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<p><strong>MemberServiceImpl - 생성자 주입</strong></p>
<pre><code class="language-java">public class MemberServiceImpl implements MemberService {
    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository){
        this.memberRepository = memberRepository;
    }

    public void join(Member member) {
        memberRepository.save(member);
    }

    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }
}</code></pre>
<ul>
<li>설계 변경으로 <code>MemberServiceImpl</code>은 <code>MemoryMemberRepository</code>를 의존하지 않는다!</li>
<li>단지 <code>MemberRepository</code> 인터페이스만 의존한다.</li>
<li><code>MemberServiceImpl</code>입장에서 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다.</li>
<li><code>MemberServiceImpl</code>의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부(<code>AppConfig</code>)에서 결정된다.</li>
<li><code>MemberServiceImpl</code>은 이제부터 <strong>의존관계에 대한 고민은 외부</strong>에 맡기고 <strong>실행에만 집중</strong>하면 된다.</li>
</ul>
<p><strong>그림 - 클래스 다이어그램</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/e76c0e75-55c3-4b05-828e-12a50302da44/image.png" alt=""></p>
<ul>
<li>객체의 생성과 연결은 <code>AppConfig</code>가 담당한다.</li>
<li><strong>DIP 완성</strong> : <code>MemberServiceImpl</code>은 <code>MemberRepository</code>인 추상에만 의존하면 된다. 이제 구체 클래스를 몰라도 된다.</li>
<li><strong>관심사의 분리</strong> : 객체를 생성하고 연결하는 역할과 실행하는 역할이 명확히 분리되있다.</li>
</ul>
<p><strong>그림 - 회원 객체 인스턴스 다이어그램</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/d5f341cb-d1ba-4b52-9aba-61c7a023cd22/image.png" alt=""></p>
<ul>
<li><code>appConfig</code>객체는 <code>memoryMemberRepository</code>객체를 생성하고 그 참조값을 <code>memberServiceImpl</code>을 생성하면서 생성자로 전달한다.</li>
<li>클라이언트인 <code>memberServiceImpl</code> 입장에서 보면 의존관계를 마치 외부에서 주입해주는 것 같다고 해서 DI(Dependency Injection) 우리말로 의존관계 주입 또는 의존성 주입이라 한다.</li>
</ul>
<p><strong>OrderServiceImpl - 생성자 주입</strong></p>
<pre><code class="language-java">public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
       private final DiscountPolicy discountPolicy;

    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}
    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice){
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }</code></pre>
<ul>
<li>설계 변경으로 <code>OrderServiceImpl</code>은 <code>FixDiscountPolicy</code>를 의존하지 않는다!</li>
<li>단지 <code>DiscountPolicy</code> 인터페이스만 의존한다.</li>
<li><code>OrderServiceImpl</code>입장에서 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다.</li>
<li><code>OrderServiceImpl</code>의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부 (<code>AppConfig</code>)에서 결정한다.</li>
<li><code>OrderServiceImpl</code>은 이제부터 실행에만 집중하면 된다.</li>
<li><code>OrderServiceImpl</code>에는 <code>MemoryMemberRepository</code>, <code>FixDiscountPolicy</code>객체의 의존관계가 주입된다.</li>
</ul>
<p><strong>AppConfig 실행</strong>
<strong>사용 클래스 - memberApp</strong></p>
<pre><code class="language-java">public class MemberApp{

    public static void main(Stringp[] args){
        AppConfig appConfig = new AppConfig();
        MemberService memberService = appConfig.memberService();
        Member member = new Member(1L, &quot;memberA&quot;, Grade.VIP);
        memberService.join(member);

        Member findMember = memberService.findMember(1L);
        System.out.println(&quot;new member = &quot; + member.getName());
        System.out.println(&quot;find member = &quot; + findMember.getName());
    }
}</code></pre>
<p><strong>사용 클래스 - OrderApp</strong></p>
<pre><code class="language-java">public class OrderApp {

    public static void main(String[] args) {
        AppConfig appConfig = new AppConfig();
        MemberService memberService = appConfig.memberService();
        OrderService orderService = appConfig.orderService();

        long memberId = 1L;
        Member member = new Member(memberId, &quot;itemA&quot;, 10000);
        memberService.join(member);

        Order order = orderService.createOrder(memberId, &quot;itemA&#39;, 10000);

        System.out.println(&quot;order = &quot; + order);

    }
}</code></pre>
<p><strong>테스트 코드 오류 수정</strong></p>
<pre><code class="language-java">class MemberServiceTest {
    MemberService memberService;

    @BeforeEach
    public void beforeEach(){
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
    }
}</code></pre>
<pre><code class="language-java">class OrderServiceTest{

    MemberService memberService;
    OrderService orderService;

    @BeforeEach
    public void beforeEach(){
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
        orderService = appConfig.orderService();
      }
}</code></pre>
<p>테스트 코드에서 <code>@BeforeEach</code>는 각 테스트를 실행하기 전에 호출된다.</p>
<p><strong>정리</strong></p>
<ul>
<li>AppConfig를 통해서 관심사를 확실하게 분리했다.</li>
<li>배역, 배우를 생각해보자.</li>
<li>AppConfig는 공연 기획자다.</li>
<li>AppConfig는 구체 클래스를 선택한다. 배역에 맞는 담당 배우를 선택한다. 애플리케이션이 어떻게 동작해야 할지 전체 구성을 책임진다.</li>
<li><code>OrderServiceImpl</code>은 기능을 실행하는 책임만 지면 된다.</li>
</ul>
<p><strong>AppConfig 리펙토링</strong>
현재 AppConfig를 보면 <strong>중복</strong>이 있고, <strong>역할</strong>에 따른 <strong>구현</strong>이 잘 알보인다.
<strong>기대하는 그림</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/98d9e50d-0a7f-431e-82ec-c0dce641ee63/image.png" alt=""></p>
<p><strong>리팩터링 전</strong></p>
<pre><code class="language-java">public class AppConfigP{

    public MemberService memberService(){
        return nwe MemberServiceImpl(new MemberRepository());
    }

    public OrderService orderService(){
        return new OrderServiceImpl(
                new MemoryMemberRepository(),
                new FixDiscountPolicy());
    }
}</code></pre>
<p>중복을 제거하고, 역할에 따른 구현이 보이도록 리팩토링 하자.</p>
<p><strong>리팩터링 후</strong></p>
<pre><code class="language-java">public class AppConfig{

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

    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(), discountPolicy());
       }

    public MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }

    public DiscountPolicy discountPolicy(){
        return new FixDiscountPolicy();
    }

}</code></pre>
<ul>
<li><code>new MemoryRepository()</code> 이 부분이 중복 제거되었다. 이제 <code>MemoryMemberRepository</code>를 다른 구현체로 변경할 때 한 부분만 변경하면 된다.</li>
<li><code>AppConfig</code>를보면 역할과 구현 클래스가 한눈에 들어온다. 애플리케이션 전체 구성이 어떻게 되어있는지 빠르게 파악할 수 있다.</li>
</ul>
<p><strong>새로운 구조와 할인 정책 적용</strong></p>
<ul>
<li>처음으로 돌아가서 정액 할인 정책을 정률% 할인 정책으로 변경해보자.</li>
<li>FixDiscountPolicy -&gt; RateDiscountPolicy</li>
<li>어떤 부분만 변경하면 되겠는가?</li>
</ul>
<p><strong>AppConfig의 등장으로 애플리케이션이 크게 사용 역역과, 객체를 생성하고 구성(Configuration)하는 영역으로 분리되었다.</strong></p>
<p><strong>그림 - 사용, 구성의 분리</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/ac8e729f-269b-4839-88da-697fb2e516f3/image.png" alt=""></p>
<p><strong>그림 - 할인 정책의 변경</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/abe0b744-606b-46d8-af76-613b29a9d720/image.png" alt=""></p>
<ul>
<li><code>FixDiscountPolicy</code> -&gt; <code>RateDiscountPolicy</code> 로 변경해도 구성 영역만 영향을 받고, 사용 영역은 전혀 영향을 받지 않는다.</li>
</ul>
<p><strong>할인 정책 변경 구성 코드</strong></p>
<pre><code class="language-java">public class AppConfig {
    public MemberService memberService {
        return new MemberServiceImpl(memberRepository());
    }

    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    public MemberRepository memberRespository() {
        return new MemoryMemberRepository();
    }

    public DiscountPolicy discountPolicy() {
        //return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }
}</code></pre>
<ul>
<li><code>AppConfig</code>에서 할인 정책 역할을 담당하는 구현을 <code>FixDiscountPolicy</code> -&gt; <code>RateDiscountPolicy</code>객체로 변경했다.</li>
<li>이제 할인 정책을 변경해도, 애플리케이션의 구성 역할을 담당하는 AppConfig만 변경하면 된다.
클라이언트 코드인 <code>OrderServiceImpl</code>를 포함해서 <strong>사용 영역</strong>의 어떤 코드도 변경할 필요가 없다.</li>
<li><strong>구성 영역</strong>은 당연히 변경된다. 구성 역할을 담당하는 AppConfig를 애플리케이션이라는 공연의 기획자로 생각하자. 공연 기획자는 공연 참여자인 구현 객체들을 모두 알아야 한다.</li>
</ul>
<p><strong>전체 흐름 정리</strong>
지금까지의 흐름을 정리해보자.</p>
<ul>
<li>새로운 할인 정책 개발</li>
<li>새로운 할인 정책 적용과 문제점</li>
<li>관심사의 분리</li>
<li>AppConfig 리팩터링</li>
<li>새로운 구조와 할인 정책 적용</li>
</ul>
<p><strong>새로운 할인 정책 개발</strong>
당형성 덕분에 새로운 정률 할인 정책 코드를 추가로 개발하는 것 자체는 아무 문제가 없음</p>
<p><strong>새로운 할인 정책 적용과 문제점</strong>
새로 개발한 정률 할인 정책을 적용하려고 하니 <strong>클라이언트 코드</strong>인 주문 서비스 구현체도 함께 변경해야 함.
주문 서비스 클라이언트가 인터페이스 <code>DiscountPolicy</code>뿐만 아니라, 구체 클래스인 <code>FixDiscountPolicy</code>도 함께 의존 -&gt; <strong>DIP 위반</strong></p>
<p><strong>관심사의 분리</strong></p>
<ul>
<li>애플리케이션을 하나의 공연으로 생각</li>
<li>기존에는 클라이언트가 의존하는 서버 구현 객체를 직접 생성하고, 실행함</li>
<li>AppConfig가 나올 시점.</li>
<li>AppConfig는 애플리케이션의 전체 동작 방시을 구성(Config)하기 위해, <strong>구현 객체를 생성</strong>하고, <strong>연결</strong>하는 책임</li>
<li>이제부터 클라이언트 객체는 자신의 역할을 실행하는 것만 집중, 권한이 줄어듦(책임이 명확해짐)</li>
</ul>
<p><strong>AppConfig 리팩터링</strong></p>
<ul>
<li>구성 정보에서 역할과 구현을 명확하게 분리</li>
<li>역할이 잘 드러남</li>
<li>중복 제거</li>
</ul>
<p><strong>새로운 구조와 할인 정책 적용</strong></p>
<ul>
<li>정액 할인 정책 -&gt; 정률% 할인 정책으로 변경</li>
<li>AppConfig의 등장으로 애플리케이션이 크게 <strong>사용 영역</strong>과, 객체를 생성하고 <strong>구성(Configuration)하는 영역</strong>으로 분리</li>
<li>할인 정책을 변경해도 AppConfig가 있는 구성 영역만 변경하면 됨, 사용 영역은 변경할 필요가 없음, 물론 클라이언트 코드인 주문 서비스 코드도 변경하지 않음</li>
</ul>
<br/>

<p><strong>좋은 객체 지향 설계의 5가지 원칙의 적용</strong>
여기서 3가지 SRP, DIP, OCP 적용</p>
<p><strong>SRP 단일 책임 원칙</strong>
<strong>한 클래스는 하나의 책임만 가져야 한다.</strong></p>
<ul>
<li>클라이언트 객체는 직접 구현 객체를 생성하고, 연결하고, 실행하는 다양한 책임을 가지고 있음</li>
<li>SRP 단일 책임 원칙을 따르면서 관심사를 분리</li>
<li>구현 객체를 생성하고 연결하는 책임은 AppConfig가 담당</li>
<li>클라이언트 객체는 실행하는 책임만 담당</li>
</ul>
<p><strong>DIP 의존관계 역전 원칙</strong>
<strong>프로그래머는 &quot;추상화에 의존해야지, 구체와에 의존하면 안된다.&quot; 의존성 주입은 이 원칙을 따르는 방법 중 하나다.</strong></p>
<ul>
<li>새로운 할인 정책을 개발하고, 적용하려고 하니 클라이언트 코드도 함께 변경해야 했다. 왜냐하면 기존 클라이언트 코드(<code>OrderServiceImpl</code>)는 DIP를 지키며 <code>DiscountPolicy</code> 추상화 인터페이스에 의존하는 것 같았지만, <code>FixDiscountPolicy</code>추상화 인터페이스에만 의존하도록 코드를 변경했다.</li>
<li>클라이언트 코드가 <code>DiscountPolicy</code> 추상화 인터페이스에만 의존하도록 코드를 변경했다.</li>
<li>하지만 클라이언트 코드는 인터페이스만으로는 아무것도 실행할 수 없다.</li>
<li>AppConfig가 <code>FixDiscountPolicy</code> 객체 인스턴스를 클라이언트 코드 대신 생성해서 클라이언트 코드에 의존관계를 주입했다. 이렇게해서 DIP 원칙을 따르면서 문제도 해결했다.</li>
</ul>
<p><strong>OCP</strong>
<strong>소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.</strong></p>
<ul>
<li>다형성 사용하고 클라이언트가 DIP를 지킴</li>
<li>애플리케이션을 사용 영역과 구성 영역으로 나눔</li>
<li>AppConfig가 의존관계를 <code>FixDiscountPolicy</code> </li>
<li><blockquote>
<p><code>RateDiscountPolicy</code>로 변경해서 클라이언트 코드에 주입하므로 클라이언트 코드는 변경하지 않아도 됨</p>
</blockquote>
</li>
<li><strong>소프트웨어 요소를 새롭게 확장해도 사용 영역의 변경은 닫혀있다!</strong></li>
</ul>
<p><strong>IOC, DI, 그리고 컨테이너</strong>
<strong>제어의 역전 IOC(Inversion of Control)</strong></p>
<ul>
<li>기존 프로그램은 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고, 연결하고, 실행했다.</li>
<li>반면에 AppConfig가 등장한 이후에 구현 객체는 자신의 로직을 실행하는 역할만 담당한다. 프로그램의 제어 흐름은 이제 AppConfig가 가져간다. 예를 들어서 <code>OrderServiceImpl</code>은 필요한 인터페이스들을 호출하지만 어떤 구현 객체들이 실행될지 모른다.</li>
<li>프로그램에 대한 제어 흐름에 대한 권한은 모두 AppConfig가 가지고 있다. 심지어 <code>OrderServiceImpl</code>도 AppConfig가 생성한다. 그리고 AppConfig는 <code>OrderServiceImpl</code>이 아닌 OrderService 인터페이스의 다른 구현 객체를 생성하고 실행할 수도 있다. 그런 사실도 모른체 <code>OrderServiceimpl</code>은 묵묵히 자신의 로직을 실행할 뿐이다.</li>
<li>이렇듯 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역정(IoC)이라 한다.</li>
</ul>
<p><strong>프레임워크 vs 라이브러리</strong></p>
<ul>
<li>프레임워크가 내가 작성한 코드를 제어하고, 대신 실행하면 그것은 프레임워크가 맞다.(JUnit)</li>
<li>반면에 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 프레임워크가 아니라 라이브러리다.</li>
</ul>
<p><strong>정적인 클래스 의존관계</strong>
클래스가 사용하는 import코드만 보고 의존관계를 쉽게 판단할 수 있다.정적인 의존관계는 애플리케이션을 실행하지 않아도 분석할 수 있다. 클래스 다이어그램을 보자. <code>OrderServiceImpl</code>은 <code>MemberRepository</code>, <code>DiscountPolicy</code>에 의존한다는 것을 알 수 있다.
그런데 이러한 클래스 의존관계 만으로는 실제 어떤 객체가 <code>OrderServiceImpl</code>에 주입 될지 알 수 없다.
<strong>클래스 다이어그램</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/30d3d90e-d93d-4e68-bb5e-3135742b74cc/image.png" alt=""></p>
<p><strong>동적인 객체 인스턴스 의존 관계</strong>
애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계다.</p>
<p><strong>객체 다이어그램</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/415b4589-80e7-4852-95e0-277072ee8493/image.png" alt=""></p>
<ul>
<li>애플리케이션 <strong>실행 시점(런타임)</strong>에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결 되는 것을 <strong>의존관계 주입</strong>이라 한다.</li>
<li>객체 인스턴스를 생성하고, 그 참조값을 전달해서 연결된다.</li>
<li>의존관계 주입을 사용하면 클라이언트 코드를 변경하지 않고, 클라이언트가 호출하는 대상의 타입 인스턴스를 변경할 수 있다.</li>
<li>의존관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다.</li>
</ul>
<p><strong>IOC 컨테이너, DI 컨테이너</strong></p>
<ul>
<li>AppConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을 IOC컨테이너 또는 <strong>DI 컨테이너</strong>라 한다.</li>
<li>의존관계 주입에 초점을 맞추어 최근에는 주로 DI컨테이너라 한다.</li>
<li>또는 어셈블러, 오브젝트 팩토리 등으로 불리기도 한다.</li>
</ul>
<p><strong>스프링으로 전환하기</strong>
지금까지 순수한 자바 코드만으로 DI를 적용했다. 이제 스프링을 사용해보자. 
<strong>AppConfig 스프링 기반으로 변경</strong></p>
<pre><code class="language-java">@Configuration
public class AppConfig{

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

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

    @Bean
    public MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }

    @Bean
    public DiscountPolicy discountPolicy(){
        return new RateDiscountPolicy();
    }
}</code></pre>
<ul>
<li>AppConfig에 설정을 구성한다는 뜻의 <code>@Configuration</code>을 붙여준다.</li>
<li>각 메서드에 <code>@Bean</code>을 붙여준다. 이렇게 하면 스프링 컨테이너에 스프링 빈으로 등록한다.</li>
</ul>
<p><strong>MemberApp에 스프링 컨테이너 적용</strong></p>
<pre><code class="language-java">public class MemberApp{

    public static void main(String[] args){
//      AppConfig appConfig = new AppConfig();
//        MemberService memberService = appConfig.memberService();
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = applicationContext.getBean(&quot;memberService&quot;, MemberService.class);

        Member member = new Member(1L, &quot;memberA&quot;, Grade.VIP);
        memberService.join(member);

        Member findMember = memberService.findMember(1L);
        System.out.println(&quot;new member = &quot; +member.getName());
        System.out.println(&quot;find member = &quot; +findMember.getName());
    }
}</code></pre>
<p><strong>OrderApp에 스프링 컨테이너 적용</strong></p>
<pre><code class="language-java">public class OrderApp{

    public static void main(String[] args){
//        AppConfig appConfig = new AppConfig();
//        MemberService memberService = appConfig.memberService();
//        OrderService orderService = appConfig.orderService();

        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = applicationContext.getBean(&quot;orderService&quot;,
  OrderService.class);
          long memberId = 1L;
          Member member = new Member(memberId, &quot;memberA&quot;, Grade.VIP);
          memberService.join(member);
          Order order = orderService.createOrder(memberId, &quot;itemA&quot;, 10000);
          System.out.println(&quot;order = &quot; + order);
    }

}</code></pre>
<ul>
<li>두 코드를 실행하면 스프링 관련 로그가 몇줄 실행되면서 기존과 동일한 결과가 출력된다.</li>
</ul>
<p><strong>스프링 컨테이너</strong></p>
<ul>
<li><code>ApplicationContext</code>를 스프링 컨테이너라 한다.</li>
<li>스프링 컨테이너는 <code>@Configuration</code>이 붙은 <code>AppConfig</code>를 설정(구성)정보로 사용한다. 여기서 <code>@Bean</code>이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다. 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라 한다.</li>
<li>스프링 빈은 <code>@Bean</code>이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다. (<code>memberService</code>, <code>orderService</code>)</li>
<li>이전에는 개발자가 필요한 객체를 <code>AppConfig</code>를 사용해서 직접 조회했지만, 이제부터는 스프링 컨테이너를 통해서 필요한 스프링 빈(객체)를 찾아야 한다. 스프링 빈은 <code>applicationContext.getBean()</code>메서드를 사용해서 찾을 수 있다.</li>
<li>기존에는 개발자가 직접 자바코드로 모든 것을 했다면 이제부터는 스프링 컨테이너에 객체를 스프링 빈으로 등록하고, 스프링 컨테이너에서 스프링 빈을 찾아서 사용하도록 변경되었다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 핵심 원리_예제 만들기]]></title>
            <link>https://velog.io/@free_mill/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC%EC%98%88%EC%A0%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@free_mill/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC%EC%98%88%EC%A0%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sat, 18 Mar 2023 23:38:03 GMT</pubDate>
            <description><![CDATA[<p><em>출처. inflearn, 김영한 스프링 핵심 원리 - 기본편</em></p>
<h3 id="프로젝트-생성">프로젝트 생성</h3>
<p><img src="https://velog.velcdn.com/images/free_mill/post/13a26c7b-ed6d-4916-951c-da4375092651/image.png" alt=""></p>
<h3 id="비즈니스-요구사항과-설계">비즈니스 요구사항과 설계</h3>
<ul>
<li>회원 <ul>
<li>회원을 가입하고 조회할 수 있다.</li>
<li>회원은 일반과 VIP 두 가지 등급이 있다.</li>
<li>회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다.(미확정)</li>
</ul>
</li>
</ul>
<ul>
<li>주문과 할인 정책<ul>
<li>회원은 상품을 주문할 수 있다.</li>
<li>회원 등급에 따라 할인 정책을 적용할 수 있다.</li>
<li>할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라.(나중에 변경 가능)</li>
<li>할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수도 있다.(미확정)</li>
</ul>
</li>
</ul>
<p>요구사항을 보면 회원 데이터, 할인 정책 같은 부분은 지금 결정하기 어려운 부분이다. 그렇다고 이런 정책이 결정될 때 까지 개발을 무기한 기다릴 수도 없다. 우리는 앞에서 배운 객체 지향 설계 방법으로 문제를 해결한다.</p>
<h3 id="회원-도메인-설계">회원 도메인 설계</h3>
<ul>
<li>회원 도메인 요구사항<ul>
<li>회원을 가입하고 조회할 수 있다.</li>
<li>회원은 일반관 VIP 두 가지 등급이 있다.</li>
<li>회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다.(미확정)</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/free_mill/post/08472166-3e6d-4024-9a31-b4c7ba1c6463/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/free_mill/post/9f6e35ba-c1d6-4c10-9277-799eb61a4723/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/free_mill/post/b816a5ce-8535-4a8a-831b-a52917496834/image.png" alt=""></p>
<h3 id="회원-도메인-개발">회원 도메인 개발</h3>
<p><strong>회원 엔티티</strong></p>
<p><em>회원 등급</em></p>
<pre><code class="language-java">
public enum Grade {
    BASIC,
    VIP
}</code></pre>
<p><em>회원 엔티티</em></p>
<pre><code class="language-java">
public class Member {
    private long id;
    private String name;
    private Grade grade;

    public Member(Long id, String name, Grade grade){
    this.id = id;
    this.name = name;
    this.grade = grade;
    }

    public Long getId(){
        return id;
    }

    public void setId(Long id){
        this.id = id;
    }

    public String getName(){
        return name;
    }

    public void setName(String name){
        this.name = name;
    }

    public Grade getGrade(){
        return grade;
    }

    public void setGrade(Grade grade){
        this.grade = grade;
    }
}</code></pre>
<p><strong>회원 저장소</strong></p>
<p><em>회원 저장소 인터페이스</em></p>
<pre><code class="language-java">
public interface MemberRepository{
    void save(Member member);

    Member findById(Long memberId);
}</code></pre>
<p><em>메모리 회원 저장소 구현체</em></p>
<pre><code class="language-java">public class MemoryMemberRepository implements MemberRepository{
    private static Map&lt;Long, Member&gt; store = new HashMap&lt;&gt;();

    @Override
    public void save(Member member){
        store.put(member.getId(), member);
    }

    @Override
    public Member findById(Long memberId){
        return store.get(memberId);
    }
}</code></pre>
<blockquote>
<p>참고 : <code>HashMap</code>은 동시성 이슈가 발생할 수 있다.
이런 경우 <code>ConcurrentHashMap</code>을 사용하자.</p>
</blockquote>
<p><strong>회원 서비스</strong>
<em>회원 서비스 인터페이스</em></p>
<pre><code class="language-java">public interface MemberService{
    void join(Member member);

    Member findMember(Long memberId);
}</code></pre>
<p><em>회원 서비스 구현체</em></p>
<pre><code class="language-java">package hello.core.member;

public class MemberServiceImpl implements MemberService{
    private final MemberRepository memberRepository = new MemoryMemberRepository();

    public void join(Member member){
        memberRepository.save(member);
    }

    public Member findMember(Long memberId){
        return memberRepository.findById(memberId);
    }
}</code></pre>
<p><strong>회원 도메인 실행과 테스트</strong>
<em>회원 도메인 - 회원 가입 main</em></p>
<pre><code class="language-java">public class MemberApp {
    public static void main(String[] args){
        MemberService memberService = new MemberServiceImpl();
        Member member = new Member(1L, &quot;memberA&quot;, Grade.VIP);

        memberService.join(member);
        Member findMember = memberService.findMember(1L);

        System.out.println(&quot;new member = &quot; + member.getName());
        System.out.println(&quot;find member = &quot; +
        findMember.getName());
    }
}</code></pre>
<p><em>회원 도메인 - 회원 가입 테스트</em></p>
<pre><code class="language-java">class MemberServiceTest{
    MemberService memberService = new MemberServiceImpl();

    @Test
    void join(){
        //given
        Member member = new Member(1L, &quot;memberA&quot;, Grade.VIP);

        //when
        memberservice.join(member);
        Member findMember = memberService.findMember(1L);

        //then
        Assertions.assertThat(member).isEqualTo(findMember);
    }
}</code></pre>
<p><strong>주문과 할인 도메인 설계</strong></p>
<ul>
<li>주문과 할인 정책<ul>
<li>회원은 상품을 주문할 수 있다.</li>
<li>회원 등급에 따라 할인 정책을 적용할 수 있다.</li>
<li>할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라.(나중에 변경 가능)</li>
<li>할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 있다.(미확정)</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/free_mill/post/2c18ecdc-5be7-4241-a602-5036091655fe/image.png" alt=""></p>
<p><strong>1. 주문 생성 :</strong> 클라이언트는 주문 서비스에 주문 생성을 요청한다.
<strong>2. 회원 조회 :</strong> 할인을 위해서는 회원 등급이 필요하다. 그래서 주문 서비스는 회원 저장소에서 회원을 조회한다.
<strong>3. 할인 적용 :</strong> 주문 서비스는 회원 등급에 따른 할인 정책에 위임한다.
<strong>4. 주문 결과 반환 :</strong> 주문 서비스는 할인 결과를 포함한 주문 결과를 반환한다.</p>
<blockquote>
<p>참고 : 실제로는 주문 데이터를 DB에 저장하겠지만, 예제가 너무 복잡해 질 수 있어서 생략하고, 단순히 주문 결과를 반환한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/free_mill/post/e74164da-3ab9-4eb3-aefe-85ac6127850e/image.png" alt=""></p>
<p><strong>역활과 구현을 분리</strong>해서 자유롭게 구현 객체를 조립할 수 있게 설계했다. 덕분에 회원 저장소는 물론이고, 할인 정책을 유연하게 변경할 수 있다.</p>
<p><strong>주문 도메인 클래스 다이어그램</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/a77ee7c7-bfee-4768-a731-861acc653111/image.png" alt=""></p>
<p><strong>주문 도메인 객체 다이어그램1</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/2ab2e437-4df2-46fa-937b-664aaf6d9855/image.png" alt=""></p>
<p>회원을 메모리에서 조회하고, 정액 할인 정책(고정 금액)을 지원해도 주문 서비스를 변경하지 않아도 된다.
역할들의 협력 관계를 그대로 재사용 할 수 있다.</p>
<br/>

<p><strong>주문 도메인 객체 다이어그램2</strong>
<img src="https://velog.velcdn.com/images/free_mill/post/965a1616-4df9-40ec-acf9-7f3c8fb38999/image.png" alt=""></p>
<p>회원을 메모리가 아닌 실제 DB에서 조회하고, 정률 할인 정책(주문 금액에 따라 % 할인)을 지원해도 주문 서비스를 변경하지 않아도 된다.
협력 관계를 그대로 재사용 할 수 있다.</p>
<p><strong>주문과 할인 도메인 개발</strong></p>
<p><em>할인 정책 인터페이스</em></p>
<pre><code class="language-java">public interface DiscountPolicy{
    /**
    * @return 할인 대상 금액
    */
    int discount(Member member, int price);
}</code></pre>
<p><em>정액 할인 정책 구현체</em></p>
<pre><code class="language-java">public class FixDiscountPolicy implements DiscountPolicy{
    private int discountFixAmount = 1000;
    //1000원 할인

    @Override
    public int discount(Member member, int price){
        if (member.getGrade == Grade.VIP){
            return discountFixAmount;
            } else {
                return 0;
            }
        }
}</code></pre>
<p>VIP면 1,000원 할인, 아니면 할인 없음</p>
<p><em>주문 엔티티</em></p>
<pre><code class="language-java">public class Order{
    private Long memberId;
    private String itemName;
    private int itemPrice;
    private int discountPrice;

    public Order(Long memberId, String itemName, int itemPrice, int discountPrice){
        this.memberId = memberId;
        this.itemName = itemName;
        this.itemPrice = itemPrice;
        this.discountPrice = discountPrice;
}

    public int calculatePrice(){
        return itemPrice - discountPrice;
    }

    public Long getMemberId(){
        return memberId;
    }

    public String getItemName(){
        return itemPrice;
    }

    public int getDiscountPrice(){
        return discountPrice;
    }

    @Override
    public String toString(){
        return &quot;Order{&quot; +
                  &quot;memberId=&quot; + memberId +
                  &quot;, itemName=&#39;&quot; + itemName + &#39;\&#39;&#39; +
                  &quot;, itemPrice=&quot; + itemPrice +
                  &quot;, discountPrice=&quot; + discountPrice +
                  &#39;}&#39;;
                  }
    }</code></pre>
<p><em>주문 서비스 인터페이스</em></p>
<pre><code class="language-java">public interface OrderService{
    Order createOrder(Long memberId, String itemName, int itemPrice);
}</code></pre>
<p><em>주문 서비스 구현체</em></p>
<pre><code class="language-java">public class OrderServiceImpl implements OrderService {
    private final MemberRespository memberRepository = new MemoryMemberRepository();
    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
    Member member = memberRepository.findById(memberId);
    int discountPrice = discoutnPolicy.discount(member, itemPrice);

    return new Order(memberId, itemPrice, discountPrice);
    }
}</code></pre>
<p>주문 생성 요청이 오면, 회원 정보를 조회하고, 할인 정책을 적용한 다음 주문 객체를 생성해서 반환한다.
<strong>메모리 회원 리포지토리와, 고정 금액 할인 정책을 구현체로 생성한다.</strong></p>
<p><br></br></p>
<p><strong>주문과 할인 도메인 실행과 테스트</strong></p>
<p><em>주문과 할인 정책 실행</em></p>
<pre><code class="language-java">public class OrderApp {
    public static void main(String[] args) {
        MemberService memberService = new MemberServiceImpl();
        OrderService orderService = new OrderServiceImpl();

        long memberId = 1L;
        Member member = new Member(memberId, &quot;memberA&quot;, Grade.VIP);
        memberService.join(member);

        Order order = /orderService.createOrder(memberId, &quot;itemA&quot;, 10000);

        System.out.println(&quot;order = &quot; + order);
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 핵심 원리_객체 지향 설계와 스프링]]></title>
            <link>https://velog.io/@free_mill/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%99%80-%EC%8A%A4%ED%94%84%EB%A7%81</link>
            <guid>https://velog.io/@free_mill/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%99%80-%EC%8A%A4%ED%94%84%EB%A7%81</guid>
            <pubDate>Sat, 18 Mar 2023 01:05:12 GMT</pubDate>
            <description><![CDATA[<p><em>출처. inflearn, 김영한 스프링 핵심 원리 - 기본편</em></p>
<h3 id="스프링-생태계">스프링 생태계</h3>
<p>스프링은 특정 하나의 기술이 아니라, 여러가지 기술의 모임이라고 할 수 있다.
<img src="https://velog.velcdn.com/images/free_mill/post/a477b578-7c22-44d1-95ab-551de147e117/image.png" alt=""></p>
<p>아래와 같은 기술들을 모두 통합해서 스프링 프레임워크라 한다.
강의는 스프링 핵심 기술에 초점을 맞춰 진행된다.
<img src="https://velog.velcdn.com/images/free_mill/post/f9c99581-3b5f-4262-9a44-17e1c50e248f/image.png" alt=""></p>
<p>요즘 트렌드는 스프링 프레임 워크를 편리하게 사용하기 위해 스프링 부트를 사용한다.</p>
<p><img src="https://velog.velcdn.com/images/free_mill/post/15b3a8f8-07f7-47b4-a8d9-138d6990ab98/image.png" alt=""></p>
<h3 id="스프링의-진짜-핵심">스프링의 진짜 핵심</h3>
<ul>
<li>스프링은 자바 언어 기반의 프레임워크</li>
<li>자바 언어의 가장 큰 특징 - <strong>객체 지향 언어</strong></li>
<li>스프링은 객체 지향 언어가 가진 강력한 특징을 살려내는 프레임워크</li>
<li>스프링은 <strong>좋은 객체 지향</strong> 애플리케이션을 개발할 수 있게 도와주는 프레임워크</li>
</ul>
<h3 id="좋은-객체-지향">좋은 객체 지향</h3>
<p><strong>객체 지향 특징</strong></p>
<ul>
<li>추상화</li>
<li>캡슐화</li>
<li>상속</li>
<li><strong>다형성</strong></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Oracle 실전 pack]]></title>
            <link>https://velog.io/@free_mill/Oracle-%EC%8B%A4%EC%A0%84-pack</link>
            <guid>https://velog.io/@free_mill/Oracle-%EC%8B%A4%EC%A0%84-pack</guid>
            <pubDate>Sun, 19 Feb 2023 08:28:12 GMT</pubDate>
            <description><![CDATA[<h3 id="1-테이블-생성">1. 테이블 생성</h3>
<pre><code class="language-sql">CREATE TABLE TABLE_NAME(
    COLUMN_NAME1 COLUMN_PROPERTY (CONSTRAINT)
    COLUMN_NAME2 COLUMN_PROPERTY (CONSTRAINT)
    .
    .
    .
    )

 COMMENT ON TABLE TABLE_NAME IS &quot;DISCRIPTION&quot;
 COMMENT ON COLUMN TABLE_NAME.COLUMN_NAME1 IS &quot;DISCRIPTION&quot;</code></pre>
<p>COLUMN PROPERTY에는 VARCHAR, NUMBER, DATE등이 있다.
CONSTRAINT에는 PRIMARY KEY등이 있다.
TABlE을 생상하면 COMMENT와 같이 설명을 달아주는 것이 좋다.</p>
<h3 id="2-시퀀스-생성">2. 시퀀스 생성</h3>
<pre><code class="language-sql">CREATE SEQUENCE MAN.ORDER_SEQ
       INCREMENT BY 1
       START WITH 1
       MINVALUE 1
       MAXVALUE 9999
       CYCLE
       NOCACHE
       NOORDER;</code></pre>
<p>시퀀스를 사용하지 않고 MAX+1등의 select문을 사용하면 동시성 문제가 발생할 수 있다. 그러니 시퀀스를 사용하도록 하자.</p>
<h3 id="3-인덱스-생성">3. 인덱스 생성</h3>
<pre><code class="language-sql">CREATE INDEX INDEX_NAME TABLE_NAME(COLUMN_NAME);</code></pre>
<h3 id="4-function-생성">4. FUNCTION 생성</h3>
<pre><code class="language-sql">// 공통코드 테이블에서 코드값을 활용해 명칭을 뱉어내는 함수
CREATE OR FUNCTION FUNCNTION_NAME(PARAM1, PARAM2)
RETURN DATA_TYPE
IS VAR_NAME VARCHAR2(100);  //내부에서 사용할 변수 생성
BEGIN
    SELECT TARGET_COLUMN_NAME INTO V_NM
         FROM TARGET_TABLE_NAME
     WHERE TARGET_TABLE_COLUMN1 = PARAM1
      AND TARGET_TABLE_COLUMN2 = PARAM2
     //  TARGET_TABLE_NAME에서 WHERE 조건에 맞는 DATA를 찾고 이것을 V_NM에 입력
     RETURN V_NM;
END FN_CODE_NM;
//BEGIN~END 실행할 쿼리        </code></pre>
<p>FUNCTION은 보통 db의 데이터를 사용자에게 보여줄 때 사용한다.</p>
<p><strong><em>등록 방법</em></strong>
<img src="https://velog.velcdn.com/images/free_mill/post/be90d78c-1a12-44bb-98c9-99aac9bfab3d/image.png" alt="">
<img src="https://velog.velcdn.com/images/free_mill/post/3d91ea8f-6743-4847-ae8a-a8866987df27/image.png" alt="">
ctrl+f9를 통해 compile을 하면 등록 완료.
<img src="https://velog.velcdn.com/images/free_mill/post/64000ae9-e4aa-40b4-8fd1-3e4705ccad6d/image.png" alt="">
실행 성공</p>
<h3 id="5-procedure-생성">5. PROCEDURE 생성</h3>
<pre><code class="language-sql">CREATE OR REPLACE PROCEDURE PROCEDURE_NAME(
    #입력 받는 매개변수
    IV_ITEM_CD IN VARCHAR2,
    IV_ITEM_NM IN TB_ITEM.ITEM_NM%TYPE,
    #PROCEDURE가 출력하는 매개변수
    OV_REG_DT OUT TB_ITEM.REG_DT%TYPE 
)
IS 
    #BEGIN ~ END에서 사용할 지역변수
    V_CNT NUMBER;
    V_REG_DT DATE;
BEGIN
    SELECT COUNT(*) INTO V_CNT
      FROM TB_ITEM 
     WHERE ITEM_CD = IV_ITEM_CD;

    IF V_CNT = 0 THEN
        INSERT INTO TB_ITEM(ITEM_CD, ITEM_NM, REG_DT, UPD_DT)
        VALUES(IV_ITEM_CD, IV_ITEM_NM, SYSDATE, SYSDATE);
    ELSE
        UPDATE TB_ITEM 
           SET ITEM_NM = IV_ITEM_NM
           ,   UPD_DT = SYSDATE 
          WHERE ITEM_CD = IV_ITEM_CD;
    END IF;

    SELECT REG_DT INTO V_REG_DT
      FROM TB_ITEM 
     WHERE ITEM_CD = IV_ITEM_CD;

    OV_REG_DT := V_REG_DT;
    COMMIT;
    EXCEPTION
        WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE(&#39;예상치 못한 에러가 발생했습니다.&#39;);
        ROLLBACK;
    RETURN;
END PR_ITEM_MERGE;</code></pre>
<p>테이블에 data를 추가하는 procedure문 만약 이미 있는 테이블이라면 이름만 update하는 procedure이다.</p>
<p>PROCEDURE 실행 문</p>
<pre><code class="language-sql">#1. out변수가 있는 경우
DECLARE 
    V_REG_DT DATE;
BEGIN
    PR_ITEM_MERGE(&#39;A0006&#39;, &#39;삼성 갤럭시&#39;, V_REG_DT);
    DBMS_OUTPUT.PUT_LINE(&#39;프로시저 결과 값&#39; || V_REG_DT);
END;

#2. out변수가 없는 경우
CALL PR_ITEM_MERGE(&#39;A0006&#39;, &#39;삼성 갤럭시`);</code></pre>
<h3 id="6-merge문">6. MERGE문</h3>
<pre><code class="language-sql">MERGE
    INTO TB_ITEM ITEM -- UPDATE OR INSERT 대상 테이블
USING DUAL    -- 비교군 테이블(비교할 테이블이 없을 경우 DUAL을 쓴다)
    ON (ITEM.ITEM_CD = &#39;A0005&#39;)
WHEN MATCHED THEN -- 조건에 일치하는게 존재한다면 UPDATE를 
    UPDATE 
        SET ITEM.ITEM_NM = &#39;아이오닉7&#39;
        ,    UPD_DT = SYSDATE 
WHEN NOT MATCHED THEN -- 조건에 일치하는게 없다면 INSERT를 
    INSERT(ITEM_CD, ITEM_NM, REG_DT, UPD_DT)
    VALUES(&#39;A0005&#39;, &#39;아이오닉7&#39;, SYSDATE, SYSDATE);

COMMIT;</code></pre>
<h3 id="7-group-by-활용하기">7. GROUP BY 활용하기</h3>
<p><strong><em>example 1.</em></strong>
고객에게 설치 완료한 상품 수는 총 몇건인가?</p>
<pre><code class="language-sql">select count(*) from tb_ord where ord_stat_cd = &#39;D&#39;;</code></pre>
<p><strong><em>example 2.</em></strong>
설치 완료 건 중 가장 최근에 등록된건과 가장 최초로 등록된 건의 일자는 각각 어떻게 되는가?</p>
<pre><code class="language-sql">select max(reg_dt), min(reg_dt) from tb_ord where ord_stat_cd = &#39;D&#39;;</code></pre>
<p><strong><em>example 3.</em></strong>
그럼 모든 주문 건 중 설치 완료되지 않고 설치 취소 되거나 주문 접수된 건은 얼마나 되지?</p>
<pre><code class="language-sql">select count(*) from tb_ord where ord_stat_cd in (&#39;B&#39;, &#39;E&#39;);</code></pre>
<p><strong><em>example 4.</em></strong>
모든 주문 건 중 설치 완료디지 않고 설치 취소 되거나 주문 접수된 건은 각각 얼마나 되지?</p>
<pre><code class="language-sql">SELECT ord_stat_cd, count(*) FROM tb_ord WHERE ord_stat_cd IN (&#39;B&#39;, &#39;E&#39;)
GROUP BY ord_stat_cd;</code></pre>
<p><img src="https://velog.velcdn.com/images/free_mill/post/69de4af8-caf8-41f3-9bd6-39599183fcd3/image.png" alt="">
결과물 그런데 이렇게 보여주면 사용자들이 B, E가 무엇인지 파악할 수 없다. 이럴때 보여주는것이 function이다.</p>
<pre><code class="language-sql">SELECT fn_code_nm(&#39;001&#39;, ord_stat_cd), count(*) FROM tb_ord WHERE ord_stat_cd IN (&#39;B&#39;, &#39;E&#39;)
GROUP BY ord_stat_cd;</code></pre>
<p>고객에게 설치 완료된 건 중 가장 인기있는 상품부터 순서대로 리스트를 가져와 보겠나?</p>
<p><strong><em>example 5.</em></strong></p>
<pre><code class="language-sql">SELECT ITEM_CD, COUNT(*) AS CNT FROM TB_ORD WHERE ORD_STAT_CD=&#39;D&#39; GROUP BY ITEM_CD ORDER BY CNT DESC;</code></pre>
<p><img src="https://velog.velcdn.com/images/free_mill/post/dd917bb3-bbea-4346-b2c6-06a75208bf08/image.png" alt=""></p>
<p>결과물 그런데 이렇게 보여주면 사용자들이 B, E가 무엇인지 파악할 수 없다. join문 사용!</p>
<pre><code class="language-sql">SELECT item.ITEM_NM, COUNT(*) AS CNT FROM TB_ORD ord LEFT JOIN tb_item item ON ord.item_cd = item.item_cd WHERE ORD.ORD_STAT_CD=&#39;D&#39; GROUP BY item.item_nm ORDER BY CNT DESC;</code></pre>
<p><strong><em>example 6.</em></strong>
1건 이하의 제품은 안봤으면 하네..</p>
<pre><code class="language-sql">SELECT ITEM.ITEM_NM, COUNT(*) AS CNT FROM TB_ORD ORD LEFT JOIN TB_ITEM ITEM ON ORD.ITEM_CD = ITEM.ITEM_CD WHERE ORD.ORD_STAT_CD = &#39;D&#39; GROUP BY ITEM.ITEM_NM HAVING 
COUNT(*) &gt; 1 ORDER BY CNT DESC;</code></pre>
<p><strong><em>example 7.</em></strong>
어떤 고객이 우리 제품을 몇 건씩 쓰고 있는지도 알 수 있나?</p>
<pre><code class="language-sql">SELECT CUST.CUST_NM,
       ITEM.ITEM_NM, 
       COUNT(*) AS CNT 
FROM TB_ORD ORD 
LEFT JOIN TB_ITEM ITEM ON ORD.ITEM_CD = ITEM.ITEM_CD 
LEFT JOIN TB_CUST CUST ON ORD.CUST_CD = CUST.CUST_CD 
WHERE ORD.ORD_STAT_CD = &#39;D&#39;
GROUP BY CUST.CUST_NM, ITEM.ITEM_NM
ORDER BY CUST.CUST_NM;</code></pre>
<p><strong><em>example 8.</em></strong>
우리 공통 코드 테이블에서 공통 코드명 별로 해당 공통 코드에 속한 코드명들을 &#39;,&#39; 구분 지어 나열해 출력해 줄 수 있겠나?</p>
<pre><code class="language-sql">SELECT CD_NM, LISTAGG(CD_NM1, &#39;,&#39;)
    FROM TB_CD_MST
GROUP BY CD_NM</code></pre>
<p><strong><em>example 9.</em></strong>
우리 주문 건들 중에서 설치 상태 구분 없이 주문 들어온 모든 건을 제품명과 제품별 가장 처음으로 등록된 시간으로 간추려 보여 줄 수 있겠나?</p>
<pre><code class="language-sql">SELECT ITEM_CD, MIN(REG_DT)
    FROM TB_ORD
     GROUP BY ITEM_CD</code></pre>
<p><strong><em>example 10.</em></strong>
그 일자에 접수된 주문 번호는 각각 무엇인가?</p>
<pre><code class="language-sql">SELECT ORD2.ITEM_CD, ORD2.REG_DT, ORD2.ORD_NO 
    FROM(SELECT ITEM_CD,
    MIN(REG_DT),
    FROM TB_ORD 
    GROUP BY ITEM_CD) ORD
    LEFT JOIN TB_ORD ORD2 ON ORD.ITEM_CD = ORD2.ITEM_CD
    AND ORD.REG_DT = ORD.REG_DT</code></pre>
<p>이것의 코드 길이를 줄여줄 수 있는 함수로 keep dense_rank함수가 있다.</p>
<pre><code class="language-sql">SELECT ITEM_CD
,MIN(REG_DT)
,MIN(ORD_NO) KEEP(DENSE_RANK FIRST ORDER BY REG_DT)
FROM TB_ORD
GROUP BY ITEM_CD
ORDER BY ITEM_CD;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[CI/CD 자동화 도구의 이해]]></title>
            <link>https://velog.io/@free_mill/CICD-%EC%9E%90%EB%8F%99%ED%99%94-%EB%8F%84%EA%B5%AC%EC%9D%98-%EC%9D%B4%ED%95%B4</link>
            <guid>https://velog.io/@free_mill/CICD-%EC%9E%90%EB%8F%99%ED%99%94-%EB%8F%84%EA%B5%AC%EC%9D%98-%EC%9D%B4%ED%95%B4</guid>
            <pubDate>Thu, 09 Feb 2023 11:17:57 GMT</pubDate>
            <description><![CDATA[<h3 id="cicd">CI/CD</h3>
<p>CI/CD는 개발자 및 팀에 의해서 개발된 결과물에 대해 지속적인 통합/배포 하는 프로세스를 말함.</p>
<ul>
<li>CI(Continuous Integration)</li>
<li>CD(Continuous Delivery) -- CI에서 통합된 데이터를 검증하고 최종 배포를 수동으로 수행하는 것</li>
<li>CD(Continuous Deployment) -- 자동으로 전 과정을 배포하는 것을 deployment라고 한다.</li>
</ul>
<img src="https://velog.velcdn.com/images/free_mill/post/374f4ad0-68e7-41c7-97de-eba543203663/image.png">


<h4 id="ci의-역할">CI의 역할</h4>
<img src="https://velog.velcdn.com/images/free_mill/post/33d3d224-902a-4c47-9897-1efd9ae6c935/image.png">
개발자들이 각자 개발한 코드를 SCM(Source Control Management system)에 업로드(Commit) 하게 된다.
그런데 같은 코드를 여러명의 사용자가 사용하는 경우에는 코드의 버전 관리가 필요하다.
이렇게 버전관리와 코드의 Tracking을 해주는 프로그램을 SCM이라고 한다.

<p><img src="https://velog.velcdn.com/images/free_mill/post/002e5114-58ee-4945-8a5f-e19a8bcb56d1/image.png" alt="">
다음으로 CI도구인 Jenkins는 SCM에 저장된 코드를 불러온 후 소스 코드에 Build, Test, Package하는 작업을 처리해준다.</p>
<p><img src="https://velog.velcdn.com/images/free_mill/post/cf03f905-5754-489e-8ee2-d400fcd7f7b5/image.png" alt="">
그럼 이제 패키지한 자료를 가지고 원했던 환경 서버에 배포를 할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Baekjoon 11659 : 구간 합 구하기]]></title>
            <link>https://velog.io/@free_mill/Baekjoon-11659-%EA%B5%AC%EA%B0%84-%ED%95%A9-%EA%B5%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@free_mill/Baekjoon-11659-%EA%B5%AC%EA%B0%84-%ED%95%A9-%EA%B5%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 09 Feb 2023 03:42:36 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p><a href="https://www.acmicpc.net/problem/11659">https://www.acmicpc.net/problem/11659</a>
<img src="https://velog.velcdn.com/images/free_mill/post/dee67f16-1b0a-479a-8522-7e73b04d0be6/image.png" alt="">
<img src="https://velog.velcdn.com/images/free_mill/post/b8057113-9ae5-4a73-897e-a81037cdb214/image.png" alt=""></p>
<h3 id="문제풀이">문제풀이</h3>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class baekjoon11659 {
    public static void main(String arg[]) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer stringTokenizer = new StringTokenizer(bufferedReader.readLine());

        int suNo = Integer.parseInt(stringTokenizer.nextToken());
        int quizNo = Integer.parseInt(stringTokenizer.nextToken());
        long[] S = new long[suNo + 1];

        stringTokenizer = new StringTokenizer(bufferedReader.readLine());
        for (int i = 1; i &lt;= suNo; i++) {
            S[i] = S[i - 1] + Integer.parseInt(stringTokenizer.nextToken());
        }

        for (int q = 0; q &lt; quizNo; q++) {
            stringTokenizer = new StringTokenizer(bufferedReader.readLine());
            int i = Integer.parseInt(stringTokenizer.nextToken());
            int j = Integer.parseInt(stringTokenizer.nextToken());

            System.out.println(S[j] - S[i - 1]);
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Baekjoon 1546 : 평균 구하기]]></title>
            <link>https://velog.io/@free_mill/Baekjoon-1546-%ED%8F%89%EA%B7%A0-%EA%B5%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@free_mill/Baekjoon-1546-%ED%8F%89%EA%B7%A0-%EA%B5%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 08 Feb 2023 03:38:30 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p><a href="https://www.acmicpc.net/problem/1546">https://www.acmicpc.net/problem/1546</a></p>
<p><img src="https://velog.velcdn.com/images/free_mill/post/84a7718a-6b9b-4655-a5db-917f29cb88bf/image.png" alt="">
<img src="https://velog.velcdn.com/images/free_mill/post/39fcb836-8fac-41cc-9a39-875ee4c56bb7/image.png" alt=""></p>
<h3 id="문제풀이">문제풀이</h3>
<pre><code class="language-java">public class baekjoon1546 {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int N = sc.nextInt();
        int A[] = new int[N];

        for (int i = 0; i &lt; N; i++) {
            A[i] = sc.nextInt();
        }

        long sum = 0;
        long max = 0;
        for (int i = 0; i &lt; N; i++) {
            if (A[i] &gt; max) max=A[i];
            sum = sum + A[i];
        }

        System.out.println(sum * 100.0 / max / N);
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Baekjoon 11720 : 숫자의 합]]></title>
            <link>https://velog.io/@free_mill/Baekjoon-11720-%EC%88%AB%EC%9E%90%EC%9D%98-%ED%95%A9</link>
            <guid>https://velog.io/@free_mill/Baekjoon-11720-%EC%88%AB%EC%9E%90%EC%9D%98-%ED%95%A9</guid>
            <pubDate>Tue, 07 Feb 2023 03:32:09 GMT</pubDate>
            <description><![CDATA[<h3 id="배열의-특징">배열의 특징</h3>
<ol>
<li>인덱스를 사용하여 값에 바로 접근할 수 있다.</li>
<li>새로운 값을 삽입하거나 특정 인덱스에 있는 값을 삭제하기 어렵다. 값    을 삽입하거나 삭제하려면 해당 인덱스 주변에 값을 이동시키는 과정이 
필요하다.</li>
<li>배열의 크기는 선언할 때 지정할 수 있으며, 한 번 선언하면 크기를 늘    리거나 줄일 수 없다.</li>
<li>구조가 간단하므로 코딩 테스트에서 많이 사용한다.</li>
</ol>
<h3 id="리스트의-특징">리스트의 특징</h3>
<ol>
<li>인덱스가 없으므로 값에 접근하려면 Head 포인트로부터 순서대로 접근해    야 한다. 다시 말해 값에 접근하는 속도가 느리다.</li>
<li>포인터로 연결되어 있으므로 데이터를 삽입하거나 삭제하는 연산 속도가    빠르다.</li>
<li>선언할 때 크기를 별도로 지정하지 않아도 된다. 다시 말해 리스트의 크
기는 정해져 있지 않으며, 크기가 변하기 쉬운 데이터를 다룰 때 적절하
다.</li>
<li>포인터를 저장할 공간이 필요하므로 배열보다 구조가 복잡하다.</li>
</ol>
<h2 id="문제">문제</h2>
<p><a href="https://www.acmicpc.net/problem/11720">https://www.acmicpc.net/problem/11720</a>
<img src="https://velog.velcdn.com/images/free_mill/post/6e4a14a8-d609-40f0-bb10-ba43d0ab6c5b/image.png" alt=""><img src="https://velog.velcdn.com/images/free_mill/post/a3a5dcbd-c330-4b99-b1c9-825ec0b9d73d/image.png" alt=""></p>
<h2 id="문제-풀이">문제 풀이</h2>
<pre><code class="language-java">import java.util.Scanner;

public class baekjoon11720 {
    public static void main(String args[]){
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        String sNum = sc.next();
        char[] cNum = sNum.toCharArray();

        int sum = 0;
        for (int i = 0; i &lt; cNum.length; i++) {
            sum += cNum[i] - &#39;0&#39;;
        }

        System.out.println(sum);

    }
}</code></pre>
<p><em>java에서의 형 변환</em></p>
<ol>
<li>String형 -&gt; 숫자형(int, double, float, long, short)
String sNum = &quot;1234&quot;;
int i1 = Integer.parseInt(sNum);
int i2 = Integer.valueOf(sNum);
double d1 = Double.parseDouble(sNum);
double d2 = Double.valueOf(sNum);
float f1 = Float.parseFloat(sNum);
float f2 = Float.valueOf(sNum);
long l1 = Long.parseLong(sNum);
long l2 = Long.valueOf(sNum);
short s1 = Short.parseShort(sNum);
short s2 = Short.valueOf(sNum);</li>
</ol>
<ol start="2">
<li>숫자형(int, double, float, long, short) -&gt; String형
int i = 1234;
String i1 = String.valueOf(i);
String i2 = String.toString(i);
double d = 1.23;
String d1 = String.valueOf(d);
String d2 = Double.toString(d);
float f = (float) 1.23;
String f1 = String.valueOf(f);
String f2 = Float.toString(f);
long l = 1234;
String l1 = String.valueOf(l);
String l2 = Long.toString(l);
short s = 1234;
String s1 = String.valueOf(s);
String s2 = Short.toString(s);</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Waterfall vs Agile]]></title>
            <link>https://velog.io/@free_mill/Waterfall-vs-Agile</link>
            <guid>https://velog.io/@free_mill/Waterfall-vs-Agile</guid>
            <pubDate>Mon, 06 Feb 2023 11:10:18 GMT</pubDate>
            <description><![CDATA[<h3 id="warterfall-vs-agile">Warterfall vs agile</h3>
<img src="https://velog.velcdn.com/images/free_mill/post/8f9fc632-15ee-4445-aeef-3cd1ed10eff6/image.png" width="500">

<p><strong>WATERFALL :</strong> 전통적인 개발 방식 중 하나이다. 순차적인 프로젝트 관리 방법론으로도 불린다. </p>
<p>순차적인 흐름은 
요구사항정의 -&gt; 분석/설계 -&gt; 구현 -&gt; 테스트 -&gt; 운영 순으로 이어지고 각 단계는 이전 단계가 완료된 후에만 진행되고 이전 단계로 역행할 수 없다.</p>
<p>WATERFALL은 지금도 많이 쓰인다.</p>
<p><strong>AGILE :</strong> WATERFALL방식의 지나친 계획 의존성과 형식성으로 인한 효율성 문제를 최소화 하는 과정에서 급부상한 방법론.
프로젝트의 생명주기 동안 반복적인 과정 속에서 프로젝트를 점점 향상시키고 사용자의 Needs를 만족시켜가는 방법론.</p>
<h5 id="agile-선언문">AGILE 선언문</h5>
<img src="https://velog.velcdn.com/images/free_mill/post/8aca109b-4c1f-4973-bc71-17c62bf2d48c/image.png" width="600">


<br>

<h3 id="warterfall-vs-agile-vs-devops">Warterfall vs agile vs DevOps</h3>
<img src="https://velog.velcdn.com/images/free_mill/post/72305c01-dd87-45e1-8358-af09862a784a/image.png" width="600">
2000년대 까지는 AGILE 방법론이 도입되었던 시기이며 분산, 가상 환경이 널리 사용되었다.

<p>그리고 최근 IT Sysytem에서 나타나는 가장 큰 트렌드는 DevOps Microservices Containers Cloud라는 키워드 들이다. 이러한 키워드들을 묶어 Cloud Native Architecture라고 한다.</p>
<p>클라우드 네이티브 아키텍처 및 기술은 클라우드에서 빌드되고 클라우드 컴퓨팅 모델을 최대한 활용하는 워크로드를 디자인, 생성 및 운영하는 접근 방식이다.</p>
<p>클라우드 네이티브 기술을 통해 조직은 퍼블릿, 프라이빗 및 하이브리드 클라우드와 같은 최식 동적 환경에서 확장 가능한 애플리케이션을 빌드하고 실행할 수 있다. 컨테이너, 서비스 메시, 마이크로 서비스, 변경할 수 없는 인프라 및 선언적 API는 이 접근 방식을 예로 들 수 있다.</p>
<p>이러한 기술을 사용하면 복원력, 관리 가능 및 관찰 가능한 느슨하게 결합된 시스템을 사용할 수 있다. 강력한 자동화와 결합되어 엔지니어는 최소한의 수고로 자주 예측 가능하게 높은 영향을 미치는 변경을 할 수 있다.</p>
<p>출처 : Inflearn, Jenkins를 이용한 CI/CD Pipeline 구축</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[전자정부프레임워크의 Spring MVC 패턴]]></title>
            <link>https://velog.io/@free_mill/%EC%A0%84%EC%9E%90%EC%A0%95%EB%B6%80%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC%EC%9D%98-Spring-MVC-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@free_mill/%EC%A0%84%EC%9E%90%EC%A0%95%EB%B6%80%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC%EC%9D%98-Spring-MVC-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Sun, 08 Jan 2023 11:29:20 GMT</pubDate>
            <description><![CDATA[<h3 id="전자정부프레임워크의-mvc-패턴">전자정부프레임워크의 MVC 패턴</h3>
<img src="https://velog.velcdn.com/images/free_mill/post/3d80c55e-78e4-40b3-b79d-9b479deef825/image.png"  width="600"/>
전자정부프레임워크의 구조는 기본적인 MVC구조와 동일하다.



<h3 id="mvc패턴-흐름">MVC패턴 흐름</h3>
<h5 id="webxmlsrc--main--webapp--web-inf--webxml">[web.xml]src &gt;&gt; main &gt;&gt; webapp &gt;&gt; WEB-INF &gt;&gt; web.xml</h5>
<pre><code>49    &lt;servlet-mapping&gt;
        &lt;servlet-name&gt;action&lt;/servlet-name&gt;
        &lt;url-pattern&gt;*.do&lt;/url-pattern&gt;
52    &lt;/servlet-mapping&gt;

&lt;!-- .do로 끝나는 url을 action serlvet으로 mapping해 놓는다. --&gt;

39    &lt;servlet&gt;
        &lt;servlet-name&gt;action&lt;/servlet-name&gt;
        &lt;servlet-class&gt;org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt;
        &lt;init-param&gt;
            &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
            &lt;param-value&gt;/WEB-INF/config/egovframework/springmvc/dispatcher-servlet.xml&lt;/param-value&gt;
        &lt;/init-param&gt;
        &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
47    &lt;/servlet&gt;

&lt;!-- action 서브릿은 이제 org.springframework.web.servlet.DispatcherServlet을 사용한다.
&lt;!-- DispatcherServlet은 이제 init-param을 통해서 설정파일이 /WEB-INF/config/egovframework/springmvc/dispatcher-servlet.xml 경로에 있다. --&gt;</code></pre><h5 id="servletxml-web-inf--config--egovframework--springmvc--dispatcher-servletxml">[servlet.xml] WEB-INF &gt;&gt; config &gt;&gt; egovframework &gt;&gt; springmvc &gt;&gt; dispatcher-servlet.xml</h5>
<pre><code>10  &lt;context:component-scan base-package=&quot;egovframework&quot;&gt;
        &lt;context:include-filter type=&quot;annotation&quot; expression=&quot;org.springframework.stereotype.Controller&quot;/&gt;
        &lt;context:exclude-filter type=&quot;annotation&quot; expression=&quot;org.springframework.stereotype.Service&quot;/&gt;
        &lt;context:exclude-filter type=&quot;annotation&quot; expression=&quot;org.springframework.stereotype.Repository&quot;/&gt;
14  &lt;/context:component-scan&gt;

&lt;!-- controller를 bean으로 등록한다.--&gt;

16  &lt;bean class=&quot;org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter&quot;&gt;
        &lt;property name=&quot;webBindingInitializer&quot;&gt;
            &lt;bean class=&quot;egovframework.example.cmmn.web.EgovBindingInitializer&quot;/&gt;
        &lt;/property&gt;
    &lt;/bean&gt;
    &lt;bean class=&quot;org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping&quot;&gt;
        &lt;property name=&quot;interceptors&quot;&gt;
            &lt;list&gt;
                &lt;ref bean=&quot;localeChangeInterceptor&quot; /&gt;
            &lt;/list&gt;
        &lt;/property&gt;
27  &lt;/bean&gt;

&lt;!-- 이것들을 통해 @RequestMapping의 Annotation을 지원한다. --&gt;


47  &lt;bean class=&quot;org.springframework.web.servlet.view.UrlBasedViewResolver&quot; p:order=&quot;1&quot;
        p:viewClass=&quot;org.springframework.web.servlet.view.JstlView&quot;
        p:prefix=&quot;/WEB-INF/jsp/egovframework/example/&quot; p:suffix=&quot;.jsp&quot;/&gt;

&lt;!-- viewResolver가 등록돼 있기 때문에 .jsp파일을 반환할 수 있다. --&gt;</code></pre><h4 id="webxml-servletxml-참조">[web.xml], [servlet.xml] 참조</h4>
<ol>
<li>사용자가 url(~~/main.do)요청</li>
<li>web.xml에 정의된 servlet-mapping의 url-pattern으로 servlet선택</li>
<li>context:component-scan으로 Controller클래스를 bean으로 등록</li>
<li>Controller와 url을 매핑시켜주는 RequestMappingHandlerMapping으로 Controller로 이동</li>
<li>@Controller가 붙은 Controller클래스 중 @RequestMapping(value=&quot;/main.do&quot;)에 해당하는 함수 호출</li>
<li>@RequestMapping(value=&quot;/main.do&quot;)에 해당하는 함수는 Service로직을 실행 및 모델 데이터를 설정하고 뷰 이름을 DispatcherServlet에게 전달</li>
<li>DispatcherServlet은 Controller에게 받은 View를 선택하기 위해 View Resolver의 도움을 받음</li>
<li>모델 데이터를 요청된 .jsp로 전달</li>
</ol>
<h5 id="출처--inflearn-웹개발-코스-스프링-프레임워크전자정부-표준프레임워크">출처 : Inflearn, 웹개발 코스 [스프링 프레임워크+전자정부 표준프레임워크]</h5>
]]></description>
        </item>
    </channel>
</rss>