[{"data":1,"prerenderedAt":1260},["ShallowReactive",2],{"post-document:\u002Fjava\u002Fspring\u002F2026\u002F08\u002F19\u002Fspring-proxy\u002F":3},{"id":4,"title":5,"body":6,"categories":1240,"date":1242,"description":1243,"extension":1244,"image":1245,"key_concepts":1245,"last_modified_at":1245,"legacyPath":1246,"meta":1247,"navigation":252,"part":1249,"path":1250,"published":252,"robots":1245,"seo":1251,"series":1252,"stem":1253,"strengths":1245,"summary":1245,"tags":1254,"tradeoffs":1245,"__hash__":1259},"posts\u002Fposts\u002FBackend\u002FSPRING\u002FSpring\u002F2026-08-19-spring-proxy.md","Spring 기초 Part 7: Proxy 패턴과 Spring Proxy",{"type":7,"value":8,"toc":1225},"minimark",[9,13,20,31,45,48,53,197,199,203,206,233,238,288,292,387,390,441,447,450,459,461,465,468,487,490,546,549,569,572,634,636,640,643,662,668,708,714,721,727,729,733,736,803,806,808,815,843,846,852,904,906,910,917,920,978,984,987,990,1028,1090,1092,1096,1132,1137,1139,1143,1199,1204,1208,1221],[10,11,5],"h2",{"id":12},"spring-기초-part-7-proxy-패턴과-spring-proxy",[14,15,16],"blockquote",{},[17,18,19],"p",{},"Proxy는 Client와 실제 객체 사이에서 요청을 먼저 받고, 실제 메서드 호출 전후에 부가 기능을 수행하는 대리 객체다.",[21,22,28],"pre",{"className":23,"code":25,"language":26,"meta":27},[24],"language-text","Client\n  → Proxy\n  → 실제 객체\n","text","",[29,30,25],"code",{"__ignoreMap":27},[17,32,33,34,37,38,37,41,44],{},"Proxy를 이해하면 ",[29,35,36],{},"@Transactional",", ",[29,39,40],{},"@Async",[29,42,43],{},"@Cacheable",", Spring AOP가 왜 같은 클래스의 내부 호출에서 동작하지 않을 수 있는지도 이해할 수 있다.",[46,47],"hr",{},[49,50,52],"h3",{"id":51},"_1-먼저-알아둘-단어","1. 먼저 알아둘 단어",[54,55,56,72],"table",{},[57,58,59],"thead",{},[60,61,62,66,69],"tr",{},[63,64,65],"th",{},"용어",[63,67,68],{},"정의",[63,70,71],{},"쉽게 말하면",[73,74,75,87,98,109,120,131,142,153,164,175,186],"tbody",{},[60,76,77,81,84],{},[78,79,80],"td",{},"Client",[78,82,83],{},"기능을 요청하는 객체",[78,85,86],{},"Service를 호출하는 Controller",[60,88,89,92,95],{},[78,90,91],{},"Subject",[78,93,94],{},"Proxy와 실제 객체가 함께 따르는 인터페이스",[78,96,97],{},"두 객체의 공통 사용법",[60,99,100,103,106],{},[78,101,102],{},"RealSubject",[78,104,105],{},"핵심 기능을 실제로 처리하는 객체",[78,107,108],{},"실제 Service",[60,110,111,114,117],{},[78,112,113],{},"Proxy",[78,115,116],{},"실제 객체 대신 요청을 먼저 받는 객체",[78,118,119],{},"대리인",[60,121,122,125,128],{},[78,123,124],{},"Target",[78,126,127],{},"Proxy가 감싸고 있는 실제 대상 객체",[78,129,130],{},"Proxy 내부의 Service",[60,132,133,136,139],{},[78,134,135],{},"Wrapping",[78,137,138],{},"한 객체가 다른 객체를 내부에 보관하는 구조",[78,140,141],{},"Proxy가 Target을 감쌈",[60,143,144,147,150],{},[78,145,146],{},"Delegation",[78,148,149],{},"받은 작업을 다른 객체에 넘기는 것",[78,151,152],{},"Proxy가 실제 작업을 Target에 전달",[60,154,155,158,161],{},[78,156,157],{},"전처리",[78,159,160],{},"실제 메서드 실행 전에 수행하는 작업",[78,162,163],{},"트랜잭션 시작, 권한 확인",[60,165,166,169,172],{},[78,167,168],{},"후처리",[78,170,171],{},"실제 메서드 실행 후 수행하는 작업",[78,173,174],{},"커밋, 로그 기록",[60,176,177,180,183],{},[78,178,179],{},"동적 Proxy",[78,181,182],{},"실행 시점에 자동으로 생성되는 Proxy",[78,184,185],{},"개발자가 Proxy 클래스를 직접 작성하지 않음",[60,187,188,191,194],{},[78,189,190],{},"Bean",[78,192,193],{},"Spring Container가 생성하고 관리하는 객체",[78,195,196],{},"Spring이 보관하고 주입하는 객체",[46,198],{},[49,200,202],{"id":201},"_2-proxy-패턴의-기본-구조","2. Proxy 패턴의 기본 구조",[17,204,205],{},"Proxy와 실제 객체는 같은 인터페이스를 사용한다.",[21,207,211],{"className":208,"code":209,"language":210,"meta":27,"style":27},"language-java shiki shiki-themes github-light github-dark","public interface UserService {\n    String findUserName(Long id);\n}\n","java",[29,212,213,221,227],{"__ignoreMap":27},[214,215,218],"span",{"class":216,"line":217},"line",1,[214,219,220],{},"public interface UserService {\n",[214,222,224],{"class":216,"line":223},2,[214,225,226],{},"    String findUserName(Long id);\n",[214,228,230],{"class":216,"line":229},3,[214,231,232],{},"}\n",[234,235,237],"h4",{"id":236},"실제-객체","실제 객체",[21,239,241],{"className":208,"code":240,"language":210,"meta":27,"style":27},"public class UserServiceImpl implements UserService {\n\n    @Override\n    public String findUserName(Long id) {\n        System.out.println(\"실제 사용자 조회\");\n        return \"민형\";\n    }\n}\n",[29,242,243,248,254,259,265,271,277,283],{"__ignoreMap":27},[214,244,245],{"class":216,"line":217},[214,246,247],{},"public class UserServiceImpl implements UserService {\n",[214,249,250],{"class":216,"line":223},[214,251,253],{"emptyLinePlaceholder":252},true,"\n",[214,255,256],{"class":216,"line":229},[214,257,258],{},"    @Override\n",[214,260,262],{"class":216,"line":261},4,[214,263,264],{},"    public String findUserName(Long id) {\n",[214,266,268],{"class":216,"line":267},5,[214,269,270],{},"        System.out.println(\"실제 사용자 조회\");\n",[214,272,274],{"class":216,"line":273},6,[214,275,276],{},"        return \"민형\";\n",[214,278,280],{"class":216,"line":279},7,[214,281,282],{},"    }\n",[214,284,286],{"class":216,"line":285},8,[214,287,232],{},[234,289,291],{"id":290},"proxy-객체","Proxy 객체",[21,293,295],{"className":208,"code":294,"language":210,"meta":27,"style":27},"public class UserServiceProxy implements UserService {\n\n    private final UserService target;\n\n    public UserServiceProxy(UserService target) {\n        this.target = target;\n    }\n\n    @Override\n    public String findUserName(Long id) {\n        System.out.println(\"조회 시작\");\n\n        String result = target.findUserName(id);\n\n        System.out.println(\"조회 종료\");\n        return result;\n    }\n}\n",[29,296,297,302,306,311,315,320,325,329,333,338,343,349,354,360,365,371,377,382],{"__ignoreMap":27},[214,298,299],{"class":216,"line":217},[214,300,301],{},"public class UserServiceProxy implements UserService {\n",[214,303,304],{"class":216,"line":223},[214,305,253],{"emptyLinePlaceholder":252},[214,307,308],{"class":216,"line":229},[214,309,310],{},"    private final UserService target;\n",[214,312,313],{"class":216,"line":261},[214,314,253],{"emptyLinePlaceholder":252},[214,316,317],{"class":216,"line":267},[214,318,319],{},"    public UserServiceProxy(UserService target) {\n",[214,321,322],{"class":216,"line":273},[214,323,324],{},"        this.target = target;\n",[214,326,327],{"class":216,"line":279},[214,328,282],{},[214,330,331],{"class":216,"line":285},[214,332,253],{"emptyLinePlaceholder":252},[214,334,336],{"class":216,"line":335},9,[214,337,258],{},[214,339,341],{"class":216,"line":340},10,[214,342,264],{},[214,344,346],{"class":216,"line":345},11,[214,347,348],{},"        System.out.println(\"조회 시작\");\n",[214,350,352],{"class":216,"line":351},12,[214,353,253],{"emptyLinePlaceholder":252},[214,355,357],{"class":216,"line":356},13,[214,358,359],{},"        String result = target.findUserName(id);\n",[214,361,363],{"class":216,"line":362},14,[214,364,253],{"emptyLinePlaceholder":252},[214,366,368],{"class":216,"line":367},15,[214,369,370],{},"        System.out.println(\"조회 종료\");\n",[214,372,374],{"class":216,"line":373},16,[214,375,376],{},"        return result;\n",[214,378,380],{"class":216,"line":379},17,[214,381,282],{},[214,383,385],{"class":216,"line":384},18,[214,386,232],{},[234,388,80],{"id":389},"client",[21,391,393],{"className":208,"code":392,"language":210,"meta":27,"style":27},"public class ProxyExample {\n\n    public static void main(String[] args) {\n        UserService target = new UserServiceImpl();\n        UserService proxy = new UserServiceProxy(target);\n\n        String name = proxy.findUserName(1L);\n        System.out.println(name);\n    }\n}\n",[29,394,395,400,404,409,414,419,423,428,433,437],{"__ignoreMap":27},[214,396,397],{"class":216,"line":217},[214,398,399],{},"public class ProxyExample {\n",[214,401,402],{"class":216,"line":223},[214,403,253],{"emptyLinePlaceholder":252},[214,405,406],{"class":216,"line":229},[214,407,408],{},"    public static void main(String[] args) {\n",[214,410,411],{"class":216,"line":261},[214,412,413],{},"        UserService target = new UserServiceImpl();\n",[214,415,416],{"class":216,"line":267},[214,417,418],{},"        UserService proxy = new UserServiceProxy(target);\n",[214,420,421],{"class":216,"line":273},[214,422,253],{"emptyLinePlaceholder":252},[214,424,425],{"class":216,"line":279},[214,426,427],{},"        String name = proxy.findUserName(1L);\n",[214,429,430],{"class":216,"line":285},[214,431,432],{},"        System.out.println(name);\n",[214,434,435],{"class":216,"line":335},[214,436,282],{},[214,438,439],{"class":216,"line":340},[214,440,232],{},[21,442,445],{"className":443,"code":444,"language":26,"meta":27},[24],"Client가 proxy.findUserName() 호출\n          ↓\nProxy가 \"조회 시작\" 출력\n          ↓\ntarget.findUserName()에 실제 작업 위임\n          ↓\nProxy가 \"조회 종료\" 출력\n          ↓\n결과 반환\n",[29,446,444],{"__ignoreMap":27},[17,448,449],{},"Proxy 안의 다음 코드가 위임이다.",[21,451,453],{"className":208,"code":452,"language":210,"meta":27,"style":27},"String result = target.findUserName(id);\n",[29,454,455],{"__ignoreMap":27},[214,456,457],{"class":216,"line":217},[214,458,452],{},[46,460],{},[49,462,464],{"id":463},"_3-proxy를-사용하는-이유","3. Proxy를 사용하는 이유",[17,466,467],{},"회원 조회 Service의 핵심 목적은 회원을 조회하는 것이다.",[21,469,471],{"className":208,"code":470,"language":210,"meta":27,"style":27},"public User findUser(Long id) {\n    return userRepository.findById(id).orElseThrow();\n}\n",[29,472,473,478,483],{"__ignoreMap":27},[214,474,475],{"class":216,"line":217},[214,476,477],{},"public User findUser(Long id) {\n",[214,479,480],{"class":216,"line":223},[214,481,482],{},"    return userRepository.findById(id).orElseThrow();\n",[214,484,485],{"class":216,"line":229},[214,486,232],{},[17,488,489],{},"여기에 로그, 실행 시간, 권한 검사, 트랜잭션 코드를 모두 넣으면 핵심 로직이 흐려진다.",[21,491,493],{"className":208,"code":492,"language":210,"meta":27,"style":27},"public User findUser(Long id) {\n    long startTime = System.currentTimeMillis();\n    checkPermission();\n    beginTransaction();\n\n    User user = userRepository.findById(id).orElseThrow();\n\n    commitTransaction();\n    logExecutionTime(startTime);\n    return user;\n}\n",[29,494,495,499,504,509,514,518,523,527,532,537,542],{"__ignoreMap":27},[214,496,497],{"class":216,"line":217},[214,498,477],{},[214,500,501],{"class":216,"line":223},[214,502,503],{},"    long startTime = System.currentTimeMillis();\n",[214,505,506],{"class":216,"line":229},[214,507,508],{},"    checkPermission();\n",[214,510,511],{"class":216,"line":261},[214,512,513],{},"    beginTransaction();\n",[214,515,516],{"class":216,"line":267},[214,517,253],{"emptyLinePlaceholder":252},[214,519,520],{"class":216,"line":273},[214,521,522],{},"    User user = userRepository.findById(id).orElseThrow();\n",[214,524,525],{"class":216,"line":279},[214,526,253],{"emptyLinePlaceholder":252},[214,528,529],{"class":216,"line":285},[214,530,531],{},"    commitTransaction();\n",[214,533,534],{"class":216,"line":335},[214,535,536],{},"    logExecutionTime(startTime);\n",[214,538,539],{"class":216,"line":340},[214,540,541],{},"    return user;\n",[214,543,544],{"class":216,"line":345},[214,545,232],{},[17,547,548],{},"Proxy가 공통 기능을 맡으면 실제 Service는 업무 로직에 집중할 수 있다.",[54,550,551,559],{},[57,552,553],{},[60,554,555,557],{},[63,556,108],{},[63,558,113],{},[73,560,561],{},[60,562,563,566],{},[78,564,565],{},"회원 조회, 주문 처리, 재고 감소",[78,567,568],{},"로그, 권한, 트랜잭션, 캐시",[17,570,571],{},"Proxy가 처리할 수 있는 기능은 다음과 같다.",[54,573,574,584],{},[57,575,576],{},[60,577,578,581],{},[63,579,580],{},"기능",[63,582,583],{},"Proxy가 하는 일",[73,585,586,594,602,610,618,626],{},[60,587,588,591],{},[78,589,590],{},"트랜잭션",[78,592,593],{},"메서드 전후에 시작, 커밋, 롤백 처리",[60,595,596,599],{},[78,597,598],{},"로그",[78,600,601],{},"메서드 실행 정보 기록",[60,603,604,607],{},[78,605,606],{},"권한 검사",[78,608,609],{},"실제 메서드 실행 전에 접근 권한 확인",[60,611,612,615],{},[78,613,614],{},"캐시",[78,616,617],{},"저장된 결과가 있으면 실제 메서드 호출 생략",[60,619,620,623],{},[78,621,622],{},"실행 시간 측정",[78,624,625],{},"시작과 종료 시각 비교",[60,627,628,631],{},[78,629,630],{},"비동기 실행",[78,632,633],{},"작업을 Executor에 제출",[46,635],{},[49,637,639],{"id":638},"_4-bean과-proxy의-관계","4. Bean과 Proxy의 관계",[17,641,642],{},"Bean은 Spring Container가 관리하는 객체다.",[21,644,646],{"className":208,"code":645,"language":210,"meta":27,"style":27},"@Service\npublic class TransferService {\n}\n",[29,647,648,653,658],{"__ignoreMap":27},[214,649,650],{"class":216,"line":217},[214,651,652],{},"@Service\n",[214,654,655],{"class":216,"line":223},[214,656,657],{},"public class TransferService {\n",[214,659,660],{"class":216,"line":229},[214,661,232],{},[17,663,664,667],{},[29,665,666],{},"@Service","가 붙었다고 항상 Proxy가 생성되는 것은 아니다. Spring이 적용해야 할 부가 기능을 발견했을 때 Proxy로 감쌀 수 있다.",[21,669,671],{"className":208,"code":670,"language":210,"meta":27,"style":27},"@Service\npublic class TransferService {\n\n    @Transactional\n    public void transfer() {\n        \u002F\u002F 출금과 입금\n    }\n}\n",[29,672,673,677,681,685,690,695,700,704],{"__ignoreMap":27},[214,674,675],{"class":216,"line":217},[214,676,652],{},[214,678,679],{"class":216,"line":223},[214,680,657],{},[214,682,683],{"class":216,"line":229},[214,684,253],{"emptyLinePlaceholder":252},[214,686,687],{"class":216,"line":261},[214,688,689],{},"    @Transactional\n",[214,691,692],{"class":216,"line":267},[214,693,694],{},"    public void transfer() {\n",[214,696,697],{"class":216,"line":273},[214,698,699],{},"        \u002F\u002F 출금과 입금\n",[214,701,702],{"class":216,"line":279},[214,703,282],{},[214,705,706],{"class":216,"line":285},[214,707,232],{},[21,709,712],{"className":710,"code":711,"language":26,"meta":27},[24],"Component Scan\n   ↓\nTransferService 객체 생성\n   ↓\n@Transactional 적용 대상 확인\n   ↓\nProxy가 Target을 감쌈\n   ↓\n다른 Bean에는 Proxy가 주입됨\n",[29,713,711],{"__ignoreMap":27},[17,715,716,717,720],{},"Controller가 ",[29,718,719],{},"TransferService","를 호출한다고 생각하지만, 실제 참조가 Proxy일 수 있다.",[21,722,725],{"className":723,"code":724,"language":26,"meta":27},[24],"Controller\n   ↓\nTransferService Proxy\n   ↓ 트랜잭션 시작\n실제 TransferService\n   ↓\nProxy가 커밋 또는 롤백\n",[29,726,724],{"__ignoreMap":27},[46,728],{},[49,730,732],{"id":731},"_5-spring의-동적-proxy","5. Spring의 동적 Proxy",[17,734,735],{},"Spring은 실행 시점에 Proxy 객체를 자동으로 만들 수 있다.",[54,737,738,751],{},[57,739,740],{},[60,741,742,745,748],{},[63,743,744],{},"구분",[63,746,747],{},"JDK Dynamic Proxy",[63,749,750],{},"CGLIB Proxy",[73,752,753,764,775,786],{},[60,754,755,758,761],{},[78,756,757],{},"기준",[78,759,760],{},"인터페이스",[78,762,763],{},"클래스",[60,765,766,769,772],{},[78,767,768],{},"인터페이스 필요 여부",[78,770,771],{},"필요",[78,773,774],{},"없어도 됨",[60,776,777,780,783],{},[78,778,779],{},"생성 방식",[78,781,782],{},"Reflection을 이용한 Proxy 생성",[78,784,785],{},"대상 클래스를 상속한 하위 클래스 생성",[60,787,788,791,794],{},[78,789,790],{},"주요 제약",[78,792,793],{},"인터페이스에 선언된 호출 중심",[78,795,796,799,800,802],{},[29,797,798],{},"final"," 클래스와 ",[29,801,798],{}," 메서드 재정의 불가",[17,804,805],{},"Spring Boot에서는 클래스 기반 Proxy가 널리 사용되지만, 실제 방식은 설정과 대상 구조에 따라 달라질 수 있다. 코드를 작성할 때 특정 Proxy 방식에 과도하게 의존하지 않는 것이 좋다.",[46,807],{},[49,809,811,812,814],{"id":810},"_6-transactional-호출-흐름","6. ",[29,813,36],{}," 호출 흐름",[21,816,818],{"className":208,"code":817,"language":210,"meta":27,"style":27},"@Transactional\npublic void updateUser(Long id, String name) {\n    User user = userRepository.findById(id).orElseThrow();\n    user.changeName(name);\n}\n",[29,819,820,825,830,834,839],{"__ignoreMap":27},[214,821,822],{"class":216,"line":217},[214,823,824],{},"@Transactional\n",[214,826,827],{"class":216,"line":223},[214,828,829],{},"public void updateUser(Long id, String name) {\n",[214,831,832],{"class":216,"line":229},[214,833,522],{},[214,835,836],{"class":216,"line":261},[214,837,838],{},"    user.changeName(name);\n",[214,840,841],{"class":216,"line":267},[214,842,232],{},[17,844,845],{},"개발자가 작성한 메서드에는 회원 수정 로직만 있다. 트랜잭션 처리는 Proxy가 앞뒤에서 수행한다.",[17,847,848],{},[849,850,851],"strong",{},"Transactional Proxy 호출 흐름",[21,853,857],{"className":854,"code":855,"language":856,"meta":27,"style":27},"language-mermaid shiki shiki-themes github-light github-dark","flowchart TD\n    C[Controller] --> P[Transactional Proxy]\n    P --> B[트랜잭션 시작]\n    B --> T[실제 updateUser 실행]\n    T --> Q{정상 종료?}\n    Q -->|예| COMMIT[Commit]\n    Q -->|아니오| ROLLBACK[Rollback]\n    COMMIT --> C\n    ROLLBACK --> C\n","mermaid",[29,858,859,864,869,874,879,884,889,894,899],{"__ignoreMap":27},[214,860,861],{"class":216,"line":217},[214,862,863],{},"flowchart TD\n",[214,865,866],{"class":216,"line":223},[214,867,868],{},"    C[Controller] --> P[Transactional Proxy]\n",[214,870,871],{"class":216,"line":229},[214,872,873],{},"    P --> B[트랜잭션 시작]\n",[214,875,876],{"class":216,"line":261},[214,877,878],{},"    B --> T[실제 updateUser 실행]\n",[214,880,881],{"class":216,"line":267},[214,882,883],{},"    T --> Q{정상 종료?}\n",[214,885,886],{"class":216,"line":273},[214,887,888],{},"    Q -->|예| COMMIT[Commit]\n",[214,890,891],{"class":216,"line":279},[214,892,893],{},"    Q -->|아니오| ROLLBACK[Rollback]\n",[214,895,896],{"class":216,"line":285},[214,897,898],{},"    COMMIT --> C\n",[214,900,901],{"class":216,"line":335},[214,902,903],{},"    ROLLBACK --> C\n",[46,905],{},[49,907,909],{"id":908},"_7-자기-호출-문제","7. 자기 호출 문제",[17,911,912,913,916],{},"Spring Proxy는 ",[849,914,915],{},"Proxy를 거쳐 들어오는 호출","을 가로챈다.",[17,918,919],{},"같은 객체 안에서 자신의 메서드를 직접 호출하면 Proxy를 거치지 않는다.",[21,921,923],{"className":208,"code":922,"language":210,"meta":27,"style":27},"@Service\npublic class PaymentService {\n\n    public void order() {\n        pay(); \u002F\u002F 같은 객체의 내부 호출\n    }\n\n    @Transactional\n    public void pay() {\n        \u002F\u002F 결제 처리\n    }\n}\n",[29,924,925,929,934,938,943,948,952,956,960,965,970,974],{"__ignoreMap":27},[214,926,927],{"class":216,"line":217},[214,928,652],{},[214,930,931],{"class":216,"line":223},[214,932,933],{},"public class PaymentService {\n",[214,935,936],{"class":216,"line":229},[214,937,253],{"emptyLinePlaceholder":252},[214,939,940],{"class":216,"line":261},[214,941,942],{},"    public void order() {\n",[214,944,945],{"class":216,"line":267},[214,946,947],{},"        pay(); \u002F\u002F 같은 객체의 내부 호출\n",[214,949,950],{"class":216,"line":273},[214,951,282],{},[214,953,954],{"class":216,"line":279},[214,955,253],{"emptyLinePlaceholder":252},[214,957,958],{"class":216,"line":285},[214,959,689],{},[214,961,962],{"class":216,"line":335},[214,963,964],{},"    public void pay() {\n",[214,966,967],{"class":216,"line":340},[214,968,969],{},"        \u002F\u002F 결제 처리\n",[214,971,972],{"class":216,"line":345},[214,973,282],{},[214,975,976],{"class":216,"line":351},[214,977,232],{},[21,979,982],{"className":980,"code":981,"language":26,"meta":27},[24],"외부 호출\n→ Proxy\n→ @Transactional 적용 가능\n\n같은 객체의 pay() 내부 호출\n→ Proxy를 다시 거치지 않음\n→ 새 트랜잭션 설정이 적용되지 않을 수 있음\n",[29,983,981],{"__ignoreMap":27},[17,985,986],{},"이것을 Self-invocation, 자기 호출 문제라고 한다.",[17,988,989],{},"트랜잭션 경계가 분명히 달라야 한다면 별도 Bean으로 분리하는 방법을 사용할 수 있다.",[21,991,993],{"className":208,"code":992,"language":210,"meta":27,"style":27},"@Service\npublic class PaymentProcessor {\n\n    @Transactional\n    public void pay() {\n        \u002F\u002F 결제 처리\n    }\n}\n",[29,994,995,999,1004,1008,1012,1016,1020,1024],{"__ignoreMap":27},[214,996,997],{"class":216,"line":217},[214,998,652],{},[214,1000,1001],{"class":216,"line":223},[214,1002,1003],{},"public class PaymentProcessor {\n",[214,1005,1006],{"class":216,"line":229},[214,1007,253],{"emptyLinePlaceholder":252},[214,1009,1010],{"class":216,"line":261},[214,1011,689],{},[214,1013,1014],{"class":216,"line":267},[214,1015,964],{},[214,1017,1018],{"class":216,"line":273},[214,1019,969],{},[214,1021,1022],{"class":216,"line":279},[214,1023,282],{},[214,1025,1026],{"class":216,"line":285},[214,1027,232],{},[21,1029,1031],{"className":208,"code":1030,"language":210,"meta":27,"style":27},"@Service\npublic class OrderService {\n\n    private final PaymentProcessor paymentProcessor;\n\n    public OrderService(PaymentProcessor paymentProcessor) {\n        this.paymentProcessor = paymentProcessor;\n    }\n\n    public void order() {\n        paymentProcessor.pay(); \u002F\u002F 다른 Bean의 Proxy를 통해 호출\n    }\n}\n",[29,1032,1033,1037,1042,1046,1051,1055,1060,1065,1069,1073,1077,1082,1086],{"__ignoreMap":27},[214,1034,1035],{"class":216,"line":217},[214,1036,652],{},[214,1038,1039],{"class":216,"line":223},[214,1040,1041],{},"public class OrderService {\n",[214,1043,1044],{"class":216,"line":229},[214,1045,253],{"emptyLinePlaceholder":252},[214,1047,1048],{"class":216,"line":261},[214,1049,1050],{},"    private final PaymentProcessor paymentProcessor;\n",[214,1052,1053],{"class":216,"line":267},[214,1054,253],{"emptyLinePlaceholder":252},[214,1056,1057],{"class":216,"line":273},[214,1058,1059],{},"    public OrderService(PaymentProcessor paymentProcessor) {\n",[214,1061,1062],{"class":216,"line":279},[214,1063,1064],{},"        this.paymentProcessor = paymentProcessor;\n",[214,1066,1067],{"class":216,"line":285},[214,1068,282],{},[214,1070,1071],{"class":216,"line":335},[214,1072,253],{"emptyLinePlaceholder":252},[214,1074,1075],{"class":216,"line":340},[214,1076,942],{},[214,1078,1079],{"class":216,"line":345},[214,1080,1081],{},"        paymentProcessor.pay(); \u002F\u002F 다른 Bean의 Proxy를 통해 호출\n",[214,1083,1084],{"class":216,"line":351},[214,1085,282],{},[214,1087,1088],{"class":216,"line":356},[214,1089,232],{},[46,1091],{},[49,1093,1095],{"id":1094},"_8-proxy와-aop의-차이","8. Proxy와 AOP의 차이",[54,1097,1098,1107],{},[57,1099,1100],{},[60,1101,1102,1104],{},[63,1103,744],{},[63,1105,1106],{},"역할",[73,1108,1109,1117,1124],{},[60,1110,1111,1114],{},[78,1112,1113],{},"AOP",[78,1115,1116],{},"어떤 공통 기능을 어디에 적용할지 정의하는 설계 방식",[60,1118,1119,1121],{},[78,1120,113],{},[78,1122,1123],{},"실제 메서드 호출을 가로채 공통 기능을 실행하는 객체",[60,1125,1126,1129],{},[78,1127,1128],{},"Spring",[78,1130,1131],{},"AOP 설정을 읽고 Proxy를 자동 생성하고 관리",[14,1133,1134],{},[17,1135,1136],{},"AOP가 곧 Proxy인 것은 아니다. Spring AOP가 공통 기능을 적용하는 핵심 구현 방식으로 Proxy를 사용하는 것이다.",[46,1138],{},[49,1140,1142],{"id":1141},"핵심-정리","핵심 정리",[54,1144,1145,1155],{},[57,1146,1147],{},[60,1148,1149,1152],{},[63,1150,1151],{},"개념",[63,1153,1154],{},"한 줄 정리",[73,1156,1157,1164,1171,1178,1185,1191],{},[60,1158,1159,1161],{},[78,1160,113],{},[78,1162,1163],{},"실제 객체 대신 요청을 먼저 받는 대리 객체",[60,1165,1166,1168],{},[78,1167,124],{},[78,1169,1170],{},"Proxy가 감싸고 있는 실제 객체",[60,1172,1173,1175],{},[78,1174,135],{},[78,1176,1177],{},"Proxy가 Target을 내부에 보관하는 구조",[60,1179,1180,1182],{},[78,1181,146],{},[78,1183,1184],{},"Proxy가 실제 작업을 Target에 전달하는 것",[60,1186,1187,1189],{},[78,1188,179],{},[78,1190,182],{},[60,1192,1193,1196],{},[78,1194,1195],{},"자기 호출",[78,1197,1198],{},"같은 객체 내부 호출이 Proxy를 거치지 않는 문제",[14,1200,1201],{},[17,1202,1203],{},"Proxy의 핵심은 실제 업무 코드를 크게 바꾸지 않고 메서드 호출 앞뒤에 공통 기능을 추가하는 것이다.",[49,1205,1207],{"id":1206},"참고-자료","참고 자료",[1209,1210,1211],"ul",{},[1212,1213,1214],"li",{},[1215,1216,1220],"a",{"href":1217,"rel":1218},"https:\u002F\u002Fdocs.spring.io\u002Fspring-framework\u002Freference\u002Fcore\u002Faop\u002Fproxying.html",[1219],"nofollow","Spring Framework AOP Proxying Mechanisms",[1222,1223,1224],"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":27,"searchDepth":223,"depth":223,"links":1226},[1227],{"id":12,"depth":223,"text":5,"children":1228},[1229,1230,1231,1232,1233,1234,1236,1237,1238,1239],{"id":51,"depth":229,"text":52},{"id":201,"depth":229,"text":202},{"id":463,"depth":229,"text":464},{"id":638,"depth":229,"text":639},{"id":731,"depth":229,"text":732},{"id":810,"depth":229,"text":1235},"6. @Transactional 호출 흐름",{"id":908,"depth":229,"text":909},{"id":1094,"depth":229,"text":1095},{"id":1141,"depth":229,"text":1142},{"id":1206,"depth":229,"text":1207},[210,1241],"spring","2026-08-19 09:00:00 +0900","대리 객체인 Proxy가 실제 객체의 호출을 감싸는 원리와 Spring의 JDK Dynamic Proxy, CGLIB, 자기 호출 문제를 정리한다.","md",null,"\u002Fjava\u002Fspring\u002F2026\u002F08\u002F19\u002Fspring-proxy\u002F",{"layout":1248},"post","7","\u002Fposts\u002Fbackend\u002Fspring\u002Fspring\u002F2026-08-19-spring-proxy",{"title":5,"description":1243},"Spring 기초","posts\u002FBackend\u002FSPRING\u002FSpring\u002F2026-08-19-spring-proxy",[1255,1128,113,1256,1257,1258,190],"Java","Dynamic Proxy","JDK Proxy","CGLIB","HO9g-ySmLqbZhgwdx3URy-OmyuyWmXJXgZtkaLuTDW8",1788744786319]