Spring AOP์™€ Self Invocation

na.ramยท2025๋…„ 7์›” 8์ผ

Spring

๋ชฉ๋ก ๋ณด๊ธฐ
11/13
post-thumbnail

๐Ÿ”Ž Self Invocation?

๊ธฐ๋ณธ ๊ฐœ๋…

public class Sample {
    public void innerMethod1() {
        innerMethod2();
    }
    
    public void innerMethod2() {
        System.out.println("innerMethod2");
    }
}

Sample ํด๋ž˜์Šค ๋‚ด์— innerMethod1์ด๋ผ๋Š” ๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ๊ฐ€ innerMethod2๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
์ด๋ ‡๊ฒŒ ํ•œ ํด๋ž˜์Šค ๋‚ด์—์„œ ๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ๊ฐ€ ๋˜ ๋‹ค๋ฅธ ๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์„ Self Invocation, ์ฆ‰ ์ž๊ฐ€ ํ˜ธ์ถœ์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฐ Self Invocation์€ ๋ณดํ†ต์˜ ๊ฒฝ์šฐ๋ผ๋ฉด ์•„๋ฌด ๋ฌธ์ œ ์—†๋Š” ์ฝ”๋“œ์ด์ง€๋งŒ ๋งŒ์•ฝ @Transactional, @Async์™€ ๊ฐ™์ด ํ”„๋ก์‹œ๋ฅผ ํ†ตํ•ด ๋™์ž‘ํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์„ ๋งŒ๋‚˜๋ฉด ์˜๋„๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

@Service
@RequiredArgsConstructor
public class SampleService {
    private final SampleRepository sampleRepository;

    @Transactional(readOnly = true)
    public void makeNewSample(long id) {
        sample sample = sampleRepository.findById(id);
        
        if(sample != null)
            throw new DuplicateException();
        
        Sample sample = new Sample();
        saveSample(sample);
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveSample(Sample sample) {
        sampleRepository.save(sample);
    }
}

์œ„์˜ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด makeNewSample() ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์—์„œ saveSample() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š”๋ฐ
์ด๋•Œ @Transactional(propagation = Propagation.REQUIRES_NEW) ์„ค์ •์ด ๋ฌด์‹œ๋˜๊ณ  makeNewSample() ๋ฉ”์„œ๋“œ์˜ readOnly = true ํŠธ๋žœ์žญ์…˜ ์ปจํ…์ŠคํŠธ๊ฐ€ ๊ทธ๋Œ€๋กœ ์œ ์ง€๋˜์–ด sampleRepository.save(sample);์ด ๋™์ž‘ํ•˜์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์™œ ์ด๋Ÿฐ ์ƒํ™ฉ์ด ๋ฐœ์ƒํ• ๊นŒ?

@Transactional, @Async ์™€ ๊ฐ™์€ AOP ์–ด๋…ธํ…Œ์ด์…˜๋“ค์€ ํ”„๋ก์‹œ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋ฏ€๋กœ @Transactional ์–ด๋…ธํ…Œ์ด์…˜์ด ์„ ์–ธ๋œ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ํ”„๋ก์‹œ๋ฅผ ํ†ตํ•ด ํŠธ๋žœ์žญ์…˜์ด ์‹œ์ž‘๋˜๊ณ  ์ดํ›„์— ์‹ค์ œ ๊ฐ์ฒด์˜ makeNewSample() ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
์ด๋•Œ makeNewSample() ๋ฉ”์„œ๋“œ์—์„œ saveSample() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์ด๋Š” ํ”„๋ก์‹œ๊ฐ€ ์•„๋‹Œ this ์ฐธ์กฐ์— ๋Œ€ํ•ด ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ Self Inovcation์ด ์ผ์–ด๋‚˜๋ฉด ํ•ด๋‹น ๋ฉ”์„œ๋“œ์— ์ ์šฉ๋œ ์–ด๋“œ๋ฐ”์ด์Šค(Advice)๊ฐ€ ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์œ„์˜ ์˜ˆ์‹œ์—์„œ๋Š” Transactional(propagation = Propagation.REQUIRES_NEW) ์„ค์ •์ด ๋ฌด์‹œ๋˜์–ด์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋˜์ง€ ์•Š๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

Self Invocation ํ•ด๊ฒฐ๋ฒ•

Avoid Self Invocation

๋ณดํ†ต ๋ณ„๋„์˜ ํด๋ž˜์Šค๋กœ ๋ถ„๋ฆฌํ•ด Self Invocation์ด ์ผ์–ด๋‚˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
์ด๋กœ ์ธํ•ด ํด๋ž˜์Šค๊ฐ€ ๊ณผ๋„ํ•˜๊ฒŒ ๋งŽ์•„์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ ์ ˆํ•œ ์ฑ…์ž„ ๋ถ„๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.


Inject a self reference

Self Injection์œผ๋กœ this ๋Œ€์‹ ์— ์ฃผ์ž…๋œ Proxy ์ฐธ์กฐ๋ฅผ ํ†ตํ•ด ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
์ˆœํ™˜ ์ฐธ์กฐ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— @Lazy๋กœ ์‚ฌ์šฉ ์‹œ ์ดˆ๊ธฐํ™”ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

@Service
@RequiredArgsConstructor
public class SampleService {
    private final SampleRepository sampleRepository;
    
    @Lazy
    @Autowired
    private SampleService sampleService;

    @Transactional(readOnly = true)
    public void makeNewSample(long id) {
        sample sample = sampleRepository.findById(id);
        
        if(sample != null)
            throw new DuplicateException();
        
        Sample sample = new Sample();
        sampleService.saveSample(sample);
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveSample(Sample sample) {
        sampleRepository.save(sample);
    }
}    

Use AopContext.currentProxy()

AopContext์˜ currentProxy() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
ํ•ด๋‹น ๋ฐฉ๋ฒ•์€ Spring Docs์— ๊ถŒ์žฅ๋˜์ง€ ์•Š๊ณ , ์ตœํ›„์˜ ์ˆ˜๋‹จ์œผ๋กœ ๊ฐ€๊ธ‰์  ํ”ผํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์ž‘์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

public class SampleServiceImpl implements SampleService {

	@Transactional(readOnly = true)
    public void makeNewSample() {
		// This works, but it should be avoided if possible.
		((SampleService) AopContext.currentProxy()).saveSample(sample);
	}

	@Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveSample(Sample sample) {
		// some logic...
	}
}

0๊ฐœ์˜ ๋Œ“๊ธ€