1-1. 이야기 - 자바 진영의 추운 겨울과 스프링의 탄생
1) EJB(J2EE) -> 하이버네이트 -> JPA
2) JPA구현체들(하이버네이트, EclipseLink, 기타..) => JPA(표준 인터페이스)로
3) 스프링 역사
1-2. 스프링
1-3. 좋은 객체 지향 프로그램이란?
1-4 좋은 객체 지향 설계 5가지 원칙 (SOLID)
Single Responsibility Principle
open/closed principle 중요
MemberRepository m = new MemoryMemberRepository(); // 기존 코드
MemberRepository m = new JdbcMemberRepository(); // 변경 코드
Liskov Substitution Principle
Dependency Inversion Principle
EX) 문제 상황
MemberService는 memberRepository (인터페이스) 뿐만 아니라, JdbcMemberRepository (클래스) 도 인지하는 상태이다.
=> DIP를 위반한 상태
=> 어떻게 해결해야 하나?
<정리>
1.5 객체 지향 설계와 스프링
.bean 등록
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
// 자바를 통한 bean 등록
GenericXmlApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
// xml를 통한 bean 등록
@Test
@DisplayName("빈 설정 메타 정보 확인")
void findApplicationBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
System.out.println("beanDefinitionNAME = " + beanDefinitionName +
" beandefinition" + beanDefinition);
}
}
}
}
beanDefinitionNAME = appConfig beandefinitionGeneric bean: class [hello.core.AppConfig$$EnhancerBySpringCGLIB$$ae35904e]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null
beanDefinitionNAME = memberService beandefinitionRoot 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.AppConfig
beanDefinitionNAME = memberRepository beandefinitionRoot 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.AppConfig
beanDefinitionNAME = orderService beandefinitionRoot 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.AppConfig
beanDefinitionNAME = discountPolicy beandefinitionRoot 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.AppConfig
beanDefinitionNAME = memberService beandefinitionGeneric bean: class [hello.core.member.MemberServiceImpl]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [appConfig.xml]
beanDefinitionNAME = orderService beandefinitionGeneric bean: class [hello.core.order.OrderServiceImpl]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [appConfig.xml]
beanDefinitionNAME = memberRepository beandefinitionGeneric bean: class [hello.core.member.MemoryMemberRepository]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [appConfig.xml]
beanDefinitionNAME = discountPolicy beandefinitionGeneric bean: class [hello.core.discount.RateDiscountPolicy]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [appConfig.xml]
public class orderService {
private int price;
// price의 상태가 유지되늰 stateful 방식 구현
public void getPrice(String name, int price) {
this.price = price;
}
public int getPrice() {
return price;
}
}
----------------------------------------------------------------
public class orderServiceTest {
int order1Price = statefulService1.order("A", 10000);
int order2Price = statefulService2.order("B", 20000);
int price = statefulService1.getPrice();
System.out.println("price = " + price);
// price = 20000
}
public class orderService {
private int price;
// stateless 방식 구현
public int getPrice(String name, int price) {
return price;
}
public int getPrice() {
return price;
}
}
----------------------------------------------------------------
public class orderServiceTest {
int order1Price = statefulService1.order("A", 10000);
int order2Price = statefulService2.order("B", 20000);
int price = statefulService1.getPrice();
System.out.println("price = " + price);
// price = 10000
}
@Test
void configurationDeep() {
ApplicationContext ac= new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean);
//bean = hello.core.AppConfig$$EnhancerBySpringCGLIB$$e68e7158@710b18a6
// bean = ??? 클래스맞음?
}
스프링은 cglib라는 바이트코드 조작 라이브러리를 통해 한 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한다.
클래스#cglib는 스프링 컨테이너에 객체가 없다면 기존 로직과 동일하게 new 를 통해 스프링 컨테이너 등록하지만 객체가 있다면 스프링 컨테이너에서 찾아서 반환함.
AppConfig 내 @Configuration 없고 @Bean만 존재한다면 스프링 빈 설정은 되지만 싱글톤 자동 구현이 되지 않는다.
(마치 싱글톤을 구현하지 않은 자바 소스와 동일하다..)
그냥 설정 정보 있는 곳에는 @Configuration을 필수적으로 사용하자.
- @Component(해당 클래스) : config 설정없이 자동으로 bean 등록해준다.
- @Autowired(생성자 메서드) : (Spring bean 안에서 인자의 Type과 동일한 클래스를 찾아) 자동으로 의존관계를 주입해준다.
(== ac.getBean(클래스이름.class))- spring bean에 등록된 클래스를 출력해주는 @ComponentScan 을 활용해 빈 등록 여부와 의존관계 형성 여부를 확인할 수 있음.
@Component("빈 등록 이름")
public class CarServiceImpl implements CarService {
~~~
}
// name이 설정되어있지 않다면 클래스명의 맨 앞 글자만 소문자로 바뀐 이름이 빈에 등록된다.
// (ex) carServiceImpl
@Component("service")
public class serviceA {}
---------
@Compoent("service")
public class serviceB {}
- result
: BeanDefinitionStoreException: Failed to parse configuration class [hello.core.AutoAppConfig]
// 자동 bean 등록
@Component("service")
public class serviceA {}
---
// 수동 bean 등록
@Configuration
public class AppConfig {
@Bean(name = "service")
public ServiceA serviceA() {
return new xxxServiceA();
}
}
- result : 문제 없음
- 스프링 부트로 사용 시, 자동 빈 등록과 수동 빈 등록이 충돌나면 오류가 발생하도록 기본 값을 바꾸었다.
-> 빈등록 충돌에 대해 정확히 인지하지 못할 가능성이 크고 추후에 더 큰 문제 발생의 가능성이 있기 때문.
- 생성자 호출 시 딱 1번만 호출되는 것이 보장된다.
- '불변', '필수' 의존관계에 사용
"불변"
set method를 통해 생성자를 변하게 만들지 않는다.
"필수"
// final 로 설정되어 있으므로 생성자 값이 1개 명시되어야 함을 의미
private "final" MemberRepository memberRepository;
@Autowired
public AServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
- 선택적으로 의존관계 주입
- 변경 가능성이 있는 의존관계에 사용
- 문제점
: ex) 이후 순수한 자바코드 테스트를 하고 싶을 때, 각 service에서 활용할 dao, repository에 대한 구현객체를 넣을 수 있는 방법이 없다.
-> 결국 set method를 통해 바꿔야 한다. (이런 경우 수정자 주입을 하는게 낫다.)
동일한 자바 빈이 2개 이상 설정되어 있는 경우
: 한 서비스 클래스에서 스프링 빈 안의 구현체들을 사용하고자 할 때, 동일한 인터페이스로 만들어진 구현체 클래스들은 동일한 빈 이름을 가지므로 exception이 발생하게 된다.
(동일한 이름을 가진 두 개의 자바 빈을 만들려고 하기 때문에, 스프링 컨테이너는 이를 거부하고 exception을 발생시킴.)
=> 해결방법 ? - 3가지 존재
1) @Quilifier끼리 매칭 ex) @Quilifier("A_Repository") -> A_Repository type의 클래스 DI
2) 빈 이름 매칭, A_Repository 라는 이름을 갖는 클래스 DI
3) 2)도 실패하면 NoSuchBeanDefinitionException 발생