1. 스프링 컨테이너 생성
2. 스프링 빈 생성
3. 의존관계 주입
4. 초기화 콜백
5. 사용
6. 소멸 콜백
7. 스프링 종료
스프링 컨테이너가 빈의 의존관계 주입을 마친 후 실행됩니다.
@Component
public class DatabaseInitializer {
private final DataSource dataSource;
public DatabaseInitializer(DataSource dataSource) {
this.dataSource = dataSource;
// 이 시점에는 아직 초기화가 완료되지 않음
}
@PostConstruct
public void init() {
// 모든 의존성 주입이 완료된 후 실행
try (Connection conn = dataSource.getConnection()) {
// 데이터베이스 초기화 작업 수행
}
}
}
스프링 컨테이너가 빈을 제거하기 전에 실행됩니다.
@Component
public class ResourceCleaner {
private final ExecutorService executorService;
public ResourceCleaner() {
this.executorService = Executors.newFixedThreadPool(10);
}
@PreDestroy
public void cleanup() {
// 컨테이너가 종료되기 전에 리소스 정리
executorService.shutdown();
try {
if (!executorService.awaitTermination(1, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
}
}
}
@Component
public class NetworkClient implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
// 초기화 작업
connect();
call("초기화 연결 메시지");
}
@Override
public void destroy() throws Exception {
// 소멸 전 작업
disconnect();
}
}
Pointcut은 어드바이스가 적용될 조인 포인트를 선별하는 표현식입니다.
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
@Aspect
@Component
public class SystemArchitecture {
// execution: 메서드 실행 조인 포인트 매칭
@Pointcut("execution(public * *(..))")
public void anyPublicMethod() {}
// within: 특정 타입 내의 모든 메서드 매칭
@Pointcut("within(com.example.service.*)")
public void inService() {}
// @annotation: 특정 애노테이션이 붙은 메서드 매칭
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethod() {}
// args: 특정 타입의 파라미터를 가진 메서드 매칭
@Pointcut("args(String, ..)")
public void stringArgumentMethod() {}
// bean: 특정 이름의 빈 매칭
@Pointcut("bean(*Service)")
public void beanPointcut() {}
}
@Aspect
@Component
public class ComplexPointcutExample {
// AND 조건
@Pointcut("execution(* com.example.service.*.*(..)) && @annotation(Transactional)")
public void transactionalServiceMethod() {}
// OR 조건
@Pointcut("within(com.example.service.*) || within(com.example.repository.*)")
public void businessLayer() {}
// NOT 조건
@Pointcut("execution(* com.example.*.*(..)) && !within(com.example.aspect.*)")
public void notInAspect() {}
}
@Aspect
@Component
public class PerformanceAspect {
// 모든 Service 클래스의 public 메서드
@Pointcut("execution(public * com.example.service.*Service.*(..))")
private void serviceOperation() {}
// 모든 Repository 메서드
@Pointcut("within(com.example.repository.*Repository)")
private void repositoryOperation() {}
// 위의 두 Pointcut을 조합
@Around("serviceOperation() || repositoryOperation()")
public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long endTime = System.currentTimeMillis();
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
System.out.println(className + "." + methodName + ": " + (endTime - startTime) + "ms");
}
}
}
@Aspect
@Component
public class DetailedPointcutExample {
// 특정 애노테이션이 있는 메서드만 대상
@Pointcut("@annotation(com.example.annotation.Auditable)")
public void auditableMethod() {}
// 특정 파라미터 타입을 가진 메서드
@Pointcut("args(org.springframework.security.core.Authentication, ..)")
public void authenticatedMethod() {}
// 리턴 타입이 특정 타입인 메서드
@Pointcut("execution(org.springframework.http.ResponseEntity *(..))")
public void returnsResponseEntity() {}
// 조합된 Pointcut 사용
@Around("auditableMethod() && authenticatedMethod()")
public Object auditAuthenticatedOperations(ProceedingJoinPoint joinPoint) throws Throwable {
// 보안 감사 로직
return joinPoint.proceed();
}
}
메서드 실행의 특정 시점에 로직을 추가할 수 있습니다.
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
// 메서드 실행 전 로직
}
@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
// 메서드 실행 후 로직
}
@AfterReturning(
pointcut = "execution(* com.example.service.*.*(..))",
returning = "result"
)
public void logAfterReturning(JoinPoint joinPoint, Object result) {
// 메서드 정상 반환 후 로직
}
@AfterThrowing(
pointcut = "execution(* com.example.service.*.*(..))",
throwing = "ex"
)
public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
// 메서드 예외 발생 시 로직
}
}
빈과 AOP의 실행 순서를 제어할 수 있습니다.
@Configuration
public class AppConfig {
@Bean
@DependsOn("dataSource") // dataSource 빈이 먼저 생성되도록 보장
public UserService userService() {
return new UserService();
}
}
@Aspect
@Component
@Order(1) // 낮은 숫자가 먼저 실행
public class SecurityAspect {
// 보안 관련 로직
}
@Aspect
@Component
@Order(2)
public class LoggingAspect {
// 로깅 관련 로직
}
Q: Pointcut 표현식의 주요 지시자들과 그 용도를 설명해주세요.
A: Pointcut 표현식의 주요 지시자들은 다음과 같습니다:
1. execution: 가장 많이 사용되며, 메서드의 실행을 매칭합니다. 반환타입, 패키지, 클래스, 메서드, 파라미터 등을 세세하게 지정할 수 있습니다.
2. within: 특정 타입 내의 모든 메서드를 매칭합니다. 주로 특정 패키지나 클래스 내의 모든 메서드를 지정할 때 사용합니다.
3. @annotation: 특정 애노테이션이 붙은 메서드를 매칭합니다. 커스텀 애노테이션을 만들어 사용할 때 유용합니다.
4. bean: 특정 이름의 빈을 매칭합니다. 스프링 빈의 이름으로 메서드를 선택할 때 사용합니다.
이러한 지시자들은 &&, ||, ! 등의 연산자를 통해 조합하여 사용할 수 있습니다.
A: @PostConstruct와 InitializingBean은 모두 빈의 초기화 시점에 로직을 수행하기 위한 방법이지만, 몇 가지 주요 차이가 있습니다. @PostConstruct는 자바 표준 애노테이션으로, 메서드 이름을 자유롭게 지정할 수 있고 코드를 깔끔하게 유지할 수 있습니다. 반면 InitializingBean은 스프링 전용 인터페이스로, 인터페이스 구현이 필요하고 메서드 이름이 고정됩니다. 일반적으로 @PostConstruct 사용이 권장되며, InitializingBean은 레거시 코드에서 주로 볼 수 있습니다.
Q: AOP의 각 어드바이스(@Before, @After, @Around 등)의 실행 순서는 어떻게 되나요?
A: AOP 어드바이스의 실행 순서는 다음과 같습니다:
1. @Around의 시작 부분
2. @Before
3. 실제 메서드 실행
4. @AfterReturning 또는 @AfterThrowing
5. @After
6. @Around의 종료 부분
여러 개의 AOP가 적용될 경우 @Order 애노테이션으로 순서를 제어할 수 있습니다. 또한, 같은 종류의 어드바이스라면 더 구체적인 포인트컷이 우선 순위를 가집니다.
Q: 스프링의 빈 생명주기 관리가 중요한 이유는 무엇인가요?
A: 스프링의 빈 생명주기 관리는 다음과 같은 이유로 중요합니다:
1. 리소스 관리: 데이터베이스 연결, 파일 핸들러 등의 리소스를 적절한 시점에 초기화하고 정리할 수 있습니다.
2. 의존성 보장: 의존관계 주입이 완료된 후에 초기화 로직을 실행할 수 있어 안전합니다.
3. 애플리케이션 컨텍스트 관리: 애플리케이션 시작과 종료 시의 필요한 작업들을 체계적으로 관리할 수 있습니다.