안녕하세요. 오늘은 스프링의 3대 요소 DI, AOP, PAS에 대해 정리해보려합니다.
1. DI (의존성 주입)
개념 : 한 객체가 다른 객체에 의존할 때, 그 객체를 직접 생성하지 않고 외부에서 주입받는 방식입니다.
상황 : 새로 이사한 집에 전구가 필요한 상황입니다. 직접 전구를 만들지 않고 이미 만들어진 전구를 구매해 사용하는 것이 더 효율적입니다. 전구가 필요할 때, 이를 외부에서 주입받는 것입니다.
직접 객체 생성하는 방식 (DI 활용 X)
public class Light {
private Bulb bulb;
public Light() {
this.bulb = new Bulb();
}
}
외부에서 주입받는 방식 (DI 활용)
@Component
public class Bulb {}
@Component
public class Light {
private final Bulb bulb;
@Autowired
public Light(Bulb bulb) {
this.bulb = bulb;
}
}
결과적으로 코드가 더 유연해지고, Bulb의 변경이 필요할 때 쉽게 교체가 가능합니다.
2. AOP (관점 지향 프로그래밍)
개념 : 핵심적인 비즈니스 로직으로부터 횡단 관심사를 분리하는 것에 목적을 둡니다.
- 횡단 관심사 : 애플리케이션 코드가 중복되고, 강력하게 결합되어 있어 다른 로직과 분리할 수 없는 로직
상황 : 사용자는 로그인을 해야 서비스를 확인할 수 있습니다. 로그인한 사용자인지 확인하는 로직은 모든 비즈니스 로직이 실행되기 전에 반드시 필요합니다.
해결 : AOP를 활용하여 클라이언트가 서비스 메서드를 호출하면 AOP가 가로채서 인증 확인 로직을 먼저 실행하도록 합니다.
AOP 용어
- Target : 부가기능을 부여할 대상 (UserService)
- Advice : 타깃에게 제공할 부가기능을 담은 모듈 (사용자 로그인 했는지 확인하는 로직)
- Join Point : Advice가 적용될 수 있는 위치
- Pointcut : Advice를 적용할 조인 포인트를 선별하는 작업 (어떤 메서드에 적용할 거야?)
부가기증과 핵심 로직이 섞인 코드 (AOP 활용 X)
아래 코드는 로직이 반복되고 있습니다.
public class UserService {
public void getUserDetails(String userId) {
if (!isAuthenticated()) {
throw new RunTimeException("사용자가 인증되지 않았습니다.");
}
// 핵심 로직
...
}
public void updateUser(String userId) {
if (!isAuthenticated()) {
throw new RunTimeException("사용자가 인증되지 않았습니다.");
}
// 핵심 로직
...
}
private boolean isAuthenticated() {
// 인증 로직
...
return true;
}
}
핵심 로직과 부가기능 분리 (AOP 활용)
@Aspect
@Component
public class AuthCheckAspect {
@Before("execution(* com.example.service.*.*(..))")
public void checkAuthentication() {
if (!isAuthenticated()) {
throw new RuntimeException("사용자가 인증되지 않았습니다.");
}
System.out.println("사용자 인증 확인 완료");
}
private boolean isAuthenticated() {
// 인증 로직
...
return true;
}
}
public class UserService {
public void getUserDetails(String userId) {
// 핵심 로직
}
public void updateUser(String userId) {
// 핵심 로직
}
}
AOP 동작 과정
BeanPostProcessor (빈 후처리기) : 생성된 빈 객체를 스프링 컨테이너에 등록하기 전에 조작하는 객체
Proxy : 원본 객체를 감싼 새로운 객체로, 스프링 AOP에서 부가기능을 추가할 때 사용하는 패턴
- 빈 객체를 생성한 뒤에 빈 후처리에게 전달합니다.
- 빈 후처리기는 Advisor를 싹 훑어보고 전달받은 빈이 프록시 적용 대상인지 확인합니다.
- 프록시 생성 대상 빈들을 대상으로 프록시 객체를 생성합니다.
- 프록시를 생성한 빈이라면 프록시 객체를, 프록시를 생성하지 않은 빈이라면 그냥 빈을 전달합니다.
- 빈 후처리기에게 전달받은 객체를 컨테이너 빈으로 등록합니다.
3. PSA (범용 서비스 추상화)
개념 : 스프링이 제공하는 기술 독립적인 추상화 계층으로, 다양한 기술을 스프링 환경에서 일관되게 사용할 수 있게 하는 개념입니다.
상황 : 여행 시 전세계적으로 다른 전기 플러그 모양이 있지만, 어댑터를 사용하면 동일한 기기로 어디든 충전할 수 있습니다. Spring은 플러그 모양에 상관없이 어댑터 역할을 제공하여 동일한 방식으로 기술을 사용할 수 있게 해줍니다.
1. 기술 독립성
개발자가 특정 기술에 종속되지 않도록, 스프링은 기술을 추상화한 공통된 인터페이스를 제공합니다.
- 관계형 데이터베이스는 JPA를 통해 추상화
- 스케줄링 작업은 Spring Scheduler로 추상화
- 메시지 브로커는 Spring AMQP로 추상화
2. 일관된 프로그래밍 모델
PSA를 사용하면 기술마다 다른 API를 배우지 않아도, 스프링이 제공하는 방식으로 동일하게 코딩할 수 있습니다.
3. 교체 가능성
PSA를 사용하면 기술을 쉽게 교체할 수 있습니다. 예를 들어, 관계형 데이터베이스를 사용할 떄 H2에서 MySQL로 바꾸거나, 메시징 시스템을 RabbitMP에서 Kafka로 변경할 떄도 큰 수정 없이 동작합니다.
데이터베이스 연동
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "user", "password");
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
while (rs.next()) {
System.out.println(rs.getString("name"));
}
스프링에서 제공하는 JPA 추상화를 사용하면 기술 종속적 코드도 없고, 데이터베이스 교체 시 JPA 설정만 변경하면 됩니다.
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByName(String name);
}
'🤿백엔드 내실 채우기' 카테고리의 다른 글
[Spring] 스프링 IoC(Inversion of Control Container)와 빈 (1) | 2024.12.01 |
---|---|
[Spring] Spring MVC 작동 방식 - DispatcherServlet은 어떤 역할을 하는가? (1) | 2023.10.24 |