Back-end/Spring Boot, JPA

newlecture: spring AOP (ft.빵또아)

philo0407 2021. 4. 24. 22:32

 

 

 빵또아.,. 짱짱 ㅎ

 

 

www.youtube.com/watch?v=y2JkXjOocZ4&list=PLq8wAnVUcTFUHYMzoV2RoFoY2HDTKru3T&index=19

 

항상 감사합니다 뉴렉쳐 쌤 !!

 

 

AOP는 마치 빵또아와 같다.

 

개발을 하다보면 사용자를 위한 주 업무 로직 이외에,

개발자와 운영자를 위한 로직이 같이 생긴다!!! 뚜둥 !   ㅃ빵또앟ㅎ !!

 

 

각 주 업무 로직마다 로그, 보안, 트랜잭션 처리를 해야 하는데,

각 로직마다 호출했던 저 일들을 AOP가 관리를 해준다.

앞 뒤 관련 처리를 하나의 장소 안에서, 주 업무 로직을 호출하는 방식으로 처리하기 때문에

해당 로직들은 모두 로그, 보안, 트랜잭션 처리를 하는 것처럼 보인다.

 

이모습이 마치 빵또아를 보는 것과 비슷하다.

 

 

구체적으로는 proxy를 통해서 호출할 수 있다.

 

아래는 순수 자바코드로 AOP를 구현한 것이다! 

 

public static void main(String[] args) {

	Exam exam = new NewlecExam(1, 1, 1, 1);

	Exam proxy = (Exam) Proxy.newProxyInstance(
			NewlecExam.class.getClassLoader(), new Class[] { Exam.class },
			new InvocationHandler() {

				@Override
				public Object invoke(Object proxy, Method method, Object[] args)
						throws Throwable {

					long start = System.currentTimeMillis();

					Object result = method.invoke(exam, args);

					try {
						Thread.sleep(200);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

					long end = System.currentTimeMillis();

					String message = (end - start) + "ms 시간이 흘렀습니다. : 난 proxy얌 야 빵또아 좋아하냐?";

					System.out.println(message);

					return result;
				}
			});

	System.out.println(proxy.total());
	System.out.println(proxy.avg());

}

위와 같이 Proxy Instance를 생성하여 그 안에 InvocationHandler를 구현하여 매개변수로 던진다.

 

이떄 구현부 안에 .invoke 메서드를 호출하는데 이곳이 바로 주업무로직의 호출부이다 !

 

이제 스프링의 AOP 기능을 이용하여 작성해보자

아래와 같이 LogAroundAdvice를 만들고

 

 

 

public class LogAroundAdvice implements MethodInterceptor {
	
	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		
		long start = System.currentTimeMillis();
		
		Object result = invocation.proceed();
		
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		long end = System.currentTimeMillis();

		String message = (end - start) + "ms 시간이 흘렀습니다. : 난 proxy얌 야 빵또아 좋아하냐?";

		System.out.println(message);
		
		return result;
	}
	
}

실행결과

209ms 시간이 흘렀습니다. : 난 proxy얌 야 빵또아 좋아하냐?
204ms 시간이 흘렀습니다. : 난 proxy얌 야 빵또아 좋아하냐?

 

위와 같이 코드를 작성해 준다.

인터셉터가 2개 있는데 위에꺼다 !

이번에는 호출부가 invocation.proceed() 이다.

 

<bean id="exam" class="newlecture_spring.di.entity.NewlecExam" >
	<constructor-arg name="kor" value="1" />
	<constructor-arg name="eng" value="1" />
	<constructor-arg name="com" value="1" />
	<constructor-arg name="math" value="1" />
</bean>
<bean id="logAroundAdvice" class="newlecture_spring.aop.advice.LogAroundAdvice" />

<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="target" ref="exam" />
	<property name="interceptorNames" >
		<list>
			<value>logAroundAdvice</value>
		</list>
	</property>
</bean>

그리고 xml 설정은 위와 같이 바꾼다 !

 

public static void main(String[] args) {
	ApplicationContext context = new ClassPathXmlApplicationContext("newlecture_spring/aop/setting.xml");
	Exam proxy = (Exam) context.getBean("proxy");
}

그리고 메인함수에서 실행하면 된다 !

 

 

참고로 아래는 XML을 Annotation으로 바꾼 것인데 잘 동작하지 않았다 ㅠㅠ..

참고 하면 아니 된다..

public class SwAppConfig {
	@Bean
	public Exam exam() {
		Exam exam = new NewlecExam(1, 1, 1, 1);
		return exam;
	}
	
	@Bean
	public LogAroundAdvice logAroundAdvice() {
		return new LogAroundAdvice();
	}
	

	@Bean
	public Exam proxy() {

		ProxyFactoryBean proxy = new ProxyFactoryBean();

		proxy.setTarget("exam");
		proxy.setInterceptorNames("logAroundAdvice");
		
		return (Exam) proxy.getObject();
	}
}

 

BeforeAdvice는 아래 처럼 해보았다.

<bean id="proxy"
	...
			<list>
				<value>logAroundAdvice</value>
				<value>logBeforeAdvice</value>
			</list>
	...
public class LogBeforeAdvice implements MethodBeforeAdvice {
	
	@Override
	public void before(Method method, Object[] args, Object target)
			throws Throwable {
		System.out.println("앞에서 실행될 로직");
	}
}

AfterReturningAdvice

public class LogAfterReturningAdvice implements AfterReturningAdvice{
	
	@Override
	public void afterReturning(Object returnValue, Method method, Object[] args,
			Object target) throws Throwable {
		System.out.println("returnValue: " + returnValue.getClass() +", method: " +  method.getName());
	}
}

실행결과

앞에서 실행될 로직
returnValue: class java.lang.Integer, method: total
208ms 시간이 흘렀습니다. : 난 proxy얌 야 빵또아 좋아하냐?
total : 4
앞에서 실행될 로직
returnValue: class java.lang.Float, method: avg
203ms 시간이 흘렀습니다. : 난 proxy얌 야 빵또아 좋아하냐?
avg : 1.0

 

AfterThrowingAdivce

@Override
public int total() {
	
	if(kor > 100)
		throw new IllegalArgumentException("유효하지 않은 국어 점수");
	
	...
}

total에서 해당 조건에서 예외 설정을 해주고

 

public class LogAfterThrowingAdvice implements ThrowsAdvice {
	
	public void afterThrowing(IllegalArgumentException e) throws Throwable {
		System.out.println("예외가 발생하였습니다 : " + e.getMessage());
	}
}

위 클래스를 추가한다.

 

짜잔 !

BeforeAdvice만 실행되고 AfterThrowingAdivce에서 예외가 발생한 것을 볼 수 있다.

 

 

Point Cut : 원하는 메서드에만 Weaving을 하고 싶다.

...

<bean id="classicPointCut" class="org.springframework.aop.support.NameMatchMethodPointcut">
	<property name="mappedName" value="total" />
</bean>

<bean id="classBeforeAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
	<property name="advice" ref="logBeforeAdvice" />
	<property name="pointcut" ref="classicPointCut" />
</bean>

<bean id="classAroundAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
	<property name="advice" ref="logAroundAdvice" />
	<property name="pointcut" ref="classicPointCut" />
</bean>

<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="target" ref="exam" />
	<property name="interceptorNames" >
		<list>
			<value>classAroundAdvisor</value>
			<value>classBeforeAdvisor</value>
...

실행결과

앞에서 실행될 로직
returnValue: class java.lang.Integer, method: total
204ms 시간이 흘렀습니다. : 난 proxy얌 야 빵또아 좋아하냐?
total : 4

returnValue: class java.lang.Float, method: avg
avg : 1.0

total에서만 Before과 Around가 출력되었다.

 

<bean id="classAroundAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
	<property name="advice" ref="logAroundAdvice" />
	<property name="mappedNames" >
		<list>
			<value>total</value>
		</list>
	</property>
</bean>

<bean id="classBeforeAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
	<property name="advice" ref="logBeforeAdvice" />
	<property name="mappedNames" >
		<list>
			<value>total</value>
		</list>
	</property>
</bean>

Refactoring : 위와 같이 간소화된 Advisor로도 표현이 가능하다.

 

<bean id="classBeforeAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
	<property name="advice" ref="logBeforeAdvice" />
	<property name="patterns" >
		<list>
			<value>.*to.*</value>
			...

Refactoring 2 : NameMatchMethodPointcutAdvisor 대신에 RegexpMethodPointcutAdvisor를 사용하면 정규식으로 method 명을 지정할 수 있다.