Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Main <- Section11 advanced spring aop pointcut #22

Merged
merged 10 commits into from
Mar 31, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package hello.aop.member;

public interface MemberService {
String hello(String param);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package hello.aop.member;

import hello.aop.member.annotation.ClassAop;
import hello.aop.member.annotation.MethodAop;
import org.springframework.stereotype.Component;

@ClassAop
@Component
public class MemberServiceImpl implements MemberService {
@Override
@MethodAop("test value")
public String hello(final String param) {
return "ok";
}

public String internal(String param) {
return "ok";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package hello.aop.member.annotation;

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassAop {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package hello.aop.member.annotation;

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 MethodAop {
String value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package hello.aop.pointcut;

import hello.aop.member.MemberService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;

@Slf4j
@Import(AtAnnotationTest.AtAnnotationAspect.class)
@SpringBootTest
public class AtAnnotationTest {

@Autowired
MemberService memberService;

@Test
void success() {
log.info("memberSergvice Proxy={}", memberService.getClass());
memberService.hello("helloA");
}

@Slf4j
@Aspect
static class AtAnnotationAspect {

@Around("@annotation(hello.aop.member.annotation.MethodAop)")
public Object doAtAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[@annotation] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package hello.aop.pointcut;

import hello.aop.order.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;

@Slf4j
@Import(BeanTest.BeanAspect.class)
@SpringBootTest
public class BeanTest {

@Autowired
OrderService orderService;

@Test
void success() {
orderService.orderItem("itemA");
}

// Spring Bean 이름으로 매칭

@Aspect
static class BeanAspect {
@Around("bean(orderService) || bean(*Repository)")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[bean] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
package hello.aop.pointcut;

import static org.assertj.core.api.Assertions.*;

import hello.aop.member.MemberServiceImpl;
import java.lang.reflect.Method;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;

@Slf4j
public class ExecutionTest {

AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
Method helloMethod;

@BeforeEach
public void init() throws NoSuchMethodException {
// 테스트에서 실행할 수 있게 getMethod()를 추출해서 helloMethod에 보관
helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
}

@Test
void printMethod() {

// 앞으로 알아볼 execution으로 시작하는 포인트컷 표현식은 이 메서드 정보를 매칭해서 포인트컷 대상을 찾아낸다.
// execution(* ..package..Class.)
// public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
log.info("helloMethod={}", helloMethod);
}

/**
* *매칭 조건*
* '?' 는 생략이 가능
* 접근제어자?:public
* 반환타입: String
* 선언타입?: hello.aop.member.MemberServiceImpl
* 메서드이름: helLo
* 파라미터: (String)
* 예외?: 생략I
*/
@Test
@DisplayName("가장 정확한 포인트 컷")
void exactMatch() {
// public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String) 예외는 생략
pointcut.setExpression("execution(public String hello.aop.member.MemberServiceImpl.hello(String))");

// pointcut의 조건이 True냐?
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

/**
* *매칭 조건*
* 접근제어자?: 생략
* 반환타입:'*'
* 선언타입?: 생략
* 메서드이름: ‘*'
* 파라미터: (..)
* 예외?: 없음
*/
@Test
@DisplayName("가장 많이 새약한 포인트 컷")
void allMatch() {
// 반환타입: 전체, 메서드이름: 전체, 파라미터: 전체
pointcut.setExpression("execution(* *(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

@Test
@DisplayName("메서드 이름으로 매칭")
void nameMatch() {
pointcut.setExpression("execution(* hello(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

@Test
@DisplayName("메서드 이름으로 매칭하는데 패턴으로 확인")
void nameMatchStar1() {
pointcut.setExpression("execution(* hel*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

@Test
@DisplayName("메서드 이름으로 매칭하는데 패턴으로 확인")
void nameMatchStar2() {
pointcut.setExpression("execution(* *el*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

@Test
@DisplayName("매칭에 실패하는 케이스")
void nameMatchFalse() {
pointcut.setExpression("execution(* nono(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
}

@Test
@DisplayName("패키지 이름으로 정확히 매칭")
void packageExactMatch1() {
pointcut.setExpression("execution(* hello.aop.member.MemberServiceImpl.hello(..)))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

@Test
@DisplayName("패키지 이름을 *로 줄임")
void packageExactMatch2() {
pointcut.setExpression("execution(* hello.aop.member.*.*(..)))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

@Test
@DisplayName("정확한 패키지 이름이 아니기 때문에 실패함. 아래에서 이를 해결하는법을 적용")
void packageExactFalse() {
pointcut.setExpression("execution(* hello.aop.*.*(..)))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
}

@Test
@DisplayName("정확한 패키지 이름이 아니기 때문에 실패함. 아래에서 이를 해결하는법을 적용")
void packageMatchSubPackage1() {
pointcut.setExpression("execution(* hello.aop.member..*.*(..)))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

@Test
@DisplayName("hello aop와 그것의 하위에 있는 모든 것")
void packageMatchSubPackage2() {
pointcut.setExpression("execution(* hello.aop..*.*(..)))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

/**
* 패키지에서의 차이를 이해해야 한다. . : 정확하게 해당 위치의 패키지 ..: 해당 위치의 패키지와 그 하위 패키지도 포함
*/

@Test
@DisplayName("helloMethod 는 MemberServiceImpl 클래스 안에 있으므로 당연히 매칭된다.")
void typeExactMatch() {
pointcut.setExpression("execution(* hello.aop.member.MemberServiceImpl.*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

@Test
@DisplayName("MemberServiceImpl와 MemberService는 매칭이 되냐? - 됨")
// execution 에서는 MemberService처럼 부모 타입을 선언해도 그 자식 타입은 매칭된다.
void typeExactSuperType() {
pointcut.setExpression("execution(* hello.aop.member.MemberService.*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

@Test
@DisplayName("MemberServiceImpl와 MemberService에서, 부모가 갖고 있는 메서드만 가능함. 자식의 다른 메서드는 불가능함")
// 아래 코드는 본인(자식)에서 꺼낸 것이므로 당연히 가능
void typeMatchInternal() throws NoSuchMethodException {
pointcut.setExpression("execution(* hello.aop.member.MemberServiceImpl.*(..))");
Method internalMethod = MemberServiceImpl.class.getMethod("internal", String.class);
assertThat(pointcut.matches(internalMethod, MemberServiceImpl.class)).isTrue();
}

@Test
@DisplayName("MemberServiceImpl와 MemberService에서, 부모가 갖고 있는 메서드만 가능함. 자식의 다른 메서드는 불가능함")
void typeMatchNoSUperTypeMethodFalse() throws NoSuchMethodException {
pointcut.setExpression("execution(* hello.aop.member.MemberService.*(..))");
Method internalMethod = MemberServiceImpl.class.getMethod("internal", String.class);
assertThat(pointcut.matches(internalMethod, MemberServiceImpl.class)).isFalse();
}

// String 타입의 파라미터 허용
// (String)

@Test
void argsMatch() {
pointcut.setExpression("execution(* *(String))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

@Test
void argsMatchNoArgs() {
pointcut.setExpression("execution(* *())");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
}

@Test
// (Xxx)
@DisplayName("정확히 하나의 파라미터 허용, 모든 타입 허용")
void argsMatchStar() {
pointcut.setExpression("execution(* *(*))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

@Test
// (), (Xxx), (Xxx, Xxx)
@DisplayName("숫자와 무관하게 모든 파라미터, 모든 타입 허용")
void argsMatchAll() {
pointcut.setExpression("execution(* *(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

@Test
// (String), (String, Xxx), (String, Xxx, Xxx)
@DisplayName("String 타입으로 시작, 숫자와 무관하게 모든 파라미터, 모든 타입 허용")
void argsMatchAllString() {
pointcut.setExpression("execution(* *(String, ..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
/**
* execution 파라미터 매칭 규칙은 다음과 같다.
* (String): 정확하게 String 타입 파라미터
* ():파라미터가 없어야 한다
* (*): 정확히 하나의 파라미터, 단 모든 타입을 허용한다.
* (*, *): 정확히 두 개의 파라미터, 단 모든 타입을 허용한다.
* (..): 숫자와 무관하게 모든 파라미터, 모든 타입을 허용한다. 참고로 파라미터가 없어도 된다. '0..*'로 이해하면 된다.
* (String, ..): String 타입으로 시작해야 한다. 숫자와 무관하게 모든 파라미터, 모든 타입을 허용한다.
* - 예) (String), (String, Xxx), (String, Xxx, Xxx) 허용
*/
}
Loading