Spring을 사용하면 개발 생산성을 높이고 유지보수를 쉽게 만들어준다고 한다.
개발을 하다 보면 왜 그런지 항상 느끼지만 말로 정리를 못하겠어서 여기에 한 번 정리해야겠다.
Spring Framework는 자바 기반 엔터프라이즈 애플리케이션 개발을 지원하는 플랫폼이다.
Spring이 POJO 프로그래밍을 추구한다고 한다. Spring의 가장 기본적인 설계 철학이라고 할 정도이다.
POJO는 특정 프레임워크나 라이브러리에 종속되지 않은 순수한 Java 객체를 일컫는 말이다.

Spring은 POJO 프로그래밍을 위해 IoC/DI, AOP, PSA를 지원한다고 한다.
장점:
예시:
POJO 객체 사용:
Spring 설정이 없어도 해당 코드를 사용할 수 있다.
public class UserService {
public String getUser() {
return "User Info";
}
}
public class Main {
public static void main(String[] args) {
UserService userService = new UserService();
System.out.println(userService.getUser());
}
}
IoC (Inversion of Control)
객체의 생성과 의존성 관리를 개발자가 아니라 Spring IoC 컨테이너(이하 Spring 컨테이너)가 맡는 것이다. 필요한 의존성을 주입하는 과정을 프레임워크에 위임하여 내가 관리하지 않으면서 코드의 결합도를 낮추고 유연성을 높이는 것이다.
장점:
예시:
IoC 적용
이 코드에서는 OrderService가 특정 PaymentService의 의존하게 된다. PaymentService를 다른 구현체로 교체하려면 OrderService의 코드까지 수정해야 한다. 직접 의존성을 생성하게 되면 PaymentService에 대한 결합도로 인해 재사용성도 떨어진다.
OrderService를 테스트 시에도 PaymentService도 함께 초기화해야한다.
class OrderService {
private PaymentService paymentService;
public OrderService() {
// 객체를 직접 생성 (강한 결합)
this.paymentService = new PaymentService();
}
public void placeOrder() {
paymentService.processPayment();
}
}
class PaymentService {
public void processPayment() {
System.out.println("Payment processed.");
}
}
IoC 적용
이 코드에서는 객체 생성 책임을 Spring 컨테이너에 위임한다. 이로써 OrderService는 PaymentService의 특정 구현체에 대한 결합도가 낮아지고, 해당 구현체를 쉽게 변경하고 확장할 수 있다.
OrderService를 테스트 시에 PaymentService를 초기화하지 않고 Mock 객체로 대체할 수 있어 독립적인 테스트가 가능하다.
@Service
class PaymentService {
public void processPayment() {
System.out.println("Payment processed.");
}
}
@Service
class OrderService {
private final PaymentService paymentService;
// 의존성 주입 (생성자 주입)
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void placeOrder() {
paymentService.processPayment();
}
}
DI (Dependency Injection)
DI는 IoC를 구현하는 방법 중 하나이다. 객체가 필요로 하는 의존성을 외부(Spring 컨테이너)에서 주입 받는 방식이다.
Spring은 애너테이션 기반, Java Config 클래스, XML 설정 등으로 DI를 지원한다.
동작 원리:
장점:
예시:
생성자 주입(요즘 권고된다):
@Service
class OrderService {
private final PaymentService paymentService;
@Autowired // 생성자 주입 하나일 때는 Autowired 생략 가능
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
Setter 주입:
@Service
class OrderService {
private PaymentService paymentService;
@Autowired
public void setPaymentService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
Field 주입:
@Service
class OrderService {
@Autowired
private PaymentService paymentService;
}
@Component, @Controller, @Service, @Repository을 사용하면 @ComponentScan으로 자동적으로 해당 객체들을 Bean으로 Spring 컨테이너에 등록해준다.
반면, 명시적으로 Bean을 등록하고자 할 때에는 @Configuration, @Bean을 사용하면 된다.
명시적 Bean 등록
@Configuration
public class AppConfig {
@Bean
public PaymentService paymentService() {
return new PaymentService(); // 빈으로 등록
}
@Bean
public OrderService orderService() {
return new OrderService(paymentService()); // 의존성 주입
}
}
PSA는 Spring이 제공하는 추상화 원칙으로, 특정 기술과 환경에 종속되지 않도록 제공하는 것이다.
PSA를 통해 개발자는 다양항 기술 스택에서 동일한 방식으로 코드를 작성할 수 있고, Spring 하위 구현의 세부사항을 몰라도 서비스를 개발할 수 있다!
장점:
구현체 교체도 쉽게 할 수 있고, 다른 기술(SQL 같은)의 종속성도 낮아지고, 이로써 환경 변경의 유연성을 가지게 된다.
예시:
public interface UserRepository extends JpaRepository<User, Integer> {
// 메서드 정의만으로 데이터베이스 작업 가능
User findById(int id);
}Spring은 공통적으로 사용되는 로직(로깅, 트랜잭션 관리 등)을 핵심 비즈니스 로직과 분리하여 모듈화할 수 있도록 지원한다.
장점:
예시:
로그를 출력해주는 AOP 클래스 적용 예시:
@EnableAspectJAutoProxy 추가해주고
// 비즈니스 로직 클래스
@Service
public class PaymentService {
public void processPayment() {
System.out.println("Processing payment...");
// 결제 로직
}
public void cancelPayment() {
System.out.println("Cancelling payment...");
// 결제 취소 로직
}
}
// 로그 전용 Aspect 클래스
@Aspect
@Component
@Slf4j
public class LoggingAspect {
// Pointcut: PaymentService 클래스의 모든 메서드
@Pointcut("execution(* com.example.app.PaymentService.*(..))")
public void allPaymentMethods() {}
// Advice: 메서드 실행 전 로그 출력
@Before("allPaymentMethods()")
public void logBefore() {
logger.info("[AOP] Method execution started");
}
// Advice: 메서드 실행 후 로그 출력
@After("allPaymentMethods()")
public void logAfter() {
logger.info("[AOP] Method execution finished");
}
// Advice: 메서드 정상 완료 후 로그 출력
@AfterReturning("allPaymentMethods()")
public void logAfterReturning() {
logger.info("[AOP] Method executed successfully");
}
}
// 이 코드가 수행되면
paymentService.processPayment();
paymentService.cancelPayment();
# 이렇게 로그가 출력(저장)될 것이다
INFO - [AOP] Method execution started
Processing payment...
INFO - [AOP] Method execution finished
INFO - [AOP] Method executed successfully
INFO - [AOP] Method execution started
Cancelling payment...
INFO - [AOP] Method execution finished
INFO - [AOP] Method executed successfully
Spring Framework는 엔터프라이즈 애플리케이션 개발을 위한 프레임워크입니다.
특징:
Spring Framework는 설정 과정이 어렵고 시간이 오래 걸리지만, 세밀한 제어가 필요한 복잡한 애플리케이션에 적합하다.
Spring Boot는 Spring 기반 애플리케이션을 빠르게 개발할 수 있도록 도와주는 프레임워크이다.
특징:
Spring Boot는 초기 설정을 간소화하고, 개발 생산성을 높이는 데 최적화된 프레임워크이다.
Spring은 POJO를 추구하고, 이를 실현시키기 위해 IoC/DI, PSA, AOP를 지원한다.
IoC/DI는 객체의 생성과 의존성 관리를 Spring IoC 컨테이너에게 위임하여 코드 간의 결합도를 낮추고 유연성을 높이는 것이다.
PSA는 잘 만든 인터페이스를 만들어서 코드의 수정을 최소화할 수 있도록 하는 것이다.
AOP는 여러 비즈니스 로직 간 공통적인 로직을 모듈화 하는 것이다.
Spring Framework와 Spring Boot의 차이점은 아래 표로 간략히 정리해봤다.
| 항목 | Spring Framework | Spring Boot |
|---|---|---|
| 설정 방식 | 수동 설정 필요 (XML, Java Config) | 자동 설정 제공 (기본 설정 자동 처리) |
| 라이브러리 관리 | 직접 추가 및 버전 관리 | Starter 패키지 제공 |
| 서버 구성 | 외부 서버 필요 | 내장 서버 제공 (Tomcat, Jetty 등) |
| 개발 속도 | 설정 시간이 오래 걸림 | 빠른 프로젝트 시작 가능 |
| 유연성 | 복잡한 요구사항을 세밀하게 커스터마이징 가능 | 기본 제공되는 설정으로 빠르게 개발 가능 |
참고 및 출처
https://www.elancer.co.kr/blog/detail/158