μ΄μ° λκ² μ€λλ§μ κΈ μ°λ€μ..γ γ AOP 곡λΆνλ€κ° λ¦μμ΅λλ€...
μλ§ μ΄λ² ν¬μ€ν μ΄νλ‘ νλμ ν¬μ€ν μ λͺ» ν κ±° κ°λ€μ...
νκ΅λ 볡ννκ³ , νμ₯μ΄ λλ²λ €μ μ’ μ΄κ²μ κ² λ°μ©λλ€.. κ·Έλ¦¬κ³ ν λΉμ μ€νλ§λ μ½κ³ , μ΄κ²μ κ² κ³΅λΆν κ² λ§μμμ..γ γ
μ€λμ AOPλ₯Ό μ μ©νμ¬ Controller, Service, Repository κ³μΈ΅μ λν΄ λ©μλκ° μνλλ©΄ λ‘κ·Έλ₯Ό λ¨κΈ°κ³ , μ΄λ₯Ό νμΌλ‘ μ μ₯νλλ‘ λ°κΏλ³΄κ² μ΅λλ€.
κ·Έλ¦¬κ³ μ΄μνκ²½λ³ λ‘κ·Έ μμ€μ λ€λ₯΄κ² λ¨κΈ°λ κ²λ ν΄λ³΄κ² μ΅λλ€!
- μν리ν°λ₯Ό μ΄μ©ν JSON λ°μ΄ν°λ‘ λ‘κ·ΈμΈ (μλ£)
- JWTλ₯Ό μ΄μ©ν μΈμ¦ (μλ£)
- λλ©μΈ, ν μ΄λΈ μ€κ³, μν°ν° μμ± (μλ£)
- λκΈ μμ λ‘μ§ κ΅¬ν (μλ£)
- νμκ°μ + μ 보μμ λ± νμ μλΉμ€ ꡬν (μλ£)
- κ²μν μλΉμ€ ꡬν (μλ£)
- λκΈ μλΉμ€ ꡬν (1λκΈ -> *(무ν) λλκΈ κ΅¬μ‘°) (μλ£)
- μμΈ μ²λ¦¬ (μλ£)
- μμΈ λ©μΈμ§ κ΅μ ν
- μΉ΄ν κ³ λ¦¬λ³ κ²μν λΆλ₯
- κ²μκΈ νμ΄μ§ (μλ£)
- λμ μΈ κ²μ 쑰건μ μ¬μ©ν κ²μ (μλ£)
- μ¬μ©μ κ° μͺ½μ§ κΈ°λ₯
- 무ν μͺ½μ§ μ€ν¬λ‘€
- κ²μλ¬Ό & λκΈμ λν μλ
- μͺ½μ§μ λν μλ
- μ μν μ¬μ©μ κ° μ€μκ° μ±ν
- νμκ°μ μ κ²μ¦(μ: XXλνκ΅ XXκ³Όκ° μλλ©΄ κ°μ ν μ μκ²)
- Swaggerλ₯Ό μ¬μ©ν API λ¬Έμ λ§λ€κΈ°
- μ κ³ & λΈλ리μ€νΈ κΈ°λ₯
- AOPλ₯Ό ν΅ν λ‘κ·Έ (μ§ν μ€)
- μ΄λλ―Ό νμ΄μ§
- μΊμ
- λ°°ν¬ (+ 무μ€λ¨ λ°°ν¬)
- λ°°ν¬ μλν
- ν¬νΈμ μ΄λν° μ€κ³λ₯Ό λ°λ₯΄λ ν¨ν€μ§ ꡬ쑰 μ€κ³νκΈ°
- ...
TraceId μμ±
TraceIdλ μ΄λ€ μ μ κ° λ³΄λΈ μμ²μΈμ§(μ΅λͺ μ΄λΌλ©΄ μμμ λλ€ λ²νΈλ‘ ꡬλΆ)μ λν μ 보λ₯Ό κ°μ§κ³ μμΌλ©°,
μ΄ λ©μλκ° λ©μλ μ½μ€ν μ€μμ λͺλ²μ§Έλ‘ νΈμΆλ λ©μλμΈμ§μ λν μ 보λ₯Ό κ°μ§κ³ μμ΅λλ€.
μμΉλ λ€μκ³Ό κ°μ΅λλ€.
package boardexample.myboard.global.log;
import boardexample.myboard.global.util.security.SecurityUtil;
import java.util.UUID;
public class TraceId {
private String id;
private int level;//κΉμ΄
public TraceId() {
this.id = createId();
this.level = 0;
}
public TraceId(String id, int level) {
this.id = id;
this.level = level;
}
private String createId() {
try {
SecurityUtil.getLoginUsername();
}catch (NullPointerException | ClassCastException e ){ //λ‘κ·ΈμΈ μνκ³ μ κ·Ό & signUpλ±μΌ κ²½μ° anonymousUserκ° λ°νλλ―λ‘ μΊμ€ν
μ΄ λΆκ°λ₯
return String.format("[Anonymous: %S]",UUID.randomUUID().toString().substring(0,8));
}
return SecurityUtil.getLoginUsername();
}
public TraceId createNextId(){
return new TraceId(id, level + 1);
}
public TraceId createPreviousId(){
return new TraceId(id, level - 1);
}
public boolean isFirstLevel(){
return level == 0;
}
public String getId() {
return id;
}
public int getLevel() {
return level;
}
}
createId λ©μλλ λ§μ½ λ‘κ·ΈμΈ ν μ μ κ° μλ€λ©΄ ν΄λΉ usernameμ idλ‘ μ¬μ©νκ³ , μλ€λ©΄ μμμ λλ UUIDλ₯Ό μ¬μ©ν©λλ€.
levelμ λ©μλκ° λͺ λ²μ§Έλ‘ νΈμΆλμλμ§μ λν μ 보λ₯Ό κ°μ§κ³ μμ΅λλ€.
TraceStatus ꡬν
public class TraceStatus {
private TraceId traceId;
private Long startTimeMs;
private String message;
public TraceStatus(TraceId traceId, Long startTimeMs, String message) {
this.traceId = traceId;
this.startTimeMs = startTimeMs;
this.message = message;
}
public TraceId getTraceId() {
return traceId;
}
public Long getStartTimeMs() {
return startTimeMs;
}
public String getMessage() {
return message;
}
}
traceStatusλ TraceIdμ λ©μλκ° μ²μμΌλ‘ νΈμΆλ μκ°, κ·Έλ¦¬κ³ λ©μΈμ§λ₯Ό κ°μ§κ³ μμ΅λλ€.
μ΄μ μ΄ λ ν΄λμ€λ₯Ό κ°μ§κ³ λ‘κ·Έλ₯Ό λ¨κ²¨λ³΄λλ‘ νκ² μ΅λλ€.
LogTrace μμ±
public interface LogTrace {
TraceStatus begin(String message);
void end(TraceStatus status);
void exception(TraceStatus status, Throwable e);
}
beginμ λ©μλκ° μ€νλκΈ° λ°λ‘ μ§μ μ νΈμΆλμ΄ μμ μκ°κ³Ό λ©μΈμ§λ₯Ό κ°μ§ TraceStatusλ₯Ό λ°νν©λλ€.
μ΄ν μμΈλ λ©μλκ° μ μ μ’ λ£λμμ λ μ΄λ₯Ό κ°μ§κ³ κ±Έλ¦° μκ°μ μΈ‘μ ν©λλ€.
ThreadLocalLogTrace μμ±
package boardexample.myboard.global.log;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class ThreadLocalLogTrace implements LogTrace {
private static final String START_PREFIX = "-->";
private static final String COMPLETE_PREFIX = "<--";
private static final String EX_PREFIX = "<X-";
private ThreadLocal<TraceId> traceIdHolder = new ThreadLocal<>();
@Override
public TraceStatus begin(String message) {
syncTraceId();
TraceId traceId = traceIdHolder.get();
Long startTimeMs = System.currentTimeMillis();
log.info("[{}] {}{}",
traceId.getId(),
addSpace(START_PREFIX, traceId.getLevel()),
message);
return new TraceStatus(traceId, startTimeMs, message);
}
private void syncTraceId(){
TraceId traceId = traceIdHolder.get();
if(traceId == null){
traceIdHolder.set(new TraceId());
}else {
traceIdHolder.set(traceId.createNextId());
}
}
@Override
public void end(TraceStatus status) {
complete(status, null);
}
@Override
public void exception(TraceStatus status, Throwable e) {
complete(status, e);
}
private void complete(TraceStatus status, Throwable e) {
Long stopTimeMs = System.currentTimeMillis();
long resultTimeMs = stopTimeMs - status.getStartTimeMs();
TraceId traceId = status.getTraceId();
if (e == null) {
log.info("[{}] {}{} time={}ms",
traceId.getId(),
addSpace(COMPLETE_PREFIX, traceId.getLevel()),
status.getMessage(),
resultTimeMs);
} else {
log.info("[{}] {}{} time={}ms ex={}",
traceId.getId(),
addSpace(EX_PREFIX, traceId.getLevel()),
status.getMessage(),
resultTimeMs,
e.toString());
}
releaseTraceId();
}
private void releaseTraceId() {
TraceId traceId = traceIdHolder.get();
if(traceId.isFirstLevel()){
traceIdHolder.remove();
}else{
traceIdHolder.set(traceId.createPreviousId());
}
}
private static String addSpace(String prefix, int level) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < level; i++) {
sb.append(
(i == level - 1)
? "|" + prefix
: "| "
);
}
return sb.toString();
}
}
ThreadLocalμ μ¬μ©νμ¬ λμμ± λ¬Έμ κ° λ°μνμ§ μλλ‘ λ§μμ£Όμμ΅λλ€.
μμ λ‘κ·Έλ₯Ό λ¨κΈ°λ μ½λλ "μ€νλ§ ν΅μ¬ μ리 κ³ κΈνΈ"μ λ³΄κ³ μ°Έκ³ ν κ²μ΄λ μμΈνκ² κΆκΈνλ€λ©΄ μ¬λ³΄λκ±° μΆμ²..
μ΄μ ν΄λΉ LogTraceλ₯Ό μ μ©νκΈ° μν AOPλ₯Ό μμ±ν΄λ³΄λλ‘ νκ² μ΅λλ€.
LogAop μμ±
μμΉλ λ€μκ³Ό κ°μ΅λλ€.
package boardexample.myboard.global.aop;
import boardexample.myboard.global.log.LogTrace;
import boardexample.myboard.global.log.TraceStatus;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
@RequiredArgsConstructor
public class LogAop {
private final LogTrace logTrace;
@Pointcut("execution(* boardexample.myboard.domain..*Service*.*(..))")
public void allService(){};
@Pointcut("execution(* boardexample.myboard.domain..*Repository*.*(..))")
public void allRepository(){};
@Pointcut("execution(* boardexample.myboard.domain..*Controller*.*(..))")
public void allController(){};
@Around("allService() || allController() || allRepository()")
public Object logTrace(ProceedingJoinPoint joinPoint) throws Throwable {
TraceStatus status = null;
try{
status = logTrace.begin(joinPoint.getSignature().toShortString());
Object result = joinPoint.proceed();
logTrace.end(status);
return result;
}catch (Throwable e){
e.printStackTrace();
logTrace.exception(status, e);
throw e;
}
}
}
μ κΉ ν¬μΈνΈμ»· νλλ§ μ΄ν΄λ³΄κ³ λμ΄κ°κ² μ΅λλ€.
@Pointcut("execution(* boardexample.myboard.domain..*Service*.*(..))")
μλ boardexample.myboard.domainκ³Ό κ·Έ νμ λͺ¨λ ν΄λλ€ μ€ ν΄λμ€ μ΄λ¦μ μ€κ°μ Serviceκ° λ€μ΄κ° λͺ¨λ λ©μλμ μ μ©λλ ν¬μΈνΈμ»·μ λλ€.
joinPoint.getSignature().toShortString()μ νλ©΄ 'MemberService.signUp(..)'κ³Ό κ°μ νμμΌλ‘ λ°νλλ―λ‘ μ΄λ₯Ό μ΄μ©ν΄ λ‘κ·Έλ₯Ό λ¨κΈΈ κ²μ λλ€.
νκ²½μ λΆλ¦¬
ν μ€νΈνκ²½μ μμ μ μ΄λ―Έ λΆλ¦¬νμΌλ―λ‘ μ΄λ²μλ κ°λ°νκ²½(dev)μ λ°°ν¬νκ²½(prod)λ‘ κ΅¬λΆνμ¬ νκ²½μ λλ 보λλ‘ νκ² μ΅λλ€.
application.yml
spring:
profiles:
group:
"dev-profile": "dev,jwt"
"prod-profile": "prod,jwt"
μμ κ°μ΄ dev-profileμ κ°λ° νκ²½μ΄λ©°, application-dev.ymlμ application-jwt.ymlλ₯Ό μ¬μ©ν©λλ€.
jwtλ μ΄μ μ μμ±ν κ²κ³Ό κ·Έλλ‘μ λλ€.
κ°λ° νκ²½ - application-dev.yml
spring:
config:
activate:
on-profile: dev
banner:
location: dev-banner.txt
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:tcp://localhost/~/jpa
username: sa
password: 1
data:
web:
pageable:
default-page-size: 20 #νμ΄μ§ ν λ κΈ°λ³Έκ°, 20κ°μ© μ‘°ν
servlet:
multipart:
max-request-size: 5MB #μ
λ‘λ νμΌ ν¬κΈ° μ΄λ μ ν
max-file-size: 2MB #μ
λ‘λ νμΌ ν¬κΈ° μ ν
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
user_sql_cooments: true
default_batch_fetch_size: 100 #λ°°μΉ μ¬μ΄μ¦ (Collection μ‘°ν μ΅μ ν)
hibernate:
ddl-auto: none
open-in-view: false #OSIV μ¬μ©νμ§ μκΈ°
logging:
level:
p6spy: debug
org:
apache:
coyote:
http11: debug #debug
hiberante:
SQL: debug
boardexample:
myboard: debug
file:
dir: D:\files\
μμ§ λ°μ΄ν°λ² μ΄μ€λ κ°λ°νκ²½κ³Ό λ°°ν¬νκ²½μ΄ λΆλ¦¬κ° λμ§ μμμ΅λλ€.
κ·Έλ¦¬κ³ κ°λ°νκ²½μμλ μ€νλλ SQL λ¬Έμ λ‘κ·Έλ‘ λ¨κΈ°λ κ²μ λ¬Όλ‘ , λ‘κΉ λ 벨μ debugλ‘ μ€μ νμμ΅λλ€.
μΆκ°λ‘ κ°λ° νκ²½ λ°°λμ λ°°ν¬ νκ²½ λ°°λλ₯Ό λ°λ‘ λ§λ€μ΄ ꡬλΆνκΈ° νΈνκ² λ§λ€μμ΅λλ€.
κ°λ° νκ²½ - application-prod.yml
spring:
config:
activate:
on-profile: prod
banner:
location: prod-banner.txt
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:tcp://localhost/~/jpa
username: sa
password: 1
data:
web:
pageable:
default-page-size: 20 #νμ΄μ§ ν λ κΈ°λ³Έκ°, 20κ°μ© μ‘°ν
servlet:
multipart:
max-request-size: 5MB #μ
λ‘λ νμΌ ν¬κΈ° μ΄λ μ ν
max-file-size: 2MB #μ
λ‘λ νμΌ ν¬κΈ° μ ν
jpa:
properties:
hibernate:
default_batch_fetch_size: 100 #λ°°μΉ μ¬μ΄μ¦ (Collection μ‘°ν μ΅μ ν)
hibernate:
ddl-auto: none
open-in-view: false #OSIV μ¬μ©νμ§ μκΈ°
logging:
level:
p6spy: error
org:
apache:
coyote:
http11: info #debug
file:
path: D:\Log\mybored-log.txt
boardexample:
myboard: info
file:
dir: D:\files\
λ‘κ·Έλ₯Ό info νΉμ errorλ‘λ§ μ€μ νμμ΅λλ€. κ·Έλ¦¬κ³ logging.file.path μμ±μ ν΅ν΄ λ‘κ·Έ νμΌμ΄ μ μ₯λλ μμΉλ₯Ό μ§μ ν΄ μ£Όμμ΅λλ€. μ λ κ²λ§ ν΄μ£Όλ©΄ λ‘κ·Έ νμΌμ΄ μλμΌλ‘ μμ±λ©λλ€.
(μμ§ μ κ° λ‘κ·Έλ₯Ό μ λλ‘ νμ©ν΄ λ³Έ μ μ΄ μμ΄μ μ½μμ°½μ μΆλ ₯λλ λ‘κ·Έμ νμΌμ μ μ₯λλ λ‘κ·Έμ λ 벨μ λ€λ₯΄κ² νκ±°λ μμΌλ³λ‘ λ‘κ·ΈνμΌμ λ°λ‘ κ΄λ¦¬νλ λ°©λ² λ±μ μ μ©νμ§λ λͺ»νμ΅λλ€..γ γ λ€μμ 곡λΆν΄μ μ μ©ν΄ 보λλ‘ νκ² μ΅λλ€.)
μ΄λ κ² ν΄μ AOPλ₯Ό ν΅ν΄ λ‘κ·Έλ₯Ό λ¨κ²¨λ³΄κ³ , κ°λ°νκ²½κ³Ό λ°°ν¬νκ²½μ λΆλ¦¬νλ μμ μ μ§νν΄ λ³΄μμ΅λλ€.
λ€μλ²μλ... μ... λκ° λ§λ§νλ€μ...γ γ redisλ μ¨λ³΄κ³ μΆκ³ , λ§ μ΄κ²μ κ² ν΄λ³΄κ³ μΆμλ° μκ°λ λ무 λΆμ‘±νκ³ μ€λ ₯λ λ무 λΆμ‘±νλ€ λ³΄λ...
λ ν μ§λ λͺ¨λ₯΄κ² μ΅λλ€.
κ·Έλλ κΈ°λ³Έμ μΈ κ²μνμ λ€ λ§λ€μμΌλ νΉμλ μ μ μ΄λ° λΆμ‘±ν ν¬μ€ν μ λ΄μ£Όμλ λΆλ€μ΄ κ³μ λ€λ©΄... μ§κΈλΆν°λ λ§λλ‘ κΎΈλ©°λ³΄μλ κ²λ..γ γ
(λ΄μ£Όμλ λΆλ€μ΄ κ³μλ€λ©΄ κ°μ¬ν©λλ€..!!)
μλλ κ°λ° νκ²½λ³ λ°°λμ, νκ²½λ³λ‘ μ€ννλ λ°©λ², κΉνλΈ μ£Όμμ λλ€.
λ°°λ Text
βββββββ βββββββββββ βββ βββββββ βββββββ βββββββ ββββββββββββββ ββββββββ
βββββββββββββββββββ βββ βββββββββββββββββββββββββββββββββββββββ ββββββββ
βββ βββββββββ βββ βββ βββββββββββββββββββ βββββββββ ββββββ ββββββ
βββ βββββββββ ββββ ββββ βββββββ βββββββββββ βββββββββ ββββββ ββββββ
ββββββββββββββββ βββββββ βββ βββ βββββββββββββββ βββββββββββββββββββ
βββββββ ββββββββ βββββ βββ βββ βββ βββββββ βββ βββββββββββββββββββ
βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ ββββββββββββββ ββββββββ
βββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββ ββββββββ
βββββββββββββββββββ ββββββ βββ βββββββββββββββββββ βββββββββ ββββββ ββββββ
βββββββ βββββββββββ ββββββ βββ βββββββ βββββββββββ βββββββββ ββββββ ββββββ
βββ βββ ββββββββββββββββββββ βββ βββ βββββββββββββββ βββββββββββββββββββ
βββ βββ βββ βββββββ βββββββ βββ βββ βββ βββββββ βββ βββββββββββββββββββ
νκ²½λ³λ‘ μ€ννλ λ²
λ°°ν¬νκ²½(prod-profile) μ€ν
μΈν 리μ μ΄ μ°μΈ‘ μλ¨μ λ§μΉλ²νΌ λ°λ‘ μ€λ₯Έμͺ½μ μΉΈμ ν΄λ¦ν΄ 보면 Edit Configurationμ΄λΌλ μ΅μ μ΄ λΉλλ€.
(νμΌμ λ©μΈ λ©μλκ° μλ νμΌμ μ νν΄μ£ΌμΈμ)
μμ κ°μ΄ Active profilesμ prod-profileμ μ λ ₯ν΄μ£ΌμΈμ.
λκ°μ΄ κ°λ°νκ²½λ dev-profileμ μ λ ₯ν΄ μ£Όμλ©΄ λ©λλ€.
μ 체 μ½λλ κΉνλΈμμ νμΈνμ€ μ μμ΅λλ€.
https://github.com/ShinDongHun1/SpringBoot-Board-API