AOP실습
Spring AOP,AspectJDependency 정의
。gradle기준build.gradle에 정의 후 Reload 수행.implementation 'org.springframework.boot:spring-boot-starter-aop'
Spring AOP실습을 위한 간단한Business Logic구현
。레이어드 아키텍처에 따라 각각Repository 클래스( =Persistence Layer)와Service 클래스( =Business Layer)를 구분하여클래스생성
▶고수준 클래스에서저수준 클래스로의존
Logger 활용@Service class BusinessService{ private BusinessRepository repository; public BusinessService(BusinessRepository repository) { this.repository = repository; } public int calMax(){ int[] data = repository.getData(); return Arrays.stream(data).max().orElse(0); } }▶
Business Layer역할의Business Logic구현@Repository class BusinessRepository{ public int[] getData(){ return new int[]{1,2,3,4,5,6,7,8,9,10}; } }▶
Persistence Layer역할의Persistence Logic구현import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Component public class AOPPractice implements CommandLineRunner { Logger logger = LoggerFactory.getLogger(getClass()); private BusinessService service; public AOPPractice(BusinessService service){ this.service = service; } @Override public void run(String... args) throws IOException { logger.info("Max Value is {}", service.calMax()); } }。
CommandLineRunner의구현메소드run()을 통해어플리케이션실행 시Service에 구현된Business Logic을 실행
。org.slf4j.Logger의LoggerFactory.getLogger(getClass());를 통해Loggerinstance 생성 및logger객체.로깅수준("log message", 값);으로 정의된 로깅수준(info)로 Console에log를 표현.
Logging목적의Aspect생성하기
。Business Layer,Persistence Layer에 동일하게 존재하는Cross Cutting Concern인Logging기능을Aspect로 분리
▶Aspect는 양쪽Layer의Spring Bean에서 Method를 실행 시Logging하는 역할을 수행
▶AOP 용도의클래스생성 시 뒤에 이름에Aspect 키워드를 기입하는게 관례
@Aspect가 선언된 Class 생성 및 내부에@Before으로 선언된Advice Method생성.
。Cross Cutting Concern에 해당하는클래스에@Component와@Aspect를 선언
▶@Component를 선언하여Spring Bean으로 등록
。@Before를 선언하여Pointcut으로 지정된JoinPoint를 Intercept하여Cross Cutting Concern가 정의된Advice 메서드가 실행되도록 설정@Component @Aspect public class LoggerAspect { private Logger logger = LoggerFactory.getLogger(getClass()); // com.ktcloud.excercise.AOP 패키지의 하위 Spring Bean의 // 모든 Method가 호출되기 전에 intercept하여 선언된 Method가 실행. @Before("execution(* com.ktcloud.excercise.AOP.*.*(..))") // JoinPoint를 통해 인터셉트한 Method의 이름, 매개변수, 반환값등의 정보를 매개변수로 제공. public void beforeLogMethodCall(JoinPoint joinPoint) { // Logger Logic 구현 logger.info("beforelogMethodCalled - {}",joinPoint); // beforelogMethodCalled - execution(void com.ktcloud.excercise.AOP.AOPPractice.run(String[])) } }。
@Aspect를 선언하여 해당 Class가AOP기능을 수행하는Aspect임을 선언 및@Component을 통해 사전에Spring Bean으로서 등록
。Cross Cutting Concern( =Logger)이 구현된beforeLogMethodCall()에@Before("AspectJ Pointcut표현식")을 선언하여AspectJ Pointcut표현식에 정의된Package경로 상Spring Bean의Method가 실행되기 전인터셉트하여beforeLogMethodCall()Method를 실행.
。매개변수에JoinPoint 객체를 지정 시인터셉트되는JoinPoint의이름, 매개변수, 반환값등의 정보를 확인가능
。 다음info수준의log가 Console에 도출.
▶@Before에서 설정된패키지 경로의 범위에 포함되는Spring Bean의Method가 실행되기전에beforeLogMethodCall()가 각각인터셉트하여 실행.
▶JoinPoint를 통해인터셉트한Method의이름, 매개변수, 반환값등의 정보를 제공하여로그로 출력
AOP Annotation의Pointcut구문을 공통화하여Package경로가 임의로 변경되더라도 쉽게Configuration하도록 설정하기
。기존의 모든AOP 어노테이션에 정의한Pointcut을특정Package경로를 정의하는AspectJ Pointcut 표현식을 포함하는@Pointcut Method의 경로로 통일
▶Package경로가 변경되더라도 모든AOP 어노테이션을 수정하는게 아닌,@Pointcut에 정의된AspectJ Pointcut 표현식을 수정
// 특정 Package경로를 정의하는 AspectJ Pointcut 표현식을 정의하는 @Pointcut Method // 해당 @Pointcut Method의 경로를 다른 AOP Method의 pointcut으로 설정하여 공통적으로 해당 Package경로를 참조하도록 설정 @Pointcut("execution(* com.ktcloud.excercise.AOP.*.*(..))") public void DataPackageConfig(){}▶ 해당
@Pointcut Method의 경로를 다른AOP Method의pointcut으로 설정하여 공통적으로 해당Package경로를 참조하도록 설정.@Component @Aspect public class LoggerAspect { // 특정 Package경로를 정의하는 AspectJ Pointcut 표현식을 정의하는 @Pointcut Method // 해당 @Pointcut Method의 경로를 다른 AOP Method의 pointcut으로 설정하여 공통적으로 해당 Package경로를 참조하도록 설정 @Pointcut("execution(* com.ktcloud.excercise.AOP.*.*(..))") public void DataPackageConfig(){} // Pointcut을 특정Package경로를 정의하는 AspectJ Pointcut 표현식을 정의하는 @Pointcut Method의 경로로 설정. private Logger logger = LoggerFactory.getLogger(getClass()); @Before("DataPackageConfig()") public void beforeLogMethodCall(JoinPoint joinPoint) { logger.info("beforelogMethodCalled - {}",joinPoint); } @Around("DataPackageConfig()") public Object findExecutionTime(ProceedingJoinPoint proccedingjoinPoint) throws Throwable { long startTimeMilis = System.currentTimeMillis(); Object returnValue = proccedingjoinPoint.proceed(); long endTimeMilis = System.currentTimeMillis(); long durationTimeMilis = endTimeMilis - startTimeMilis; logger.info("@Around 적용 Aspect - {} Method 실행시간 {} ms", proccedingjoinPoint , durationTimeMilis); return returnValue; } }
▶@Pointcut Method의 경로로 설정하더라도 문제없이 실행됨.
- 커스텀 Annotation 생성후 해당 Annotation이 선언된 Method 호출 전에
@Before로 Intercept하기.
JAVA 어노테이션 관련
@Retention(RetentionPolicy.RUNTIME):
。선언된 커스텀 Annotation이 Compile 뿐만아닌 Runtime에도 유지되어 java reflection에 의해 접근되도록 설정
@Target(ElementType.METHOD):
。선언된 커스텀 Annotation이 Method에서만 선언되도록 적용.import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // 선언된 커스텀 Annotation이 Method에서만 선언되도록 적용. @Target(ElementType.METHOD) // 선언된 커스텀 Annotation이 Compile 뿐만아닌 Runtime에도 유지되어 java reflection에 의해 접근되도록 설정 @Retention(RetentionPolicy.RUNTIME) public @interface Tracktime {}@Pointcut("@annotation(com.wjdtn747.rest.webservices.aop_practice.aspects.Tracktime)") public void TracktimePointcut(){}▶ 해당
Annotation을Pointcut으로 활용 가능. AOP 관련// @Pointcut Method 경로를 pointcut으로 설정하여 해당 Annotation에 접근 @Before("com.wjdtn747.rest.webservices.aop_practice.aspects.CommonPointcutConfig.TracktimePointcut()") public void logMethodCall(JoinPoint joinPoint){ // Advice 부분 logger.info("logMethodCalled - {}",joinPoint); }▶ 이후 해당 Custom
Annotation이 선언된 Method가 호출되기전에 이를Pointcut에 의해 intercept하여 해당@Before(AspectJ Pointcut)이 선언된 Method를 실행.
![]()