본문 바로가기
[인프런] 김영한의 스프링 완전 정복/0. 스프링 핵심 원리 - 기본편

[스프링 핵심 원리 - 기본편] 1. 스프링에 핵심기술에 대한 이해

by NJ94 2023. 11. 30.

출처

https://www.inflearn.com/course/%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

 

스프링 핵심 원리 - 기본편 강의 - 인프런

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢

www.inflearn.com

 

 

어떤 프로그래밍 언어라도 해당 언어에 핵심기술에대한 이해도가 높으면

언어를 바라보는 관점과 배움의 속도가 다르다. 

 

Spring

https://spring.io/

 

Spring | Home

Cloud Your code, any cloud—we’ve got you covered. Connect and scale your services, whatever your platform.

spring.io

 

spring initializr

https://start.spring.io/

 

Spring 역사


Expert One-on-One J2EE Design and Development

 

 

Spring은 자바 기반의 오픈 소스 애플리케이션 프레임워크로, 엔터프라이즈급 자바 애플리케이션을 개발하기 위한 다양한 기능과 도구를 제공합니다. 이 프레임워크는 2002년에 로드 존슨(Rod Johnson)이 자신의 저서 "Expert One-on-One J2EE Design and Development"에서 제안한 아이디어를 기반으로 만들어졌습니다. 초기에는 Interface21이라는 회사에서 개발되었으며, 2004년부터 오픈 소스로 공개되었습니다.

 

로드 존슨의 "J2EE Design and Development" 책에서는 300줄이 넘는 코드로 구성된 간단한 비즈니스 로직을 처리하기 위해 J2EE(Java 2 Platform, Enterprise Edition)의 복잡성과 불편한 점을 지적하며, 이로 인해 개발 생산성이 저하된다고 지적했습니다.

이러한 문제점에 대해 반발하여 Spring 프레임워크를 만든 사람은 로드 존슨(Rod Johnson)입니다. 로드 존슨은 EJB(Enterprise JavaBeans)를 비롯한 J2EE의 불편한 점들을 해결하고 개발자들이 보다 간편하고 효율적으로 엔터프라이즈 애플리케이션을 개발할 수 있도록 돕기 위해 Spring 프레임워크를 제안하고 만들었습니다.

따라서 Spring 프레임워크는 초기에 J2EE의 복잡성을 해결하고 개발 생산성을 향상시키기 위해 만들어진 프레임워크 중 하나입니다. 그리고 로드 존슨의 이러한 제안과 노력으로 Spring은 오늘날 엄청난 인기를 얻게 되었으며,

 

스프링 버전


  1. Spring 1.x: 초기 버전으로, 핵심적인 기능을 제공하면서도 경량성과 테스트 용이성을 강조했습니다.
  2. Spring 2.x: AOP, JDBC, 트랜잭션 관리 등을 개선하고, XML 구성을 통한 빈 설정 등이 추가되었습니다.
  3. Spring 3.x: 자바 5 이상을 지원하며, 애노테이션 기반의 설정과 개발이 가능해졌습니다. 이를 통해 XML보다 더 간결한 설정이 가능해졌습니다.
  4. Spring 4.x: 자바 8과 호환되며, 람다 표현식과 자바 8의 기능을 활용할 수 있게 되었습니다. 또한, WebSocket 및 HTML5를 지원하는 등의 기능이 추가되었습니다.
  5. Spring 5.x: 리액티브 프로그래밍 지원을 강화하고, 코틀린(Kotlin)과의 통합을 개선하였습니다. 또한, 모듈화와 빠른 속도의 업데이트가 이뤄졌습니다.

 

J2EE ?


J2EE는 Java 2 Platform, Enterprise Edition의 약자로, 자바 기반의 엔터프라이즈 애플리케이션을 개발하고 구축하기 위한 플랫폼입니다. J2EE는 엔터프라이즈급 애플리케이션을 개발하기 위한 다양한 기술과 스펙을 포함

 

  1. Servlets: 동적인 웹 페이지를 생성하는데 사용되는 자바 프로그래밍 기술입니다. HTTP 요청 및 응답을 처리하는 서버 측 프로그램을 작성할 수 있도록 지원합니다.
  2. JSP(JavaServer Pages): HTML 내에 자바 코드를 삽입하여 동적 웹 페이지를 생성하는 기술로, Servlet과 함께 사용되며 웹 애플리케이션의 프레젠테이션 계층을 구현하는 데 사용됩니다.
  3. EJB(Enterprise JavaBeans): 엔터프라이즈 애플리케이션의 비즈니스 로직을 개발하고 관리하기 위한 서버 측 컴포넌트 모델입니다. 데이터 처리, 트랜잭션 관리 등을 수행할 수 있습니다.
  4. JPA(Java Persistence API): 객체 관계 매핑(ORM)을 위한 자바 API로, 관계형 데이터베이스와 객체를 매핑하여 데이터를 영구적으로 저장하고 관리하는 기술을 제공합니다.
  5. JMS(Java Message Service): 애플리케이션 간의 비동기적인 메시지 송수신을 지원하는 API로, 분산 시스템에서 메시지 기반의 통신을 위해 사용됩니다.
  6. JTA(Java Transaction API): 트랜잭션 관리를 위한 API로, 여러 리소스간의 트랜잭션을 조정하고 제어하는 데 사용됩니다.
  7. JNDI(Java Naming and Directory Interface): 네이밍 및 디렉터리 서비스에 접근하기 위한 API로, 분산 환경에서의 객체 검색과 관리를 지원합니다.

 

J2EE 단점


J2EE(지금은 Jakarta EE로 명칭이 변경되었습니다)는 엔터프라이즈급 애플리케이션을 개발하는데 많은 유용한 기술과 API를 제공합니다. 그러나 몇 가지 제한과 단점이 있어서 Spring과 같은 프레임워크가 선택되고 있습니다. 이러한 단점은 다음과 같습니다:

 

  1. 복잡한 설정과 제한된 유연성: J2EE 애플리케이션은 XML 기반의 설정이 많이 필요했고, 이는 복잡성을 증가시켰습니다. 또한, EJB(Enterprise JavaBeans)을 사용하는 경우에는 컨테이너 관리 빈에 대한 제약이 있었고, 이로 인해 유연성이 떨어졌습니다.
  2. 무겁고 느린 배포 및 실행: J2EE 애플리케이션은 애플리케이션 서버에 배포해야 했고, 이는 느린 배포와 무거운 애플리케이션 서버의 실행을 야기했습니다.
  3. 컴포넌트 간 상호 의존성: EJB 컴포넌트들 간의 강력한 상호 의존성은 유연성을 감소시키고 테스트하기 어렵게 만들었습니다.
  4. 설정의 복잡성: J2EE에서는 많은 설정 파일과 XML을 사용하여 애플리케이션을 구성해야 했는데, 이는 오류가 발생하기 쉽고, 유지보수가 어려웠습니다.
  5. 높은 학습 곡선: J2EE의 다양한 기술과 API를 사용하려면 많은 학습과 경험이 필요했습니다. 특히 EJB의 경우 복잡한 스펙과 사용법으로 인해 개발자들이 접근하기 어려웠습니다.
Spring 프레임워크는 이러한 J2EE의 단점을 극복하고자 설계되었습니다.

 

Spring은 경량화되어 있으며, 간결한 설정과 자바 객체 간의 의존성 주입(Dependency Injection)을 통해 복잡성을 낮추고 유연성을 높입니다. 또한, Spring은 더 빠른 개발과 테스트가 가능하도록 도와주고, 복잡한 설정보다는 애노테이션 기반의 간단한 구성을 지향합니다. 이로 인해 개발자들은 더 빠르게 애플리케이션을 개발하고 유지보수할 수 있게 되었습니다.

 

Spring 기술


 

https://spring.io/projects

 

Spring | Projects

Projects From configuration to security, web apps to big data—whatever the infrastructure needs of your application may be, there is a Spring Project to help you build it. Start small and use just what you need—Spring is modular by design.

spring.io

구성부터 보안, 웹 앱, 빅 데이터까지 애플리케이션의 인프라 요구 사항이 무엇이든 Spring 프로젝트를 통해 이를 구축할 수 있습니다. 작게 시작하여 필요한 것만 사용하세요. Spring은 모듈식으로 설계되었습니다.

 

  1. 의존성 주입(Dependency Injection, DI): 객체 간의 의존성을 낮추고 유지보수 및 테스트를 용이하게 하는 핵심 기능입니다. XML 또는 애노테이션을 사용하여 의존성을 주입할 수 있습니다.
  2. AOP(Aspect-Oriented Programming): 관점 지향 프로그래밍을 통해 핵심 비즈니스 로직과 횡단 관심 사항을 분리하여 개발하는 기술입니다. Logging, 트랜잭션 처리 등을 관점으로 분리하여 적용할 수 있습니다.
  3. Spring MVC: Model-View-Controller 아키텍처를 기반으로 하는 웹 애플리케이션을 개발할 때 사용되는 모듈입니다. HTTP 요청을 처리하고 응답을 생성하는 데 사용됩니다.
  4. Spring Boot: 애플리케이션을 빠르게 구축하고 실행하기 위한 도구로, 별도의 설정 없이 쉽게 개발을 시작할 수 있도록 도와줍니다.
  5. Spring Data: 데이터 액세스 기술을 추상화하고, NoSQL 및 관계형 데이터베이스에 대한 통합 지원을 제공합니다.
  6. Spring Security: 보안 기능을 제공하여 인증(Authentication)과 권한 부여(Authorization)를 관리하고 웹 애플리케이션의 보안을 강화합니다.
  7. Spring Integration: 다양한 시스템 및 프로토콜 간의 메시지 기반 통합을 위한 프레임워크로, 엔터프라이즈 애플리케이션의 통합을 지원합니다.
  8. Spring Batch: 대용량 데이터 처리 및 배치 작업을 수행하는데 사용되며, 대용량 데이터를 효율적으로 처리할 수 있는 기능을 제공합니다.
  9. Spring Cloud: 마이크로서비스 아키텍처를 위한 도구와 라이브러리를 제공하여 클라우드 기반 애플리케이션을 구축 및 관리할 수 있도록 합니다.
  10. Spring Testing: 단위 테스트, 통합 테스트 및 기능 테스트를 지원하는 모듈로, JUnit 또는 Mockito와 같은 테스트 프레임워크와의 통합을 제공합니다.

 

객체 지향 프로그래밍이란 ?


객체 지향 프로그래밍(Object-Oriented Programming, OOP)은 소프트웨어를 모듈화하고, 코드의 재사용성을 높이며, 유지보수를 용이하게 하는 프로그래밍 패러다임 중 하나입니다. 객체 지향 프로그래밍은 다음과 같은 주요 개념을 기반으로 합니다:

 

  1. 클래스(Class)와 객체(Object): 클래스는 데이터와 해당 데이터를 조작하는 메서드(함수)를 함께 묶어 놓은 것이며, 객체는 이러한 클래스의 인스턴스입니다. 클래스는 객체를 만들기 위한 설계도와 같은 역할을 하며, 객체는 실제로 메모리에 할당되어 사용되는 것입니다.
  2. 캡슐화(Encapsulation): 데이터와 해당 데이터를 조작하는 메서드들을 하나의 단위로 묶어 외부에서의 직접적인 접근을 제어하는 개념입니다. 이를 통해 데이터를 보호하고 객체의 내부를 숨기고 외부에서는 객체의 메서드를 통해 상호작용할 수 있습니다.
  3. 상속(Inheritance): 부모 클래스의 특성과 기능을 자식 클래스가 물려받는 개념으로, 코드의 재사용성을 높이고 계층적인 관계를 형성할 수 있습니다.
  4. 다형성(Polymorphism): 여러 개의 클래스가 동일한 이름의 메서드를 가질 수 있고, 각각의 클래스에서 이를 다르게 구현할 수 있는 기능입니다. 메서드 오버라이딩(Override)과 메서드 오버로딩(Overloading)을 통해 구현됩니다.
  5. 추상화(Abstraction): 복잡한 시스템에서 필요한 부분만 추출하여 모델링하는 것을 의미합니다. 즉, 객체가 가진 중요한 특성과 행동을 간추려 나타내는 것을 말합니다.

 

객체 지향 프로그래밍은 현실 세계를 모델링하여 소프트웨어를 개발하는 방식으로, 코드의 가독성과 유지보수성을 높이고, 개발 과정에서의 생산성을 향상시킬 수 있는 장점을 가지고 있습니다. 대부분의 현대 프로그래밍 언어들은 객체 지향 프로그래밍을 지원하고 있으며, 이 개념들을 적절히 활용하여 소프트웨어를 개발하는 것이 일반적입니다.

 

다형성 ?


다형성(Polymorphism)은 객체 지향 프로그래밍에서의 중요한 개념으로, 하나의 인터페이스나 메서드가 다양한 형태로 동작할 수 있는 능력을 의미합니다. 이것은 객체가 여러 형태를 가질 수 있음을 나타내며, 코드의 재사용성과 유연성을 증가시키는데 중요한 역할을 합니다.

 

다형성(Polymorphism)은 객체 지향 프로그래밍에서 역할과 구현을 분리하여 코드를 설계하고 작성하는 개념 중 하나

 

"역할과 구현으로 구분한다(Role and Implementation Separation)"라고 설명

 

  1. 역할(Role): 소프트웨어 설계 시, 객체들이 수행하는 기능이나 행위를 나타내는 것을 의미합니다. 이는 인터페이스나 추상 클래스 등을 통해 정의됩니다. 역할은 어떤 특정한 동작이나 기능에 대한 추상적인 정의를 나타내며, 구현 방식에 대한 세부 사항을 포함하지 않습니다.
  2. 구현(Implementation): 역할에 대한 실제 구현을 말합니다. 클래스가 이러한 역할을 구현하여 실제 동작을 수행하게 됩니다. 이때, 한 개체는 여러 가지 역할을 수행할 수 있으며, 이러한 역할들은 해당 객체가 구현한 인터페이스나 추상 클래스에 따라 정의됩니다.

 

예시로, 동물의 소리를 내는 행위를 역할로 정의하고, 이를 구체적으로 구현하는 개별 동물 클래스들이 있을 수 있습니다.

 

// 역할(Role)을 정의한 인터페이스
interface Animal {
    void makeSound();
}

// 구현(Implementation)된 클래스들
class Dog implements Animal {
    public void makeSound() {
        System.out.println("멍멍!");
    }
}

class Cat implements Animal {
    public void makeSound() {
        System.out.println("야옹!");
    }
}

class Duck implements Animal {
    public void makeSound() {
        System.out.println("꽥꽥!");
    }
}

 

위의 예시에서 Animal 인터페이스가 역할을 정의하고, 각 구현 클래스(Dog, Cat, Duck)가 해당 역할을 구현하고 있습니다.

 

이렇게 역할과 구현을 분리함으로써, 유연하고 확장 가능한 코드를 작성할 수 있으며, 다형성의 개념을 적극적으로 활용

 

SOLID 원칙


SOLID는 객체 지향 프로그래밍에서 디자인 원칙을 설명하는 다섯 가지의 기본 원칙을 나타냅니다. 이 원칙들은 소프트웨어의 설계를 유연하고 확장 가능하게 만들어줍니다. SOLID는 다음의 다섯 가지 원칙으로 구성됩니다:

 

  1. 단일 책임 원칙 (Single Responsibility Principle, SRP): 클래스는 하나의 책임만 가져야 합니다. 즉, 클래스는 변경되어야 하는 이유가 하나여야 합니다. 이는 클래스가 한 가지 목적 또는 기능을 수행하도록 하여 코드의 응집성을 높이고 유지보수를 용이하게 만듭니다.
  2. 개방-폐쇄 원칙 (Open-Closed Principle, OCP): 소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해 열려 있어야 하지만 수정에 대해서는 닫혀 있어야 합니다. 이는 새로운 기능을 추가할 때 코드를 수정하지 않고 확장할 수 있도록 하는 것을 의미합니다. 주로 상속과 인터페이스를 통해 구현됩니다.
  3. 리스코프 치환 원칙 (Liskov Substitution Principle, LSP): 상위 타입의 객체는 하위 타입의 객체로 대체 가능해야 합니다. 즉, 어떤 클래스가 있을 때 그 클래스의 하위 클래스는 상위 클래스를 대체하여 사용될 수 있어야 하며, 이를 위해서는 하위 클래스가 상위 클래스의 기능을 변경하지 않고 확장하는 것이 중요합니다.
  4. 인터페이스 분리 원칙 (Interface Segregation Principle, ISP): 클라이언트는 자신이 사용하지 않는 메서드에 의존하도록 강요받지 말아야 합니다. 즉, 클라이언트는 필요한 메서드만 제공받아야 하며, 인터페이스가 너무 많은 기능을 포함하지 않도록 분리하는 것이 중요합니다.
  5. 의존성 역전 원칙 (Dependency Inversion Principle, DIP): 추상화에 의존해야 하며, 구체적인 것은 추상화에 의존하면 안 됩니다. 즉, 고수준 모듈은 저수준 모듈에 의존하면 안 되며, 추상화에 의존해야 합니다.

 

이러한 SOLID 원칙들은 객체 지향 설계의 기본 원칙으로, 코드의 유연성, 재사용성, 유지보수성을 높이고, 안정적이며 확장 가능한 소프트웨어를 설계하는 데 도움을 줍니다.

 

JAVA 코드로 객체지향 코드를 코딩할떄  OCP, DIP를 지키가 어렵고, 지키려고 해도 복잡한 로직을 개발자가 코딩을 해야된다.  OCP, DIP 2가지의 원칙을 개발자가 지켜줄 수 있게 쉽게 만든것이 Spring 이다

 

 

OCP 위반한 예시


소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해 열려 있어야 하지만 수정에 대해서는 닫혀 있어야 합니다. 이는 새로운 기능을 추가할 때 코드를 수정하지 않고 확장할 수 있도록 하는 것을 의미합니다. 주로 상속과 인터페이스를 통해 구현됩니다.

 

public class PaymentProcessor {
    public void processPayment(CreditCardPayment payment) {
        // 신용카드 결제 처리 로직
    }
}

 

위의 예시에서 PaymentProcessor 클래스는 CreditCardPayment에만 의존하고 있습니다. 이 클래스는 만약에 새로운 결제 방법인 PaypalPayment, BankTransferPayment 등이 추가된다면 기존 코드를 수정하거나 확장해야 할 것입니다. 이는 OCP를 위반하는 예시입니다.

 

DIP 위반한 예시


 추상화에 의존해야 하며, 구체적인 것은 추상화에 의존하면 안 됩니다. 즉, 고수준 모듈은 저수준 모듈에 의존하면 안 되며, 추상화에 의존해야 합니다.

 

public class Client {
    private Server server;

    public Client() {
        this.server = new Server(); // Server에 직접 의존성을 갖는다.
    }

    public void communicate() {
        server.sendData("Some data");
    }
}

public class Server {
    public void sendData(String data) {
        // 데이터 전송 로직
    }
}

 

위의 예시에서 Client 클래스는 Server 클래스에 직접 의존하고 있습니다. 이는 DIP를 위반하는 예시입니다. 이 경우에는 Client 클래스가 Server 클래스의 변경에 영향을 받을 수 있으며, 새로운 서버를 사용하고 싶을 때 기존 코드를 수정해야 합니다.

 

결론적으로,

Spring이 다형성이라는 말은 역할과 구현으로 구분한다 라는 의미를 가지고 있다.

즉, OCP, DIP 원칙을 지키기위해서는 애플리케이션에서 각각의 역할을 하는 (결제 역할, 로그인 역할) 로직을 각각 나누고, 구현하는 부분을 수정하지않고 개발을 하는것을 말한다. 

역할과 구현을 쉽게 나누고, 합칠 수 있도록 도와주는것이 바로 Spring 의 핵심 원리 이다.

 

IoC, DI, Container


Spring 프레임워크는 Java 기반의 오픈 소스 애플리케이션 프레임워크로서, 엔터프라이즈급 애플리케이션을 개발하기 위한 다양한 기능과 라이브러리를 제공합니다. Spring은 Inversion of Control (IoC) 원칙과 Dependency Injection (DI) 디자인 패턴을 기반으로 합니다. 이러한 개념들은 Spring의 핵심적인 개념이며, Spring의 핵심적인 기능들을 이해하는 데 중요합니다.

 

  1. Inversion of Control (IoC - 제어의 역전):
    • IoC는 제어의 역전을 의미하며, 일반적으로 애플리케이션 개발에서 제어 흐름의 방향을 바꾸는 것을 의미합니다.
    • 전통적인 방식에서는 개발자가 프로그램의 제어 흐름을 결정하고 관리하지만, IoC에서는 제어 흐름의 일부가 외부에 의해 결정됩니다.
    • Spring에서 IoC는 객체의 생성, 생명주기 및 의존성 관리를 컨테이너에 위임하여 개발자가 객체의 생성과 관리에 집중하지 않도록 합니다.
  2. Dependency Injection (DI - 의존성 주입):
    • DI는 객체가 직접 필요로 하는 의존성을 직접 생성하지 않고, 외부에서 주입받는 디자인 패턴입니다.
    • 객체 간의 의존성을 코드 내에 직접 작성하는 것이 아니라 외부에서 주입받아 사용함으로써, 유연성과 테스트 용이성을 높입니다.
    • Spring에서 DI는 객체 간의 의존성을 주입하기 위해 XML 설정, Java Annotation 또는 Java Config 등을 사용하여 객체들을 관리하고 조립합니다.
  3. 컨테이너 (Container):
    • Spring IoC 컨테이너는 Spring에서 IoC와 DI를 구현한 핵심 엔진입니다.
    • 컨테이너는 애플리케이션의 구성 요소들을 관리하고, 객체의 생명주기를 관리합니다. 이를 통해 객체들 사이의 의존성을 해결하고 필요에 따라 객체를 제공합니다.
    • 또한, 컨테이너는 Bean Factory라고도 불리며, 개발자가 작성한 객체들(빈, Bean)을 생성, 관리하고 제공하는 역할을 합니다.

 

Spring IoC 컨테이너는 다양한 형태로 구현되는데, 주로 사용되는 것은 ApplicationContext 인터페이스를 구현한 클래스들입니다. XML 파일이나 Java Configuration 클래스 등에서 빈들의 설정 정보를 제공하면, 컨테이너가 해당 빈들을 생성하고 의존성을 해결하여 애플리케이션에서 사용할 수 있도록 합니다.

 

간단하게 말하면, Spring의 IoC 컨테이너는 개발자가 작성한 객체들을 관리하고, 의존성을 주입하여 객체 간의 결합도를 낮춰 유연하고 효율적인 애플리케이션 개발을 도와줍니다.

 

아래의 코드는 Spring 사용 없이, 의존성 주입을 하는 예제.

 

Java 코드 IoC 예시


MyApplication 클래스는 생성자를 통해 MessageService 인터페이스에 대한 의존성을 주입받습니다. 이러한 제어의 역전은 Main 클래스에서 이루어집니다. Main 클래스에서는 MessageService의 구체적인 구현체를 생성하고 MyApplication에 주입합니다.

 

// 인터페이스
public interface MessageService {
    String getMessage();
}

// 구현 클래스
public class EmailService implements MessageService {
    @Override
    public String getMessage() {
        return "Email Message";
    }
}

// IoC 컨테이너를 사용하는 클래스
public class MyApplication {
    private final MessageService service;

    // 생성자를 통한 의존성 주입
    public MyApplication(MessageService service) {
        this.service = service;
    }

    public void processMessages() {
        System.out.println(service.getMessage());
    }
}

// Main 클래스
public class Main {
    public static void main(String[] args) {
        // IoC 컨테이너를 사용하여 의존성 주입
        MessageService service = new EmailService(); // 이 부분이 IoC 컨테이너에서 처리될 수 있습니다.
        MyApplication app = new MyApplication(service);
        app.processMessages();
    }
}

 

 

DI (Dependency Injection) Java 코드 예시


UserService 클래스는 EmailService를 의존하고 있습니다. 이 의존성은 UserService의 생성자를 통해 주입됩니다. 이렇게 의존성 주입(DI)을 통해 클래스는 직접 의존성을 만들지 않고 외부에서 주입받아 사용합니다

 

// 의존성 주입을 받을 클래스
public class UserService {
    private final EmailService emailService;

    // 생성자를 통한 의존성 주입
    public UserService(EmailService emailService) {
        this.emailService = emailService;
    }

    public void processUser() {
        // emailService를 사용하여 이메일 전송
        emailService.sendEmail("user@example.com", "Welcome!");
    }
}

// DI를 수행하는 클래스
public class Main {
    public static void main(String[] args) {
        // 의존성 주입
        EmailService emailService = new EmailService();
        UserService userService = new UserService(emailService);
        userService.processUser();
    }
}

 

위의 2개의 예제는 객체를 생성해서, 의존성을 주입해주는 예제이다. 하지만 구현체를 직접 수정하는 행위자체는 OCP, DIP 에 위반하는 예제이다. OCP, DIP SOLID 원칙을 따를 수 있도록 개발자가 코딩을 할 수 있지만, 많이 복잡하고 불편하다.

OCP, DIP 원칙을 개발자가 쉽게 지킬 수 있고, 개발할 수 있도록 만들어주는 것이 바로 Spring 이다.

 

 Spring을 이용한 IoC 및 DI 예시


// MessageService 인터페이스
public interface MessageService {
    String getMessage();
}

// EmailService 구현체
@Component
public class EmailService implements MessageService {
    @Override
    public String getMessage() {
        return "Email Message";
    }
}

// MyApplication 클래스
@Component
public class MyApplication {
    private final MessageService service;

    // 의존성 주입을 위한 생성자
    @Autowired
    public MyApplication(MessageService service) {
        this.service = service;
    }

    public void processMessages() {
        System.out.println(service.getMessage());
    }
}

// Main 클래스 (Spring 컨텍스트 사용)
public class Main {
    public static void main(String[] args) {
        // Spring 컨텍스트 초기화
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        // MyApplication 빈을 가져와서 사용
        MyApplication app = context.getBean(MyApplication.class);
        app.processMessages();
    }
}

// Spring 설정 클래스 (Bean 구성)
@Configuration
@ComponentScan(basePackages = "your.package.name") // Component 스캔을 위한 패키지 설정
public class AppConfig {
    // 추가적인 설정이 필요하다면 여기에 추가 가능
}

 

  1. @Component 애노테이션: EmailService와 MyApplication 클래스에 붙어 있는 이 애노테이션은 Spring 컨테이너가 해당 클래스를 빈으로 등록하도록 지시합니다.
  2. @Autowired 애노테이션: 생성자에 붙어 있으며, Spring은 해당 생성자를 사용하여 의존성을 주입합니다.
  3. @Configuration 및 @ComponentScan: 이것들은 Spring의 Java 기반 설정을 나타내며, 빈을 등록하고 컴포넌트를 스캔하여 빈으로 등록할 패키지를 지정합니다.

 

Spring을 사용하면 클래스 간의 결합도를 낮추고, 확장성 있고 유연한 코드를 작성할 수 있으며, 이는 OCP와 DIP 같은 디자인 원칙을 준수하는데 도움이 됩니다. 새로운 기능을 추가하거나 변경할 때 기존 코드를 수정할 필요 없이 Spring의 IoC 컨테이너가 의존성을 관리하므로 원칙을 위반하지 않고 코드를 유지

 

AnnotationConfigApplicationContext


ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

 

위의 코드는 Spring Framework에서 AnnotationConfigApplicationContext를 사용하여 애플리케이션 컨텍스트를 초기화하는 것을 나타냅니다. 이를 통해 Spring 컨테이너를 구성하고 설정할 수 있습니다.

 

여기서 사용된 AppConfig.class는 @Configuration 애노테이션이 부여된 클래스입니다. 이 클래스는 Spring 애플리케이션의 설정을 구성하기 위한 클래스로, 빈의 정의와 다양한 설정을 포함할 수 있습니다.

 

AnnotationConfigApplicationContext는 Java 기반 설정 클래스를 스캔하고 구성하여 Spring 애플리케이션 컨텍스트를 생성합니다. AppConfig 클래스에 정의된 빈들과 다른 설정들을 해당 애플리케이션 컨텍스트로 로드합니다.

 

주로 AnnotationConfigApplicationContext는 JavaConfig로 된 설정 클래스들을 사용하여 Spring 애플리케이션 컨텍스트를 초기화하는 데 사용됩니다. 이를 통해 XML을 사용하지 않고도 애플리케이션을 설정하고 관리할 수 있습니다.

 

스프링 애플리케이션을 시작할 때 위의 코드는 설정된 AppConfig 클래스에 정의된 빈들과 설정들을 읽어들여 Spring 컨테이너를 초기화하고 준비합니다. 이후에 이 컨테이너를 통해 애플리케이션 내의 빈들을 관리하고 주입합니다.

 

Spring Container 스프링 컨테이너


https://docs.spring.io/spring-framework/reference/core/beans/basics.html

 

Container Overview :: Spring Framework

As the preceding diagram shows, the Spring IoC container consumes a form of configuration metadata. This configuration metadata represents how you, as an application developer, tell the Spring container to instantiate, configure, and assemble the objects i

docs.spring.io

 

https://docs.spring.io/spring-framework/reference/core/beans/basics.html

 

 

  1. Java 객체로 코딩: 개발자는 Java로 객체를 작성합니다. 이 때, Spring에서 제공하는 다양한 어노테이션들 (@Component, @Service, @Repository, @Autowired 등)을 활용하여 객체에 특정한 역할과 의존성을 명시할 수 있습니다. -> Your Business Object (POJOs)
  2. 어플리케이션 설정: Java 클래스 중 하나를 @Configuration 어노테이션으로 설정하여 Spring 설정 파일로 사용합니다. 이 설정 클래스에서는 @Bean 어노테이션을 사용하여 빈(Bean)을 정의합니다.
  3. Spring 시작: 애플리케이션을 시작하면 Spring 컨테이너가 생성됩니다.
  4. 컨테이너 초기화: @Configuration이 붙은 클래스를 스캔하거나 설정 클래스를 확인하여 Spring 컨테이너가 초기화됩니다. 이 때 설정된 빈들은 생성되고, 필요한 경우 빈의 라이프사이클과 관련된 메서드가 실행될 수 있습니다.
  5. 빈 등록: 스프링은 설정된 빈들을 인스턴스화하고 컨테이너에 등록합니다. @ComponentScan을 통해 패키지 스캔이나 수동으로 등록한 빈들은 컨테이너에 등록됩니다.
  6. 의존성 주입(Dependency Injection): @Autowired 어노테이션이 적용된 필드, 생성자, 메서드 등에서 필요한 의존성을 주입합니다. 이를 통해 객체 간의 의존성을 자동으로 해결하고, 개발자가 직접 의존성을 관리할 필요가 없습니다.
즉, Spring은 설정된 빈들을 관리하고 초기화하며, 의존성 주입을 통해 객체 간의 연결을 자동화하여 개발자가 객체 생성과 의존성 관리에 신경 쓸 필요 없이 모듈화된 코드를 작성할 수 있도록 도와줍니다.