<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>kor_hoonie.log</title>
        <link>https://velog.io/</link>
        <description>사우루스 팡팡!</description>
        <lastBuildDate>Sun, 19 Mar 2023 08:26:49 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>kor_hoonie.log</title>
            <url>https://velog.velcdn.com/images/kor_hoonie/profile/c64e7814-fda6-477b-83e8-46511464792a/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. kor_hoonie.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/kor_hoonie" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[연관관계 매핑 - 기본]]></title>
            <link>https://velog.io/@kor_hoonie/basic-relationship-mapping</link>
            <guid>https://velog.io/@kor_hoonie/basic-relationship-mapping</guid>
            <pubDate>Sun, 19 Mar 2023 08:26:49 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 포스트는 인프런 김영한님의 &#39;자바 ORM 표준 JPA 프로그래밍 - 기본편&#39;의 내용을 정리한 포스트 입니다.</p>
</blockquote>
<h2 id="연관관계-매핑에-대한-이해">연관관계 매핑에 대한 이해</h2>
<ul>
<li>객체와 테이블의 연관관계의 차이를 이해</li>
<li>객체의 <strong>참조</strong>와 테이블의 <strong>외래키</strong>를 매핑하는 과정</li>
<li>용어에 대한 이해<ul>
<li>방향: 양방향, 단방향</li>
<li>다중성: 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)</li>
<li>연관관계의 주인(Owner): 객체의 양방향 관계에서는 관리 주인이 필요</li>
</ul>
</li>
</ul>
<h2 id="객체와-테이블의-연관관계-차이">객체와 테이블의 연관관계 차이</h2>
<ul>
<li><p>객체는 참조를 사용해서 연관관계를 찾는다.</p>
</li>
<li><p>테이블은 외래 키로 조인을 해서 연관관계를 찾는다.</p>
</li>
<li><p>객체와 테이블에는 이러한 큰 차이가 존재한다.</p>
<h2 id="다대일n1-단방향-연관관계">다대일(N:1) 단방향 연관관계</h2>
</li>
<li><p><code>Member</code>와 <code>Team</code> 엔티티를 N:1 단방향 연관관계 매핑을 해보자</p>
</li>
<li><p><code>Member</code>는 하나의 <code>Team</code>에만 속할 수 있다.
<img src="https://velog.velcdn.com/images/kor_hoonie/post/91209fa7-722e-43d9-8a63-f85c4c7cf7ac/image.png" alt=""></p>
<pre><code class="language-java">@Entity
public class Member {
  @Id @GeneratedValue
  private Long id;
  private String name;

  @ManyToOne
  @JoinColumn(name = &quot;TEAM_ID&quot;)
  private Team team;

  ...
</code></pre>
</li>
</ul>
<p>}</p>
<pre><code>- ```Member```의 입장에서는 ```Team```은 ```@ManyToOne```이다.
- 이때 테이블의 외래 키는 &quot;TEAM_ID&quot;이므로 ```@JoinColumn```에 외래키의 정보를 넘겨줘야 한다.
- 단방향 연관관계 매핑을 하였으므로 이제 양방향 연관관계 매핑에 대해 알아보자.

## 다대일(N:1) 양방향 연관관계
- ```Member```와 ```Team``` 엔티티를 N:1 양방향 연관관계 매핑을 해보자
- ```Member```는 하나의 ```Team```에만 속할 수 있고 ```Team```은 여러 명의 ```Memeber```를 가질 수 있다.
![](https://velog.velcdn.com/images/kor_hoonie/post/c3be89e6-190b-4cdf-9220-fed7ffa4a773/image.png)
- ```Member```엔티티는 앞서 연관관계 매핑을 해주었으므로 이제 ```Team```엔티티를 수정해보자.
```java
@Entity
public class Team {
    @Id @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = &quot;team&quot;)
    private List&lt;Member&gt; members = new ArrayList&lt;&gt;();

    ...

}</code></pre><ul>
<li><code>Team</code>의 입장에서 <code>Member</code>를 여러 개 가질 수 있으므로 리스트로 선언해주고 <code>@OneToMany</code>어노테이션을 달아준다. 이때 연관관계에 대한 정보를 <code>MappedBy</code>를 활용해서 <code>Member</code>의 <code>team</code>과 연관관계에 있음을 알려준다.<h3 id="연관관계의-주인과-mappedby">연관관계의 주인과 mappedBy</h3>
<h4 id="객체와-테이블이-관계를-맺는-방식의-차이">객체와 테이블이 관계를 맺는 방식의 차이</h4>
</li>
<li>객체: 연관관계 -&gt; 2개<ul>
<li>회원 -&gt; 팀: 1개 (단방향)</li>
<li>팀 -&gt; 회원: 1개 (단방향)</li>
<li>즉, 객체의 경우 양방향은 2개의 단방향 관계가 합쳐진 것이다.</li>
</ul>
</li>
<li>테이블: 연관관계 -&gt; 1개<ul>
<li>회원 &lt;-&gt; 팀: 1개 (양방향)</li>
<li>테이블의 경우엔 외래 키 하나로 두 테이블의 연관관계를 관리한다.</li>
</ul>
</li>
</ul>
<p><strong>객체의 경우 테이블의 외래 키를 관리하는 주인을 정해줘야한다.</strong></p>
<h4 id="연관관계의-주인owner">연관관계의 주인(Owner)</h4>
<ul>
<li>양방향 매핑 규칙<ul>
<li>객체의 두 관계 중 하나를 연관관계의 주인으로 설정</li>
<li><strong>연관관계의 주인만이 외래 키를 관리(등록, 수정)</strong></li>
<li><strong>주인이 아닌 쪽은 읽기만 가능</strong></li>
<li>주인은 mappedBy 속성 사용X</li>
<li>주인이 아닌 쪽은 mappedBy 속성으로 주인을 지정</li>
</ul>
</li>
<li>그러면 누구를 주인으로 하면 좋을까?<ul>
<li><strong>테이블에서 외래 키가 있는 곳을 주인으로 정하자!</strong></li>
<li>현재의 예시에서는 <code>Member</code>테이블이 외래 키를 갖고 있으므로 주인으로 정한다.<h4 id="양방향-매핑시-연관관계의-주인에-값을-입력해야한다">양방향 매핑시 연관관계의 주인에 값을 입력해야한다.</h4>
</li>
</ul>
</li>
<li>오류를 방지하기 위해 두 엔티티에 모두 값을 입력해주면 좋다.<pre><code class="language-java">Team team = new Team();
team.setName(&quot;teamA&quot;);
em.persist(team);
</code></pre>
</li>
</ul>
<p>Member member = new Member();
member.setName(&quot;member1&quot;);</p>
<p>team.getMembers().add(member);</p>
<p>// 연관관계의 주인에 값을 반드시 설정해줘야한다.
members.setTeam(team);</p>
<p>em.persist(member);</p>
<p>```</p>
<h4 id="양방향-연관관계에서-주의해야할-점">양방향 연관관계에서 주의해야할 점</h4>
<ul>
<li>순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자.</li>
<li>따라서 연관관계의 주인에 값을 입력하는 과정에서 반대편에도 값이 입력될 수 있도록 하는 &#39;연관관계 편의 메소드&#39;를 생성해서 잠재적인 오류를 최소화하자.</li>
<li>양방향 매핑시에 무한 루프를 조심하자.<ul>
<li>toString(), lombok, JSON생성 라이브러리</li>
</ul>
</li>
</ul>
<h3 id="정리">정리</h3>
<ul>
<li>단방향 매핑만으로도 이미 연관관계 매핑은 완료<ul>
<li>우선 단방향 매핑으로만 연관관계들을 설정해주고 이후에 비즈니스 로직을 수행할 때 필요하다면 양방향 매핑을 설정해주자!</li>
</ul>
</li>
<li>양방향 매핑은 반대 방향으로 <strong>조회</strong>기능을 추가하는 것뿐이다!</li>
<li>JPQL에서 역방향으로 탐색할 일이 많다.</li>
<li>연관관계의 주인을 정하는 기준<ul>
<li><strong>연관관계의 주인은 테이블에서 외래 키를 갖고 있는 엔티티로 설정한다.</strong></li>
<li>비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안된다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[영속성 관리]]></title>
            <link>https://velog.io/@kor_hoonie/persistence-context</link>
            <guid>https://velog.io/@kor_hoonie/persistence-context</guid>
            <pubDate>Sun, 19 Mar 2023 07:26:22 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 포스트는 인프런 김영한님의 &#39;자바 ORM 표준 JPA 프로그래밍 - 기본편&#39;의 내용을 정리한 포스트 입니다.</p>
</blockquote>
<h2 id="영속성-컨텍스트">영속성 컨텍스트</h2>
<ul>
<li>JPA를 이해하는데 가장 중요한 개념</li>
<li>&#39;엔티티를 영구 저장하는 환경&#39;이라는 뜻</li>
<li>영속성 컨텍스트는 논리적인 개념</li>
<li>엔티티 매니저(entity manager)를 통해서 영속성 컨텍스트에 접근 가능</li>
</ul>
<h2 id="영속성-컨텍스트의-생명주기">영속성 컨텍스트의 생명주기</h2>
<ul>
<li><h3 id="비영속-new--transient">비영속 (new / transient)</h3>
<ul>
<li>영속성 컨텍스트와 전혀 관계없는 <strong>새로운</strong> 상태</li>
</ul>
</li>
<li><h3 id="영속-managed">영속 (managed)</h3>
<ul>
<li>영속성 컨텍스트에서 <strong>관리</strong>되는 상태</li>
</ul>
</li>
<li><h3 id="준영속-detached">준영속 (detached)</h3>
<ul>
<li>영속성 컨텍스트에서 관리되다가 <strong>분리</strong>된 상태</li>
</ul>
</li>
<li><h3 id="삭제-removed">삭제 (removed)</h3>
<ul>
<li><strong>삭제</strong>된 상태</li>
</ul>
</li>
</ul>
<h2 id="영속성-컨텍스트의-이점">영속성 컨텍스트의 이점</h2>
<ol>
<li>1차 캐시<ul>
<li>엔티티를 <code>persist</code>로 영속화하면 1차 캐시라는 곳에 저장이 된다. 이때 같은 엔티티를 조회하면 db에서 찾는 것이 아니라 1차 캐시에 저장되어 있는 엔티티를 조회한다. 만약 <code>find</code>를 통해서 찾는 엔티티가 1차 캐시에 존재하지 않으면 db에서 조회 후 1차 캐시에 저장한다.</li>
</ul>
</li>
<li>동일성(identity) 보장<ul>
<li>1차 캐시로 반복 가능한 읽기 등급의 트랜잭션 격리 수준을 db가 아닌 어플리케이션 차원에서 제공한다.</li>
</ul>
</li>
<li>트랜젝션을 지원하는 쓰기 지연(transactinal write-behind)<ul>
<li>엔티티를 <code>persist</code>해서 저장하는 과정에서 <code>persist</code>를 호출하는 순간에 db에 저장되는 것이 아니고 쓰기 지연 SQL 저장소에 INSERT쿼리가 저장되고 트랜젝션이 <code>commit</code>되는 순간 db에 쿼리를 날려서 INSERT쿼리가 실행된다.</li>
</ul>
</li>
<li>변경 감지(dirty checking)<ul>
<li>영속성 컨텍스트에 영속화 된 엔티티에 대해서 그 순간의 상태를 스냅샷으로 관리한다. 이때 <code>flush</code>가 호출되는 순간 스냅샷과 현재의 엔티티를 비교해서 변경 사항이 있다면(변경감지) 그때 UPDATE쿼리를 db에 날린다.</li>
<li><code>flush</code>란 영속성 컨텍스트의 변경사항을 데이터베이스에 반영하는 것이다. <code>flush</code>는 다음과 같은 경우에 호출된다.<ul>
<li>직접 호출 <code>em.flush()</code></li>
<li>트랜젝션 커밋 <code>transaction.commit()</code> -&gt; 플러시 자동 호출</li>
<li>JPQL 쿼리 실행 -&gt; 플러시 자동 호출</li>
</ul>
</li>
<li><code>flush</code>는 영속성 컨텍스트를 비우는 것은 아니다. 영속성 컨텍스트의 변경사항을 데이터베이스와 동기화하는 과정이고 영속성컨텍스트르 비우고 싶으면 <code>em.clear()</code>를 호출하면 됨.</li>
</ul>
</li>
<li>지연 로딩(lazy loading)</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[왜 JPA를 사용할까?]]></title>
            <link>https://velog.io/@kor_hoonie/why-jpa</link>
            <guid>https://velog.io/@kor_hoonie/why-jpa</guid>
            <pubDate>Sat, 18 Mar 2023 09:24:03 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 포스트는 인프런 김영한님의 &#39;자바 ORM 표준 JPA 프로그래밍 - 기본편&#39;의 내용을 정리한 포스트 입니다.</p>
</blockquote>
<h2 id="sql-중심적인-개발의-문제점">SQL 중심적인 개발의 문제점</h2>
<h3 id="1-반복적인-sql-작성">1. 반복적인 SQL 작성</h3>
<p>여러가지 형태의 데이터베이스가 있지만 주로 사용되는 데이터베이스는 관계형 데이터 베이스이다. 관계형 데이터베이스에서 데이터를 추가하고, 수정하고, 조회하고 삭제하는 과정에서 반복적인 SQL문을 작성해야하는 번거로움이 있는데 이런 과정은 전부 개발자의 몫이다. 또한 SQL을 작성하는 과정에서 발생할 수도 있는 오타에 의한 오류를 수정하는 것도 굉장히 리소스가 많이 필요하다.</p>
<h3 id="2-패러다임-불일치">2. 패러다임 불일치</h3>
<p>우리는 객체의 세상속에서 살고 있다. 따라서 주로 사용하는 언어도 객체 지향적인 언어이다. 하지만 데이터를 저장하고 관리하기 위해 관계형 데이터베이스를 사용한다. 바로 이 부분에서 패러다임의 불일치가 발생한다. 
구체적인 예로 연관관계에 있는 두 객체를 객체 세상에서 다루는 것과 관계형 데이터베이스 세상에서 다루는 것에 아주 큰 차이가 있다. 객체는 연관관계를 다루기 위해 <strong>참조</strong>를 사용하는데 테이블은 <strong>외래 키</strong>를 사용한다. 이런 불일치를 해결하기 위해 객체의 속성값으로 외래 키를 저장하는 방식을 생각해볼 수 있지만 이런 방식은 객체지향적이지 않다. 
또한 객체 세상에서는 자바 컬렉션에 <code>add()</code>를 사용해서 저장할 수가 있지만 관계형 DB세상에서는 값들을 하나씩 다 넣어준 다음 <code>insert</code>를 활용해서 값을 저장해야하는 불일치가 발생한다.
객체그래프 탐색 과정에서도 문제점을 찾아볼 수 있다. 객체 지향적으로 코드를 작성한 경우 참조를 통해서 자유롭게 객체를 탐색할 수 있다. 하지만 관계형 DB에서는 쿼리를 날리는 순간 탐색의 범위가 결정되므로 엔티티의 신뢰문제가 발생한다. 예를 들어 <code>Member</code>와 연관관계에 있는 <code>Team</code>이 있다고 하자. 이때 개발자가 쿼리를 날렸는데 과연 <code>Team</code>의 정보까지 갖고 왔다고 보장할 수 있을까? 이런 경우 당연히 <code>Team</code>이 있을거라고 생각하고 조회를 했을 때 null이 발생할 수도 있고 이를 해결하기 위해 쿼리를 날리는 코드까지 확인해봐야하는 불편함이 발생한다. 그렇다고 쿼리를 날리는 시점에서 모든 데이터를 다 가지고 오는 것은 당연히 성능 이슈가 발생한다.</p>
<p>위 상황들을 종합해보았을 때 객체지향적으로 설계를 할 수록 개발자가 해줘야하는 매핑작업만 늘어나는 비효율이 발생하는 것을 알 수 있다. 따라서 객체를 자바 컬렉션을 이용하여 다루듯이 DB에 저장할 수 없을까? 라는 의문점이 필연적으로 발생한다.
이를 해결하기 위해 나타난 기술이 <strong>JAVA PERSISTENCE API</strong>, JPA이다.</p>
<h2 id="jpa-소개">JPA 소개</h2>
<ul>
<li>JPA는 JAVA 진영의 <strong>ORM</strong> 기술 표준이다.
그렇다면 ORM은 뭘까?<h3 id="orm-object-relational-mapping이란">ORM (Object Relational Mapping)이란?</h3>
</li>
<li>객체는 객체대로 설계, 관계형 데이터베이스는 관계형 데이터베이스 대로 설계</li>
<li>ORM 프레임워크가 둘 사이에서 매핑을 해주므로써 패러다임 불일치를 해소해준다.</li>
</ul>
<h3 id="jpa는-어플리케이션과-jdbc-사이에서-동작한다">JPA는 어플리케이션과 JDBC 사이에서 동작한다.</h3>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/4870dbbc-51cc-44d2-b817-49145ca72b4c/image.png" alt=""></p>
<ul>
<li>개발자는 DB에 접근하기 위해 JDBC API를 사용해야하는데 JPA가 그 과정을 대신해준다.<h4 id="jpa-동작---저장">JPA 동작 - 저장</h4>
</li>
<li>Entity 분석</li>
<li>INSERT SQL 생성</li>
<li>JDBC API 사용</li>
<li><strong>패러다임 불일치 해결</strong><h4 id="jpa-동작---조회">JPA 동작 - 조회</h4>
</li>
<li>SELECT SQL 생성</li>
<li>JDBC API 사용</li>
<li>ResultSet 매핑</li>
<li><strong>패러다임 불일치 해결</strong></li>
</ul>
<h3 id="jpa의-탄생-과정">JPA의 탄생 과정</h3>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/82527aa3-06d9-477f-b157-88076a2b9d50/image.png" alt=""></p>
<ul>
<li>JPA는 오픈 소스인 Hibernate를 기반으로 한 자바 표준이다.</li>
<li>JPA는 <strong>표준 인터페이스들의 모음</strong>이고 이런 인터페이스들을 구현한 것들이 Hibernate, EclipseLink, DataNucleus가 있다. 이중에 가장 많이 사용되는 것은 Hibernate이다.</li>
<li>즉, 우리는 JPA의 인터페이스들의 Hibernate 구현체를 사용한다!</li>
</ul>
<h3 id="jpa를-왜-사용해야-하는가">JPA를 왜 사용해야 하는가?</h3>
<ul>
<li>SQL 중심적인 개발에서 객체중심적인 개발</li>
<li>생산성</li>
<li>유지보수성: 기존에는 필드 변경시 모든 SQL에 변경사항을 반영해야하지만 JPA는 필드만 변경하면 SQL은 자동으로 처리해준다.</li>
<li>패러다임의 불일치 해결: 상속, 연관관계, 객체 그래프 탐색</li>
<li>성능: 1차 캐시와 동일성, 트랜잭션을 지원하는 쓰기 지연, 지연 로딩</li>
<li>데이터 접근 추상화와 벤더 독립성</li>
<li>표준</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Comparable과 Comparator ]]></title>
            <link>https://velog.io/@kor_hoonie/Comparable-Comparator</link>
            <guid>https://velog.io/@kor_hoonie/Comparable-Comparator</guid>
            <pubDate>Wed, 22 Feb 2023 09:29:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>스스로 공부한 내용을 기록한 포스트로 틀린 부분이 있을 수 있으니 오류를 확인해보시고 적용하시길 <strong>강력히</strong> 당부드립니다.</p>
</blockquote>
<blockquote>
<p><a href="https://st-lab.tistory.com/243">https://st-lab.tistory.com/243</a> 를 참고하여 작성한 포스트입니다.</p>
</blockquote>
<h2 id="포스트의-시작-지점">포스트의 시작 지점</h2>
<p>알고리즘 문제를 풀다보면 정수들을 정렬해야하는 경우가 있다. primitive type인 <code>int</code>로 선언된 정수들은 <code>Arrays.sort()</code>로, wrapper class인 <code>Integer</code>로 선언된 정수들은 <code>Collections.sort()</code>로 손쉽게 오름차순으로 정렬할 수 있다. 
내림차순의 경우 <code>Collections.sort(list, Collections.reverseOrder())</code>로 정렬할 수 있는데 이것은 wrapper class인 경우만 가능하다. 따라서 배열을 내림차순으로 정렬하고 싶은 경우 <code>int[]</code>가 아닌 <code>Integer[]</code>로 선언해주어야 한다.</p>
<h2 id="문제-제기">문제 제기</h2>
<p>하지만 사용자가 임의로 선언한 객체를 원하는 기준으로 정렬하고 싶은 경우는 단순히 sort()를 사용할 수 없다. 이런 경우 자바에서 제공하는 <code>Comparable</code> 혹은 <code>Comparator</code> 인터페이스를 활용하여 객체를 정렬할 수 있다. 인터페이스는 특정 메서드의 구현을 <strong>강제</strong>하는 속성이 있기 때문에 두 인터페이스에서 반드시 정의해줘야하는 메서드가 있다. </p>
<p>이제 <code>Comparable</code>과 <code>Comparator</code>가 무엇인지, 둘의 차이는 어떤게 있는지, 활용은 어떻게 하는지 알아보자.</p>
<h2 id="comparable과-comparator">Comparable과 Comparator</h2>
<p>두 인터페이스를 사용하는 이유에 대해서 생각해보면 <strong>정렬</strong>하기 위해 사용한다고 생각할 것이다. 하지만 이것은 좁은 의미에서 맞는 말이라고 할 수 있다. </p>
<p>두 인터페이스를 사용하는 궁극적인 목표는 <strong>두 객체를 &#39;비교&#39;하기 위함</strong>이다. </p>
<p>예를 들어 나이와 키가 정보로 주어진 어떤 두 사람이 있다고 하자. 이 두 사람을 비교해 달라는 요청을 받았을 때 반드시 되물어야할 질문이 있다. &quot;나이로 비교할까요? 키로 비교할까요?&quot; , &quot;나이로 비교한다면 나이가 많은 사람을 우선순위에 둘까요? 적은 사람을 우선순위에 둘까요?&quot; 등 정해줘야할 기준이 모호할 것이다. 바로 이런 문제를 해결하기 위해 두 인터페이스가 필요하다. 
또한 반드시 구현해줘야하는 메서드의 파라미터도 차이가 있다.
<code>Comparable</code>의 경우 <code>compareTo(T o)</code>메서드에서 파라미터를 1개를 받고 <code>Comparator</code>의 경우 <code>compare(T o1 T o2)</code>메서드에서 파라미터 2개를 받는다. 대략적으로 감이 왔겠지만 compareTo()는 나 자신과 다른 객체를 비교하는 것이고 Compare()은 두 객체를 비교하는 것이다. 이제 기본적인 내용은 알아봤고 각각의 특징에 대해서 알아보자.</p>
<h2 id="comparable-인터페이스">Comparable 인터페이스</h2>
<pre><code class="language-java">public class Person implements Comparable&lt;Person&gt;{
        int age;
        int height;

        public Person (int age, int height) {
            this.age = age;
            this.height = height;
        }

        @Override
        public int compareTo(Person o) {
            return this.height - o.height;
        }
}</code></pre>
<ul>
<li><code>Person</code>이라는 사용자 정의 객체를 만들었다.</li>
<li><code>Comparable</code>인터페이스를 사용하였고 반드시 <code>CompareTo(T o)</code>메서드를 구현해주어야한다.</li>
<li>여기서 리턴 타입이 <code>int</code>임을 확인할 수 있는데 그렇다면 정수값을 가지고 어떻게 비교를 하는 것일까?<ul>
<li><strong>자기 자신</strong>과 상대방의 값을 비교하여 나타낸다.</li>
<li>즉, 자신의 height가 173이고 상대방의 height가 180이면 내가 7만큼 작은것이니 -7만큼 더 큰 것이고, 상대방의 height가 170이면 내가 +3만큼 더 크다고 할 수 있다.</li>
</ul>
</li>
<li><strong>(중요)</strong>다양한 방법으로 int값을 리턴할 수 있지만 위와 같이 작성하는 경우 반드시 고려해야하는 부분이 있다.<ul>
<li>만약 문제의 조건에 따라 리턴값이 <code>int</code>의 범위를 넘어갈 수도 있는 경우에는 오류가 발생한다. 그런 경우에는 case를 나눠 &lt;, ==, &gt;를 사용하여 리턴값이 int 범위가 되도록 하여야한다.</li>
<li>예를들어 나의 값이 1이고 상대방의 값이 -2,147,483,648 (<code>Integer.MIN_VALUE</code>)인 경우 리턴값이 2,147,483,649가 되므로 오류가 overflow 이슈가 발생한다.<h3 id="comparable정리">Comparable정리</h3>
</li>
</ul>
</li>
</ul>
<ol>
<li><p>나 자신과 상대방을 비교하는 방식</p>
</li>
<li><p><code>compareTo(T o1)</code>을 반드시 정의해줘야한다.</p>
<h2 id="comparator-인터페이스">Comparator 인터페이스</h2>
<pre><code class="language-java">public class Person implements Comparator&lt;Person&gt; {
     int age;
     int height;

     public Person (int age, int height) {
         this.age = age;
         this.height = height;
     }

     @Override
     public int compare(Person o1, Person o2) {
         return o1.height - o2.height;
     }
}</code></pre>
</li>
</ol>
<ul>
<li><code>Person</code>이라는 사용자 정의 객체를 만들었다.</li>
<li><code>Comparator</code>인터페이스를 사용하였고 반드시 <code>Compare(T o1, T o2)</code>메서드를 구현해주어야한다.</li>
<li>비교하는 방식은 <code>compareTo(T o)</code>과 비슷하지만 <code>compare(T o1, T o2)</code>는 자기자신이 아니라 서로 다른 두 객체를 비교한다는 점에서 차이가 있다. 즉, 비교를 하는 과정에서 자기 자신은 두 객체와 상관이 없다는 뜻이다.</li>
</ul>
<pre><code class="language-java">    Person personA = new Person(28, 183);
    Person personB = new Person(25, 177);
    Person personC = new Person(21, 173);

    int isBig = personA.compare(personB, personC);
    if(isBig &gt; 0){
        System.out.println(&quot;personB가 personC보다 큽니다.&quot;);
    } else if (isBig == 0){
        System.out.println(&quot;personB와 personC가 같습니다.&quot;);
    } else {
        System.out.println(&quot;personB가 personC보다 작습니다.&quot;);
    }

    // 출력결과
    // personB가 personC보다 큽니다.</code></pre>
<ul>
<li><p>위 코드에서 알 수 있듯이 어떤 두 객체를 비교하기 위해선 <code>compare</code>메서드를 호출해야하는데 비교 대상이 아닌 (personA)의 메서드에 파라미터로 personB, personC를 넣는 것이나 비교 대상 중 하나인 personB의 compare메서드를 호출해서 파라미터로 personB, personC를 넣는 것이나 결과는 똑같다.</p>
</li>
<li><p>다시말해 어떤 객체의 compare를 사용하던지 상관이 없다면 비교만을 위한 객체를 생성해서 compare메서드를 사용할 수도 있을 것이다.</p>
<ul>
<li><p>예를들어 <code>Person onlyForCompare = new Person(0,0)</code>을 만들고 <code>onlyForCompare.compare(personB, personC)</code>와 같이 사용할 수도 있을 것이다.</p>
</li>
<li><p>하지만 여기서 의문점이 들었을 것이다. 굳이 사용하지도 않는 객체를 만들어서 비교메서드만 사용하는것이 효율적일까?</p>
</li>
<li><p>해결하는 방법은 <strong>익명 객체(클래스)</strong>를 이용하는 것이다.</p>
<h3 id="익명-객체란">익명 객체란?</h3>
<p>익명 객체는 무엇일까? 쉽게 말해서 이름이 없는 객체(클래스)라는 것이다. 아래 코드들을 보면서 이해를 해보자.</p>
<pre><code class="language-java">public class Anonymous {
public static void main(String[] args) {

   Rectangle a = new Rectangle();

   // 익명객체1
   Rectangle anonymous1 = new Rectangle() {

       @Override
       int get() {
           return height;
       }
   };

   // 익명객체2
   Rectangle anonymous2 = new Rectangle() {
       int depth = 5;

       @Override
       int get() {
           return height * width * depth;
       }
   };

   System.out.println(a.get());
   System.out.println(anonymous1.get());
   System.out.println(anonymous2.get());

}

static class Rectangle {
   int width = 20;
   int height = 30;

   int get() {
       return width;
   }
}
}</code></pre>
</li>
</ul>
</li>
<li><p>일반적으로 객체를 생성하는 방식은 <code>Rectangle a = new Rectangle()</code>과 같은 방식일 것이다.</p>
</li>
<li><p>하지만 익명 객체는 <code>Rectangle anonymous1 = new Rectangle() { //..구현부..//}</code>로 생성한다. 거의 유사하지만 우리는 {} 안의 구현부에 집중해야한다.</p>
</li>
<li><p>일반적으로 우리가 객체를 구현한다고 하면 변수를 선언하고, 메서드를 구현하는 것이다. 이런 방식으로 객체를 구현할때는 반드시 이름을 지정해준다.</p>
</li>
<li><p>하지만 <code>Rectangle anonymous1 = new Rectangle() { //..구현부..// }</code>에서 구현부를 보면 우리가 일반적으로 변수를 선언하고, 메서드를 재정의(override)하는 부분들이 있기 때문에 Rectangle과는 다른 새로운 객체를 생성하였다. 하지만 어느 부분에서도 객체의 이름을 찾아볼 수 없다.</p>
</li>
<li><p>이렇게 클래스 이름이 정의되어 있지 않는 객체를 익명 객체라고 한다.</p>
</li>
</ul>
<blockquote>
<p>익명 객체에 대한 설명을 간략하게 하였는데 이해가 잘 되지 않는다면 포스트 맨 위 참고 페이지를 참조하길 바란다.</p>
</blockquote>
<h3 id="익명-객체를-활용한-comparator">익명 객체를 활용한 Comparator</h3>
<p>익명 객체에 대해 길게 알아본 이유에 대해서 다시 짚고 넘어가야한다. 우리는 두 객체를 비교해주는 Comparator를 구현해서 기능만 사용하고 싶기 때문에 익명 객체를 활용하기로 하였다.
분명히 Comparator는 인터페이스이기 때문에 구현(상속)할 대상이 존재한다. 즉 익명객체로 만들 수 있다는 것이다.
따라서 <strong>이름은 정의되지 않지만 Comparator를 구현하는 익명객체를 만들어서</strong> compare 메서드를 사용할 수 있을 것이다. 코드를 통해 구현방법을 알아보자.</p>
<pre><code class="language-java">import java.util.Comparator;

public class Test {

    public static void main(String[] args) {

        // 익명 객체 구현 1
        Comparator&lt;Person&gt; comp1 = new Comparator&lt;Person&gt;() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.age - o2.age;
            }
        };
    }

    // 익명 객체 구현 2
    public static Comparator&lt;Person&gt; comp2 = new Comparator&lt;Person&gt;() {
        @Override
        public int compare(Person o1, Person o2) {
            return o1.age - o2.age;
        }
    };
}


// 외부에서 익명 객체로 Comparator를 생성하기 때문에 클래스에서 Comparator를 구현할 필요가 없다.
class Person {
    int age;
    int height;

    public Person(int age, int height) {
        this.age = age;
        this.height = height;
    }
}</code></pre>
<ul>
<li>이렇게 익명객체를 활용하면 객체를 생성할 필요없이 변수명(comp1, comp2)만 바꿔서 자신이 원하는 기준으로 compare를 정의할 수 있다는 장점이 있다.</li>
<li>Comparable을 익명객체로 구현할 순 없을까? <ul>
<li>구현은 할 수 있겠지만 원하는 대로 동작하기 힘들것이다. 이유는 간단하다. compareTo메서드는 <strong>자기 자신</strong>과 상대방을 비교하는 것이기 때문에 <strong>익명 객체</strong>와 상대방을 비교할 수 밖에 없기 때문에 원하는 로직을 수행하기 부적절하다.</li>
</ul>
</li>
</ul>
<h2 id="comparable-comparator와-정렬-관계">Comparable, Comparator와 정렬 관계</h2>
<p>이제 Comparable, Comparator 두 인터페이스의 차이는 이해가 되었을 것이다.
객체를 비교하기 위해 compareTo, compare를 사용하는 것은 사용자가 정의한 기준을 토대로 양수, 0, 음수를 중 하나를 반환하는 것이다.</p>
<p>정렬관계를 알아보기 전에 한 가지 알고 가야할 것이 있다. Java에서의 일반적인 정렬기준이다. java는 특별한 정의가 되어 있지 않는 한 <strong>오름차순</strong>으로 정렬을 한다.
예를 들어 {1, 3, 2} 배열이 있다고 해보자. 우리가 정렬 알고리즘을 사용하면 두 수를 비교하게 된다. 0번 인덱스의 1과 1번 인덱스의 3을 비교하는 과정에서 1 - 3을 하면 음수가 반환될 것이다. java는 오름차순을 기본으로 하기 때문에 compareTo와 compare가 반환하는 값이 음수이면 선행 원소가 후행 원소보다 작다는 뜻이므로 위치를 교환하지 않는다. 그렇다면 1번 인덱스의 3과 2번 인덱스의 2를 비교해보자. 3 - 2를 하면 양수가 반환되고 이 뜻은 선행원소가 후행원소보다 크다는 뜻이므로 위치를 교환한다. 정리를 하면 오름차순을 디폴트로 두는 java의 특성상 두 수를 비교할 때 반환되는 값이 </p>
<ul>
<li>음수인 경우: 위치를 교환하지 않는다.</li>
<li>양수인 경우: 위치를 교환한다.</li>
</ul>
<p>이를 염두해두고 객체를 내림차순으로 정렬하고자 하면 어떻게 해야할까?
앞서 비교하는 메서드의 반환값이 음수이면 위치를 교환하고, 양수이면 위치를 교환하지 않는다고 하였다. 그렇다면 선행원소가 후행원소보다 클 때 반환되는 값이 양수가 되도록 하면 java는 위치를 교환하지 않을 것이다. 따라서 리턴값을 반대로 하면 원하는 대로 내림차순으로 정렬될 것이다. 쉽게 말해 (선행원소 - 후행원소)의 값이 음수일 때 반대로 바꿔주어 양수가 반환되도록하면 내림차순 정렬이 완성될 것이다. 즉, 우리가 사용했던 Person예제에서 <code>compare(T o1, T o2)</code>의 반환부인 <code>return o1.height - o2.height</code>를  <code>return -(o1.height - o2.height)</code>로 바꿔주기만 하면 된다. 이를 좀 더 간단히 하면 <code>return o2.height - o1.height</code>으로 바꿔줄 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자동, 수동의 올바른 실무 운영 기준]]></title>
            <link>https://velog.io/@kor_hoonie/auto-vs-manual</link>
            <guid>https://velog.io/@kor_hoonie/auto-vs-manual</guid>
            <pubDate>Mon, 20 Feb 2023 08:41:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>인프런 김영한님의 강의 &#39;스프링 핵심 원리 - 기본편&#39;의 강의 내용을 참고하여 작성한 글입니다.</p>
</blockquote>
<h3 id="자동-수동의-올바른-실무-운영-기준">자동, 수동의 올바른 실무 운영 기준</h3>
<h4 id="편리한-자동-기능을-기본으로-사용하자">편리한 자동 기능을 기본으로 사용하자</h4>
<p>그러면 어떤 경우에 컴포넌트 스캔과 자동 주입을 사용하고, 어떤 경우에 설정 정보를 통해서 수동으로 빈을 등록하고, 의존관계도 수동으로 주입해야 할까?</p>
<p>결론부터 이야기하면 스프링이 나오고 시간이 갈수록 점점 자동화를 선호하는 추세다. 스프링은 <code>@Component</code>뿐만 아니라, <code>@Controller</code>, <code>@Service</code>, <code>@Repository</code>처럼 계층에 맞추어 일반적인 어플리케이션 로직을 자동으로 스캔할 수 있도록 지원한다. 거기에 더해서 최근 스프링 부트는 컴포넌트 스캔을 기본으로 사용하고, 스프링 부트의 다양한 스프링 빈들도 조건이 맞으면 자동으로 등록하도록 설계했다.</p>
<p>설정 정보를 기반으로 어플리케이션을 구성하는 부분과 실제 동작하는 부분을 명확하게 나누는 것이 이상적이지만, 개발자 입장에서 스프링 빈을 하나 등록할 때 <code>@Component</code>만 넣어주면 끝나는 일을 <code>@Configuration</code> 설정 정보에 가서 <code>@Bean</code>을 적고, 객체를 생성하고, 주입할 대상을 일일이 적어주는 과정은 상당히 번거롭다.<del>귀찮다.</del>
또 관리할 빈이 많아서 설정 정보가 커지면 설정 정보를 관리하는 것 자체가 부담이 도니다. 그리고 결정적으로 자동 빈 등록을 사용해도 OCP, DIP를 지킬 수 있다.</p>
<p><strong>그러면 수동 등록 빈은 언제 사용하면 좋을까?</strong>
어플리케이션은 크게 업무 로직과 기술 지원 로직으로 나눌 수 있다.</p>
<ul>
<li><p><strong>업무 로직 빈</strong>: 웹을 지원하는 컨트롤러, 핵심 비즈니스 로직이 있는 서비스, 데이터 계층의 로직을 처리하는 리포지토리등이 모두 업무 로직이다. 보통 비즈니스 요구사항을 개발할 때 추가되거나 변경된다.</p>
</li>
<li><p><strong>기술 지원 빈</strong>: 기술적인 문제나 공통 관심사(AOP)를 처리할 때 주로 사용된다. 데이터베이스 연결이나, 공통 로그 처리처럼 업무 로직을 지원하기 위한 하부 기술이나 공통 기술들이다.</p>
</li>
<li><p>업무로직은 숫자도 매우 많고 한번 개발해야하면 컨트롤러, 서비스, 리포지토리처럼 어느정도 유사한 패턴이 있다. 이런 경우 자동 기능을 적극 사용하는 것이 좋다. 보통 문제가 발생해도 어떤 곳에서 문제가 발생했는지 명확하게 파악하기 쉽다.</p>
</li>
<li><p>기술 지원 로직은 업무 로직과 비교해서 그 수가 매우 적고, 보통 어플리케이션 전반에 걸쳐서 광범위하게 영향을 미친다. 그리고 업무 로직은 문제가 발생했을 때 어디가 문제인지 명확하게 잘 드러나지만, 기술 지원 로직은 적용이 잘 되고 있는지 아닌지조차 파악하기 어려운 경우가 많다. 그래서 이런 기술 지원 로직들은 가급적 수동 빈 등록을 사용해서 명확하게 드러내는 것이 좋다.</p>
</li>
</ul>
<p><strong>1. 어플리케이션에 광범위하게 영향을 미치는 기술 지원 객체는 수동 빈으로 등록해서 딱! 설정정보에 바로 나타나게 하는 것이 유지보수 하기 좋다.</strong></p>
<p><strong>2. 비즈니스 로직 중에서 다형성을 적극 활용할 때</strong>
의존관계 자동 주입 - 조회한 빈이 모두 필요할떄, List, Map을 다시 보자.
<code>DiscountService</code>가 의존관계 자동 주입으로 <code>Map&lt;String, DiscountPolicy&gt;</code>에 주입 받는 상황을 생각해보자. 여기에 어떤 빈들이 주입될 지, 각 빈들의 이름은 무엇일지 코드만 보고 한번에 쉽게 파악할 수 있을까? 자신이 개발한 코드라면 쉽게 파악이 가능하지만 다른 개발자가 개발한 코드를 사용하는 경우에는 쉽게 파악하기 힘들것이다. 파악하기 위해 여러 코드를 다 찾아봐야하는 번거로움이 발생한다.
이런 경우 수동 빈으로 등록하거나 또는 자동으로 하게 된다면 <strong>특정 패키지에 같이 묶어</strong>도는게 좋다.
이 부분을 별도의 설정 정보로 만들고 수동으로 등록하면 다음과 같다</p>
<pre><code class="language-java">@Configuration
public class DiscountPolicyConfig {

    @Bean
    public DiscountPolicy rateDiscountPolicy () {
        return new RateDiscountPolicy();
    }

    @Bean
    public DiscountPolicy fixDiscountPolicy () {
        return new FixDiscountPolicy();
    }
}</code></pre>
<p>이 설정 정보만 봐도 한눈에 빈의 이름은 물론이고 어떤 빈들이 주입될지 파악할 수 있다. 그래도 자동 등록을 사용하고 싶으면 파악하기 좋게 <code>DiscountPolicy</code>의 구현 빈들만 따로 모아서 특정 패키지에 모아두자.</p>
<p>참고로 <strong>스프링과 스프링 부트가 자동으로 등록하는 수 많은 빈들은 예외다.</strong> 이런 부분들을 스프링 자체를 잘 이해하고 스프링의 의도대로 잘 사용하는게 중요하다. 스프링 부트의 경우 <code>DataSource</code>같은 데이터 베이스 연결에 사용하는 기술 지원 로직까지 내부에서 자동으로 등록하는데, 이런 부분은 메뉴얼을 잘 참고해서 스프링 부트가 의도한 대로 편리하게 사용하면된다. 반면에 <strong>스프링 부트가 아니라 내가 직접 기술 지원 객체를 스프링 빈으로 등록한다면 수동으로 등록해서 명확하게 드러내는 것이 좋다.</strong></p>
<h4 id="정리">정리</h4>
<ul>
<li>편리한 자동 기능을 기본으로 사용하자</li>
<li>직접 등록하는 기술 지원 객체는 수동 등록</li>
<li>다형성을 적극 활용하는 비즈니스 로직은 수동 등록을 고민해보자.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[의존관계 자동 주입 - 2]]></title>
            <link>https://velog.io/@kor_hoonie/autowired-2</link>
            <guid>https://velog.io/@kor_hoonie/autowired-2</guid>
            <pubDate>Mon, 20 Feb 2023 08:39:57 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>인프런 김영한님의 강의 &#39;스프링 핵심 원리 - 기본편&#39;의 강의 내용을 참고하여 작성한 글입니다.</p>
</blockquote>
<h3 id="문제점-조회-빈이-2개-이상인-경우">(문제점) 조회 빈이 2개 이상인 경우</h3>
<p><code>@Autowired</code>는 타입(Type)으로 조회한다.</p>
<blockquote>
<p>참고: 설명의 편의성을 위해 이전 포스트에서 적용했던 lombok설정을 해제하고 기존에 사용하던 코드로 진행함.</p>
</blockquote>
<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>FixDiscountPolicy</code>, <code>RateDiscountPolicy</code> 둘 다 스프링 빈으로 선언해보자.</p>
<pre><code class="language-java">@Component
public class FixDiscountPolicy implements DiscountPolicy{}</code></pre>
<pre><code class="language-java">@Component
public class RateDiscountPolicy implements DiscountPolicy{}</code></pre>
<p>이렇게 한 뒤 의존관계 자동 주입을 실행(DiscountPolicy를 자동주입 받는 코드실행)하면</p>
<pre><code class="language-java">@Autowired
private DiscountPolicy discountPolicy</code></pre>
<p><code>NoUniqueBeanDefinitionException</code> 오류가 발생한다.</p>
<pre><code>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개의 빈이 발견되었다고 알려준다.
이때, 주입받는 필드를 하위 타입으로 구체화해서 지정할 수 있는데 이렇게 코드를 짜게되면 DIP를 위배하는 결과와 함께 코드의 유연성이 떨어지게 된다. 또한 이름만 다르고 완전히 똑같은 타입의 스프링 빈이 2개 이상 있을때는 하위 타입으로 지정하는 방법으로도 해결할 수 었다.
스프링 빈을 수동등록해서 문제를 해결해도 되지만, 의존관계 자동주입에서 이를 해결하는 방법이 여러개 있으니 한 번 알아보자.</p>
<h3 id="autowired-필드-명-qualifier-primary">@Autowired 필드 명, @Qualifier, @Primary</h3>
<p>해결 방법을 하나씩 알아보자.
조회 대상 빈이 2개 이상일 때 해결 방법</p>
<ul>
<li>@Autowired 필드명 매칭</li>
<li>@Qualifier -&gt; @Qualifier끼리 매칭 -&gt; 빈 이름 매칭</li>
<li>@Primary 사용</li>
</ul>
<h4 id="autowired-필드-명-매칭">@Autowired 필드 명 매칭</h4>
<p><code>@Autowired</code>는 타입 매칭을 시도하고, 이때 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.
<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>
<blockquote>
<p>참고: 위 코드는 필드 주입을 활용하여 필드명 매칭을 구현한 코드이다. 같은 원칙을 적용하여 생성자 주입을 활용해보면 </p>
</blockquote>
<pre><code class="language-java">@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy rateDiscountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = rateDiscountPolicy;
}</code></pre>
<p>와 같이 파라미터 명에 빈 이름을 명시해주면 타입으로 조회를 하더라도 원하는 빈을 주입받을 수 있다.</p>
<p><strong>@Autowired 매칭 정리</strong></p>
<ol>
<li>타입 매칭</li>
<li>타입 매칭의 결과가 2개 이상일 때 필드 명 파라미터 명으로 빈 이름 매칭</li>
</ol>
<h3 id="qualifier-사용">@Qualifier 사용</h3>
<p><code>@Qualifier</code>는 추가 구분자를 붙여주는 방법이다. 주입 시 추가적인 방법을 제공하는 것이지 빈 이름을 변경하는 것은 아니다.</p>
<p><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 RateDiscountPolicy 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;mainDiscountPolicy) DiscountPolicy discountPolicy) {
    this.discountPolicy = discountPolicy;
}</code></pre>
<p><code>@Qualifier</code>로 주입할 때 <code>@Qualifier(&quot;mainDiscountPolicy&quot;)</code>를 못 찾으면 어떻게 될까? 그러면 mainDiscountPolicy라는 이름의 스프링 빈을 추가로 찾는다. 하지만 개발자가 정말 의도를 갖고 짠 코드가 아니고 실수로 mainDiscountPolicy를 못 찾는 코드를 작성했을 확률이 높기 때문에 이런 경우는 바람직하지 않다고 생각한다. 따라서 <code>@Qualifier</code>는 <code>@Qualifier</code>를 찾는 용도로만 사용되는게 명확할 것이다.
다음과 같이 직접 빈 등록시에도 <code>@Qualifier</code>를 동일하게 사용할 수 있다.</p>
<pre><code class="language-java">@Bean
@Qualifer(&quot;mainDiscountPolicy)
public DiscountPolicy discountPolicy () {
    return new ...
}</code></pre>
<p><strong>@Qualifier 정리</strong></p>
<ol>
<li>@Qualifer끼리 매칭</li>
<li>빈 이름 매칭</li>
<li><code>NoSuchBeanDefinitionException</code>예외 발생</li>
</ol>
<h4 id="primary-사용">@Primary 사용</h4>
<p><code>@Primary</code>는 우선순위를 정하는 방법이다. @Autowired시에 여러 빈이 매칭되면 <code>@Primary</code>가 우선권을 가진다.
<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>코드를 실행해보면 원하는 빈이 주입받아지는 것을 확인 할 수 있다는 것을 알 수 있다.</p>
<p>여기까지 보면 <code>@Primary</code>와 <code>@Qualifier</code> 중에 어떤 것이 좋을지 고민이 될 것이다. <code>@Qualifier</code>의 단점은 주입 받기 위해 모든 코드에 어노테이션을 붙여주어야한다는 점이다. 반면에 <code>@Primary</code>는 빈에만 붙여주면 된다.</p>
<h4 id="primary-qualifier-활용">@Primary, @Qualifier 활용</h4>
<p>예를 들어 코드에서 자주 사용하는 메인 데이터베이스의 커넥션을 획득하는 스프링 빈이 있고, 코드에서 특별한 기능으로 서브 데이터베이스의 커넥션을 획득하는 스프링 빈이 있다. 메인 데이터베이스의 커넥션을 획득하는 스프링 빈은 <code>@Primary</code>를 사용해서 조회하는 곳에서 <code>@Qualifier</code> 지정 없이 편리하게 사용할 수 있도록 하고 서브 데이터베이스 커넥션을 획득할 때에는 <code>@Qualifier</code>를 지정해서 <strong>명시적</strong>으로 획득하는 방식으로 코드를 작성하면 코드를 깔끔하게 유지할 수 있다. 물론 이때 메인 데이터베이스의 커넥션을 획득하는 스프링 빈을 등록할 때 <code>@Qualifier</code>를 지정해주는것은 상관이 없다.</p>
<p><strong>우선순위</strong>
<code>@Primary</code>는 기본값처럼 동작을 하고 <code>@Qualifier</code>는 매우 상세하게 동작한다. 이런 경우에 어떤 것이 우선권을 가져갈까? 스프링은 자동보다는 수동이, 넓은 범위의 선택권보단 좁은 범위의 선택권이 우선순위가 높다. 따라서 여기서도 <code>@Qualifier</code>가 우선권이 높다.</p>
<h3 id="어노테이션-직접-만들기">어노테이션 직접 만들기</h3>
<p><code>@Qualifier(&quot;mainDiscountPolicy&quot;)</code> 이렇게 <strong>문자열</strong>를 적으면 컴파일시 타입 체크가 안된다. 다음과 같은 어노테이션을 직접 만들어서 문제를 해결할 수 있다.</p>
<pre><code class="language-java">package hello.core.annotation;

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.*;

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier(&quot;mainDiscountPolicy&quot;)

public @interface MainDiscountPolicy {
}</code></pre>
<p><strong>생성자 자동 주입</strong></p>
<pre><code class="language-java">@Autowired // 생성자가 딱 1개만 있으면 생략가능.
public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
    }</code></pre>
<p>어노테이션의 경우 상속이라는 개념이 없다. 이렇게 여러 어노테이션을 모아서 사용하는 기능은 스프링이 지원하는 기능이다. @Qualifier뿐만 아니라 다른 어노테이션들도 함께 조합해서 사용할 수 있다. 단적으로 @Autowired도 재정의할 수 있다. 하지만 스프링이 제공하는 기능을 뚜렷한 목적없이 무분별하게 재정의하는 것은 유지보수에 혼란만 가중할 수 있으므로 주의해야한다.</p>
<h3 id="조회한-빈이-모두-필요할-때---list-map">조회한 빈이 모두 필요할 때 - List, Map</h3>
<p>의도적으로 해당 타입의 빈이 모두 필요한 경우가 있다. 예를 들어 할인 서비스를 제공하는 과정에서 사용자가 rate, fix 중 원하는 것을 선택할 수 있어야하는 경우가 있다. 이때, 스프링을 사용하면 소위 말하는 전략패턴을 매우 간단하게 구현할 수 있다.</p>
<p><strong>test코드</strong></p>
<pre><code class="language-java">package hello.core.autowired;

import hello.core.AutoAppConfig;
import hello.core.discount.DiscountPolicy;
import hello.core.member.Grade;
import hello.core.member.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.*;

public class AllBeanTest {

    @Test
    void findAllBean() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
        DiscountService discountService = ac.getBean(DiscountService.class);

        assertThat(discountService).isInstanceOf(DiscountService.class);
        Member member = new Member(1L, &quot;userA&quot;, Grade.VIP);

        int fixDiscountPrice = discountService.discount(member, 20000, &quot;fixDiscountPolicy&quot;);
        assertThat(fixDiscountPrice).isEqualTo(1000);

        int rateDiscountPrice = discountService.discount(member, 20000, &quot;rateDiscountPolicy&quot;);
        assertThat(rateDiscountPrice).isEqualTo(2000);

    }
    static class DiscountService {
        private final Map&lt;String, DiscountPolicy&gt; policyMap;
        private final List&lt;DiscountPolicy&gt; policies;

        @Autowired
        public DiscountService(Map&lt;String, DiscountPolicy&gt; policyMap, List&lt;DiscountPolicy&gt; policies) {
            this.policyMap = policyMap;
            this.policies = policies;
            System.out.println(&quot;policyMap = &quot; + policyMap);
            System.out.println(&quot;policies = &quot; + policies);
        }

        public int discount(Member member, int price, String discountPolicy) {
            DiscountPolicy selectedPolicy = policyMap.get(discountPolicy);

            System.out.println(&quot;discountPolicy = &quot; + discountPolicy);
            System.out.println(&quot;selectedPolicy = &quot; + selectedPolicy);

            return selectedPolicy.discount(member, price);
        }
    }

}</code></pre>
<p><strong>로직 분석</strong></p>
<ul>
<li>DiscountService는 Map으로 모든 <code>DiscountPolicy</code>를 주입받는다. 이때 <code>fixDiscountPolicy</code>, <code>rateDiscountPolicy</code>가 <code>AutoAppConfig.class</code>로 주입받는다.</li>
<li>static으로 정의한 DiscountService의 경우 <code>@Component</code> 어노테이션이 없으므로 컴포넌트 스캔 대상이 아니므로 명시적으로 스프링 빈에 등록해준다.</li>
<li><code>discount()</code> 메서드는 discountPolicy로 사용자가 선택한 할인 정책을 받고 해당 객체를 스프링 빈에서 찾아서 실행한다.</li>
</ul>
<p><strong>주입 분석</strong></p>
<ul>
<li><code>Map&lt;String, DiscountPolicy&gt;</code>: map의 key에는 스프링 빈의 이름을 넣어주고 value에는 <code>DiscountPolicy</code>로 조회한 모든 스프링 빈을 담아준다.</li>
</ul>
<p><strong>참고 - 스프링 컨테이너를 생성하면서 스프링 빈 등록하기</strong>
스프링 컨테이너는 생성자에 클래스 정보를 받는다. 여기에 클래스 정보를 넘기면 해당 클래스가 스프링 빈으로 자동 등록된다.
<code>new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);</code></p>
<p>위 코드는 2가지로 나누어 이해할 수 있다.</p>
<ul>
<li><code>new AnnotationConfigApplicationContext()</code>를 통해 스프링 컨테이너를 생성한다.</li>
<li><code>AutoAppConfig.class</code>, <code>DiscountService.class</code>를 파라미터로 넘기면서 해당 클래스를 자동으로 스프링 빈으로 등록한다.
정리하면 스프링 컨테이너를 생성하면서, 해당 컨테이너에 동시에 <code>AutoAppConfig</code>, <code>DiscountService</code>를 스프링 빈으로 자동 등록한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[롬복과 최신 트렌드]]></title>
            <link>https://velog.io/@kor_hoonie/lombok</link>
            <guid>https://velog.io/@kor_hoonie/lombok</guid>
            <pubDate>Mon, 20 Feb 2023 06:18:22 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>인프런 김영한님의 강의 &#39;스프링 핵심 원리 - 기본편&#39;의 강의 내용을 참고하여 작성한 글입니다.</p>
</blockquote>
<h3 id="롬복과-최신트렌드">롬복과 최신트렌드</h3>
<p>막상 개발을 하다보면 대부분이 불변이고, 그래서 다음과 같이 필드값에 final 키워드를 사용하게된다. 이렇게 되면 생성자도 만들어야하고 주입 받은 값을 대입하는 코드도 만들어야하고 <strong>귀찮은 과정이 반복</strong>되는 경우가 많다. 필드 주입처럼 편리하게 사용하는 방법은 없을까? 다음 코드를 최적화해보자!</p>
<p><strong>기본코드</strong></p>
<pre><code class="language-java">package hello.core.order;

import hello.core.discount.DiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@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>
<p> 여기서 생성자가 딱 1개만 있으면 <code>@Autowired</code>를 생략할 수 있다.</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>
<ul>
<li>이제 롬복을 적용해보자. 롬복 라이브러리 적용 방법은 아래에 적어두었다.</li>
<li>롬복 라이브러리가 제공하는 <code>@RequiredArgsConstructor</code>기능을 사용하면 final이 붙은 필드를 모아서 생성자를 자동으로 만들어준다.</li>
</ul>
<p>** 최종 결과 코드**</p>
<pre><code class="language-java">@Component
@RequiredArgsContructor
public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository;
    private  final DiscountPolicy discountPolicy;

    // ...
}</code></pre>
<p>이 최종 결과 코드와 이전의 코드는 완전히 동일하다. 롬복이 자바의 어노테이션 프로세서라는 기능을 이용해서 컴파일 시점에 생성자 코드를 자동으로 생성해준다. 실제 <code>class</code>를 열어보면 다음 코드가 추가되어 있는 것을 확인할 수 있다.</p>
<p><strong>정리</strong>
최근에는 생성자를 딱 1개 두고, <code>@Autowired</code>를 생략하는 방법을 주로 사용한다. 여기에 Lombok라이브러리의 <code>@RequiredArgsConstructor</code>를 함께 사용하면 기능은 다 제공하면서, 코드는 깔끔하게 사용할 수 있다.</p>
<h4 id="롬복-라이브러리-적용-방법">롬복 라이브러리 적용 방법</h4>
<p><code>build.gradle</code>에 라이브러리 및 환경 추가</p>
<pre><code class="language-java">plugins {
    id &#39;java&#39;
    id &#39;org.springframework.boot&#39; version &#39;2.7.8&#39;
    id &#39;io.spring.dependency-management&#39; version &#39;1.0.15.RELEASE&#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;
}

tasks.named(&#39;test&#39;) {
    useJUnitPlatform()
}</code></pre>
<ul>
<li>Preferences(윈도우 File -&gt; Settings) -&gt; plugin -&gt; lombok 검색 설치 실행(재시작)</li>
<li>Preferences -&gt; Annotation Processors 검색 -&gt; Enable annotation processing 체크 (재시작)</li>
<li>임의의 테스트 클래스 만들고 @Getter, @Setter 확인</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[의존관계 자동 주입 - 1]]></title>
            <link>https://velog.io/@kor_hoonie/autowired-1</link>
            <guid>https://velog.io/@kor_hoonie/autowired-1</guid>
            <pubDate>Fri, 17 Feb 2023 06:26:16 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>인프런 김영한님의 강의 &#39;스프링 핵심 원리 - 기본편&#39;의 강의 내용을 참고하여 작성한 글입니다.</p>
</blockquote>
<h3 id="다양한-의존관계-주입-방법">다양한 의존관계 주입 방법</h3>
<p>의존관계 주입은 크게 4가지 방법이 있다.</p>
<ul>
<li>생성자 주입</li>
<li>수정자 주입(setter 주입)</li>
<li>필드 주입</li>
<li>일반 메서드 주입</li>
</ul>
<h3 id="생성자-주입">생성자 주입</h3>
<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;
</code></pre>
</li>
</ul>
<p>//    @Autowired
    public OrderServiceImpl (MemberRepository memberRepository, DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
    }
}</p>
<pre><code>

### 수정자(setter) 주입
- setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법이다.
- 특징
   - **선택, 변경** 가능성이 있는 의존관계에 사용
   - 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용하는 방법이다.
```java
@Component
public class OrderServiceImpl implements OrderService {
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowired
    public void setMemberRepository (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>
<blockquote>
<p>참고: 자바빈 프로퍼티, 자바에서는 과거부터 필드의 값을 직접 변경하지 않고, setXxx, getXxx라는 메서드를 통해서 값을 변경하고 읽는 규칙을 만들었는데, 이것이 자바빈 프로퍼티 규약이다. 더 자세한 내용이 궁금하면 자바빈 프로퍼티로 검색해보자.</p>
</blockquote>
<p><strong>자바빈 프로퍼티 규약 예시</strong></p>
<pre><code class="language-java">class Data {
    private age;

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

    public int getAge() {
        return age;
    }
}</code></pre>
<h3 id="필드-주입">필드 주입</h3>
<ul>
<li>이름 그대로 필드에 바로 주입하는 방법이다.</li>
<li>특징<ul>
<li>코드가 간결해서 많은 개발자들을 유혹하지만 외부에서 변경이 불가능해서 테스트 하기 힘들다는 치명적인 단점이 있다.</li>
<li>DI 프레임워크가 없으면 아무것도 할 수 없다.</li>
<li><strong>사용하지말자!</strong><ul>
<li>어플리케이션의 실제 코드와 관계없는 테스트 코드</li>
<li>스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 사용</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderService {

    @Autowired
    private final MemberRepository memberRepository;

    @Autowired
    private final DiscountPolicy discountPolicy;

}</code></pre>
<blockquote>
<p>참고: 순수한 자바 테스트 코드에는 당연히 @Autowired가 동작하지 않는다. <code>@SpringBootTest</code>처럼 스프링 컨테이너를 테스트에 통합한 경우에만 가능하다.</p>
</blockquote>
<blockquote>
<p>참고: 다음 코드와 같이 <code>@Bean</code>에서 파라미터에 의존관계는 자동 주입된다. 수동 등록시 자동 등록된 빈의 의존관계가 필요할 때 문제를 해결할 수 있다.</p>
</blockquote>
<pre><code class="language-java">@Bean
OrderService orderService(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
    new OrderServiceImpl (memberRepository, discountPolicy);
}</code></pre>
<h3 id="일반-메서드-주입">일반 메서드 주입</h3>
<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 final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;

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

import hello.core.member.Member;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.lang.Nullable;

import java.util.Optional;

public class AutowiredTest {

    @Test
    void AutowiredOption(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class);
    }

    static class TestBean {

        @Autowired (required = false)
        public void setNoBean1(Member noBean1){
            System.out.println(&quot;noBean1 = &quot; + noBean1);
        }

        @Autowired
        public void setNoBean2(@Nullable Member noBean2){
            System.out.println(&quot;noBean2 = &quot; + noBean2);
        }

        @Autowired
        public void setNoBean3(Optional&lt;Member&gt; noBean3){
            System.out.println(&quot;noBean3 = &quot; + noBean3);
        }
    }
}</code></pre>
<ul>
<li><strong><code>Member</code>는 스프링 빈이 아니므로 컨테이너에 등록되어있지 않다.</strong></li>
<li>따라서 @Autowired로 Member를 주입 받고 싶어도 등록되어 있지 않으므로 등록할 수 가 없는 상황이다.</li>
<li>여기서 <code>@Autowired (required = false</code>)로 해주면 메서드 호출 자체가 안된다.</li>
</ul>
<p><strong>출력결과</strong></p>
<pre><code>noBean3 = Optional.empty
noBean2 = null</code></pre><ul>
<li><code>required = false</code>를 하면 주입할 대상이 없는 경우 메서드 자체가 실행되지 않음</li>
<li><code>@Nullabe</code>을 붙여주면 주입할 대상이 없는 경우 null처리를 해준다.</li>
<li><code>Optional&lt;T&gt;</code>를 해주면 주입할 대상이 없는 경우 Optional.empty를 반환한다.</li>
</ul>
<blockquote>
<p>참고: @Nullable, Optional은 스프링 전반에 걸쳐서 지원된다. 예를 들어서 생성자 자동 주입에서 특정 필드에만 사용해도 된다.</p>
</blockquote>
<h3 id="생성자-주입을-선택하라">생성자 주입을 선택하라!</h3>
<p>과거에는 수정자 주입과 필드 주입을 많이 사용했지만, 최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장한다. 그 이유는 다음과 같다.</p>
<p><strong>불변</strong></p>
<ul>
<li>대부분의 의존관계 주입은 한번 일어나면 어플리케이션 종료 시점까지 의존관계를 변경할 일이 없다. 오히려 대부분의 의존관계는 어플리케이션 종료 전까지 변하면 안된다.</li>
<li>수정자 주입을 사용하면, setXxx메서드를 public으로 열어두어야한다. 이때 누군가 실수로 변경할 수도 있고, 변경하면 안되는 메서드를 열어두는 것은 좋은 설계방법이 아니다.</li>
<li>생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에는 호출되는 일이 없다. 따라서 불변하게 설계할 수 있다.</li>
</ul>
<p><strong>누락</strong>
프레임워크 없이 순수한 자바 코드를 단위 테스트 하는 경우에 다음과 같이 수정자 의존관계인 경우</p>
<pre><code class="language-java">public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final 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() {
    OrderServiceImple orderService = new OrderServiceImpl();
    orderService.createOrder(1L, &quot;itemA&quot;, 10000);
}</code></pre>
<p>그런데 막상 실행결과는 NPE(null pointer exception)이 발생하는데, memberRepository, discountPolicy 모두 의존관계 주입이 누락되었기 때문이다. 프레임워크를 사용할 때는 setXxx를 자동으로 실행하여 의존관계를 주입해주지만 순수한 자바코드에서는 수정자가 실행될 이유가 없기 때문에 NPE가 발생한다.
하지만 생성자 주입을 사용하면 다음처럼 주입 데이터를 누락했을 때 <strong>컴파일 오류</strong>가 발생한다. 왜냐하면 객체를 생성하기 위해서는 반드시 생성자를 실행해야하는데 이때 주입 받을 값이 없기때문이다. 그리고 IDE에서 바로 어떤 값을 필수로 주입해야 하는지 알 수 있다.</p>
<p><strong>final 키워드</strong>
생성자 주입을 사용하면 필드에 <code>final</code>키워드를 사용할 수 있다. 그래서 생성자에 혹시라도 값이 설정되지 않는 오류를 <strong>컴파일 시점</strong>에서 막아준다. 다음 코드를 보자</p>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowired
    public OrderServiceImple(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></li>
</ul>
<blockquote>
<p>참고: 수정자 주입을 포함한 나머지 주입 방식은 모두 생성자 <strong>이후</strong>에 호출되므로, 필드에 <code>final</code>키워드를 사용할 수 없다. 오직 생성자 주입 방식만 <code>final</code>키워드를 사용할 수 있다.</p>
</blockquote>
<h4 id="정리">정리</h4>
<ul>
<li>생성자 주입 방식을 선택하는 이유는 여러가지가 있지만, 프레임워크에 의존하지 않고, 순수한 자바 언어의 특징을 잘 살리는 방법이기도 하다.</li>
<li>기본으로 생성자 주입을 사용하고, 필수 값이 아닌 경우에는 수정자 주입 방식을 옵션으로 부여하면된다. 생성자 주입과 수정자 주입을 동시에 사용할 수 있다.</li>
<li>항상 생성자 주입을 선택해라! 그리고 가끔 옵션이 필요하면 수정자 주입을 선택해라. 필드 주입은 사용하지 않는게 좋다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[컴포넌트 스캔]]></title>
            <link>https://velog.io/@kor_hoonie/component-scan</link>
            <guid>https://velog.io/@kor_hoonie/component-scan</guid>
            <pubDate>Wed, 15 Feb 2023 09:19:20 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>인프런 김영한님의 강의 &#39;스프링 핵심 원리 - 기본편&#39;의 강의 내용을 참고하여 작성한 글입니다.</p>
</blockquote>
<h3 id="컴포넌트-스캔과-의존관계-자동-주입-시작하기">컴포넌트 스캔과 의존관계 자동 주입 시작하기</h3>
<p>지금까지는 XML의 <code>&lt;bean&gt;</code>이나 <code>@Bean</code>을 통해서 설정 정보에 직접 등록할 스프링 빈을 작성하였었다. 실제 어플리케이션은 빈의 개수가 수십, 수백 개가 되기 때문에 일일히 빈을 직접 등록하기는 현실적으로 불가능할 것이다. 스프링의 컴포넌트 스캔 기능을 통해 자동으로 스프링 빈을 등록하는 방법을 알아보자.</p>
<h3 id="컴포넌트-스캔과-의존관계-자동-주입-시작하기-1">컴포넌트 스캔과 의존관계 자동 주입 시작하기</h3>
<ul>
<li>앞서 말했듯이 어플리케이션의 규모가 커질수록 사용되는 빈의 개수가 굉장히 많아지게 된다. 개발자가 일일히 등록하는 과정에서 발생하는 오류가 생길 수도 있고 무엇보다 매우 귀찮다.</li>
<li>그래서 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 <strong>컴포넌트 스캔</strong>이라는 기능을 제공한다.</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">  package hello.core;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

import static org.springframework.context.annotation.ComponentScan.*;

@Configuration
@ComponentScan(
        excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {


}</code></pre>
<ul>
<li><p>컴포넌트 스캔을 사용하려면 먼저 <code>@ComponentScan</code>을 설정 정보에 붙여주면 된다.</p>
</li>
<li><p>기존의 AppConfig와는 다르게 @Bean으로 등록한 클래스가 하나도 없다!!</p>
<blockquote>
<p>참고: 컴포넌트 스캔을 사용하면 <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>
</li>
<li><p><em>MemoryMemberRepository @Component 추가*</em></p>
<pre><code class="language-java">@Component
public class MemoryMemberRepository implements MemberRepository{}</code></pre>
</li>
<li><p><em>RateDiscountPolicy @Component 추가*</em></p>
<pre><code class="language-java">@Component
public class RateDiscountPolicy implements DiscountPolicy{}</code></pre>
</li>
<li><p><em>MemberServiceImpl @Component, @Autowired 추가*</em></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>
</li>
<li><p>이전에 AppConfig에서는 <code>@Bean</code>으로 직접 설정 정보를 작성했고, 의존관계도 직접 명시했다. 이제는 이런 설정 정보 자체가 없기 때문에 의존관계 주입도 이 클래스 안에서 해결해야 한다.</p>
</li>
<li><p><code>@Autowired</code>는 의존관계를 자동으로 주입해준다. 어떻게 동작하는 지는 조금 뒤에 설명하겠다.</p>
</li>
<li><p><em>OrderServiceImpl @Component, @Autowired 추가*</em></p>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderService {
   private final MemberRepository memberRepository;
   private final DiscountPolicy discountPolicy;

   @Autowired
   public OrderServiceImple (MemberRepository memberRepository, DiscountPolicy discountPolicy) {
       this.memberRepository = memberRepository;
       this.discountPolicy = discountPolicy;
   }
}</code></pre>
</li>
<li><p><code>@Autowired</code>를 사용하면 생성자에서 여러 의존관계도 한번에 주입받을 수 있다.</p>
</li>
</ul>
<p><strong>AutoAppConfigTest.java</strong></p>
<pre><code class="language-java">package hello.core.scan;

import hello.core.member.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import static org.junit.jupiter.api.Assertions.*;

class AutoAppConfigTest {

    @Test
    void basicScan() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
        MemberService memberService = ac.getBean(MemberService.class);
        Assertions.assertThat(memberService).isInstanceOf(MemberService.class);
    }

}</code></pre>
<ul>
<li><code>AnnotationConfigApplicationContext</code>를 사용하는 것은 기존과 동일하다.</li>
<li>설정 정보로 <code>AutoAppConfig</code>클래스를 넘겨준다.</li>
<li>실행해보면 기존과 같이 잘 동작하는 것을 확인할 수 있다.</li>
</ul>
<p>컴포넌트 스캔과 자동 의존관계 주입이 어떻게 동작하는지 그림으로 알아보자.</p>
<h4 id="1-componentscan">1. @ComponentScan</h4>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/1c0bc4db-8228-433f-b53f-f6563cd6eb78/image.png" alt=""></p>
<ul>
<li><code>@ComponentScan</code>은 <code>@Component</code>가 붙은 모든 클래스를 스프링 빈으로 등록한다.</li>
<li>이때 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용한다.<ul>
<li>빈 이름 기본 전략: MemberServiceImpl 클래스 -&gt; memberServiceImpl</li>
<li>빈 이름 직접 지정: 만약 스프링 빈의 이름을 직접 지정하고 싶으면 <code>@Component(&quot;memberService2&quot;)</code>와 같은 방법으로 이름을 부여하면 된다.<h4 id="2-autowired-의존관계-자동-주입">2. @Autowired 의존관계 자동 주입</h4>
<img src="https://velog.velcdn.com/images/kor_hoonie/post/a6bb4dc0-710e-4f1b-9aa6-49a99dc302c8/image.png" alt=""></li>
</ul>
</li>
<li>생성자에 <code>@Autowired</code>를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다.</li>
<li>이때 기본 조회 전략은 <strong>타입이 같은 빈</strong>을 찾아서 주입한다.<ul>
<li><code>getBean(MemberRepository.class)</code>와 동일하다고 이해하면 된다.</li>
<li>더 자세한 내용은 뒤에서 설명한다.
<img src="https://velog.velcdn.com/images/kor_hoonie/post/2e390d58-b1ae-4992-9be3-2259db32b660/image.png" alt=""></li>
</ul>
</li>
<li>생성자에 파라미터가 많아도 다 찾아서 자동으로 주입한다.</li>
</ul>
<h3 id="탐색-위치와-기본-스캔-대상">탐색 위치와 기본 스캔 대상</h3>
<h4 id="탐색할-패키지의-시작-위치-지정">탐색할 패키지의 시작 위치 지정</h4>
<p>모든 자바 클래스를 다 컴포넌트 스캔하면 시간이 오래 걸린다. 그래서 꼭 필요한 위치부터 탐색하도록 시작 위치를 지정할 수 있다.</p>
<pre><code class="language-java">@ComponentScan(
    basePackages = &quot;hello.core&quot;,
)</code></pre>
<ul>
<li><code>basePackages</code>: 탐색할 패키지의 시작 위치를 지정한다. 이 패키지를 포함해서 하위 패키지를 모두 탐색한다.<ul>
<li><code>basePackages = { &quot;hello.core&quot;, &quot;hello.service&quot; }</code> 이렇게 여러 시작 위치를 지정할 수도 있다.</li>
</ul>
</li>
<li><code>basePackageClasses</code>: 지정한 클래스의 패키지를 탐색 시작 위치로 지정한다.</li>
<li>만약 지정하지 않으면 <code>@ComponentScan</code>이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.</li>
<li><em>recommended*</em>
패키지 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것이다. 최근 스프링 부트도 이 방법을 기본으로 제공한다. 예를 들어서 프로젝트가 다음과 같은 구조로 되어 있으면</li>
<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> -&gt; 프로젝트 시작루트, 여기에 AppConfig 같은 메인 설정 정보를 두고, @ComponentScan 어노테이션을 붙이고, basePackages 지정은 생략한다.</p>
<p>이렇게 하면 <code>com.hello</code>를 포함한 하위는 모두 자동으로 컴포넌트 스캔의 대상이 된다. 그리고 프로젝트 메인 설정 정보는 프로젝트를 대표하는 정보이기 때문에 프로젝트 시작 루트 위치에 두는 것이 좋다. 참고로 스프링 부트를 사용하면 스프링 부트의 대표 시작 정보인 <code>@SpringBootApplication</code>을 이 프로젝트 시작 루트에 두는 것이 관례이다.(그리고 이 설정 안에 바로 <code>@ComponentScan</code>이 들어있다.</p>
<h4 id="컴포넌트-스캔-기본-대상">컴포넌트 스캔 기본 대상</h4>
<p>컴포넌트 스캔은 <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>: 이 어노테이션은 사실 특별한 처리를 해주진 않는다. 대신 개발자들이 핵심 비즈니스 로직이 여기에 있겠구나 라고 비즈니스 계층을 인식하는데 도움이 된다.</li>
</ul>
<blockquote>
<p>참고: <code>userDefaultFilters</code>옵션은 기본으로 켜져있는데, 이 옵션을 끄면 기본 스캔 대상들이 제외된다.</p>
</blockquote>
<h3 id="필터">필터</h3>
<ul>
<li><code>includeFilters</code>: 컴포넌트 스캔 대상을 추가로 지정한다.</li>
<li><code>excludeFilters</code>: 컴포넌트 스캔에서 제외할 대상을 지정한다.</li>
</ul>
<p>빠르게 예제로 확인해보자. 모든 코드는 테스트 코드에 추가
<strong>컴포넌트 스캔 대상에 추가할 어노테이션</strong></p>
<pre><code class="language-java">package hello.core.scan.filter;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}</code></pre>
<p><strong>컴포넌트 스캔 대상에서 제외할 어노테이션</strong></p>
<pre><code class="language-java">package hello.core.scan.filter;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}</code></pre>
<p><strong>컴포넌트 스캔 대상에 추가할 클래스</strong></p>
<pre><code class="language-java">package hello.core.scan.filter;

@MyIncludeComponent
public class BeanA {
}</code></pre>
<p><strong>컴포넌트 스캔 대상에서 제외할 클래스</strong></p>
<pre><code class="language-java">package hello.core.scan.filter;

@MyExcludeComponent
public class BeanB {
}</code></pre>
<p><strong>설정 정보와 전체 테스트 코드</strong></p>
<pre><code class="language-java">package hello.core.scan.filter;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

public class ComponentFilterAppConfigTest {
    @Test
    void filterScan() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
        BeanA beanA = ac.getBean(&quot;beanA&quot;, BeanA.class);
        org.assertj.core.api.Assertions.assertThat(beanA).isNotNull();
        Assertions.assertThrows(
                NoSuchBeanDefinitionException.class, () -&gt; ac.getBean(&quot;beanB&quot;, BeanB.class)
        );
    }

    @Configuration
    @ComponentScan(
            includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
            excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
    )
    static class ComponentFilterAppConfig{
    }
}</code></pre>
<ul>
<li><code>includeFilters</code>에 <code>MyIncludeFilter</code> 어노테이션을 추가해서 BeanA가 스프링 빈에 등록된다.</li>
<li><code>excludeFilters</code>에 <code>MyExcludeFilter</code> 어노테이션을 추가해서 BeanB는 스프링 빈에 등록되지 않는다.</li>
</ul>
<h4 id="filtertype-옵션">FilterType 옵션</h4>
<p>FilterType 옵션은 5가지가 있다.</p>
<ul>
<li>ANNOTATION: 기본값, 어노테이션을 인식해서 동작한다.</li>
<li>ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작한다.</li>
<li>ASPECTJ: AspectJ 패턴 사용</li>
<li>REGEX: 정규 표현식</li>
<li>CUSTOM: <code>TypeFilter</code>이라는 인터페이스를 구현해서 처리</li>
</ul>
<p>예를 들어서 BeanA도 빼고 싶으면 다음과 같이 추가하면 된다.</p>
<pre><code class="language-java">@ComponentScan(
    includeFilters = {
    @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class) 
    },
    excludeFilters = {
    @Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class),
    @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = BeanA.class)
    }</code></pre>
<blockquote>
<p>참고: <code>@Component</code>면 충분하기 때문에, <code>includeFilters</code>를 사용할 일은 거의 없다. <code>excludeFilters</code>는 여러가지 이유로 사용할 때도 있지만 그렇게 많지는 않다. 특히 최근 스프링 부트는 컴포넌트 스캔을 기본으로 제공하는데, 개인적으로는 옵션을 변경하면서 사용하기 보다는 스프링의 기본 설정에 최대한 맞추어 사용하는 것을 권장하고 선호하는 편이다.</p>
</blockquote>
<h3 id="중복-등록과-충돌">중복 등록과 충돌</h3>
<p>컴포넌트 스캔에서 같은 빈 이름을 등록하면 어떻게 될까? 다음 2가지 상황이 있다.</p>
<ol>
<li>자동 빈 등록 vs 자동 빈 등록</li>
<li>수동 빈 등록 vs 자동 빈 등록</li>
</ol>
<h4 id="자동-빈-등록-vs-자동-빈-등록">자동 빈 등록 vs 자동 빈 등록</h4>
<ul>
<li>컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록되는데, 그 이름이 같은 경우 스프링은 오류를 발생시킨다.<ul>
<li><code>ConfilctingBeanDefinitionException</code>예외 발생</li>
</ul>
</li>
</ul>
<h4 id="수동-빈-등록-vs-자동-빈-등록">수동 빈 등록 vs 자동 빈 등록</h4>
<p>만약 수동 빈 등록과 자동 빈 등록에서 빈 이름이 충돌되면 어떻게 될까?</p>
<pre><code class="language-java">@Component
public class MemoryMemberRepository implements MemberRepository {}</code></pre>
<pre><code class="language-java">@Configuration
@ComponentScan(
    excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
    @Bean(name = &quot;memoryMemberRepository&quot;)
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}</code></pre>
<p>이 경우 수동 빈 등록이 우선권을 가진다.
(수동 빈이 자동빈을 오버라이딩 해버린다.)</p>
<p><strong>수동 빈 등록시 남는 로그</strong></p>
<pre><code>Overriding bean definition for bean &#39;memoryMemberRepository&#39; with a different definition: replacing</code></pre><p>물론 개발자가 의도적으로 이런 결과를 기대했다면, 자동보다는 수동이 우선권을 가지는 것이 좋다. 하지만 현실은 개발자가 의도적으로 설정해서 이런 결과가 만들어지기 보다는 여러 설정들이 꼬여서 이런 결과가 만드어지는 경우가 대부분이다.
<strong>그러면 정말 잡기 어려운 버그가 만들어진다. 항상 잡기 어려운 버그는 애매한 버그다.</strong>
그래서 최근 스프링 부트에서는 수동 빈 등록과 자동 빈 등록이 충돌나면 오류가 발생하도록 기본 값을 바꾸었다.
<strong>수동 빈 등록과 자동 빈 등록 오류시 스프링 부트 에러 메시지</strong></p>
<pre><code>Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true</code></pre><p>스프링 부트인 <code>CoreApplication</code>을 실행해보면 오류를 볼 수 있다.</p>
<h3 id="다음-포스트">다음 포스트</h3>
<p>다음 포스트에서는 @Autowired를 통한 의존관계 자동 주입에 대해서 좀 더 자세히 알아 보겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[싱글톤 컨테이너]]></title>
            <link>https://velog.io/@kor_hoonie/singleton-container</link>
            <guid>https://velog.io/@kor_hoonie/singleton-container</guid>
            <pubDate>Tue, 14 Feb 2023 08:02:57 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>인프런 김영한님의 강의 &#39;스프링 핵심 원리 - 기본편&#39;의 강의 내용을 참고하여 작성한 글입니다.</p>
</blockquote>
<h3 id="웹-어플리케이션과-싱글톤">웹 어플리케이션과 싱글톤</h3>
<ul>
<li>spring은 태생이 기업용 온라인 서비스 기술을 지원하기 위해 탄생했다.</li>
<li>대부분의 spring 어플리케이션은 웹 어플리케이션이다. 물론 웹이 아닌 어플리케이션 개발도 얼마든지 개발할 수 있다.</li>
<li>웹 어플리케이션은 보통 여러 고객이 <strong>동시에 요청</strong>을 한다.
<img src="https://velog.velcdn.com/images/kor_hoonie/post/a728e6f0-9d85-4f72-96fa-784d12c7c384/image.png" alt=""></li>
</ul>
<h4 id="스프링-없는-순수한-di-컨테이너-테스트">스프링 없는 순수한 DI 컨테이너 테스트</h4>
<pre><code class="language-java">package hello.core.singleton;

import hello.core.AppConfig;
import hello.core.member.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

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);
        Assertions.assertThat(memberService1).isNotSameAs(memberService2);


    }
}</code></pre>
<ul>
<li>우리가 만들었던 스프링 없는 순수한 DI 컨테이너인 AppConfig는 요청을 할 때 마다 객체를 새로 생성한다.</li>
<li>고객 트래픽이 초당 100이 나오면 초당 100개의 객체가 생성되고 소멸된다! -&gt; 메모리 낭비가 심하다.</li>
<li>해결방안은 해당 객체가 딱 <strong>1개만 생성</strong>되고, 공유하도록 설계하면 된다. -&gt; 싱글톤 패턴</li>
</ul>
<h3 id="싱글톤-패턴">싱글톤 패턴</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">package hello.core.singleton;

public class SingletonService {

    // 1. static 영역에 객체를 딱 1개만 생성해둔다.
    private static final SingletonService instance = new SingletonService();

    // 2. public으로 열어서 객체 인스턴스가 필요하면 이 static 메서드를 통해서만 조회하도록 허용한다.

    public static SingletonService getInstance() {
        return instance;
    }

    // 3. 생성자를 private으로 선언해서 외부에서 new 키워드를 사용한 객체 생성을 못하게 막는다.
    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>싱글톤 패턴을 사용하는 테스트 코드를 보자.(위에서 작성한 SingletonTest.java에 작성)</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.println(&quot;singletonService2 = &quot; + singletonService2);

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

        singletonService1.logic();

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

        Assertions.assertThat(memberService1).isSameAs(memberService2);

    }</code></pre>
<h4 id="싱글톤-컨테이너-적용-후">싱글톤 컨테이너 적용 후</h4>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/63eed244-3aec-4d6d-8f20-72167c521ffb/image.png" alt=""></p>
<ul>
<li>스프링 컨테이너 덕분에 고객의 요청이 올 때마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유해서 효율적으로 재사용 할 수 있다.<blockquote>
<p>참고: 스프링의 기본 빈 등록 방식은 싱글톤이지만, 당연하게도 싱글톤 방식만 가능한 것은 아니다. 필요에 따라서 요청할 때마다 새로운 객체를 생성하고자 한다면 얼마든지 구현할 수 있다. 자세한 내용은 추후에 빈 스코프 포스트에서 설명하겠다.</p>
</blockquote>
</li>
</ul>
<h4 id="싱글톤-방식의-주의점">싱글톤 방식의 주의점</h4>
<ul>
<li>싱글톤 패턴이든, 스프링 같은 싱글톤 컨테이너를 사용하든, 객체 인스턴스를 하나만 생성해서 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태유지(stateful) 방식으로 설계하면 안된다!</li>
<li><strong>무상태(stateless)</strong>방식으로 설계해야한다.<ul>
<li>특정 클라이언트에 의존적인 필드가 있으면 안된다.</li>
<li>특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.</li>
<li>따라서 가급적 읽기만 가능하도록 설계해야한다.</li>
<li>필드 대신 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.</li>
</ul>
</li>
<li>스프링 빈의 필드에 공유 값을 설정하면 예상치 못한(오류를 잡기 힘든) 장애가 발생할 수 있다.<br></li>
<li><em>상태를 유지할 경우 발생하는 문제점 예시 - Stateful한 서비스 설계*</em><pre><code class="language-java">package hello.core.singleton;
</code></pre>
</li>
</ul>
<p>public class StatefulService {
    private int price; // 상태를 유지하는 필드</p>
<pre><code>public void order(String name, int price) {
    System.out.println(&quot;name = &quot; + name + &quot; price = &quot; + price);
    this.price = price;
}

public int getPrice() {
    return price;
}</code></pre><p>}</p>
<pre><code>**상태를 유지할 경우 발생하는 문제점 예시 - 테스트 코드**
```java
package hello.core.singleton;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;

public class StatefulServiceTest {

    @Test
    void statefulServiceSingleton() {
        ApplicationContext 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사용자 10000원 주문
        statefulService1.order(&quot;userA&quot;, 10000);
        // ThreadB: B사용자 20000원 주문
        statefulService2.order(&quot;userB&quot;, 20000);

        // ThreadA: userA 주문 금액 조회
        int price = statefulService1.getPrice();

        // ThreadA: userA는 10000원을 기대했지만 기대와는 다르게 20000원이 출력
        System.out.println(&quot;price = &quot; + price);

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


    }

    static class TestConfig {

        @Bean
        public StatefulService statefulService() {
            return new StatefulService();
        }
    }
}</code></pre><ul>
<li>statful 상황에 집중하기 위해 실제 쓰레드는 사용하지 않았다.</li>
<li>ThreadA가 사용자A 코드를 호출하고 ThreadB가 사용자B 코드를 호출한다.</li>
<li><code>StatefulService</code>의 <code>price</code>필드는 공유되는 필드인데, 특정 클라이언트(A 또는 B)가 값을 변경한다. -&gt; 여기서 문제가 발생!!</li>
<li>사용자A의 주문금액은 10000원인데 20000원이라는 결과가 나왔다.</li>
<li>이런 문제점 때문에 공유필드는 정말 조심해야한다. -&gt; <strong>스프링 빈은 항상 무상태(stateless)로 설계하자!!!</strong></li>
</ul>
<h3 id="configuration과-싱글톤">@Configuration과 싱글톤</h3>
<p>그런데,, 이상한 점이 있다. 다음 <code>AppConfig</code> 코드를 보자</p>
<pre><code class="language-java">package hello.core;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@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 FixDiscountPolicy();
        return new RateDiscountPolicy();
    }

}</code></pre>
<ul>
<li>memberService 빈을 만드는 코드를 보면 <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>가 생성되면서 싱글톤이 깨지는 것처럼 보인다. 스프링 컨테이너는 이 문제를 어떻게 해결하는지 직접 테스트 코드로 알아보자
<strong>검증용 테스트 코드 (MemberServiceImpl과 OrderServiceImpl에 조회 기능 추가)</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 memberRepository;

    // 테스트 용도(추가된 코드)
    public MemberRepository getMemberRepository() {
        return memberRepository;
    }
}</code></pre>
<p><strong>테스트 코드</strong></p>
<pre><code class="language-java">package hello.core.singleton;

import hello.core.AppConfig;
import hello.core.member.MemberRepository;
import hello.core.member.MemberServiceImpl;
import hello.core.order.OrderServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ConfigurationSingletonTest {
    @Test
    void configurationTest() {
        ApplicationContext ac = new 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 -&gt; memberRepository = &quot; + memberRepository);

        Assertions.assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
        Assertions.assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
    }
}</code></pre>
<ul>
<li>위의 코드에서 확인했듯이 memberRepository 인스턴스는 모두 같은 인스턴스가 공유되어 사용된다.</li>
<li>AppConfig의 자바 코드를 보면 분명히 각각 2번 <code>new MemoryMemberRepository</code>를 호출해서 다른 인스턴스가 생성되어야 하는데,,,? 혹시 두 번 호출이 안되는 것인지 테스트를 통해 알아보자.</li>
<li><em>AppConfig*</em>에 호출 로그 남김<pre><code class="language-java">package hello.core;
</code></pre>
</li>
</ul>
<p>import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;</p>
<p>@Configuration
public class AppConfig {
    @Bean
    public MemberService memberService() {
        System.out.println(&quot;call AppConfig.memberService&quot;);
        return new MemberServiceImpl(memberRepository());
    }</p>
<pre><code>@Bean
public OrderService orderService() {
    System.out.println(&quot;call AppConfig.orderSerivce&quot;);
    return new OrderServiceImpl(
            memberRepository(),
            discountPolicy());
}

@Bean
public MemberRepository memberRepository() {
    System.out.println(&quot;call AppConfig.memberRepository&quot;);
    return new MemoryMemberRepository();
}

@Bean
public DiscountPolicy discountPolicy() {</code></pre><p>//        return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }</p>
<p>}</p>
<pre><code>- 호출 로그를 남겨서 memberService, orderService, memoryRepository가 각각 몇 번 호출되는지 알아보았다.
- 예상 시나리오
   - 스프링 컨테이너가 각각 ```@Bean```을 호출해서 스프링 빈을 생성한다. 그래서 ```memoryRepository()```는 다음과 같은 로직으로 총 3번 호출될 것이라고 예상된다.
       1. 스프링 컨테이너가 스프링 빈에 등록하기 위해 ```@Bean```이 붙어있는 ```memberRepository()``` 호출
    2. memberService() 로직에서 ```memberRepository()``` 호출
    3. orderService() 로직에서 ```memberRepository()``` 호출

그런데 출력결과는 각각 1번만 호출된다.

#### @Configuration과 바이트코드 조작의 마법

스프링 컨테이너는 싱글톤 레지스트리다. 따라서 스프링 빈이 싱글톤이 되도록 보장해주어야 한다. 그런데 **스프링이 자바 코드까지 자신의 방식대로 동작하도록 할 수는 없다.** 자바 언어의 규칙대로라면 분명히 3번 호출되어야 하는 것이 지당하다. 그래서 스프링은 클래스의 **바이트코드**를 조작하는 라이브러리를 사용한다.
**모든 비밀은 ```@Configuration```을 적용한 ```AppConfig```에 있다!!**

다음 코드를 보자
```java
    @Test
    void configurationDeep() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

        // AppConfig도 스프링 빈으로 등록된다(이전 포스트에서 배웠다.)
        AppConfig bean = ac.getBean(AppConfig.class);

        System.out.println(&quot;bean = &quot; + bean.getClass());
        // 출력: bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$721f06a6
    }</code></pre><ul>
<li>사실 <code>AnnotationConfigApplicationContext</code>에 파라미터로 넘긴 값은 스프링 빈으로 등록된다. 그래서 <code>AppConfig</code>도 스프링 빈이 된다.</li>
<li><code>AppConfig</code> 스프링 빈을 조회해서 클래스 정보를 출력해보자.<ul>
<li>만약 순수한 클래스라면 다음과 같이 출력되어야 한다.</li>
<li><code>class.hello.core.AppConfig</code>
그런데 예상과는 다르게 클래스 명에 &#39;EnhancerBySpring<strong>CGLIB</strong>&#39;가 붙으면서 상당히 복잡해진 것을 볼 수 있다. 이것은 내가 만든 클래스가 아니라 스프링이 <strong>CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한 것이다!!</strong></li>
</ul>
</li>
</ul>
<p><strong>그림</strong>
<img src="https://velog.velcdn.com/images/kor_hoonie/post/e9f858ed-40fc-4c73-9756-1fbd449a45a0/image.png" alt="">
이 임의의 다른 클래스가 바로 싱글톤이 보장되도록 해준다. 아마도 다음과 같이 바이트 코드를 조작해서 작성되어있을 것이다.(실제로는 CGLIB의 내부 기술은 매우 복잡하다...)
<br>
<strong>AppConfig@CGLIB 예상 코드</strong></p>
<pre><code class="language-java">@Bean
public MemberRepository memberRepository () {
    if(memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면) {
        return 스프링 컨테이너에서 찾은 memoryMemberRepository;
    }
    else {
        기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
        return 방금 만든 MemoryMemberRepository;
    }
}</code></pre>
<ul>
<li>@Bean이 붙은 메서드마다 이미 컨테이너에 등록되어 있다면 등록된 객체를 반환하고 아니면 기존 로직을 활용해서 새로 만든 뒤 반환하는 형식으로 코드가 작성되어 있을 것이다.</li>
<li>덕분에 싱글톤이 보장되는 것이다.<blockquote>
<p>참고: AppConfig@CGLIB는 AppConfig의 자식 타입이므로, AppConfig타입으로 조회가 가능하다.</p>
</blockquote>
</li>
</ul>
<h4 id="configuration을-적용하지-않고-bean만-적용하면-어떻게-될까"><code>@Configuration</code>을 적용하지 않고, <code>@Bean</code>만 적용하면 어떻게 될까?</h4>
<p><code>@Configuration</code>을 붙이면 바이트코드를 조작하는 CGLIB기술을 사용해서 싱글톤을 보장하지만, 만약 @Bean만 적용하면 어떻게 될까?</p>
<ul>
<li>기존 AppConfig.java에서 <code>@Configuration</code> 어노테이션만 제거 후 실행해보자.</li>
</ul>
<p><strong>결과</strong></p>
<ul>
<li>앞서 AppConfig의 클래스 정보에 CGLIB가 출력되었지만 지금은 그냥 AppConfig만 출력된다. 따라서 CGLIB 기술이 적용되지 않은 순수한 AppConfig가 스프링 빈으로 등록되었음을 알 수 있다.</li>
<li>memberServiceImpl와 orderServiceImpl의 memberRepository, memberRepository의 참조값이 전부 다르다. 따라서 당연히 참조값이 같다고 작성한 test코드도 실패한다.</li>
<li>또한 memberService와 orderService가 호출될 때마다 <code>memberRepository()</code> 로그에 찍히는 것을 볼 수 있다.</li>
<li><del>(중요)</del> 확인이 끝났으면 AppConfig.java에 다시 <code>@Configuration</code> 어노테이션을 달아놓자.</li>
</ul>
<h3 id="정리">정리</h3>
<ul>
<li><code>@Bean</code>만 사용해도 스프링 빈으로 등록되지만, 싱글톤을 보장하지 않는다.</li>
<li>크게 고민할 것이 없다. 스프링의 강력한 <code>@Configuration</code>어노테이션을 항상 사용하자.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[spring으로의 전환]]></title>
            <link>https://velog.io/@kor_hoonie/change-to-spring</link>
            <guid>https://velog.io/@kor_hoonie/change-to-spring</guid>
            <pubDate>Mon, 13 Feb 2023 09:01:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>인프런 김영한님의 강의 &#39;스프링 핵심 원리 - 기본편&#39;의 강의 내용을 참고하여 작성한 글입니다.</p>
</blockquote>
<br>
지금까지는 순수한 java 코드만으로 DI를 적용해서 서비스를 개발하였고, 이제 본격적으로 spring을 사용해보자

<h4 id="appconfig-스프링-기반으로-변경">AppConfig 스프링 기반으로 변경</h4>
<pre><code class="language-java">package hello.core;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(new MemoryMemberRepository());
    }

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

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

    @Bean
    public DiscountPolicy discountPolicy() {
//        return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }

}</code></pre>
<ul>
<li>AppConfig에 설정을 구성한다는 뜻의 어노테이션(@Configuration)을 붙여준다.</li>
<li>각 메서드에 @Bean을 붙여준다. 이렇게 하면 스프링 컨테이너에 스프링 빈으로 등록해줄 수 있다.</li>
</ul>
<h4 id="memberapp에-스프링-컨테이너-적용">MemberApp에 스프링 컨테이너 적용</h4>
<pre><code class="language-java">package hello.core;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

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;findMember = &quot; + findMember.getName());
    }
}</code></pre>
<h4 id="orderapp에-스프링-컨테이너-적용">OrderApp에 스프링 컨테이너 적용</h4>
<pre><code class="language-java">package hello.core;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.order.Order;
import hello.core.order.OrderService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

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;memberService&quot;, MemberService.class);
        OrderService orderService = 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>기존에 AppConfig를 생성하고 메서드를 실행해서 주입해주는 방식이 아닌 AppConfig의 정보를 읽어서 AnnotationContext 컨테이너를 생성하고 거기에 등록되어있는 빈들을 불러오는 방식으로 변경하였다.</li>
</ul>
<h4 id="스프링-컨테이너">스프링 컨테이너</h4>
<ul>
<li><code>ApplicationContext</code>를 스프링 컨테이너라 한다.</li>
<li>기존에는 개발자가 AppConfig를 사용해서 직접 객체를 생성하고 DI를 했지만, 이제부터는 스프링컨테이너를 통해서 사용한다.</li>
<li>스프링 컨테이너는 <code>@Configuration</code> 어노테이션이 붙은 <code>AppConfig</code>를 설정(구성) 정보로 사용한다. 여기서 <code>@Bean</code>어노테이션이 붙은 메서드를 모두 호출해서 반환된 객체를 <strong>스프링 컨테이너</strong>에 등록한다. 이렇게 스프링 컨테이너에 등록된 객체를 <strong>스프링 빈</strong>이라고 한다.</li>
<li>스프링 빈은 <code>@Bean</code>이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다.(<code>memberService</code>, <code>OrderService</code>)</li>
<li>이전에는 개발자가 필요한 객체를 <code>AppConfig</code>를 사용해서 직접 조회했지만, 이제부터는 스프링 컨테이너를 통해서 필요한 스프링 빈(객체)를 찾아야한다. 스프링 빈은 <code>applicationContext.getBean()</code>메서드를 사용해서 찾을 수 있다.</li>
<li>기존에는 개발자가 직접 자바코드로 모든 것을 했다면 이제부터는 스프링 컨테이너에 객체를 스프링 빈으로 등록하고, 스프링 컨테이너에서 스프링 빈을 찾아서 사용하도록 변경되었다.</li>
<li>단순하게 생각하면 어노테이션, applicationContext 객체 등 코드가 더 복잡해진 것같은데 어떤 장점이 있을까?</li>
</ul>
<h3 id="스프링-컨테이너-생성">스프링 컨테이너 생성</h3>
<p>스프링 컨테이너가 생성되는 과정을 알아보자</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 AnnotationConfigApplicationContext(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>
<h4 id="스프링-컨테이너의-생성과정">스프링 컨테이너의 생성과정</h4>
<ol>
<li>스프링 컨테이너 생성
<img src="https://velog.velcdn.com/images/kor_hoonie/post/837bd59e-45d9-49f0-ad40-480a76d3c22f/image.png" alt=""></li>
</ol>
<ul>
<li><code>new AnnotationConfigApplicationContext(AppConfig.class);</code></li>
<li>스프링 컨테이너를 생성할 때는 구성 정보를 지정해주어야 한다.</li>
<li>여기서는 <code>AppConfig.class</code>를 구성 정보로 지정했다.</li>
</ul>
<ol start="2">
<li>스프링 빈 등록
<img src="https://velog.velcdn.com/images/kor_hoonie/post/0bbc074f-ec2f-4615-8968-d707678ecb79/image.png" alt=""></li>
</ol>
<ul>
<li>스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용해서 스프링 빈을 등록한다.</li>
<li>빈(Bean) 이름<ul>
<li>빈 이름은 메서드 이름을 사용한다.</li>
<li>빈 이름을 직접 부여할 수도 있다. </li>
<li>@Bean(name = &quot;mySpecialMemberService&quot;)<blockquote>
<p>주의: <strong>빈 이름은 항상 다른 이름을 부여</strong>해야 한다. 같은 이름을 부여하면, 다른 빈이 무시되거나 기존 빈을 덮어버리거나 설정에 따라서는 오류가 발생할 수도 있다. 
따라서 통상적인 관례를 따르는 것이 협업시 의사소통에 편리하므로 특별한 경우가 아니면 메서드 이름을 사용하자.</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<ol start="3">
<li>스프링 빈 의존관계 설정 - 준비
<img src="https://velog.velcdn.com/images/kor_hoonie/post/1a238aaf-590d-4ce6-acde-5fbaf9883e97/image.png" alt=""></li>
<li>스프링 빈 의존관계 설정 - 완료
<img src="https://velog.velcdn.com/images/kor_hoonie/post/8550e716-dd01-456b-aeeb-76eb54d5c486/image.png" alt=""></li>
</ol>
<ul>
<li>스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)한다.</li>
<li>단순히 자바 코드를 호출하는 것 같지만 차이가 있다. 이 차이는 뒤에 <strong>싱글톤 컨테이너</strong>에서 설명한다.<blockquote>
<p>참고: 스프링 빈을 생성하고, 의존관계를 주입하는 단계가 나누어져 있다. 그런데 이렇게 자바 코드로 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한번에 처리된다. 여기서는 이해를 돕기 위해 개념적으로 나누어 설명했따. 자세한 내용은 <strong>의존관계 자동 주입</strong>에서 다시 설명하겠다.</p>
</blockquote>
</li>
</ul>
<h4 id="정리">정리</h4>
<p>스프링 컨테이너를 생성하고, 설정(구성) 정보를 참고해서 스플이 빈도 등록하고, 의존관계도 설정했다. 이제 스프링 컨테이너에서 데이터를 조회해보자.</p>
<h3 id="컨테이너에-등록된-모든-빈-조회">컨테이너에 등록된 모든 빈 조회</h3>
<p>스프링 컨테이너에 실제 스프링 빈들이 잘 등록되었는지 확인해보자.</p>
<h4 id="컨테이너에-등록된-빈-조회---테스트-코드-작성">컨테이너에 등록된 빈 조회 - 테스트 코드 작성</h4>
<pre><code class="language-java">package hello.core.beanfind;

import hello.core.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Role;

import static org.junit.jupiter.api.Assertions.*;

class ApplicationContextInfoTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

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

    @Test
    @DisplayName(&quot;애플리케이션 빈 출력하기&quot;)
    void findApplicationBean() {
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
//        Role ROLE_APPLICATION: 직접 등록한 애플리케이션 빈
//        Role ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈

        for(String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
            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>모든 빈 출력하기<ul>
<li>실행하면 스프링에 등록된 모든 빈 정보를 출력할 수 있다.</li>
<li><code>ac.getBeanDefinitionNames()</code>: 스프링에 등록된 모든 빈 이름을 조회한다.</li>
<li><code>ac.getBean()</code>: 빈 이름으로 빈 객체(인스턴스)를 조회한다.</li>
</ul>
</li>
<li>애플리케이션 빈 출력하기<ul>
<li>스프링이 내부에서 사용하는 빈은 제외하고, 내가 등록한 빈만 출력</li>
<li>스프링이 내부에서 사용하는 빈은 <code>getRole()</code>로 구분할 수 있다.<ul>
<li><code>ROLE_APPLICATION</code>: 일반적으로 사용자가 정의한 빈</li>
<li><code>ROLE_INFRASTRUCTURE</code>: 스프링이 내부에서 사용하는 빈<h4 id="스프링-빈-조회---기본">스프링 빈 조회 - 기본</h4>
스프링 컨테이너에서 스프링 빈을 찾는 가장 기본적인 조회 방법</li>
</ul>
</li>
</ul>
</li>
<li><code>ac.getBean(빈이름, 타입)</code></li>
<li><code>ac.getBean(타입)</code></li>
<li>조회 대상 스프링 빈이 없으면 예외 발생<ul>
<li><code>NoSuchBeanDefinitionException: No bean named &#39;xxxx&#39; available</code></li>
</ul>
</li>
</ul>
<p><strong>예제 테스트 코드</strong></p>
<pre><code class="language-java">package hello.core.beanfind;

import hello.core.AppConfig;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

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(MemberService.class);
    }
    @Test
    @DisplayName(&quot;이름없이 타입만으로 조회&quot;)
    void findBeanByType() {
        MemberService memberService = ac.getBean(MemberService.class);
        assertThat(memberService).isInstanceOf(MemberService.class);
    }

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

    @Test
    @DisplayName(&quot;빈 이름으로 조회X&quot;)
    void findBeanByNameX() {
        assertThrows(NoSuchBeanDefinitionException.class, () -&gt;
                ac.getBean(&quot;xxxx&quot;, MemberService.class));
    }
}</code></pre>
<blockquote>
<p>참고: 구체 타입으로 조회하면 변경시 유연성이 떨어진다.</p>
</blockquote>
<h4 id="스프링-빈-조회---동일한-타입-둘-이상">스프링 빈 조회 - 동일한 타입 둘 이상</h4>
<ul>
<li>타입으로 조회시 같은 타입의 스프링 빈이 둘 이상이면 오류가 발생한다. 이때는 빈 이름을 지정하자.</li>
<li><code>ac.getBeansOfType()</code>을 사용하면 해당 타입의 모든 빈을 조회할 수 있다.</li>
<li><em>예제 코드 - 테스트*</em><pre><code class="language-java">package hello.core.beanfind;
</code></pre>
</li>
</ul>
<p>import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;</p>
<p>import java.util.Map;</p>
<p>import static org.assertj.core.api.Assertions.*;</p>
<p>public class ApplicationContextSameBeanFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);</p>
<pre><code>@Test
@DisplayName(&quot;타입으로 조회시 같은 타입이 둘 이상 있으면, 중복오류가 발생한다.&quot;)
void findBeanByTypeDuplicate() {
    Assertions.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><p>}</p>
<pre><code>
#### 스프링 빈 조회 - 상속 관계
- 부모 타입으로 조회하면, 자식 타입도 함께 조회된다.
- 그래서 모든 자바 객체의 최고 부모인 ```Object```타입으로 조회하면, 모든 스프링 빈을 조회한다.
**예제 코드 - 테스트**
```java
package hello.core.beanfind;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

public class ApplicationContextExtendsFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

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

    @Test
    @DisplayName(&quot;부모 타입으로 조회시, 자식이 둘 이상 있으면, 빈 이름을 지정하면 된다.&quot;)
    void findBeanByParentTypeBeanName() {
        DiscountPolicy bean = ac.getBean(&quot;rateDiscountPolicy&quot;, DiscountPolicy.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><h3 id="beanfactory와-applicationcontext">BeanFactory와 ApplicationContext</h3>
<p>beanFactory와 ApplicationContext에 대해서 알아보자
<img src="https://velog.velcdn.com/images/kor_hoonie/post/5d448928-1b71-4775-9e9d-d48f0ec98e7f/image.png" alt=""></p>
<h4 id="beanfactory">BeanFactory</h4>
<ul>
<li>스프링 컨테이너의 최상위 인터페이스다.</li>
<li>스프링 빈을 관리하고 조회하는 역할을 담당한다.</li>
<li><code>getBean()</code>을 제공한다.</li>
<li>지금까지 우리가 사용했던 대부분의 기능은 BeanFactory가 제공하는 기능이다.</li>
</ul>
<h4 id="applicationcontext">ApplicationContext</h4>
<ul>
<li>BeanFactory 기능을 모두 상속받아서 제공한다.</li>
<li>빈을 관리하고 검색하는 기능을 BeanFactory가 제공해주는데 둘의 차이는 뭘까?</li>
<li>어플리케이션을 개발할 때는 빈을 관리하고 조회하는 기능은 물론이고 수 많은 부가기능이 필요하다.</li>
<li><em>ApplicationContext가 제공하는 부가기능*</em>
<img src="https://velog.velcdn.com/images/kor_hoonie/post/0f0393b6-1c3a-4b15-a2b9-823632ee98dc/image.png" alt=""></li>
<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>
<h3 id="정리-1">정리</h3>
<ul>
<li>ApplicationContext는 BeanFactory의 기능을 상속받는다.</li>
<li>ApplicationContext는 빈 관리기능 + 편리한 부가기능을 제공한다.</li>
<li>BeanFactory를 직접 사용할 일은 거의 없다. 부가기능이 포함된 ApplicationContext를 사용한다.</li>
<li>BeanFactory나 ApplicationContext를 <strong>스프링 컨테이너</strong>라고 한다.</li>
</ul>
<h3 id="다양한-설정-형식-지원---자바-코드-xml">다양한 설정 형식 지원 - 자바 코드, XML</h3>
<ul>
<li>스프링 컨테이너는 다양한 형식의 설정 정보를 받아드릴 수 있게 유연하게 설계되어 있다.<ul>
<li>자바 코드, XML, Groovy 등
<img src="https://velog.velcdn.com/images/kor_hoonie/post/204f114c-c4c1-4eca-8ae6-3dc16b76e01c/image.png" alt=""></li>
</ul>
</li>
</ul>
<h4 id="어노테이션-기반-자바-코드-설정-사용">어노테이션 기반 자바 코드 설정 사용</h4>
<ul>
<li>지금까지 사용했던 설정</li>
<li><code>new AnnotationConfigApplicationContext(AppConfig.class)</code></li>
<li><code>AnnotationConfigApplicationContext</code> 클래스를 사용하면서 자바 코드로 된 설정 정보를 넘기면 된다.</li>
</ul>
<h4 id="xml-설정-사용">XML 설정 사용</h4>
<ul>
<li>최근에는 스프링 부트를 많이 사용하면서 XML기반의 설정은 잘 사용하지 않는다. 하지만 아직은 많은 레거시 프로젝트들이 XML로 되어있고 또 XML을 사용하면 컴파일 없이 빈 설정 정보를 변경할 수 있느 장점도 있어서 배워두면 좋다.</li>
</ul>
<p><strong>XMLAppConfig 사용 자바 코드</strong></p>
<pre><code class="language-java">package hello.core.xml;

import hello.core.member.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

import static org.assertj.core.api.Assertions.*;

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).isInstanceOf(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.java</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>
<h3 id="스프링-빈-설정-메타-정보---beandefinition">스프링 빈 설정 메타 정보 - BeanDefinition</h3>
<ul>
<li>스프링은 어떻게 이런 다양한 설정 형식을 지원하는 것일까? 그 중심에는 <code>BeanDefinition</code>이라는 추상화가 있다.</li>
<li>쉽게 이야기해서 <strong>역할과 구현을 개념적으로 나눈 것</strong>이다!<ul>
<li>XML을 읽어서 BeanDefinition을 만들면 된다.</li>
<li>자바 코드를 읽어서 BeanDefinition을 만들면 된다.</li>
<li>스프링 컨테이너는 자바 코드인지, XML인지 몰라도 된다. 오직 BeanDefinition만 알면 된다.</li>
</ul>
</li>
<li><code>BeanDefinition</code>을 빈 설정 메타정보라 한다.<ul>
<li><code>@Bean</code>, <code>&lt;bean&gt;</code>당 각각 하나씩 메타 정보가 생성된다.</li>
</ul>
</li>
<li>스프링 컨테이너는 이 메타 정보를 기반으로 스프링 빈을 생성한다.
<img src="https://velog.velcdn.com/images/kor_hoonie/post/ed13317b-5ee0-4deb-b777-6cb633aeb7f9/image.png" alt=""></li>
<li><em>코드 레벨로 조금 더 깊이 있게 들어가보자*</em>
<img src="https://velog.velcdn.com/images/kor_hoonie/post/8430322b-5b57-4491-a7a6-32a57025cae8/image.png" alt=""></li>
<li><code>AnnotationConfigApplicationContext</code>는 <code>AnnotationBeanDefinitionReader</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>
<h3 id="beandefinition-살펴보기">BeanDefinition 살펴보기</h3>
<h4 id="beandefinition-정보">BeanDefinition 정보</h4>
<ul>
<li>BeanClassName: 생성할 빈의 클래스 명(자바 설정처럼 팩토리 역할의 빈을 사용하면 없음)</li>
<li>factoryBeanName: 팩토리 역할의 빈을 사용할 경우 이름, 예) appConfig</li>
<li>factoryMethodName: 빈을 생성할 팩토리 메서드 지정, 예) memberService</li>
<li>Scope: 싱글톤(default)</li>
<li>lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때까지 최대한 생성을 지연처리 하는지 여부</li>
<li>DestroyMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명</li>
<li>Constructor arguments, Properties: 의존관계 주입에서 사용한다.(자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)</li>
<li><em>예제 코드 - 테스트*</em><pre><code class="language-java">package hello.core.beandefinition;
</code></pre>
</li>
</ul>
<p>import hello.core.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;</p>
<p>public class BeanDefinitionTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);</p>
<pre><code>@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>}</p>
<p>```</p>
<h3 id="정리-2">정리</h3>
<ul>
<li>BeanDefinition을 직접 생성해서 스프링 컨테이너에 등록할 수도 있다. 하지만 실무에서 BeanDefinition을 직접 정의하거나 사용할 일은 거의 없다.</li>
<li>BeanDefinition에 대해서는 너무 깊이 있는 이해보다는 스프링이 다양한 형태의 설정 정보를 BeanDefinition으로 추상화해서 사용한다는 정도만 이해하고 있으면 된다.</li>
<li>가끔 스프링 코드나 스프링 관련 오픈 소스의 코드를 볼 때 BeanDefinition이 등장하면 앞서 배운 메커니즘을 떠올려보자.</li>
</ul>
<h3 id="다음-포스트">다음 포스트</h3>
<ul>
<li>아까 BeanDefinition의 정보 중 Scope의 기본값이 싱글톤이라고 했는데, 이 싱글톤이 무엇이고 왜 필요한지에 대해 다뤄볼 예정이다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[객체 지향 원리 적용]]></title>
            <link>https://velog.io/@kor_hoonie/develop-OOP</link>
            <guid>https://velog.io/@kor_hoonie/develop-OOP</guid>
            <pubDate>Thu, 09 Feb 2023 06:42:37 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>인프런 김영한님의 강의 &#39;스프링 핵심 원리 - 기본편&#39;의 강의 내용을 참고하여 작성한 글입니다.</p>
</blockquote>
<h3 id="새로운-할인-정책정률할인정책-개발">새로운 할인 정책(정률할인정책) 개발</h3>
<p>서비스 기획 과정에서 이전에 적용하던 정액할인정책(fixDiscountPolicy)보단 정률할인정책이 적합하다는 판단하에 서비스에 정률할인정책 적용하고자 한다. 객체 지향 원리를 적용하여 서비스의 할인정책을 수정해보자.</p>
<h3 id="ratediscountpolicy-추가">RateDiscountPolicy 추가</h3>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/940c0e51-cbe7-417f-aebb-da16815e99f9/image.png" alt=""></p>
<h3 id="ratediscountpolicy-코드-추가">RateDiscountPolicy 코드 추가</h3>
<pre><code class="language-java">package hello.core.discount;

import hello.core.member.Grade;
import hello.core.member.Member;

public class RateDiscountPolicy implements DiscountPolicy{
    private int discountRate = 10; // 10% 할인
    @Override
    public int discount(Member member, int price) {
        if(member.getGrade() == Grade.VIP){
            return price * discountRate / 100;
        }
        return 0;
    }
}</code></pre>
<h3 id="테스트-작성">테스트 작성</h3>
<pre><code class="language-java">package hello.core.discount;

import hello.core.member.Grade;
import hello.core.member.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class RateDiscountPolicyTest {
    RateDiscountPolicy discountPolicy = new RateDiscountPolicy();

    @Test
    @DisplayName(&quot;VIP는 가격의 10% 할인이 적용되어야한다.&quot;)
    void vip_o(){
        // given
        Member member = new Member(1L, &quot;memberA&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(1L, &quot;memberA&quot;, Grade.BASIC);
        // when
        int discount = discountPolicy.discount(member, 10000);
        // then
        Assertions.assertThat(discount).isEqualTo(0);
    }
}</code></pre>
<h3 id="새로운-할인-정책-적용과-문제점">새로운 할인 정책 적용과 문제점</h3>
<h4 id="할인-정책을-어플리케이션에-적용해보자">할인 정책을 어플리케이션에 적용해보자.</h4>
<p><strong>할인 정책을 변경하려면 클라이언트인 <code>OrderServiceImpl</code> 코드를 고쳐야 한다.</strong></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>
<h4 id="문제점-발견">문제점 발견</h4>
<ul>
<li>역할과 구현을 충실하게 분리했다. -&gt; OK</li>
<li>다형성을 활용하고, 인터페이스와 구현 객체를 분리했다. -&gt; OK</li>
<li><strong>OCP, DIP 같은 객체지향 설계 원칙을 충실히 준수했다?</strong><ul>
<li>-&gt; 그렇게 보이지만 사실은 아니다!</li>
</ul>
</li>
<li>DIP(dependency inversion principle): 주문서비스 클라이언트<code>OrderServiceImpl)</code>는 <code>DiscountPolicy</code>인터페이스에 의존하면서 DIP를 지킨 것 같지만 추상(인터페이스)뿐만 아니라 <strong>구체(구현)클래스</strong>에도 의존하고 있음을 알 수 있다!<ul>
<li>추상(인터페이스) 의존: <code>DiscountPolicy</code></li>
<li>구체(구현) 클래스: <code>FixDiscountPolicy</code>, <code>RateDiscountPolicy</code></li>
</ul>
</li>
<li>OCP(open-closed principle): <strong>변경없이 확장</strong>할 수 있어야하는데...?<ul>
<li>-&gt; <strong>현재의 코드는 기능을 확장(할인정책변경)하기 위해선 클라이언트 코드를 변경해야하므로 OCP를 위반한다.</strong></li>
</ul>
</li>
</ul>
<h3 id="왜-클라이언트를-변경해야하는-문제점이-발생-했을까">왜 클라이언트를 변경해야하는 문제점이 발생 했을까?</h3>
<h4 id="기대했던-의존관계">기대했던 의존관계</h4>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/1a834e60-5fd5-4f0f-98b2-4cfe97736a9b/image.png" alt=""></p>
<ul>
<li>위와 같이 단순히 <code>DiscountPolicy</code>에만 의존한다고 생각하였지만 아니었다.
<br><br><h4 id="실제-의존관계">실제 의존관계</h4>
<img src="https://velog.velcdn.com/images/kor_hoonie/post/10a37e28-dc24-47e1-8af7-c883c9f218b7/image.png" alt=""></li>
<li><code>OrderServiceImpl</code>을 보면 인터페이스<code>DiscountPolicy</code>뿐만 아니라 구현 클래스<code>FixDiscountPolicy</code>와 <code>RateDiscountPolicy</code>에도 의존함을 알 수 있다. -&gt; DIP 위반
<br><br></li>
</ul>
<h4 id="정책-변경">정책 변경</h4>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/1b58ecdd-700a-4992-9aea-a2e318006e13/image.png" alt=""></p>
<ul>
<li>따라서 정책 변경을 위해 <code>FixDiscountPolicy</code>를 <code>RateDiscountPolicy</code>로 변경하는 순간 <code>OrderServiceImpl</code>의 코드도 변경해야한다. -&gt; OCP 위반
<br><br></li>
</ul>
<h3 id="어떻게-문제를-해결할-수-있을까">어떻게 문제를 해결할 수 있을까?</h3>
<ul>
<li>구체 클래스를 변경하여도 클라이언트 코드에 변경이 없도록 바꿔야한다.</li>
<li><strong>즉 DIP를 위반하지 않도록 추상(인터페이스)에만 의존하도록 의존관계를 변경해야한다.</strong></li>
</ul>
<h4 id="인터페이스에만-의존하도록-설계를-변경하자">인터페이스에만 의존하도록 설계를 변경하자</h4>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/cd1e8ac9-30fd-464f-ba15-aeff7da5edab/image.png" alt=""></p>
<h4 id="인터페이스에만-의존하도록-코드-변경">인터페이스에만 의존하도록 코드 변경</h4>
<pre><code class="language-java">public class OrderServiceImpl implements OrderService {
//    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
    private DiscountPolicy discountPolicy;
}</code></pre>
<ul>
<li>인터페이스에만 의존하도록 설계와 코드를 변경했다.</li>
<li>new RateDiscountPolicy()가 사라지면서 구현체가 없어졌는데 어떻게 작동할 수 있을까?</li>
<li>실제로 실행을 해보면 null pointer exception이 발생한다.</li>
</ul>
<h4 id="해결방안">해결방안</h4>
<ul>
<li>이 문제를 해결하기 위해선 누군가가 클라이언트인 <code>OrderServiceImpl</code>에 <code>DiscountPolicy</code>의 <strong>구현 객체를 대신 생성하고 주입</strong>해주어야 한다.</li>
</ul>
<h3 id="관심사의-분리">관심사의 분리</h3>
<ul>
<li>애플리케이션을 하나의 공연이라고 생각해보자. 각각의 인터페이스를 배역(배우 역할)이라 생각하자. 그런데 실제 배역에 맞는 배우를 선택하는 것은 누가 하는 것일까?</li>
<li>예를 들어 로미오와 줄리엣 공연을 한다고 하면 로미오 역할을 누가 할지, 줄리엣 역할을 누가 할지는 배우들이 정하는게 아니다. 이전 코드는 마치 로미오 역할(인터페이스)을 하는 레오나르도 디카프리오(구현체, 배우)가 줄리엣 역할(인터페이스)을 하는 여자 주인공(구현체, 배우)를 직접 초빙하는 것과 같다. 이렇게 되면 디카프리오는 공연도 해야하고 동시에 여자 주인공도 섭외하는 <strong>다양한 책임</strong>을 갖게 된다.</li>
</ul>
<h4 id="관심사를-분리하자">관심사를 분리하자!</h4>
<ul>
<li>배우는 본인의 역할인 배역을 수행하는 것에만 집중하도록 한다.</li>
<li>디카프리오는 어떤 여자 주인공이 선택되더라도 공연을 할 수 있어야한다.</li>
<li>공연을 구성하고, 담당 배우를 섭외하고 역할에 맞는 배우를 지정하는 책임을 담당하는 별도의 <strong>공연 기획자</strong>가 등장해야하는 시점이다.</li>
<li>공연 기획자를 만들고, 배우와 공연 기획자의 책임을 확실하게 분리해보자!</li>
</ul>
<h3 id="appconfig-등장">AppConfig 등장</h3>
<ul>
<li>애플리케이션의 전체 동작 방식을 구성(configurate)하기 위해서, <strong>구현 객체를 생성</strong>하고, <strong>연결</strong>하는 책임을 가지는 별도의 설정 클래스를 만들자</li>
</ul>
<h4 id="appconfig">AppConfig</h4>
<pre><code class="language-java">package hello.core;

import hello.core.discount.FixDiscountPolicy;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;

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>AppConfig는 애플리케이션의 실제 동작에 필요한 <strong>구현 객체를 생성</strong>한다.<ul>
<li><code>MemberServiceImpl</code></li>
<li><code>MemoryMemberRepository</code></li>
<li><code>OrderServiceImple</code></li>
<li><code>FixDiscountPolicy</code></li>
</ul>
</li>
<li>AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 **생성자를 통해서 주입(연결)해준다.</li>
<li><code>MemberServiceImpl</code> -&gt; <code>MemoryMemberRepository</code></li>
<li><code>OrderServiceImpl</code> -&gt; <code>MemoryMemberRespository</code>, <code>FixDiscountPolicy</code></li>
</ul>
<blockquote>
<p>참고: 지금은 각 클래스에 생성자가 없어서 컴파일 오류가 발생하므로 바로 다음 코드에서 생성자를 만든다.</p>
</blockquote>
<h4 id="memberserviceimpl---생성자-주입">MemberServiceImpl - 생성자 주입</h4>
<pre><code class="language-java">package hello.core.member;

public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository

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

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

    @Override
    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></li>
</ul>
<h4 id="클래스-다이어그램">클래스 다이어그램</h4>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/24198f81-1410-495e-8132-3d2a559797e2/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>
<h4 id="회원-객체-인스터스-다이어그램">회원 객체 인스터스 다이어그램</h4>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/afcbd7c8-1a9f-4c61-a35e-6754e93ad78c/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>
<h4 id="orderserviceimpl---생성자-주입">OrderServiceImpl - 생성자 주입</h4>
<pre><code class="language-java">package hello.core.order;

import hello.core.discount.DiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;

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>OrderSerivceImpl</code>은 이제부터 실행에만 집중하면 된다.</li>
<li><code>OrderServiceImpl</code>에는 <code>MemoryMemberRepository</code>, <code>FixDiscountPolicy</code> 객체의 의존관계가 주입된다.</li>
</ul>
<h3 id="appconfig-실행">AppConfig 실행</h3>
<h4 id="사용-클래스---memberapp">사용 클래스 - MemberApp</h4>
<pre><code class="language-java">package hello.core;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;

public class MemberApp {
    public static void main(String[] 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;findMember = &quot; + findMember.getName());
    }
}</code></pre>
<h4 id="사용-클래스---orderapp">사용 클래스 - OrderApp</h4>
<pre><code class="language-java">package hello.core;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.order.Order;
import hello.core.order.OrderService;

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;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>
<h4 id="테스트-코드-오류-수정">테스트 코드 오류 수정</h4>
<pre><code class="language-java">package hello.core.member;

import hello.core.AppConfig;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class MemberServiceTest {
    MemberService memberService;

    @BeforeEach
    public void beforeEach() {
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
    }

    @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>
<hr>
<pre><code class="language-java">package hello.core.order;

import hello.core.AppConfig;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class OrderServiceTest {

    MemberService memberService;
    OrderService orderService;

    @BeforeEach
    public void beforeEach() {
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
        orderService = appConfig.orderService();
    }

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

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

        Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
    }

}</code></pre>
<ul>
<li>테스트 코드에서 <code>@BeforeEach</code>는 각 테스트를 실행하기 전에 호출된다.</li>
</ul>
<h4 id="정리">정리</h4>
<ul>
<li>AppConfig를 통해서 관심사를 확실하게 분리했다.</li>
<li>앞서 예로 든 배역, 배우를 생각해보자</li>
<li>AppConfig는 공연기획자다.</li>
<li>AppConfig는 구체 클래스를 선택한다. 배역에 맞는 담당 배우를 선택한다. 애플리케이션이 어떻게 동장해야할지 전체 구성을 책임진다.</li>
<li>이제 각 배우들은 담당 기능을 실행하는 책임만 지면 된다.</li>
<li><code>OrderServiceImpl</code>은 기능을 실행하는 책임만 지면 된다.</li>
</ul>
<h3 id="appconfig-리팩터링">AppConfig 리팩터링</h3>
<p>현재 AppConfig를 보면 <strong>중복</strong>이 있고, ** 역할<strong>에 따른 **구현</strong>이 잘 안 보인다.</p>
<h4 id="기대하는-그림">기대하는 그림</h4>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/600dc556-d22b-44f3-80b9-75ee500331c1/image.png" alt=""></p>
<h4 id="리팩터링-후">리팩터링 후</h4>
<pre><code class="language-java">package hello.core;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;

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 MemoryMeberRepository()</code>이 부분이 중복 제거되었다. 이제 <code>MemoryMemberRepository</code>를 다른 구현체로 변경할 때 한 부부만 변경하면 된다.</li>
<li><code>AppConfig</code>를 보면 역할과 구현 클래스가 한 눈에 들어온다. 애플리케이션 전체 구성이 어떻게 되어있는지 빠르게 파악할 수 있다.</li>
</ul>
<h3 id="새로운-구조와-할인-정책-적용">새로운 구조와 할인 정책 적용</h3>
<ul>
<li>처음으로 돌아가서 정액 할인정책을 정률 할인정책으로 변경해보자</li>
<li>FixDiscountPolicy -&gt; RateDiscountPolicy</li>
<li>어떤 부분만 변경하면 되겠는가?</li>
</ul>
<h4 id="appconfig의-등장으로-애플리케이션이-크게-사용영역과-객체를-생성하고-구성configuration하는-영역으로-분리되었다">AppConfig의 등장으로 애플리케이션이 크게 사용영역과 객체를 생성하고 구성(Configuration)하는 영역으로 분리되었다.</h4>
<h4 id="다이어그램---사용-구성의-분리">다이어그램 - 사용, 구성의 분리</h4>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/9510ca02-e566-4a70-9bd4-47842cf80eaf/image.png" alt=""></p>
<h4 id="다이어그램---할인-정책의-변경">다이어그램 - 할인 정책의 변경</h4>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/312e57c0-2b60-4f9f-b38d-cfd30a5cc3a1/image.png" alt=""></p>
<ul>
<li><code>FixDiscountPolicy</code> -&gt; <code>RateDiscountPolicy</code>로 변경해도 <strong>구성 영역만 영향을 받고, 사용 영역은 전혀 영향을 받지 않는다.</strong></li>
</ul>
<h4 id="할인-정책-변경-구성-코드">할인 정책 변경 구성 코드</h4>
<pre><code class="language-java">package hello.core;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;

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();
        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>
<h3 id="전체-흐름-정리">전체 흐름 정리</h3>
<ul>
<li>새로운 할인 정책 개발</li>
<li>새로운 할인 정책 적용과 문제점</li>
<li>관심사의 분리</li>
<li>AppConfig 리팩터링</li>
<li>새로운 구조와 할인 정책 적용</li>
</ul>
<h4 id="새로운-할인-정책-개발">새로운 할인 정책 개발</h4>
<p>다형성 덕분에 새로운 정률 할인 정책 코드를 추가로 개발하는 것 자체는 아무 문제가 없음</p>
<h4 id="새로운-할인-정책-적용과-문제점-1">새로운 할인 정책 적용과 문제점</h4>
<p>새로 개발한 정률 할인 정책을 적용하려고 하니 <strong>클라이언트 코드</strong>인 주문 서비스 구현체도 함께 변경해야함
주문 서비스 클라이언트가 인터페이스인 <code>DiscountPolicy</code>뿐만 아니라, 구체 클래스인 <code>FixDiscountPolicy</code>도 함께 의존 -&gt; <strong>DIP</strong> 위반</p>
<h4 id="관심사의-분리-1">관심사의 분리</h4>
<ul>
<li>애플리케이션을 하나의 공연으로 생각</li>
<li>기존에는 클라이언트가 의존하는 할인정책 구현 객체를 직접 생성하고 실행함</li>
<li>비유를 하면 기존에는 남자 주인공 배우가 공연도 하고, 동시에 여자 주인공도 직접 초빙하는 다양한 책임을 가지고 있음</li>
<li>공연을 구성하고, 담당 배우를 섭외하고, 지정하는 책임을 담당하는 별도의 <strong>공연 기획자</strong>가 나올 시점</li>
<li>공연 기획자인 AppConfig가 등장</li>
<li>AppConfig는 애플리케이션의 전체 동작 방식을 구성(configurate)하기 위해, <strong>구현 객체를 생성</strong>하고, <strong>연결</strong>하는 책임을 가짐</li>
<li>이제부터 클라이언트 객체는 자신의 역할을 실행하는 것만 집중, 권한이 줄어듦(책임이 명확해짐)<h4 id="appconfig-리팩터링-1">AppConfig 리팩터링</h4>
</li>
<li>구성 정보에서 역할과 구현을 명확하게 분리</li>
<li>역할이 잘 드러남</li>
<li>중보 제거<h4 id="새로운-구조와-할인-정책-적용-1">새로운 구조와 할인 정책 적용</h4>
</li>
<li>정액 할인정책 -&gt; 정률 할인정책으로 변경</li>
<li>AppConfig의 등장으로 애플리케이션이 크게 <strong>사용 영역</strong>과 객체를 생성하고 <strong>구성(configuration)하는 영역</strong>으로 분리</li>
<li>할인정책을 변경해도 AppConfig가 있는 구성 영역만 변경하면 됨. 사용 영역은 변경할 필요가 없음. 따라서 클라이언트 코드인 주문 서비스 코드도 변경하지 않음</li>
</ul>
<hr>
<h3 id="좋은-객체-지향-설계의-5가지-원칙-적용">좋은 객체 지향 설계의 5가지 원칙 적용</h3>
<p>여기서 3가지 SRP, DIP, OCP적용</p>
<h4 id="srp-단일-책임-원칙">SRP 단일 책임 원칙</h4>
<p><strong>한 클래스는 하나의 책임만 가져야 한다.</strong></p>
<ul>
<li>클라이언트 객체는 직접 구현 객체를 생성하고, 연결하고, 실행하는 다양한 책임을 가지고 있음</li>
<li>SRP를 따르면서 관심사를 분리함</li>
<li>구현 객체를 생성하고 연결하는 책임은 AppConfig가 담당</li>
<li>클라이언트 객체는 실행하는 책임만 담당</li>
</ul>
<h4 id="dip-의존관계-역전-원칙">DIP 의존관계 역전 원칙</h4>
<p><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>
<h4 id="ocp">OCP</h4>
<p><strong>소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야한다.</strong></p>
<ul>
<li>다형성을 사용하고 클라이언트가 DIP를 지킴</li>
<li>애플리케이션을 사용 영역과 구성 영역으로 나눔</li>
<li>AppConfig가 의존관계를 <code>FixDiscountPolicy</code> -&gt; <code>RateDiscountPolicy</code>로 변경해서 클라이언트 코드에 주입하므로 클라이언트 코드는 변경하지 않아도 됨</li>
<li>**소프트웨어 요소를 새롭게 확장해도 사용 영역의 변경은 닫혀있다.```</li>
</ul>
<hr>
<h3 id="ioc-di-그리고-컨테이너">IoC, DI, 그리고 컨테이너</h3>
<h4 id="제어의-역전-iocinversion-of-control">제어의 역전 IoC(inversion of control)</h4>
<ul>
<li>기존 프로그램은 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고, 연결하고, 실행하였다. 한마디로 구현 객체가 프로그램의 제어 흐름을 스스로 조종했다. 개발자 입장에서는 이 흐름이 자연스럽게 느껴진다.</li>
<li>하지만 AppConfig가 등장하고 나서는 구현 객체는 자신의 로직을 수행하는 것에만 집중한다. 예를 들어 <code>OrderServiceImpl</code>은 필요한 인터페이스만을 호출할 뿐 어떤 구현 객체들이 실행될 지 알지 못한다.</li>
<li>프로그램에 대한 제어 흐름은 온전히 AppConfig가 관리한다. 심지어 <code>OrderServiceImpl</code>도 AppConfig가 생성한다. 그리고 AppConfig는 <code>OrderServiceImpl</code>이 아닌 OrderService 인터페이스의 다른 구현 객체를 생성하고 실행할 수도 있다. 여기서 <code>OrderSerivceImpl</code>은 어떤 구현 객체가 생성될지 모른 상태로 자신의 로직만을 수행한다.</li>
<li>이렇듯 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)이라 한다.</li>
</ul>
<p><strong>프레임워크 vs 라이브러리</strong></p>
<ul>
<li>프레임워크가 내가 작성한 코드를 제어하고, 대신 실행하면 그것은 프레임워크가 맏다.(JUnit)</li>
<li>반면에 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 프레임워크가 아니라 라이브러리다.</li>
</ul>
<h4 id="의존관계-주입-didependency-injection">의존관계 주입 DI(dependency injection)</h4>
<ul>
<li><code>OrderServiceImpl</code>은 <code>DiscountPolicy</code>인터페이스에만 의존하고 실제로 어떤 구현 객체가 사용될지는 모른다.</li>
<li>의존관계는 <strong>정적인 클래스 의존 관계와, 실행 시점에 결정되는 동적인 객체(인스턴스) 의존 관계</strong> 둘을 분리해서 생각해야한다.<br></li>
<li><em>정적인 클래스 의존관계*</em>
클래스가 사용하는 import 코드만 보고 의존관계를 쉽게 판단할 수 있다. 정적인 의존고나계는 애플리케이션을 실행하지 않아도 분석할 수 있다. 클래스다이어그램을 보자
<code>OrderServiceImpl</code>은 <code>MemberRepository</code>, <code>DiscountPolicy</code>에 의존하는 것을 알 수 있다.
하지만 이 상태에서는 실제로 어떤 구현 객체가 <code>OrderServiceImpl</code>에 주입될지 알 수 없다.</li>
<li><em>클래스 다이어그램*</em>
<img src="https://velog.velcdn.com/images/kor_hoonie/post/6458fb9e-2013-46eb-b0cb-b0c438ca7ff0/image.png" alt=""><br></li>
<li><em>동적인 객체 인스턴스 의존 관계*</em>
애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계다.<br></li>
<li><em>객체 다이어그램*</em>
<img src="https://velog.velcdn.com/images/kor_hoonie/post/44fc30b1-50f5-4914-bbf8-8db4e87a53ce/image.png" alt=""></li>
<li>애플리케이션 <strong>실행 시점(런타임)</strong>에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결되는 것을 <strong>의존관계주입</strong>이라 한다.</li>
<li>객체 인스턴스를 생성하고, 그 참조값을 전달해서 연결한다.</li>
<li>의존관계 주입을 사용하면 클라이언트 코드를 변경하지 않고, 클라이언트가 호출하는 대상의 타입 인스턴스를 변경할 수 있다.</li>
<li>의존관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다.</li>
</ul>
<h4 id="ioc컨테이너-di컨테이너">IoC컨테이너, DI컨테이너</h4>
<ul>
<li>AppConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 IoC컨테이너 또는 <strong>DI컨테이너</strong>라 한다.</li>
<li>의존관계 주입에 초점을 맞추어 최근에는 주로 DI컨테이너라 한다.</li>
</ul>
<hr>
<h3 id="다음-포스트">다음 포스트</h3>
<p>지금까지는 순수한 java만을 이용해서 서비스를 구현하였다. 다음 포스트에서는 본격적으로 spring을 활용하여 똑같은 서비스를 어떻게 구현하는지 알아보고 그 과정에서 spring의 강력한 역할과 개발자를 어떻게 도와주고 있는지에 대해서 알아보겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[예제 만들기]]></title>
            <link>https://velog.io/@kor_hoonie/create-example</link>
            <guid>https://velog.io/@kor_hoonie/create-example</guid>
            <pubDate>Mon, 06 Feb 2023 09:04:03 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>인프런 김영한님의 강의 &#39;스프링 핵심 원리 - 기본편&#39;의 강의 내용을 참고하여 작성한 글입니다.</p>
</blockquote>
<h3 id="비즈니스-요구사항과-설계">비즈니스 요구사항과 설계</h3>
<ul>
<li><p>회원</p>
<ul>
<li>회원을 가입하고 조회할 수 있다.</li>
<li>회원은 일반과 VIP 2가지 등급이 있다.</li>
<li>회원은 일반과 VIP 2가지 등급이 있다.</li>
<li>회원 데이터는 자체 DB를 구축할 수 이쏙, 외부 시스템과 연동할 수 있다.(미확정)</li>
</ul>
</li>
<li><p>주문과 할인 정책</p>
<ul>
<li>회원은 상품을 주문할 수 있다.</li>
<li>회원 등급에 따라 할인 정책을 적용할 수 있다.</li>
<li>할인 정책은 모든 VIP 고객에 대해서는 1000원을 할인해주는 고정 금액 할인을 적용한다.(나중에 변경 될 수 있음)</li>
<li>할인 정책은 <strong>변경가능성</strong>이 높다.</li>
</ul>
</li>
</ul>
<p>이처럼 하나의 서비스를 개발하는 과정에서는 추후에 변경에 대한 요구가 있을 수 있다. 하지만 변경이 확정되기 전까지 개발을 미루고 있을 수 는 없기 때문에 <strong>인터페이스</strong>를 만들고 <strong>구현체</strong>를 갈아끼울 수 있도록 설계를 진행해야한다.</p>
<blockquote>
<p>우선은 스프링의 도움없이 순수하게 자바로만 개발을 하고 추후에 스프링으로 코드를 수정하는 방향으로 진행한다. 이를 통해 스프링의 동작방식을 직접적으로 이해할 수 있다.</p>
</blockquote>
<h3 id="회원-도메인-설계">회원 도메인 설계</h3>
<h4 id="회원-도메인-협력관계">회원 도메인 협력관계</h4>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/5c96ce30-3c2c-4458-a9b4-3c6ee8ad3010/image.png" alt=""></p>
<h4 id="회원-클래스-다이어그램">회원 클래스 다이어그램</h4>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/84eaa828-a8e0-46d1-b5d3-8be242eb4386/image.png" alt=""></p>
<h4 id="회원-객체-다이어그램">회원 객체 다이어그램</h4>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/9ef57cdc-a7af-4f5b-9799-f372d4475ae7/image.png" alt=""></p>
<h3 id="회원-도메인-개발">회원 도메인 개발</h3>
<h4 id="회원-엔티티">회원 엔티티</h4>
<ul>
<li>회원 등급<pre><code class="language-java">package hello.core.member;
</code></pre>
</li>
</ul>
<p>public enum Grade {
    BASIC,
    VIP
}</p>
<pre><code>- 회원 엔티티
```java
package hello.core.member;

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><h3 id="회원-저장소">회원 저장소</h3>
<h4 id="회원-저장소-인터페이스">회원 저장소 인터페이스</h4>
<pre><code class="language-java">package hello.core.member;

public interface MemberRepository {
    void save(Member member);
    Member findById(Long memberId);
}</code></pre>
<h4 id="메모리-회원-저장소-구현체">메모리 회원 저장소 구현체</h4>
<pre><code class="language-java">package hello.core.member;

import java.util.HashMap;
import java.util.Map;

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 findeById(Long memberId) {
        return store.get(memberId);
    }
}</code></pre>
<h3 id="회원-서비스">회원 서비스</h3>
<h4 id="회원-서비스-인터페이스">회원 서비스 인터페이스</h4>
<pre><code class="language-java">package hello.core.member;

public interface MemberService {
    void join(Member member);
    Member findMember(Long memberId);
}</code></pre>
<h4 id="회원-서비스-구현체">회원 서비스 구현체</h4>
<pre><code class="language-java">package hello.core.member;

public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository = new MemoryMemberRepository();

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

    @Override
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }
}</code></pre>
<h3 id="회원-도메인-실행과-테스트">회원 도메인 실행과 테스트</h3>
<h4 id="회원-도메인---회원-가입-main">회원 도메인 - 회원 가입 main</h4>
<pre><code class="language-java">package hello.core;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;

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;findMember = &quot; + findMember.getName());
    }
}</code></pre>
<p> 애플리케이션 로직으로 이렇게 테스트 하는 것은 권장되는 방법이 아니므로 JUnit 테스트를 사용하자. 아래는 회원가입 테스트를 하는 코드이다. IntelliJ의 test 폴더 아래 작성하면 테스트 코드를 돌릴 수 있다.
(window 기준으로 ctrl + shift + T 단축키로 생성할 수 있음)</p>
<blockquote>
<p>참고: 테스트 코드가 실행되지 않고 다음과 같은 오류가 발생한다면 이 페이지를 참고해보자.
오류메시지: <strong>Execution failed for task &#39;:test&#39;.
There were failing tests. See the report at: file:///{프로젝트 경로}/index.html</strong>
<a href="https://velog.io/@be_have98/IntelliJ-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%8B%A4%ED%96%89-%EC%8B%9C-Execution-failed-for-task-test.-%EC%98%A4%EB%A5%98">https://velog.io/@be_have98/IntelliJ-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%8B%A4%ED%96%89-%EC%8B%9C-Execution-failed-for-task-test.-%EC%98%A4%EB%A5%98</a></p>
</blockquote>
<h4 id="회원-도메인---회원-가입-test">회원 도메인 - 회원 가입 test</h4>
<pre><code class="language-java"> package hello.core.member;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

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> Assertion는 org.junit.jupiter.api.Assertions에 있는 것을 사용한다.</p>
<h3 id="지금까지-작성한-회원-도메인-설계의-문제점">지금까지 작성한 회원 도메인 설계의 문제점</h3>
<ul>
<li>현재 사용하고 있는 MemoryMemberRepository를 다른 저장소로 변경할 때 OCP원칙을 잘 준수할 수 있을지?</li>
<li>DIP를 잘 지키고 있는지?</li>
</ul>
<p>-&gt; <strong>의존관계가 인터페이스뿐만 아니라 구현까지 모두 의존하는 문제점이 있다.</strong></p>
<p>발견된 문제점과 해결방안은 주문 기능까지 완성한 후 다음 포스트에서 다룰 예정.</p>
<h3 id="주문과-할인-도메인-설계">주문과 할인 도메인 설계</h3>
<h4 id="주문-도메인-협력-역할-책임">주문 도메인 협력, 역할, 책임</h4>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/17fc6171-783e-4809-a6a6-823bc1283d2b/image.png" alt=""></p>
<ol>
<li>주문 생성: 클라이언트는 주문 서비스에 주문 생성을 요청한다.</li>
<li>회원 조회: 할인을 위해서는 회원의 등급이 필요하므로 주문 서비스는 회원 저장소에서 회원을 조회하고 등급을 확인한다.</li>
<li>할인 적용: 주문 서비스는 회원 등급에 따른 할인 여부를 할인 정책에 <strong>위임</strong>한다.</li>
<li>주문 결과 반환: 주문 서비스는 할인 결과를 포함한 주문 결과를 반환한다.(실제로는 주문 데이터를 DB에 저장하는 로직이 필요하지만 예제인 만큼 주문결과를 반환하는 것으로 대체)</li>
</ol>
<h4 id="주문-도메인-전체">주문 도메인 전체</h4>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/e31cc67c-bb60-47ee-9256-b0f13db1c4a6/image.png" alt=""></p>
<ul>
<li><strong>역할과 구현을 분리</strong>해서 자유롭게 구현 객체를 조립하고 사용할 수 있다. 덕분에 회원저장소는 물론 할인정책을 필요에 따라 유연하게 변경할 수 있다는 장점이 생겼다!
<br><br></li>
</ul>
<h4 id="주문-도메인-클래스-다이어그램">주문 도메인 클래스 다이어그램</h4>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/3d9fb152-4f54-463c-a2b4-34863115e8e1/image.png" alt=""></p>
<h4 id="주문-도메인-객체-다이어그램1">주문 도메인 객체 다이어그램1</h4>
<p><img src="https://velog.velcdn.com/images/kor_hoonie/post/5d458dc1-b62b-4dee-8e11-90db35904a4a/image.png" alt=""></p>
<ul>
<li>회원저장소를 메모리에서 조회하고 정액 할인 정책을 지원해도 <strong>주문서비스를 변경하지 않아도 된다.</strong> 즉 역할들의 협력 관계를 그대로 재사용할 수 있다.
<br><br><h4 id="주문-도메인-객체-다이어그램2">주문 도메인 객체 다이어그램2</h4>
<img src="https://velog.velcdn.com/images/kor_hoonie/post/afebe302-9f06-47b6-91a6-3b986e4ab27b/image.png" alt=""></li>
<li>회원저장소를 실제DB에서 조회하고 정률 할인 정책을 지원해도 <strong>주문서비스를 변경하지 않아도 된다.</strong> 즉 역할들의 협력 관계를 그대로 재사용할 수 있다.
<br><br></li>
</ul>
<h3 id="주문과-할인-도메인-개발">주문과 할인 도메인 개발</h3>
<h4 id="할인-정책-인터페이스">할인 정책 인터페이스</h4>
<pre><code class="language-java">package hello.core.discount;

import hello.core.member.Member;

public interface DiscountPolicy {

    /**
     * @return 할인된 금액
     */
    int discount(Member member, int price);
}</code></pre>
<h4 id="정액-할인-정책-구현체">정액 할인 정책 구현체</h4>
<pre><code class="language-java">package hello.core.discount;

import hello.core.member.Grade;
import hello.core.member.Member;

public class FixDiscountPolicy implements DiscountPolicy{
    private int discountFixAmount = 1000; // 고정적으로 할인 해주는 금액


    @Override
    public int discount(Member member, int price) {
        if(member.getGrade() == Grade.VIP){ // enum형식은 equals가 아닌 ==로 비교한다.
            return discountFixAmount;
        } else {
            return 0;
        }
    }
}</code></pre>
<h4 id="주문-엔티티">주문 엔티티</h4>
<pre><code class="language-java">package hello.core.order;

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 itemName;
    }

    public int getItemPrice() {
        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>
<h4 id="주문서비스-인터페이스">주문서비스 인터페이스</h4>
<pre><code class="language-java">package hello.core.order;

public interface OrderService {
    Order createOrder(Long memberId, String itemName, String itemPrice);
}</code></pre>
<h4 id="주문서비스-구현체">주문서비스 구현체</h4>
<pre><code class="language-java">package hello.core.order;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;

public class OrderServiceImpl implements OrderService{
    private final MemberRepository 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 = discountPolicy.discount(member, itemPrice);
        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}</code></pre>
<ul>
<li>주문 생성 요청이 들어오면, 회원정보(등급)를 조회해서 할인정책을 적용 후 주문 객체를 생성해서 반환. 이때 MemoryMemberRepository와 FixDiscountPolicy를 구현체로 활용하여 객체를 생성한다.</li>
</ul>
<h3 id="주문과-할인-도메인-실행과-테스트">주문과 할인 도메인 실행과 테스트</h3>
<h4 id="주문과-할인-정책-실행---main">주문과 할인 정책 실행 - main</h4>
<pre><code class="language-java">package hello.core;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.order.Order;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;

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>
<ul>
<li>회원 도메인 실행과 마찬가지로 애플리케이션 로직으로 테스트하는 것은 좋은 방법이 아니므로 JUnit테스트를 활용해보자.</li>
</ul>
<h4 id="주문과-할인-정책-test">주문과 할인 정책 test</h4>
<pre><code class="language-java">package hello.core.order;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class OrderServiceTest {

    MemberService memberService = new MemberServiceImpl();
    OrderService orderService = new OrderServiceImpl();

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

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

        Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
    }

}</code></pre>
<h3 id="정리">정리</h3>
<ul>
<li>member 패키지엔 Member 엔티티, MemberService 인터페이스, MemberServiceImpl 구현체를 만들었고 MemberServiceImpl은 MemoryMemberRepository를 활용하여 Member정보를 저장한다.</li>
<li>discount 패키지엔 DiscountPolicy 인터페이스(역할)와 이를 구현한 FixDiscountPolicy 구현체(구현)를 만들었다.</li>
<li>order 패키지엔 Order 엔티티, OrderService 인터페이스(역할)와 이를 구현한 OrderServiceImpl(구현)을 만들었다. OrderServiceImpl은 memberRepository와 discountPolicy를 이용하여 비즈니스 로직을 실행한다.</li>
</ul>
<h3 id="다음-포스트">다음 포스트</h3>
<ul>
<li>정액 할인 정책이 아닌 정률 할인 정책을 개발하고 이를 지금까지 작성한 코드에 적용할 때 발생하는 문제점과 이런 문제를 어떻게 효율적으로 해결할 수 있는지에 대해 알아볼 예정이다.(중요)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SOLID] 좋은 객체 지향 설계의 5가지 원칙]]></title>
            <link>https://velog.io/@kor_hoonie/SOLID</link>
            <guid>https://velog.io/@kor_hoonie/SOLID</guid>
            <pubDate>Wed, 11 Jan 2023 07:37:50 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>앞으로의 글들은 인프런 김영한님의 강의 &#39;스프링 핵심 원리 - 기본편&#39;의 강의 내용을 참고하여 작성한 글입니다.</p>
</blockquote>
<br>

<h3 id="solid-로버트-마틴이-정리한-좋은-객체-지향-설계의-5가지-원칙">SOLID: 로버트 마틴이 정리한 좋은 객체 지향 설계의 5가지 원칙</h3>
<blockquote>
<ol>
<li>SRP(single responsibility principle): 단일 책임 원칙</li>
<li>OCP(open-closed principle): 개방-폐쇄 원칙</li>
<li>LSP(Liskov substition principle): 리스코프 치환 원칙</li>
<li>ISP(interface segregation principle): 인터페이스 분리 원칙</li>
<li>DIP(dependency inversion principle): 의존관계 역전 원칙</li>
</ol>
</blockquote>
<h4 id="1-srp-단일-책임-원칙">1. SRP 단일 책임 원칙</h4>
<ul>
<li>한 클랙스는 하나의 책임만 가져야 한다.</li>
<li>이때 하나의 책임이라는 것이 모호할 수 있다.</li>
<li><blockquote>
<p>따라서 중요한 기준은 <strong>변경</strong>이다. 즉 코드의 수정이 있는 경우에 그 수정에 따른 파급효과가 적으면 단일 책임 원칙을 잘 따른 것이라고 할 수 있다.</p>
</blockquote>
</li>
</ul>
<h4 id="2-ocp-개방-폐쇄-원칙">2. OCP 개방-폐쇄 원칙</h4>
<ul>
<li><strong>소프트웨어 요소는 확장에는 열려 있으나, 변경에는 닫혀 있어야한다.</strong></li>
<li>생각을 좀 해보면 확장을 할려면 기존 코드를 변경해야 하는데 변경에 닫혀 있다는 것은 이해하기 어려울 수도 있다.</li>
<li><strong>다형성</strong>을 활용하여 OCP를 구현을 해보자!</li>
<li>인터페이스를 구현한 새로운 클래스를 만들고 새로운 기능을 추가할 수 있다.(역할과 구현의 분리)<pre><code>// memory를 사용하는 리포지토리를 생성
public class MemberService {
  private MemberRepository memberRepository = new MemoryMemberRepository();
}
</code></pre></li>
</ul>
<p>// 리포지토리 변경에 대한 소요가 생겨서 memory가 아닌 Jdbc를 리포지토리로 사용하기 위해 변경
public class MemberService {
    // private MemberRepository memberRepository = new MemoryMemberRepository();
    private MemberRepository memberRepository = new JdbcMemberRepository();
}</p>
<pre><code>위 코드에서 볼 수 있듯이 MemberService 클라이언트가 구현 클래스(MemoryMemberRepository, JdbcMemberRepository)를 직접 선택하는 상황에서 구현 객체를 변경하기 위해 클라이언트 코드를 변경할 수 밖에 없다.
즉 구현 객체를 변경하기 위해 클라이언트 코드를 수정해야하는 상황이 발생하였고 이는 다형성을 활용했지만 OCP원칙을 지킬 수 없는 상황이다.
-&gt; 따라서 객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요함을 알 수 있다.(추후에 설명)

#### 3. LSP 리스코프 치환 원칙
- 프로그램의 객체는 프로그램의 정확성을 깨드리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
- 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야한다는 것, 다형성을 지원하기 위한 원칙, 인터페이스를 구현한 구현체는 믿고 사용하려명 이 원칙이 반드시 필요하다.
- 예) 자동차의 핸들을 오른쪽으로 돌리면 차는 오른쪽으로 가야하는 기능, 여기서 핸들을 오른쪽으로 돌렸는데 차가 왼쪽으로 가면 LSP를 위반한 것. 꺾이는 정도의 차이는 있어도 되지만 반드시 오른쪽으로 가게 설계해야함

#### 4. ISP 인터페이스 분리 원칙
- 모든 클라이언트를 위한 범용적인 인터페이스 하나보단 특정 클라이언트를 위한 인터페이스 여러 개를 구현하는 것이 낫다.
- 하나의 자동차 인터페이스보단 운전 인터페이스, 정비 인터페이스 2개로 분리하는 것이 좋음
- 분리를 하면 정비 인터페이스를 수정하더라도 운전자 클라이언트에 영향을 주지 않음
- 인터페이스가 명확해지고 대체 가능성이 높아진다.

#### 5. DIP 의존관계 역전 원칙
- 프로그래머는 &quot;추상화에 의존해야지, 구체화에 의존하면 안된다&quot;
- 의존성 주입은 이 원칙을 따르는 방법 중 하나다.
- 즉, 구체적인 구현 클래스에 의존하지말고, 인터페이스에 의존해야한다는 의미
- 역할(role)에 의존해야한다는 것과 같다. 객체 세상도 클라이언트가 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있다. 구현체에 의존하게 되면 변경이 아주 어려워짐!
- 하지만 2번(OCP)에서 작성한 코드는 MemberService가 인터페이스에 의존하지만 구현 클래스에도 동시에 의존하는 문제점이 있다. 다시 말해, MemberService 클라이언트가 구현 클래스를 직접 선택한다는 문제점이 있다.</code></pre><p>// DIP 위반!!!
MemberRepository = new MemoryMemberRepository();</p>
<h2 id="">```</h2>
<h3 id="정리">정리</h3>
<ul>
<li>객체 지향의 핵심은 다형성</li>
<li>다형성만으로는 구현 객체를 변경할 때 클라이언트 코드도 함께 변경되는 문제점이 있다.</li>
<li>따라서 다형성만으로는 OCP, DIP를 지킬 수 없다.</li>
<li>새로운 <strong>뭔가</strong>가 필요하다!!</li>
</ul>
<p>[스포] 스프링은 DI(dependency injection)과 DI컨테이너를 활용하여 클라이언트 코드의 변경 없이 기능을 확장할 수 있다. (OCP, DIP를 지키면서 개발을 할 수 있게 해줌)</p>
<p>출처: 김영한님의 강의 &#39;스프링 핵심원리 - 기본편&#39;</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Counting Sort(카운팅 정렬)]]></title>
            <link>https://velog.io/@kor_hoonie/Counting-Sort</link>
            <guid>https://velog.io/@kor_hoonie/Counting-Sort</guid>
            <pubDate>Thu, 01 Sep 2022 14:24:30 GMT</pubDate>
            <description><![CDATA[<p>기본 아이디어: 배열의 있는 숫자들의 개수를 센 뒤 크기에 따라서 배열을 정렬해준다. 카운팅정렬은 <strong>안정정렬</strong>이다.</p>
<blockquote>
</blockquote>
<p>작동방식</p>
<ol>
<li>배열(arr)에서 가장 큰 값을 찾는다. 이 값이 개수를 세어주는 배열의 길이가 된다.</li>
<li>숫자들의 개수를 저장하는 배열(countArr)을 만들고 원래 배열(arr)을 돌면서 해당 숫자를 countArr의 인덱스로 해서 숫자의 개수 정보를 저장한다.</li>
<li>countArr의 값을 누적합으로 변경한다. 누적합으로 변경한 countArr은 인덱스에 해당하는 숫자 앞에 몇 개의 숫자가 있는지에 대한 정보를 담고 있다.</li>
<li>arr배열을 거꾸로 순회하면서(안정정렬을 위함) arr배열의 값을 countArr의 인덱스 사용하여 countArr의 값을 꺼내서 -1을 해주고 그 값을 다시 arr배열의 인덱스로 하여 결과 배열(resultArr)에 넣어준다.</li>
</ol>
<p>** 설명이 어려워 코드로 보는게 편할거같다....**</p>
<p>시간복잡도: O(n)</p>
<p>시간복잡도를 보면 매우 뛰어난 성능을 보이는 것 같지만 정렬하고자하는 숫자의 범위가 숫자의 개수에 비해 너무 넓으면 메모리 낭비가 심해진다. 예를 들어 정렬하고자하는 숫자가 1~10억이지만 숫자의 개수가 3개이면 3개의 숫자를 정렬하기 위해 10억의 길이를 갖는 배열을 만들어야하는 비효율이 발생한다.</p>
<h4 id="java-코드">java 코드</h4>
<pre><code>private static int[] countingSort(int[] arr, int size) {
        int max = 0;
        int[] resultArr = new int[size];

        // countArr 배열의 길이를 알기 위함
        for(int i = 0; i &lt; arr.length; i++){
            if(arr[i] &gt; max) max = arr[i];
        }

        // 인덱스가 0부터 시작하므로 정렬하고자 하는 숫자가 포함될 수 있도록 길이에 1을 더해준다.
        int[] countArr = new int[max+1];

        // 배열에 있는 숫자들이 각각 몇개가 있는지 체크하고 각 숫자들에 해당하는 idx에 저장
        for(int i = 0 ; i &lt; arr.length; i++){
            ++countArr[arr[i]];
        }

        // 개수를 누적합으로 다시 계산
        for(int i = 0 ; i &lt; countArr.length-1; i++){
            countArr[i+1] += countArr[i];
        }

        // 원래 배열을 거꾸로 돌면서(안정정렬) 개수를 저장한 배열에 있는 정보를 이용해서 크기 순으로 정렬
        for(int i = arr.length-1; i &gt;= 0; i--){
            resultArr[--countArr[arr[i]]] = arr[i];
        }

        return resultArr;
    }
</code></pre><p><strong>코드에 오류가 있으면 말씀해주세요.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Insertion Sort(삽입정렬)]]></title>
            <link>https://velog.io/@kor_hoonie/Insertion-Sort</link>
            <guid>https://velog.io/@kor_hoonie/Insertion-Sort</guid>
            <pubDate>Mon, 29 Aug 2022 10:41:31 GMT</pubDate>
            <description><![CDATA[<p>기본 아이디어: 현재 비교하고자 하는 target원소와 이전의 원소들을 삽입할 위치를 정한 후 나머지 원소를 오른쪽으로 옮기고 그 자리에 target원소를 삽입하는 방식이다. 삽입정렬은 <strong>안정정렬</strong>이다.</p>
<blockquote>
</blockquote>
<p>작동방식</p>
<ol>
<li>2번째 원소부터 비교를 시작한다.</li>
<li>target원소 왼쪽에 있는 원소들과 비교하여 자신보다 값이 큰 원소가 나오면 순서에 맞게 삽입하고 나머지 원소를 오른쪽으로 이동시킨다.</li>
<li>정렬이 될때까지 반복</li>
</ol>
<p>시간복잡도: O(n^2)</p>
<h4 id="java-코드">java 코드</h4>
<pre><code>static int[] insertionSort(int[] arr, int size) {

        for (int i = 1; i &lt; size; i++) {
            int idx = i;
            for (int j = i - 1; j &gt;= 0; j--) {
                if (arr[idx] &lt; arr[j]) {
                    int tmp = arr[idx];
                    arr[idx] = arr[j];
                    arr[j] = tmp;
                    --idx;
                }
            }
        }

        return arr;
    }
</code></pre><p><strong>코드에 오류가 있으면 말씀해주세요.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Selection Sort(선택정렬)]]></title>
            <link>https://velog.io/@kor_hoonie/Selection-Sort</link>
            <guid>https://velog.io/@kor_hoonie/Selection-Sort</guid>
            <pubDate>Mon, 29 Aug 2022 07:36:23 GMT</pubDate>
            <description><![CDATA[<p>기본 아이디어: 주어진 자료로부터 가장 작은 값의 원소부터 차례대로 선택해서 위치를 교환하는 방식</p>
<blockquote>
</blockquote>
<p>작동방식</p>
<ol>
<li>주어진 자료 중 최솟값을 찾는다.</li>
<li>찾은 값을 리스트의 가장 왼쪽값과 교환한다.</li>
<li>교환한 가장 왼쪽값을 제외하고 1,2 과정을 반복한다.</li>
</ol>
<p>시간복잡도: O(n^2)</p>
<h4 id="java-코드">java 코드</h4>
<pre><code>static int[] selectionSort(int arr[], int size) {
        for(int i = 0; i &lt; size-1; i++){
            // 우선 현재 i번째 값을 가장 작은 값으로 설정한다.
            int minIdx = i;
            // i번째 이후부터 마지막 요소까지 확인하면서 최솟값이 있으면 그 인덱스로 minIdx를 수정한다.
            for(int j = i+1; j &lt; size; j++){
                if(arr[minIdx] &gt; arr[j]){
                    minIdx = j;
                }
            }
            // 리스트의 가장 왼쪽값과 minIdx번째 값을 교환한다.
            int tmp = arr[i];
            arr[i] = arr[minIdx];
            arr[minIdx] = tmp;
        }

        return arr;
    }</code></pre><p><strong>코드에 오류가 있으면 말씀해주세요.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Bubble Sort(버블정렬)]]></title>
            <link>https://velog.io/@kor_hoonie/Bubble-Sort</link>
            <guid>https://velog.io/@kor_hoonie/Bubble-Sort</guid>
            <pubDate>Sun, 28 Aug 2022 09:14:21 GMT</pubDate>
            <description><![CDATA[<p>기본 아이디어: 인접한 2개의 원소를 비교하며 자리를 계속 교환하는 방식</p>
<blockquote>
</blockquote>
<p>작동방식</p>
<ol>
<li>첫 번째 원소부터 인접한 원소와 값을 비교하여 자리를 교환하면서 맨 마지막 자리까지 이동</li>
<li>한 단계가 끝나면 가장 큰 원소가 마지막 자리로 정렬된다.</li>
</ol>
<p>시간복잡도: O(n^2)</p>
<h4 id="java-코드">java 코드</h4>
<pre><code>static void bubbleSort(int[] arr, int size) {

        for (int i = size - 1; i &gt; 0; i--) { // i + 1 번째까지 비교하기 위함
            for (int j = 0; j &lt; i; j++) { // 0번째부터 i+1번째까지 비교하고 오른쪽 값이 더 크면 swap
                if (arr[j] &gt; arr[j + 1]) {
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }</code></pre><p><strong>코드에 오류가 있으면 말씀해주세요.</strong></p>
]]></description>
        </item>
    </channel>
</rss>