💡 AOP(Aspect Oriented Programming)란?
- 기존에는 개발자 또는 운영자에게 필요한 코드를 비즈니스 로직 코드와 함께 작성하였습니다.
public int total() {
long start = System.currentTimeMillis();
int result = kor + eng + math + com; // 사용자 코드 부분(주 업무)
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long end = System.currentTimeMillis();
String message = (end - start) + "ms 시간이 걸렸습니다."; // 개발자 및 운영자 코드 부분(부 업무)
System.out.println(message);
return result;
}
- 다만 이처럼 코드를 작성할 경우 코드가 복잡해져서 핵심 비즈니스 로직에 집중하지 못할 수 있고, 변경사항이 있을 시 비즈니스 로직이 작성되어 있는 파일을 직접 열어서 수정해야 하는 불편함이 생기게 됩니다.
- 따라서 트랜잭션, 로그, 보안 등과 같이 핵심 로직에 공통적으로 포함되는 부가적인 기능들을 따로 분리하여 관리할 수 있도록 하는 기법이 AOP(Aspect Oriented Programming)입니다.
- 이는 관점 지향 프로그래밍이라고도 불리며, 관점 지향이란 쉽게 말해 어떤 로직을 핵심적인 관점과 부가적인 관점으로 나누어서 보고, 그 관점을 기준으로 각각을 모듈화 하겠다는 것입니다.
- 여기서 핵심적인 관점은 비즈니스 로직(주 업무), 부가적인 관점은 관리자 코드(부 업무)라고 볼 수 있으며, 부가적인 관점은 흩어진 관심사(Crosscutting Concerns)라고 부르기도 합니다.
💡 AOP 주요 개념
- Aspect: 부가적인 관점, 즉 흩어진 관심사를 모듈화한 것
- Target: Aspect를 적용하는 곳
- Advice: 실질적으로 어떤 일을 해야할 지에 대한 것(실질적인 부가기능을 담은 구현체)
- JointPoint: Advice가 적용될 위치(메서드 진입 시점, 생성자 호출 시점, 필드에서 값을 꺼내올 때 등 다양한 시점에 적용 가능)
- PointCut: JointPoint의 상세한 스펙을 정의한 것으로, 'A라는 메소드의 진입 시점에 호출할 것'과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있음
💡 Spring AOP - Proxy
- 이를 실현하기 위해서는 관점에 따라 나눈 두 코드를 연결시켜줄 수 있는 도구가 필요한데, 스프링에서는 이를 프록시(Proxy)를 통해 해결합니다.
- 프록시는 타겟(Target)을 감싸서 타겟의 요청을 대신 받아주는 랩핑(Wrapping) 오브젝트입니다.
- 호출자(클라이언트)에서 타겟을 호출하게 되면 실제로는 타겟이 아닌, 타겟을 감싸고 있는 프록시 객체가 호출되어, 타겟 메소드 호출 전에 선처리, 타겟 메소드 호출 후에 후처리를 진행합니다.
- 프록시는 아래와 같은 구문을 통해 생성됩니다.
Exam proxy = Proxy.newProxyInstance(loader, interfaces, h);
- 여기서 인자로 들어가는
loader
는 프록시가 호출할 업무가 있는 클래스 정보이고,interfaces
는loader
로 대입된 클래스가 구현하는 인터페이스를 말합니다. - 다만
interfaces
는 보다시피 복수형이기 때문에 배열의 형태로 전달되어야 합니다. - 마지막으로
h
는 우리가 실행할 관리자 코드, 즉 부가적인 업무를 말합니다. - 따라서
h
에는 다음과 같은 익명 클래스가 작성될 수 있습니다.
Exam proxy = (Exam) Proxy.newProxyInstance(NewlecExam.class.getClassLoader(), new Class[] {Exam.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
Object result = method.invoke(exam, args);
long end = System.currentTimeMillis();
String message = (end - start) + "ms 시간이 걸렸습니다.";
System.out.println(message);
return result;
}
}
);
💡 AOP 종류
- AOP를 사용할 경우 Aspect 실행 시점을 지정하는데에는 다음과 같은 어노테이션을 사용할 수 있습니다.
@Before
: Target Method가 호출되기 전에 Advice 기능을 수행합니다.@After
: Target Method 결과와 상관없이 Target Method가 완료되면 Advice 기능을 수행합니다.@AfterReturning
: Target Method가 성공적으로 결과값을 반환한 경우에만 Advice 기능을 수행합니다.@AfterThrowing
: Target Method가 수행 중 예외를 던지는 경우에만 Advice 기능을 수행합니다.@Around
: Advice가 Target Method를 감싸는 형태로, Target Method 호출 전과 후에 Advice 기능을 수행합니다.
💡 AOP 사용 예제
- 스프링 AOP를 사용하기 위해선 다음과 같은 의존성 추가 작업을 수행해주어야 합니다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 다음으로 Aspect로 사용될 클래스를 생성하여
@Aspect
어노테이션을 붙여주고,@Component
어노테이션을 통해 스프링 빈으로 등록합니다.
@Component
@Aspect
public class PerfAspect {
@Around("execution(* com.baegwon..*.EventService.*(..))")
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable{
long begin = System.currentTimeMillis();
Object retVal = pjp.proceed(); // 메서드 호출 자체를 감쌈
System.out.println(System.currentTimeMillis() - begin);
return retVal;
}
}
@Around
어노테이션은 타겟 메소드를 감싸서 특정 Advice를 실행하겠다는 의미이며, 위 코드에서 Advice는 타겟 메소드가 실행된 시간을 측정하기 위한 로직으로 구현되어 있습니다.execution(\* com.baegwon..\*.EventService.\*(..))
가 의미하는 바는com.baegwon
아래의 패키지 경로의EventService
객체의 모든 메서드에 이 Aspect를 적용하겠다는 의미입니다.- 이 외에도 특정 어노테이션이 붙은 포인트에 해당 Aspect를 실행할 수 있는 기능과
@Around("@annotation(PerLogging)")
- 스프링 빈의 모든 메서드에 적용할 수 있는 기능을 제공합니다.
@Around("bean(simpleEventService)")
- 이 외에도 특정 어노테이션이 붙은 포인트에 해당 Aspect를 실행할 수 있는 기능과
- Around Advice에서 사용할 공통 기능 메소드는 대부분 파라미터로 전달받은
ProceedingJointPoint
의proceed()
메소드만 호출하면 됩니다. ProceedingJoinPoint
인터페이스는 개발도중 호출되는 대상 객체에 대한 정보, 실행되는 메서드에 대한 정보, 메서드를 호출할 때 전달된 인자에 대한 정보에 접근할 수 있도록 해줍니다.- 이제 위 Aspect를 적용할 Target을 생성해줍니다.
public interface EventService {
void createEvent();
void publishEvent();
void deleteEvent();
}
@Component
public class SimpleEventService implements EventService {
@Override
public void createEvent() {
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("Created an event");
}
@Override
public void publishEvent() {
try {
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("Published an event");
}
@Override
public void deleteEvent() {
System.out.println("Delete an event");
}
}
@Service
public class AppRunner implements ApplicationRunner {
@Autowired
EventService eventService;
@Override
public void run(ApplicationArguments args) throws Exception {
eventService.createEvent();
eventService.publishEvent();
eventService.deleteEvent();
}
}
- 이후 프로그램을 실행해보면 아래와 같은 실행 결과를 확인할 수 있습니다.
Created an event
1003
Published an event
1000
Delete an event
0
📌 References
'🥑 Web Technoloy' 카테고리의 다른 글
JPA와 Hibernate 그리고 Spring Data JPA (0) | 2023.06.04 |
---|---|
Lombok이란? (1) | 2023.06.04 |
SpringBoot에서 SMTP를 활용한 메일 전송 구현하기 (0) | 2022.11.16 |
Spring Interceptor 개념 정리, 적용법 (0) | 2022.11.15 |
OAuth 2.0 개념 정리 (0) | 2022.11.11 |