Spring의 핵심 개념 (IoC, DI, Bean)
- DI (Dependency Injection, 의존성 주입)**→ 객체를 직접 생성하지 않고, 외부에서 주입받아 사용 (@Autowired, 생성자 주입 등) 개발자가 직접 new 키워드를 사용하지 않음
- IoC (Inversion of Control, 제어의 역전)**→ 객체의 생성과 생명 주기를 Spring이 관리
- Bean→ Spring이 관리하는 객체 (@Component, @Service, @Repository 등으로 등록)
스프링의 핵심(1) : DI(Dependency Injection) 의존관계주입
의존성 주입(DI) 개념
- 의존성: 한 클래스가 기능을 수행하기 위해 다른 클래스의 메서드나 데이터에 의존하는 관계를 말합니다.
- 주입: 의존성이 필요한 객체를 직접 만들지 않고, 생성자, 메소드, 또는 프로퍼티를 통해 외부에서 받는 과정을 말합니다.
- 의존성주입 : 스프링이 제공하는 설정 Configuration을 통해 외부에서 실제 구현 객체를 생성하고 연결(주입) 하는 방식이 DI.
- 효과 : 클래스 A가 클래스 B에 직접 의존하면, B의 변화에 따라 A도 함께 변경될 가능성이 있음. DI를 통해 느슨한 결합(Loose Coupling) 을 유지할 수 있으며, 변경에 유연한 설계가 가능.
코드로 보는 Java와 Spring DI 방식 차이
1. 일반 Java 방식 (직접 생성)
public class OrderService {
private final PaymentService paymentService = new KakaoPayService(); // 직접 생성 (new)
public void placeOrder() {
paymentService.processPayment();
}
}
2. 스프링 DI 방식 (스프링 컨테이너가 주입)
(1) 구현체 간의 의존성을 인터페이스로 연결하는 구조
// 인터페이스 도입 (다른 결제 서비스로 쉽게 변경 가능)
public interface PaymentService {
void processPayment();
}
// 실제 구현 클래스 1 (카카오페이)
@Service
public class KakaoPayService implements PaymentService {
@Override
public void processPayment() {
System.out.println("카카오페이 결제 처리");
}
}
// 실제 구현 클래스 2 (네이버페이)
@Service
public class NaverPayService implements PaymentService {
@Override
public void processPayment() {
System.out.println("네이버페이 결제 처리");
}
}
// OrderService가 인터페이스에 의존 (구현체를 직접 생성하지 않음)
@Service
public class OrderService {
private final PaymentService paymentService;
@Autowired // 스프링이 자동으로 의존성 주입
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void placeOrder() {
paymentService.processPayment(); // 구현체가 자동으로 주입됨
}
}
(2) 스프링 설정 파일 (Java Config)
@Configuration
public class AppConfig {
@Bean
public PaymentService paymentService() {
return new KakaoPayService(); // 주입할 실제 구현체 지정
}
@Bean
public OrderService orderService(PaymentService paymentService) {
return new OrderService(paymentService); // 의존성 주입
}
}
스프링의 핵심(2) : IOC 제어의 역전
- 제어 : 코드 실행의 흐름(객체 생성, 메서드 호출, 실행 순서)을 개발자가 직접 관리하는 것"
- 제어의 역전 : 코드 실행의 흐름을 프레임워크(Spring)에게 넘겨서 관리하도록 하는 것"
기존 Java 방식과 IoC의 차이
- 기존 Java에서는 객체가 직접 다른 객체를 생성하고 실행 흐름을 관리.
- 그러나 Spring에서는 Configuration(설정 파일)을 통해 객체 생성과 의존성 관리 역할을 분리.
프레임워크 vs 라이브러리 차이
- 라이브러리: 개발자가 직접 객체를 생성하고 필요한 시점에 호출.
- 프레임워크: 객체의 생성과 실행을 프레임워크가 담당하며, 개발자는 필요한 로직만 구현.
- 즉, 제어의 흐름이 어디에 있느냐(개발자 vs 프레임워크)가 프레임워크와 라이브러리의 차이.
코드로 보는 Java와 Spring DI 방식 차이
일반적인 JDBC 사용 (IoC 없이 직접 객체 생성)
public class OrderRepository {
private Connection connection;
public OrderRepository() {
try {
this.connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/shop", "user", "password");
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
Spring DI 활용 : JDBC 연결을 인터페이스로 주입하는 구조
(1)DataSource 인터페이스를 통한 주입
@Repository
public class OrderRepository {
private final DataSource dataSource;
@Autowired
public OrderRepository(DataSource dataSource) { // 인터페이스로 주입받음
this.dataSource = dataSource;
}
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}
(2)Spring Bean 설정을 통한 DataSource 관리
DataSource가 MySQL, PostgreSQL, H2 등으로 변경되더라도 OrderRepository 코드 변경 없이 사용 가능
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/shop");
dataSource.setUsername("user");
dataSource.setPassword("password");
return dataSource;
}
}
코드로 보는 Java와 Spring 코드 제어 방식 차이
(1) 일반적인 Java 방식 객체생성과 실행, 개발자가 모든 흐름을 직접 제어
public class OrderService {
private final PaymentService paymentService = new PaymentService(); // 직접 객체 생성
public void placeOrder() {
paymentService.processPayment();
}
public static void main(String[] args) {
OrderService orderService = new OrderService(); // 개발자가 직접 객체 생성
orderService.placeOrder(); // 개발자가 직접 실행
}
}
(2) 완전히 제어의 흐름이 프레임워크로 넘어가는 경우 ; 객체생성과 실행, 프레임워크가 모든 흐름을 직접 제어
2. 클라이언트가 /orders API를 호출하면 Spring이 알아서 createOrder() 메서드를 실행
@RestController
@RequestMapping("/orders")
public class OrderController {
private final OrderService orderService;
@Autowired
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@PostMapping
public ResponseEntity<String> createOrder() {
orderService.placeOrder(); // 개발자가 직접 호출하지 않음
return ResponseEntity.ok("주문 완료");
}
}
OCP(개방-폐쇄 원칙) &인터페이스 활용과 스프링
- 인터페이스라는 기본 제약과 원칙을 부여하고 이를 다양한 구현체를 추가할 때, 기존 코드 수정 없이 변경과 교체를 쉽게 가능하도록 한다.
- 완전한 자유보다는 적절한 제약이 유지보수성과 확장성을 높이는 데 유리함. 스프링도 동일한 논리로, 제약을 부여하는 툴이다.
- 공통된 개발 로직(http protocol 호출 등)에 대해서는 프레임워크 코드를 사용하고 이게 맞도록 제약을 부여해서 제어의 흐름을 개발자 자신에서 프레임워크로 전이시킨다. 사용자가 직접 class의 객체를 new로 생성하는 것이 아니라 프레임워크가 규격에 맞는 클래스를 제어하도록 함.
스프링이 IoC, DI를 적극 활용하는 대표적인 사례
- 서블릿 컨테이너 (Tomcat)과 Spring의 연결
- DispatcherServlet을 Spring이 관리하고, 컨트롤러를 자동으로 DI해줌
- @Controller 빈을 등록하면 Tomcat이 직접 컨트롤러 객체를 만들지 않고 Spring이 관리
- JDBC, JPA, DataSource와의 연결
- DataSource 인터페이스를 활용하여 DB 연결 객체를 관리 (HikariCP, Apache DBCP 등의 커넥션 풀 사용)
- JDBC 드라이버(MysqlDataSource, H2DataSource)를 직접 생성하는 대신, Spring이 관리하고 DI를 통해 주입
- 트랜잭션 관리 (Spring의 @Transactional)
- PlatformTransactionManager 인터페이스를 사용하여 다양한 트랜잭션 전략 (DataSourceTransactionManager, JpaTransactionManager)을 쉽게 교체 가능
- Controller와 Repository에서 DI가 중요한 이유
- 서블릿 컨테이너(Tomcat 등)나 데이터베이스(JDBC)가 변경되더라도 DI 방식 을 적용하면 개발자 코드에는 영향을 주지 않음.
- 스프링 컨테이너가 객체 생성 및 관리 → 개발자는 객체의 생성 방식과 라이프사이클을 신경 쓰지 않아도 됨.
- 비유
- 자바 개발을 찰흙으로 물건을 만드는 것에 비유하면, 스프링은 레고 조립과 유사.
- Bean이라는 규격에 맞춰 클래스를 만들면, 스프링 컨테이너에서 자동으로 객체를 생성하고 연결해줌.
'개발기술 > Spring' 카테고리의 다른 글
스프링 전체구조 (0) | 2024.07.24 |
---|---|
스프링 부트와 프로젝트 초기 환경설정 (0) | 2024.07.23 |
스프링 MVC, 예외처리 (0) | 2024.07.18 |
스프링 부가기능 (Resource, AOP, 유효성검증, 데이터바인딩,spel) (0) | 2024.07.17 |
스프링 도입의 배경 ; Java로 스프링 구현해보기 (0) | 2024.05.17 |