Spring AOP

kangjuju·2023년 3월 10일
0

Spring

목록 보기
3/13

Spring AOP

Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 불린다.

관점 지향은 쉽게 말해 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화하겠다는 것이다.

AOP에서 각 관점을 기준으로 로직을 모듈화한다는 것은 코드들을 부분적으로 나누어서 모듈화하겠다는 의미다.

이때, 소스 코드상에서 다른 부분에 계속 반복해서 쓰는 코드들을 발견할 수 있는 데 이것을 흩어진 관심사 (Crosscutting Concerns)라 부른다. 4

위와 같이 흩어진 관심사를 Aspect로 모듈화하고 핵심적인 비즈니스 로직에서 분리하여 재사용하겠다는 것이 AOP의 취지다 .

reference 1

reference 2

AOP 핵심 용어

● Joinpoint : ‘클래스의 인스턴스 생성 시점’, ‘메소드 호출 시점’ 및 ‘예외 발생 시점’ 과 같이 어플리케이션을 실행할 때 특정 작업이 시작되는 시점을 의미.

● Advice : 조인포인트에 삽입되어 동작할 수 있는 코드를 말함.

● Pointcut : 여러 개의 조인포인트를 하나로 결합한(묶은) 것을 말함.

● Advisor : 어드바이스와 포인트컷을 하나로 묶어 취급한 것을 말함.

● Weaving : 어드바이스를 핵심 로직 코드에 삽입하는 것을 말함.

● Target : 핵심 로직을 구현하는 클레스.

● Aspect : 여러 객체에 공통으로 적용되는 공통 관점 사항을 말함.

Proxy

AOP 실습

먼저 AspectJ Weaver 추가 한다.

MessageInter.interface

  • 간단한 문자열 출력을 구현한 MessageImpl.class
public interface MessageInter {
	void sayHi();
}
public class MessageImpl implements MessageInter{
	//핵심로직 클래스 - target
	private String name;
	
	public void setName(String name) {
		this.name = name;
	}
	
	public void sayHi() {
		System.out.println("SayHi Method-name: " + name);
		//이 메소드에서 많은 로직처리로 지연시간이 있다고 가정함
		System.out.println("처리완료");
		System.out.println();
	}
} 

main.class : AOP적용 전

  • 기존 context 출력이다.
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("aopinit.xml");
		//AOP적용 전.
		MessageInter inter = (MessageInter)context.getBean("targetBean");
		inter.sayHi();
	} 

sayHi() 의 메소드내용이 출력되었다.

이제 AOP,Proxy를 적용해보자.

LoggingAdvice.class : Advice클래스

  • Advice : 타겟에 제공할 부가기능을 담고 있는 모듈이다.
  • Aspect(관심사항)를 클래스로 구현한 advice.class
  • 이는 joinpoint에 삽입되어 동작할 코드이다.
public class LoggingAdvice implements MethodInterceptor{

	//MethodInterceptor는 자동으로 AroundAdvice를 지원
	public Object invoke(MethodInvocation invocation) throws Throwable {
		//target(sayHi) 메소드 이름 얻기
		String methodName = invocation.getMethod().getName();
		System.out.println
        ("핵심 로직 수행 전 관심사항 : 호출될 타겟 메서드 이름 : " + methodName);
		
		Object object = invocation.proceed(); //핵심로직 수행 - ex)sayHi
	
		System.out.println("핵심로직 수행 후 뭔가를 처리");
		return object;
	}
} 
  • implements MethodInterceptor 으로 인터페이스 상속을 하고, 필수 구현클래스인 invoke메소드를 생성한다.
  • invoke메소드는 joinpoint에 삽입 되어 동작할 코드를 기술하면된다.

aopinit.xml

  • xml에서 AOP관련 연결정보를 모두 정의 해야한다.
<!-- 핵심 로직 클래스 : target -->
	<bean id="targetBean" class="pack.MessageImpl">
		<property name="name" value="금요일" />
	</bean>
	
	<!-- AOP관련 -->
	<!-- Advice(Aspect 클래스) : target으로 weaving -->
	<bean id="loggingAdvice" class="pack.LoggingAdvice" />
	
	<!-- Proxy를 통한 간접 접근 -->
	<bean id="proxyFactoryBean" 
    class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="target" >
			<ref bean="targetBean" />
		</property>
		<property name="interceptorNames" >
			<list><!-- value가 여러개일땐 list -->
				<value>defaultPointcutAdvisor</value> <!-- Advisor기술 -->
			</list>
		</property>
	</bean>
	
	<!-- Advisor (Advice + pointcut) 연결역할 -->
	<bean id="defaultPointcutAdvisor" 
    class="org.springframework.aop.support.DefaultPointcutAdvisor">
		<property name="advice">
			<ref bean="loggingAdvice"/>
		</property>
		<!-- 어드바이스를 적용할 타겟의 메서드를 선별하는 정규표현식 -->
		<property name="pointcut">
			<bean class="org.springframework.aop.support.JdkRegexpMethodPointcut">
				<property name="pattern">
					<value>.*sayHi*.</value>
				</property>
			</bean>
		</property>
	</bean>

ProxyFactoryBean 을 만드는것이 궁극적인 목적이라 생각해보자.

  • Proxy는 두개를 property로 요구한다.
  1. target으로 사용할 객체
  2. interceptorNames으로 사용할 Advisor

target 은 핵심로직 클래스 Bean으로 가장위에 선언 되었고,
interceptorNames 에는 Advisor 명을 기술하였다.

Advisor는 Advice + pointcut 두개를 연결한 것이므로

  • Advisor 선언시에도 두개의 property를 요구한다.
  1. LoggingAdvice : 위에서 별도로 작성했던 advice용 클래스의 bean.
  2. pointcut : 어드바이스를 적용할 타겟의 메서드를 선별하는 정규표현식

Main.class AOP연결 후.

	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("aopinit.xml");
		//AOP적용 후. proxy패턴을 사용하여 코드를 분리
		MessageInter inter2 = (MessageInter)context.getBean("proxyFactoryBean");
		inter2.sayHi();
	} 
결과

AOP 이전
SayHi Method-name: 금요일
.....처리완료

===AOP이후===

핵심 로직 수행 전 관심사항 : 호출될 타겟 메서드 이름 : sayHi
SayHi Method-name: 금요일
.....처리완료
핵심로직 수행 후 뭔가를 처리

단어 참고시 사용한 글


AOP 관련 단어

AOP 예제2

Article interface와 그의 구현메소드 (Model)

public interface ArticleInter {
	void selectAll();
}
public class ArticleDao implements ArticleInter{
	public void selectAll() {
		System.out.println("상품 db 전체 자료 읽기");
	}
} 

Logic interface와 그의 구현메소드 (controller)

public interface LogicInter {
	void selectData_process1();
	void selectData_process2();
}
public class LogicImpl implements LogicInter {
	private ArticleInter articleInter;
	
	//constuctor injection
	public LogicImpl(ArticleInter articleInter) {
		this.articleInter = articleInter;
	}

	public void selectData_process1() {
		System.out.println("selectData_process1() 호출");
		articleInter.selectAll();
	}

	public void selectData_process2() {
		System.out.println("selectData_process2() 처리");
		int t = 0;
		while(t<5) {
			try {
				Thread.sleep(1000);
				System.out.print(".");
				t++;
			} catch (Exception e) {}
		}
		System.out.println("처리 완료");
	}
} 

ProfileAdvice.class : advice용 클래스.

  • around로써 앞뒤에 기능이 위치하고, 단순출력만 추가했다.
  • 뒤에서 선언할 pointcut의 조건에 맞게 해당 advice가 적용 될 예정이다.
 
public class ProfileAdvice { //advice용
	public Object kbs(ProceedingJoinPoint joinPoint) throws Throwable {
		
		System.out.println("핵심 메소드 전 뭔가를 처리");
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		
		System.out.println("핵심 로직처리");
		Object object = joinPoint.proceed();
		
		System.out.println("핵심 메소드 후 뭔가를 마무리");
		stopWatch.stop();
		System.out.println("처리 소요시간 : " + stopWatch.getTotalTimeSeconds());
		
		return object;
	}
} 

이제 advice와 controller,model을 연결해보자.


initaop.XML

  • namespace의 정규식으로 ProfileAdvice 기능을 입힐 멤버를 필터링했다.

	<!-- 일반 객체 생성 : 핵심 로직-->
	<bean id="logicImpl" class="aop1.LogicImpl">
		<constructor-arg>
			<ref bean="articleDao" />
		</constructor-arg>
	</bean>
	<bean id="articleDao" class="aop1.ArticleDao" />
	
	<!-- AOP설정 -->
	<!-- advice용 클래스의 bean. -->
	<bean id="profileAdvice" class="aop1.ProfileAdvice" />
	<!-- nameSpace 활용. 정규식에 정의된 필터링 대상에게. -->
	<aop:config>
		<aop:aspect ref="profileAdvice">
			<aop:pointcut expression="execution(* *..*LogicInter*.*(..)) or 
            execution(public void selectAll())" 
			id="poi"/>
			<!-- 
			AroundAdvice를 사용(핵심메소드의 앞뒤)
			advice클래스에서 invoke가 아닌 
			직접지정한 메소드명이기때문에 명시해줘야한다
			 -->
			<aop:around method="kbs" pointcut-ref="poi"/>
		</aop:aspect>
	</aop:config>
  • JointPoint : Advice가 적용될 위치, 끼어들 수 있는 지점. 메서드 진입 지점, 생성자 호출 시점, 필드에서 값을 꺼내올 때 등 다양한 시점에 적용가능

  • PointCut : JointPoint의 상세한 스펙을 정의한 것. 'A란 메서드의 진입 시점에 호출할 것'과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있음

여기서 execution의 정규식을 확인하면 된다.

0개의 댓글