관점지향프로그래밍(AOP: Aspect Oriented Programming) :
。Business Logic과Cross Cutting Concern을 분리하여 모듈화하는 프로그래밍 기법.
▶ 어떤 logic을 기준으로핵심적인 관점,부가적인 관점으로 나눠서 보고 해당 관점을 기준으로 각각모듈화를 수행
。Cross Cutting Concern을Aspect단위로 분리 후Joint Point를Pointcut으로 지정하여Joint Point의메소드 호출을인터셉트및Advice를Weaving하여 핵심Business Logic과 독립적으로 적용
▶Aspect의Advice로서 작성한 코드를Pointcut으로 지정한 특정패키지내부의Spring Bean의 Method를 호출 시인터셉트하여 적용
。Java의AOP종류 :Spring AOP,AspectJ
。AOP용도의클래스생성 시 뒤에 이름에Aspect 키워드를 기입하는게 관례
。 각Layer의Business Logic에서Logging,Security,Transaction등의Cross Cutting Concern을 별도의Aspect로 분리하여Boiler Plate Code를 줄이고 집중적으로 관리하여 유지보수를 쉽게 수행implementation 'org.springframework.boot:spring-boot-starter-aop'
공통관심사( Cross Cutting Concern )
。어플리케이션의 여러모듈에서 공통적으로 필요하지만 , 각모듈의Business Logic과는 별개인 기능
▶ 전반에서 코드중복(WET)을 유발하는boiler plate code
。같은 기능의 코드가 여러곳에서중복으로 사용하여유지보수성과코드가독성을 떨어뜨리는 원인
▶Spring AOP을 통해Cross Cutting Concern를Aspect로 분리하여 해결.
。주로레이어드 아키텍처에서 분리된 각계층의 공통으로 사용하는Security(보안) ,Performance(성능측정) ,Logging(문제식별) 부분을AOP로 구현
Compile 시점에서 실행되는AOP기능
。개발자에 의해 작성된Java Source Code에서기계어로 변환
。Aspect,Advice,Weaver
Runtime 시점에서 실행되는AOP기능
。빌드된어플리케이션이 사용자에 의해 실행이 되는 시점
。JoinPoint,Pointcut
Aspect
。AOP에서Cross Cutting Section을 정의한Advice와적용대상(Pointcut)을 정의한모듈
▶Aspect=Advice+Pointcut+JoinPoint
@Aspect:org.aspectj.lang.annotation.Aspect
。Spring AOP에서Aspect목적으로Cross Cutting Concern을 구현하는클래스에 선언하는어노테이션
▶ 여러모듈에서 동일하게 사용하는Logging,Transaction,Security등을 구현
。해당Annotation을 선언 시 해당 Class가AOP기능을 수행하는Aspect임을 선언
。@Component처럼Spring Bean으로 등록하는Logic이 없으므로@Configuration또는@Component를 추가로 선언하여Spring Bean으로서 등록해 활용
Advice
。각모듈의Cross Cutting Concern에 해당하는Code Section을Aspect 클래스내에메서드로 지정 및Advice 어노테이션을 선언
▶Join Point를인터셉트하여Advice 어노테이션이 적용된메서드를 호출
。인터셉트하여 실행할메서드의 실행 시점에 따라서@Before,@After,@Around등의Advice 어노테이션이 존재.// Aspect 부분 @Before(Pointcut 부분 : "execution(* com.wjdtn747.rest.webservices.aop_practice.AOPPractice1.*.*(..))") public void logMethodCall(JoinPoint joinPoint){ // Advice 부분 logger.info("logMethodCalled - {}",joinPoint); }▶
com.wjdtn747.rest.webservices.aop_practice.AOPPractice1.*.*(..))"의 하위패키지 경로에 존재하는 모든Spring Bean에서 실행되는메서드들이 실행되기전인터셉트하여logMethoCall()을 실행
ProceedingJoinPoint:org.aspectj.lang.ProceedingJoinPoint
。Spring AOP에서@Around Method의 매개변수로 전달되어Advice로서 활용하여AOP적용 Method의 실행을 제어하는 역할의 Interface
ProceedingJoinPoint객체.proceed()
。AOP적용 Method를 실행.
▶ 실행을 지연하거나, 조건에 따라 실행을 취소가능.
ProceedingJoinPoint객체.getArgs():
。AOP적용Method의매개변수를배열로 가져오는 역할을 수행
@Before("AspectJ Pointcut"):org.aspectj.lang.annotation.Before
▶JUnit의@Before와 다르다.
。Spring AOP가 적용될메서드가 실행되기전에 해당어노테이션이 선언된 Method를 실행하도록 하는Advice Annotation
▶Business Logic이 수행되기 전에 실행될 부가기능(Logging,Authentication검사 )을 정의 시 사용.
@After("AspectJ Pointcut")
。AOP대상 Method가 실행된 후 예외발생에 상관없이 해당Annotation이 선언된 Method가 항상 실행.
▶AOP대상 Method의 성공 / 예외여부와 관계없이 실행.
。주로 Resource 정리, log 기록, Transaction 종료 등의 후처리 Method로서의 작업 수행 시 선언.
@AfterReturing(pointcut="AspectJ Pointcut",returning="반환될변수명")
。AOP대상 Method가 실행 시 정상적으로 실행된 경우 해당Annotation이 선언된 Method가 실행.
▶AOP대상 Method가 정상 실행 시 실행하므로 반환값을 사용가능
。Annotation에returning="예외변수명"속성을 추가하여AOP적용 Method의 반환값을 활용가능.@AfterReturning(pointcut = "execution(* com.example.service.MyService.getData(..))", returning = "result") public void afterReturningAdvice(JoinPoint joinPoint, Object result) { System.out.println("메서드 실행 성공 후: " + joinPoint.getSignature()); System.out.println("반환된 값: " + result); }。
@AfterReturing(pointcut="AspectJ Pointcut",returning="반환값변수명")정의 후 매개변수에Object 반환될변수명정의 시AOP적용 Method의 반환값을 활용 가능.
▶AOP적용 Method가 예외 발생 시 실행되지 않는다.
@AfterThrowing(pointcut="AspectJ Pointcut",throwing="예외변수명")
。AOP대상 Method가 실행 중 예외가 발생된 경우 해당Annotation이 선언된 Method가 실행.
▶AOP대상 Method가 예외 발생 시 실행하므로Exception에 대한 정보를 사용가능
。Annotation에throwing="예외변수명"속성을 추가하여AOP적용 Method에서 발생한Exceptioninstance를 활용가능.@AfterThrowing(pointcut = "execution(* com.example.service.MyService.process(..))", throwing = "ex") public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) { System.out.println("메서드 실행 중 예외 발생: " + joinPoint.getSignature()); System.out.println("발생한 예외: " + ex.getMessage()); }。
@AfterThrowing(pointcut="AspectJ Pointcut",throwing="예외변수명")정의 후 매개변수에Exception 예외변수명정의 시AOP적용 Method에서 발생한Exceptioninstance를 활용 가능.
▶AOP적용 Method가 정상 실행 시 실행되지않는다.
@Around("AspectJ Pointcut"):
。AOP적용 Method의 실행 전후를 모두 제어하는 기능을 제공하는Annotation
▶@Before+@After를 모두 합친 기능으로서 실행여부까지 제어가능.
。AOP적용 Method 정상 실행 후 반환값을 임의로 변경가능.
。Method 실행시간의 측정을 통한 성능측정이 가능.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.context.annotation.Configuration; @Aspect @Configuration public class PerformanceTrackingAspect { public Logger logger = LoggerFactory.getLogger(getClass()); // Pointcut으로 정의된 Package의 하위경로의 Class에서 호출되는 AOP 적용 Method를 // Intercept하여 실행 전후의 시간을 측정. @Around("execution(* com.wjdtn747.rest.webservices.aop_practice.AOPPractice1.*.*(..))") public Object findExecutionTime(ProceedingJoinPoint proccedingjoinPoint) throws Throwable { // Timer 시작 시점 long startTimeMilis = System.currentTimeMillis(); // AOP 적용 Method를 실행 후 반환값을 변수로 assign. Object returnValue = proccedingjoinPoint.proceed(); // Timer 종료 시점 long endTimeMilis = System.currentTimeMillis(); long durationTimeMilis = endTimeMilis - startTimeMilis; logger.info("@Around 적용 Aspect - {} Method 실행시간 {} ms", proccedingjoinPoint , durationTimeMilis); return returnValue; } }
▶ Pointcut으로 정의된 Package의 하위경로의 Class에서 호출되는AOP적용 Method를 Intercept하여 실행 전후의 시간을 측정.
。@Around Method의 매개변수에ProceedingJoinPointinstance를 선언하여ProceedingJoinPoint객체.proceed()를@Around Method의 원하는 시점의Advice에 기입하여AOP적용 Method이 실행되도록 설정.
。AOP를 통해Aspect의Advice로서 작성한 코드를 특정 Package 내부의 여러 Class의Spring Bean의 Method 호출 시 Intercept하여 모두 적용 가능.
▶ 따로 해당 Class에Advice기능을 각각 구현할 필요가 없이 공통적으로 적용.
Weaver
。Aspect의Advice와어플리케이션 모듈의Business Logic을 결합하는 역할을 수행.
▶Application의 기존 코드에Aspect를 적절한 지점과Runtime또는Compile시점을 선택하여 결합을 수행.
。AOP Framework인Spring AOP,AspectJ는Weaver의 역할로서Weaving작업을 수행.
Weaving:Pointcut으로 지정된Join point에Advice를 실제로 결합하는 행위
JoinPoint:org.aspectj.lang.JoinPoint
。Pointcut으로 지정되어호출시AOP에 의해인터셉트하는메서드부분
▶Advice가 적용될 수 있는 모든 위치를 의미.
▶Spring AOP의JoinPoint는인터셉트되는Method실행지점을 의미.
。Join Point로서Pointcut에 정의되어인터셉트된Spring Bean의 Method의 이름, 매개변수, 반환값등의 정보를JoinPointinstance에 포함하여Aspect에서Advice 어노테이션이 선언된매개변수로 전달// 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[])) }
JoinPoint객체.getSignature(),JoinPoint객체.getArgs()
。인터셉트한Method명,Class 정보,매개변수등을 가져올 수 있음.
Pointcut
。AOP에서Advice를 적용하도록인터셉트할 특정JoinPoint를 정의 하는표현식
▶Join Point로서Pointcut을 설정하여 범위 지정 시 해당 범위의Spring Bean에서 발생하는Method 호출만인터셉트
。Pointcut의 조건이 참일 경우,Advice가 실행됨.
。Spirng AOP에서는"execution","within","args"등의AspectJ Pointcut표현식을 사용하여Pointcut을Advice 어노테이션인@Before(AspectJ Pointcut)에 정의.
ex)@Before("execution(* 패키지명.*.*(..))")
▶ 해당패키지내Spring Bean의메서드가 실행되기 전@Before가 선언된Advice 메서드를 실행
ex )execution(* 패키지명.*.*(..))": 특정Package의 1단계 하위 모든Class에서 호출되는 모든Method를Intercept하여 선언된 Method가 실행됨.
@Pointcut("AspectJ Pointcut"):org.aspectj.lang.annotation.Pointcut;
。pointcut 표현식을 통해 특정JoinPoint 범위를 지정하여 다른AOP 어노테이션들에게 참조용도로 활용되는Annotation.
。기존의 모든AOP 어노테이션에 정의한Pointcut을특정Package경로를 정의하는AspectJ Pointcut 표현식을 포함하는@Pointcut Method의 경로로 통일
▶Package경로가 변경되더라도 모든AOP 어노테이션을 수정하는게 아닌,@Pointcut에 정의된AspectJ Pointcut 표현식을 수정
AspectJ Pointcut표현식 종류
@Pointcut("execution(Package경로)"):
。지정된 범위의 Package 경로의 하위Class의Spring Bean의Method또는Class를 호출할 경우Intercept하는Pointcut.@Pointcut("execution(* com.wjdtn747.rest.webservices.aop_practice.AOPPractice1.*.*(..))") public void DataPackageConfig(){}
@Pointcut("bean(*Service*)"):
▶ 이름에"Service"가 들어간Spring Bean를 포함하는Method 호출시Intercept.
@Pointcut("@annotation(Annotation경로)"):
。Annotation을Pointcut으로 활용 시 선언@Pointcut("@annotation(com.wjdtn747.rest.webservices.aop_practice.aspects.Tracktime)") public void TracktimePointcut(){}