ποΈ[SpringBoot] μ€νλ§ λΆνΈμ ν΅μ¬ κ°λ - κ΄μ μ§ν₯ νλ‘κ·Έλλ°
AOPλ?
μλ°μμλ μ¬μ¬μ© κ°λ₯ν μ½λλ₯Ό λ§λ€κΈ° μν΄ κ°μ²΄ μ§ν₯ νλ‘κ·Έλλ°(OOP)μ μ¬μ©ν©λλ€. ν΄λμ€λ₯Ό μμ±νκ³ κ°μ²΄λ₯Ό μμ±νλ©°, κ³΅ν΅ κΈ°λ₯μ λΆλͺ¨ ν΄λμ€λ‘ μμ±ν΄ μμμ ν΅ν΄ μ¬μ¬μ©ν©λλ€.
νμ§λ§ μλ°λ μμ§μ μμ μΈμλ μνμ μΌλ‘ κ³΅ν΅ κ΄μ¬μ¬λ₯Ό ꡬννλ λ°©λ²μ μ 곡νλλ°, μ΄κ²μ΄ λ°λ‘ κ΄μ μ§ν₯ νλ‘κ·Έλλ°(AOP, Aspect Oriented Programming)μ λλ€.
Spring Bootλ AOPλ₯Ό μ½κ³ νΈλ¦¬νκ² κ΅¬νν μ μλλ‘ Spring AOPλ₯Ό μ 곡ν©λλ€.
μ€μ μμ : λ©μλ μ€ν μκ° μΈ‘μ
λ¬Έμ μν©
λͺ¬ν μΉ΄λ₯Όλ‘ κΈ°λ²μΌλ‘ μμ£Όμ¨μ κ³μ°νλ λ©μλκ° μλ€κ³ κ°μ ν΄λ΄ μλ€. μ΄ λ©μλμ μ€ν μκ°μ μΈ‘μ νλ €λ©΄ λ€μκ³Ό κ°μ΄ μ½λλ₯Ό μμ±ν΄μΌ ν©λλ€.
// PiCalculator.java
public class PiCalculator {
public static void main(String[] args) {
PiCalculator pi = new PiCalculator();
System.out.println(pi.calculate(100000000));
}
double calculate(int points) {
long start = System.currentTimeMillis();
int circle = 0;
for (long i = 0; i < points; i++) {
double x = Math.random() * 2 - 1;
double y = Math.random() * 2 - 1;
if (x * x + y * y <= 1) {
circle++;
}
}
long executionTime = System.currentTimeMillis() - start;
System.out.println("executed in " + executionTime + "ms.");
return 4.0 * circle / points;
}
}
λ¬Έμ μ : μ€ν μκ°μ μΈ‘μ νκ³ μΆμ λ©μλκ° μ¬λ¬ κ°λΌλ©΄ λ§€λ² μ΄λ° μ½λλ₯Ό λ°λ³΅ν΄μΌ ν©λλ€.
AOPλ₯Ό νμ©ν ν΄κ²°μ±
λ€μκ³Ό κ°μ΄ μ λ Έν μ΄μ νλλ§ μΆκ°νλ©΄ μλμΌλ‘ μ€ν μκ°μ΄ μΈ‘μ λλ€λ©΄ μΌλ§λ νΈν κΉμ?
@PrintExecutionTime
double calculate(int points) {
int circle = 0;
for (long i = 0; i < points; i++) {
double x = Math.random() * 2 - 1;
double y = Math.random() * 2 - 1;
if (x * x + y * y <= 1) {
circle++;
}
}
return 4.0 * circle / points;
}
μ΄κ²μ΄ λ°λ‘ AOPμ μ₯μ μ λλ€!
Spring AOP μ€μ
1. μμ‘΄μ± μΆκ°
build.gradle νμΌμ λ€μ μμ‘΄μ±μ μΆκ°ν©λλ€.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
}
β οΈ Spring AOPλ Spring Initializrμμ μ§μ μΆκ°ν μ μμΌλ―λ‘ μλμΌλ‘ μΆκ°ν΄μΌ ν©λλ€.
AOP ꡬννκΈ°
2. 컀μ€ν μ λ Έν μ΄μ μ μ
// PrintExecutionTime.java
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 PrintExecutionTime {
}
μ£Όμ μ€μ :
-
@Target(ElementType.METHOD): λ©μλμλ§ μ μ© κ°λ₯ -
@Retention(RetentionPolicy.RUNTIME): λ°νμμ λμ (μ»΄νμΌ μμ μ΄ μλ)
3. Aspect ν΄λμ€ κ΅¬ν
// PrintExecutionTimeAspect.java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class PrintExecutionTimeAspect {
@Around("@annotation(PrintExecutionTime)")
public Object printExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - start;
System.out.println("executed " + joinPoint.toShortString() +
" with " + joinPoint.getArgs().length +
" args in " + executionTime + "ms.");
return result;
}
}
λμ μ리:
-
@PrintExecutionTimeμ΄ λΆμ λ©μλκ° νΈμΆλλ©΄ - μ€μ λ©μλ λμ
printExecutionTimeλ©μλκ° λ¨Όμ μ€νλ¨ -
joinPoint.proceed()λ‘ μλ λ©μλλ₯Ό νΈμΆ - μ νλ‘ μΆκ° λ‘μ§(μ€ν μκ° μΈ‘μ ) μν
4. μ λ Έν μ΄μ μ μ©
// Pi.java
import org.springframework.stereotype.Component;
@Component
public class Pi {
@PrintExecutionTime
double calculate(int points) {
int circle = 0;
for (long i = 0; i < points; i++) {
double x = Math.random() * 2 - 1;
double y = Math.random() * 2 - 1;
if (x * x + y * y <= 1) {
circle++;
}
}
return 4.0 * circle / points;
}
}
5. μ€ν μμ
// PiApplication.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class PiApplication implements ApplicationRunner {
@Autowired
private Pi pi;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("PI with 10,000 points = " + pi.calculate(10000));
System.out.println("PI with 100,000 points = " + pi.calculate(100000));
System.out.println("PI with 1,000,000 points = " + pi.calculate(1000000));
System.out.println("PI with 10,000,000 points = " + pi.calculate(10000000));
System.out.println("PI with 100,000,000 points = " + pi.calculate(100000000));
}
}
μ€ν κ²°κ³Ό
executed execution(Pi.calculate(..)) with 1 args in 1ms.
PI with 10,000 points = 3.1376
executed execution(Pi.calculate(..)) with 1 args in 4ms.
PI with 100,000 points = 3.14336
executed execution(Pi.calculate(..)) with 1 args in 18ms.
PI with 1,000,000 points = 3.141108
executed execution(Pi.calculate(..)) with 1 args in 169ms.
PI with 10,000,000 points = 3.1411124
executed execution(Pi.calculate(..)) with 1 args in 1724ms.
PI with 100,000,000 points = 3.14153364
Spring AOP μ£Όμ μ λ Έν μ΄μ
1. @Before
λ©μλ μ€ν μ μ μνν λ‘μ§μ ꡬνν©λλ€.
@Component
@Aspect
public class PrintExecutionTimeAspect {
@Before("@annotation(PrintExecutionTime)")
public void beforePrintExecutionTime(JoinPoint joinPoint) {
System.out.println("do something before " + joinPoint.toShortString() +
" with " + joinPoint.getArgs().length + " args.");
}
}
2. @After
λ©μλ μ€ν νμ μνν λ‘μ§μ ꡬνν©λλ€ (μμΈ λ°μ μ¬λΆμ 무κ΄νκ² νμ μ€ν).
@Component
@Aspect
public class PrintExecutionTimeAspect {
@After("@annotation(PrintExecutionTime)")
public void afterPrintExecutionTime(JoinPoint joinPoint) {
System.out.println("do something after " + joinPoint.toShortString() +
" with " + joinPoint.getArgs().length + " args.");
}
}
3. @AfterReturning
λ©μλκ° μ μμ μΌλ‘ λ°νκ°μ 리ν΄ν νμ μνν λ‘μ§μ ꡬνν©λλ€.
@Component
@Aspect
public class PrintExecutionTimeAspect {
@AfterReturning(
pointcut = "@annotation(PrintExecutionTime)",
returning = "result"
)
public void afterReturning(JoinPoint joinPoint, Object result) {
System.out.println("afterReturning " + joinPoint.toShortString()
+ " with " + joinPoint.getArgs().length
+ " args returning " + result.toString());
}
}
4. @AfterThrowing
λ©μλ μ€ν μ€ μμΈκ° λ°μνμ λ μνν λ‘μ§μ ꡬνν©λλ€.
@Component
@Aspect
public class PrintExecutionTimeAspect {
@AfterThrowing(
pointcut = "@annotation(PrintExecutionTime)",
throwing = "ex"
)
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
System.out.println("afterThrowing " + joinPoint.toShortString()
+ " with " + joinPoint.getArgs().length
+ " args throwing " + ex.toString());
}
}
5. @Around
λ©μλ μ€ν μ ν λͺ¨λλ₯Ό μ μ΄ν μ μλ κ°μ₯ κ°λ ₯ν μ λ Έν μ΄μ μ λλ€.
AOP μ λ Έν μ΄μ λΉκ΅ν
| μ λ Έν μ΄μ | μ€ν μμ | μμΈ μ²λ¦¬ | λ°νκ° μ κ·Ό | λ©μλ μ€ν μ μ΄ | μ£Όμ μ©λ |
|---|---|---|---|---|---|
| @Before | λ©μλ μ€ν μ | λ¬΄κ΄ | β | β | μΈμ¦, λ‘κΉ , νλΌλ―Έν° κ²μ¦ |
| @After | λ©μλ μ€ν ν(νμ) | ν¬ν¨ | β | β | 리μμ€ μ 리, κ³΅ν΅ νμ²λ¦¬ |
| @AfterReturning | λ©μλ μ μ μ’ λ£ ν | μμΈ μ λ―Έμ€ν | β | β | λ°νκ° λ‘κΉ , μλ΅ νμ²λ¦¬ |
| @AfterThrowing | λ©μλ μμΈ λ°μ ν | μ μ© | β | β | μμΈ λ‘κΉ , μμΈ μ ν |
| @Around | λ©μλ μ€ν μ /ν λͺ¨λ | ν¬ν¨ | β | β | νΈλμμ , μ±λ₯ μΈ‘μ , μ 체 μ μ΄ |
μ€λ¬΄μμμ AOP νμ©
Spring Bootλ AOPλ₯Ό κΈ°λ°μΌλ‘ λ€μν μ λ Έν μ΄μ μ μ 곡ν©λλ€. κ°μ₯ λνμ μΈ μκ° @Transactionalμ λλ€.
@Transactional
public void saveUser(User user) {
userRepository.save(user);
// λ©μλ μ€ν μ : BEGIN TRANSACTION
// λ©μλ μ€ν ν: COMMIT (λλ μμΈ μ ROLLBACK)
}
@Transactional μ λ
Έν
μ΄μ
λ§ μΆκ°νλ©΄ μλμΌλ‘ νΈλμμ
μ²λ¦¬κ° λμ΄ λ§€μ° νΈλ¦¬ν©λλ€.
μ 리
AOPμ ν΅μ¬ μ₯μ :
- β μ€λ³΅ μ½λ μ κ±°
- β λΉμ¦λμ€ λ‘μ§κ³Ό λΆκ° κΈ°λ₯ λΆλ¦¬
- β μ½λ μ μ§λ³΄μμ± ν₯μ
- β μνμ κ΄μ¬μ¬ μ²λ¦¬
Spring AOPλ₯Ό νμ©νλ©΄ λ‘κΉ , νΈλμμ , 보μ, μ±λ₯ μΈ‘μ λ±μ κ³΅ν΅ κ΄μ¬μ¬λ₯Ό ν¨μ¨μ μΌλ‘ κ΄λ¦¬ν μ μμ΅λλ€.