본문 바로가기
java/spring

spring - aop 직접 구현해보기

by rewind 2024. 7. 10.

1. 공통 코드의 분리 → 여러 메서드에 공통 코드를 추가해야 한다면?

class MyClass {
	void syrian() {	System.out.print("Syrian() is called "); }
	void dwarf() { System.out.print("Dwarf() is called "); }
	void geobil() { System.out.print("Geobil() is called "); }	
}

 

이렇게 출력하는(메인기능) serian, dwarf, geobil이라는 세가지 메서드가 있을때 메서드의 앞뒤로 특정문구(부가기능)를 추가로 넣어주려면 

 

class MyClass {
	void syrian() {
		System.out.println("[Before] ");
		System.out.print("Syrian() is called ");
		System.out.println("[After]");	
	}
	void dwarf() {
		System.out.println("[Before] ");
		System.out.print("Dwarf() is called ");
		System.out.println("[After]");	
	}
	void geobil() {
		System.out.println("[Before] ");
		System.out.print("Geobil() is called ");
		System.out.println("[After]");
	}	
}

 

이런식으로 메서드마다 아래위로 매번 출력문(부가기능)을 넣어줘야 한다. 늘 이런 작업을 할수는 없으니 추가할 출력문을 별도의 메서드로 빼서 여러 기능을 하나로 작업하고자 하는것이 AOP의 핵심(관심사 분리 → 분리된 기능을 동적으로 합쳐주는것)

 

리플렉션 API를 사용해서 구현 하는데 우선 클래스 분리를 하고

class MyAdvice {
	void invoke(Method m , Object obj, Object... args) throws Exception {
        // m : 호출할 메서드의 메타데이터를 가지고 있는 메서드 객체
        // obj : 메서드를 호출할 객체
        // args : 가변인수로 받을때
        System.out.println("[Before] ");
        m.invoke(obj, args); // → 이부분에 MyClass의 해당 메소드들을 호출한다(핵심기능)
        System.out.println("[After]");
	}
}
class MyClass {
	void syrian() {	System.out.println("Syrian() is called "); }                   
	void dwarf() { System.out.println("Dwarf() is called "); }                   
	void geobil() { System.out.println("Geobil() is called "); }
}

 

메인클래스에서 간단하게 리플렉션 API로 호출해보자

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

public class AopMain {
	public static void main(String[] args) throws Exception {
		MyAdvice myAdvice = new MyAdvice();
		// 1. 객체생성
		Class myClass = Class.forName("com.fastcampus.ch3.aop.MyClass");
		// 2. 리플렉션 API를 사용해서 클래스 정보 가져오기
		Object obj = myClass.newInstance();
		// 3. 클래스 정보를 가지고 객체 생성
		for(Method m : myClass.getDeclaredMethods()) {
		// 4. 반복문을 통해 해당 클래스에 선언된 모든 메서드를 반환한다
		myAdvice.invoke(m, obj, null);
		// 5. obj객체에서 메서드(m)를 호출한다
		}
	}
}

잘 실행된다.

 

1. 패턴에 따른 적용 : 메서드명에 a가 포함된 메서드에만 적용(syrian, dwarf) → MyAdvice 클래스 수정

class MyAdvice {
	Pattern p = Pattern.compile(".*a.*"); // 패턴에 맞는 메서드에만 적용(패턴 : a가 포함)	
	boolean mathches(Method m) {
		Matcher matcher = p.matcher(m.getName());
		return matcher.matches();
		// 메서드 명과 패턴의 일치 여부 판단
	}
	void invoke(Method m , Object obj, Object... args) throws Exception {
		if(mathches(m))
			System.out.println("[Before]");
		m.invoke(obj, args);
		if(mathches(m))
			System.out.println("[After]");
	// if문으로 해당 패턴에 맞는 메서드에만 출력
	}    
}

아주 잘나온다

 

2. 애너테이션으로 적용하기 : @Transactional

class MyClass {
// 적용할 메서드에 @Transactional 선언
	@Transactional
	void syrian() {	System.out.println("Syrian() is called "); }   
	void dwarf() { System.out.println("Dwarf() is called "); }                   
	void geobil() { System.out.println("Geobil() is called "); }
}
class MyAdvice {
	void invoke(Method m , Object obj, Object... args) throws Exception {
		if(m.getAnnotation(Transactional.class) != null)
			System.out.println("[Before]");
		m.invoke(obj, args);

		if(m.getAnnotation(Transactional.class) != null)
        	System.out.println("[After]");
		// if문 : Method객체 m에서 @Transactional이 존재하는지 확인 후 참이면 실행(출력)
	}
}

@Transactional을 적용한 syrian()메소드에만 적용

 

코드를 자동으로 추가한다면 어디에?(영역지정) 앞,뒤,양쪽(앞+뒤)에 지정 가능한데 위에서 if문의 위치로 설정할 수 있다