-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c205f94
commit e95c7b4
Showing
5 changed files
with
124 additions
and
38 deletions.
There are no files selected for viewing
40 changes: 5 additions & 35 deletions
40
.../com/thirdparty/ticketing/domain/ticket/service/proxy/LettuceReservationServiceProxy.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 20 additions & 0 deletions
20
src/main/java/com/thirdparty/ticketing/global/lock/CustomSpringELParser.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.thirdparty.ticketing.global.lock; | ||
|
||
import org.springframework.expression.ExpressionParser; | ||
import org.springframework.expression.spel.standard.SpelExpressionParser; | ||
import org.springframework.expression.spel.support.StandardEvaluationContext; | ||
|
||
public class CustomSpringELParser { | ||
private CustomSpringELParser() {} | ||
|
||
public static Object getDynamicValue(String[] parameterNames, Object[] args, String key) { | ||
ExpressionParser parser = new SpelExpressionParser(); | ||
StandardEvaluationContext context = new StandardEvaluationContext(); | ||
|
||
for (int i = 0; i < parameterNames.length; i++) { | ||
context.setVariable(parameterNames[i], args[i]); | ||
} | ||
|
||
return parser.parseExpression(key).getValue(context, Object.class); | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
src/main/java/com/thirdparty/ticketing/global/lock/lettuce/LettuceLockAnnotation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.thirdparty.ticketing.global.lock.lettuce; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
@Target(ElementType.METHOD) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
public @interface LettuceLockAnnotation { | ||
String key(); // SpEL 표현식으로 Lock 키를 결정 | ||
|
||
int retryLimit() default 5; // 기본 재시도 횟수 | ||
|
||
int sleepDuration() default 300; // 기본 슬립 시간 (밀리초) | ||
} |
80 changes: 80 additions & 0 deletions
80
src/main/java/com/thirdparty/ticketing/global/lock/lettuce/LettuceLockAspect.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package com.thirdparty.ticketing.global.lock.lettuce; | ||
|
||
import java.lang.reflect.Method; | ||
|
||
import org.aspectj.lang.ProceedingJoinPoint; | ||
import org.aspectj.lang.annotation.Around; | ||
import org.aspectj.lang.annotation.Aspect; | ||
import org.aspectj.lang.reflect.MethodSignature; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.context.expression.MethodBasedEvaluationContext; | ||
import org.springframework.core.DefaultParameterNameDiscoverer; | ||
import org.springframework.expression.spel.standard.SpelExpressionParser; | ||
import org.springframework.stereotype.Component; | ||
|
||
import com.thirdparty.ticketing.domain.common.ErrorCode; | ||
import com.thirdparty.ticketing.domain.common.LettuceRepository; | ||
import com.thirdparty.ticketing.domain.common.TicketingException; | ||
import com.thirdparty.ticketing.global.lock.CustomSpringELParser; | ||
|
||
import lombok.extern.slf4j.Slf4j; | ||
|
||
@Slf4j | ||
@Aspect | ||
@Component | ||
public class LettuceLockAspect { | ||
|
||
@Autowired private LettuceRepository lettuceRepository; | ||
|
||
private final SpelExpressionParser spelExpressionParser = new SpelExpressionParser(); | ||
|
||
private static final String LETTUCE_LOCK_PREFIX = "seat-lock-"; | ||
|
||
@Around("@annotation(com.thirdparty.ticketing.global.lock.lettuce.LettuceLockAnnotation)") | ||
public Object lock(ProceedingJoinPoint joinPoint) throws Throwable { | ||
MethodSignature signature = (MethodSignature) joinPoint.getSignature(); | ||
Method method = signature.getMethod(); | ||
LettuceLockAnnotation lettuceLockAnnotation = | ||
method.getAnnotation(LettuceLockAnnotation.class); | ||
String lockKey = | ||
LETTUCE_LOCK_PREFIX | ||
+ CustomSpringELParser.getDynamicValue( | ||
signature.getParameterNames(), | ||
joinPoint.getArgs(), | ||
lettuceLockAnnotation.key()); | ||
|
||
int retryLimit = lettuceLockAnnotation.retryLimit(); | ||
int sleepDuration = lettuceLockAnnotation.sleepDuration(); | ||
|
||
try { | ||
while (retryLimit > 0 && !lettuceRepository.seatLock(lockKey)) { | ||
retryLimit -= 1; | ||
Thread.sleep(sleepDuration); | ||
} | ||
|
||
if (retryLimit > 0) { | ||
return joinPoint.proceed(); | ||
} else { | ||
throw new TicketingException(ErrorCode.NOT_SELECTABLE_SEAT); | ||
} | ||
|
||
} catch (InterruptedException e) { | ||
throw new TicketingException(ErrorCode.NOT_SELECTABLE_SEAT, e); | ||
} finally { | ||
lettuceRepository.unlock(lockKey); | ||
} | ||
} | ||
|
||
private String parseSpel(String spel, ProceedingJoinPoint joinPoint) { | ||
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); | ||
Object rootObject = joinPoint.getTarget(); // 메서드가 호출된 대상 객체 | ||
MethodBasedEvaluationContext context = | ||
new MethodBasedEvaluationContext( | ||
rootObject, | ||
methodSignature.getMethod(), // Method | ||
joinPoint.getArgs(), // Method Arguments | ||
new DefaultParameterNameDiscoverer() // Parameter Name Discoverer | ||
); | ||
return spelExpressionParser.parseExpression(spel).getValue(context, String.class); | ||
} | ||
} |