티스토리 뷰

반응형

서론

spring을 사용하다 보면 proxy 객체를 사용하는 경우가 많습니다.

그 전에 proxy라고 함은 디자인 패턴에서 Proxy Pattern를 말하고 있습니다.

Proxy Pattern이란 가짜 객체를 미리 만들어 두어 메모리에 진짜 객체가 올라가지 않게 하여

호출되기전 메모리 상 이득을 볼 수 있게 하는 디자인 패턴 중 하나 입니다.

Proxy Pattern에 대해서는 다른 블로그에서 잘 정리 되어 있고 아래 블로그들을 참고 사이트로 추천합니다.

https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%ED%94%84%EB%A1%9D%EC%8B%9CProxy-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90

 

💠 프록시(Proxy) 패턴 - 완벽 마스터하기

Proxy Pattern 프록시 패턴(Proxy Pattern)은 대상 원본 객체를 대리하여 대신 처리하게 함으로써 로직의 흐름을 제어하는 행동 패턴이다. 프록시(Proxy)의 사전적인 의미는 '대리인'이라는 뜻이다. 즉, 누

inpa.tistory.com

 

또한, spring에서는 dynamic proxy를 통해서 bean 객체들을 관리합니다.

이 것은 spring을 배우면서 가장 처음에 그리고 가장 중요하게 배우는 부분입니다.

이 부분도 방대하여 다른 블로그를 추천해주겠습니다.

https://incheol-jung.gitbook.io/docs/study/tobys-spring/undefined/6-aop

 

6장 AOP - Incheol's TECH BLOG

단위 테스트의 단위는 정하기 나름이다. 테스트 대역을 이용해 의존 오브젝트나 외부의 리소스를 사용하지 않도록 고립시켜서 테스트하는 것을 단위 테스트라고 부르겠다. 반면에 두 개 이상의

incheol-jung.gitbook.io

 

이제 본론으로 들어가서 왜 spring에서 proxy 객체를 사용하는 지에 대해서 글을 작성하게 됐나면

spring을 사용하다보면 proxy 객체를 쉽게 볼 수 있는데, 이를 한번 정리해야겠다 싶어서 작성하게 됐습니다.

 

1. AOP

위에서 추천한 블로그에서 잘 설명되어 있겠지만 proxy 객체를 생각하면 가장 먼저 떠오르는 것은 AOP입니다.

AOP는 코드에서 공통된 관심사를 분리하여 재사용성을 높이고 코드의 모듈성을 향상시키기 위해 사용됩니다.

Spring AOP는 주로 프록시 기반의 AOP를 구현합니다.

사용 예시로는 특정 메서드 호출 전후에 로깅, 트랜잭션 관리, 보안 검사 등의 공통 기능을 적용할 수 있습니다.

 

@Aspect
@Component
public class LoggingAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void logBeforeServiceMethod(JoinPoint joinPoint) {
        System.out.println("Before executing: " + joinPoint.getSignature().getName());
    }
}

 

위처럼 예시 코드를 작성이 가능합니다.

AOP를 사용할 때 주의할 점 몇가지를 나열하자면

 

  • AOP를 사용할 때는 포인트컷 표현식을 정확하게 지정해야 합니다. 잘못된 표현식은 예상치 못한 메서드에 어드바이스를 적용하게 만들 수 있습니다.
  • 프록시 기반 AOP는 내부 메서드 호출에는 적용되지 않습니다. 즉, 같은 빈 내부의 다른 메서드를 호출할 때는 AOP가 적용되지 않습니다.

2. Transactional

선언적 트랜잭션 관리에서 @Transactional 어노테이션이 붙은 클래스나 메서드에 대해

Spring은 트랜잭션을 자동으로 시작하고 종료하는 프록시 객체를 생성합니다.

이를 통해서 비즈니스 로직에 집중할 수 있으며, 트랜잭션의 시작과 종료를 수동으로 관리할 필요가 없습니다.

 

@Service
public class TransactionalService {
    @Transactional
    public void performTransactionalOperation() {
        // 트랜잭션 내에서 데이터베이스 작업 수행
    }
}

 

트랙잭션을 한마디로 설명하면 DB에서의 데이터 무결성을 보장하기 위해서 사용됩니다.

Spring 코드에서 DAO를 통해서 DB에 접근 할 때 트랜잭션을 요구하는 로직이 많고 이는 코드에서 중복을 만들게 됩니다.

@Transaction을 사용할 때 주의점은 

 

  • @Transactional 어노테이션은 public 메서드에만 적용해야 합니다. 비공개(private), 보호된(protected) 또는 패키지-프라이빗 메서드에 적용된 @Transactional은 무시됩니다.
  • 트랜잭션 설정이 잘못되어 롤백이 예상대로 작동하지 않을 수 있습니다. 예를 들어, unchecked 예외는 기본적으로 롤백을 유발하지만, checked 예외는 그렇지 않습니다.

3. Spring Security

Spring Security는 메서드 수준의 보안을 제공하기 위해 프록시 객체를 사용합니다.

사용자 역할에 따라 메서드 접근 권한을 제한하거나, 사용자 인증 여부를 검사하는 등의 보안 관련 작업을 수행합니다.

 

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // Security configuration
}

@Service
public class SecureService {
    @PreAuthorize("hasRole('ADMIN')")
    public void adminOnlyOperation() {
        // 관리자만 실행할 수 있는 작업
    }
}

 

거의 모든 사이드 프로젝트를 시작하게 될 때, Spring Security를 접하게 되는데,

저 또한 처음 시작했을 때 꽤나 어려웠던 경험이 있었습니다.

 

  • @PreAuthorize와 같은 보안 어노테이션은 메서드 수준 보안을 위해 사용됩니다. 이 어노테이션을 잘못 적용하면 예상치 못한 접근 제어 문제가 발생할 수 있습니다.(보통 Controller에서 많이 쓰입니다.)
  • 스프링 시큐리티 설정은 복잡할 수 있으며, 잘못 구성된 경우 애플리케이션의 보안 취약점이 될 수 있습니다.

4. Lazy Bean / Lazy Loading

스프링은 특정 빈의 지연 로딩을 위해 프록시 객체를 사용할 수 있습니다.

이는 애플리케이션의 시작 시간을 단축하고, 실제로 사용될 때까지 객체의 생성을 지연시킵니다.

예를 들어, @Lazy 어노테이션을 사용하여 지연 로딩을 구성할 수 있습니다.

 

@Service
@Lazy
public class LazyService {
    public LazyService() {
        System.out.println("LazyService 생성");
    }
}
  • @Lazy 어노테이션을 사용할 때는 해당 빈이 실제로 필요할 때까지 초기화되지 않는다는 것을 명심해야 합니다. 이로 인해 예상치 못한 지연이 발생할 수 있습니다.

 

또한, JPA를 사용할 때 Lazy Loading에 대해서 많이 들어보셨을 겁니다.

이는 OneToMany 관계에 있는 테이블에서 한번에 모든 데이터를 긁어오게 되면 사용하지 않을 경우 불필요한 작업이라

proxy를 통해서 지연 로딩을 하는 방법을 사용합니다.

 

@Data
public class DataEntity {

    @Id
    private String id;

    @OneToMany(fetch = FetchType.LAZY)
    private List<String> otherEntities;
}

 

Lazy Loading의 경우에는 무조건적으로 사용하기엔 N+1문제가 발생합니다.

 

https://incheol-jung.gitbook.io/docs/q-and-a/spring/n+1

 

N+1 문제 - Incheol's TECH BLOG

Query를 실행하도록 지원해주는 다양한 플러그인이 있다. 대표적으로 Mybatis, QueryDSL, JOOQ, JDBC Template 등이 있을 것이다. 이를 사용하면 로직에 최적화된 쿼리를 구현할 수 있다.

incheol-jung.gitbook.io

해당 문제를 상황에 맞게 해결해가는 절차는 위 블로그에서 굉장히 잘 다루었습니다.

 

5. Caching

스프링의 캐싱 추상화는 메서드 호출의 결과를 캐싱하기 위해 프록시를 사용합니다.

이를 통해 동일한 메서드 호출에 대한 반복적인 처리를 방지하고 성능을 향상시킬 수 있습니다.

캐시된 결과가 있을 경우, 실제 메서드를 호출하지 않고 캐시에서 결과를 반환합니다.

 

@Service
public class CachingService {
    @Cacheable("items")
    public String expensiveOperation(String param) {
        // 시간이 많이 걸리는 연산
        return "Result";
    }
}

 

캐싱은 주로 시간이 많이 걸리는 연산에 대해서 이전에 계산이 된 결과라면

메모리에 올려놓고 중복된 계산을 피하는 것입니다. 

 

  • 캐싱을 사용할 때는 캐시 키 충돌을 피하기 위해 적절한 키 생성 전략을 사용해야 합니다. 잘못된 키 전략은 예상치 못한 결과를 초래할 수 있습니다.
  • 캐시된 데이터가 최신 상태인지 항상 확인해야 합니다. 데이터 일관성을 유지하는 것이 중요합니다.

6. 비동기 처리

@Async 어노테이션을 사용하여 메서드의 실행을 별도의 스레드로 비동기적으로 실행할 수 있습니다.

이 경우 Spring은 내부적으로 해당 메서드를 호출하는 프록시 객체를 생성하여 비동기적인 메서드 호출을 처리합니다.

 

@Service
public class AsyncService {

    @Async
    public CompletableFuture<String> performAsyncTask() {
        // 비동기 작업 수행
        return CompletableFuture.completedFuture("Result");
    }
}

 

 

  • @Async 어노테이션이 붙은 메서드는 void나 Future 타입의 값을 반환해야 합니다. 그렇지 않으면, 비동기 처리 결과를 제대로 처리할 수 없습니다.
  • 비동기 메서드에서 발생한 예외는 호출 스택에 직접 전파되지 않습니다. 따라서 Future를 반환하는 경우, 결과를 가져올 때 발생한 예외를 적절히 처리해야 합니다.
  • 같은 클래스 내부에서 @Async 메서드를 호출하는 경우, 비동기 처리가 적용되지 않습니다. 이는 Spring의 프록시 기반 동작 때문입니다. 외부 호출을 통해 비동기 메서드를 사용해야 합니다.
728x90
반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함
250x250