Spring - 16. AOP의 개념과 용어

병호·2023년 9월 4일
0

Spring

목록 보기
16/16
post-thumbnail

AOP란?

어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화 하는 것.

공통 코드의 분리

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

위 코드에서 핵심기능은 aaa,aaa2,bbb를 호출하는 기능이고 위아래로 before, after는 부가기능이며 중복된다.이를 reflection api와 aop 개념을 적용하여 좀 더 변경이 유리한 코드로 바꾸어보자.

package com.fastcampus.ch3.aop;

import java.lang.reflect.Method;

public class AopMain {
    public static void main(String[ ] args) throws Exception {
        MyAdvice myAdvice = new MyAdvice();

        Class myClass = Class.forName("com.fastcampus.ch3.aop.MyClass");
        Object obj = myClass.newInstance();

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

class MyAdvice {
 
    void invoke(Method m, Object obj, Object... args) throws Exception {
  
            System.out.println("[before](");
        m.invoke(obj, args);
            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.");
    }
}

위 코드를 실행하면

[before](
bbb() is called.
)[after]
[before](
aaa() is called.
)[after]
[before](
aaa2() is called.
)[after]

다음과 같다. 위 코드는 reflection api를 사용해서 MyClass의 정보를 얻어온 후, invoke 메서드를 실행하여 중간에 코드를 삽입한 것이다.

위 코드에서 a로 시작하는 메서드만 실행하고 싶다면 Pattern을 사용해서 실행할 수 있다.

package com.fastcampus.ch3.aop;


import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class AopMain {
    public static void main(String[ ] args) throws Exception {
        MyAdvice myAdvice = new MyAdvice();

        Class myClass = Class.forName("com.fastcampus.ch3.aop.MyClass");
        Object obj = myClass.newInstance();

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

class MyAdvice {

    Pattern p = Pattern.compile("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);

        if(matches(m))
            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.");
    }
}

pattern을 a로 시작하는 패턴으로 주고 matches 메서드를 사용해서 실행하면

[before](
aaa() is called.
)[after]
bbb() is called.
[before](
aaa2() is called.
)[after]

위와 같이 b로 시작하는 메서드는 부가기능이 붙지 않는 것을 볼 수 있다.

위를 통해 알수 있는 AOP의 기능은 부가기능(advice)을 동적으로 추가해 주는 기술이라는 것을 알 수 있다. 메서드의 시작 또는 끝에 자동으로 코드를 추가해준다.

AOP 관련 용어

target : advice가 추가될 객체
advice : target에 동적으로 추가될 부가 기능(코드)
join point : advice가 추가(join)될 대상(메서드)
pointcut : join point들을 정의한 패턴
proxy : target에 advice가 동적으로 추가되어 생성된 객체
weaving : target에 advice를 추가해서 proxy를 생성하는 것

Advice의 종류

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

종류 애너테이션 설명
around advice @Around 메서드의 시작과 끝 부분에 추가되는 부가 기능
before advice @Before 메서드의 시작부분에 추가되는 부가 기능
after advice @After 메서드의 끝 부분에 추가되는 부가 기능
after returning @AfterReturning 예외가 발생하지 않았을 때, 실행되는 부가기능
after throwing @AfterThrowing 예외가 발생했을 때, 실행되는 부가기능

package com.fastcampus.ch3.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
@Aspect
public class LoggingAdvice {
    @Around("execution(* com.fastcampus.ch3.aop.MyMath.add*(..))") //pointcut - 부가기능이 적용될 메서드의 패턴
    public Object methodCallLog(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("<<[start] " + pjp.getSignature().getName()+ Arrays.toString(pjp.getArgs()));

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

        System.out.println("result=" + result);
        System.out.println("[end]>> " + (System.currentTimeMillis() - start)+"ms");
        return result;
    }
}
package com.fastcampus.ch3.aop;

import org.springframework.stereotype.Component;

@Component
public class MyMath {
    public int add(int a, int b) {
        int result = a+b;
        return result;
    }
    public int add(int a, int b, int c) {
        int result = a+b+c;
        return result;
    }
    public int subtract(int a, int b) {
        int result = a-b;
        return result;
    }

    public int multiply(int a, int b) {
        int result = a * b;
        return result;
    }
}
package com.fastcampus.ch3.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class AopMain2 {
    public static void main(String[] args) {
        ApplicationContext ac = new GenericXmlApplicationContext("file:src/main/webapp/WEB-INF/spring/**/root-context_aop.xml");
        MyMath mn = (MyMath)ac.getBean("myMath");
        mn.add(3,5);
        mn.add(1,2,3);
        mn.multiply(3,5);
    }
}
<<[start] add[3, 5]
result=8
[end]>> 31ms
<<[start] add[1, 2, 3]
result=6
[end]>> 0ms

위와 같이 작성하면 @Around로 메서드의 위아래를 부가 기능으로 감싸고 @Around에 패턴을 다음과 같이 적용하면 a로 시작하는 메서드만 적용된다. AopMain2 클래스에서 multiply 메서드는 적용 제외되어서 맨 아래와 같은 출력결과가 나오게 된다.

profile
안녕하세요

0개의 댓글