java

[201019] spring AOP pointcut annotation

hjk927 2020. 10. 19. 11:04

 

.

 

spring framework를 공부하던 중 AOP 파트에서 에러를 맞닥뜨렸다. 

책을 보고 그대로 따라하고 있었는데 프로그램이 원하는 대로 돌아가지 않아서 꽤 구글링을 했다. 

 

AOP를 구현하고 @Aspect와 @Around를 사용한 단순한 예제를 실습했는데, 실행하니 BeanCreationException이 발생했다. 계속 읽어보면 circular reference라는 게 발생한것 같기도 한데, 그건 잘 모르겠고... 아무튼 Bean을 생성할 수 없다. 부족한 실력으로 짐작해봤을 때 왠지 @Pointcut execution에서 표현식 오류가 생긴 것 같다는 생각이 들었다. 왜냐면 거기 말고는 책과 다른 점이 없었고, 평소에도 정규식 작성 부분이 약했기 때문에... 

 

@Pointcut("execution(public * example07..*(..))")

 

책을 보고 작성한 execution 표현식은 위와 같다. example07 패키지의 하위에 있는 패키지까지를 대상으로 public 메소드에 공통 기능을 정의한다. 이때 매개변수 유형이나 파라미터 개수는 관계없이 정의한다. 

 

예제 유형을 보고 그대로 적었다고 생각했는데, 위와 같이 적으니 BeanCreationException이 발생한 것이다. 결론부터 말하자면, 수정한 표현식은 아래와 같다. 

 

@Pointcut("execution(* com.example07.*.*(..))")

 

내 코드가 책과 달랐던 점은 package 구조 뿐인데, 패키지 이름에서 .으로 구분된 가장 마지막에 있는 단어만 적어주면 되는 줄 알았다. 지금 생각하면 정말 말도 안 되는 생각이지만... 부가적으로 "execution(* com.example07..*.*(..))" 으로 적었을 때도 BeanCreationException이 발생했다. 

 

AOP는 프록시를 구현해주는 기법으로 지정한 메소드에 공통 기능을 추가할 수 있다. 데코레이터 패턴이나 파이썬의 @decorator과도 유사하다는 느낌이 들었다. 이 공통 기능을 Aspect라고 하며, 다른 것처럼 annotation을 사용해서 기능을 적용할 수 있다. Aspect를 적용할 클래스에 @Aspect 애노테이션을 붙여주고, 빈 메소드에 @Pointcut 애노테이션을 붙이고 execution 명시자를 통해 aspect 적용 범위를 정의한다. 공통 기능이 구현된 메소드에는 @Before, @After, @Around, @AfterReturning, @AfterThrowing 같은 애노테이션을 통해 공통 기능이 사용될 시점을 정의할 수 있다. 다양한 시점에 기능을 사용할 수 있는 @Around 애노테이션이 가장 자주 쓰인다고 한다. 

 

execution 명시자는 Advice를 적용할 메소드를 지정할 때 사용한다. 모든 메소드를 직접 지정해줄 수는 없으니, 표현식을 잘 사용하면 편하다. execution 기본식은 다음과 같다. 

 

execution (modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern (param-pattern) throws -pattern?)

 

 

modifiers-pattern : 접근 지정자(public, protected, private. AOP는 public 메소드에만 지정 가능하기 때문에 실상 의미는 없다고 한다.)

ret-type-pattern : 반환형 

declaring-type-pattern : 패키지|클래스명

name-pattern : 메소드의 이름 유형

param-pattern : 메소드의 파라미터 

throws-pattern : 메소드가 던지는 Exception 유형 

 

기본식 예시는 아래와 같다. 

execution(public * * (..)) 모든 public 메소드
execution(* set*(..)) 이름이 set으로 시작하는 모든 메소드
execution(* com.xyz.servie.AccountService.*(..)) AccountService 인터페이스에 의해 정의된 모든 메소드
execution(* com.xyz.service.*.*(..)) service 패키지에 정의된 모든 메소드
execution(* com.xyz.service..*.*(..)) service 패키지나 하위 패키지에 정의된 모든 메소드
within(com.xyz.service.*) service 패키지의 모든 join point
within(com.xyz.serivce..*) service 패키지나 하위 패키지에 있는 모든 join point 
this(com.xyz.service.AccountService) AccountService 인터페이스를 구현한 proxy에 있는 모든 join point 
target(com.xyz.service.AccountService) AccountService 인터페이스를 구현한 대상 객체의 모든 join point
args(java.io.Serializable) 1개의 파라미터를 가지고 매개변수가 Serializable인 모든 join point
@target(org.springframework.transcation.annotation.Transactional) @Transactional 애노테이션이 있는 대상 객체에 있는 모든 join point
@whitin(org.springframework.transaction.annotation.Transactional) 대상 객체의 선언 타입이 @Transactional 애노테이션을 가지고 있는 모든 join point
@annotation(org.springframework.transaction.annotation.Transactional) 실행중인 메소드가 @Transactional 애노테이션을 가지는 모든 join point
@args(com.xyz.security.Classified) 1개 파라미터를 가지고, 매개변수 유형이 @Classified annotation를 가지는 모든 join point 
bean(tradeService) 이름이 tradeService인 Spring bean에 있는 모든 join point
bean(*Service) 와일드카드 표현식과 일치하는 Spring bean에 있는 모든 join point 

 

정리를 해보니까 내가 쓴 코드 중 "execution(* com.example07..*.*(..))"에서 오류가 났던 건, com.example07 패키지 아래에 Aspect 대상인 클래스가 있어서 그런 것 같다는 생각이 든다. 그리고 표현식을 잘 외워야겠다는 생각이 든다... 

 

이외에도 오류를 고치다가 프로젝트가 이상해져서 다른 패키지에 있는 클래스 import에 오류가 발생하기도 했다. 이건 project 탭에서 clean해서 해결했다. Spring을 배워서 웹 프로젝트에 응용해보는게 목표인 만큼, 더 열심히 배워야겠다. 

 

 

참고자료

docs.spring.io/spring-framework/docs/4.3.15.RELEASE/spring-framework-reference/html/aop.html

stackoverflow.com/questions/34352148/spring-aop-execution-coverage

www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte:fdl:aop:aspectj

서적 "초보 웹 개발자를 위한 스프링 프로그래밍 입문 5(최범균)"