XML Schema Based AOP with Spring
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd "> <!-- bean definition & AOP specific configuration --> </beans>
Declaring an aspect
<aop:config> <aop:aspect id="myAspect" ref="aBean"> ... </aop:aspect> </aop:config> <bean id="aBean" class="..."> ... </bean>
Declaring a pointcut
pointcut은 다른 advice로 실행되어지는 흥미(interest - 기능 정도로 보면 되지 않을까?)의 join point(즉 함수)를 결정하는데 도움을 준다. XML기반 설정으로 동작할때, pointcut은 아래와 같이 정의될 것이다.
<aop:config> <aop:aspect id="myAspect" ref="aBean"> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service.*.*(..))"/> ... </aop:aspect> </aop:config> <bean id="aBean" class="..."> ... </bean>
다음 예제는 com.tutorialspoint 패키지의 Student class에서 유용한 getName()함수의 실행을 일치시킬 'businessService'로 이름지어진 pointcut을 정의한다.
<aop:aspect id="myAspect" ref="aBean"> <aop:pointcut id="businessService" expression="execution(* com.tutorialspoint.Student.getName(..))"/> ... </aop:aspect> </aop:config> <bean id="aBean" class="..."> ... </bean>
Declaring advices
아래와 같이 <aop:{ADVICE NAME}> 요소를 사용하여 <aop:aspect>내에 다섯가지 advice의 어떤것도 선언할 수 있다.
<aop:config> <aop:aspect id="myAspect" ref="aBean"> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service.*.*(..))"/> <!-- a before advice definition --> <aop:before pointcut-ref="businessService" method="doRequiredTask"/> <!-- an after advice definition --> <aop:after pointcut-ref="businessService" method="doRequiredTask"/> <!-- an after-returning advice definition --> <!--The doRequiredTask method must have parameter named retVal --> <aop:after-returning pointcut-ref="businessService" returning="retVal" method="doRequiredTask"/> <!-- an after-throwing advice definition --> <!--The doRequiredTask method must have parameter named ex --> <aop:after-throwing pointcut-ref="businessService" throwing="ex" method="doRequiredTask"/> <!-- an around advice definition --> <aop:around pointcut-ref="businessService" method="doRequiredTask"/> ... </aop:aspect> </aop:config> <bean id="aBean" class="..."> ... </bean>
다른 advice에 대하여 다른 함수 또는 동일한 doRequiredTask를 사용할 수 있다. 이 함수들은 aspect 모듈의 한 부분으로써 정의될 것이다.
XML Schema Based AOP Example
XML schema 기반 AOP에 관련하여 이미 언급된 개념을 이해하기 위해 몇가지 advice를 구현하는 예제를 작성할 것이다.
[003] HelloWorld Example 강좌를 참고하여 프로젝트를 생성한다.
Logging.java: 다양한 point에서 호출되어지는 함수를 정의하는 실제 aspect모듈의 예이다.
package com.tutorialspoint; public class Logging { /** * This is the method which I would like to execute * before a selected method execution. */ public void beforeAdvice(){ System.out.println("Going to setup student profile."); } /** * This is the method which I would like to execute * after a selected method execution. */ public void afterAdvice(){ System.out.println("Student profile has been setup."); } /** * This is the method which I would like to execute * when any method returns. */ public void afterReturningAdvice(Object retVal){ System.out.println("Returning:" + retVal.toString() ); } /** * This is the method which I would like to execute * if there is an exception raised. */ public void AfterThrowingAdvice(IllegalArgumentException ex){ System.out.println("There has been an exception: " + ex.toString()); } }
Student.java:
package com.tutorialspoint; public class Student { private Integer age; private String name; public void setAge(Integer age) { this.age = age; } public Integer getAge() { System.out.println("Age : " + age ); return age; } public void setName(String name) { this.name = name; } public String getName() { System.out.println("Name : " + name ); return name; } public void printThrowException(){ System.out.println("Exception raised"); throw new IllegalArgumentException(); } }
MainApp.java:
package com.tutorialspoint; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MainApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml"); Student student = (Student) context.getBean("student"); student.getName(); student.getAge(); student.printThrowException(); } }
Beans.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd "> <aop:config> <aop:aspect id="log" ref="logging"> <aop:pointcut id="selectAll" expression="execution(* com.tutorialspoint.*.*(..))"/> <aop:before pointcut-ref="selectAll" method="beforeAdvice"/> <aop:after pointcut-ref="selectAll" method="afterAdvice"/> <aop:after-returning pointcut-ref="selectAll" returning="retVal" method="afterReturningAdvice"/> <aop:after-throwing pointcut-ref="selectAll" throwing="ex" method="AfterThrowingAdvice"/> </aop:aspect> </aop:config> <!-- Definition for student bean --> <bean id="student" class="com.tutorialspoint.Student"> <property name="name" value="Zara" /> <property name="age" value="11"/> </bean> <!-- Definition for logging aspect --> <bean id="logging" class="com.tutorialspoint.Logging"/> </beans>
실행 결과는 아래와 같다라고 하고 싶지만, AOP에 접어들면 원문에서의 지침에 따라 프로젝트 생성시 계속 관련 라이브러리가 제대로 올라오지 않는 듯한 오류가 발생하여(원인은 모르겠다. aopaliance.jar 관련 오류라는데, 구글링을 통해 해결하려 해봤으나, 원문의 환경에서는 안되었다.) 결국은 maven을 이용하기로 하였다.
따라서 pom.xml의 <dependencies> 내부에 아래와 같이 의존관계를 추가 설정해 주어야 한다. (아래설정중 org.springframework의 <version>부분은 pom.xml파일을 잘 보면 version을 담은 변수가 이미 사용되고 있는 것을 찾을 수 있을것이다. 혹시 저부분에서 에러가 난다면, 실행하고 있는 pom.xml에서 복사해서 붙여넣으면 된다.)
<!-- Spring AOP + AspectJ -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.11</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.11</version>
</dependency>
이후 잘 돌아가면 다행히고, 혹시 pom.xml에 오류가 표시시가 되면서 실행이 안되는 경우는 아래와 같이 처리하면 된다.(Eclipse mars 기준이니 약간 다를 수 있다.)
위와같이 하고 실행
실행 결과는 아래와 같다.
Going to setup student profile. Name : Zara Student profile has been setup. Returning:Zara Going to setup student profile. Age : 11 Student profile has been setup. Returning:11 Going to setup student profile. Exception raised Student profile has been setup. There has been an exception: java.lang.IllegalArgumentException ..... other exception content
여기서 맨 마지막에 exception이 발생하는 이유는 Logging.java의 AfterThrowingAdvice()를 MainApp.java에서 student.printThrowException()을 통해 호출하기 때문이다.
위에서 정의된 <aop:pointcut>은 com.tutorialspoint 패키지아래 정의된 모든 함수를 선택하고 있음을 나타낸다. 특정 함수 전 또는 후에 advice를 실행하기를 원할 때, 실제 class와 함수이름ㅇ으로 pointcut정의 안에 star(*)를 대체하는 것으로 pointcut 실험범위를 좁히도록 정의할 수 있다. 아래는 이를 반영한 XML설정이다.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd "> <aop:config> <aop:aspect id="log" ref="logging"> <aop:pointcut id="selectAll" expression="execution(* com.tutorialspoint.Student.getName(..))"/> <aop:before pointcut-ref="selectAll" method="beforeAdvice"/> <aop:after pointcut-ref="selectAll" method="afterAdvice"/> </aop:aspect> </aop:config> <!-- Definition for student bean --> <bean id="student" class="com.tutorialspoint.Student"> <property name="name" value="Zara" /> <property name="age" value="11"/> </bean> <!-- Definition for logging aspect --> <bean id="logging" class="com.tutorialspoint.Logging"/> </beans>
실행 결과는 다음과 같다.
Going to setup student profile. Name : Zara Student profile has been setup. Age : 11 Exception raised ..... other exception content
--> 기존것이 expression="execution(* com.tutorialspoint.*.*(..))" 로 com.tutorialspoint 패키지 하위 함수에 대해 모두 적용되는 것이라면
변경된 것은 expression="execution(* com.tutorialspoint.Student.getName(..)) 로 Student.getName()으로 범위를 좁혔다.