Skip to content

Commit 8709751

Browse files
authored
Merge pull request #22 from toychip/Section11-Advanced-SpringAOP-Pointcut
Main <- Section11 advanced spring aop pointcut
2 parents 7981986 + d592293 commit 8709751

File tree

12 files changed

+684
-0
lines changed

12 files changed

+684
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package hello.aop.member;
2+
3+
public interface MemberService {
4+
String hello(String param);
5+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package hello.aop.member;
2+
3+
import hello.aop.member.annotation.ClassAop;
4+
import hello.aop.member.annotation.MethodAop;
5+
import org.springframework.stereotype.Component;
6+
7+
@ClassAop
8+
@Component
9+
public class MemberServiceImpl implements MemberService {
10+
@Override
11+
@MethodAop("test value")
12+
public String hello(final String param) {
13+
return "ok";
14+
}
15+
16+
public String internal(String param) {
17+
return "ok";
18+
}
19+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package hello.aop.member.annotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target(ElementType.TYPE)
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface ClassAop {
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package hello.aop.member.annotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target(ElementType.METHOD)
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface MethodAop {
11+
String value();
12+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package hello.aop.pointcut;
2+
3+
import hello.aop.member.MemberService;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.aspectj.lang.ProceedingJoinPoint;
6+
import org.aspectj.lang.annotation.Around;
7+
import org.aspectj.lang.annotation.Aspect;
8+
import org.junit.jupiter.api.Test;
9+
import org.springframework.beans.factory.annotation.Autowired;
10+
import org.springframework.boot.test.context.SpringBootTest;
11+
import org.springframework.context.annotation.Import;
12+
13+
@Slf4j
14+
@Import(AtAnnotationTest.AtAnnotationAspect.class)
15+
@SpringBootTest
16+
public class AtAnnotationTest {
17+
18+
@Autowired
19+
MemberService memberService;
20+
21+
@Test
22+
void success() {
23+
log.info("memberSergvice Proxy={}", memberService.getClass());
24+
memberService.hello("helloA");
25+
}
26+
27+
@Slf4j
28+
@Aspect
29+
static class AtAnnotationAspect {
30+
31+
@Around("@annotation(hello.aop.member.annotation.MethodAop)")
32+
public Object doAtAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
33+
log.info("[@annotation] {}", joinPoint.getSignature());
34+
return joinPoint.proceed();
35+
}
36+
}
37+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package hello.aop.pointcut;
2+
3+
import hello.aop.order.OrderService;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.aspectj.lang.ProceedingJoinPoint;
6+
import org.aspectj.lang.annotation.Around;
7+
import org.aspectj.lang.annotation.Aspect;
8+
import org.junit.jupiter.api.Test;
9+
import org.springframework.beans.factory.annotation.Autowired;
10+
import org.springframework.boot.test.context.SpringBootTest;
11+
import org.springframework.context.annotation.Import;
12+
13+
@Slf4j
14+
@Import(BeanTest.BeanAspect.class)
15+
@SpringBootTest
16+
public class BeanTest {
17+
18+
@Autowired
19+
OrderService orderService;
20+
21+
@Test
22+
void success() {
23+
orderService.orderItem("itemA");
24+
}
25+
26+
// Spring Bean 이름으로 매칭
27+
28+
@Aspect
29+
static class BeanAspect {
30+
@Around("bean(orderService) || bean(*Repository)")
31+
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
32+
log.info("[bean] {}", joinPoint.getSignature());
33+
return joinPoint.proceed();
34+
}
35+
}
36+
}
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
package hello.aop.pointcut;
2+
3+
import static org.assertj.core.api.Assertions.*;
4+
5+
import hello.aop.member.MemberServiceImpl;
6+
import java.lang.reflect.Method;
7+
import lombok.extern.slf4j.Slf4j;
8+
import org.assertj.core.api.Assertions;
9+
import org.junit.jupiter.api.BeforeEach;
10+
import org.junit.jupiter.api.DisplayName;
11+
import org.junit.jupiter.api.Test;
12+
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
13+
14+
@Slf4j
15+
public class ExecutionTest {
16+
17+
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
18+
Method helloMethod;
19+
20+
@BeforeEach
21+
public void init() throws NoSuchMethodException {
22+
// 테스트에서 실행할 수 있게 getMethod()를 추출해서 helloMethod에 보관
23+
helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
24+
}
25+
26+
@Test
27+
void printMethod() {
28+
29+
// 앞으로 알아볼 execution으로 시작하는 포인트컷 표현식은 이 메서드 정보를 매칭해서 포인트컷 대상을 찾아낸다.
30+
// execution(* ..package..Class.)
31+
// public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
32+
log.info("helloMethod={}", helloMethod);
33+
}
34+
35+
/**
36+
* *매칭 조건*
37+
* '?' 는 생략이 가능
38+
* 접근제어자?:public
39+
* 반환타입: String
40+
* 선언타입?: hello.aop.member.MemberServiceImpl
41+
* 메서드이름: helLo
42+
* 파라미터: (String)
43+
* 예외?: 생략I
44+
*/
45+
@Test
46+
@DisplayName("가장 정확한 포인트 컷")
47+
void exactMatch() {
48+
// public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String) 예외는 생략
49+
pointcut.setExpression("execution(public String hello.aop.member.MemberServiceImpl.hello(String))");
50+
51+
// pointcut의 조건이 True냐?
52+
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
53+
}
54+
55+
/**
56+
* *매칭 조건*
57+
* 접근제어자?: 생략
58+
* 반환타입:'*'
59+
* 선언타입?: 생략
60+
* 메서드이름: ‘*'
61+
* 파라미터: (..)
62+
* 예외?: 없음
63+
*/
64+
@Test
65+
@DisplayName("가장 많이 새약한 포인트 컷")
66+
void allMatch() {
67+
// 반환타입: 전체, 메서드이름: 전체, 파라미터: 전체
68+
pointcut.setExpression("execution(* *(..))");
69+
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
70+
}
71+
72+
@Test
73+
@DisplayName("메서드 이름으로 매칭")
74+
void nameMatch() {
75+
pointcut.setExpression("execution(* hello(..))");
76+
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
77+
}
78+
79+
@Test
80+
@DisplayName("메서드 이름으로 매칭하는데 패턴으로 확인")
81+
void nameMatchStar1() {
82+
pointcut.setExpression("execution(* hel*(..))");
83+
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
84+
}
85+
86+
@Test
87+
@DisplayName("메서드 이름으로 매칭하는데 패턴으로 확인")
88+
void nameMatchStar2() {
89+
pointcut.setExpression("execution(* *el*(..))");
90+
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
91+
}
92+
93+
@Test
94+
@DisplayName("매칭에 실패하는 케이스")
95+
void nameMatchFalse() {
96+
pointcut.setExpression("execution(* nono(..))");
97+
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
98+
}
99+
100+
@Test
101+
@DisplayName("패키지 이름으로 정확히 매칭")
102+
void packageExactMatch1() {
103+
pointcut.setExpression("execution(* hello.aop.member.MemberServiceImpl.hello(..)))");
104+
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
105+
}
106+
107+
@Test
108+
@DisplayName("패키지 이름을 *로 줄임")
109+
void packageExactMatch2() {
110+
pointcut.setExpression("execution(* hello.aop.member.*.*(..)))");
111+
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
112+
}
113+
114+
@Test
115+
@DisplayName("정확한 패키지 이름이 아니기 때문에 실패함. 아래에서 이를 해결하는법을 적용")
116+
void packageExactFalse() {
117+
pointcut.setExpression("execution(* hello.aop.*.*(..)))");
118+
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
119+
}
120+
121+
@Test
122+
@DisplayName("정확한 패키지 이름이 아니기 때문에 실패함. 아래에서 이를 해결하는법을 적용")
123+
void packageMatchSubPackage1() {
124+
pointcut.setExpression("execution(* hello.aop.member..*.*(..)))");
125+
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
126+
}
127+
128+
@Test
129+
@DisplayName("hello aop와 그것의 하위에 있는 모든 것")
130+
void packageMatchSubPackage2() {
131+
pointcut.setExpression("execution(* hello.aop..*.*(..)))");
132+
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
133+
}
134+
135+
/**
136+
* 패키지에서의 차이를 이해해야 한다. . : 정확하게 해당 위치의 패키지 ..: 해당 위치의 패키지와 그 하위 패키지도 포함
137+
*/
138+
139+
@Test
140+
@DisplayName("helloMethod 는 MemberServiceImpl 클래스 안에 있으므로 당연히 매칭된다.")
141+
void typeExactMatch() {
142+
pointcut.setExpression("execution(* hello.aop.member.MemberServiceImpl.*(..))");
143+
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
144+
}
145+
146+
@Test
147+
@DisplayName("MemberServiceImpl와 MemberService는 매칭이 되냐? - 됨")
148+
// execution 에서는 MemberService처럼 부모 타입을 선언해도 그 자식 타입은 매칭된다.
149+
void typeExactSuperType() {
150+
pointcut.setExpression("execution(* hello.aop.member.MemberService.*(..))");
151+
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
152+
}
153+
154+
@Test
155+
@DisplayName("MemberServiceImpl와 MemberService에서, 부모가 갖고 있는 메서드만 가능함. 자식의 다른 메서드는 불가능함")
156+
// 아래 코드는 본인(자식)에서 꺼낸 것이므로 당연히 가능
157+
void typeMatchInternal() throws NoSuchMethodException {
158+
pointcut.setExpression("execution(* hello.aop.member.MemberServiceImpl.*(..))");
159+
Method internalMethod = MemberServiceImpl.class.getMethod("internal", String.class);
160+
assertThat(pointcut.matches(internalMethod, MemberServiceImpl.class)).isTrue();
161+
}
162+
163+
@Test
164+
@DisplayName("MemberServiceImpl와 MemberService에서, 부모가 갖고 있는 메서드만 가능함. 자식의 다른 메서드는 불가능함")
165+
void typeMatchNoSUperTypeMethodFalse() throws NoSuchMethodException {
166+
pointcut.setExpression("execution(* hello.aop.member.MemberService.*(..))");
167+
Method internalMethod = MemberServiceImpl.class.getMethod("internal", String.class);
168+
assertThat(pointcut.matches(internalMethod, MemberServiceImpl.class)).isFalse();
169+
}
170+
171+
// String 타입의 파라미터 허용
172+
// (String)
173+
174+
@Test
175+
void argsMatch() {
176+
pointcut.setExpression("execution(* *(String))");
177+
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
178+
}
179+
180+
@Test
181+
void argsMatchNoArgs() {
182+
pointcut.setExpression("execution(* *())");
183+
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
184+
}
185+
186+
@Test
187+
// (Xxx)
188+
@DisplayName("정확히 하나의 파라미터 허용, 모든 타입 허용")
189+
void argsMatchStar() {
190+
pointcut.setExpression("execution(* *(*))");
191+
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
192+
}
193+
194+
@Test
195+
// (), (Xxx), (Xxx, Xxx)
196+
@DisplayName("숫자와 무관하게 모든 파라미터, 모든 타입 허용")
197+
void argsMatchAll() {
198+
pointcut.setExpression("execution(* *(..))");
199+
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
200+
}
201+
202+
@Test
203+
// (String), (String, Xxx), (String, Xxx, Xxx)
204+
@DisplayName("String 타입으로 시작, 숫자와 무관하게 모든 파라미터, 모든 타입 허용")
205+
void argsMatchAllString() {
206+
pointcut.setExpression("execution(* *(String, ..))");
207+
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
208+
}
209+
/**
210+
* execution 파라미터 매칭 규칙은 다음과 같다.
211+
* (String): 정확하게 String 타입 파라미터
212+
* ():파라미터가 없어야 한다
213+
* (*): 정확히 하나의 파라미터, 단 모든 타입을 허용한다.
214+
* (*, *): 정확히 두 개의 파라미터, 단 모든 타입을 허용한다.
215+
* (..): 숫자와 무관하게 모든 파라미터, 모든 타입을 허용한다. 참고로 파라미터가 없어도 된다. '0..*'로 이해하면 된다.
216+
* (String, ..): String 타입으로 시작해야 한다. 숫자와 무관하게 모든 파라미터, 모든 타입을 허용한다.
217+
* - 예) (String), (String, Xxx), (String, Xxx, Xxx) 허용
218+
*/
219+
}

0 commit comments

Comments
 (0)