[Spring] AOP, AOP Proxy

[verify$y]Β·2025λ…„ 5μ›” 27일

Spring

λͺ©λ‘ 보기
9/16

πŸ’‘ Spring 핡심 κ°œλ… 인터뷰 Q&A


μš”μ•½

AOPλ₯Ό μ μš©ν•  λŒ€μƒ 객체(Bean)κ°€ μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜κ³  있으면, μŠ€ν”„λ§μ€ JDK 동적 ν”„λ‘μ‹œλ₯Ό μ‚¬μš©ν•œλ‹€.


ν”„λ‘μ‹œλž€

  • ν”„λ‘μ‹œ(proxy)λŠ” β€œμ§„μ§œ 객체(μ›λž˜ 둜직)” μ•žμ— μ„œμ„œ λŒ€μ‹  일 처리λ₯Ό ν•΄μ£ΌλŠ” λŒ€λ¦¬ 객체
  • λΉ„μœ ν•˜λ©΄ λΉ„μ„œ(ν”„λ‘μ‹œ), λŒ€ν‘œ(μ‹€μ œ 객체)이닀.
  • μŠ€ν”„λ§μ€ AOPλ₯Ό ν•  λ•Œ κΈ°μ‘΄ λ‘œμ§μ„ κ±΄λ“œλ¦¬μ§€ μ•Šκ³ , λ©”μ„œλ“œ 호좜 전후에 λ‘œμ§μ„ μΆ”κ°€ν•˜κ³  μ‹Άμ–΄ν•œλ‹€.
  • κ·Έκ±Έ κ°€λŠ₯ν•˜κ²Œ ν•΄μ£ΌλŠ” 게 ν”„λ‘μ‹œκ°μ²΄μ΄λ‹€.

ν”„λ‘μ‹œ λ™μž‘

[ν΄λΌμ΄μ–ΈνŠΈ] -> [ν”„λ‘μ‹œ] -> [μ‹€μ œκ°μ²΄]



ν”„λ‘μ‹œλ₯Ό μ™œ λ§Œλ“€κΉŒ

  • μŠ€ν”„λ§ AOPλ™μž‘
μ‹€μ œκ°μ²΄λ₯Ό ν”„λ‘μ‹œκ°μ²΄λ‘œ κ°μ‹Έμ„œ 
λ©”μ„œλ“œ μ‹€ν–‰ μ „ν›„ 둜그 
νŠΈλžœμž­μ…˜μ„ μΆ”κ°€ν•˜κΈ° μœ„ν•΄ μŠ€ν”„λ§μ€ ν”„λ‘μ‹œ 객체λ₯Ό μƒμ„±ν•œλ‹€.
  • λ‘œκ·ΈμΈλ©”μ„œλ“œ ν”„λ‘μ‹œ 적용된 μ˜ˆμ‹œ
login() λ©”μ„œλ“œ μ‹€ν–‰ β†’ 둜그 찍고 β†’ μ§„μ§œ login μ‹€ν–‰ β†’ λ‹€μ‹œ 둜그 찍기



ν”„λ‘μ‹œ νŒ¨ν„΄


μŠ€ν”„λ§μ—μ„œ ν”„λ‘μ‹œ 생성방법

  • JDK둜 μƒμ„±ν•˜λŠ” 방법, JDK Dynamic Proxy객체생성
  • CGLIB둜 μƒμ„±ν•˜λŠ” 방법, CGLIB Proxy객체 생성
  • AOPλ₯Ό μ μš©ν•  λŒ€μƒ 클래슀(빈 μΈμŠ€ν„΄μŠ€)κ°€ μΈν„°νŽ˜μ΄μŠ€λ₯Ό implements ν•˜κ³  μžˆλŠ”μ§€κ°€ 선택 기쀀이닀.


JDK Dynamic Proxy생성

Proxy.newProxyInstance() 



JDK Dynamic Proxy객체 생성 방식

1. μœ μ €μ„œλΉ„μŠ€λ‹¨μ„ μΈν„°νŽ˜μ΄μŠ€λ‘œ μ„ μ–Έν•˜μ˜€λ‹€.

public interface MemberService {
    void login(String name);
}

2. MemberService μΈν„°νŽ˜μ΄μŠ€λ₯Ό μƒμ†ν•œ 객체 (MemberServiceImpl)

public class MemberServiceImpl implements MemberService {
    public void login(String name) {
        System.out.println(name + " 둜그인");
    }
}

3. ν”„λ‘μ‹œ 생성

MemberService target = new MemberServiceImpl();

MemberService proxy = (MemberService) Proxy.newProxyInstance(
        MemberService.class.getClassLoader(),
        new Class[]{MemberService.class}, // β˜… μΈν„°νŽ˜μ΄μŠ€κ°€ λ°˜λ“œμ‹œ ν•„μš”
        (proxyObj, method, args) -> {
            System.out.println(">> 호좜 μ „");
            Object result = method.invoke(target, args);
            System.out.println(">> 호좜 ν›„");
            return result;
        } );
  • μœ„ μ½”λ“œλ₯Ό 보면 proxyλŠ” μΈν„°νŽ˜μ΄μŠ€νƒ€μž…μœΌλ‘œ μ„ μ–Έλ˜μ–΄ μΈν„°νŽ˜μ΄μŠ€ 역할을 ν•œλ‹€.
  • μ΄λ•Œ ν”„λ‘μ‹œ κ°μ²΄λŠ” MemberServiceμΈν„°νŽ˜μ΄μŠ€λ§Œ ν‰λ‚΄λƒ…λ‹ˆλ‹€.
  • target 객체λ₯Ό 직접 μƒμ†ν•˜κ±°λ‚˜ 클래슀 ꡬ쑰λ₯Ό λ³΅μ œν•˜λŠ” 건 μ „ν˜€ λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€.


JDK Proxy ꡬ쑰

[ν΄λΌμ΄μ–ΈνŠΈ] -> [JDK Proxy] -> [MemberServiceImpl(μ‹€μ œκ°μ²΄)]



JDK Proxy μ—­ν• 

  • JDK ν”„λ‘μ‹œκ°€ ν•˜λŠ” 일은 μΈν„°νŽ˜μ΄μŠ€λ₯Ό λŒ€μ‹  κ΅¬ν˜„ν•΄μ„œ, μ§„μ§œ κ°μ²΄μ—κ²Œ ν˜ΈμΆœμ„ 전달(delegation)ν•˜λŠ” 것!



ν”„λ‘μ‹œ 객체 생성 μ‹œμ 

  • Spring AOPλŠ” λŸ°νƒ€μž„μ— μ‹€μ œ 클래슀의 ν”„λ‘μ‹œ 객체λ₯Ό μžλ™μœΌλ‘œ μƒμ„±ν•©λ‹ˆλ‹€. 이 κ°μ²΄λŠ” λ‘˜ 쀑 ν•˜λ‚˜λ‘œ λ§Œλ“€μ–΄μ§„λ‹€. (JDK Dynamic Proxy, CGLIB Proxy)
  • 생성 μ‹œ 선택 κΈ°μ€€ : μΈν„°νŽ˜μ΄μŠ€ κ΅¬ν˜„ 유무
  • AOPλ₯Ό μ μš©ν•  λŒ€μƒ Bean이 μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜κ³  μžˆλ‹€λ©΄ β†’ μžλ°” λ‚΄μž₯ 객체인 JDK 동적 ν”„λ‘μ‹œλ₯Ό μ‚¬μš©ν•œλ‹€.



μ™œ JDK ν”„λ‘μ‹œλŠ” "μΈν„°νŽ˜μ΄μŠ€"κ°€ λ°˜λ“œμ‹œ ν•„μš”ν•œμ§€

  • JDK 동적 ν”„λ‘μ‹œλŠ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•œ 객체만 ν”„λ‘μ‹œλ‘œ λ§Œλ“€ 수 μžˆλ‹€.
  • 클래슀 μžμ²΄μ— λŒ€ν•œ ν”„λ‘μ‹œλ₯Ό λ§Œλ“€λ €λ©΄ CGLIB이 ν•„μš”ν•©λ‹ˆλ‹€.



CGLIB Proxy객체 생성 방식

μ‚¬μš©λͺ©μ 

  • λ§Œμ•½ μΈν„°νŽ˜μ΄μŠ€κ°€ μ—†λ‹€λ©΄? 이런 ν΄λž˜μŠ€λŠ” JDK Proxyλ₯Ό λ§Œλ“€ 수 μ—†μŠ΅λ‹ˆλ‹€.
  • μ™œλƒν•˜λ©΄ JDK ProxyλŠ” λ°˜λ“œμ‹œ μΈν„°νŽ˜μ΄μŠ€λ₯Ό 기반으둜 μƒμ„±λ˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.
public class MemberService {
    public void login(String name) { ... }
}
  • μ—¬κΈ°μ„œ jdkλ₯Ό μ“°λ©΄ μ—λŸ¬κ°€ λ‚©λ‹ˆλ‹€.
java.lang.IllegalArgumentException: com.example.MemberService is not an interface

  • CGLIB은 μΈν„°νŽ˜μ΄μŠ€κ°€ 없어도 되게 λ§Œλ“€κΈ° μœ„ν•΄ 클래슀λ₯Ό 직접 μƒμ†ν•΄μ„œ μžμ‹ 클래슀λ₯Ό λ™μ μœΌλ‘œ μƒμ„±ν•©λ‹ˆλ‹€.
// MemberService β†’ MemberService$$CGLIB$$Proxy λΌλŠ” 클래슀λ₯Ό λ§Œλ“€μ–΄μ„œ login()을 μ˜€λ²„λΌμ΄λ“œ



CGLIB Proxyλž€?

  • CGLIB은 클래슀 자체λ₯Ό μƒμ†ν•΄μ„œ μžμ‹ 클래슀λ₯Ό λ§Œλ“€κ³ , λ©”μ„œλ“œλ₯Ό μ˜€λ²„λΌμ΄λ”©ν•΄μ„œ κ°€λ‘œμ±„λŠ” λ°©μ‹μœΌλ‘œ ν”„λ‘μ‹œλ₯Ό λ§Œλ“­λ‹ˆλ‹€.

  • 즉, μΈν„°νŽ˜μ΄μŠ€κ°€ 없어도 ν”„λ‘μ‹œλ₯Ό λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.



CGLIB 예제 : Java + CGLIB 라이브러리)

  • cglib μ˜μ‘΄μ„± μΆ”κ°€
  • μ‹€μ œν΄λž˜μŠ€μ΄κ³  μΈν„°νŽ˜μ΄μŠ€ μ—†μŒ

1.μ‹€μ œν΄λž˜μŠ€

public class MemberService {
    public void login(String name) {
        System.out.println(name + " 둜그인 μ™„λ£Œ");
    }
}

2. CGLIB ν”„λ‘μ‹œ 생성 μ½”λ“œ

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxyTest {
    public static void main(String[] args) {
    
        Enhancer enhancer = new Enhancer();
        
        enhancer.setSuperclass(MemberService.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println(">> 둜그인 μ‹œλ„ (Before)");
                Object result = proxy.invokeSuper(obj, args); // 원본 λ©”μ„œλ“œ 호좜
                System.out.println(">> 둜그인 μ™„λ£Œ (After)");
                return result; }
        }
    );

   MemberService proxy = (MemberService) enhancer.create(); // ν”„λ‘μ‹œ 객체 생성
   proxy.login("iamUser");
   
   }
}

3. CGLIB λ‚΄λΆ€λ™μž‘

class MemberService$$EnhancerByCGLIB extends MemberService {
    public void login(String name) {
        System.out.println(">> Before");
        super.login(name);
        System.out.println(">> After");
    }
}
  • MemberService$$EnhancerByCGLIBλ₯Ό λŸ°νƒ€μž„μ— λ°”μ΄νŠΈμ½”λ“œλ‘œ μƒμ„±ν•©λ‹ˆλ‹€:


4. μ‹€ν–‰κ²°κ³Ό

>> 둜그인 μ‹œλ„ (Before)
iamUser 둜그인 μ™„λ£Œ
>> 둜그인 μ™„λ£Œ (After)



CGLIB이 μ“°μ΄λŠ” μ˜ˆμ‹œ

  • 클래슀만 μ •μ˜λ˜μ–΄ μžˆλŠ” μ„œλΉ„μŠ€
  • @Transactional, @Async, @Cacheable 같은 AOP μ–΄λ…Έν…Œμ΄μ…˜ 적용 μ‹œ μΈν„°νŽ˜μ΄μŠ€κ°€ μ—†μœΌλ©΄ CGLIB μ‚¬μš©
  • Spring Boot 기본값은 μΈν„°νŽ˜μ΄μŠ€ 있으면 JDK Proxy, μ—†μœΌλ©΄ μžλ™μœΌλ‘œ CGLIB




Spring AOPμ—μ„œ CGLIB ν”„λ‘μ‹œκ°€ μ‹€μ œλ‘œ μ–΄λ–»κ²Œ μž‘λ™ν•˜λŠ”μ§€

μ˜ˆμ‹œ

1. ν”„λ‘œμ νŠΈ ꡬ쑰

com.example.demo
β”œβ”€β”€ AppConfig.java
β”œβ”€β”€ MemberService.java
β”œβ”€β”€ LogAspect.java
└── DemoApplication.java

2. μ˜μ‘΄μ„± (Spring Boot + AOP)
3. AOP λŒ€μƒ 클래슀 β†’ μΈν„°νŽ˜μ΄μŠ€ μ—†μŒ

package com.example.demo;

import org.springframework.stereotype.Service;

@Service
public class MemberService {
    public void login(String name) {
        System.out.println(name + " 둜그인 성곡");
    }
}

4. AOP μ„€μ •: Aspect μ •μ˜

package com.example.demo;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LogAspect {

    @Before("execution(* com.example.demo.MemberService.*(..))")
    public void logBefore() {
        System.out.println(">> AOP: 둜그인 전에 둜그 좜λ ₯");
    }
}

5. μ‹€ν–‰

package com.example.demo;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    public CommandLineRunner runner(MemberService memberService) {
        return args -> {
            System.out.println("ν”„λ‘μ‹œ 클래슀: " + memberService.getClass());
            memberService.login("iamUserser");
        };
    }
}

6. μ‹€ν–‰κ²°κ³Ό

ν”„λ‘μ‹œ 클래슀: class com.example.demo.MemberService$$SpringCGLIB$$0
>> AOP: 둜그인 전에 둜그 좜λ ₯
iamUserser 둜그인 성곡




CGLIB ν”„λ‘μ‹œ 생성

Enhancer.create()



CGLIB ν”„λ‘μ‹œ νŠΉμ§•

ν”„λ‘μ‹œ 클래슀 이름$$SpringCGLIB$$이 λΆ™μŒ β†’ CGLIB μ‚¬μš©
둜그 좜λ ₯ μˆœμ„œ@Before Aspectκ°€ login() μ‹€ν–‰ 전에 μž‘λ™ν•¨
μΈν„°νŽ˜μ΄μŠ€ μ—†μŒκ·Έλž˜μ„œ JDK ν”„λ‘μ‹œκ°€ μ•„λ‹Œ CGLIB 방식 μ‚¬μš©




AOP 미적용 λŒ€ν‘œ μΌ€μ΄μŠ€

κ²°λ‘ 

  • AOPλŠ” ν”„λ‘μ‹œ 객체가 μ™ΈλΆ€μ—μ„œ ν˜ΈμΆœλ°›μ„ λ•Œλ§Œ μž‘λ™ν•©λ‹ˆλ‹€.
  • 클래슀 λ‚΄λΆ€μ—μ„œ this.λ©”μ„œλ“œ()둜 ν˜ΈμΆœν•˜λ©΄ ν”„λ‘μ‹œλ₯Ό κ±°μΉ˜μ§€ μ•Šκ³  직접 λ©”μ„œλ“œλ₯Ό μ‹€ν–‰ν•˜κΈ° λ•Œλ¬Έμ— AOPκ°€ μž‘λ™ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.


예제

@Service
public class MemberService {

    public void login() {
        System.out.println("둜그인 처리");
        validate(); // λ‚΄λΆ€ λ©”μ„œλ“œ 호좜
    }

    public void validate() {
        System.out.println("μœ νš¨μ„± 검사");
    }
}
@Aspect
@Component
public class LogAspect {

    @Before("execution(* com.example.demo.MemberService.validate(..))")
    public void beforeValidate() {
        System.out.println("AOP: validate() 호좜 μ „ 둜그");
    }
}

μ‹€ν–‰κ²°κ³Ό

  • AOP μž‘λ™ μ•ˆ 함
  • μ™œλƒν•˜λ©΄ this.validate()λŠ” ν”„λ‘μ‹œλ₯Ό μ•ˆ 거치고 κ·Έλƒ₯ 자기 μžμ‹ μ„ ν˜ΈμΆœν–ˆκΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.



이유

  • AOP μž‘λ™ ꡬ쑰

    [Client]
    ↓
    [ν”„λ‘μ‹œ 객체] β†’ Advice μ‹€ν–‰ β†’ Target 객체의 λ©”μ„œλ“œ 호좜
    
  • ν”„λ‘μ‹œ μ‹€νŒ¨ν•˜κ²Œ λ˜λŠ” this호좜(μžκΈ°μžμ‹ ν˜ΈμΆœ)

    [자기 클래슀 μ•ˆμ—μ„œ]
        ↓
    [λ°”λ‘œ 자기 λ©”μ„œλ“œ 호좜] β†’ ν”„λ‘μ‹œ μ•ˆ κ±°μΉ¨ β†’ AOP 적용 μ•ˆ 됨



ν•΄κ²° 방법 (μ™ΈλΆ€μ—μ„œ ν˜ΈμΆœν•˜λ„λ‘ ꡬ쑰 λ³€κ²½)

  • λ©”μ„œλ“œλ₯Ό λΆ„λ¦¬ν•˜κ³ , μ™ΈλΆ€μ—μ„œ ν˜ΈμΆœν•˜κ²Œ λ§Œλ“€κΈ°
  • μ΄λ ‡κ²Œ ν•˜λ©΄ memberServiceλŠ” ν”„λ‘μ‹œλ₯Ό 톡해 λ©”μ„œλ“œκ°€ ν˜ΈμΆœλ˜λ―€λ‘œ AOPκ°€ μž‘λ™ν•¨
@Component
public class LoginManager {

    @Autowired
    private MemberService memberService;

    public void loginProcess() {
        memberService.login(); // ν”„λ‘μ‹œλ₯Ό 톡해 호좜
    }
}




정리

  • AOPλŠ” λ‚˜ μžμ‹ (this)을 κ°μ‹ΈλŠ” 게 μ•„λ‹ˆλΌ, μ™ΈλΆ€μ—μ„œ λ‚˜λ₯Ό κ°μ‹ΈλŠ” 것이닀.
  • κ·Έλž˜μ„œ 자기 μžμ‹  μ•ˆμ—μ„œμ˜ λ©”μ„œλ“œ ν˜ΈμΆœμ—λŠ” AOPκ°€ μž‘λ™ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.



@Transactional μ—μ„œμ˜ AOP 미적용 사둀

  • @Transactional은 ν”„λ‘μ‹œ 기반 AOP둜 λ™μž‘ν•©λ‹ˆλ‹€.
  • 즉, ν”„λ‘μ‹œ 객체λ₯Ό 톡해 λ©”μ„œλ“œκ°€ 호좜될 λ•Œλ§Œ νŠΈλžœμž­μ…˜μ΄ μ‹œμž‘λ©λ‹ˆλ‹€.
  • 그런데 자기 μžμ‹ (this) μ•ˆμ—μ„œ this.save()처럼 직접 ν˜ΈμΆœν•˜λ©΄ ν”„λ‘μ‹œλ₯Ό μš°νšŒν•˜κ²Œ λ˜μ–΄ νŠΈλžœμž­μ…˜μ΄ μ μš©λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€

μ•ˆλ˜λŠ” μ˜ˆμ‹œ

@Service
public class OrderService {

    @Transactional
    public void save() { ... }

    public void process() {
        save(); // this.save() β†’ ν”„λ‘μ‹œ μ•ˆ κ±°μΉ¨ β†’ @Transactional 무효
    }
}

해결법 : ν˜ΈμΆœμ„ μ™ΈλΆ€ Bean으둜 뢄리

@Service
public class OrderProcessor {

    @Autowired
    private OrderService orderService;

    public void process() {
        orderService.save(); // ν”„λ‘μ‹œ 톡해 호좜됨 β†’ @Transactional 적용됨
    }
}

@Transactional λ™μž‘ μˆœμ„œ

  1. ν΄λΌμ΄μ–ΈνŠΈκ°€ μŠ€ν”„λ§ Bean λ©”μ„œλ“œ 호좜
  2. μŠ€ν”„λ§μ΄ ν”„λ‘μ‹œ 객체λ₯Ό λ¨Όμ € λ°›μŒ
  3. ν”„λ‘μ‹œκ°€ λ©”μ„œλ“œ μ‹€ν–‰ μ „ TransactionManager.begin() 호좜
  4. μ‹€μ œ νƒ€κ²Ÿ 객체의 λ©”μ„œλ“œ μ‹€ν–‰
  5. 정상 μ’…λ£Œ μ‹œ commit(), μ˜ˆμ™Έ μ‹œ rollback()
  • ν”„λ‘μ‹œκ°€ νŠΈλžœμž­μ…˜μ˜ μ‹œμž‘κ³Ό 끝을 μ±…μž„μ§„λ‹€. κ·Έλž˜μ„œ ν”„λ‘μ‹œλ₯Ό κ±°μΉ˜μ§€ μ•ŠμœΌλ©΄ νŠΈλžœμž­μ…˜λ„ μ—†λ‹€.


profile
welcome

0개의 λŒ“κΈ€