- 목표
- 스프링을 사용하는 이유에 대해 설명할 수 있다.
- 스프링 어플리케이션 컨텍스트, IoC, DI 장단점을 설명할 수 있다..
- 스프링 빈의 생명주기와 콜백을 설명할 수 있다.
Q. Spring을 사용하지 않고 서버를 개발하면 어떻게 될까?
- 미들웨어에 종속적인 환경이된다.
- 배포할때 많은 양의 XML을 작성해야 한다.
- 엔터프라이즈이기때문에 비용 문제도 있다.
- 불필요하게 EJB 객체를 상속받아야 한다.
Java로 서버를 만들기 위해서는 J2EE의 서블릿 및 JSP를 이용한다.
J2EE는 실제 사용하는데 불편함이 많았다.
- ORM 기능 중 order by를 미제공 => 미들웨어 제품에서 직접 구현 => 미들웨어에 종속적인 환경이된다.
- 배포할때 많은 양의 XML를 작성
- 엔터프라이즈이기때문에 비용 문제도 있다.
- 불필요하게 EJB 객체를 상속받아야 하는 등 애플리케이션이 무거워지거나 개발이 어려워지는 단점이 발생
Q. Spring을 왜 사용할까?
- Spring makes programming Java quicker, easier, and safer for everybody.
- EJB 개발보다 간편하고 가볍다
- 벤더에 종속적이지 않고, 추상화를 이용하여 같은 종류지만 여러 방식의 기술들의 전환이 자유롭다(MySQL, Mongo)
- IoC, DI를 이용하여 결합도가 약하다
- 자바 객체인 POJO(Plain Old Java Object) 객체를 사용하여 개발
- EJB 객체를 상속받지 않음.
- Tomcat 같은 일반 서블릿 컨테이너에서도 동작하므로 비싼 엔터프라이즈 서버를 구매하지 않아도 된다.
- EJB보다 훨씬 간편한 방식으로 선언적 트랜잭션 및 보안 처리, 분산 환경 지원 등 주요 기능을 모두 사용
- 벤더에 종속적이지 않다.
- 자바EE 서버 제품에 특화된 설정을 따로 공부 필요 X
- 하거나 서버 제품을 바꿀 때마다 포팅 작업이 필요없음
- IoC을 통해 결합도를 약하게 해준다.
- AOP : 로깅, 세션, 인증, 인가, JDBC 커넥션 등 작업의 전후에서 실행되는 반복적인 작업을 AOP(Aspect Oriented Programming) 기술을 사용하여 코드를 간결하게 만들어 준다.
- PSA : 같은 종류지만 여러 방식의 기술들을 추상화
- JDBC Connection, JTAUserTransaction, HibernateTransaction과 같이 여러 방식에 따라 사용하는 객체가 달랐는데 이를 추상화하였다.
3. Ease of Use and Learning Curve:
extensive documentation, vibrant community, and the availability of many online resources make it relatively straightforward for developers to learn and adopt.
4. Community and Support:
Spring has a large and active community, which means that developers can find plenty of third-party libraries, extensions, and support forums for problem-solving.
6. Microservices:
Spring: Spring Boot, part of the Spring ecosystem, is particularly well-suited for building microservices due to its ease of setup and the ability to embed application servers.
Q. IoC를 이용하여 객체 간의 결합도가 줄면 무슨 장점이 있는가?
- O(Open/Closed Principle) : 기능 추가는 열림. 기능 변경은 닫힘. => Interface, SuperClass 추상화 잘됐음
- D(Dependency Inversion Principle) : high-level module은 low-level module 구현으로부터 독립된다.
인터페이스나 추상클래스 등을 이용하여 낮은 결합도를 유지한다.
결합도가 낮다 => 변경시에 영향도가 적다.
결합도가 높다 => 코드 수정시 여러 클래스에 영향을 줄 수 있다.
개인적으로 즐겨했던 코드가 Repository를 다른 서비스에서 직접참조하지 않고 Service로 래핑해서 사용
=> Repository를 다른 서비스에서 직접 이용하지 못하게 만듦
=> 공통적으로 처리하기 위한 (ex. supported)등의 코드가 한곳에 집중되고 다른 서비들에게 제공되는 데이터들은 가공된 채 돌아다닐 수 있다. 그리고 테스트도 쉬워진다.
Open/Closed Principle : 결합도가 높은 코드 1 : 직접 참조 => 코드 재활용 불가능
class CoffeeMachine {
private BasicCoffeeExtractor extractor;
public String extract() {
return extractor.extract();
}
}
class BasicCoffeeExtractor {
public String extract() {
return "Extracted From BasicCoffeeExtractor";
}
}
위와 같은 코드는 CoffeeMachine과 BasicCoffeeExtractor와 강결합되어 있다.
만약 다른 CoffeeExtractor가 생긴다면 비슷한 일을 하는 extract() 함수가 똑같이 만들어져 중복이 될 것이다.
class CoffeeMachine {
private AbstractCoffeeExtractor extractor;
public String extract() {
return extractor.extract();
}
}
class BasicCoffeeExtractor extends AbstractCoffeeExtractor {
public String extract() {
return "Extracted From BasicCoffeeExtractor";
}
}
class PremiumCoffeeExtractor extends AbstractCoffeeExtractor {
public String extract() {
return "Extracted From PremiumCoffeeExtractor";
}
}
위와 같이 추상화를 이용하여 비슷한 일을 하게 되는 경우 코드를 재활용 가능하다.
결합도가 높은 코드 2 : 직접 참조 => 확장 어려움
커피 원두에 따라 CoffeeExtractor를 고르는 코드를 만들어보자.
class CoffeeMachine {
private BasicCoffeeExtractor basicCoffeeExtractor;
private PremiumCoffeeExtractor basicCoffeeExtractor;
public CoffeeExtractor getCoffeeExtractor(bean : AbstractBean) {
bean.getClass().getSimpleName()
switch(bean.getClass().getSimpleName()) {
case "basicBean":
return basicCoffeeExtractor;
break;
case "premuimBean"
return premiumCoffeeExtractor;
break;
}
}
}
class BasicCoffeeExtractor extends AbstractCoffeeExtractor {
public String extract() {
return "Extracted From BasicCoffeeExtractor";
}
}
class PremiumCoffeeExtractor extends AbstractCoffeeExtractor {
public String extract() {
return "Extracted From PremiumCoffeeExtractor";
}
}
클래스 이름을 확인하는 방법도 어렵다. (getClass() or instance of 사용)
class CoffeeMachine {
private BasicCoffeeExtractor basicCoffeeExtractor;
private PremiumCoffeeExtractor basicCoffeeExtractor;
public CoffeeExtractor getCoffeeExtractor(bean : AbstractBean) {
return bean.getExtractor()
}
}
class BasicCoffeeExtractor extends AbstractCoffeeExtractor {
public String extract() {
return "Extracted From BasicCoffeeExtractor";
}
}
class PremiumCoffeeExtractor extends AbstractCoffeeExtractor {
public String extract() {
return "Extracted From PremiumCoffeeExtractor";
}
}
abstract class AbstractBean {
abstract public AbstractCoffeeExtractor getExtractor()
}
class BasicBean : AbstractBean {
private AbstractCoffeeExtractor extractor;
public AbstractCoffeeExtractor getExtractor() {
return this.extractor
}
}
class PremiumBean : AbstractBean {
private AbstractCoffeeExtractor extractor;
public AbstractCoffeeExtractor getExtractor() {
return this.extractor
}
}
- 코드가 훨씬 깔끔하다.
- 추후에 Bean으로 공통처리하기도 좋다.
Q. 스프링 빈의 생명주기와 콜백란 무엇인가?
컨테이너 생성 -> 빈 생성 -> Dependency 주입 -> @PostConstruct 콜백 메소드 -> 어플리케이션 구동 -> @PreDestroy 호출 -> 스프링 종료
컨테이너 생성
-> 빈 생성
-> Dependency 주입
-> @PostConstruct 호출
-> 어플리케이션 구동
-> @PreDestroy 호출
-> 스프링 종료
1. Spring Application Context라는 Container 생성
2. Container는 Component-Sacn을 통해 Bean 등록 (싱글톤)
3. Bean 객체 생성 및 의존성 주입
- 생성자 주입 방식 : 객체의 생성 + 의존관계 주입 => 자바의 생성자를 이용하기때문
- Setter, Field 주입 방식 : 객체의 생성 -> 의존관계 주입 => setter 이용. lazy init 가능
- 생성자가 더 선호되는 이유 : NPE, 컴파일타임 오류 감지
4. 초기화 콜백 메소드 호출
5. 사용
6. 소멸 전 콜백 메소드 호출
7. 스프링 종료
Q. Bean들은 왜 싱글턴 패턴을 가질까? 장/단점은 무엇인가?
여러개를 만들 필요가 없다.
- 요청 1개당 1개의 인스턴스를 만들면 힙 메모리가 아깝다.
- 이미 생성된 객체를 재활용할 수 있도록 컨텍스트가 주입을 통해 도와준다.
단점
Thread Safe 하지 않다. => Thread Safe하지 않은 코드를 넣지 말자
= 객체가 상태를 가지면 안된다. (Stateless)
public class SingletonTest {
//객체를 요청할 때 마다 객체를 새로 생성하는 문제점을 가진다
@Test
@DisplayName("스프링이 없는 순수한 DI 컨테이너")
void pureContainer() {
AppConfig appConfig = new AppConfig();
//1. 조회 : 호출 할 때마다 객체를 생성(문제점)
MemberService memberService1 = appConfig.memberService();
MemberService memberService2 = appConfig.memberService();
//참조값이 다른것을 확인
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
//memberService1 != memberService2
assertThat(memberService1).isNotSameAs(memberService2);
}
}
코드 출처 : https://hongchangsub.com/springcore5/