1. AOP๋?
- ๊ด์ ์งํฅ ํ๋ก๊ทธ๋๋ฐ (Aspect Oriented Programming)
- ํ๋ก์ ํธ ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ผ๋ณด๋ ๊ด์ ์ ๋ฐ๊ฟ๋ณด์๋ ์๋ฏธ
- ํก๋จ ๊ด์ฌ์ฌ(Cross-Cutting Concern)์ ๋ถ๋ฆฌ๋ฅผ ํ์ฉํจ์ผ๋ก์จ ๋ชจ๋์ฑ์ ์ฆ๊ฐ์ํค๋ ๊ฒ์ด ๋ชฉ์ ์ธ ํ๋ก๊ทธ๋๋ฐ ํจ๋ฌ๋ค์
- ์ฌ๋ฌ ๊ฐ์ฒด์ ๊ณตํต์ผ๋ก ์ ์ฉํ ์ ์๋ ๊ธฐ๋ฅ์ ๋ถ๋ฆฌํด์ ๊ฐ๋ฐ์๋ ๋ฐ๋ณต ์์ ์ ์ค์ด๊ณ ํต์ฌ ๊ธฐ๋ฅ ๊ฐ๋ฐ์๋ง ์ง์คํ ์ ์์
๊ฐ๋จํ๊ฒ ํ์ค๋ก AOP๋ฅผ ์ ๋ฆฌํด๋ณด์๋ฉด, AOP๋ ๊ณตํต๋ ๊ธฐ๋ฅ์ ์ฌ์ฌ์ฉํ๋ ๊ธฐ๋ฒ์ด๋ค.
์ ์ฌ์ฉํด์ผํ ๊น??
์ ์ฌ์ง์ ์ฐ์ํ ํ ํฌ 10๋ถ ํ ํฌํก ์ ํ๋ธ ์์์์ ์บก์ณํ ์ฌ์ง์ด๋ค.
๋ง์ฝ 3๊ฐ์ ๋ฉ์๋์ ์คํ ์๊ฐ์ ์ธก์ ํด๋ฌ๋ผ๋ ์์ฒญ์ด ๋ค์ด์ค๋ฉด ๋ฉ์๋ ์์ ์ง์ ๊ณผ ๋๋๋ ์ง์ ์ ์๊ฐ์ ์ธก์ ํด ์ด์ ์ฐจ์ด๋ฅผ ์ถ๋ ฅํ ๊ฒ์ด๋ค.
์ด๋ฌ๋ฉด ๋ถ๊ฐ๊ธฐ๋ฅ๊ณผ ๋น์ฆ๋์ค ๋ก์ง์ด ํจ๊ป ์๊ฒ ๋๊ณ , ์ ์ง๋ณด์ ๋ฟ๋ง ์๋๋ผ ๊ฐ๋ ์ฑ๋ ๋จ์ด์ง๊ฒ๋๋ค.
์ ์ฌ์ง์์ ๋ถ๊ฐ๊ธฐ๋ฅ์ ์คํ์๊ฐ ์ธก์ , ๋ก๊น ์ด๊ณ ์ด๋ค์ ๋ฌถ์ ๊ฒ์ด ํก๋จ ๊ด์ฌ์ฌ์ด๋ค.
์ด๋ฐ ์ํฉ์์ AOP๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ฅ์ ์ด ์๋ค.
- ๊ณตํต ๊ด์ฌ ์ฌํญ์ ํต์ฌ ๊ด์ฌ์ฌํญ์ผ๋ก๋ถํฐ ๋ถ๋ฆฌ์์ผ ํต์ฌ ๋ก์ง์ ๊น๋ํ๊ฒ ์ ์งํ ์ ์๋ค.
- ๊ทธ์ ๋ฐ๋ผ ์ฝ๋์ ๊ฐ๋ ์ฑ, ์ ์ง๋ณด์์ฑ ๋ฑ์ ๋์ผ ์ ์๋ค.
- ๊ฐ๊ฐ์ ๋ชจ๋์ ์์ ์ด ํ์ํ๋ฉด ๋ค๋ฅธ ๋ชจ๋์ ์์ ์์ด ํด๋น ๋ก์ง๋ง ๋ณ๊ฒฝํ๋ฉด ๋๋ค.
- ๊ณตํต ๋ก์ง์ ์ ์ฉํ ๋์์ ์ ํํ ์ ์๋ค
2. AOP์ ์ฃผ์ ๊ฐ๋
Target
๋ถ๊ฐ๊ธฐ๋ฅ์ ๋ถ์ฌํ ๋์(ํด๋์ค)์ ์๋ฏธํ๋ค. ๋ฐ๋ก ์๋์ ์ค๋ช ํ๋ Aspect๊ฐ ์ ์ฉ๋๋ ๋์์ ์๋ฏธํ๋ค.
Aspect
๊ฐ์ฒด์งํฅ ๋ชจ๋์ ์ค๋ธ์ ํธ๋ผ ๋ถ๋ฅด๋ ๊ฒ๊ณผ ๋น์ทํ๊ฒ ๋ถ๊ฐ๊ธฐ๋ฅ ๋ชจ๋์ Aspect๋ผ๊ณ ๋ถ๋ฅด๋ฉฐ, ํต์ฌ ๊ธฐ๋ฅ์ ๋ถ๊ฐ๋์ด ์๋ฏธ๋ฅผ ๊ฐ๋ ํน๋ณํ ๋ชจ๋ ์ด๋ค.
Aspect = Advice + PointCut
Aspect ์์๋ ๋ถ๊ฐ๊ธฐ๋ฅ์ ์ ์ํ๋ Advice,
Advice๋ฅผ ์ด๋์ ์ ์ฉํ ์ง ๊ฒฐ์ ํ๋ PointCut์ด ์๋ค.
์ฐธ๊ณ ๋ก AOP๋ผ๋ ๋ป ์์ฒด๊ฐ ์ ํ๋ฆฌ์ผ์ด์
์ ํต์ฌ์ ์ธ ๊ธฐ๋ฅ์์ ๋ถ๊ฐ์ ์ธ ๊ธฐ๋ฅ์ ๋ถ๋ฆฌํด์ Aspect๋ผ๋ ๋ชจ๋์ ๋ง๋ค์ด ์ค๊ณํ๊ณ ๊ฐ๋ฐํ๋ ๋ฐฉ๋ฒ์ ๋ปํ๋ค.
Advice
Aspect์์ ์ค์ง์ ์ผ๋ก ์ด๋ค ์ผ์ ํด์ผํ ์ง์ ๋ํ ๋ถ๊ฐ๊ธฐ๋ฅ์ ๋ด์ ๊ตฌํ์ฒด๋ฅผ ์๋ฏธํ๋ค.
Advice๋ Target Object์ ์ข
์๋์ง ์๊ธฐ ๋๋ฌธ์ ์์ํ๊ฒ ๋ถ๊ฐ๊ธฐ๋ฅ์๋ง ์ง์คํ ์ ์๋ค. Advice๋ Aspect๊ฐ ‘๋ฌด์’์ ‘์ธ์ ’ํ ์ง๋ฅผ ์ ์ํ๊ณ ์๋ค.
JoinPoint
Advice๊ฐ ์ ์ฉ๋ ์์น๋ฅผ ์๋ฏธํ๋ค.
Spring ํ๋ ์์ํฌ์์ JoinPoint๋ผ๋ฉด Advice๊ฐ ์ ์ฉ๋๋ ๋ฉ์๋๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค.
PoinCut
๋ถ๊ฐ๊ธฐ๋ฅ์ด ์ ์ฉ๋ ๋์(๋ฉ์๋)๋ฅผ ์ ์ ํ๋ ๋ฐฉ๋ฒ์ ์๋ฏธํ๋ค.
Advice๋ฅผ ์ ์ฉํ JoinPoint๋ฅผ ์ ๋ณํ๋ ๊ธฐ๋ฅ์ ์ ์ํ ๋ชจ๋์ด๋ค. JoinPoint์ ์์ธํ ๊ธฐ๋ฅ์ ์ ์ํ๋ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋ฉ๋๋ค.
Proxy
Target์ ๊ฐ์ธ์ Target์ ๋ค์ด์ค๋ ์์ฒญ์ ๋์ ๋ฐ์์ฃผ๋ Warpping ์ค๋ธ์ ํธ์ด๋ค.
Client์์ Target์ ํธ์ถํ๊ฒ ๋๋ฉด Target์ด ์๋ Target์ ๊ฐ์ธ๊ณ ์๋ Proxy๊ฐ ํธ์ถ๋์ด, Target ๋ฉ์๋ ์คํ ์ ์ ์ ์ฒ๋ฆฌ, Target ๋ฉ์๋ ์คํ ํ ํ์ฒ๋ฆฌ๋ฅผ ์คํ์ํฌ ์ ์๋๋ก ๊ตฌ์ฑ๋์ด ์๋ค.
3. ์ฌ์ฉ๋ฒ
@Aspect
@Component
public class TestAop {
@Around("execution(* com.gjgs.modules.users.services.interfaces.UserService.getUser(..))")
public void adviceMethod(ProceedingJoinPoint joinPoint){
Object result = null;
try {
System.out.println("AOP ์์");
result = proceedingJoinPoint.proceed();
System.out.println("AOP ์ข
๋ฃ");
} catch (){
...
}
}
}
@Around๋ Advice๋ก์ Aspect๊ฐ ‘๋ฌด์์’, ์ธ์ ’ ํ ์ง๋ฅผ ์ ์ํ๋ ๋ถ๋ถ์์ ‘์ธ์ ’ ๋ผ๋ ์์ ์ ์ ์ํ ๊ฒ์ด๋ค. ์๋์ ๊ฐ์ด 5๊ฐ์ง ํ์ ์ด ์กด์ฌํ๋ค.
- @Before
- Advice ํ๊ฒ ๋ฉ์๋๊ฐ ํธ์ถ๋๊ธฐ ์ ์ Advice ๊ธฐ๋ฅ์ ์ํ
- @After
- Target ๋ฉ์๋์ ๊ฒฐ๊ณผ์ ๊ด๊ณ์์ด(์ฑ๊ณต,์์ธ์ ๊ด๊ณ์์ด) Target ๋ฉ์๋๊ฐ ์๋ฃ๋๋ฉด Advice ๊ธฐ๋ฅ์ ์ํ
- @AfterReturning(์ ์์ ๋ฐํ ์ดํ)
- Target ๋ฉ์๋๊ฐ ์ฑ๊ณต์ ์ผ๋ก ๊ฒฐ๊ณผ๊ฐ์ ๋ฐํ ํ์ Advice ๊ธฐ๋ฅ์ ์ํ
- @AfterThrowing (์์ธ ๋ฐ์ ์ดํ)
- Target ๋ฉ์๋๊ฐ ์ํ ์ค ์์ธ๋ฅผ ๋์ง๊ฒ ๋๋ฉด Advice ๊ธฐ๋ฅ์ ์ํ
- @Around(๋ฉ์๋ ์คํ ์ ํ)
- Target ๋ฉ์๋๋ฅผ ๊ฐ์ธ์ Target ๋ฉ์๋ ํธ์ถ ์ ๊ณผ ํธ์ถ ํ์ Advice ๊ธฐ๋ฅ์ ์ํ
- Around์ ๊ฒฝ์ฐ, ์ธ์๋ก ProceedingJoinPoint๋ฅผ ๋ฐ์์ proceed ๋ฉ์๋๋ฅผ ์คํ์์ผ์ผ๋ง ํฉ๋๋ค. proceed๋ ์ค์ Target ๋ฉ์๋๋ฅผ ์คํํ๋ ๊ฒ์ด๋ผ๊ณ ๋ณด๋ฉด ๋๊ณ ์ ๋ค์ ๋ฉ์๋ ์คํ ์ , ํ์ ์งํํ ์์ ์ ์ฝ๋ฉ๋ฉด ๋๋ค.
execution(* com.gjgs.modules.users.services.impl.UserService.getUser(..))
Advice์ Value๋ก ๋ค์ด๊ฐ ๋ฌธ์์ด์ PointCut ํํ์ ์ด๋ผ๊ณ ํ๋ค.
PointCut์ ๊ตฌ์ฑ์ 2๊ฐ์ง๋ก ๋ถ๋ฅ๋๋๋ฐ execution์ ์ง์ ์ ๋ผ๊ณ ๋ถ๋ฅด๋ฉฐ ์ด์ธ์ ๋ค์ชฝ ๋ถ๋ถ์ Target ๋ช
์ธ ๋ผ๊ณ ํ๋ค.
์ง์ ์๋ ์ด 9๊ฐ์ง ํ์
์ด ์์ง๋ง ์์ฃผ ์ฐ๋ execution๊ณผ @annotation ์ ๋๋ง ์์๋ณด์.
- execution()
- ์ ๊ทผ์ ํ์, ๋ฆฌํดํ์ , ์ธ์ํ์ , ํด๋์ค/์ธํฐํ์ด์ค, ๋ฉ์๋๋ช , ํ๋ผ๋ฏธํฐํ์ , ์์ธํ์ ๋ฑ์ ์ ๋ถ ์กฐํฉ๊ฐ๋ฅํ ๊ฐ์ฅ ์ธ์ฌํ ์ง์ ์
- ex) execution(* com.blogcode.service.AccountService.*(..)
- AccountService ์ธํฐํ์ด์ค์ ๋ชจ๋ ๋ฉ์๋
- @annotation()
- Target ๋ฉ์๋์ ํน์ ์ ๋ ธํ ์ด์ ์ด ์ง์ ๋ ๊ฒฝ์ฐ
- @annotation(org.springframework.transaction.annotation.Transactional)
- Transactional ์ ๋ ธํ ์ด์ ์ด ์ง์ ๋ ๋ชจ๋ ๋ฉ์๋
execution ํํ์์ ๋ํด ์์ธํ ์์๋ณด๋ฉด
execution([์์์ด] ๋ฆฌํดํ์ [ํด๋์ค์ด๋ฆ].๋ฉ์๋์ด๋ฆ(ํ๋ผ๋ฏธํฐ))
- ์์์ด
- public, private ๋ฑ ์์์ด๋ฅผ ๋ช ์ํ์ง๋ง ์คํ๋ง AOP์์๋ private๋ง ๊ฐ๋ฅ (์๋ต ๊ฐ๋ฅ)
- ๋ฆฌํดํ์
- ๋ฆฌํด ํ์ ์ ๋ช ์
- ํด๋์ค์ด๋ฆ ๋ฐ ๋ฉ์๋์ด๋ฆ
- ํด๋์ค์ด๋ฆ๊ณผ ๋ฉ์๋ ์ด๋ฆ์ ๋ช ์ (ํด๋์ค ์ด๋ฆ์ ํจํค์ง๋ช ๊น์ง ํจ๊ป ๋ช ์)
- ํ๋ผ๋ฏธํฐ
- ๋ฉ์๋์ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ช ์
- ํํ์
- * : ๋ชจ๋ ๊ฐ์ ํํ
- .. : 0๊ฐ ์ด์์ ์๋ฏธ
- execution(void set*(..))
- ๋ฆฌํด ํ์ ์ด void์ด๊ณ ๋ฉ์๋ ์ด๋ฆ์ด set์ผ๋ก ์์ํ๋ฉฐ, ํ๋ผ๋ฏธํฐ๊ฐ 0๊ฐ ์ด์์ธ ๋ฉ์๋
- execution(* sp.aop.service..())
- sp.aop.service ํจํค์ง์ ํ๋ผ๋ฏธํฐ๊ฐ ์๋ ๋ชจ๋ ๋ฉ์๋
- execution(* sp.aop.service...(..))
- sp.aop.service ํจํค์ง ๋ฐ ํ์ ํจํค์ง์ ์๋ ํ๋ผ๋ฏธํฐ๊ฐ 0๊ฐ ์ด์์ธ ๋ชจ๋ ๋ฉ์๋
- execution(* get())
- get์ผ๋ก ์์ํ๊ณ 1๊ฐ์ ํ๋ผ๋ฏธํฐ๋ฅผ ๊ฐ๋ ๋ฉ์๋
- execution(Integer read*(Integer, ..))
- ๋ฆฌ๋๊ฐ์ด Integer์ด๊ณ ๋ฉ์๋ ์ด๋ฆ์ด read๋ก ์์ํ๋ฉฐ ์ฒซ๋ฒ์งธ ํ๋ผ๋ฏธํฐ ํ์ ์ด Integer์ด๊ณ , 1๊ฐ ์ด์์ ํ๋ผ๋ฏธํฐ๋ฅผ ๊ฐ๋ ๋ฉ์๋
์ด์ ํ๋ก์ ํธ์ ์ ์ฉํด๋ณด์.
๋น์ฅ์ ์ ์ฉํด๋ณผ๋งํ ๊ฒ ํ๋ผ๋ฏธํฐ ๋ก๊ทธ์ฐ๊ธฐ์ ์คํ์๊ฐ ์ ๋์๋ค.
๊ทธ ์ค Service๋ ์ด์ด์ ๋ฉ์๋ ์คํ ์๊ฐ์ ์ธก์ ํ์ฌ ์ถ๋ ฅํด์ฃผ์๋ค. ์ถํ์ ๊ณตํต๊ด์ฌ์ฌ๊ฐ ์๊ธด๋ค๋ฉด ๊ทธ๋ ์ถ๊ฐ๋ก ๋ถ๋ฆฌํ๊ธฐ๋ก ํ๋ค.
@Component
@RequiredArgsConstructor
@Aspect
@Slf4j
public class TimingAdvisor {
private final SlackService slackService;
@Pointcut("execution(public * jikgong.domain..*Service.*(..))")
public void pointcut() {}
@Around("pointcut()") // ์ด๋๋ฐ์ด์ค + ํฌ์ธํธ์ปท ์ค์
public Object advice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
String methodName = proceedingJoinPoint.getSignature().getName();
long start = System.currentTimeMillis();
Object result = proceedingJoinPoint.proceed();// ํ๊น ๋ฉ์๋ ํธ์ถ
long end = System.currentTimeMillis();
long runningTime = end - start;
if (runningTime <= 1000) {
log.info("[์ ์ ์คํ] method = {}, ์คํ์๊ฐ = {} ms", methodName, runningTime);
} else {
String message = "[!๊ฒฝ๊ณ ] [๊ธฐ์ค ์คํ ์๊ฐ์ ์ด๊ณผํ์์ต๋๋ค] method = " + methodName + ", ์คํ์๊ฐ = " + runningTime + "ms";
log.error(message);
// Slack ๋ฉ์์ง ์ ์ก
HashMap<String, String> data = new HashMap<>();
data.put("๊ฒฝ๊ณ ๋ด์ฉ", message);
slackService.sendMessage("๊ฒฝ๊ณ : ์คํ ์๊ฐ ์ด๊ณผ", data);
}
return result;
}
}
์ด๋ค ๋ฉ์๋์ ์คํ ์๊ฐ์ด ์ผ๋ง ๊ฑธ๋ ธ๋ค๋ฅผ ์ถ๋ ฅํด์ฃผ๋ค.
๊ธฐ์ค ์๊ฐ (1000ms) ๋ณด๋ค ์ค๋ ๊ฑธ๋ฆฌ๋ ๊ฒฝ์ฐ ๊ฒฝ๊ณ log ๋ฐ slack์ผ๋ก ๋ฉ์์ง๋ฅผ ๋ณด๋ด์ฃผ์๊ณ , ๊ธฐ์ค ์๊ฐ ๋ณด๋ค ์ ๊ฒ ๊ฑธ๋ฆฌ๋ ๊ฒฝ์ฐ๋ ์ ์ ์คํ log๋ฅผ ์ฐ์ด์ฃผ์ด ๋น์ ์์ ์ผ๋ก ์ค๋ ๊ฑธ๋ฆฌ๋ ๋ฉ์๋์ ๋ํด ํ์ธํ ์ ์๋๋ก ์ธํ ํ๋ค.
(์ฐธ๊ณ )
slack ํธ์ถ ์ฝ๋
@Service
@Slf4j
public class SlackService {
@Value("${webhook.slack.url}")
private String SLACK_WEBHOOK_URL;
private final Slack slackClient = Slack.getInstance();
/**
* ์ฌ๋ ๋ฉ์์ง ์ ์ก
**/
public void sendMessage(String title, HashMap<String, String> data) {
try {
slackClient.send(SLACK_WEBHOOK_URL, payload(p -> p
.text(title) // ๋ฉ์์ง ์ ๋ชฉ
.attachments(List.of(
Attachment.builder().color(Color.GREEN.toString()) // ๋ฉ์์ง ์์
.fields( // ๋ฉ์์ง ๋ณธ๋ฌธ ๋ด์ฉ
data.keySet().stream().map(key -> generateSlackField(key, data.get(key))).collect(Collectors.toList())
).build())))
);
log.info("slack ์๋ฆผ ์ ์ก");
} catch (IOException e) {
log.error("slack ์๋ฆผ ์ ์ก ์คํจ");
e.printStackTrace();
}
}
/**
* Slack Field ์์ฑ
**/
private Field generateSlackField(String title, String value) {
return Field.builder()
.title(title)
.value(value)
.valueShortEnough(false)
.build();
}
}