Spring은 Spring Triangle이라고 부르는 세가지 개념을 제공해준다. 각각 IoC, AOP, PSA를 일컫는다. AOP는 Aspect Oriented Programming의 약자로 측면/양상 지향적인 프로그래밍이라는 의미이다.
AOP는 중복되는 코드를 떼어내서 분리하고 메소드는 가자 해야할 작업만 갖고 있자는 개념이다. 여러군데서 중복되는 코드가 AOP에서 말하는 aspect라고 이해하면 된다.
프록시는 원래 객체를 감싸고 있는 객체이다. 타입은 동일하며 트록시 객체가 원래 객체를 감싸서 클라이언트 요청을 처리하게 하는 패턴이다.
사용하는 이유는 접근을 제어하거나, 부가 기능을 추가하고 싶을 때 사용한다.
ex) 한 서비스 클래스에 두 메소드가 있다고 가정했을 때 같은 기능을 두 메소드에 직접 코드를 적어 넣는 것은 중복되고 비효율 적임 때문에 프록시 객체를 구현해서 넣어줌
위 처럼 하면 기능을 추가 할 수는 있지만, 프록시 객체에 중복 코드가 발생할 수 있고, 다른 클래스에서도 동일한 기능을 사용하고자 할 때, 매번 코딩을 해줘야해서 효율적이지 못하다.
이런 문제를 해결해주는게, 런타임시에 동적으로 프록시객체를 만들어주는 것인데 그것이 스프링 AOP이다.
어떠한 클래스가 AOP의 대상이라면 기존 클래스의 빈이 만들어질 때 AOP가 프록시(기능이 추가된 클래스)를 자동으로 만들고 원본 클래스 대신 프록시를 빈으로 등록한다.
그리고 원본 클래스가 사용되는 지점에서 프록시를 대신 사용한다.
예를 들어, @Transactional 어노테이션이 붙어있으면 OwnerRepository 타입의 프록시가 만들어지고 AOP에 의해 생성되는 OwnerRepository 프록시에는 @Transactional 어노테이션에 담긴 코드가 삽입된다. 때문에 원래는 SQL 실행문 앞 뒤에 commit/rollback 코드가 항상 붙어 반복적으로 사용해야하지만 @Transactional 어노테이션을 사용함으로서 프록시에 그 코드를 넣어서 반복,중복되는 코드를 생략할 수 있게 된다.
먼저 메소드에 내가 정의하고 싶은 어노테이션을 작성한다.
@어노테이션명
내가 정의한 어노테이션은 제공하는 것이 아닌 새로 정의해야는 어노테이션이기 때문에, 에러가 발생하고 quick fixes 창에서 @interface타입 인터페이스를 만들 수 있다. 해당 인터페이스를 정의하고 사용하려면 몇 가지 설정을 알아야한다.
위와 같은 설정을 하고 나면, 어디에 AOP를 적용할 것인지 정의하였다. 이제 해당 메소드에 어떤 기능을 추가할 것이지 알려주는 실제 aspect를 구현해야한다.
같은 패키지에 LogAspect 클래스를 만든다.
// 메소드 성능 측정 기능
package org.springframework.samples.petclinic.owner;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
@Component
@Aspect
public class LogAspect {
Logger logger = LoggerFactory.getLogger(LogAspect.class);
@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// @LogExecutionTime 애노테이션이 붙어있는 타겟 메소드를 실행
Object proceed = joinPoint.proceed();
stopWatch.stop();
logger.info(stopWatch.prettyPrint());
return proceed; // 결과 리턴
}
}
이제 해당 메소드 실행 시 메소드 측정 결과가 콘솔에 출력되게 된다.