Spring AOP
✒️ 2025-05-28 11:41 내용 수정
Aspect-Oriented Programming
- 참고 자료 : Spring Aspect Oriented Programming with Spring
- 공통 기능의 구현과 핵심 기능의 구현을 분리한 방법이다.
- 반복되는 로직들을 모듈화하여 필요할 때 호출해서, 여러 객체에 공통적으로 적용할 수 있는 기능을 분리해서 재사용성을 높여줄 수 있다.
- OOP(객체 지향 프로그래밍)에서는 모듈화의 단위 핵심이 class이지만, AOP(관점 지향 프로그래밍)에서는 모듈화의 단위가 관점이다.
| 용어 | 설명 |
|---|---|
| Aspect | 여러 클래스를 가로지르는 관심 사항의 모듈화, Advice + Pointcut |
Spring AOP에선 Aspect를 일반 클래스로 구현하거나, 일반 클래스에 @Aspect Annotation을 추가하여 구현함 |
|
| Join point | 프로그램 실행 지점(ex: 예외를 담당하는 메소드의 실행)을 의미하며, Spring에서는 항상 메소드 실행 지점을 의미함 |
| Advice | 특정 Join point에서 Aspect에 의해 실행되는 액션으로, 반복 로직의 구현체 |
| around : 메소드 실행 전 후 | |
| before : 메소드 실행 전 | |
| after : 메소드 실행 후 | |
| AfterReturning : 메소드가 반환값을 반환한 후 | |
| AfterThrowing : 메소드가 예외처리를 한 후 | |
| Pointcut | Join point보다 더 세분화된 지점을 의미함(ex: 특정 이름일 때 메소드 실행) |
| Introduction | 추가적인 메소드나 필드를 타입에 선언하는 것 |
| Target object | 한 개 이상의 Aspect에 의해 조언을 받는 객체 |
| AOP proxy | AOP 프레임워크에 의해 Aspect 조건을 구현하기 위한 객체 |
| Weaving | 다른 어플리케이션과 연결된 Aspect나 advise 객체를 생성하기 위한 객체 |
Spring에서 AOP 만들기
| Annotation | 설명 |
|---|---|
@Aspect |
Aspect 클래스를 선언할때 사용하는 Annotation |
@Pointcut(execution()) |
pointcut을 지정하는 Annotation. execution()를 사용해서 특정 메소드 실행 지점을 설정할 수 있음 |
@Before("메소드이름") |
특정 메소드 실행 전에 수행할 Advice(액션)을 설정 |
@After("메소드이름") |
특정 메소드 실행 후에 수행할 Advice(액션)을 설정 |
@EnableAspectJAutoProxy |
AspectJ 스타일의 선언적 Aspect를 지원하도록 활성화하는 Annotation |
| AspectJ 스타일의 AOP를 활성화 하면 스프링은 @Aspect Annotation을 가진 클래스를 찾아서 프록시를 생성하고, 해당 Aspect를 적용 |
- Annotation 기반 설정 파일#프로젝트 설정, 방명록 만들기대로 파일들 및 패키지를 준비한다.
- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver 에 접속해서 현재 pom.xml에서 properties에 있는 org.aspectj-version의 버전과 동일한 버전의 Maven 항목을 복사해 dependencies에 추가한다. (1.9.0버전 사용)
<properties>
<java-version>11</java-version>
<org.springframework-version>5.1.20.RELEASE</org.springframework-version>
<!-- 이 항목의 버전과 동일한 버전으로 받는다 -->
<org.aspectj-version>1.9.0</org.aspectj-version>
<org.slf4j-version>1.7.25</org.slf4j-version>
</properties>
<!-- aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.0</version>
</dependency>
- src/main/resources 폴더에 advice 패키지를 만들고 Advice 클래스를 만든다.
package advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect//Aspect 클래스를 선언할때 사용하는 어노테이션
public class Advice {
long start;
//execution() : 메서드 실행을 나타내는 키워드
//* : 반환타입
//dao.*DAO : dao패키지의 DAO로 끝나는 모든 클래스
//.* : 모든 메서드
//(..) : 메서드의 매개변수의 개수는 상관없음
@Pointcut("execution(* dao.*DAO.*(..))")
public void myPoint() {};
@Before("myPoint()")
public void before(JoinPoint jp) { //JoinPoint : pointcut이 걸린 위치의 정보를 받는 클래스
System.out.println("----before : " + jp.getSignature());
//getSignature() : 현재 JoinPoint에서 실행되는 메서드의 서명정보 반환
//서명정보에는 메서드명, 반환형, 파라미터 타입 등등이 존재
start = System.currentTimeMillis();
}
@After("myPoint()")
public void after(JoinPoint jp) {
System.out.println("----after: " + jp.toLongString());
long end = System.currentTimeMillis();
System.out.printf("[수행시간] : %d(ms)\n",end-start);
}
}
- src/main/resources 폴더에 dao 패키지를 만들어 TestDAO 클래스를 만든다.
- test() 메소드의 수행 시간을 Advice에서 캐치하려는데, 메소드 1개를 호출하는 시간은 0초에 가깝기 때문에 임의로 시간 딜레이를 준다.
package dao;
public class TestDAO {
public void test() {
System.out.println("---- call TestDAO.test() ----");
try {
Thread.sleep(3000); // 3초 대기를 걸어준다.
} catch (Exception e) {
}
}
}
- Context_3_dao 클래스에 객체를 생성한다.
- AspectJ 스타일의 AOP를 활성화 하면 스프링은 @Aspect 어노테이션을 가진 클래스를 찾아서 프록시를 생성하고, 해당 Aspect를 적용한다.
package context;
import org.apache.ibatis.session.SqlSession;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import advice.Advice;
import dao.TestDAO;
@Configuration
@EnableAspectJAutoProxy
public class Context_3_dao {
@Bean
public TestDAO testDAO() {
return new TestDAO();
}
@Bean
public Advice advice() {
return new Advice();
}
}
- 프로젝트를 실행하고 console 창을 보면 메소드 수행 시간이 Thread로 딜레이를 줬던 거의 3초가 나온다.