인프런 김영한님의 '스프링 핵심 원리 기본편' 강의를 요약정리한 내용입니다
https://start.spring.io/
위 사이트에서 프로젝트를 생성할 수 있다. Gradle 외에는 설정을 그대로 따랐다.
비즈니스 요구 사항은 다음과 같다.
회원
회원을 가입하고 조회할 수 있다.
회원은 일반과 VIP 두 가지 등급이 있다.
회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다. (미확정)주문과 할인 정책
회원은 상품을 주문할 수 있다.
회원 등급에 따라 할인 정책을 적용할 수 있다.
할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라. (나중에 변경 될 수 있다.)
할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 도 있다. (미확정)
주문과 할인 정책의 경우에는 객제치향 설계 방법으로 설계한다. 인터페이스를 만들고 구현체를 언제든지 갈아끼울 수 있도록 설계한다.
: 회원 DB를 자체 구축 할 수도 잇고, 연동할수도 있기 때문에, 회원 데이터에 접근할 수 있는 계층을 따로 만들고, 이게 회원 저장소라는 인터페이스이다.
: 하위 계층의 3가지 (메모리 회원 저장소 db회원 저장소 외부시스템 연동 회원 저장소)를 선택하면 된다.
: 도메인 그림은 기획자도 볼 수 있는 그림, 바탕으로 클래스 다이어그램을 만들고(서버 실행 X 클래스들만 분석) 동적으로 발생하는 것들에 대해서 객체 다이어그램으로 계획한다.
: 먼저, 회원 정보에 관련한 내용을 만든다.
: 생성자, getter/setter 관련한 단축키는 Alt+ insert이다.
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;
}
}
회원 저장소 인터페이스
package hello.core.member;
public interface MemberRepository {
void save(Member member);
Member findById(Long memberId);
}
회원 저장소 구현체
package hello.core.member;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class MemoryMemberRepository implements MemberRepository {
private static Map<Long, Member> store = new HashMap<>();
@Override
public void save(Member member) {
store.put(member.getId(), member);
}
@Override
public Member findById(Long memberId) {
return store.get(memberId);
}
}
회원 서비스 인터페이스
package hello.core.member;
public interface MemberService {
void join(Member member); //가입
Member findMember(Long memberId); // 조회
}
회원 서비스 인터페이스 구현체
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);
}
}
: 리팩토링 중 변수 추출하기(ctrl + alt + v)
public static void main(String[] args) {
MemberService memberService = new MemberServiceImpl();
Member member = new Member(1L, "pitchu", Grade.VIP);
memberService.join(member);
Member findMember = memberService.findMember(1L);
System.out.println("new member = " + member.getName());
System.out.println("find member = " + findMember.getName());
}
// 순수한 자바로 만들어낸 코드이다.
: 애플리케이션 로직으로 하는건 한계가 있음
: 깨지면 오류를 보여주기 때문에 빠르게 캐치가 가능하다.
MemberService memberService = new MemberServiceImpl();
@Test
void join(){
//given 주어졌을때
Member member = new Member(1L, "pitchu",Grade.VIP);
//when 이렇게 했을때
memberService.join(member);
Member findMember = memberService.findMember(1L);
//then 이렇게 된다.
Assertions.assertThat(member).isEqualTo(findMember);
// 검증하는 내용
// 조인 한거와 찾는게 똑같은지 확인한다.
}
주문과 할인 정책
회원은 상품을 주문할 수 있다.
회원 등급에 따라 할인 정책을 적용할 수 있다.
할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라. (나중에 변경 될 수 있
다.)
할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을
미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 도 있다. (미확정)\
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member,itemPrice);
// 단일 설계원칙을 잘 지킨 케이스
// 할인은 discountPolicy가 하고 나에게 결과만 전해줘.
return new Order(memberId, itemName, itemPrice, discountPrice);
}
MemberService memberService = new MemberServiceImpl();
OrderService orderService = new OrderServiceImpl();
Long memberId = 1L;
Member member = new Member(memberId, "pitchu", Grade.VIP );
memberService.join(member);
Order order = orderService.createOrder(memberId, "pitchu", 10000);
System.out.println("order = " + order);
MemberService memberService = new MemberServiceImpl();
OrderService orderService = new OrderServiceImpl();
@Test
void createOrder(){
Long memberId = 1L;
Member member = new Member(memberId, "pitchu", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "pitchu",10000);
Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
인프런 김영한님의 '스프링 핵심 원리 기본편' 강의를 요약정리한 내용입니다
핵심 내용 : 스프링 컨테이너가 왜 탄생했는가?
: 새로운 할인 정책을 적용한다고 가정해보자
: 정책을 만들고 테스트 할 때, VIP인 경우, 아닌 경우 2가지로 나누어 테스트를 실행한다.
: 밑의 경우에서 보듯이, 옳은 답이 나오지 않을 경우 인텔리제이에서 주석처리한 에러메시지를 띄우기 때문에, 이를 통해 알 수 있다.
class RateDiscountPolicyTest {
RateDiscountPolicy discountPolicy = new RateDiscountPolicy();
@Test
@DisplayName("VIP는 10% 할인이 적용되어야 한다 ")
void vip_o(){
//given
Member member = new Member(1L, "mem", Grade.VIP);
//when
int discount = discountPolicy.discount(member, 10000);
//then
Assertions.assertThat(discount).isEqualTo(1000);
}
@Test
@DisplayName("VIP가 아닌 경우")
void vip_x(){
//given
Member member = new Member(2L, "memberBASIC", Grade.BASIC);
//when
int discount = discountPolicy.discount(member, 10000);
//then
//Assertions.assertThat(discount).isEqualTo(1000); 일 경우
//Expecting:
// <0>
//to be equal to:
// <1000>
Assertions.assertThat(discount).isEqualTo(0);
}
}
: 할인 정책을 변경하려면 OrderServiceImpl의 코드를 고쳐야 하는데 이는 다음과 같다.
public class OrderServiceImpl implements OrderService{
//private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
: 이런 코드일 경우, 문제점이 생기는데 OCP와 DIP를 준수하지 못했기 때문이다.
- DIP의 문제
: OrderServiceImpl이 DiscountPolicy 인터페이스 뿐만 아니라 FixDiscountPolicy에도 의존하고 있기 때문이다.
- OCP 위반
: 이 의존 관계를 변경하고자 할 경우 OCP도 위반하게 된다.
문제 해결법
: DIP를 위반하지 않도록 인터페이스만 의존하게 변경하면 된다.
//private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private final DiscountPolicy discountPolicy;
주석처리된 코드를 아래와 같이 바꾸면 된다.
추가 문제
: 이렇게 하면 Null pointer exception 문제가 발생한다.
추가 문제 해결법
: 누군가가 클라이언트인 orderServiceImpl에 DiscountPolicy의 구현 객체를 대신 생성하고 주입해주어야 한다. 이에 관련한 내용은 밑에 더 자세하게 있다.
: 지금 까지의 코드는, 애플리케이션이 공연이라고 가정 했을 때, 배우가 공연도 하고 여자 주인공도 공연에 초빙하는 다양한 책임을 지니는 코드이다.
: 이에 공연을 구성하고 섭외하는 공연 기획자를 만들어야 한다.
: 이것을 AppConfig로 가정하고 만들어본다.
: 이 클래스는 구현 객체를 생성하고 연결하는 책임을 가지는 별도의 설정 클래스이다.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(
new MemoryMemberRepository(),
new FixDiscountPolicy());
}
}
: AppConfig가 구현 객체를 생성하고, 생성한 객체의 참조를 통해 생성자를 통해서 주입한다.
: 이렇게 하면 DIP도 지키게 된다.
: appConfig 객체는 memoryMemberRepository 객체를 생성하고 그 참조값을 memberServiceImpl 을 생성하면서 생성자로 전달한다.
: 클라이언트인 memberServiceImpl 입장에서 보면 의존관계를 마치 외부에서 주입해주는 것 같다고 해서 DI(Dependency Injection) 우리말로 의존관계 주입 또는 의존성 주입이라 한다
: 위의 문제였던 discountPolicy을 고친 모습이다.
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);
// 단일 설계원칙을 잘 지킨 케이스 할인은 discountPolicy가 하고 나에게 결과만 전해줘.
return new Order(memberId, itemName, itemPrice, discountPrice);
}
}
: 새로운 구조를 적용할 경우, 구성 영역은 당연히 변경되지만, 사용영역은 변경 될 필요가 없다.
@Configuration
public class AppConfig {
//@Bean memberService -> new MemoryMemberRepository
//@Bean orderservice -> new MemoryMemberRepository
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy(){
//return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
// 다음과 같이 Appconfig내 코드만 변경하면 되는 것을 알 수 있다.
: 한 클래스는 하나의 책임만 가져야 한다.
: 배역의 문제를 AppConfig를 통하여 나눔.
: 의존관계가 복합적이던 문제를 AppConfig를 통하여 해결
: 애플리케이션을 사용영역과 구성영역으로 나눔.
: 프로그램의 제어 흐름을 클라이언트 구현 객체가 제어하지 않고, 외부의 구성 영역에서 가져가는 것을 제어의 역전 이라고 한다.
: 의존 관계는 정적인 클래스 의존 관계와 실행 시점에 결정되는 동적인 객체 의존 관계로 나누어서 생각해야 한다.
: 정적은 실행하지 않고도 알 수 있는 것이다.
: 애플리케이션 실행 시점에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결되는 것을 '의존관계 주입'이라고 한다.
: 의존 관계 주입을 사용하면 클라이언트 코드를 변경하지 않고 호출하는 대상의 타입 인스턴스를 변경 할 수 있다.
: 정적인 클래스 의존 관계를 변경하지 않고 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다.
: AppConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것
: @Configuration과 @Bean을 붙여준다
: AppConfig를 통해 기존에는 직접 객체를 생성하고 DI했지만, 스프링 컨테이너를 통해서 사용한다.
: 스프링 컨테이너는 @Configuration이 붙은 AppConfig를 설정 정보로 사용한다. @Bean이 붙은 메소드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록하는데, 이렇게 등록된 객체를 스프링 빈이라 한다.
: 스프링 빈은 applicationContext.getBean() 메서드
를 사용해서 찾을 수 있다.
인프런 김영한님의 '스프링 핵심 원리 기본편' 강의를 요약정리한 내용입니다
//스프링 컨테이너 생성
ApplicationContext applicationContext =
new
AnnotationConfigApplicationContext(AppConfig.class);
: 위의 코드에서 ApplicationContext 를 스프링 컨테이너이자 인터페이스이다.
: 파라미터로 구성 정보를 지정하고 파라미터의 설정 클래스 정보를 이용해서 스프링 빈을 등록한다.
: 빈의 이름은 메소드 이름을 사용한다.
: ac.getBeanDefinitionNames()를 이용하면 모든 빈을 조회할 수 있고, (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION을 활용하면 직접 등록한 애플리케이션 빈만 추출할 수 있다.
: 스프링 컨테이너에서 스프링 빈을 찾는 가장 기본적인 방법은 아래와 같다.
ac.getBean(빈이름, 타입)
ac.getBean(타입)
: 다만, 조회 대상 스프링 빈이 없으면 예외 발생 하는데 이는 아래와 같다.
: 조회하는 자세한 방법은 아래와 같다.
NoSuchBeanDefinitionException: No bean named 'xxxxx' available
public class ApplicationContextBasicFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName(){
MemberService memberService = ac.getBean("memberService", MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("이름 없이 타입으로만 조회")
void findBeanByType(){
MemberService memberService = ac.getBean(MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("구체 타입으로 조회")
void findBeanByName2(){
MemberService memberService = ac.getBean("memberService", MemberServiceImpl.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("빈 이름으로 조회 X") // 통과되지않는 경우
void findBeanByNameX(){
org.junit.jupiter.api.Assertions.assertThrows(NoSuchBeanDefinitionException.class,
() -> ac.getBean("XXXXX", MemberService.class));
}
: 같은 타입의 스프링 빈이 둘 이상이면 오류(NoUniqueBeanDefinition)가 발생하는데, 이럴때는 빈이름을 지정해주어야한다.
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다")
void findBeanByName() {
MemberRepository memberRepository = ac.getBean("memberRepository1",
MemberRepository.class);
assertThat(memberRepository).isInstanceOf(MemberRepository.class);
}
: 위와 같이 빈 이름을 지정하면 된다.
@Test
@DisplayName("특정 타입을 모두 조회하기")
void findAllBeanByType(){
Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
for (String key : beansOfType.keySet()){
System.out.println("key = " + key + "value = " + beansOfType.get(key));
}
org.assertj.core.api.Assertions.assertThat(beansOfType.size()).isEqualTo(2);
}
: 다음과 같은 코드를 통하여 같은 타입의 빈을 모두 조회 할 수 있다.
: 부모 타입을 조회할 경우, 자식 타입도 함께 조회된다.
: object타입으로 조회하면 모든 스프링 빈이 조회가 가능하다.
: 자식 타입이 둘 이사 있으면 중복 오류가 발생하기 때문에 마찬가지로 빈 이름을 지정해주면 된다.
: 스프링 컨테이너의 최상위 인터페이스
: 스프링 빈을 관리하고 조회하는 역할을 담당한다. ex) getBean
: 직접 사용하지 않고 ApplicationContext를 사용한다.
: beanfactory의 기능을 모두 상속받아서 제공한다.
: 빈 관리 기능에 편리한 여러가지 부가기능들을 제공한다.
위의 두개를 스프링 컨테이너라고 한다.
: 스프링 컨테이너는 다양한 설정 정보를 받아들일 수 있게 설계되어 있다.
: 스프링이 이렇게 다양한 설 정 형식을 지원하는 이유는 역할과 구현을 개념적으로 나누었기 때문이다.
: BeanDefinition이 빈 설정 메타 정보인데, @Bean 당 각각 하나씩 메타 정보가 생성 되고 이것을 기반으로 스프링 빈이 생성된다.
: BeanDefinition은 스프링이 다양한 형태의 설정 정보를BeanDefinition으로 추상화해서 사용하는 것 정도만 이해하면 된다.
인프런 김영한님의 '스프링 핵심 원리 기본편' 강의를 요약정리한 내용입니다
: 대부분의 스프링 어플리케이션은 웹 어플리케이션인데, 여러 고객이 동시에 요청을 하는 경우가 많음.
: 매번 객체를 새로 생성함(스프링 없는 순수한 DI컨테이너의 경우)
싱글톤이란?
: 해당 객체가 하나만 생성되고 공유되는 것
: 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다
: 객체 인스턴스를 2개 이상 생성치 못하도록 막아야 한다.
: 싱글톤 패턴을 적용한 예제 코드는 다음과 같다.
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("싱글톤 객체 로직 호출");
}
}
: 싱글톤 패턴은 유연성이 떨어지는 단점이 있다.
: 싱글톤 패턴의 문제점이었던 지저분한 코드 X DIP, OCP, 테스트 , private 생성자로부터 자유롭게 싱글톤 사용 가능
: 스프링 빈이 싱글톤으로 관리되는 빈이다.
: 싱글톤 객체는 상태를 유지하게 설계하면 안된다.
: 무상태로 설계해야한다.
: 공유필드의 문제점이 있는데, 이는 다음 코드를 통해 알 수 있다.
: 싱글톤에 Stateful Service 클래스를 만들고,
package hello.core.singleton;
public class StatefulService {
private int price; // 상태를 유지하는 필드
public void order(String name, int price){
System.out.println("name = " + name + "price = " + price);
this.price = price;
}
public int getPrice(){
return price;
}
}
: 이어서 테스트를 다음과 같이 만들었을 경우,
class StatefulServiceTest {
@Test
void statefulServiceSingleton(){
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
//Thread A 사용자 10000주문
statefulService1.order("user A ", 10000);
//Thread B 사용자 30000주문
statefulService2.order("user B ", 30000);
//Thread A : 사용자 주문 금액 조회
int price = statefulService1.getPrice();
System.out.println(price);
org.assertj.core.api.Assertions.assertThat(statefulService1.getPrice()).isEqualTo(30000);
}
static class TestConfig{
@Bean
public StatefulService statefulService(){
return new StatefulService();
}
}
}
: 사용자 A 의 값을 물었을때 사용자 B의 값이 나오는걸 볼 수 있음.
결론 : 스프링은 항상 무상태로 설계 해야함
: configuration은 싱글톤을 위하여 존재함
: 아래 코드를 보자
void configurationTest(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class);
MemberRepository memberRepository = ac.getBean("memberRepsitory", MemberRepository.class);
MemberRepository memberRepository1 = memberService.getMemberRepository();
MemberRepository memberRepository2 = orderService.getMemberRepository();
// 1
System.out.println("memberService -> memberRepository = " + memberRepository1);
// 2
System.out.println("orderService -> memberRepository = " + memberRepository2);
// 3
System.out.println("memberRepository = " + memberRepository);
}
: 이 코드에서 1 2 3은 모두 같은 결과값을 나타낸다.(memberRepository가 한번만 호출된다.)
: 위의 이유는 @Configuration 때문이다. 아래의 코드의 실행 결과를 살펴보자.
@Test
void ConfigurationDeep(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
}

: 위와 같이 CGLIB가 붙어서 나오는 것을 확인할 수 있다.
: 스프링은 싱글톤 보장을 위해 CGLIB라는 바이트코드 조작 라이브러리를 사용한다.
: 이에 @Configuration을 적용한 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 이를 스프링 빈으로 등록한 것이다.
: 로직상으로 빈이 존재할 경우, 존재하는 빈을 반환하고 없을 경우 생성해서 스프링빈으로 등록하고 반환하는 코드가 동적으로 만들어지기 때문이다.

: 위와 같이 그대로 AppConfig가 나오는 것을 볼 수 있다.
: 위에서 만든 void configurationTest() 코드를 돌려보면, 세개가 일치하지 않음을 확인 할 수 있다.
: @Autowired MemberRepository memberRepository;
: 위 문장은 동적으로 의존관계를 자동 주입 시켜준다.
인프런 김영한님의 '스프링 핵심 원리 기본편' 강의를 요약정리한 내용입니다
: 스프링 빈을 등록할 때, @bean을 통하여 등록 했지만, 그 방법을 쓰지 않고 설정정보 없이 자동으로 스프링 빈을 등록하는 것이 컴포넌트 스캔이다.
: 컴포넌트 스캔은 @Component가 붙은 모든 클래스를 스프링 빈으로 등록한다.
: 이렇게 할 경우 AppConfig와 달리 의존관계 주입을 한다고 명시할 수 없음
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
// AppConfig내의 수동으로 등록하는 @configuration
// annotation 에도 component가 들어가기 때문에 임의로 제거함.
// 예제코드를 살리기 위함
)
: 의존관계를 자동으로 주입한다.
: 앞의 문제를 해결해주는 기능으로, 주로 컴포넌트 스캔과 같이 쓰임
: 생성자 위에 쓰임
ac.getBean(MemberRePository.class)// 와 같은 기능을 한다.
: 패키지 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것이 좋다.
: 컴포넌트 스캔은 다음과 같은 내용도 대상에 포함한다.
: ex) @Component, @Controller, @Service, @Repository, @Configuration
basepackages = "hello.core.member"
//1. 탐색할 패키지의 시작 위치를 지정 이 패키지부터 하위 패키지로 찾아감
basePackageClasses
// 2. 지정한 클래스의 패키지를 탐색 시작 위로 지정한다.
// 3. 지정하지 않을 경우 : @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.
: includeFilters / excludeFilters : 컴포넌트 스캔에서 추가 or 제외
: 아래와 같이 작동한다.
@ComponentScan(
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
)
: FilterType은 5가지 옵션이 있다.
: 이름이 같을 경우 ConfilctingBeanDefinitionException오류 발생
: 수동 빈 등록이 우선권을 가진다. (수동빈이 자동 빈 오버라이딩)
인프런 김영한님의 '스프링 핵심 원리 기본편' 강의를 요약정리한 내용입니다
크게 4가지 방법이 있다.
A. 생성자 주입
B. 수정자 주입(setter)
C. 필드 주입
D. 일반 메소드 주입
: @Autowired
: 생성자 호출 시점에 딱 1번만 호출 되는 것이 보장된다.
: 주로 불변과 필수 의존관계에 쓰인다.
: 스프링 빈의 경우 생성자가 하나일 경우에는 자동적으로 @Autoqired가 적용된다.
: setter라 불리는 필드의 값을 변경하는 수정자 메소드를 통하여 의존관계를 주입하는 방법이다.
: 주로 선택과 변경 의존관계에 쓰인다.
: 자바빈 프로퍼티 규약의 수정자 메소드 방식을 사용하는 방법이다.
: 스프링 빈 의존관계 설정 - 완료 단계에서 주입함.
@Autowired(required = false //선택적임)
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
: 이름 그대로 필드에 바로 주입하는 방법이다.
: 간단하고 좋으나, 외부에서 변경이 불가능해서 테스트하기 힘들다는 단점이 있다.
: DI프레임워크가 있어야 함
: 다만, 애플리케이션의 실제 코드와 관련없는 테스트코드, 스프링 설정을 목적으로 하는 @Configuration과 같은 곳에서만 특별한 용도로 사용된다.
@Autowired private MemberRepository memberRepository
: 아무 메소드에나 @Autowired 가능
: 한번에 여러 필드를 주입받는다.
@Autowired
public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy){
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
: 주입할 스프링 빈이 없어도 동작해야 할 때가 있는데, @Autowired만 사용하면 자동 주입 대상이 ㅇ벗을시 오류가 발생 할 수 있다.
자동주입 대상을 옵션으로 처리하는 방법은 다음과 같다.
A. @Autowired(required = false) 자동 주입 대상이 없을 시 수정자 메서드가 호출이 되지 않는다.
B. org.springframework.lang.@NUllable 자동 주입 대상이 없을 시 null이 입력됨
C. Optional<> 자동 주입 대상이 없을 시 Optional.empty가 입력됨
package hello.core.autowired;
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("no Bean1 = " + noBean1); // 호출이 안됨 메소드가
}
@Autowired
public void setNoBean2(@Nullable Member noBean2){
System.out.println("no Bean2 = " + noBean2); // no Bean2 = null
}
@Autowired
public void setNoBean3(Optional<Member> noBean3){ // java 8
System.out.println("no Bean3 = " + noBean3); // no Bean3 = Optional.empty
}
}
}
생성자 주입을 권장하는 이유 (주를 생성자로, 옵션을 수정자로 선택하라. 필드주입은 쓰지 마라)
A. 불변
: 대부분의 의존관계 주입은 변경할 필요가 없고, 하면 안된다.
: 수정자 주입 사용시 set Xxx메소드가 public으로 열림
: 누군가가 변경할 수 있는 메서드를 열어두는 것은 좋은 방법이 아니다.
: 생성할때 한번 호출되기 떄문에 불변하게 설계할 수 있다.
B. 누락
: 프레임 워크 없이 순수한 자바 코드를 테스트 할 경우에 빠르게 의존관계를 detect 할 수 있다.
C. final 키워드
: 생성자 주입관계를 써야 final 키워드를 쓸 수 있고, 이는 생성자에서만 값을 세팅할 수 있음을 의미
: 컴파일러가 값이 설정되지 않는 오류가 있을시에 알려준다.
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
//밑의 두 코드가 없으면 알려준다
//this.memberRepository = memberRepository;
//this.discountPolicy = discountPolicy;
}
: 롬복이란? 자동으로 getter와 setter를 만들어주는 것을 뜻한다.
// 롬복이란 이 코드를
@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;
}
// 이것과 같이 바꾸는 것을 뜻한다.
package hello.core;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class HelloLombok {
private String name;
private int age;
public static void main(String[] args) {
HelloLombok helloLombok = new HelloLombok();
helloLombok.setName("gerrard");
System.out.println("name = " + helloLombok);
}
}
@RequiredArgsConstructor
: final 이 붙은 필드를 모아 자동으로 생성자를 만들어줌
: @Autowired는 타입으로 조회한다.
: 스프링 빈 조회와 유사하게, 빈이 2개 이상일 경우에 문제가 발생한다.
: 'NoUniqueBeanDefinitionException' 오류가 발생하며, 하나의 빈을 기대했는데 두개의 빈이 발견되었다고 알려준다.
: @Autowired는 처음에 타입 매칭을 시도하고, 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.
: @Qualifier는 추가 구분자를 붙여주는 방법이고, 주입시 추가적인 방법을 제공한다.
: Qualifier끼리 매칭한다. 빈이름을 매칭 하고, 예외가 터진다.
: 자주 사용하는 것.
: 우선순위를 지정하는데, @primary가 지정되어 있을 경우 우선권을 가진다.
: @Qualifier("mainDiscountPolicy") 이렇게 문자를 적으면 컴파일시 타입 체크가 안되기 때문에 Annotation을 만든다.
//Annotation을 만들고
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("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
//다음과 같이 사용한다.
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
: 해당 타입의 스프링 빈이 다 필요한 경우가 있다. 이럴 경우 사용한다.
: 자동으로 웬만하게 쓰되, 직접 등록하는 기술 지원 객체는 수동 등록하자.
: 다형성을 활용하는 비즈니스 로직은 수동등록이 좋다.
인프런 김영한님의 '스프링 핵심 원리 기본편' 강의를 요약정리한 내용입니다
: 여러 작업들에서, 객체의 초기화와 종료 작업이 필요하다.
: 스프링 빈은 객체 생성 -> 의존관계 주입 이라는 사이클을 가진다.
: 의존관계 주입 뒤에야, 필요한 데이터를 사용할 수 있는 준비가 완료된다.
: 이에 초기화 작업은 의존 관계 주입 후 호출해야한다.
: 스프링은 의존 관계 주입이 완료되는 시점을 알려주는 다양한 기능이 있고, 스프링 컨테이너 종료 전에는 소멸 콜백도 준다.
스프링 빈의 이벤트 라이프사이클
스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백(빈 의존 관계 주입 완료) -> 사용 -> 소멸전 콜백(빈 소멸 직전)-> 스프링 종료
스프링은 아래의 2, 3,4 번을 통해서 빈 생명주기 콜백을 지원한다.
: 의존 관계 주입이 끝나면 호출하는 메소드 를 생성
public void afterPropertiesSet() throws Exception {
}
@Override
public void destroy() throws Exception {
disconnect();
}
다만 이 방법에는 단점이 있는데,
1. 스프링 전용 인터페이스에 의존한다
2. 외부 라이브러리에 적용이 불가능하고, 메소드 이름 변경이 불가능.
: 자유롭게 메소드를 만들고, 빈에 아래와 같이 메소드를 저장.
@Bean(initMethod = "init",destroyMethod = "close")
장점
: 스프링 빈이 스프링 코드에 의존하지 않게 됨
: 코드가 아니라 설정 정보를 사용하기 때문에, 외부 라이브러리에도 초기화 종료 메소드를 적용할 수 있다.
: @PostConstruct, @PreDestroy 어노테이션을 활용한다.
: 가장 편리하게 초기화와 종료를 실행 할 수 있다.
: 다만 외부 라이브러리에는 적용하지 못하는 단점이 있다.
: 위의 경우에는 3번을 사용하면 된다.
: 싱글톤 스코프에서는, 스프링 빈이 스프링 컨테이너가 시작할때 생성되고, 종료할때까지 유지된다. 스프링빈은 기본적으로 싱글톤 스코프로 생성된다.
- 스코프란?
: 빈이 존재할 수 있는 범위
- 스코프의 종류
- 싱글톤
: 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위- 프로토타입
: 프로토타입 빈의 생성과 의존관계 주입 초기화 까지하고 관리하지 않는 매우 짧은 범위의 스코프- 웹 관련
A. request : 웹 요청이 들어오고 나갈때까지
B. session : 웹 세션의 생성과 종료
C. application : 웹이 서블릿 컨텍스와 같은 범위로 유지
: 싱글톤 스코프에서는, 스프링 컨테이너가 항상 같은 인스턴스의 스프링 빈을 반환한다.(5장의 내용)
: 프로토타입 스코프의 경우에는 항상 새로운 인스턴스를 생성해서 반환한다.
: 스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계 주입, 초기화 까지만 처리한다.
: 프로토타입 빈을 관리할 책임은 프로토타입 빈을 받은 클라이언트에 있다. @predestory와 같은 종료 메소드는 호출되지 않는다.
- import static = alt + enter
: 같이 사용할 경우, 프로토타입 빈은 과거에 이미 주입이 끝난 빈이고(주입 시점에만 생산이 되고), 사용할 때마다 새로 생성이 되지 않는다.
싱글톤 빈이 프로토타입을 사용할때마다 새로 요청하는 것이다.
: 의존관계를 주입받는게 아니라 필요한 의존관계를 찾는데, 이것이 Dependency Lookup이다.
: 다만 더욱 정확한 해결을 위해 지정한 프로토타입 빈을 컨테이너에서 대신 찾아주는 DL정도의 기능만 제공하는 무언가가 있어야 한다.
ObjectFactory, ObjectProvider
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
: 웹 환경에서만 동작하고, 스프링이 해당 스코프의 종료 시점까지 관리하여 종료 메소드가 호출되는 스코프이다.
: 요청에 맞춰 각각 다른 스프링 빈을 생성하여 전용 객체를 만들어 사용된다.
- 웹 스코프의 종류
- request
- session
- application
- websocket
위의 문제를 해결하기 위하여
: objectProvider를 활용하여 request생성까지 지연할 수 있다.
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
: 가짜 프록시 클래스를 만든 후 request와 상관없이 가짜 프록시 클래스를 다른 빈에 미리 주입해둘 수 있다.
: 위의 코드보다 훨씬 간단하다.