Chapter 1. 본격적인 Spring 시작!!
지난 주차에 자바에 대해서 간단하게 학습했다.
이번 주차에는 본격적으로 Spring에 대해 학습했다.
팀원들과 함께 Spring으로 간단한 CRUD가 가능한 프로젝트를 만들어보며 스프링에 대한 이론적인 부분과 사용법에 대해 익혔다.
이번 WIL에서는 Spring과 관련된 이론적인 부분을 정리해보고자 한다.
Chapter 2. Spring의 기본 철학
위 그림은 Spring 삼각형이라고 한다.
이 삼각형은 Spring의 기본 철학을 정리한 것인데 DI(Dependency injection), AOP, Portable Sericve abstractions, 그리고 가운데 POJO가 있다.
오늘은 이 중에서 DI에 대해 정리하고자 한다.
Chapter 3. Dependency - 의존성
의존성이란 개념은 확실히 사람한테 해로운(?) 개념인 거 같다.
Spring은 정확히 말하자면 Web framework가 아니라 DI framework다.
다른 언어에는 Inversify(Node js), Dependency Injector(Python)이 있는데 도데체 이 DI가 무엇이길래 모든 언어에서 DI Framework를 만드는 것일까
의존성, Dependency는 두 모듈, 또는 두 클래스 간의 관계를 뜻하며 상위 객체가 하위 객체를 사용하는 것을 뜻한다.
위와 같은 관계가 만들어지면 "A는 B에 의존성을 가지고 있다"라고 표현할 수 있다.
Chapter 4. 의존성이 왜 중요한가??
이 의존성이 왜 중요한 것이냐면, 어떠한 객체가 변경이 되면, 그 객체에 의존성을 가지고 있는 상위 객체 모두 변경될 수 있다.
예를 들어 아래와 같이 객체들이 의존성을 가지고 있다면, D가 변경됐을 때, A, B, C 모두가 변경된다.
프로그래밍을 할 때 중요한 것은 바로 책임의 분리이며, 객체 변경이 타 객체에 영향을 줘서는 안된다!!!!!
그렇기 때문에 이렇게 의존성을 관리해주지 않으면 D를 다른 객체로 변경할 때, A B C가 수정되는 것처럼 수정이 많아진다(경우에 따라서 3개가 아니라 10여개가 될 수 있다.)
예를 들어 D를 E라는 객체로 수정해야하는 경우 다음과 같이 수정해야 한다.
(이때 D와 E는 같은 interface를 상속받아서 대체가 가능하다.)
// Before
class A{
D d;
public A(){
this.d = new D();
}
}
class B{
D d;
public B(D d){
this.d = new D();
}
}
class C{
D d;
public C(D d){
this.d = new D();
}
}
class D{
public D(){
}
}
//After
class A{
D d;
public A(){
this.d = new E();
}
}
class B{
D d;
public B(D d){
this.d = new E();
}
}
class C{
D d;
public C(D d){
this.d = new E();
}
}
class E{
public E(){
}
}
위 방식대로 하면 D와 의존성이 있는 모든 객체를 수정해야한다.
그렇다면 어떻게 해야할까???
Chapter 5. DI - 의존성 주입
바로 최상위 객체,(예를 들면 메인 객체)에서 객체를 만들어서 생성자 파라미터로 넘겨주는 방식이며 이를 의존성 주입이라 한다.
의존성 주입의 정의는 "객체 생성 부분과 사용 부분을 분리하여 개발자가 비지니스 로직에 집중할 수 있도록 하는 방식"이다.
우선 예시를 살펴보도록 하자.
Main에서 각 클래스의 생성을 만들어서 생성자 파라미터로 넘겨주면 위와 같이 구현할 수 있다.
코드 예시
// Before
class A{
D d;
public A(D d){
this.d = d;
}
}
class B{
D d;
public B(D d){
this.d = d;
}
}
class C{
D d;
public C(D d){
this.d = d;
}
}
class D{
public D(){
}
}
public static void main(string[] args){
D d = new D();
A a = new A(d);
B b = new B(d);
C c = new C(d);
}
// After
class A{
D d;
public A(D d){
this.d = d;
}
}
class B{
D d;
public B(D d){
this.d = d;
}
}
class C{
D d;
public C(D d){
this.d = d;
}
}
class D{
public D(){
}
}
class E{
public E(){
}
}
public static void main(string[] args){
E e = new E();
A a = new A(e);
B b = new B(e);
C c = new C(e);
}
이처럼 코드를 생성하는 부분에 단 한번만 수정하면 된다.
(물론 다시 말하지만 D와 E는 같은 인터페이스를 상속받아야 한다.)
의존성 주입을 활용하면 객체의 생성은 main에서 처리하며 각 객체에서는 의존성을 고려하지 않고 비지니스 로직만 신경써서 구현하면 된다.
Chapter 6. IoC Container
이 의존성 주입 덕분에 개발자는 자신이 맡은 객체 구현에만 신경쓰면 된다. 그러나 프로젝트 규모가 커지면서 객체가 많아짐에 따라 이 객체 생성 부분을 관리하는 것도 어려워졌다.
예를 들어 프로젝트에 객체가 100개가 있으면 이 main 클래스에는 객체 생성 부분만 100줄이 넘게 된다.
그래서 DI Framework - IoC Container를 만들어서 프레임워크 레벨에서 이 객체 생성을 다루기로 했다.
즉 쉽게 얘기하면 우리가 작성했던 main 부분을 프레임워크가 자동으로 작성해준다는 것이다.
Before
After
Chapter 7. Bean
그렇다면 Spring은 이 main 문을 어떻게 작성할까??
우선 개발자는 자신이 작성한 클래스는 Bean 어노테이션을 통해서 ApplicationContext에 Bean으로 등록한다. 그리고 Spring Framework는 Bean으로 등록된 객체가 호출될 경우, ApplicationContext에서 가져와서 넘겨준다.
여기서 Bean이라는 개념이 등장하는데 Bean이 무엇인가??
Bean은 Spring framework에서 관리하는 객체 단위로 개발자가 @Bean이나 @Component 어노테이션으로 framework에게 Bean으로 등록해달라고 요청을 하면 시작할 때, 등록된 모든 객체를 생성하여 의존성을 주입해준다.
위 그림에서 개발자는 @Component를 통해 B, C, D, E, F를 Bean으로 등록했으며, A 객체에서 B, C, D, E, F를 호출할 경우 자동으로 주입해준다.
이때 A 객체에서는 @Autowired를 통해서 주입받고자 하는 객체를 정의해줘야 한다.
@component
class A{
@Autowired
B b;
@Autowired
C c;
@Autowired
D d;
@Autowired
E e;
@Autowired
F f;
}
@component
class B{
}
@component
class C{
}
@component
class D{
}
@component
class E{
}
@component
class F{
}
Chapter 8. 더 나아가기 - Singleton
위 내용만 이해할 수 있으면 Spring의, 아니 DI framework의 대부분을 이해할 수 있다.
다면 대부분의 DI framework들은 이 Bean 객체를 Singleton으로 구현한다.
Singleton이란 하나의 클래스에 하나의 객체만 생성 가능하며, 객체 생성을 요청했는데 이미 생성된 객체가 있을 경우, 기존 객체를 재사용하는 것을 뜻한다.
특히 Spring에서는 이 싱글톤 객체가 중요한데 Spring은 요청이 들어오면 하나의 Thread를 만들어서 처리한다. 이때 모든 Tread에서 Bean 객체를 만들게 되면 메모리 낭비가 심해진다.
따라서 Bean 객체를 싱글톤으로 만들고 모든 Thread에서 이 객체를 사용할 수 있도록 하는 것이 더 메모리를 효율적으로 쓸 수 있게 한다.
Singleton 예시
public class Singleton {
private Singleton() {}
private static class InnerClz {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return InnerClz.INSTANCE;
}
}
Chapter 9. 3주차 끝!!!!!
드디어 3주차가 끝이 났다. 이번 주차에선 Spring의 기초에 대해 알아봤다. Spring은 몇년 간 꾸준히 인기가 많았고 그만큼 발전되어 온 프레임워크다. 그만큼 그 프레임워크의 이론적 내용이 너무나 많다.
오늘 정리한 의존성 주입 부분도 전체의 극히 일부일 뿐이며 전체 Spring 철학에서 의존성 주입 그 자체도 매우 매우 작은 부분이다.
앞으로 남은 항해를 해나가면서 조금 더 Spring에 대해 알아가보려고 한다.
(무엇보다 오늘 정리해야할 내용이 많아서 원피스 짤을 많이 못쓴 게 너무 아쉽다.)