🤿백엔드 내실 채우기

[Spring] 스프링 3대 요소 - DI, AOP, PSA 정리

pkyung 2024. 11. 30. 13:07
반응형

 

 

안녕하세요. 오늘은 스프링의 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 용어

  1. Target : 부가기능을 부여할 대상 (UserService)
  2. Advice : 타깃에게 제공할 부가기능을 담은 모듈 (사용자 로그인 했는지 확인하는 로직)
  3. Join Point : Advice가 적용될 수 있는 위치
  4. 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에서 부가기능을 추가할 때 사용하는 패턴

  1. 빈 객체를 생성한 뒤에 빈 후처리에게 전달합니다. 
  2. 빈 후처리기는 Advisor를 싹 훑어보고 전달받은 빈이 프록시 적용 대상인지 확인합니다.
  3. 프록시 생성 대상 빈들을 대상으로 프록시 객체를 생성합니다.
  4. 프록시를 생성한 빈이라면 프록시 객체를, 프록시를 생성하지 않은 빈이라면 그냥 빈을 전달합니다.
  5. 빈 후처리기에게 전달받은 객체를 컨테이너 빈으로 등록합니다. 

 

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);
}

 

반응형