<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>se-ol.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 05 Jul 2022 15:29:45 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>se-ol.log</title>
            <url>https://velog.velcdn.com/cloudflare/se-ol/95f245cb-6018-4186-b908-b12de854ddaa/KakaoTalk_20220216_213929570.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. se-ol.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/se-ol" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[스프링 핵심원리 - 기본편] 섹션 7, 8, 9]]></title>
            <link>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8-%EC%84%B9%EC%85%98-7-8-9</link>
            <guid>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8-%EC%84%B9%EC%85%98-7-8-9</guid>
            <pubDate>Tue, 05 Jul 2022 15:29:45 GMT</pubDate>
            <description><![CDATA[<h1 id="7">7</h1>
<h2 id="옵션-처리">옵션 처리</h2>
<p>자동 주입 대상이 없으면 오류 발생,,
<strong>자동 주입 대상을 옵션으로 처리하는 방법</strong></p>
<ul>
<li><p>@Autowired(required=false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출 안됨</p>
</li>
<li><p>org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 null이 입력된다.</p>
</li>
<li><p>Optional&lt;&gt; : 자동 주입할 대상이 없으면 Optional.empty 가 입력된다.</p>
<h2 id="생성자-주입">생성자 주입</h2>
<p>왜 생성자 주입인가 ? 
=&gt; <strong>불변</strong></p>
</li>
<li><p>대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다. 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다.(불변해야 한다.)</p>
</li>
<li><p>수정자 주입을 사용하면, setXxx 메서드를 public으로 열어두어야 한다.</p>
</li>
<li><p>누군가 실수로 변경할 수 도 있고, 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아니다.</p>
</li>
<li><p>생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없다. 따라서 불변하게 설계할 수 있다.</p>
</li>
<li><p><em>누락,,*</em>
수정자 주입 등의 나머지 주입 방식은 모두 생성자 이후에 호출 되므로 final 키워드 사용 불가 -&gt; 생성자 주입만 final 사용 가능 !!</p>
<h2 id="롬복">롬복</h2>
<p>여러가지 @Annotation을 제공하고 이를 기반으로 반복 소스코드를 컴파일 과정에서 생성해주는 방식으로 동작하는 라이브러리 -&gt; 코드 최적화하기에 딱 !</p>
</li>
<li><p>는 @RequiredArgsConstructor 기능을 사용하면 final이 붙은 필드를 모아서 생성자 자동 생성</p>
<h2 id="조회-빈-2개-이상---문제">조회 빈 2개 이상 - 문제</h2>
<p>의존 관계 자동 주입으로 해결할 수 있음.</p>
</li>
<li><p>@Autowired 필드 명 매칭</p>
</li>
<li><p>@Qualifier @Qualifier끼리 매칭 빈 이름 매칭</p>
</li>
<li><p>@Primary 사용</p>
<h4 id="autowired-필드-명-매칭">@Autowired 필드 명 매칭</h4>
<p>타입 매칭 시도, 빈이 여러개라면 필드 이름, 파라미터 이름으로 빈 이름 추가 매칭 (타입 매칭 우선 시도)</p>
<h4 id="qualifier-qualifier끼리-매칭-빈-이름-매칭">@Qualifier @Qualifier끼리 매칭 빈 이름 매칭</h4>
<p>추가 구분자 붙여주는 방법 -&gt; 빈 이름 변경 X
NoSuchBeanDefinitionException 예외 발생 가능</p>
<h4 id="primary-사용">@Primary 사용</h4>
<p>우선 순위 설정.</p>
<h4 id="primary-qualifier-활용">@Primary, @Qualifier 활용</h4>
<p>스프링 빈은 @Primary 를 적용해서 조회하는 곳에서 @Qualifier 지정 없이 편리하게 조회하고, 서브 데이터베이스 커넥션 빈을 획득할 때는 @Qualifier 를 지정해서 명시적으로 획득 하는 방식</p>
<h4 id="우선순위">우선순위</h4>
<p>@Qualifier가 우선순위가 더 높음(자세한게 더 우선순위 가져감)</p>
<h2 id="애노테이션">애노테이션</h2>
<p>애노테이션은 상속 개념 X, 애노테이션 집합 기능은 스프링이 지원하는 것 !</p>
<h2 id="list-map">List, Map</h2>
</li>
<li><p><em>로직 분석*</em></p>
</li>
<li><p>DiscountService는 Map으로 모든 DiscountPolicy 를 주입받는다. 이때 fixDiscountPolicy, rateDiscountPolicy 가 주입된다.</p>
</li>
<li><p>discount () 메서드는 discountCode로 &quot;fixDiscountPolicy&quot;가 넘어오면 map에서 fixDiscountPolicy 스프링 빈을 찾아서 실행한다. 물론론 “rateDiscountPolicy”가 넘어오면 rateDiscountPolicy 스프링 빈을 찾아서 실행한다.</p>
</li>
<li><p><em>주입 분석*</em></p>
</li>
<li><p>Map&lt;String, DiscountPolicy&gt; : map의 키에 스프링 빈의 이름을 넣어주고, 그 값으로 DiscountPolicy 타입으로 조회한 모든 스프링 빈을 담아준다.</p>
</li>
<li><p>List<DiscountPolicy> : DiscountPolicy 타입으로 조회한 모든 스프링 빈을 담아준다.</p>
</li>
<li><p>만약 해당하는 타입의 스프링 빈이 없으면, 빈 컬렉션이나 Map을 주입한다.</p>
<h2 id="자동-수동의-올바른-실무-운영-기준">자동, 수동의 올바른 실무 운영 기준</h2>
<h1 id="8">8</h1>
<h2 id="빈-생명주기-콜백">빈 생명주기 콜백</h2>
<p>빈이 사라지기 직전에 안전하게 종료할 수 있는 메소드를 호출하는 것.
스프링 빈의 이벤트 라이프사이클
<strong>스프링 컨테이너 생성 -&gt; 스프링 빈 생성 -&gt; 의존관계 주입 -&gt; 초기화 콜백(빈 생성, 의존관계 주입 완료 호 호출) -&gt; 사용 -&gt; 소멸전 콜백( 빈 소멸 직전 호출) -&gt; 스프링 종료</strong></p>
<h3 id="빈-생명주기-콜백-지원">빈 생명주기 콜백 지원</h3>
<ul>
<li>인터페이스</li>
<li>설정 정보에 초기화 메서드, 종료 메서드 지정</li>
<li>@PostConstruct, @PreDestroy 애노테이션 지원<h4 id="인터페이스-initializingbean-disposablebean">인터페이스 InitializingBean, DisposableBean</h4>
</li>
<li>초기화 메서드가 주입 완료 후에 호출</li>
<li>컨테이너의 종료 호출 이서 소멸 메서드 호출</li>
<li><em>단점*</em></li>
<li>스프링 전용 인터페이스에 의존</li>
<li>초기화 및 소멸 메서드의 이름 변경 불가</li>
<li>외부 라이브러리에 적용 불가 <h4 id="설정-정보에-초기화-메서드-종료-메서드-지정">설정 정보에 초기화 메서드, 종료 메서드 지정</h4>
</li>
<li>메서드 이름 자유로움</li>
<li>스프링 코드에 의존 X</li>
<li>외부 라이브러리에 초기화 및 종료 메서드 적용 가능<h4 id="애노테이션-postconstruct-predestroy">애노테이션 @PostConstruct, @PreDestroy</h4>
</li>
<li>애노테이션 하나만 붙이면 되서 매우 간편</li>
<li>스프링 아닌 다른 컨테이너에서도 동작</li>
<li>컴포넌트 스캔과 조합 좋음
But, 외부 라이브러리 적용 불가 -&gt; Bean 사용<h2 id="빈-스코프">빈 스코프</h2>
</li>
<li><em>빈 스코프?*</em></li>
<li><blockquote>
<p>서버 미리 한개만 만들어서 쓸건지 아니면 요청마다 생성해서 쓸 것인지를 결정함.</p>
</blockquote>
</li>
</ul>
</li>
<li><p>싱글톤: 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프</p>
</li>
<li><p>프로토타입: 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프</p>
</li>
<li><p><strong>웹 관련 스코프</strong>
request: 웹 요청이 들어오고 나갈때 까지 유지되는 스코프
session: 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프
application: 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프</p>
<p><strong>싱글톤 빈 요청</strong>
<img src="https://velog.velcdn.com/images/se-ol/post/340e2ea4-8aea-4429-8a89-9884a79cbf5d/image.png" alt="">
싱글톤 스코프의 빈 요청 -&gt; 스프링 빈 반환 -&gt; 같은 요청 시 같은 객체 인스턴스의 스프링 빈 반환
<strong>프로토타입 빈 요청</strong>
<img src="https://velog.velcdn.com/images/se-ol/post/da3a37fe-b3e7-4999-a817-08d06ba0665f/image.png" alt="">
요청 -&gt; 프로토타입 빈 생성 및 필요한 의존관계 주입 -&gt; 클라이언트에 반환 -&gt; 같은 요청시 항상 new 프로토타입 빈 생성 및 반환</p>
<ul>
<li>요청 마다 새로 빈 생성</li>
<li>컨테이너는 프로토타입 빈 생성과 주입 및 초기화만 관여</li>
<li>종료 메서드 호출 X</li>
<li>관리는 클라이언트가 !!</li>
</ul>
</li>
<li><ul>
<li>=&gt; 싱글톤 빈과 프로토타입 스코프를 함께 사용 시 주입 받는 시점에 각각 새로운 프로토타입 빈이 생성되는 문제 발생**
이는 <strong>Provider</strong>로 해결 가능</li>
</ul>
</li>
<li><blockquote>
<p>ObjectFactory, ObjectProvider, JSR-330 Provider</p>
</blockquote>
<ul>
<li>ObjectFactory: 기능이 단순, 별도의 라이브러리 필요 없음, 스프링에 의존</li>
</ul>
</li>
<li><p>ObjectProvider: ObjectFactory 상속, 옵션, 스트림 처리등 편의 기능이 많고, 별도의 라이브러리 필요 없음, 스프링에 의존</p>
<ul>
<li>JSR-330 Provider : 기능 매우 단순, 별도 라이브러리 필요, 스프링 이외의 컨테이너에서도 사용 가능</li>
</ul>
</li>
</ul>
<h4 id="웹-스코프">웹 스코프</h4>
<p>  웹 환경에서만 동작하는 스코프로, 스프링이 스코프 종료시점까지 관리 -&gt; 종료 메서드 호출</p>
<ul>
<li>request: HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고, 관리된다.<ul>
<li>session: HTTP Session과 동일한 생명주기를 가지는 스코프</li>
<li>application: 서블릿 컨텍스트( ServletContext )와 동일한 생명주기를 가지는 스코프</li>
<li>websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프
<img src="https://velog.velcdn.com/images/se-ol/post/1cb3c110-eb25-46fc-8e89-6acea1df4c6e/image.png" alt=""><h3 id="request-스코프-예제">request 스코프 예제</h3>
request scope를 사용하지 않고 파라미터로 이 모든 정보를 서비스 계층에 넘길 순 있는데,, 파라미터가 많아서 지저분함. 그리고 웹 관련 정보가 웹 노관련 서비스 계층까지 넘어가기 때문에,, request scope를 쓰는 것이 좋다. -&gt; 고객 요청 시에만 생성 ! 깔끔 !<h2 id="스코프와-provider">스코프와 Provider</h2>
</li>
<li>ObjectProvider.getObject() 를 호출하는 시점까지 request scope 빈 생성 지연 가능</li>
</ul>
</li>
<li>호출 시점에 HTTP 요청 진행중이라 request scope 빈 생성 정상 처리<h2 id="스코프와-프록시">스코프와 프록시</h2>
proxyMode = ScopedProxyMode.TARGET_CLASS(인터페이스일때는 INTERFACES) 를 추가</li>
<li><em>동작원리*</em>
<img src="https://velog.velcdn.com/images/se-ol/post/4e8b3dc7-a36c-413e-bd6d-8ea396ce8e1f/image.png" alt=""><ul>
<li>CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입</li>
<li>이 가짜 프록시 객체는 실제 요청이 오면 그때 내부에서 실제 빈을 요청하는 위임 로직이 들어있다.</li>
<li>가짜 프록시 객체는 실제 request scope와는 관계 X. 그냥 가짜이고, 내부에 단순한 위임 로직만 있고, 싱글톤처럼 동작</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 핵심원리 - 기본편] 섹션 6 + 다양한 의존관계 주입 방법]]></title>
            <link>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8-%EC%84%B9%EC%85%98-6-%EB%8B%A4%EC%96%91%ED%95%9C-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84-%EC%A3%BC%EC%9E%85-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8-%EC%84%B9%EC%85%98-6-%EB%8B%A4%EC%96%91%ED%95%9C-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84-%EC%A3%BC%EC%9E%85-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Tue, 28 Jun 2022 11:42:54 GMT</pubDate>
            <description><![CDATA[<h2 id="컴포넌트-스캔과-의존관계-자동-주입-시작하기">컴포넌트 스캔과 의존관계 자동 주입 시작하기</h2>
<p>현재까지의 등록 방법 -&gt; @Bean 등 설정 정보에 직접 스프링 빈 나열
-&gt; 귀찮다 ....
=&gt; @Autowired !!
@Component와 @Autowired를 각 필요한 위치에 넣은 코드 작성
<img src="https://velog.velcdn.com/images/se-ol/post/420ce37c-b165-4e1d-aed8-5ab671a24f08/image.png" alt="">
<img src="https://velog.velcdn.com/images/se-ol/post/5dd482a0-97c5-45cd-9b07-089b3e6f3a06/image.png" alt="">
클래스 명 사용 주의 (앞 두글자만 바꾸고 !)
<img src="https://velog.velcdn.com/images/se-ol/post/ec3ca9c6-9280-4b59-a253-d2adfb98f0b8/image.png" alt="">
의존관계 주입 시 스프링 컨테이너에서 자동으로 찾아서 주입함 !</p>
<h2 id="탐색-위치와-기본-스캔-대상">탐색 위치와 기본 스캔 대상</h2>
<ul>
<li>basePacakges: 탐색할 패키지 시작 위치 지정 ( 포함 하위 패키지 모두 탐색 , 여러 시작 위치 설정 가능)</li>
<li>basePackageClasses: 지정한 클래스 패키지를 탐색 시작 위치로.
만약 이 둘을 지정하지 않을 시 @ComponentScan이 붙은 정보 클래스 패키지가 시작위치가 된다.</li>
<li><blockquote>
<p>사실 디폴트로 패키지 위치 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것을 권장 (@SpringBootApplication이 시작 루트 위치에 두는 것이 관건인데 얘가 @ComponentScan 포함함)</p>
</blockquote>
</li>
</ul>
<p>골뱅이모음</p>
<ul>
<li>@Component : 컴포넌트 스캔에서 사용</li>
<li>@Controlller : 스프링 MVC 컨트롤러에서 사용</li>
<li>@Service : 스프링 비즈니스 로직에서 사용</li>
<li>@Repository : 스프링 데이터 접근 계층에서 사용</li>
<li>@Configuration : 스프링 설정 정보에서 사용</li>
<li>@Controller : 스프링 MVC 컨트롤러로 인식</li>
<li>@Repository : 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해</li>
<li>@Configuration : 앞서 보았듯이 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리</li>
<li>@Service : 비즈니스 계층 인식에 도움<h2 id="필터">필터</h2>
</li>
<li>includeFilters : 컴포넌트 스캔 대상을 추가로 지정</li>
<li>excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정<pre><code>@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent { //혹은 MyIncludeComponent 형식
}</code></pre></li>
</ul>
<pre><code>package hello.core.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;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.context.annotation.ComponentScan.Filter;

public class ComponentFilterAppConfigTest {
    @Test
    void filterScan() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
        BeanA beanA = ac.getBean(&quot;beanA&quot;, BeanA.class);
        assertThat(beanA).isNotNull();
        Assertions.assertThrows( NoSuchBeanDefinitionException.class, () -&gt; ac.getBean(&quot;beanB&quot;, BeanB.class));
    }
    @Configuration
    @ComponentScan(
            includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
            excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class))
    static class ComponentFilterAppConfig {
    }
}

</code></pre><h4 id="필터-옵션">필터 옵션</h4>
<ul>
<li>ANNOTATION: 기본값, 애노테이션을 인식해서 동작
ex) org.example.SomeAnnotation</li>
<li>ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작
ex) org.example.SomeClass</li>
<li>ASPECTJ: AspectJ 패턴 사용
ex) org.example..*Service+</li>
<li>REGEX: 정규 표현식
ex) org.example.Default.*</li>
<li>CUSTOM: TypeFilter 이라는 인터페이스를 구현해서 처리
ex) org.example.MyTypeFilter</li>
</ul>
<h2 id="중복-등록과-충돌">중복 등록과 충돌</h2>
<p>같은 빈 이름을 등록한다면 ??</p>
<ol>
<li>자동 vs 자동
재 등록 -&gt; ConflictingBeanDefinitionException 예외 발생</li>
<li>수동 vs 자동
수동 빈 등록이 우선권
<em>Overriding bean definition for bean &#39;memoryMemberRepository&#39; with a different 
definition: replacing</em>
ㄴ 로그 내용
로그의 내용을 보고 어떤 오류인지 확인할 수 있음.<h2 id="다양한-의존관계-주입-방법">다양한 의존관계 주입 방법</h2>
</li>
</ol>
<p><strong>의존관계 주입 방법</strong></p>
<ul>
<li>생성자 주입</li>
<li>수정자 주입(setter 주입)</li>
<li>필드 주입</li>
<li>일반 메서드 주입</li>
<li><em>생성자 주입*</em>
생성자를 통해서 의존 관계를 주입 받는 방법 (현 우리가 사용하는 방법)</li>
<li>생성자 호출시점에 딱 1번만 호출되는 것이 보장</li>
<li>불변, 필수 의존관계에 사용 -&gt; 공연때 배우를 바꾸지 않는 그런 상황</li>
</ul>
<p><strong>수정자 주입(setter 주입)</strong>
setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법</p>
<ul>
<li>선택, 변경 가능성이 있는 의존관계에 사용</li>
<li>자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용하는 방법</li>
</ul>
<p><strong>필드 주입</strong>
필드에 바로 주입</p>
<ul>
<li>코드가 간결해서 많은 개발자들을 유혹하지만 외부에서 변경이 불가능해서 테스트 하기 개힘듬.</li>
<li>DI 프레임워크가 없으면 아무것도 할 수 없다.
=&gt; 사용하지 말자!</li>
<li>애플리케이션의 실제 코드와 관계 없는 테스트 코드</li>
<li>스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 사용</li>
</ul>
<p><strong>일반 메서드 주입</strong>
일반 메서드를 통해서 주입 받기</p>
<ul>
<li>한 번에 여러 필드 주입 받기 가능
but, 잘 사용 안 함</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 핵심원리 - 기본편] 섹션 5]]></title>
            <link>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8-%EC%84%B9%EC%85%98-5</link>
            <guid>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8-%EC%84%B9%EC%85%98-5</guid>
            <pubDate>Tue, 21 Jun 2022 11:57:23 GMT</pubDate>
            <description><![CDATA[<h3 id="웹-애플리케이션과-싱글톤">웹 애플리케이션과 싱글톤</h3>
<p>스프링 애플리케이션 -&gt; 대부분 웹 -&gt; 웹 어플리케이션은 대부분 동시요청 (ex. AppConfig.java)
요청마다 객체를 만들어냄 ....
<img src="https://velog.velcdn.com/images/se-ol/post/5eb041dd-b65d-4e2b-83c3-84b5574e37a6/image.png" alt=""></p>
<pre><code>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><p><img src="https://velog.velcdn.com/images/se-ol/post/bd7d2101-4dfb-43a3-843c-b3f7f91d971e/image.png" alt="">
둘 다 다르다.... 호출때마다 객체 새로 생성 .. 매우 비효율적</p>
<h3 id="싱글톤-패턴">싱글톤 패턴</h3>
<p><strong>객체의 인스턴스가 오직 1개만 생성되는 패턴</strong>
=&gt; 어플리케이션 실행 시 최초 한번만 메모리를 할당하고 그 메모리에 인스턴스를 만들어서 사용하는 디자인, 객체 2개 이상 생성 못하도록 강제해야함.</p>
<pre><code>public class SingletonService {
    //static 영역에 객체를 딱 1개만 생성
    private static final SingletonService instance = new SingletonService();
    //public으로 조회시 static 통해서만 조회하게 
    public static SingletonService getInstance() {
        return instance; //얘만 조회가능
    }
    //생성 막기
    private SingletonService() {
    }
    public void logic() {
        System.out.println(&quot;싱글톤 객체 로직 호출&quot;);
    }
}</code></pre><ul>
<li><p>static 영역에 객체 인스턴트 하나 미리 생성.</p>
</li>
<li><p>객체 인스턴스 필요 시 getInstance 메소드 통해서만 조회 가능.</p>
</li>
<li><p>생성자를 priate로 막아서 외부에서 객체 생성되는 것을 방어.</p>
<pre><code>@Test
  @DisplayName(&quot;싱글톤 패턴 적용 객체 사용&quot;)
  void SingletonServiceTest() {
      SingletonService SingletonService1 = SingletonService.getInstance();

      SingletonService SingletonService2 = SingletonService.getInstance();

      System.out.println(&quot;singletonService1 = &quot; + SingletonService1);
      System.out.println(&quot;singletonService2 = &quot; + SingletonService2);

      Assertions.assertThat(singletonService1).isSameAs(singletonService2);
      singletonService1.logic();
  }</code></pre><p>테스트 코드로 같음을 확인
//isSameAs 인스턴스 비교</p>
</li>
<li><blockquote>
<p>스프링 컨테이너는 걍 싱글톤 패턴 기본 적용 ( 똑똑이 )</p>
</blockquote>
</li>
</ul>
<p>하지만 .. 싱글톤 패턴도 단점이 있음</p>
<ul>
<li>코드 추가해야됨</li>
<li>의존관계상 클라이언트가 구체 클래스에 의존하는 문제</li>
<li>DIP를 위반 //memberserviceimpl.getInstan.... 하면서</li>
<li>클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성 up</li>
<li>테스트 어려움</li>
<li>내부 속성을 변경하거나 초기화 힘듬</li>
<li>private 생성자 사용으로 자식 클래스 생성 어려움</li>
<li>결론적으로 유연성 down</li>
<li>안티패턴? (비효율 비생산)</li>
</ul>
<h3 id="싱글톤-컨테이너">싱글톤 컨테이너</h3>
<p>스프링 컨테이너는 위의 단점을 제거한 싱글톤으로 관리한다 .. -&gt; 싱글톤 패턴 적용 없이 객체 인스턴스를 싱글톤으로 관리.(싱글톤 레지스트리)</p>
<pre><code>@Test
    @DisplayName(&quot;스프링 컨테이너와 싱글톤&quot;)
    void springContainer() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

        MemberService memberService1 = ac.getBean(&quot;memberService&quot;, MemberService.class);
        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><p>싱글톤 코드  X
<img src="https://velog.velcdn.com/images/se-ol/post/461e6f19-c090-4d52-93a6-f82fe8c74d4c/image.png" alt="">
요청 올때마다 동일한 멤버 서비스 반환 -&gt; 처리 속도 up (이미 만들어진 객체 공유)
//빈 스코프(요청 시 마다 새로운 객체 생성해서 반환하는 기능)도 존재하긴 함.</p>
<h3 id="싱글톤-방식의-주의점">싱글톤 방식의 주의점</h3>
<p>하지만 이 방식도 주의점이 있다네요 ~ ..
싱글톤 방식은 한 객체 인스턴스가 공유되는 방식이기 때문에 상태를 무상태로 설계해야한다.
무상태 ?
-&gt; 읽기만 가능
-&gt; 값 수정 X
-&gt; 필드 대신에 자바에서 공유되지 않는 것들을 사용해야함
-&gt; 의존적 필드 존재하면 X</p>
<pre><code>public class StatefulService {
    private int price; //상태 유지 필드
                       //주문시 가격저장
    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>//Ctrl+Shift+T 단축키로 테스트 생성</p>
<pre><code>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;

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

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: 사용자A 주문 금액 조회
        int price = statefulService1.getPrice();
        //ThreadA: 사용자A는 10000원을 기대했지만, 기대와 다르게 20000원 출력
        System.out.println(&quot;price = &quot; + price); //사용자B의 금액 출력함
        Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
    }
    static class TestConfig {
        @Bean
        public StatefulService statefulService() {
            return new StatefulService();
        }
    }

}</code></pre><p>-&gt; 공유필드가 만드는 치명적인 문제 ,,</p>
<pre><code>public int getPrice() {
        return price;
    }</code></pre><p>얘를 지우고 테스트 코드에서 출력 코드를 조금 수정해주면 정상 출력됨</p>
<h3 id="configuration과-싱글톤">@Configuration과 싱글톤</h3>
<ul>
<li>memberService 빈을 만드는 코드를 보면 memberRepository() 를 호출한다. -&gt; 이 메서드를 호출하면 new MemoryMemberRepository() 를 호출한다.</li>
<li>orderService 빈을 만드는 코드도 동일하게 memberRepository() 를 호출한다. -&gt; 이 메서드를 호출하면 new MemoryMemberRepository() 를 호출한다.
=&gt; ??? 이게 뭐야 싱글톤 ? 이라매 ?<pre><code>import hello.core.AppConfig;
import hello.core.member.MemberRepository;
import hello.core.member.MemberServiceImpl;
import hello.core.order.OrderServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.*;

</code></pre></li>
</ul>
<p>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);</p>
<pre><code>    //모두 같은 인스턴스를 참고
    System.out.println(&quot;memberService -&gt; memberRepository = &quot; + memberService.getMemberRepository());
    System.out.println(&quot;orderService -&gt; memberRepository = &quot; + orderService.getMemberRepository());
    System.out.println(&quot;memberRepository = &quot; + memberRepository);


    assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
    assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
}</code></pre><p>}</p>
<pre><code>인스턴스는 모두 같은 인스턴스가 공유되어서 사용 ( 한 번만 생성 )
-&gt; 호출은 다른데 ? -&gt; ?? 호출 로그를 남겨봅시다.
AppConfig.java에 다음과 같은 줄 삽입
//System.out.println(&quot;call AppConfig.출력함수&quot;);
![](https://velog.velcdn.com/images/se-ol/post/e1311941-d1ce-4838-8da0-79a847dcb7ba/image.png)
memberRepository 1번만 호출된 걸 볼 수 있음! -&gt; 스프링 똑똑이
하지만 어떻게 ?
### @Configuration과 바이트코드 조작의 마법
비밀의 정답은 @Configuration</code></pre><p>@Test
    void configurationDeep() {
        ApplicationContext ac = new
                AnnotationConfigApplicationContext(AppConfig.class);
        //AppConfig도 스프링 빈으로 등록
        AppConfig bean = ac.getBean(AppConfig.class);</p>
<pre><code>    System.out.println(&quot;bean = &quot; + bean.getClass());
    //출력: bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$208f260
}</code></pre><pre><code>?? 순수 클래스와 다르게 출력되는 모습을 볼 수 있음 -&gt; 내가 만든 class가 아니기 때문 -&gt; 스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용해 다른 클래스를 임의로 제작해 등록한 것
![](https://velog.velcdn.com/images/se-ol/post/46dc1d9f-4d36-4cbc-aa69-dbfdc4fd9e5b/image.png)
AppConfig.java </code></pre><p>@Bean
public MemberRepository memberRepository() {</p>
<p> if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면?) {
 return 스프링 컨테이너에서 찾아서 반환;
 } else { //스프링 컨테이너에 없으면
 기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
 return 반환
 }
}</p>
<pre><code>AppConfig@CGLIB 예상코드 ( CGLIB는 자식타입이라 AppConfig로 조회가능)
없으면 등록하고 등록되어 있으면 꺼내서 반환하는 형식으로 싱글톤 보장.

하지만 @Configuration 하지 않고 @Bean만 하면?
call AppConfig.memberService
call AppConfig.memberRepository
call AppConfig.orderService
call AppConfig.memberRepository
call AppConfig.memberRepository
이런 형식으로 출력된다. (각기 다른 인스턴스를 가지고 있음)
스프링 컨테이너가 관리하지 않음 .. 스프링 빈이 아니야 .. new 해준 거랑 완전 똑같음. -&gt; 스프링 설정정보가 있을 시에는 무조건 @Configuration을 사용합시다 ^!^

@AutoWired는 뒤에서 심도있게 ,,</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 핵심원리 - 기본편] 섹션 4]]></title>
            <link>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8-%EC%84%B9%EC%85%98-4</link>
            <guid>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8-%EC%84%B9%EC%85%98-4</guid>
            <pubDate>Tue, 31 May 2022 13:41:31 GMT</pubDate>
            <description><![CDATA[<h2 id="스프링-컨테이너">스프링 컨테이너</h2>
<p>ApplicationContext applicationContext =
 new
AnnotationConfigApplicationContext(AppConfig.class);로 애노테이션기반 자바 설정 클래스로 생성
<img src="https://velog.velcdn.com/images/se-ol/post/2cf1e841-95fa-4dd0-aab8-3795a60d63e5/image.png" alt=""></p>
<ul>
<li>구성정보 저장
<img src="https://velog.velcdn.com/images/se-ol/post/670b7a20-6fb5-4473-b513-9aea99590a80/image.png" alt=""></li>
<li>스프링 빈 등록 ( 빈 이름은 메서드 명, 직접 부여 가능 ) 
( 단, 모두 다 달라야함. )
<img src="https://velog.velcdn.com/images/se-ol/post/03057f53-f968-4cc5-9127-cd49d9b3ce0a/image.png" alt=""></li>
<li>주입<h3 id="빈-조회">빈 조회</h3>
<pre><code>package hello.core.beanfind;
</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 ApplicationContextInfoTest {</p>
<pre><code>AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

@Test
@DisplayName(&quot;모든 빈 출력&quot;)
void findAllBean() {
    String[] beanDefinitionNames = ac.getBeanDefinitionNames();
    for(String beanDefinition : beanDefinitionNames) {
        Object bean = ac.getBean(beanDefinition);
        System.out.println(&quot;name = &quot;+beanDefinition+ &quot;object = &quot;+ bean);
    }
} @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) {
            Object bean = ac.getBean(beanDefinitionName);
            System.out.println(&quot;name = &quot;+beanDefinition+ &quot;object = &quot;+ bean);

        }
    }
}</code></pre><p>}</p>
<pre><code>name = Generic bean: class [hello.core.AppConfig$$EnhancerBySpringCGLIB$$fa440726]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=nullobject = hello.core.AppConfig$$EnhancerBySpringCGLIB$$fa440726@5b202a3a
name = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appConfig; factoryMethodName=memberService; initMethodName=null; destroyMethodName=(inferred); defined in hello.core.AppConfigobject = hello.core.member.MemberServiceImpl@10b9db7b
name = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appConfig; factoryMethodName=orderService; initMethodName=null; destroyMethodName=(inferred); defined in hello.core.AppConfigobject = hello.core.order.OrderServiceImpl@9ef8eb7
name = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appConfig; factoryMethodName=memberRepository; initMethodName=null; destroyMethodName=(inferred); defined in hello.core.AppConfigobject = hello.core.member.MemoryMemberRepository@34cdeda2
name = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appConfig; factoryMethodName=discountPolicy; initMethodName=null; destroyMethodName=(inferred); defined in hello.core.AppConfigobject = hello.core.discount.RateDiscountPolicy@6ee660fb

이렇게 출력
//ROLE_APPLICATION -&gt; 직접 등록 앱 빈
//ROLE_INFRASTRUCTURE -&gt; 스프링 내부 사용 빈
## 스프링 빈
### 스프링 빈 조회 - 기본
- ac.getBean(빈이름, 타입)
- ac.getBean(타입)
-&gt; 조회 대상 스프링 빈 없으면 예외 발생</code></pre><p>package hello.core.beanfind;</p>
<p>import hello.core.AppConfig;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;</p>
<p>import static org.assertj.core.api.Assertions.*;</p>
<p>public class ApplicationContextBasicFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);</p>
<pre><code>@Test
@DisplayName(&quot;빈 이름으로 조회&quot;)
void findBeanByName() {
    MemberService memberService = ac.getBean(&quot;memberService&quot;,MemberService.class);
    assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName(&quot;구체 타입으로 조회&quot;) //구현 의존
void findBeanByName2() {
    MemberService memberService = ac.getBean(&quot;memberService&quot;,MemberServiceImpl.class);
    assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName(&quot;타입으로만 조회&quot;)
void findBeanByType() {
    MemberService memberService = ac.getBean(MemberService.class);
    assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}</code></pre><p>}
@Test
 @DisplayName(&quot;빈 이름으로 조회X&quot;)
 void findBeanByNameX() {
 //ac.getBean(&quot;xxxxx&quot;, MemberService.class);
org.junit.jupiter.api.Assertions.assertThrows(NoSuchBeanDefinitionException.class, () -&gt;
                ac.getBean(&quot;xxxxx&quot;, MemberService.class));
    } //예외가 터져야 로직실행</p>
<pre><code>타입으로 조회 시 같은 타입이 여러개일 경우 골치아픈 경우 발생 -&gt; 다음으로 ..
### 스프링 빈 조회 - 동일한 타입 여러개
이 때는 빈 이름을 지정해주면 조회 가넝</code></pre><p>package hello.core.beanfind;</p>
<p>import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;
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.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;</p>
<p>class ApplicationContextSameBeanFindTest {
     AnnotationConfigApplicationContext ac = new
            AnnotationConfigApplicationContext(SameBeanConfig.class);
    @Test
    @DisplayName(&quot;타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다&quot;)
    void findBeanByTypeDuplicate() {
        //DiscountPolicy bean = ac.getBean(MemberRepository.class);
        assertThrows(NoUniqueBeanDefinitionException.class, () -&gt;
                ac.getBean(MemberRepository.class));
    }
    @Test
    @DisplayName(&quot;타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다&quot;)
    void findBeanByName() {
        MemberRepository memberRepository = ac.getBean(&quot;memberRepository1&quot;,
                MemberRepository.class);
        assertThat(memberRepository).isInstanceOf(MemberRepository.class);
    }
    @Test
    @DisplayName(&quot;특정 타입을 모두 조회하기&quot;)
    void findAllBeanByType() {
        Map&lt;String, MemberRepository&gt; beansOfType =
                ac.getBeansOfType(MemberRepository.class);
        for (String key : beansOfType.keySet()) {
            System.out.println(&quot;key = &quot; + key + &quot; value = &quot; +
                    beansOfType.get(key));
        }
        System.out.println(&quot;beansOfType = &quot; + beansOfType);
        assertThat(beansOfType.size()).isEqualTo(2);
    }
    @Configuration
    static class SameBeanConfig {
        @Bean
        public MemberRepository memberRepository1() {
            return new MemoryMemberRepository();
        }
        @Bean
        public MemberRepository memberRepository2() {
            return new MemoryMemberRepository(); //예외터짐..
            .
            ...
        }
    }
}</p>
<pre><code>AutoWired 추가 시 유용함
### 스프링 빈 조회 - 상속 관계
중요함 .. 근데 딱 봐도 어려워보임 ㅡ,ㅡ
기본 원칙 -&gt; 부모 타입 조회 시 자식들 강제 소집</code></pre><p>package hello.core.beanfind;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
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.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;</p>
<p>class ApplicationContextExtendsFindTest {
    AnnotationConfigApplicationContext ac = new
            AnnotationConfigApplicationContext(TestConfig.class);
    @Test
    @DisplayName(&quot;부모 타입으로 조회시, 자식이 둘 이상 있으면, 중복 오류가 발생한다&quot;)
    void findBeanByParentTypeDuplicate() {
        //DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
        assertThrows(NoUniqueBeanDefinitionException.class, () -&gt;
                ac.getBean(DiscountPolicy.class));
    }
    @Test
    @DisplayName(&quot;부모 타입으로 조회시, 자식이 둘 이상 있으면, 빈 이름을 지정하면 된다&quot;)
    void findBeanByParentTypeBeanName() {
        DiscountPolicy rateDiscountPolicy = ac.getBean(&quot;rateDiscountPolicy&quot;,
                DiscountPolicy.class);
        assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
    }
    @Test
    @DisplayName(&quot;특정 하위 타입으로 조회&quot;)
    void findBeanBySubType() {
        RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
        assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
    }
    @Test
    @DisplayName(&quot;부모 타입으로 모두 조회하기&quot;)
    void findAllBeanByParentType() {
        Map&lt;String, DiscountPolicy&gt; beansOfType =
                ac.getBeansOfType(DiscountPolicy.class);
        assertThat(beansOfType.size()).isEqualTo(2);
        for (String key : beansOfType.keySet()) {
            System.out.println(&quot;key = &quot; + key + &quot; value=&quot; +
                    beansOfType.get(key));
        }
    }
    @Test
    @DisplayName(&quot;부모 타입으로 모두 조회하기 - Object&quot;)
    void findAllBeanByObjectType() {
        Map&lt;String, Object&gt; beansOfType = ac.getBeansOfType(Object.class);
        for (String key : beansOfType.keySet()) {
            System.out.println(&quot;key = &quot; + key + &quot; value=&quot; +
                    beansOfType.get(key));
        }
    }
    @Configuration
    static class TestConfig {
        @Bean
        public DiscountPolicy rateDiscountPolicy() {
            return new RateDiscountPolicy();
        }
        @Bean
        public DiscountPolicy fixDiscountPolicy() {
            return new FixDiscountPolicy();
        }
    }
}</p>
<pre><code>![](https://velog.velcdn.com/images/se-ol/post/46081ec0-be5b-42d7-9c3d-c77a2e6dd592/image.png)
다른건 내용이 너무 많아서 부모타입 모두 조회 부분만 !

실제 개발 시 빈 조회 빈도 거의 없음.. 하지만 순수 자바 어플리케이션에서 생성해서 사용시 용이하기 때문에 알고있어라 ~..
## BeanFactory와 ApplicationContext
![](https://velog.velcdn.com/images/se-ol/post/aa4a9a6e-a9c2-4b9a-93b0-b7ba877f079c/image.png)
### BeanFactory
- 스프링 컨테이너의 최상위 인터페이스
- 스프링 빈을 관리하고 조회하는 역할을 담당
- getBean() 을 제공
// 지금까지 우리가 사용했던 대부분의 기능
### ApplicationContext
- BeanFactory 기능을 모두 상속받아서 제공
- 애플리케이션을 개발할 때는 빈을 관리하고 조회하는 기능은 물론이고, 수 많은 부가기능이 필요한데 이를 제공
![](https://velog.velcdn.com/images/se-ol/post/b87ed9d3-176e-4d8a-8c90-67faa1b53c71/image.png)
- MessageSource (메시지소스를 활용한 국제화 기능)
//예를 들어서 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력
- EnvironmentCapable (환경변수) //로컬, 개발, 운영등을 구분해서 처리
- ApplicationEventPublisher (애플리케이션 이벤트)// 이벤트를 발행하고 구독하는 모델을 편리하게 지원
- ResourceLoader (편리한 리소스 조회) //파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회
=&gt;ApplicationContext는 BeanFactory의 기능을 상속
=&gt; ApplicationContext는 빈 관리기능 + 편리한 부가 기능을 제공
=&gt; BeanFactory를 직접 사용 X, 부가기능이 포함된 ApplicationContext를 사용
=&gt; BeanFactory나 ApplicationContext를 스프링 컨테이너
## 다양한 설정 형식 지원 - 자바 코드, XML
//참고용
![](https://velog.velcdn.com/images/se-ol/post/c12324d2-d45b-478a-a996-cebf7a23f843/image.png)
### 자바
- AnotationConfigApllicationContext 클래스를 사용해 자바 코드로 된 설정 정보 전달
### XML
- GenericXmlApplicationContext를 사용하면서 xml설정 파일 전달</code></pre><p>package hello.core.xml;</p>
<p>import hello.core.member.MemberService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import static org.assertj.core.api.Assertions.*;</p>
<p>public class XmlAppContext {
    @Test
    void xmlAppContext() {
        ApplicationContext ac = new
                GenericXmlApplicationContext(&quot;appConfig.xml&quot;);</p>
<pre><code>    MemberService memberService = ac.getBean(&quot;memberService&quot;,
            MemberService.class);
    assertThat(memberService).isInstanceOf(MemberService.class);
}</code></pre><p>}</p>
<pre><code>appConfig.xml</code></pre><?xml version="1.0" encoding="UTF-8"?>
<p><beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://
www.springframework.org/schema/beans/spring-beans.xsd">
 <bean id="memberService" class="hello.core.member.MemberServiceImpl">
 <constructor-arg name="memberRepository" ref="memberRepository" />
 </bean>
 <bean id="memberRepository"
class="hello.core.member.MemoryMemberRepository" />
 <bean id="orderService" class="hello.core.order.OrderServiceImpl">
 <constructor-arg name="memberRepository" ref="memberRepository" />
 <constructor-arg name="discountPolicy" ref="discountPolicy" />
 </bean>
 <bean id="discountPolicy" class="hello.core.discount.RateDiscountPolicy" />
</beans></p>
<pre><code>![](https://velog.velcdn.com/images/se-ol/post/daa77b45-ebfa-4292-8eb9-52f408332a56/image.png)
잘 나오는 모습
## 스프링 빈 설정 메타 정보 - BeanDefinition
### BeanDefinition
- 역할과 구현을 개념적으로 나눈 것.
![](https://velog.velcdn.com/images/se-ol/post/754c48da-9ee6-4ab1-b473-3ca59b429946/image.png)
//설계 자체를 추상화에만 의존하도록 함
#### 정보</code></pre><p>package hello.core.beandefinition;</p>
<p>import hello.core.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;</p>
<p>public class BeanDefinitionTest {
    AnnotationConfigApplicationContext ac = new
            AnnotationConfigApplicationContext(AppConfig.class);
    // GenericXmlApplicationContext ac = new GenericXmlApplicationContext(&quot;appConfig.xml&quot;);
    @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);
            }
        }
    }
}</p>
<p>```
<img src="https://velog.velcdn.com/images/se-ol/post/88cff688-104d-487b-a0a5-06ee65b03289/image.png" alt=""></p>
<ul>
<li>BeanClassName: 생성할 빈의 클래스 명(자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)</li>
<li>factoryBeanName: 팩토리 역할의 빈을 사용할 경우 이름, 예) appConfig</li>
<li>factoryMethodName: 빈을 생성할 팩토리 메서드 지정, 예) memberService</li>
<li>Scope: 싱글톤(기본값)</li>
<li>lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때 까지 최대한
생성을 지연처리 하는지 여부</li>
<li>InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명</li>
<li>DestroyMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명</li>
<li>Constructor arguments, Properties: 의존관계 주입에서 사용한다. (자바 설정 처럼 팩토리 역할의
빈을 사용하면 없음)</li>
</ul>
<p>//ApplicationContext -&gt; getBeanDefinition 사용 불가 -&gt; GenericXmlApplicationContext 사용 -&gt; 정보가 덜 구체적 -&gt; 최종  AnnotationConfigApplicationContext 사용</p>
<p>=&gt; 실무에서 직접 사용하는 일은 적기 때문에 스프링이 BeanDefinition을 사용해 다양한 설정 정보를 추상화해서 사용한다는 것만 이해하면 OK.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 핵심원리 - 기본편] 섹션 3]]></title>
            <link>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8-%EC%84%B9%EC%85%98-3</link>
            <guid>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8-%EC%84%B9%EC%85%98-3</guid>
            <pubDate>Tue, 24 May 2022 14:23:26 GMT</pubDate>
            <description><![CDATA[<h2 id="새로운-할인-정책">새로운 할인 정책</h2>
<p>왜? -&gt; 섹션 2에서 발견한 DIP, OCP를 못 지키는 문제 해결하기 위해
급작스러운 요구사항이 발생되었을 때 
저번 편에서 만든 정책에서 인터페이스 추가!</p>
<pre><code>package hello.core.discount;

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

public class RateDiscountPolicy implements DiscountPolicy {
    private int discountPercent = 10; 
    @Override
    public int discount(Member member, int price) {
        if (member.getGrade() == Grade.VIP) {
            return price * discountPercent / 100; -&gt; 로직오류
        } else {
            return 0;
        }
    }
}
</code></pre><p>Test코드</p>
<pre><code>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;

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

class RateDiscountPolicyTest {
    RateDiscountPolicy discountPolicy = new RateDiscountPolicy();

    @Test
    @DisplayName(&quot;VIP는 10퍼센트 할인 적용&quot;)
    void vip_o() {
        Member member = new Member(1L, &quot;memberVIP&quot;, Grade.VIP);
        int discount = discountPolicy.discount(member, 10000);
        assertThat(discount).isEqualTo(1000); //alt+enter 온디멘드 ~&gt;. static import


    }

    @Test
    @DisplayName(&quot;VIP가 아니면 노할인&quot;)
    void vip_x() {
        //given
        Member member = new Member(2L, &quot;memberBASIC&quot;, Grade.BASIC);
        //when
        int discount = discountPolicy.discount(member, 10000);
        //then
        assertThat(discount).isEqualTo(0);
    }
}</code></pre><h4 id="문제점">문제점</h4>
<p>설정 적용을 위해 OrderServiceImpl.java에서 new RateDiscoutPolicy()로 변경 =&gt; 여기서 객체를 변경하면서 OCP, DIP 위반 ...
왜? -&gt; OrderServiceImple은 추상 의존(DicountPolicy)뿐 아니라 구체클래스에도 의존...(FixDiscountPolicy, RateDiscountPolicy)
<img src="https://velog.velcdn.com/images/se-ol/post/2bfc3813-7d69-4840-b53a-6e6d8e0f251f/image.png" alt="">
<strong>DIP위반 !!</strong> -&gt; 인터페이스에만 의존하도록 코드를 변경해줘야함 . .```
public class OrderServiceImpl implements OrderService {
 //private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
 private DiscountPolicy discountPolicy;
} //이제 널포인터에러 발생ㅋ -&gt; 구현 객체 대신 생성 및 주입 필요</p>
<pre><code></code></pre><h2 id="appconfig-refactoring">AppConfig Refactoring</h2>
<h3 id="관심사-분리">관심사 분리</h3>
<p>new ~~()가 결국은 하나하나 .. 직접해야하는? 코드임 말 그대로. 개빡셈. 집중이 불가능하므로 역할 분담 필요 !!</p>
<h3 id="appconfig">AppConfig</h3>
<p>객체 생성 및 연결하는 클래스
MemeberServiceImpl.java를 다음과 같이 수정</p>
<pre><code>private final MemberRepository memberRepository ;

    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }  
</code></pre><p>OrderServiceImple.java</p>
<pre><code>private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }</code></pre><p>AppConfig.java</p>
<pre><code>public class AppConfig {
    public MemberService memberService() {
        return new MemberServiceImpl(new MemoryMemberRepository()); //직접 할당하지 않도록 하는 부분 !
    }
    public OrderService orderService() {
        return new OrderServiceImpl(
                new MemoryMemberRepository(),new FixDiscountPolicy()); // 순서 중요
    }
}

</code></pre><p>=&gt; 생성자 주입 <del>~
<img src="https://velog.velcdn.com/images/se-ol/post/1d5086ed-05ff-4cc0-9dc9-4399f7fa067c/image.png" alt="">
굿보이 AppConfig</del>.</p>
<p>다시 정리
AppConfig 객체는 각 객체를 생성하고 그 참조값을 Impl을 생성하면서 생성자로 전달함. -&gt; Impl의 입장에서 DI라고 함.(의존관계 주입)</p>
<p>MemberApp.java</p>
<pre><code>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) {
        AppConfig appConfig= new AppConfig();
        MemberService memberService = appConfig.memberService();
        //밑은 이전 코드
        //MemberService memberService = new MemberServiceImpl(memberRepository);
        Member member = new Member(1L, &quot;memberA&quot;, Grade.VIP);
        memberService.join(member);
        Member findMember = memberService.findMember(1L);
        System.out.println(&quot;new member = &quot; + member.getName());
        System.out.println(&quot;find Member = &quot; + findMember.getName());
    }
}
</code></pre><p>OrderApp.java</p>
<pre><code>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) {

        AppConfig appConfig = new AppConfig();
        MemberService memberService = appConfig.memberService();
        OrderService orderService = appConfig.orderService();
        //MemberService memberService = new MemberServiceImpl(memberRepository);
        //OrderService orderService = new OrderServiceImpl(memberRepository, discountPolicy);
        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><p>MemberServiceTest.java</p>
<pre><code>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><p>OrderServiceTest.java</p>
<pre><code>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;itemA&quot;, 10000);
        Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
    }
}</code></pre><p>-&gt; new 객체 생성은 AppConfig에서만 실행하고 고로 다른 import도 필요 없음 .. 다른 클래스는 그냥 실행만 하고 객체는 AppConfig가 주입해줌.</p>
<h3 id="appconfig-refactoring-1">AppConfig Refactoring</h3>
<p>원래의 AppConfig.java 코드 수정</p>
<pre><code>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());
        //return new MemberServiceImpl(new MemoryMemberRepository()); //직접 할당하지 않도록 하는 부분 !
    }
    public OrderService orderService() {
        //return new OrderServiceImpl(new MemoryMemberRepository(),new FixDiscountPolicy()); // 순서 중요
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
    public DiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }
}
</code></pre><p>new MemoryMemberRepository()의 붕복 부분이 제거되어 AppConfig 부분만 변경하면 된다 !</p>
<h3 id="새-구조와-할인-정책-적용">새 구조와 할인 정책 적용</h3>
<p>AppConfig.java</p>
<pre><code>public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();//원래 FixDiscountPolicy()였음
    }</code></pre><p><img src="https://velog.velcdn.com/images/se-ol/post/80169c14-7ba7-41b7-b9e6-428e9c8201ac/image.png" alt="">
드디어 새로운 할인 정책이 손쉽게 적용 !
<strong>DIP위반 문제를 역할 분담을 통해 AppConfig가 의존관계 주입. 또 Refactoring을 통해 중복을 제거하고 역할 명확하게 분리하며 OCP도 완벽하게 지킴 ㅋ</strong></p>
<h2 id="좋은-객체지향-설계의-5가지-원칙">좋은 객체지향 설계의 5가지 원칙</h2>
<h4 id="srp">SRP</h4>
<p><strong>한 클래스는 하나의 책임만 가져야 한다.</strong>
AppConfig의 등장으로 구현 객체의 생성과 연결의 책임 분리
클라이언트 객체는 실행만 !</p>
<h4 id="dip-의존관계">DIP 의존관계</h4>
<p><strong>구체화 말고 추상화에 의존해라.</strong>
이것 또한 AppConfig가 의존관계 주입하며 해결</p>
<h4 id="ocp">OCP</h4>
<p><strong>확장에는 오픈, 변경에는 클로즈</strong>
AppConfig가 객체 인스턴트를 대신 생성하고 주입하면서 클라이언트 코드는 변경하지 않아도 됨 -&gt; 만족</p>
<h2 id="ioc-di-컨테이너">IoC, DI, 컨테이너</h2>
<h4 id="ioc--inversion-of-control">IoC = Inversion of Control</h4>
<p><strong>제어의 역전.</strong> 자신이 호출 X, 대신 호출해주는 것.
-&gt; AppConfig, 객체는 실행 역할만 담당하게 되면서 제어권이 사라짐
-&gt; Impl또한 로직만 실행하게 됨</p>
<p>&lt;프레임워크 VS 라이브러리
내가 작성한 코드 제어 및 실행(ex.JUnit) VS 코드가 직접 제어 흐름 담당&gt;</p>
<h4 id="의존관계-주입--dependency-injection">의존관계 주입 = Dependency Injection</h4>
<p>의존관계 -&gt; 정적인 클래스 의존 관계 + 실행 시점의 동적인 객체 의존 관계
<strong>정적인 클래스 의존관계</strong>
클래스가 사용하는 import 코드로 쉽게 파악 가능.
코드 수정 전 기존 코드
<strong>동적인 객체 인스턴스 의존관계</strong>
런 타임에 외부에서 객체를 생성하고 연결해주어 클라이언트와 서버의 실제 의존관계가 연결이 되는 것
AppConfig 생성 이후의 코드</p>
<h4 id="ioc-컨테이너-di-컨테이너">IoC 컨테이너, DI 컨테이너</h4>
<p>-&gt; AppConfig와 같이 객체를 생성 및 관리, 의존관계 연결해줌
&lt;의존관계 주입에 초첨 = DI 컨테이너&gt;
어샘블러, 오브젝트 팩토리 ...</p>
<h2 id="스프링-전환">스프링 전환</h2>
<p>AppConfig를 스프링 기반으로 변경 !</p>
<pre><code>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 설정 구성
@Configuration
public class AppConfig {
    //스프링 빈
    @Bean
    MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
        //return new MemberServiceImpl(new MemoryMemberRepository()); //직접 할당하지 않도록 하는 부분 !
    }
    @Bean
    public OrderService orderService() {
        //return new OrderServiceImpl(new MemoryMemberRepository(),new FixDiscountPolicy()); // 순서 중요
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }
    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
    @Bean
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }
}
</code></pre><p>MemberApp에서 원래 AppConfig코드 대신</p>
<pre><code>ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = applicationContext.getBean(&quot;memberService&quot;, MemberService.class);</code></pre><p>로 수정
OrderApp 또한 마찬가지</p>
<pre><code>ApplicationContext applicationContext = new
AnnotationConfigApplicationContext(AppConfig.class);
 MemberService memberService =
applicationContext.getBean(&quot;memberService&quot;, MemberService.class);
 OrderService orderService = applicationContext.getBean(&quot;orderService&quot;,
OrderService.class);</code></pre><p>여기서 ApplicationContext는 아까 설명했던 컨테이너.
스프링 사용 전에는 개발자가 직접 AppConfig 통해서 직접 객체 생성 및 의존관계 주입을 했다면 이제는 컨테이너를 대신 돌린다.</p>
<ul>
<li>@Configuration을 설정정보로, @Bean을 통해서 스프링 컨테이너에 메소드를 등록 (스프링 빈의 이름은 메소드 명으로)</li>
<li>객체 조회는 applicationContext.getBean()을 통해서 가능</li>
</ul>
<p>//아직은 원리까지 .. 장점은 차차 알아가봅시다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 핵심원리 - 기본편] 섹션 2]]></title>
            <link>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8-%EC%84%B9%EC%85%98-2</link>
            <guid>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8-%EC%84%B9%EC%85%98-2</guid>
            <pubDate>Tue, 17 May 2022 11:25:09 GMT</pubDate>
            <description><![CDATA[<h2 id="프로젝트-생성">프로젝트 생성</h2>
<h3 id="세팅">세팅</h3>
<p>Project: Gradle Project
Spring Boot: 2.3.x
Language: Java
Packaging: Jar
Java: 11
Dependencies : X</p>
<pre><code>plugins {
 id &#39;org.springframework.boot&#39; version &#39;2.3.3.RELEASE&#39;
 id &#39;io.spring.dependency-management&#39; version &#39;1.0.9.RELEASE&#39;
 id &#39;java&#39;
}
group = &#39;hello&#39;
version = &#39;0.0.1-SNAPSHOT&#39;
sourceCompatibility = &#39;11&#39;
repositories {
 mavenCentral()
}
dependencies {
 implementation &#39;org.springframework.boot:spring-boot-starter&#39;
 testImplementation(&#39;org.springframework.boot:spring-boot-starter-test&#39;) {
 exclude group: &#39;org.junit.vintage&#39;, module: &#39;junit-vintage-engine&#39;
 }
}
test {
 useJUnitPlatform()
}</code></pre><p>build.gradle 수정 -&gt; 기본 메인 클래스에서 main() 실행하면서 동작 확인 가능</p>
<h2 id="비즈니스-요구사항과-설계">비즈니스 요구사항과 설계</h2>
<h3 id="회원-도메인">회원 도메인</h3>
<h4 id="요구사항">요구사항</h4>
<p>-회원가입 및 조회
-회원 등급 (일반과 VIP)
-회원 각자 DB구축 및 연동</p>
<h4 id="설계">설계</h4>
<p>Client -&gt; Service -&gt; Repository //객체 다이어그램
Repository &lt;= 메모리 회원 Repo + DB 회원 Repo + 외부 시스템 연동 회원 Repo
<img src="https://velog.velcdn.com/images/se-ol/post/1255da2e-c623-40bf-b5a3-17a6647dd824/image.png" alt=""></p>
<h4 id="개발">개발</h4>
<p>설계 시 필요했던 것들을 찬찬히 생각해보자 ... 일단 member package를 만들어서 깔끔하게 정리하면 보기 좋다 !..
회원 등급을 enum으로 제작해서 vip, 일반으로 나눈다 ... -&gt; 멤버 가입도 해야겠네?? 그럼 class를 생성해서 가입 시 필요한 함수들도 작성해봐야겠군 .. -&gt; 이제 레포를 사용하기 위해 인터페이스 구성을 하자 ! -&gt; 인터페이스를 implements한 저장소를 실제로 구현하는 코드 필요하겠군 .. -&gt; 이제 멤버의 상세정보는 구현했으니 그와 관련된 서비스를 구현해보자고 ....-&gt; 인터페이스 생성 -&gt; 구현 ..
=&gt; 이제 ... 실행 ... 할 수 있는 조건이 갖춰짐 ^^
//코드 파일은 너무 많아서 생략하겠서용</p>
<h4 id="실행-및-테스트">실행 및 테스트</h4>
<p>위의 로직으로 실행하기 전에 미리 테스트해보는 것이 좋지만 강의를 따르겠습니당.
패키지를 나가서 App을 돌리는 main()파일을 만듭니다.</p>
<pre><code>public static void main(String[] args) {
        MemberService memberService = new MemberServiceImpl();
        Member member = new Member(1L, &quot;memberA&quot;, Grade.VIP);//ctrl+alt+V해서 객체 생성
        memberService.join(member);//멤버생성
        Member findMember = memberService.findMember(1L);//가입확인
        System.out.println(&quot;new member = &quot; + member.getName());
        System.out.println(&quot;find Member = &quot; + findMember.getName());
    }</code></pre><p><img src="https://velog.velcdn.com/images/se-ol/post/7acd034b-d640-4de1-90cf-6bef6c45f45d/image.png" alt="">
일단 확인은 된다.
테스트 실행을 위해 test&gt;java&gt;hello.core&gt;member 패키지 생성 후 </p>
<pre><code>package hello.core.member;


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

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><img src="https://velog.velcdn.com/images/se-ol/post/d3fc0345-931a-416b-ba0e-ee553d0c6537/image.png" alt="">
깨긋해용 조아용~
하지만 의존관계와 앞서 설명한 객체지향에 대한 문제점이 존재함.
할당하는 부분이 구현체를 의존하면서(추상화 구체화 모두 의존) -&gt; 딱봐도 안좋음 ㅡㅡ -&gt; DIP위반</p>
<h3 id="주문과-할인-도메인">주문과 할인 도메인</h3>
<h4 id="요구사항-1">요구사항</h4>
<ul>
<li>회원의 상품 주문</li>
<li>등급에 따른 할인 정책</li>
<li>VIP의 고정 금액 할인</li>
<li>할인 정책의 변동성<h4 id="설계-1">설계</h4>
일단 로직을 그림으로 확인해보는게 이해하는데 쉬움!</li>
</ul>
<ol>
<li>주문 생성: 클라이언트는 주문 서비스에 주문 생성을 요청한다.</li>
<li>회원 조회: 할인을 위해서는 회원 등급이 필요하다. 그래서 주문 서비스는 회원 저장소에서 회원을
조회한다.</li>
<li>할인 적용: 주문 서비스는 회원 등급에 따른 할인 여부를 할인 정책에 위임한다.</li>
<li>주문 결과 반환: 주문 서비스는 할인 결과를 포함한 주문 결과를 반환한다.
<img src="https://velog.velcdn.com/images/se-ol/post/d00db21e-9049-453b-b89e-6d323d89a3a9/image.png" alt="">
//강의노트에서 쌔비쥐
<img src="https://velog.velcdn.com/images/se-ol/post/5bc2c9a6-e744-4de5-b1c7-a6c948573cd3/image.png" alt="">
각자 주문 서비스 구현체에서 -&gt; DB 회원 저장소 + 정률 할인 정책 or 메모리 회원 저장소 + 정액 할인 정책 둘로 나눠줘야함.=&gt; 협력관계 재사용 가능한 로직
벌써 대박적으로 복잡함 ! !<h4 id="개발-1">개발</h4>
할인 정책 dicount package 생성
주문 관리 Order package 생성
//많은 부분이 위와 동일해서 생략<h4 id="실행-및-테스트-1">실행 및 테스트</h4>
<pre><code>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><img src="https://velog.velcdn.com/images/se-ol/post/cd023e93-522b-4b61-a666-953c88694e40/image.png" alt="">
테스트 또한 성공!!
//로직에 대한 이해 좀 더 필요함 ..</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 핵심원리 - 기본편] 섹션 1]]></title>
            <link>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8-%EC%84%B9%EC%85%98-1</link>
            <guid>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8-%EC%84%B9%EC%85%98-1</guid>
            <pubDate>Tue, 10 May 2022 12:18:09 GMT</pubDate>
            <description><![CDATA[<h2 id="스프링">스프링</h2>
<h3 id="스프링-탄생-비화">스프링 탄생 비화</h3>
<p>EJB 이론이 좋으면 뭘하니 .. 지옥불인데 ㅜ
이를 싫어한 천재 개발자들이 내가 만들어도 저것보다 낫다는 말과 함께
<img src="https://velog.velcdn.com/images/se-ol/post/aa0e99ef-d0bd-49b7-96a5-ddd21c509d45/image.png" alt="">
이 구성으로 개발기획</p>
<h3 id="역사">역사</h3>
<p>로드 존슨의 예제 코드를 보고 매력을 느낀 유겐 휠러와 얀 카로프 가 오픈소스 프로젝트를 제안하여 현재의 스프링 개발</p>
<h3 id="스프링-1">스프링</h3>
<p><img src="https://velog.velcdn.com/images/se-ol/post/2041fcc4-b196-4d03-99d4-013d4016c276/image.png" alt="">
선택기술들은 그와 관련된 기술을 좀 더 편리하게 사용할 수 있도록 도와주는 것.
<strong>스프링 프레임워크</strong>
<img src="https://velog.velcdn.com/images/se-ol/post/703f3fef-7600-414d-80ea-8ff35159a9a5/image.png" alt="">
<strong>스프링 부트</strong>
<img src="https://velog.velcdn.com/images/se-ol/post/796ab325-cf00-40a7-8b27-fef46cba0098/image.png" alt="">
스프링 부트는 스프링 프레임워크를 더 편하게 사용할 수 있도록 하는 것 !</p>
<p>_<strong>스프링은 자바 언어 기반의 프레임 워크로 객체 지향을 효율적으로 좋은 애플리케이션을 개발할 수 있게 도와주는 프레임워크이다 !!</strong></p>
<h2 id="객체-지향-프로그래밍">객체 지향 프로그래밍</h2>
<p>객체지향 -&gt; 추상화, 캡슐화, 상속, 다형성의 특징 + 객체들의 모임으로 명령어를 파악하면서 유연하고 변경에 용이함.(하나만 바꾸면 다 적용)</p>
<h3 id="다형성">다형성</h3>
<p>역할과 구현을 분리하여 변경을 편리하게 한다.
클라이언트는
-&gt;  <strong>인터페이스만 알면 된다.</strong>
-&gt;     <strong>내부구조를 몰라도 된다.</strong>
-&gt;     <strong>내부구조가 변경되어도 영향을 받지 않는다.</strong>
-&gt;     <strong>구현 대상을 변경해도 영향을 받지 않는다.</strong>
=&gt; 객체를 설계할 때 역할과 구현을 명확하게 분리 !!
=&gt; 확장 가능한 설계
<img src="https://velog.velcdn.com/images/se-ol/post/e241db28-a247-4160-a6d3-f6d2b5da13bd/image.png" alt="">
이에 따른 한계 .... -&gt; 인터페이스 자체가 변하면 큰 변경이 발생 / 역할과 대본 자체가 변경되어야 한다면 ,,,, + 인터페이스의 안정적 설계가 매우 중요...</p>
<h2 id="solid">SOLID</h2>
<p>• SRP: 단일 책임 원칙(single responsibility principle)
-&gt; 한 클래스는 하나의 책임만 가져야 한다. ( 변경이 있을 때 파급 효과가 적음 )
• OCP: 개방-폐쇄 원칙 (Open/closed principle)
-&gt; 소프트웨어 요소는 확장에는 오픈이나 변경에는 클로즈 ( 다형성 활용 )
-&gt; 구현 객체를 변경하려면 클라이언트 코드 변경 필요,, / 다형성을 활용해도 OCP 원칙 만족 불가
• LSP: 리스코프 치환 원칙 (Liskov substitution principle)
-&gt; 객체는 프로그램의 정확성을 건들지 않으면서 하위의 인스턴스로 변경 가능해야 한다. ( 규약 지키기 )
• ISP: 인터페이스 분리 원칙 (Interface segregation principle)
-&gt; 특정 목적 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다. ( 명확성 상승, 대체 가능성 상승 )
• DIP: 의존관계 역전 원칙 (Dependency inversion principle)
-&gt; 구체화 말고 추상화에 의존해야 한다. ( 역할에 의존하라. but DIP 위반 )</p>
<p><strong>=&gt; 객체 지향의 핵심은 다형성이나 다형성 만드로는 OCP, DIP를 만족할 수 없음 .. 무언가가 더 필요하다 !</strong></p>
<h2 id="객체-지향-설계와-스프링">객체 지향 설계와 스프링</h2>
<h3 id="di-컨테이너">DI 컨테이너</h3>
<p>스프링 ??? -&gt; 위에서 OCP, DIP 만족 불가능을 가능으로 만들어 주는 기술이 사용된다 !! 이것이 의존관계, 의존성 주입 = DI(Dependency Injection)</p>
<p>모든 설계에 역할과 구현을 분리하여 개발하자.</p>
<p>// 추상화라는 비용과 확장을 유념해서 개발하도록 !</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 입문] 섹션 7,8 & [스프링 핵심원리 - 기본편] 섹션 0]]></title>
            <link>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%84%B9%EC%85%98-78</link>
            <guid>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%84%B9%EC%85%98-78</guid>
            <pubDate>Tue, 03 May 2022 11:21:38 GMT</pubDate>
            <description><![CDATA[<h2 id="aop">AOP</h2>
<p>AOP는 ??? -&gt; Aspect Oriented Programming
즉 관점지향프로그래밍 ..-&gt; 특정 로직을 관점을 기준으로 각각 모듈화하는 것
핵심 로직이 아닌 .... 공통 관심 사항을 추가할때 하나하나 넣기에는 굉장히 힘들고 유지보수가 힘들다 -&gt; 이때 AOP를 추가해서 원하는 곳에 공통 관심 사항을 적용할 수 있음 !!</p>
<p><img src="https://velog.velcdn.com/images/se-ol/post/f6530f65-f798-438e-b306-fd0ff9514bc5/image.png" alt="">
이렇게 적용됨</p>
<pre><code>package hello.hellospring.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component
@Aspect 
public class TimeTraceAop {
 @Around(&quot;execution(* hello.hellospring..*(..))&quot;)
 public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
 long start = System.currentTimeMillis();
 System.out.println(&quot;START: &quot; + joinPoint.toString());
 try {
 return joinPoint.proceed();
 } finally {
 long finish = System.currentTimeMillis();
 long timeMs = finish - start;
 System.out.println(&quot;END: &quot; + joinPoint.toString()+ &quot; &quot; + timeMs +
&quot;ms&quot;);
 }
 }
}</code></pre><p>SpringConfig 스프링빈 넣어주는게 깔꼼</p>
<pre><code>@Bean
puvlic TimeTraceAop() timeTraceAop() {
    return new TimeTraceAop();
}</code></pre><p>입문 끝 !<del>!</del>!<del>!</del>!<del>!</del>!<del>!</del>!<del>!</del>!~ 나 자신은 복습공부를 하도록 하세요 <del>!</del>!<del>!</del>!~!</p>
<h2 id="핵심원리-기본편-강의-소개">핵심원리 기본편 강의 소개</h2>
<p>객체지향 .......... 가보자고 !~~!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 입문] 섹션 6]]></title>
            <link>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%84%B9%EC%85%98-6</link>
            <guid>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%84%B9%EC%85%98-6</guid>
            <pubDate>Wed, 13 Apr 2022 02:56:08 GMT</pubDate>
            <description><![CDATA[<h2 id="h2-데이터베이스-설치">H2 데이터베이스 설치</h2>
<p>1.4.200 버전 다운로드
나는 윈도우 유저라 h2.bat으로 접속
<img src="https://velog.velcdn.com/images/se-ol/post/3a9c9c61-933a-40d2-88e5-cbd5ba6169c6/image.png" alt="">
jdbc:h2:tcp://localhost/~/test 으로 소켓사용해서 접근</p>
<pre><code>create table member
(
 id bigint generated by default as identity,
 name varchar(255),
 primary key (id)
);</code></pre><p>db생성! generated by default as identity(db가 자동으로 채워줌)
<img src="https://velog.velcdn.com/images/se-ol/post/1fed0cec-ee27-4da4-b1b0-c383fdbdbb8d/image.png" alt="">
여기서 갑자기 INSERT INTO 할 때 오류생겨서 당황했었는데 &quot;를 &#39;로 변경하니까 되더라 ;;;;;</p>
<h2 id="순수-jdbc">순수 JDBC</h2>
<p>build.gradle에 라이브러리 추가
application.properties에 또 추가 ( 나는 저번에 server port 9090으로 변경해줬었음 )
고대의 방식으로 배워보는 시간~~</p>
<pre><code>package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.springframework.jdbc.datasource.DataSourceUtils;
import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class JdbcMemberRepository implements MemberRepository {
    private final DataSource dataSource;
    public JdbcMemberRepository(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    @Override
    public Member save(Member member) {
        String sql = &quot;insert into member(name) values(?)&quot;;
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null; //결과 받기
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql,
                    Statement.RETURN_GENERATED_KEYS); //맨뒤 옵션 -&gt; insert 해주는 옵션임
            pstmt.setString(1, member.getName());
            pstmt.executeUpdate();
            rs = pstmt.getGeneratedKeys();
            if (rs.next()) {
                member.setId(rs.getLong(1));
            } else {
                throw new SQLException(&quot;id 조회 실패&quot;);
            }
            return member;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }
    @Override
    public Optional&lt;Member&gt; findById(Long id) {
        String sql = &quot;select * from member where id = ?&quot;;
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setLong(1, id);
            rs = pstmt.executeQuery();
            if(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong(&quot;id&quot;));
                member.setName(rs.getString(&quot;name&quot;));
                return Optional.of(member);
            } else {
                return Optional.empty();
            }
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }
    @Override
    public List&lt;Member&gt; findAll() {
        String sql = &quot;select * from member&quot;;
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            rs = pstmt.executeQuery();
            List&lt;Member&gt; members = new ArrayList&lt;&gt;();
            while(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong(&quot;id&quot;));
                member.setName(rs.getString(&quot;name&quot;));
                members.add(member);
            }
            return members;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }
    @Override
    public Optional&lt;Member&gt; findByName(String name) {
        String sql = &quot;select * from member where name = ?&quot;;
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, name);
            rs = pstmt.executeQuery();
            if(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong(&quot;id&quot;));
                member.setName(rs.getString(&quot;name&quot;));
                return Optional.of(member);
            }
            return Optional.empty();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }
    private Connection getConnection() {
        return DataSourceUtils.getConnection(dataSource); //utils를 통해서 커넥션해야 transaction
    }
    private void close(Connection conn, PreparedStatement pstmt, ResultSet rs)
    {
        try {
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (pstmt != null) {
                pstmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (conn != null) {
                close(conn);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    private void close(Connection conn) throws SQLException {
        DataSourceUtils.releaseConnection(conn, dataSource);
    }
}</code></pre><p>코드가 대박 와방 김 ~</p>
<h2 id="스프링-통합-테스트">스프링 통합 테스트</h2>
<pre><code>package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

@SpringBootTest //이거 한줄로 다 해결됨 .. 댑악임
@Transactional
class MemberServiceIntegrationTest {
    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;
    //afterEach 필요없음
    @Test
    public void 회원가입() throws Exception {
        //Given
        Member member = new Member();
        member.setName(&quot;hello&quot;);
        //When
        Long saveId = memberService.join(member);
        //Then
        Member findMember = memberRepository.findById(saveId).get();
        assertEquals(member.getName(), findMember.getName());
    }
    @Test
    public void 중복_회원_예외() throws Exception {
        //Given
        Member member1 = new Member();
        member1.setName(&quot;spring&quot;);
        Member member2 = new Member();
        member2.setName(&quot;spring&quot;);
        //When
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class,
                () -&gt; memberService.join(member2));//예외가 발생해야 한다.
        assertThat(e.getMessage()).isEqualTo(&quot;이미 존재하는 회원입니다.&quot;);
    }
}</code></pre><p>@Transactional -&gt; 트랜잭션을 먼저 시작하고 완료 후에 항상 롤백, DB에 데이터가 안남아서 다음 테스트시 아주 클린하당</p>
<h2 id="스프링-jdbctemplate">스프링 JdbcTemplate</h2>
<p>이 템플릿은 반복코드 제거에 아쥬 탁월함.</p>
<pre><code>package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class JdbcTemplateMemberRepository implements MemberRepository {

    private final JdbcTemplate jdbcTemplate;
    //생성자가 한개면 @Autowired같은 빈 생략 가능
    public JdbcTemplateMemberRepository(DataSource dataSource) {
        jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Override
    public Member save(Member member) {
        SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
        jdbcInsert.withTableName(&quot;member&quot;).usingGeneratedKeyColumns(&quot;id&quot;);
        Map&lt;String, Object&gt; parameters = new HashMap&lt;&gt;();
        parameters.put(&quot;name&quot;, member.getName());
        Number key = jdbcInsert.executeAndReturnKey(new
                MapSqlParameterSource(parameters));
        member.setId(key.longValue());
        return member;
    }

    @Override
    public Optional&lt;Member&gt; findById(Long id) {
        List&lt;Member&gt; result = jdbcTemplate.query(&quot;select * from member where id = ?&quot;, memberRowMapper(), id);
        return result.stream().findAny();
    }

    @Override
    public List&lt;Member&gt; findAll() {
        return jdbcTemplate.query(&quot;select * from member&quot;, memberRowMapper());
    }

    @Override
    public Optional&lt;Member&gt; findByName(String name) {
        List&lt;Member&gt; result = jdbcTemplate.query(&quot;select * from member where name = ?&quot;, memberRowMapper(), name);
        return result.stream().findAny();
    }

    private RowMapper&lt;Member&gt; memberRowMapper() {
        return (rs, rowNum) -&gt; {
            Member member = new Member();
            member.setId(rs.getLong(&quot;id&quot;));
            member.setName(rs.getString(&quot;name&quot;));
            return member;
        };
    }
}</code></pre><p>몇줄만에 어마어마하게 긴 내용을 축약 가능한 매우매우 편리한 기능</p>
<h2 id="jpa">JPA</h2>
<p>템플릿을 사용해서 편하게 작성할 수는 있었으나 결국 sql은 짜줘야했었는데 ... JPA(인터페이스)는 쿼리도 자기가 짜줌 .. (👼)
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
를 추가해준다 ddl-auto에서 어떤 값을 지정하는지에 따라서 자동으로 그 단계까지 해줌
ORM(Object-Relational-Mapping) - 객체와 관계형 데이터베이스를 자동으로 연결해주는 기능</p>
<pre><code>package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;

public class JpaMemberRepository implements MemberRepository{
    private final EntityManager em;

    public JpaMemberRepository(EntityManager em) {
        this.em = em;
    }

    public Member save(Member member) {
        em.persist(member); //영구저장
        return member;
    }

    public Optional&lt;Member&gt; findById(Long id) {
        Member member = em.find(Member.class, id);
        return Optional.ofNullable(member);
    }

    public List&lt;Member&gt; findAll() {
        return em.createQuery(&quot;select m from Member m&quot;, Member.class)
                .getResultList();
    }
    public Optional&lt;Member&gt; findByName(String name) {
        List&lt;Member&gt; result = em.createQuery(&quot;select m from Member m where m.name = :name&quot;, Member.class)
                .setParameter(&quot;name&quot;, name)
                .getResultList();
        return result.stream().findAny();
    }
}</code></pre><p>SpringConfig는 다음과 같이 수정</p>
<pre><code>package hello.hellospring;

import hello.hellospring.repository.JdbcTemplateMemberRepository;
import hello.hellospring.repository.JpaMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;
import javax.sql.DataSource;
@Configuration
public class SpringConfig {

    private EntityManager em;

    @Autowired
    public SpringConfig(EntityManager em) {
        this.em = em;
    }
   /*private final DataSource dataSource;

    public SpringConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }*/


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

    @Bean
    public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
// return new JdbcMemberRepository(dataSource);
        return new JpaMemberRepository(em);
    }

}</code></pre><p>//오류가 도저히 해결이 안되어서 중단
너무 슬픈게 ... Bean이랑 다른거랑 충돌되서 일어나는 것 같은데 .. 아직도 연유를 알수가 없음 ㅠ ㅠ</p>
<h2 id="스프링-데이터-jpa">스프링 데이터 JPA</h2>
<p>여전히 안됨 ............. exit 0으로 나감 ㅠ ㅠ .. 진짜 짜증나 . .울굎어</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 입문] 섹션 3, 4, 5]]></title>
            <link>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%84%B9%EC%85%98-3-4-5</link>
            <guid>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%84%B9%EC%85%98-3-4-5</guid>
            <pubDate>Tue, 05 Apr 2022 16:05:59 GMT</pubDate>
            <description><![CDATA[<h2 id="1-회원-관리">1. 회원 관리</h2>
<p><strong>비즈니스 요구사항</strong>
가장 단순한 걸로 개발 시작 !!</p>
<p><img src="https://velog.velcdn.com/cloudflare/se-ol/8b549177-fe98-4fd5-a3fc-3ce190b7508a/image.png" alt=""></p>
<p><strong>회원 도메인과 레포지토리</strong></p>
<ul>
<li>회원 객체<pre><code>package hello.hellospring.domain;
</code></pre></li>
</ul>
<p>public class Member {
    private Long id; //고객이 아니라 시스템이 저장하는 id
    private String name; //이름</p>
<pre><code>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;
}</code></pre><p>}</p>
<pre><code>- 회원 레포지토리 인터페이스</code></pre><p>package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.List;
import java.util.Optional;
public interface MemberRepository {
    Member save(Member member);
    Optional<Member> findById(Long id); //없으면 null로 반환이 되는데 optional로 감싸줌
    Optional<Member> findByName(String name);
    List<Member> findAll();
}</p>
<pre><code>-회원 레포지토리 메모리 구현체</code></pre><p>package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.<em>;
/*</em></p>
<ul>
<li>동시성 문제가 고려되어 있지 않음, 실무에서는 ConcurrentHashMap, AtomicLong 사용 고려</li>
<li>/
public class MemoryMemberRepository implements MemberRepository {
  private static Map&lt;Long, Member&gt; store = new HashMap&lt;&gt;(); //저장을 하기 위한 변수, 실무에서는 concurrent로
  private static long sequence = 0L;
  @Override
  public Member save(Member member) {<pre><code>  member.setId(++sequence); //시퀀스 값 올려줌
  store.put(member.getId(), member);
  return member;</code></pre>  }
  @Override
  public Optional<Member> findById(Long id) {<pre><code>  return Optional.ofNullable(store.get(id)); //null값을 감싸주기 위해성</code></pre>  }
  @Override
  public List<Member> findAll() {<pre><code>  return new ArrayList&lt;&gt;(store.values()); //member 반환</code></pre>  }
  @Override
  public Optional<Member> findByName(String name) {<pre><code>  return store.values().stream()
          .filter(member -&gt; member.getName().equals(name)) //같은 name만 반환
          .findAny();</code></pre>  }
}<pre><code>
</code></pre></li>
</ul>
<p><strong>테스트 케이스 작성</strong></p>
<pre><code>
    public void clearStore() { //MemoryMemberReposity.java에 추가
        store.clear();
    }</code></pre><pre><code>package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.*;
class MemoryMemberRepositoryTest { //클래스 레벨만 따로 테스트 가능
    MemoryMemberRepository repository = new MemoryMemberRepository();

    @AfterEach
    public void afterEach() { //실행이 끝날때마다
        repository.clearStore(); //계속 clearStore
    }


    @Test //테스트로 실행, 아무런 내용 없이도 메소드 실행 가능
    public void save() {
        //given
        Member member = new Member();
        member.setName(&quot;spring&quot;); //command+shift+enter 단축키 배움
        //when
        repository.save(member);
        //then
        Member result = repository.findById(member.getId()).get(); //optional에서 값 꺼내기
        assertThat(result).isEqualTo(member); // static import
        //Assertions.assertEquals(member, result);

    }

    @Test
    public void findByName() {
        //given
        Member member1 = new Member();
        member1.setName(&quot;spring1&quot;);
        repository.save(member1);

        Member member2 = new Member();
        member2.setName(&quot;spring2&quot;);
        repository.save(member2);
        //when
        Member result = repository.findByName(&quot;spring1&quot;).get(); //spring2를 넣으면 다른 객체라고 경고
        //then
        assertThat(result).isEqualTo(member1);
    }

    @Test
    public void findAll() {
        //given
        Member member1 = new Member();
        member1.setName(&quot;spring1&quot;);
        repository.save(member1);

        Member member2 = new Member();
        member2.setName(&quot;spring2&quot;);
        repository.save(member2);
        //when
        List&lt;Member&gt; result = repository.findAll();
        //then
        assertThat(result.size()).isEqualTo(2);
    }

}</code></pre><p><strong>회원 서비스 개발</strong>
실제 비즈니스 로직을 작성하는 !..</p>
<pre><code>package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;

import java.util.List;
import java.util.Optional;

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

    //회원가입
    public Long join(Member member) {
        validateDuplicateMember(member); //중복 회원 검증
        memberRepository.save(member);
        return member.getId();//result.orElseGet()
    }
    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName())
                .ifPresent(m -&gt; { //null이 아니면, optional이라서 가능한 일
                    throw new IllegalStateException(&quot;이미 존재하는 회원입니다.&quot;);
                });
    }
    //회원조회
    public List&lt;Member&gt; findMembers() {
        return memberRepository.findAll();
    }
    public Optional&lt;Member&gt; findOne(Long memberId) {
        return memberRepository.findById(memberId);
    }
}
</code></pre><p><strong>테스트 !!</strong>
클래스 명에서 command + shift + t</p>
<pre><code>private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }</code></pre><p>-&gt; MemberService.java constructor 수정사항</p>
<pre><code>package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;
class MemberServiceTest {
    MemberService memberService;
    MemoryMemberRepository memberRepository;
    @BeforeEach
    public void beforeEach() {
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }
    @AfterEach
    public void afterEach() {
        memberRepository.clearStore();
    }
    @Test
    public void 회원가입() throws Exception { //테스트니까 과감하게 한글로 해도 됨
        //Given
        Member member = new Member();
        member.setName(&quot;hello&quot;);
        //When
        Long saveId = memberService.join(member); //검증
        //Then
        Member findMember = memberRepository.findById(saveId).get();
        assertEquals(member.getName(), findMember.getName());
    }
    @Test
    public void 중복_회원_예외() throws Exception {
        //Given
        Member member1 = new Member();
        member1.setName(&quot;spring&quot;);
        Member member2 = new Member();
        member2.setName(&quot;spring&quot;);
        //When
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class,
                () -&gt; memberService.join(member2));//예외가 발생해야 한다.
        assertThat(e.getMessage()).isEqualTo(&quot;이미 존재하는 회원입니다.&quot;);
        /*
        memberService.join(member1);
        try {
        memberService.join(member2);
        fail();
        } catch (IllegalStateException e) {
        assertThat(e.getMessage()).isEqualTo(&quot;이미 존재하는 회원입니다.&quot;);
        }
         */
    }
}</code></pre><h2 id="2-스프링-빈과-의존관계">2. 스프링 빈과 의존관계</h2>
<p><strong>컴포넌트 스캔과 자동 의존관계 설정</strong>
컨트롤러와 클래스 간의 연결 -&gt; could not be found 생김 -&gt; @Service 추가 (등록하는 과정) -&gt; @Repository도 마찬가지 -&gt; 의존관계 주입을 위해 MemberService.java에 @AutoWired추가 (생성자 도출 및 컨테이너에 주입)
<img src="https://velog.velcdn.com/cloudflare/se-ol/b85a8ef1-e516-41b9-bd3d-1b40efd41ef9/image.png" alt=""></p>
<p><strong>스프링 빈</strong>
스프링 빈 등록 방법 </p>
<ol>
<li>컴포넌트 스캔 : 골뱅이파티(@Component)</li>
<li>자바 코드 //하위 패키지가 아닌 친구들은 등록되지 않는 위험성이 있으니 ..<pre><code>package hello.hellospring;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfig {
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}</code></pre></li>
</ol>
<ul>
<li>필드주입 -&gt; 생성자를 빼고 냅다 주입하기 (별로 좋지 않음)</li>
<li>set주입 -&gt; 나중에 호출, 누군가가 호출했을때 public이어야만 함<h2 id="3-웹-mvc-개발">3. 웹 MVC 개발</h2>
</li>
<li><em>회원 웹 기능 - 홈 화면 추가*</em></li>
<li><em>회원 웹 기능 - 등록*</em><pre><code>@PostMapping(value = &quot;/members/new&quot;)
   public String create(MemberForm form) {
       Member member = new Member();
       member.setName(form.getName());
       memberService.join(member);
       return &quot;redirect:/&quot;;
   }</code></pre></li>
<li><em>회원 웹 기능 - 조회*</em><pre><code>@GetMapping(value = &quot;/members&quot;)
   public String list(Model model) {
       List&lt;Member&gt; members = memberService.findMembers();
       model.addAttribute(&quot;members&quot;, members);
       return &quot;members/memberList&quot;;
   }</code></pre><img src="https://velog.velcdn.com/cloudflare/se-ol/3ca8dbd3-cc5e-403b-ac9e-434fbd00e926/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스프링 입문] 섹션 0, 1, 2]]></title>
            <link>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8</link>
            <guid>https://velog.io/@se-ol/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8</guid>
            <pubDate>Wed, 30 Mar 2022 08:43:41 GMT</pubDate>
            <description><![CDATA[<h2 id="1-프로젝트-환경">1. 프로젝트 환경</h2>
<p><a href="https://start.spring.io">https://start.spring.io</a> 를 이용</p>
<ul>
<li>gradle project, java, spring boot ver(2.6.4), dependencies(spring web, thymeleaf) 설정하여 .zip파일을 다운받고 압축을 푼 다음</li>
</ul>
<p>IntelliJ에서 open as project</p>
<p><img src="https://images.velog.io/images/se-ol/post/2074049c-cc01-4d8b-9572-23660dcf060c/image.png" alt="">
<strong>View 환경설정</strong></p>
<pre><code>&lt;!DOCTYPE HTML&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;Hello&lt;/title&gt;
    &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
&lt;/head&gt;
&lt;body&gt;
Hello
&lt;a href=&quot;/hello&quot;&gt;hello&lt;/a&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><p><strong>빌드하고 실행하기</strong></p>
<ol>
<li>cmd로 gradlew.bat</li>
<li>gradlew build</li>
<li>cd build/libs</li>
<li>java -jar hello-spring-0.0.1-SNAPSHOT.jar<h2 id="2-스프링-웹-개발-기초">2. 스프링 웹 개발 기초</h2>
<h3 id="정적-컨텐츠">정적 컨텐츠</h3>
//컨트롤러 X, 파일을 바로</li>
</ol>
<h3 id="mvc와-템플릿-엔진">MVC와 템플릿 엔진</h3>
<p>//Model, View, Controller</p>
<pre><code>@Controller
public class HelloController {
 @GetMapping(&quot;hello-mvc&quot;)
 public String helloMvc(@RequestParam(&quot;name&quot;) String name, Model model) {
 model.addAttribute(&quot;name&quot;, name);
 return &quot;hello-template&quot;;
 }
}</code></pre><pre><code>&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;body&gt;
&lt;p th:text=&quot;&#39;hello &#39; + ${name}&quot;&gt;hello! empty&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><h3 id="api">API</h3>
<p>//반환해서 띄워줌</p>
<pre><code>@Controller
public class HelloController {
 @GetMapping(&quot;hello-string&quot;)
 @ResponseBody
 public String helloString(@RequestParam(&quot;name&quot;) String name) {
 return &quot;hello &quot; + name;
 }
}</code></pre><pre><code>@Controller
public class HelloController {
 @GetMapping(&quot;hello-api&quot;)
 @ResponseBody
 public Hello helloApi(@RequestParam(&quot;name&quot;) String name) {
 Hello hello = new Hello();
 hello.setName(name);
 return hello;
 }
 static class Hello {
 private String name;
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 }
 }
}</code></pre><p>// 객체가 json으로 반환되는 responsebody</p>
]]></description>
        </item>
    </channel>
</rss>