My μ λ μ΅ Top5 νμ μ°ΎκΈ° μ€κ³ λ° κ΅¬ν
μꡬμ¬ν
- 2μ£Όκ° 'My μ λ μ΅'μ κ°μ₯ λ§μ΄ μ¬μ©νλ Top5 νμλ€μκ² μ λ¬Ό μ¦μ μ μν΄, νμλ³ 'My μ λ μ΅' μ¬μ©μκ° μ μ₯
- 'My μ
λ μ΅' μλ² μ¬μ© μκ°μ κΈ°μ€μΌλ‘ μ€μ
- μλ² μ¬μ© μκ°: 'My μ
λ μ΅'μ μν λ° ν΄λμ κ΄λ ¨λ API μ¬μ©μκ°μ μ΄ ν©
1. μν μ‘°ν API("GET /api/search") μνμκ°
2. κ΄μ¬μν λ±λ‘ API("POST /api/products") μνμκ°
3. ν΄λ μ μ₯ API("POST /api/folders") μνμκ°
4. ...
- μλ² μ¬μ© μκ°: 'My μ
λ μ΅'μ μν λ° ν΄λμ κ΄λ ¨λ API μ¬μ©μκ°μ μ΄ ν©
- API μ¬μ© μκ° = Controllerμ μμ²μ΄ λ€μ΄μ¨ μκ° - μλ΅μ΄ λκ° μκ°
μ€ν¬λμΉ νμΌ(Scratch File)
μ€ν¬λμΉ νμΌμ νλ‘μ νΈμ μκ΄μμ΄ μ΄λ€ νΉμ ν method νΉμ μ½λλ₯Ό μνμμΌλ³΄κ³ μΆμ λ μ¬μ©νλ νμΌ

class Scratch {
public static void main(String[] args) {
// μΈ‘μ μμ μκ°
long startTime = System.currentTimeMillis();
// ν¨μ μν
long output = sumFromOneTo(1_000_000_000);
// μΈ‘μ μ’
λ£ μκ°
long endTime = System.currentTimeMillis();
long runTime = endTime - startTime;
System.out.println("μμμκ°: " + runTime);
}
private static long sumFromOneTo(long input) {
long output = 0;
for (int i = 1; i < input; ++i) {
output = output + i;
}
return output;
}
}
My μ λ μ΅ νλ‘μ νΈμ μλ² μ¬μ© μκ° μΆκ°νκΈ°
ApiUseTime Entityμ Repository μμ±
package com.sparta.myselectshop.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "api_use_time")
public class ApiUseTime {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
@Column(nullable = false)
private Long totalTime;
public ApiUseTime(User user, Long totalTime) {
this.user = user;
this.totalTime = totalTime;
}
public void addUseTime(long useTime) {
this.totalTime += useTime;
}
}
package com.sparta.myselectshop.repository;
import com.sparta.myselectshop.entity.ApiUseTime;
import com.sparta.myselectshop.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface ApiUseTimeRepository extends JpaRepository<ApiUseTime, Long> {
Optional<ApiUseTime> findByUser(User user);
}
κ΄μ¬μν μ μ₯ λΆλΆμ μ μ©
@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class ProductController {
private final ProductService productService;
private final ApiUseTimeRepository apiUseTimeRepository;
@PostMapping("/products")
public ProductResponseDto createProduct(@RequestBody ProductRequestDto requestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) {
// μΈ‘μ μμ μκ°
long startTime = System.currentTimeMillis();
try {
// μλ΅ λ³΄λ΄κΈ°
return productService.createProduct(requestDto, userDetails.getUser());
} finally {
// μΈ‘μ μ’
λ£ μκ°
long endTime = System.currentTimeMillis();
// μνμκ° = μ’
λ£ μκ° - μμ μκ°
long runTime = endTime - startTime;
// λ‘κ·ΈμΈ νμ μ 보
User loginUser = userDetails.getUser();
// API μ¬μ©μκ° λ° DB μ κΈ°λ‘
ApiUseTime apiUseTime = apiUseTimeRepository.findByUser(loginUser)
.orElse(null);
if (apiUseTime == null) {
// λ‘κ·ΈμΈ νμμ κΈ°λ‘μ΄ μμΌλ©΄
apiUseTime = new ApiUseTime(loginUser, runTime);
} else {
// λ‘κ·ΈμΈ νμμ κΈ°λ‘μ΄ μ΄λ―Έ μμΌλ©΄
apiUseTime.addUseTime(runTime);
}
System.out.println("[API Use Time] Username: " + loginUser.getUsername() + ", Total Time: " + apiUseTime.getTotalTime() + " ms");
apiUseTimeRepository.save(apiUseTime);
}
}
...
}

λΆκ° κΈ°λ₯ λͺ¨λνμ νμμ±
- 'Top5 νμ μ°ΎκΈ°' κΈ°λ₯μ΄ μΆκ°λ κ²μ 'My μ λ μ΅' νμλ€μ μ μλ μκ³ μ νμλ μμ
- ν΅μ¬ κΈ°λ₯: κ° API λ³ μνν΄μΌ ν λΉμ¦λμ€ λ‘μ§
ex) μν ν€μλ κ²μ, κ΄μ¬μν λ±λ‘, νμ κ°μ , κ΄μ¬μνμ ν΄λ μΆκ°, ...

- λΆκ° κΈ°λ₯: ν΅μ¬κΈ°λ₯μ 보쑰νλ κΈ°λ₯
ex) νμ ν¨ν΄ λΆμμ μν λ‘κ·Έ κΈ°λ‘, API μνμκ° μ μ₯, ...

λ¬Έμ μ
- λͺ¨λ 'ν΅μ¬ κΈ°λ₯'μ Controllerμ 'λΆκ° κΈ°λ₯' μ½λλ₯Ό μΆκ°νμ λ
- 'ν΅μ¬ κΈ°λ₯'μ΄ 100κ°λΌλ©΄, 100κ°μ 'ν΅μ¬ κΈ°λ₯' λͺ¨λμ λμΌν λ΄μ©μ μ½λ μΆκ° νμ
- 'ν΅μ¬ κΈ°λ₯'μ΄ λμ€μ μΆκ°λλ€λ©΄, νμ 'λΆκ° κΈ°λ₯' μΆκ°λ₯Ό μ κ²½μ¨μΌν¨
- 'ν΅μ¬ κΈ°λ₯' μμ μ
- κ°μ ν¨μ λ΄μ 'ν΅μ¬ κΈ°λ₯'κ³Ό 'λΆκ° κΈ°λ₯'μ΄ μμ¬ μμ
- 'ν΅μ¬ κΈ°λ₯'μ΄ν΄λ₯Ό μν΄ 'λΆκ° κΈ°λ₯'κΉμ§ μ΄ν΄ νμν¨
λΆκ° κΈ°λ₯ λͺ¨λν
AOP(Aspect Oriented Programming)λ₯Ό ν΅ν΄ λΆκ° κΈ°λ₯ λͺ¨λν
- λΆκ° κΈ°λ₯μ ν΅μ¬ κΈ°λ₯κ³Όλ κ΄μ (Aspect), κ΄μ¬μ΄ λ€λ¦
- λ°λΌμ ν΅μ¬ κΈ°λ₯κ³Ό λΆλ¦¬ν΄ λΆκ° κΈ°λ₯ μ€μ¬μΌλ‘ μ€κ³ λ° κ΅¬ν κ°λ₯

Spring AOP

1. μ΄λλ°μ΄μ€: λΆκ° κΈ°λ₯μ ν΅μ¬ κΈ°λ₯μ μΈμ μνν κ±΄μ§ μ νλ κ²
- ν΅μ¬ κΈ°λ₯μ΄ μνλκΈ° μ
- ν΅μ¬ κΈ°λ₯μ΄ μνλ ν
- ν΅μ¬ κΈ°λ₯μ΄ μνλκΈ° μ κ³Ό ν
- ν΅μ¬ κΈ°λ₯μ΄ μ μνλΌμ λ°νλ κ° μ¬μ©μ¬λΆ
- ν΅μ¬ κΈ°λ₯ μ€λ₯ λ°μ μ μνμ¬λΆ
2. ν¬μΈνΈμ»·: μ μ© μμΉ(μ§μ )
Spring AOP μ λν μ΄μ
@Aspect
- Spring Bean(λΉ) ν΄λμ€μλ§ μ μ© κ°λ₯
- μ¦, AOP μ€μ νλ €λ ν΄λμ€μ μ€μ
μ΄λλ°μ΄μ€ μ’ λ₯
- @Around: 'ν΅μ¬ κΈ°λ₯' μν μ κ³Ό ν(@Before+@After)
- @Before: 'ν΅μ¬ κΈ°λ₯' νΈμΆ μ (ex: Clientμ μ λ ₯κ° Validation μν)
- @After: 'ν΅μ¬ κΈ°λ₯' μν μ±κ³΅/μ€ν¨ μ¬λΆμ μκ΄μμ΄ μΈμ λ λμ (try, catchμ finally() μ²λΌ λμ)
- @AfterReturning: 'ν΅μ¬ κΈ°λ₯' νΈμΆ μ±κ³΅ μ(λ©μλμ Return κ° μ¬μ© κ°λ₯)
- @AfterThrowing: 'ν΅μ¬ κΈ°λ₯' νΈμΆ μ€ν¨ μ, μ¦ μμΈ(Exception)κ° λ°μν κ²½μ°λ§ λμ (ex: μμΈκ° λ°μνμ λ κ°λ°μμκ² emailμ΄λ SMS 보λ)
ν¬μΈνΈμ»·
[ν¬μΈνΈ μ»· Expression νν]
execution(modifiers-pattern? return-type-pattern declaring-type-pattern ?
method-name-pattern(param-pattern) throws-pattern?)
- '?' λ μλ΅ κ°λ₯
- modifiers-pattern: public, private, *
- return-type-pattern: void, String, List<String>, *
- declaring-type-pattern
- ν΄λμ€λͺ (ν¨ν€μ§λͺ νμ)
- ex) com.sparta.myselectshop.controller.* - controller ν¨ν€μ§μ λͺ¨λ ν΄λμ€μ μ μ©
- ex) com.sparta.myselectshop.controller.. - controller ν¨ν€μ§ λ° νμ ν¨ν€μ§μ λͺ¨λ ν΄λμ€μ μ μ©
- method-name-pattern(param-pattern)
- ν¨μλͺ
- ex) addFolders - addFolders() λ©μλμλ§ μ μ©
- ex) add* - addλ‘ μμνλ λͺ¨λ λ©μλμ μ μ© - νλΌλ―Έν° ν¨ν΄(param-pattern)
- ex) com.sparta.myselectshop.dto.FolderRequestDto - FolderRequestDto μΈμ (arguments)λ§ μ μ©
- (): μΈμ μμ
- (*): μΈμ 1κ° (νμ μκ΄μμ)
- (..): μΈμ 0~Nκ° (νμ μκ΄μμ)
- ν¨μλͺ
- @Pointcut
- ν¬μΈνΈμ»· μ¬μ¬μ© κ°λ₯
- ν¬μΈνΈμ»· κ²°ν©(combine) κ°λ₯
@Component
@Aspect
public class Aspect {
@Pointcut("execution(* com.sparta.myselectshop.controller.*.*(..))")
private void forAllController() {}
@Pointcut("execution(String com.sparta.myselectshop.controller.*.*(..))")
private void forAllViewController() {}
@Around("forAllController() && !forAllViewController()")
public void saveRestApiLog() {
...
}
@Around("forAllController()")
public void saveAllApiLog() {
...
}
}
νλ‘μ νΈμ μ μ©νκΈ°
- aop > UseTimeAop ν΄λμ€ μμ±
package com.sparta.myselectshop.aop;
import com.sparta.myselectshop.entity.ApiUseTime;
import com.sparta.myselectshop.entity.User;
import com.sparta.myselectshop.repository.ApiUseTimeRepository;
import com.sparta.myselectshop.security.UserDetailsImpl;
import lombok.extern.slf4j.Slf4j;
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.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
@Slf4j(topic = "UseTimeAop")
@Aspect
@Component // Bean ν΄λμ€μ μ μ©
@RequiredArgsConstructor // Bean μ£Όμ
public class UseTimeAop {
private final ApiUseTimeRepository apiUseTimeRepository;
@Pointcut("execution(* com.sparta.myselectshop.controller.ProductController.*(..))")
private void product() {} // product λΆλΆ
@Pointcut("execution(* com.sparta.myselectshop.controller.FolderController.*(..))")
private void folder() {} // folder λΆλΆ
@Pointcut("execution(* com.sparta.myselectshop.naver.controller.NaverApiController.*(..))")
private void naver() {} // naver λΆλΆ
@Around("product() || folder() || naver()")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
// μΈ‘μ μμ μκ°
long startTime = System.currentTimeMillis();
try {
// ν΅μ¬κΈ°λ₯ μν
Object output = joinPoint.proceed(); // controller λ©μλ μν
return output;
} finally {
// μΈ‘μ μ’
λ£ μκ°
long endTime = System.currentTimeMillis();
// μνμκ° = μ’
λ£ μκ° - μμ μκ°
long runTime = endTime - startTime;
// λ‘κ·ΈμΈ νμμ΄ μλ κ²½μ°, μνμκ° κΈ°λ‘νμ§ μμ
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getPrincipal().getClass() == UserDetailsImpl.class) {
// λ‘κ·ΈμΈ νμ μ 보
UserDetailsImpl userDetails = (UserDetailsImpl) auth.getPrincipal();
User loginUser = userDetails.getUser();
// API μ¬μ©μκ° λ° DB μ κΈ°λ‘
ApiUseTime apiUseTime = apiUseTimeRepository.findByUser(loginUser).orElse(null);
if (apiUseTime == null) {
// λ‘κ·ΈμΈ νμμ κΈ°λ‘μ΄ μμΌλ©΄
apiUseTime = new ApiUseTime(loginUser, runTime);
} else {
// λ‘κ·ΈμΈ νμμ κΈ°λ‘μ΄ μ΄λ―Έ μμΌλ©΄
apiUseTime.addUseTime(runTime);
}
log.info("[API Use Time] Username: " + loginUser.getUsername() + ", Total Time: " + apiUseTime.getTotalTime() + " ms");
apiUseTimeRepository.save(apiUseTime);
}
}
}
}

μ€νλ§ μ€μ λμ
- ν΅μ¬ κΈ°λ₯ μ¬μ΄μ 'νλ‘μ'κ° μ‘΄μ¬ν¨

[AOP μ μ© μ ]

[AOP μ μ© ν]

- Springμ΄ νλ‘μ(κ°μ§ νΉμ λ리) κ°μ²΄λ₯Ό μ€κ°μ μ½μ ν΄μ€
- DispatcherServletκ³Ό ProductController μ μ₯μμλ λ³νκ° μ ν μμ
- νΈμΆλλ ν¨μμ input, ouputμ΄ μμ λμΌ
- "joinPoing.proceed()"μ μν΄μ μλ νΈμΆνλ €κ³ νλ ν¨μ, μΈμ(argument)κ° μ λ¬λ¨