Spring DI, AOP

10. AOP의 원리와 용어 - 패스트캠퍼스 백엔드 부트캠프 3기

gkss2tpt 2025. 2. 24. 18:44

1. AOP(Aspect Oriented Programming)란?

  • 관점 지향 프로그래밍, 횡단 관심사, cross-cutting-concerns
  • 부가 기능(advice)을 동적으로 추가해주는 기술 
  • 메서드의 시작 또는 끝에 자동으로 코드(advice) 를 추가

 

2. 공통 코드의 분리

  • 여러 메서드에 공통 코드를 추가해야  한다면?

public class AopMain {
    public static void main(String[] args) throws Exception {
        Class myClass = Class.forName("com.fastcampus.ch3.aop.MyClass");
        Object o = myClass.newInstance();

        MyAdvice myAdvice = new MyAdvice();

        for(Method m : myClass.getDeclaredMethods()){
            myAdvice.invoke(m, o, null);
        }
    }
}

class MyAdvice{
    void invoke(Method m, Object obj, Object... args) throws Exception{
        System.out.println("[before]{");    // 공통으로 들어갈 코드
        m.invoke(obj, args);    // aaa(), aaa2(), bbb() 호출
        System.out.println("}[after]");     // 공통으로 들어갈 코드
    }
}

class MyClass{
    void aaa(){
        System.out.println("aaa() is called.");
    }
    void aaa2(){
        System.out.println("aaa2() is called.");
    }
    void bbb(){
        System.out.println("bbb() is called.");
    }
}

  • aaa(), aaa2()만 뽑고싶다면?
class MyAdvice{
    Pattern p = Pattern.compile("a.*"); // a로 시작하는 문자열

    boolean matches(Method m){  // 지정된 메서드가 패턴과 일치하는지 알려줌
        Matcher matcher = p.matcher(m.getName());
        return matcher.matches();
    }
    void invoke(Method m, Object obj, Object... args) throws Exception{
        if (matches(m))
            System.out.println("[before]{");    // 공통으로 들어갈 코드

        m.invoke(obj, args);    // aaa(), aaa2(), bbb() 호출

        if(matches(m))
            System.out.println("}[after]");     // 공통으로 들어갈 코드
    }
}
  • 패턴을주고 실행하면...

  • bbb()는 [before]와 [after]가 추가되지 않았다.
<!-- Spring AOP: @Transactional을 위한 AOP 지원 (Spring Boot에서는 기본적으로 포함됨) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
  • @Transactional 애너테이션을 사용할 수 없을 때 추가 해줘야한다.
  • @Transactional 애너테이션을 추가해준뒤...
class MyClass{

    @Transactional
    public void aaa(){
        System.out.println("aaa() is called.");
    }

    void aaa2(){
        System.out.println("aaa2() is called.");
    }
    void bbb(){
        System.out.println("bbb() is called.");
    }
}
  • 검증을 해주면...
class MyAdvice{
    Pattern p = Pattern.compile("a.*"); // a로 시작하는 문자열

    boolean matches(Method m){  // 지정된 메서드가 패턴과 일치하는지 알려줌
        Matcher matcher = p.matcher(m.getName());
        return matcher.matches();
    }
    void invoke(Method m, Object obj, Object... args) throws Exception{
        if (m.getAnnotation(Transactional.class) != null)
            System.out.println("[before]{");    // 공통으로 들어갈 코드

        m.invoke(obj, args);    // aaa(), aaa2(), bbb() 호출

        if(m.getAnnotation(Transactional.class)!=null)
            System.out.println("}[after]");     // 공통으로 들어갈 코드
    }
}

 

3. 코드를 자동으로 추가한다면?

  • 시작(Before)과 끝(After), 둘다(Around)

 

4. AOP 관련 용어

 

5. Advice의 종류

  • Advice의 설정은 XML과 애너테이션, 두 가지 방법으로 가능

 

6. pointcut expression

  • advice가 추가될 메서드를 지정하기 위한 패턴
  • execution(접근 제어자 반환타입 패키지명.클래스명.메서드명(매개변수 목록))

public class AopMain2 {
    public static void main(String[] args) {
        ApplicationContext ac = SpringApplication.run(Config.class);
        MyMath mm = ac.getBean(MyMath.class);
        mm.add(3, 5);
        mm.add(1,2,3);
        System.out.println("mm.multiply(3,5) = " + mm.multiply(3, 5));
    }
}

@EnableAspectJAutoProxy
@ComponentScan
@Configuration
class Config {

}

@Component
@Aspect
class LoggingAdvice{
    @Around("execution(* com.fastcampus.ch3.aop.MyMath.add*(..))")
    public Object methodClassLog(ProceedingJoinPoint pjp) throws Throwable {
        // target 의 메서드 시작 부분에 추가될 코드
        long start = System.currentTimeMillis();
        System.out.println("<<[start]"+pjp.getSignature().getName()+ Arrays.toString(pjp.getArgs()));

        Object result = pjp.proceed();  // target의 메서드를 호출

        // target 의 메서드 끝 부분에 추가될 코드
        System.out.println("result = " + result);
        System.out.println("[end]>>"+(System.currentTimeMillis()-start)+"ms");

        return result;
    }
}

@Component("myMath")
class MyMath {
    int add(int a, int b){
        int result = a+b;
        return result;
    }

    int add(int a, int b, int c){
        int result = a+b+c;
        return result;
    }

    int subtract(int a, int b){
        int result = a-b;
        return result;
    }

    int multiply(int a, int b){
        int result = a*b;
        return result;
    }
}
  • 실행이 안되었을때...
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>
  • 라이브러리를 전부 날리고 새 라이브러리를 추가하니 실행이 되었다.