본문 바로가기

Spring/스프링 프로젝트

뉴렉처 스프링 AOP

 

AOP (Aspect Oriented Programming)

AOP는 스프링과 무관하게 방법론

지금까지 OOP에만 관심있었음. 사용자가 원하는 업무 기반의 로직

사실 우리가 체감하지 못했던 코드들이 주업무로직 외에 들어가 있었음

개발자, 운영자가 만들다가 개발, 운영의 필요성에 따라 넣게 된 부가적 코드들

사용자의 관점(코어업무)에서 보고 객체 지향 프로그래밍해왔다면 개발자, 운영자의 관점 또한 반영하는 관점지향 프로그래밍

 

Primary(Core) Concern과 Cross-cutting Concern

- 코어업무(예약, 계좌이체 등) 외에 코어업무를 수행하기 위해 필요한 기타 업무들 (로그처리, 보안처리, 트랜잭션처리)

- 로그처리 : 성능 테스트를 위해 시간출력로그, 권한 여부 알아보기 위해 조건처리

- 코어업무의 앞뒤로 보조업무들이 껴들어 감

- 이런 보조업무들을 실행방향과 교차되게 잘라서 들어가는 업무라는 의미에서 Cross-cutting Concern이라 함

코어업무와 보조업무 코드가 분리되는데 이를 연결하는 것이 Proxy

사용자는 Proxy를 호출하고, 그 안의 보조업무 코드가 먼저 호출되고 코어업무 코드가 중간에 호출됨

Exam proxy = (Exam) Proxy.newProxyInstance(
	NewlecExam.class.getClassLoader()
    , new Class[] {Exam.class}
    , new InvocationHandler(){
    	<Cross-cutting Code>
    public Object invoke(Object proxy, Method method, Object[] args) throws Thrwable{
    	<Cross-cutting Code>
    return null
    }
});

 

 

스프링과 무관하게 AOP 테스트

① exam 객체 생성 및 초기화

② exam과 아주 유사하지만 보조업무를 껴넣은 proxy 만듦

     - Proxy.newProxyInstance(  ClassLoader loader           //target 객체

                                                            ,Class<?>[] interfaces        //타겟이 구현하는 인터페이스의 정보 배열

                                                            ,InvocationHandler h);     //보조업무 & 코어업무 코드를 포함하는 핸들러(익명구현객체)

      - 리턴타입은 Object, 다운캐스트한다

③ proxy 참조를 통해 메소드 호출

 

 

 

AOP자체는 스프링과 무관하지만

같이 사용한다면 코드 변경과 수정(프락시를 넣었다 빼는 것)의 문제를 SRPING  DI로 간단하게 해결가능하다.

 

보조업무의 위치에 따른 Advice 종류

  • Before : 코어업무 전
  • After returning : 업무 수행 후
  • After throwing : 예외 발생 후
  • Around : 앞뒤

 

 

이제 스프링과 AOP를 결합해 사용해 본다.

XML을 통한 설정

Around 형태의 Advice구현

 

① Program.java

  • 여기서는 xml파일을 통해 객체 생성하므로 이를 위해 ApplicationContext 얻어온다.
  • xml파일에서 설정해준 빈의 이름으로 proxy 빈 가져온 후
  • proxy.메소드 호출
public class Program {
	public static void main(String[] args) {
    /*xml파일을 통해 객체 생성*/
      ApplicationContext context = 
          new ClassPathXmlApplicationContext("spring/aop/setting.xml");

          /*이름을 통해 빈 가져오기*/
          Exam proxy = (Exam) context.getBean("proxy");

          //곁다리 업무가 필요없다면 exam.total()
          System.out.printf("total is %d\n", proxy.total());
          System.out.printf("avg is %f\n", proxy.avg());
    }
}    

 

 

② setting.xml

  • target을 빈으로 만든다 [NewlecExam]
  • 로그파일을 빈으로 만든다 [LogAroundAdvice]
  • proxy를 빈으로 만드는데, 우리가 만들어준 클래스를 연결하는 게 아니라 xml 내부에서 새로 만든다
    • target빈을 참조하는 setter1
    • log파일을 이어주는 setter2
<!--setter를 통해 target(NewlecExam) 객체 초기화-->
<bean id="target" class="spring.aop.entity.NewlecExam" p:kor="1" p:eng="1" p:math="1" p:com="1"/>
<!--로그파일 빈으로 만들기-->
<bean id="logAroundAdvice" class="spring.aop.advice.LogAroundAdvice"/>
<!--proxy 빈으로 만들기 | org.spring~는 오타 방지 차원에서 Program.java에서 자동완성한 후 가져온다-->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">		
	<!-- 인터페이스 이런거 스프링이 알아서 해줘서 타겟의 대상과, 핸들러(익명구현객체)만 넣으면 됨 -->
	<!-- setTarget()이 name="target"으로 변형, proxy객체는 value값을 갖지 않고 target 객체를 참조 -->
	<property name="target" ref="target"/>
	<property name="interceptorNames"> <!-- 가로채서 보조업무 처리할 빈 리스트로 설정-->
		<list>
			<value>logAroundAdvice</value> <!-- 참조하고 있는 bean의 이름 -->
		</list>
	</property>
</bean>

 

③ LogAroundAdvice.java

  • AroundAdvice이므로 MethodInterceptor를 구현한다.
  • invoke함수를 오버라이드한다.
  • 그 안에 로직을 작성 하고 보조업무코드 중간에 invocation.proceed()를 호출해서 반환값을 리턴

 

public class LogAroundAdvice implements MethodInterceptor { //MethodInterceptor 구현해야함

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		long start = System.currentTimeMillis();
		
		Object result = invocation.proceed();
		
		long end = System.currentTimeMillis();
		String message = (end-start) +"ms 시간이 걸렸습니다.";
		System.out.println(message);
		return result;
	}
}

 


 

 

Before 형태의 Advice구현

 

① setting 파일에서 bean을 추가하고, list value에 추가한다

② logBeforeAdvice 클래스는 MethodBeforeAdvice를 구현하고, 필수 메소드를 오버라이드한다.

 


 

After Returning 형태의 Advice구현

① setting 파일에서 bean을 추가하고, list value에 추가한다

② logAfterReturningAdvice 클래스는 AfterReturningAdvice를 구현하고, 필수 메소드를 오버라이드한다.

 

실행 결과

 BeforeAdvice → AfterReturningAdvice → AroundAdvice  total() → AroundAdvice

 

 

 


 

After Throwing 형태의 Advice구현

① setting 파일에서 bean을 추가하고, list value에 추가한다

② logBeforeAdvice 클래스는 ThrowsAdvice를 구현한다.

     필수 오버라이드 메소드는 없는데, 그 이유는 발생하는 예외 종류에 따라 함수의 인자가 달라지기 때문이다.

     여기서는 IllegalArgumentException을 임의로 넣어준다

 

③ 일부러 예외를 발생시키기 위해 total()함수를 조건처리하고, setter를 통해 kor 값을 변경한다.

④ 실행 결과 : 메소드를 호출하는 과정에서 예외가 발생하므로

                          AfterReturningAdvice가 수행되지 않고, AfterThrowingAdvice가 수행되면서 프로그램 종료

 

 

 


AOP구현방식

위빙(Weaving)

주업무와 보조업무를 연결하는 것

Cross-cutting Concern가 호출되고 target에 해당하는 Core Concern이 호출되는 방식이 뜨개질같다고 하여 위빙

 

조인포인트(JoinPoint)

보조업무가 대상으로 삼고 있는 주업무의 메소드가 연결해야 할 포인트지점이 되기 때문에 조인포인트라고 함

Proxy는 target을 대상으로 모든 메소드를 조인포인트라고 가정함

 

포인트컷(Pointcuts)

원하는 메소드만 위빙하고 싶을 때 설정할 수 있는 별도의 장치

 

포인트컷을 지정하는 가장 원시적인 방법 [ Advice당 Advisor 모두 생성]

1. classPointCut 빈 만들기 : 원하는 메소드만 특정

2. 포인트컷과 어드바이스 연결 : 포인트컷을 지정할 어드바이스 특정

3. 기존의 Advice 설정 proxy빈에서 advice명을 advisor로 대체

 

- 이때 org.springframework.~를 쓸 때 오타를 방지하기 위해 자바클래스에서 자동완성해와서 복붙

 

	<bean id="classPointCut" class="org.springframework.aop.support.NameMatchMethodPointcut">
		<!-- Setter로 원하는 메소드만 설정 -->
		<property name="mappedName" value="total"/>
	</bean>
	<!-- Advice를 포인트컷과 연결, 여기서는 가장 복잡한 방식을 사용 -->
	<bean id="classBeforeAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
		<!-- advice, pointcut은 해당 클래스에서 미리 정의된 메소드 -->
		<property name="advice" ref="logBeforeAdvice"/>
		<property name="pointcut" ref="classicPointCut"/>
	</bean>
	<bean id="classicAroundAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
		<!-- advice, pointcut은 해당 클래스에서 미리 정의된 메소드 -->
		<property name="advice" ref="logAroundAdvice"/>
		<property name="pointcut" ref="classicPointCut"/>
	</bean>
	<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">		
		<!-- 인터페이스 이런거 스프링이 알아서 해줘서 타겟의 대상과, 핸들러(익명구현객체)만 넣으면 됨 -->
		<!-- setTarget() > name="target" -->
		<property name="target" ref="target"/>
		<property name="interceptorNames">
			<list>
				<value>classAroundAdvisor</value> <!-- 참조하고 있는 bean의 이름 -->
				<value>classBeforeAdvisor</value> <!-- 참조하고 있는 bean의 이름 -->
				<value>logAfterReturningAdvice</value> <!-- 참조하고 있는 bean의 이름 -->
				<value>logAfterThrowingAdvice</value> <!-- 참조하고 있는 bean의 이름 -->
			</list>
		</property>
	</bean>

 

 

 

포인트컷을 지정하는 간소화된 방법 [Advisor과 PointCut을 한번에 지정]

1. 포인트컷을 지정하면서 어드바이저 특정

  NameMatchMethodPointcutAdvisor

2. proxy설정 동일

<bean id="classBeforeAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
	<!-- advice, pointcut은 해당 클래스에서 미리 정의되어 메소드 -->
	<property name="advice" ref="logBeforeAdvice"/>
	<property name="mappedName" value="total"/>
</bean>
<bean id="classAroundAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
	<!-- advice, pointcut은 해당 클래스에서 미리 정의되어 메소드 -->
	<property name="advice" ref="logAroundAdvice"/>
	<property name="mappedName" value="total"/>
</bean>
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">		
	<!-- 인터페이스 이런거 스프링이 알아서 해줘서 타겟의 대상과, 핸들러(익명구현객체)만 넣으면 됨 -->
	<!-- setTarget() > name="target" -->
	<property name="target" ref="target"/>
	<property name="interceptorNames">
		<list>
			<value>classAroundAdvisor</value> <!-- 참조하고 있는 bean의 이름 -->
			<value>classBeforeAdvisor</value> <!-- 참조하고 있는 bean의 이름 -->
			<value>logAfterReturningAdvice</value> <!-- 참조하고 있는 bean의 이름 -->
			<value>logAfterThrowingAdvice</value> <!-- 참조하고 있는 bean의 이름 -->
		</list>
	</property>
</bean>

 

여러 메소드 list로 포인트컷 지정

정규표현식 사용

 to를 포함하는 메소드


 

Annotation을 사용하는 AOP [진보한 방식]

 

아직 강의 안올라옴

 

'Spring > 스프링 프로젝트' 카테고리의 다른 글