[{"data":1,"prerenderedAt":1153},["ShallowReactive",2],{"post-document:\u002Fjava\u002Fspring\u002F2026\u002F08\u002F20\u002Fspring-aop\u002F":3},{"id":4,"title":5,"body":6,"categories":1136,"date":1138,"description":1139,"extension":1140,"image":1141,"key_concepts":1141,"last_modified_at":1141,"legacyPath":1142,"meta":1143,"navigation":590,"part":1145,"path":1146,"published":590,"robots":1141,"seo":1147,"series":1148,"stem":1149,"strengths":1141,"summary":1141,"tags":1150,"tradeoffs":1141,"__hash__":1152},"posts\u002Fposts\u002FBackend\u002FSPRING\u002FSpring\u002F2026-08-20-spring-aop.md","Spring 기초 Part 8: AOP와 공통 관심사 분리",{"type":7,"value":8,"toc":1120},"minimark",[9,13,20,23,26,37,40,45,145,148,150,154,160,163,169,220,225,227,231,234,261,264,291,325,330,332,336,339,358,364,370,373,388,391,393,397,460,466,489,495,500,539,542,547,609,612,614,620,625,717,720,726,729,738,741,769,772,774,778,913,916,918,922,928,972,975,977,981,985,988,1007,1011,1014,1018,1021,1025,1028,1030,1034,1094,1099,1103,1116],[10,11,5],"h2",{"id":12},"spring-기초-part-8-aop와-공통-관심사-분리",[14,15,16],"blockquote",{},[17,18,19],"p",{},"AOP는 여러 기능에서 반복되는 공통 관심사를 핵심 비즈니스 로직과 분리하는 프로그래밍 방식이다.",[17,21,22],{},"Service 메서드의 핵심 목적은 주문, 결제, 회원 조회 같은 업무를 처리하는 것이다.",[17,24,25],{},"하지만 로그, 권한 검사, 실행 시간 측정 같은 코드를 모든 메서드에 반복하면 핵심 로직이 흐려진다.",[27,28,34],"pre",{"className":29,"code":31,"language":32,"meta":33},[30],"language-text","핵심 관심사\n→ 주문, 결제, 회원 조회\n\n공통 관심사\n→ 로그, 보안, 트랜잭션, 실행 시간 측정\n","text","",[35,36,31],"code",{"__ignoreMap":33},[38,39],"hr",{},[41,42,44],"h3",{"id":43},"_1-먼저-알아둘-단어","1. 먼저 알아둘 단어",[46,47,48,64],"table",{},[49,50,51],"thead",{},[52,53,54,58,61],"tr",{},[55,56,57],"th",{},"용어",[55,59,60],{},"정의",[55,62,63],{},"핵심 질문",[65,66,67,79,90,101,112,123,134],"tbody",{},[52,68,69,73,76],{},[70,71,72],"td",{},"Aspect",[70,74,75],{},"공통 기능과 적용 규칙을 모아놓은 모듈",[70,77,78],{},"어떤 공통 기능인가?",[52,80,81,84,87],{},[70,82,83],{},"Target",[70,85,86],{},"공통 기능이 적용될 실제 객체",[70,88,89],{},"누구를 감쌀 것인가?",[52,91,92,95,98],{},[70,93,94],{},"Join Point",[70,96,97],{},"공통 기능을 적용할 수 있는 실행 지점",[70,99,100],{},"어느 실행 지점인가?",[52,102,103,106,109],{},[70,104,105],{},"Pointcut",[70,107,108],{},"적용할 메서드를 선택하는 조건",[70,110,111],{},"어디에 적용할 것인가?",[52,113,114,117,120],{},[70,115,116],{},"Advice",[70,118,119],{},"선택된 지점에서 실행할 공통 기능",[70,121,122],{},"언제 무엇을 실행할 것인가?",[52,124,125,128,131],{},[70,126,127],{},"Weaving",[70,129,130],{},"공통 기능을 실제 코드 실행에 연결하는 과정",[70,132,133],{},"어떻게 결합하는가?",[52,135,136,139,142],{},[70,137,138],{},"Proxy",[70,140,141],{},"Target 호출을 가로채 Advice를 실행하는 대리 객체",[70,143,144],{},"실제로 누가 가로채는가?",[17,146,147],{},"Spring AOP에서는 주로 메서드 실행이 Join Point가 된다.",[38,149],{},[41,151,153],{"id":152},"_2-전체-동작-구조","2. 전체 동작 구조",[27,155,158],{"className":156,"code":157,"language":32,"meta":33},[30],"Controller\n    ↓\nSpring Proxy\n    ↓ Before Advice\n실제 Service 메서드\n    ↓ After Advice\nSpring Proxy\n    ↓\nController\n",[35,159,157],{"__ignoreMap":33},[17,161,162],{},"Spring은 AOP 설정을 읽고 적용 대상 Bean을 Proxy로 감싼다.",[17,164,165],{},[166,167,168],"strong",{},"Spring AOP 실행 흐름",[27,170,174],{"className":171,"code":172,"language":173,"meta":33,"style":33},"language-mermaid shiki shiki-themes github-light github-dark","flowchart TD\n    C[Controller] --> P[Spring AOP Proxy]\n    P --> B[Before Advice]\n    B --> T[Target Service]\n    T --> A[After Advice]\n    A --> P\n    P --> C\n","mermaid",[35,175,176,184,190,196,202,208,214],{"__ignoreMap":33},[177,178,181],"span",{"class":179,"line":180},"line",1,[177,182,183],{},"flowchart TD\n",[177,185,187],{"class":179,"line":186},2,[177,188,189],{},"    C[Controller] --> P[Spring AOP Proxy]\n",[177,191,193],{"class":179,"line":192},3,[177,194,195],{},"    P --> B[Before Advice]\n",[177,197,199],{"class":179,"line":198},4,[177,200,201],{},"    B --> T[Target Service]\n",[177,203,205],{"class":179,"line":204},5,[177,206,207],{},"    T --> A[After Advice]\n",[177,209,211],{"class":179,"line":210},6,[177,212,213],{},"    A --> P\n",[177,215,217],{"class":179,"line":216},7,[177,218,219],{},"    P --> C\n",[14,221,222],{},[17,223,224],{},"AOP는 무엇을 어디에 적용할지 정의하고, Spring은 그 설정을 바탕으로 Proxy를 만들며, Proxy가 실제 호출을 가로챈다.",[38,226],{},[41,228,230],{"id":229},"_3-aspect-만들기","3. Aspect 만들기",[17,232,233],{},"AOP 기능을 사용하려면 일반적으로 AOP Starter를 추가한다.",[27,235,239],{"className":236,"code":237,"language":238,"meta":33,"style":33},"language-xml shiki shiki-themes github-light github-dark","\u003Cdependency>\n    \u003CgroupId>org.springframework.boot\u003C\u002FgroupId>\n    \u003CartifactId>spring-boot-starter-aop\u003C\u002FartifactId>\n\u003C\u002Fdependency>\n","xml",[35,240,241,246,251,256],{"__ignoreMap":33},[177,242,243],{"class":179,"line":180},[177,244,245],{},"\u003Cdependency>\n",[177,247,248],{"class":179,"line":186},[177,249,250],{},"    \u003CgroupId>org.springframework.boot\u003C\u002FgroupId>\n",[177,252,253],{"class":179,"line":192},[177,254,255],{},"    \u003CartifactId>spring-boot-starter-aop\u003C\u002FartifactId>\n",[177,257,258],{"class":179,"line":198},[177,259,260],{},"\u003C\u002Fdependency>\n",[17,262,263],{},"Aspect 클래스는 다음처럼 시작한다.",[27,265,269],{"className":266,"code":267,"language":268,"meta":33,"style":33},"language-java shiki shiki-themes github-light github-dark","@Aspect\n@Component\npublic class LoggingAspect {\n}\n","java",[35,270,271,276,281,286],{"__ignoreMap":33},[177,272,273],{"class":179,"line":180},[177,274,275],{},"@Aspect\n",[177,277,278],{"class":179,"line":186},[177,279,280],{},"@Component\n",[177,282,283],{"class":179,"line":192},[177,284,285],{},"public class LoggingAspect {\n",[177,287,288],{"class":179,"line":198},[177,289,290],{},"}\n",[46,292,293,303],{},[49,294,295],{},[52,296,297,300],{},[55,298,299],{},"어노테이션",[55,301,302],{},"역할",[65,304,305,315],{},[52,306,307,312],{},[70,308,309],{},[35,310,311],{},"@Aspect",[70,313,314],{},"이 클래스가 AOP 설정과 Advice를 담는 Aspect임을 표시",[52,316,317,322],{},[70,318,319],{},[35,320,321],{},"@Component",[70,323,324],{},"Spring이 객체를 생성하고 Bean으로 관리하도록 등록",[17,326,327,329],{},[35,328,311],{},"만 붙이고 Bean으로 등록하지 않으면 Spring AOP가 해당 객체를 사용하지 못한다.",[38,331],{},[41,333,335],{"id":334},"_4-pointcut","4. Pointcut",[17,337,338],{},"Pointcut은 AOP를 적용할 메서드를 고르는 조건이다.",[27,340,342],{"className":266,"code":341,"language":268,"meta":33,"style":33},"@Pointcut(\"execution(* com.example.service.*.*(..))\")\npublic void serviceMethods() {\n}\n",[35,343,344,349,354],{"__ignoreMap":33},[177,345,346],{"class":179,"line":180},[177,347,348],{},"@Pointcut(\"execution(* com.example.service.*.*(..))\")\n",[177,350,351],{"class":179,"line":186},[177,352,353],{},"public void serviceMethods() {\n",[177,355,356],{"class":179,"line":192},[177,357,290],{},[17,359,360,363],{},[35,361,362],{},"serviceMethods()","를 실제 업무 메서드로 호출하는 것이 아니다. 긴 Pointcut 표현식에 이름을 붙인 것이다.",[27,365,368],{"className":366,"code":367,"language":32,"meta":33},[30],"execution(\n    *                         반환 타입은 무엇이든 가능\n    com.example.service.*    service 패키지의 모든 클래스\n    .*                        모든 메서드\n    (..)                      매개변수 개수와 타입은 무엇이든 가능\n)\n",[35,369,367],{"__ignoreMap":33},[17,371,372],{},"이 이름을 여러 Advice에서 재사용할 수 있다.",[27,374,376],{"className":266,"code":375,"language":268,"meta":33,"style":33},"@Before(\"serviceMethods()\")\n@After(\"serviceMethods()\")\n",[35,377,378,383],{"__ignoreMap":33},[177,379,380],{"class":179,"line":180},[177,381,382],{},"@Before(\"serviceMethods()\")\n",[177,384,385],{"class":179,"line":186},[177,386,387],{},"@After(\"serviceMethods()\")\n",[17,389,390],{},"Pointcut 범위를 지나치게 넓게 지정하면 원하지 않는 메서드까지 Proxy 대상이 될 수 있다. 실제 패키지와 클래스 범위를 정확히 확인해야 한다.",[38,392],{},[41,394,396],{"id":395},"_5-advice-종류","5. Advice 종류",[46,398,399,408],{},[49,400,401],{},[52,402,403,405],{},[55,404,116],{},[55,406,407],{},"실행 시점",[65,409,410,420,430,440,450],{},[52,411,412,417],{},[70,413,414],{},[35,415,416],{},"@Before",[70,418,419],{},"Target 메서드 실행 전",[52,421,422,427],{},[70,423,424],{},[35,425,426],{},"@After",[70,428,429],{},"정상 종료와 예외 여부와 관계없이 실행 후",[52,431,432,437],{},[70,433,434],{},[35,435,436],{},"@AfterReturning",[70,438,439],{},"Target 메서드가 정상 반환한 후",[52,441,442,447],{},[70,443,444],{},[35,445,446],{},"@AfterThrowing",[70,448,449],{},"Target 메서드에서 예외가 발생한 후",[52,451,452,457],{},[70,453,454],{},[35,455,456],{},"@Around",[70,458,459],{},"Target 메서드 전체를 감싸고 전후를 직접 제어",[461,462,464],"h4",{"id":463},"before",[35,465,416],{},[27,467,469],{"className":266,"code":468,"language":268,"meta":33,"style":33},"@Before(\"serviceMethods()\")\npublic void beforeAdvice(JoinPoint joinPoint) {\n    System.out.println(\"[Before] \" + joinPoint.getSignature());\n}\n",[35,470,471,475,480,485],{"__ignoreMap":33},[177,472,473],{"class":179,"line":180},[177,474,382],{},[177,476,477],{"class":179,"line":186},[177,478,479],{},"public void beforeAdvice(JoinPoint joinPoint) {\n",[177,481,482],{"class":179,"line":192},[177,483,484],{},"    System.out.println(\"[Before] \" + joinPoint.getSignature());\n",[177,486,487],{"class":179,"line":198},[177,488,290],{},[27,490,493],{"className":491,"code":492,"language":32,"meta":33},[30],"beforeAdvice()\n      ↓\n실제 메서드\n",[35,494,492],{"__ignoreMap":33},[461,496,498],{"id":497},"afterreturning",[35,499,436],{},[27,501,503],{"className":266,"code":502,"language":268,"meta":33,"style":33},"@AfterReturning(\n        pointcut = \"serviceMethods()\",\n        returning = \"result\"\n)\npublic void afterReturning(JoinPoint joinPoint, Object result) {\n    System.out.println(\"[Success] \" + joinPoint.getSignature());\n}\n",[35,504,505,510,515,520,525,530,535],{"__ignoreMap":33},[177,506,507],{"class":179,"line":180},[177,508,509],{},"@AfterReturning(\n",[177,511,512],{"class":179,"line":186},[177,513,514],{},"        pointcut = \"serviceMethods()\",\n",[177,516,517],{"class":179,"line":192},[177,518,519],{},"        returning = \"result\"\n",[177,521,522],{"class":179,"line":198},[177,523,524],{},")\n",[177,526,527],{"class":179,"line":204},[177,528,529],{},"public void afterReturning(JoinPoint joinPoint, Object result) {\n",[177,531,532],{"class":179,"line":210},[177,533,534],{},"    System.out.println(\"[Success] \" + joinPoint.getSignature());\n",[177,536,537],{"class":179,"line":216},[177,538,290],{},[17,540,541],{},"정상적으로 값을 반환했을 때만 실행된다.",[461,543,545],{"id":544},"afterthrowing",[35,546,446],{},[27,548,550],{"className":266,"code":549,"language":268,"meta":33,"style":33},"@AfterThrowing(\n        pointcut = \"serviceMethods()\",\n        throwing = \"exception\"\n)\npublic void afterThrowing(\n        JoinPoint joinPoint,\n        Throwable exception) {\n\n    System.out.println(\"[Failure] \" + joinPoint.getSignature());\n    System.out.println(exception.getMessage());\n}\n",[35,551,552,557,561,566,570,575,580,585,592,598,604],{"__ignoreMap":33},[177,553,554],{"class":179,"line":180},[177,555,556],{},"@AfterThrowing(\n",[177,558,559],{"class":179,"line":186},[177,560,514],{},[177,562,563],{"class":179,"line":192},[177,564,565],{},"        throwing = \"exception\"\n",[177,567,568],{"class":179,"line":198},[177,569,524],{},[177,571,572],{"class":179,"line":204},[177,573,574],{},"public void afterThrowing(\n",[177,576,577],{"class":179,"line":210},[177,578,579],{},"        JoinPoint joinPoint,\n",[177,581,582],{"class":179,"line":216},[177,583,584],{},"        Throwable exception) {\n",[177,586,588],{"class":179,"line":587},8,[177,589,591],{"emptyLinePlaceholder":590},true,"\n",[177,593,595],{"class":179,"line":594},9,[177,596,597],{},"    System.out.println(\"[Failure] \" + joinPoint.getSignature());\n",[177,599,601],{"class":179,"line":600},10,[177,602,603],{},"    System.out.println(exception.getMessage());\n",[177,605,607],{"class":179,"line":606},11,[177,608,290],{},[17,610,611],{},"예외가 발생했을 때 로그를 남기는 용도로 사용할 수 있다.",[38,613],{},[41,615,617,618],{"id":616},"_6-around","6. ",[35,619,456],{},[17,621,622,624],{},[35,623,456],{},"는 실제 메서드 실행 전체를 감싼다.",[27,626,628],{"className":266,"code":627,"language":268,"meta":33,"style":33},"@Around(\"serviceMethods()\")\npublic Object measureExecutionTime(\n        ProceedingJoinPoint joinPoint) throws Throwable {\n\n    long start = System.nanoTime();\n\n    try {\n        return joinPoint.proceed();\n    } finally {\n        long end = System.nanoTime();\n        long elapsed = end - start;\n\n        System.out.println(\n                joinPoint.getSignature() + \" : \" + elapsed + \"ns\"\n        );\n    }\n}\n",[35,629,630,635,640,645,649,654,658,663,668,673,678,683,688,694,700,706,712],{"__ignoreMap":33},[177,631,632],{"class":179,"line":180},[177,633,634],{},"@Around(\"serviceMethods()\")\n",[177,636,637],{"class":179,"line":186},[177,638,639],{},"public Object measureExecutionTime(\n",[177,641,642],{"class":179,"line":192},[177,643,644],{},"        ProceedingJoinPoint joinPoint) throws Throwable {\n",[177,646,647],{"class":179,"line":198},[177,648,591],{"emptyLinePlaceholder":590},[177,650,651],{"class":179,"line":204},[177,652,653],{},"    long start = System.nanoTime();\n",[177,655,656],{"class":179,"line":210},[177,657,591],{"emptyLinePlaceholder":590},[177,659,660],{"class":179,"line":216},[177,661,662],{},"    try {\n",[177,664,665],{"class":179,"line":587},[177,666,667],{},"        return joinPoint.proceed();\n",[177,669,670],{"class":179,"line":594},[177,671,672],{},"    } finally {\n",[177,674,675],{"class":179,"line":600},[177,676,677],{},"        long end = System.nanoTime();\n",[177,679,680],{"class":179,"line":606},[177,681,682],{},"        long elapsed = end - start;\n",[177,684,686],{"class":179,"line":685},12,[177,687,591],{"emptyLinePlaceholder":590},[177,689,691],{"class":179,"line":690},13,[177,692,693],{},"        System.out.println(\n",[177,695,697],{"class":179,"line":696},14,[177,698,699],{},"                joinPoint.getSignature() + \" : \" + elapsed + \"ns\"\n",[177,701,703],{"class":179,"line":702},15,[177,704,705],{},"        );\n",[177,707,709],{"class":179,"line":708},16,[177,710,711],{},"    }\n",[177,713,715],{"class":179,"line":714},17,[177,716,290],{},[17,718,719],{},"실행 순서는 다음과 같다.",[27,721,724],{"className":722,"code":723,"language":32,"meta":33},[30],"Around Advice 시작\n       ↓\n실행 전 코드\n       ↓\njoinPoint.proceed()\n       ↓\n실제 Target 메서드\n       ↓\n실행 후 코드\n       ↓\n결과 반환\n",[35,725,723],{"__ignoreMap":33},[17,727,728],{},"다음 호출이 가장 중요하다.",[27,730,732],{"className":266,"code":731,"language":268,"meta":33,"style":33},"joinPoint.proceed();\n",[35,733,734],{"__ignoreMap":33},[177,735,736],{"class":179,"line":180},[177,737,731],{},[17,739,740],{},"이 코드를 실행하지 않으면 실제 Target 메서드가 실행되지 않는다.",[27,742,744],{"className":266,"code":743,"language":268,"meta":33,"style":33},"@Around(\"serviceMethods()\")\npublic Object wrongAdvice(ProceedingJoinPoint joinPoint) {\n    System.out.println(\"호출을 가로챔\");\n    return null; \u002F\u002F Target을 호출하지 않음\n}\n",[35,745,746,750,755,760,765],{"__ignoreMap":33},[177,747,748],{"class":179,"line":180},[177,749,634],{},[177,751,752],{"class":179,"line":186},[177,753,754],{},"public Object wrongAdvice(ProceedingJoinPoint joinPoint) {\n",[177,756,757],{"class":179,"line":192},[177,758,759],{},"    System.out.println(\"호출을 가로챔\");\n",[177,761,762],{"class":179,"line":198},[177,763,764],{},"    return null; \u002F\u002F Target을 호출하지 않음\n",[177,766,767],{"class":179,"line":204},[177,768,290],{},[17,770,771],{},"반환값을 임의로 없애거나 예외를 삼키면 기존 메서드의 동작을 깨뜨릴 수 있다.",[38,773],{},[41,775,777],{"id":776},"_7-실제-로그-aspect-예제","7. 실제 로그 Aspect 예제",[27,779,781],{"className":266,"code":780,"language":268,"meta":33,"style":33},"@Aspect\n@Component\npublic class ServiceLoggingAspect {\n\n    @Pointcut(\"execution(* com.example.service..*(..))\")\n    public void serviceMethods() {\n    }\n\n    @Around(\"serviceMethods()\")\n    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {\n        String method = joinPoint.getSignature().toShortString();\n\n        System.out.println(\"[START] \" + method);\n\n        try {\n            Object result = joinPoint.proceed();\n            System.out.println(\"[SUCCESS] \" + method);\n            return result;\n        } catch (Throwable exception) {\n            System.out.println(\"[FAILURE] \" + method);\n            throw exception;\n        } finally {\n            System.out.println(\"[END] \" + method);\n        }\n    }\n}\n",[35,782,783,787,791,796,800,805,810,814,818,823,828,833,837,842,846,851,856,861,867,873,879,885,891,897,903,908],{"__ignoreMap":33},[177,784,785],{"class":179,"line":180},[177,786,275],{},[177,788,789],{"class":179,"line":186},[177,790,280],{},[177,792,793],{"class":179,"line":192},[177,794,795],{},"public class ServiceLoggingAspect {\n",[177,797,798],{"class":179,"line":198},[177,799,591],{"emptyLinePlaceholder":590},[177,801,802],{"class":179,"line":204},[177,803,804],{},"    @Pointcut(\"execution(* com.example.service..*(..))\")\n",[177,806,807],{"class":179,"line":210},[177,808,809],{},"    public void serviceMethods() {\n",[177,811,812],{"class":179,"line":216},[177,813,711],{},[177,815,816],{"class":179,"line":587},[177,817,591],{"emptyLinePlaceholder":590},[177,819,820],{"class":179,"line":594},[177,821,822],{},"    @Around(\"serviceMethods()\")\n",[177,824,825],{"class":179,"line":600},[177,826,827],{},"    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {\n",[177,829,830],{"class":179,"line":606},[177,831,832],{},"        String method = joinPoint.getSignature().toShortString();\n",[177,834,835],{"class":179,"line":685},[177,836,591],{"emptyLinePlaceholder":590},[177,838,839],{"class":179,"line":690},[177,840,841],{},"        System.out.println(\"[START] \" + method);\n",[177,843,844],{"class":179,"line":696},[177,845,591],{"emptyLinePlaceholder":590},[177,847,848],{"class":179,"line":702},[177,849,850],{},"        try {\n",[177,852,853],{"class":179,"line":708},[177,854,855],{},"            Object result = joinPoint.proceed();\n",[177,857,858],{"class":179,"line":714},[177,859,860],{},"            System.out.println(\"[SUCCESS] \" + method);\n",[177,862,864],{"class":179,"line":863},18,[177,865,866],{},"            return result;\n",[177,868,870],{"class":179,"line":869},19,[177,871,872],{},"        } catch (Throwable exception) {\n",[177,874,876],{"class":179,"line":875},20,[177,877,878],{},"            System.out.println(\"[FAILURE] \" + method);\n",[177,880,882],{"class":179,"line":881},21,[177,883,884],{},"            throw exception;\n",[177,886,888],{"class":179,"line":887},22,[177,889,890],{},"        } finally {\n",[177,892,894],{"class":179,"line":893},23,[177,895,896],{},"            System.out.println(\"[END] \" + method);\n",[177,898,900],{"class":179,"line":899},24,[177,901,902],{},"        }\n",[177,904,906],{"class":179,"line":905},25,[177,907,711],{},[177,909,911],{"class":179,"line":910},26,[177,912,290],{},[17,914,915],{},"예외를 기록한 뒤 다시 던져야 Controller의 예외 처리와 트랜잭션 롤백 흐름이 유지된다.",[38,917],{},[41,919,921],{"id":920},"_8-aop와-proxy의-역할-구분","8. AOP와 Proxy의 역할 구분",[27,923,926],{"className":924,"code":925,"language":32,"meta":33},[30],"AOP\n→ 로그 기능을 Service 패키지의 메서드에 적용하겠다고 정의\n\nSpring\n→ 설정을 읽고 적용 대상 Bean을 확인\n\nProxy\n→ 실제 메서드 호출을 가로채 Advice 실행\n\nTarget\n→ 원래 비즈니스 로직 실행\n",[35,927,925],{"__ignoreMap":33},[46,929,930,940],{},[49,931,932],{},[52,933,934,937],{},[55,935,936],{},"구분",[55,938,939],{},"핵심 역할",[65,941,942,950,958,965],{},[52,943,944,947],{},[70,945,946],{},"AOP",[70,948,949],{},"공통 기능과 적용 범위를 정의",[52,951,952,955],{},[70,953,954],{},"Spring",[70,956,957],{},"Aspect를 읽고 Proxy를 구성",[52,959,960,962],{},[70,961,138],{},[70,963,964],{},"호출을 가로채 Advice 실행",[52,966,967,969],{},[70,968,83],{},[70,970,971],{},"원래 업무 로직 실행",[17,973,974],{},"Spring AOP가 Proxy를 사용한다고 해서 모든 AOP 구현이 Proxy만 사용하는 것은 아니다. AspectJ처럼 컴파일 시점이나 로딩 시점에 코드를 결합하는 방식도 있다.",[38,976],{},[41,978,980],{"id":979},"_9-주의할-점","9. 주의할 점",[461,982,984],{"id":983},"자기-호출","자기 호출",[17,986,987],{},"같은 객체의 내부 호출은 Spring Proxy를 다시 거치지 않으므로 Advice가 적용되지 않을 수 있다.",[27,989,991],{"className":266,"code":990,"language":268,"meta":33,"style":33},"public void outer() {\n    inner();\n}\n",[35,992,993,998,1003],{"__ignoreMap":33},[177,994,995],{"class":179,"line":180},[177,996,997],{},"public void outer() {\n",[177,999,1000],{"class":179,"line":186},[177,1001,1002],{},"    inner();\n",[177,1004,1005],{"class":179,"line":192},[177,1006,290],{},[461,1008,1010],{"id":1009},"중복-proxy","중복 Proxy",[17,1012,1013],{},"같은 기능에 수동 Proxy와 Spring AOP를 동시에 적용하면 로그나 실행 시간 측정이 두 번 실행될 수 있다.",[461,1015,1017],{"id":1016},"민감-정보-로그","민감 정보 로그",[17,1019,1020],{},"메서드 인자를 전부 기록하면 비밀번호, 토큰, 개인정보가 로그에 남을 수 있다. 기록 대상을 제한해야 한다.",[461,1022,1024],{"id":1023},"aop에-업무-로직-넣지-않기","AOP에 업무 로직 넣지 않기",[17,1026,1027],{},"AOP는 반복되는 기반 기능에 적합하다. 주문 금액 계산 같은 핵심 업무 규칙은 Service나 도메인 객체에 둔다.",[38,1029],{},[41,1031,1033],{"id":1032},"핵심-정리","핵심 정리",[46,1035,1036,1046],{},[49,1037,1038],{},[52,1039,1040,1043],{},[55,1041,1042],{},"개념",[55,1044,1045],{},"한 줄 정리",[65,1047,1048,1055,1061,1068,1077,1087],{},[52,1049,1050,1052],{},[70,1051,72],{},[70,1053,1054],{},"공통 기능과 적용 규칙을 모은 모듈",[52,1056,1057,1059],{},[70,1058,105],{},[70,1060,108],{},[52,1062,1063,1065],{},[70,1064,116],{},[70,1066,1067],{},"선택된 메서드에서 실행할 공통 기능",[52,1069,1070,1074],{},[70,1071,1072],{},[35,1073,456],{},[70,1075,1076],{},"실제 메서드 전체를 감싸는 Advice",[52,1078,1079,1084],{},[70,1080,1081],{},[35,1082,1083],{},"proceed()",[70,1085,1086],{},"실제 Target 메서드를 실행",[52,1088,1089,1091],{},[70,1090,138],{},[70,1092,1093],{},"Advice와 Target 호출을 실제로 연결하는 대리 객체",[14,1095,1096],{},[17,1097,1098],{},"AOP는 공통 기능을 핵심 로직에서 분리하고, Spring AOP는 Proxy를 이용해 선택한 메서드의 앞뒤에서 그 기능을 실행한다.",[41,1100,1102],{"id":1101},"참고-자료","참고 자료",[1104,1105,1106],"ul",{},[1107,1108,1109],"li",{},[1110,1111,1115],"a",{"href":1112,"rel":1113},"https:\u002F\u002Fdocs.spring.io\u002Fspring-framework\u002Freference\u002Fcore\u002Faop.html",[1114],"nofollow","Spring Framework AOP 공식 문서",[1117,1118,1119],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":33,"searchDepth":186,"depth":186,"links":1121},[1122],{"id":12,"depth":186,"text":5,"children":1123},[1124,1125,1126,1127,1128,1129,1131,1132,1133,1134,1135],{"id":43,"depth":192,"text":44},{"id":152,"depth":192,"text":153},{"id":229,"depth":192,"text":230},{"id":334,"depth":192,"text":335},{"id":395,"depth":192,"text":396},{"id":616,"depth":192,"text":1130},"6. @Around",{"id":776,"depth":192,"text":777},{"id":920,"depth":192,"text":921},{"id":979,"depth":192,"text":980},{"id":1032,"depth":192,"text":1033},{"id":1101,"depth":192,"text":1102},[268,1137],"spring","2026-08-20 09:00:00 +0900","Aspect, Pointcut, Advice, JoinPoint의 의미와 Before, After, Around Advice가 Proxy를 통해 동작하는 흐름을 정리한다.","md",null,"\u002Fjava\u002Fspring\u002F2026\u002F08\u002F20\u002Fspring-aop\u002F",{"layout":1144},"post","8","\u002Fposts\u002Fbackend\u002Fspring\u002Fspring\u002F2026-08-20-spring-aop",{"title":5,"description":1139},"Spring 기초","posts\u002FBackend\u002FSPRING\u002FSpring\u002F2026-08-20-spring-aop",[1151,954,946,72,105,116,138],"Java","6QF8zl4PwDL7iEUyCu9ygkYYUvX-RWnQ8Rn51tc1Z-A",1788744786383]