개념 설명
Spring은 Java로 웹사이트나 앱 서버를 만들 때 사용하는 도구 모음입니다. 2003년에 만들어져서 지금까지 전 세계 개발자들이 가장 많이 사용하고 있습니다.
Spring의 진짜 역할
잘못된 이해
- Spring = 단순히 라이브러리 모음?
- Spring = Spring Boot?
- Spring = 웹 프레임워크?
올바른 이해
- 객체의 생명주기를 관리하는 컨테이너
- 다양한 기술을 통합하는 플랫폼
@SpringBootApplication // Spring Boot 애플리케이션임을 선언
public class MyApplication {
public static void main(String[] args) {
// Spring이 애플리케이션의 모든 객체를 자동으로 생성하고 관리
SpringApplication.run(MyApplication.class, args);
}
}
왜 Spring을 사용하는가?
Plain Java vs Spring 비교표
| Plain JAVA | spring | |
| 객체 생성 | 직접 new 키워드로 생성 | 컨테이너가 자동 생성 및 관리 |
| 의존성 관리 | 직접 연결, 강한 결합 | DI로 느슨한 결합 |
| 트랜잭션 처리 | 수동으로 commit/rollback | @Transactional 어노테이션 |
| 테스트 | Mock 객체 수동 생성 | 자동 의존성 주입 |
Plain Java 방식 - 문제점
public class UserService {
// 문제 1: 구체적인 구현 클래스에 직접 의존 (강한 결합)
private UserRepository userRepository = new UserRepositoryImpl();
public User findUser(Long id) {
Connection conn = null;
try {
// 문제 2: DB 연결을 직접 관리해야 함
conn = DriverManager.getConnection(URL, USER, PASSWORD);
conn.setAutoCommit(false); // 트랜잭션 시작
User user = userRepository.findById(id);
conn.commit(); // 성공 시 커밋
return user;
} catch (Exception e) {
// 문제 3: 에러 처리도 직접 작성
if (conn != null) conn.rollback();
throw e;
} finally {
// 문제 4: 리소스 정리도 직접 해야 함
if (conn != null) conn.close();
}
}
}
Spring 방식 - 개선점
@Service // Spring에게 "이 클래스를 빈으로 등록해줘"
public class UserService {
// 개선 1: 인터페이스에만 의존 (느슨한 결합)
private final UserRepository userRepository;
// 개선 2: 생성자를 통해 의존성 주입 받음
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 개선 3: @Transactional 하나로 트랜잭션 자동 처리
@Transactional
public User findUser(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
}
코드가 훨씬 간결해지고 개발자는 비즈니스 로직에만 집중할 수 있다.
Spring의 3대 핵심 개념
1️⃣ IoC/DI - 제어의 역전 & 의존성 주입
IoC (Inversion of Control)
프로그램의 제어 흐름을 개발자가 아닌 프레임워크가 담당하는 디자인 원칙
❌ 전통적인 방식 (개발자가 제어)
public class OrderService {
public void processOrder() {
// 개발자가 직접 객체를 생성하고 관리
PaymentService paymentService = new PaymentService();
paymentService.processPayment();
NotificationService notificationService = new NotificationService();
notificationService.sendNotification();
}
}
✅ IoC 방식 (Spring이 제어)
@Service
public class OrderService {
// Spring이 생성한 객체를 받아서 사용만 함
private final PaymentService paymentService;
private final NotificationService notificationService;
// Spring이 자동으로 필요한 객체들을 생성해서 주입
public OrderService(PaymentService paymentService,
NotificationService notificationService) {
this.paymentService = paymentService;
this.notificationService = notificationService;
}
public void processOrder() {
paymentService.processPayment();
notificationService.sendNotification();
}
}
DI의 세 가지 방식
Ioc를 구현하는 구체적인 방법
1.생성자 주입 (권장)
@Service
public class UserService {
private final UserRepository userRepository;
// final 키워드로 불변성을 보장하고 순환 참조를 방지할 수 있다.
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
2.Setter 주입
@Service
public class UserService {
private UserRepository userRepository; // final 사용 불가
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
3.필드 주입 (비권장) ❌
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // 테스트 어려움
}
생성자 주입을 권장하는 이유
방식장점단점
| 생성자 주입 | final 사용, 불변성 보장, 순환 참조 방지 | 생성자 코드가 길어질 수 있음 |
| Setter 주입 | 선택적 의존성, 런타임 변경 가능 | 불변성 보장 안됨, NPE 위험 |
| 필드 주입 | 코드 간결 | 테스트 어려움, final 불가 |
2️⃣ AOP - 관점 지향 프로그래밍
핵심 비즈니스 로직과 부가 기능(로깅, 트랜잭션, 보안)을 분리하여 관리하는 프로그래밍 패러다임
❌ AOP 없이 (코드 중복)
@Service
public class UserService {
public User createUser(User user) {
// 부가 기능: 로깅 시작
log.info("createUser 시작: {}", user);
long startTime = System.currentTimeMillis();
try {
// 핵심 로직: 사용자 저장
User savedUser = userRepository.save(user);
// 부가 기능: 실행 시간 로깅
log.info("createUser 완료: {}ms",
System.currentTimeMillis() - startTime);
return savedUser;
} catch (Exception e) {
log.error("createUser 실패", e);
throw e;
}
}
public User updateUser(User user) {
// 문제: 똑같은 로깅 코드를 또 작성... (중복)
log.info("updateUser 시작: {}", user);
// ... 반복
}
}
✅ AOP 적용 (관심사 분리)
// Aspect: 공통 기능을 모아놓은 클래스
@Aspect
@Component
public class LoggingAspect {
// service 패키지의 모든 메소드에 이 로직을 적용
@Around("execution(* com.example.service.*.*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
log.info("{} 시작", joinPoint.getSignature().getName());
try {
Object result = joinPoint.proceed(); // 실제 메소드 실행
log.info("{} 완료: {}ms",
joinPoint.getSignature().getName(),
System.currentTimeMillis() - startTime);
return result;
} catch (Exception e) {
log.error("{} 실패", joinPoint.getSignature().getName(), e);
throw e;
}
}
}
// 비즈니스 로직은 핵심 기능만 집중
@Service
public class UserService {
// 로깅 코드 없이 깔끔!
public User createUser(User user) {
return userRepository.save(user);
}
public User updateUser(User user) {
return userRepository.save(user);
}
}
3️⃣ PSA - 이식 가능한 서비스 추상화
환경과 세부 기술의 변화에 관계없이 일관된 방식으로 기술에 접근할 수 있게 해주는 설계 원칙
❌ 기술에 종속된 코드
public class UserRepository {
public void save(User user) {
// 문제: JDBC 기술에 직접 의존
// JPA로 바꾸려면 이 코드 전부 수정해야 함
Connection conn = DriverManager.getConnection(...);
PreparedStatement pstmt = conn.prepareStatement(
"INSERT INTO users VALUES (?, ?)");
// ... JDBC 코드에 종속
}
}
✅ PSA 적용 (추상화)
- 기술 변경에 유연하게 대응할 수 있게 해주는 설계 원칙
// JpaRepository를 상속받으면 기본 CRUD 자동 생성
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
@Service
public class UserService {
private final UserRepository userRepository;
public User saveUser(User user) {
// 구현 기술(JDBC, JPA, MongoDB 등)과 무관하게 동일한 코드
// 나중에 기술을 바꿔도 이 코드는 수정할 필요 없음
return userRepository.save(user);
//Spring Data JPA의 JpaRepository를 사용하면, save() 메소드 하나로 구현 기술과 무관하게 데이터를 저장할 수 있습니다.
}
}
🆚 Spring vs Spring Boot
차이점 비교
| Spring | Spring Boot | |
| 설정 | XML/Java Config 수동 설정 | 자동 설정 |
| 의존성 관리 | 버전 직접 관리 | Starter로 자동 관리 |
| 내장 서버 | 별도 WAS 필요 | 내장 서버 포함 |
| 초기 설정 | 오래 걸림 | 몇 분 내 완료 |
❌ Spring Framework (복잡)
xml
<!-- web.xml -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
</servlet>
<!-- applicationContext.xml -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<!-- ... 수많은 설정 -->
</bean>
✅ Spring Boot (간단)
java
@SpringBootApplication // 자동 설정을 모두 해줌
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
yaml
# application.yml (간단한 설정만)
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: user
password: password
핵심 정리
Spring의 3대 핵심 기술
- IoC/DI: 객체의 생명주기와 의존성을 Spring이 관리
- AOP: 공통 관심사를 분리하여 중복 제거
- PSA: 기술 변경에 유연한 추상화 계층 제공
Spring을 사용해야 하는 이유
- 생산성: 반복 코드 감소, 비즈니스 로직 집중
- 유지보수성: 느슨한 결합, 변경 용이
- 테스트 용이성: DI로 Mock 테스트 간편
- 커뮤니티: 방대한 생태계와 레퍼런스
Spring Boot의 장점
- 자동 설정으로 개발 시간 단축
- 내장 서버로 배포 간편
- Starter 의존성으로 버전 관리 자동화
'-- 오늘 있었던 개발 일기' 카테고리의 다른 글
| RESTful Api란? (0) | 2026.01.04 |
|---|---|
| MCP 연결에 대해! (0) | 2025.12.26 |
| NoSql에서 트랜잭션이 가능한가? (0) | 2025.12.22 |
| RDBMS란 무엇인가? (0) | 2025.12.21 |
| 오늘의 개발 문제 : fetch였다.. (0) | 2025.12.19 |