public interface Gugudan {
void calculate(int level, int count);
}
public class GugudanByForLoop implements Gugudan {
@Override
public void calculate(int level, int number) {
for(int count = number; count < 10; count++) {
System.out.printf("%d x %d = %d\n", level, count, level * count);
}
}
}
public class GugudanByRecursion implements Gugudan {
@Override
public void calculate(int level, int count) {
if (count > 9) return;
System.out.printf("%d x %d = %d\n", level, count, level * count);
calculate(level, ++count);
}
}
public class GugudanProxy implements Gugudan {
private Gugudan delegator;
public GugudanProxy(Gugudan delegator) {
this.delegator = delegator;
}
@Override
public void calculate(int level, int count) {
long start = System.nanoTime();
delegator.calculate(level, count);
long end = System.nanoTime();
System.out.printf("클래스명 = %s\n", delegator.getClass().getSimpleName());
System.out.printf("실행 시간 = %d ns\n", end - start);
System.out.println("-".repeat(20));
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy // @Aspect 어노테이션 붙인 클래스를 공통 기능으로 적용
public class GugudanConfig {
@Bean
public Gugudan gugudanByForLoop() {
return new GugudanByForLoop();
}
@Bean
public Gugudan gugudanByRecursion() {
return new GugudanByRecursion();
}
}
public class GugudanTest {
public static void main(String[] args) {
// 프록시 객체 사용
System.out.println("for문 구구단");
GugudanProxy proxy1 = new GugudanProxy(new GugudanByForLoop());
proxy1.calculate(3, 1);
System.out.println("재귀 구구단");
GugudanProxy proxy2 = new GugudanProxy(new GugudanByRecursion());
proxy2.calculate(3, 1);
}
}
AOP(Aspect Oriented Programming)
스프링 프레임워크는 프록시 객체를 자동으로 생성하여 AOP를 구현
(프록시 사용 외에 컴파일 시점에 코드로 공통 기능을 삽입하거나 클래스 로딩 시점에 공통기능 삽입도 가능)
스프링 AOP는 타겟 객체(Target Object)를 외부에서 프록시 객체가 한번 감싸는 구조
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class GugudanAspect {
// 포인트컷 적용
// @Pointcut("execution(public void com..calculate(..))")
@Pointcut("execution(* cal*(..))") // 적용 지점
private void targetMethod() {}
// 어드바이스 정의
@Around("targetMethod()") // 적용 시점(메서드 실행 전 후 or 예외 발생 시)
public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 핵심 기능 로직 실행 전 호출
long start = System.nanoTime();
// 핵심 기능 호출
try {
Object result = joinPoint.proceed(); // 타깃 객체의 실제 메서드를 호출
return result;
}
// 핵심 기능 로직 실행 후 호출
finally {
long end = System.nanoTime();
Signature signature = joinPoint.getSignature();
System.out.printf("%s.%s 메서드 호출!\n", joinPoint.getTarget().getClass().getSimpleName(), signature.getName());
System.out.printf("실행 시간 : %d ns\n", end - start);
}
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy // @Aspect 어노테이션 붙인 클래스를 공통 기능으로 적용
public class GugudanConfig {
@Bean
public GugudanAspect gugudanAspect() {
return new GugudanAspect();
}
@Bean
public Gugudan gugudanByForLoop() {
return new GugudanByForLoop();
}
@Bean
public Gugudan gugudanByRecursion() {
return new GugudanByRecursion();
}
}
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext;
import org.springframework.context.ApplicationContext;
import java.lang.annotation.Annotation;
public class GugudanTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigReactiveWebApplicationContext(GugudanConfig.class);
Gugudan gugudan = applicationContext.getBean("gugudanByForLoop", Gugudan.class);
gugudan.calculate(3, 1);
System.out.println();
Gugudan gugudan2 = applicationContext.getBean("gugudanByRecursion", Gugudan.class);
gugudan2.calculate(3, 1);
}
}
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Aspect
public class GugudanCacheAspect {
// 임시 캐시 저장소
List<Object> cache = new ArrayList<>();
// 포인트컷 적용
@Pointcut("execution(* cal*(..))")
public void cacheTarget() {}
// 어드바이스 정의
@Around("cacheTarget()")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
// 데이터 초기화
Object[] argumentObject = joinPoint.getArgs();
String argumentToString = Arrays.toString(argumentObject);
// 만약 데이터가 있다면, 캐시에서 꺼내서 전달
if (cache.size() != 0) {
for (Object element : cache) {
String elementToString = Arrays.toString((Object[]) element);
if (elementToString.equals(argumentToString)) {
System.out.printf("캐시에서 데이터 불러오기 [%s]\n", elementToString);
return elementToString;
}
}
}
// 데이터가 없다면, 타깃 객체의 메서드를 호출하여 캐시에 데이터 추가
Object result = joinPoint.proceed();
cache.add(argumentObject);
System.out.printf("캐시에 데이터 추가[%s]\n", Arrays.toString(argumentObject));
return result;
}
}
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
@Aspect
@Order(value = 1)
public class GugudanAspect {
// 포인트컷 적용
// @Pointcut("execution(public void com..calculate(..))")
@Pointcut("execution(* cal*(..))") // 적용 지점
private void targetMethod() {}
// 어드바이스 정의
@Around("targetMethod()") // 적용 시점(메서드 실행 전 후 or 예외 발생 시)
public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 핵심 기능 로직 실행 전 호출
long start = System.nanoTime();
// 핵심 기능 호출
try {
// Object result = joinPoint.proceed(); // 타깃 객체의 실제 메서드를 호출
return joinPoint.proceed();
}
// 핵심 기능 로직 실행 후 호출
finally {
long end = System.nanoTime();
Signature signature = joinPoint.getSignature();
System.out.printf("%s.%s 메서드 호출!\n", joinPoint.getTarget().getClass().getSimpleName(), signature.getName());
System.out.printf("실행 시간 : %d ns\n", end - start);
}
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy // @Aspect 어노테이션 붙인 클래스를 공통 기능으로 적용
public class GugudanConfig {
@Bean // 추가
public GugudanCacheAspect cacheAspect() {
return new GugudanCacheAspect();
}
@Bean
public GugudanAspect gugudanAspect() {
return new GugudanAspect();
}
@Bean
public Gugudan gugudanByForLoop() {
return new GugudanByForLoop();
}
@Bean
public Gugudan gugudanByRecursion() {
return new GugudanByRecursion();
}
}
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext;
import org.springframework.context.ApplicationContext;
public class GugudanTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigReactiveWebApplicationContext(GugudanConfig.class);
Gugudan gugudan = applicationContext.getBean("gugudanByForLoop", Gugudan.class);
gugudan.calculate(3, 1);
gugudan.calculate(3, 1);
gugudan.calculate(3, 1);
}
}
GugudanCacheAspect 프록시 ⇒ GugudanAspect 프록시 ⇒ GugudanByRecursion 객체 순으로 어드바이스 적용
@Order(value = num)으로 애스펙트 적용 순서 지정 가능
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Slf4j
@Component
public class LoggingAspect {
@Pointcut("execution(public void com..*(..))")
private void before(){}
@Before("before()")
public void printMethod(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
Object[] parameters = joinPoint.getArgs();
log.info("⭐ 메서드 호출 ⭐");
log.info("===== 메서드 이름 = {} =====", signature.getName());
if (parameters.length == 0)
log.info("[파라미터] = 없음");
else {
String typeOfParameter = parameters.getClass().getSimpleName();
String valueOfParameters = Arrays.toString(parameters);
log.info("[파라미터 타입] = {}, [파라미터 값] = {}", typeOfParameter, valueOfParameters);
}
}
@Pointcut("execution(public int com..*(..))")
private void after(){}
@AfterReturning(pointcut = "after()", returning = "returnValue")
private void returnValue (JoinPoint joinPoint, Object returnValue) {
log.info("⭐️ 메서드 호출️ 후 ⭐");
Signature signature = joinPoint.getSignature();
log.info("===== 메서드 이름 = {} =====", signature.getName());
log.info("[반환값] = {}", returnValue);
}
}
시스템에서 발생하는 이벤트, 상태, 오류 등의 정보를 제공하는 일련의 기록(log)를 생성하는 과정 또는 결과
스트링 부트에 기본적으로 포함되는 라이브러리
SLF4J
로그 선언 - private static final Logger log = LoggerFactory.getLogger(Xxx.class)
String testData = "exString";
log.info("example = {}", testData);
// 출력값
example = exString
Logback
```java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class GugudanAspect {
// 포인트컷 적용
// @Pointcut("execution(public void com..calculate(..))")
@Pointcut("execution(* cal*(..))") // 적용 지점
private void targetMethod() {}
// 어드바이스 정의
@Around("targetMethod()") // 적용 시점(메서드 실행 전 후 or 예외 발생 시)
public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 핵심 기능 로직 실행 전 호출
long start = System.nanoTime();
// 핵심 기능 호출
try {
Object result = joinPoint.proceed(); // 타깃 객체의 실제 메서드를 호출
return result;
}
// 핵심 기능 로직 실행 후 호출
finally {
long end = System.nanoTime();
Signature signature = joinPoint.getSignature();
System.out.printf("%s.%s 메서드 호출!\n", joinPoint.getTarget().getClass().getSimpleName(), signature.getName());
System.out.printf("실행 시간 : %d ns\n", end - start);
}
}
}