1. 프록시 패턴 (Proxy Pattern)
- 정의:
프록시 패턴은 어떤 객체에 대한 접근을 제어하기 위해, 그 객체를 대신하는 ‘대리인(Proxy)’ 객체를 두는 디자인 패턴입니다. - 역할:
실제 객체에 접근하기 전에 추가 작업(예: 접근 제어, 캐싱, 로깅, 지연 초기화 등)을 수행합니다. - 예시:
- 원격 객체에 대한 접근을 대리하는 원격 프록시
- 접근 권한 검사를 하는 보호 프록시
- 무거운 객체를 필요할 때만 생성하는 지연 로딩 프록시 등
프록시 패턴의 장점
| 장점 | 설명 |
| 접근 제어 | 객체에 대한 접근을 제어할 수 있습니다. (예: 권한 검사) |
| 부가기능 추가 | 원본 객체의 기능에 부가적인 기능을 추가할 수 있습니다. (예: 로깅, 캐싱 등) |
| 지연 초기화 | 실제 객체를 나중에 생성하여 성능을 최적화할 수 있습니다. (예: 무거운 객체 로딩 시점 지연) |
프록시 패턴 예시 (수동 구현)
인터페이스와 실제 서비스
public interface UserService {
void saveUser(String username);
}
public class UserServiceImpl implements UserService {
@Override
public void saveUser(String username) {
System.out.println(username + "님 저장 완료!");
}
}
- UserService는 핵심 기능(회원 저장)을 위한 인터페이스.
- UserServiceImpl은 그 실제 구현체로, saveUser가 호출되면 저장 완료 메시지를 출력합니다.
프록시 클래스
public class UserServiceProxy implements UserService {
private final UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void saveUser(String username) {
System.out.println("[트랜잭션 시작]"); // 부가 기능(트랜잭션 시작)
target.saveUser(username); // 실제 핵심 기능 호출
System.out.println("[트랜잭션 커밋]"); // 부가 기능(트랜잭션 커밋)
}
}
- UserServiceProxy는 핵심 기능을 호출하기 전후에 부가 기능(여기선 트랜잭션 관리)을 추가하는 역할을 해요.
- 핵심 객체(UserServiceImpl)를 target으로 받아서 saveUser 호출 시 앞뒤로 로그를 찍습니다.
- 클라이언트는 이 프록시 객체를 통해 기능을 호출하면, 핵심 기능 + 부가 기능이 같이 실행됩니다.
실행부
public class Main {
public static void main(String[] args) {
UserService realService = new UserServiceImpl();
UserService proxy = new UserServiceProxy(realService);
proxy.saveUser("홍길동");
}
}
- UserServiceImpl 객체를 직접 쓰지 않고, 프록시(UserServiceProxy)를 생성해 사용합니다.
- 호출 시 부가 기능과 핵심 기능이 함께 동작합니다.
실행결과
[트랜잭션 시작]
홍길동님 저장 완료!
[트랜잭션 커밋]
2. AOP (Aspect-Oriented Programming)
- 정의:
AOP는 핵심 비즈니스 로직과는 별개로, 공통 관심사(cross-cutting concerns) — 예를 들어, 로깅, 트랜잭션 관리, 보안 검사 등을 모듈화해서 코드 중복 없이 재사용할 수 있도록 하는 프로그래밍 패러다임입니다. - 핵심 개념:
- Aspect (관점): 공통 관심사 모듈
- Advice: 공통 관심사가 실제로 실행되는 코드 (예: Before, After, Around)
- Join point: Advice가 적용될 수 있는 지점 (예: 메서드 호출)
- Pointcut: Advice가 적용될 Join point 집합
- Weaving: Advice를 핵심 코드에 적용하는 과정
AOP 없는 코드
public class UserService {
public void createUser() {
System.out.println("[Log] 사용자 생성 시작"); // 로깅 코드
// 회원가입 로직
System.out.println("회원가입 완료");
System.out.println("[Log] 사용자 생성 끝"); // 로깅 코드
}
}
- createUser() 안에 비즈니스 로직 + 로깅 로직이 섞임
- 이걸 여러 메서드마다 반복하면 코드가 지저분해지고 중복이 많아집니다.
AOP를 사용한 코드 (Spring)
1. 핵심 로직
@Service
public class UserService {
public void createUser() {
System.out.println("회원가입 로직 실행");
}
}
- @Service: 이 클래스를 서비스 레이어의 컴포넌트로 등록해주는 Spring 어노테이션입니다.
- createUser() 메서드에는 오직 핵심 기능만 있습니다.
- 로깅 등 공통 기능은 따로 Aspect에서 처리됩니다. 이것이 AOP의 핵심입니다.
2. 공통 기능: 로깅 Aspect
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
System.out.println("[AOP] 메서드 실행 전 로그");
}
@After("execution(* com.example.service.*.*(..))")
public void logAfter() {
System.out.println("[AOP] 메서드 실행 후 로그");
}
}
| 코드 | 의미 |
| @Aspect | 이 클래스가 AOP의 Aspect(관심사 모듈) 임을 나타냅니다. |
| @Component | Spring이 이 클래스를 빈으로 자동 등록하게 합니다. |
| @Before(...) | 지정된 메서드 실행 전에 수행할 공통 로직 (Advice)입니다. |
| @After(...) | 지정된 메서드 실행 후에 수행할 공통 로직입니다. |
| execution(* com.example.service.*.*(..)) | com.example.service 패키지 아래의 모든 메서드에 적용하는 Pointcut 표현식입니다. |
실행결과
[AOP] 메서드 실행 전 로그
회원가입 로직 실행
[AOP] 메서드 실행 후 로그
왜 사용하는가?
- 관심사 분리 (Separation of Concerns)
- 코드 중복 제거
- 유지보수 편리
- 비즈니스 로직 간결화
'JAVA' 카테고리의 다른 글
| SpringBoot VS Spring (1) | 2025.06.16 |
|---|---|
| POJO (4) | 2025.05.21 |
| JAVA 깊은 복제와 얕은 복제 (1) | 2025.04.28 |
| JAVA 어노테이션 (0) | 2025.04.28 |
| 자바의 제네릭에 대하여 (3) | 2025.04.23 |