오늘도 배우자!

귀찮음에 배움을 멀리하지 않기를...

Develop/Server

SpringBoot AOP 이해 - AOP(2)

리다양 2019. 11. 30. 23:30

오늘은 AOP 두번째 시간인데요 잠시 이전시간의 정리를 한번 해보도록 하겠습니다!!

 

AOP는 흩어진 공통 기능들을 한데모아 관리하는 프로그래밍 방식으로 이해했어요..

깊게 공부하고 이해하면 정말 딥하게 갈 수도있고 저도 그정도까지는 어려워서 최대한 간단히 이해하려고

하니 이렇게 정리가 되네요 ㅎㅎ

 

AOP를 실행하기 위해 @Aspect 어노테이션이 포함된 빈(@Component)을 구현해서 진행을 했죠??

그럼 사용자 요청부터 다시 정리해볼게요.

 

1) 사용자가 특정 URL에 Request를 보낸다.

2) AOP로 관리되고 있는 영역이 특정 URL을 관리하는 Controller에 포함이 되어있나 확인한다.

3) 해당 URL이 AOP로 설정된 Controller라면 해당 AOP를 관리하는 클래스로 간다.

4) 해당 클래스(@Aspect 어노테이션이 붙고, 해당 Controller를 execution내애 포함하는)에 정의된대로 실행한다.

 

그럼 여기서 잠깐!!

AOP는 그럼 Controller 실행 이전에 먼저 실행되는 것 같다... 라고 생각되신분 손!!!

거의 천재입니다 ㅎㅎㅎ

 

비록 AOP 설정이 Controller 코드 실행 후에 로깅 등과 같은 작업을 선언했어도 먼저 AOP관련 설정으로 들어와서

프로세스를 진행했기 때문에 AOP가 선행되는 것이 정답입니다!!ㅎㅎ

 

AOP관련 설정을 Controller에 했다면 Controller 실행 이전일 것이고 Service쪽에 했다면 Service 호출에 관련해서 진행을 하겠죠??

 

이번에는 AOP 설정 방법 2가지를 알아보도록 하겠습니다.

 

1. 특정 어노테이션을 포함하고 있는 메서드가 실행될 때

특정 어노테이션을 먼저 한번 만들어보도록 할게요!!(어노테이션 만들고싶은분들을 위한 꾸르팁 >_<)

 

1) 커스텀 어노테이션 만들기


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {

}

엥 이게 끝이야??? 네 이게 다입니다...ㅎㅎ 어노테이션이 어떤 기능이라고 생각하시는 분들도 있으실텐데요..

어노테이션은 그냥 거의 주석처럼.. 설명일뿐 어떤 기능을 하지않습니다!! 어노테이션을 읽어서 그걸 바탕으로 처리하는

녀석이 따로 있을뿐이죠!!

 

@Tartget을 통해 이 어노테이션이 어떤 종류의 녀석들을 위한 것인지(메소드를 위한 녀석인지.. 패키지를 위한 녀석 인지..) 설정해줍니다. ElementType이 METHOD로 메소드를 위한 어노테이션임을 나타내구요!!

 

@Retention은 Retention(보유, 유지력)이라는 말 그대로 이 어노테이션이 언제까지 유효한지 설정해주는데

런타임시에도(프로그램이 동작하고 있을 때에) 작동해야하므로 RetentionPolicy를 RUNTIME으로 설정해줍니다.

 

이렇게하면 어떤 메소드를 위해(@Target(ElementType.METHOD))

런타임시에도 프로그램이 인식할 수 있는(@Retention(RetentionPolicy.RUNTIME))

TestAnnotation이라는 이름의 어노테이션이(@interface TestAnnotation) 완성되었습니다!!

 

2) 원하는 메소드에 커스텀 어노테이션붙이기


@RestController
public class TestController {
	@TestAnnotation
	@GetMapping("/test")
	public String getTest() {
		return "Test";
	}
}

저는 TestController라는 녀석의 getTest()라는 메서드에 @TestAnnotation을 붙였습니다.

 

이제 TestAnnotation이라는 어노테이션이 붙은 녀석을 처리할!!! AOP 기능을 구현해볼게요!!!

 

3) 어노테이션을 인식해 처리하는 AOP만들기


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;


@Component
@Aspect
public class TestAspect {
	Logger log = LoggerFactory.getLogger(TestAspect.class);
	
	@Around("@annotation(TestAnnotation)")
	public Object log(ProceedingJoinPoint joinPoint) throws Throwable{
		log.info("Before Execute Method!!!");
		Object proceed = joinPoint.proceed();
		log.info("After Execute Method!!!!");
		return proceed;
	}
	
}

저는 앞의 글처럼 특정 작업 앞뒤로 로그를 만드는 기능을 추가했어요!!

 

여.기.서.잠.깐.만.효!

앞의글에서는 @Around다음에 execution나왔는데 좀 다르네요???

네 맞습니다!!!(자문자답 왕 ㅠㅠ) @Around뒤의 괄호()에는 특정 어노테이션을 지칭할수도, 표현식을 통해

어떤 패턴의 패키지, 경로에 있는 녀석들을 포함할 수도있습니다!!

지금은 @annoation을 통해 나 어떤 어노테이션 기반으로 동작하게 할거야 라고 말을하고있고

TestAnnotation이라는 이름의 어노테이션을 가진 녀석들을 대상으로 실행하겠다!! 라고 이야기하고 있습니다.

 

그런데 @annotation(TestAnnotation) 요기서 TestAnnotation이라는 녀석이 어디에 있는녀석인지 경로도 없이 바로 TestAnnotation이라고 하니까 바로 인식이 되는데요 그 이유는 아래의 그림을 한번 봐주세요!!

 

커스텀 Annotation과 TestAspect가 같은 경로에 있을 경우 전체 경로 안적어도 됨
커스텀 Annotation과 TestAspect가 다른 경로에 있을 경우 어디에 있는 어노테이션인지 경로까지 적어야 함

 

같은 경로에 있기때문에 특별히 경로까지 풀로 적지 않았는데 다른 경로에 있다면

예를들어 TestAnnotation이 com.example.demo.annotation.TestAnnotation 요런 경로에 있고 TestAspect 클래스가

다른 경로에 있다면 @annotation(com.example.demo.annotation.TestAnnotation) 요렇게 풀로 적어주셔야 어노테이션을 찾아서 인식합니다!! 아니면... 찾을 수 없다는 에러가...ㅠㅠ

(java.lang.IllegalArgumentException: error Type referred to is not an annotation type 요런에러 떠요 ㅠㅠ)

 

이렇게 설정을 모두 마쳐준다면 @TestAnnotation이 붙은 메서드들의 실행 전, 후의 로그를 찍는 AOP 기능이 구현된것입니다!!!

 

2. 특정 경로에 있는 녀석들에 대해서 실행할 때

이전글에서 사용했던 것인데요 execution키워드를 통해서 경로를 기술하는 방법입니다!!


@Component
@Aspect
public class LoggerAspect {

	private Logger log = LoggerFactory.getLogger(this.getClass());

	@Around("execution(* com.example.demo.controller.*Controller.*(..)) "
			+ "or execution(* com.example.demo.service..*(..)) " + "or execution(* com.example.demo.mapper..*(..))")
	public Object logPrint(ProceedingJoinPoint joinPoint) throws Throwable {
		log.info("Before Execute Method!!!");
		Object proceed = joinPoint.proceed();
		log.info("After Execute Method!!!!");
		return proceed;
	}
}

 

앞의 글에선 execution(* com.example.demo.controller.*Controller.*(..))만 있었는데 이번에는 or 키워드롤 통해서

여러 execution을 포함하고 있습니다!!

한번 해석해볼게요!!

@Around : 어떤 메소드 실행 전, 후, 에러시 모두 전반적으로 다룰꺼야

execution(* com.example.demo.controller.*Controller.*(..)) : 메소드의 리턴타입 상관없이 com.example.demo.controller

패키지안에있는 이름이 Controller로 끝나는 모든 녀석들에있는 메소드들을 다루는데 그 메소드들의 인자가 0개던, 1개던, 2개던, 100개던 상관없이 모두 다룰꺼야.

 

여기서 또 잠깐!! execution다음에 () 요 괄호사이에 들어가는게 어떤 규칙을 가지고 있는지 궁금하실텐데요!!

결론으로는 형태가 어떤 메소드인지 나타내는건데 먼저 메소드 선언할때를 생각해볼게요.

 

메소드는 보통 리턴타입 메소드이름(인자) 형태로 나타내죠? (ex. void print(), void print(String word),

String getWord(String word), String getWord(String word1, String word2...))

 

그래서 어떤 리턴타입의 메소드고, 메소드의 이름이 뭐고, 그 메소드가 인자가 몇개냐에 따라서 메소드의 형태가

다양한데 그걸 * com.example.demo.controller.*Controller.*(..) 요렇게 표현식으로 한거에요!!

비슷하게 볼수있게 비교해볼게요 아래의 그림을 봐주세요!!

어때요 조금 감시 오시나효 ㅎㅎㅎ 이런방식으로 여러 경로의 녀석들을 같이 관리하고 싶을때

조건문에서 &&, || 이렇게 엮어서 조건문 완성하듯이 or로 경로를 추가하면

@Around("execution(* com.example.demo.controller.*Controller.*(..)) "
+ "or execution(* com.example.demo.service..*(..)) " + "or execution(* com.example.demo.mapper..*(..))")

요런 결과가 나올테고, com.example.demo.controller 패키지이름이 Controller로 끝나는 클래스들의 메소드

com.example.demo.service 패키지 하위의 모든 메소드들과, com.example.demo.mapper 패키지 하위의 모든 메소드들에 대해서 처리를 하겠다 라는 해석이 가능해집니다.

 

요기서 또 잠깐!! 컨트롤러쪽에는 com.example.demo.controller. 이렇게 끝에 점하나 붙고

서비스랑 매퍼쪽에는 com.example.demo.service.. 이렇게 끝에 점 두개 붙었는데 뭐지? 오타인가... 해석도 몬가 좀 컨트롤러랑 다르고... 라고 생각하신 당신은 매의 눈!!!

 

저렇게 점(.) 요게 두개 붙으면 하위 경로 모두!! 라는 뜻입니다!!ㅎㅎ 그래서 com.example.demo.service.. 요렇게하면

com.example.demo.service 하위 경로의 모든 녀석들!!! 이라는 뜻이죠 ㅎㅎ

 

참고로 @Around 어노테이션말고 @Before, @AfterReturning, @AfterThrowing, @After 요 네가지가 더있어요!!

빠른 습득을 위해 간단히 설명해드리자면!

 

@Before : 특정 메서드 실행

@AfterReturning : 특정 메서드 성공적으로 실행되고 난

@AfterThrowing : 특정 메서드가 예외 발생시켰을때(Throwing이라는 표현이 예외 던지는 느낌이랑 비슷하죠?? 헤헤)

@After : 특정 메서드 성공하던 예외로 실패하던 메소드 진입  무조건 실행

 

요런 역할을 하고 있습니다.

그리고 특정 메서드는 해당 어노테이션들(@Around, @Before, @AfterReturning, @AfterThrowing,  @After)뒤에 execution이나 @annotation으로 어떤 메서드들을 대상으로 AOP적용하는지 설정했는데 그 적용 대상 메서드들을 지칭 합니돳!!!

 

3. AOP 구성요소

사실 지금까지 어려울까봐 @Aspect라는 어노테이션과 @Aspect 안의 메소드(public Object logPrint(ProceedingJoinPoint joinPoint))에서 Aspect, joinpoint라는 단어만 나오고 대외적인(??) 구성요소에 대해서 말씀을 안드렸어요..

 

이제 정리를 해볼게요 지금 정리하는거보면서 아!! 하고 이해할 수 있을거에요!! 이걸위해..여기까지 달렸...다...

AOP는 공통적인 기능을 모아서 관리하죠!! 그리고 어떤 시점에 어떤 녀석들을 대상으로 실행할지 정의를 했구요!!

 

자 갑니다!!

1) Aspect : 공통적으로 정의될 기능을 의미

(@Aspect 어노테이션은 그럼?? 나 공통 기능이야!! 하는거겠죠?)

 

2) Advice : 어느 시점에 이 기능들이 동작하는지 의미

(@Around, @Before, @AfterReturning, @AfterThrowing,  @After)

 

3) PointCut : 어떤 메서드 대상으로 실행할지 정의

(execution, @annotation으로 어떤놈들 대상인지 정했죠오??)

 

4) JoinPoint : 공통 기능말고 특수 기능을 가진 특정 메서드 실행

(로그 앞뒤로 찍기전에 joinPoint.proceed(); 요거 기억!!)

 

자 오늘로써 AOP를 한번 훑어봤습니다!!! 특지 마지막 1),2),3),4) 연계해서 코드를 보시면 뭔가 더 이해가 잘 되실거에요!!ㅎㅎㅎ 다음 글에서는...음... 어떤 기능으로 찾아올지.. 고민해볼게요!! ㅎㅎㅎ 혹시 혹시나!! 어떤걸 다루면 좋을지

댓글남겨주시면 그 내용바탕으로 최대한 빠른시일내에 다뤄볼게요!! 없다면... 내맘대로 진행하겠(다)습니다!!!

 

ps. 이전글(2019/11/12 - [Develope/Server] - SpringBoot Controller, Service, DAO 이해 - Service(2) + MyBatis)에서

DB트랜잭션 처리를 AOP로 했는데 위의 그림과는 다를거에요 해당글에서는 @Congifuration에 AOP기능을 포함해서

트랜잭션을 관리했는데 시간나시면 한번 찾아보시거나 어렵다면!! 저에게 글 남겨주시면 최대한 열심히 설명해볼게요!! ㅎㅎ