프로그래밍/Spring

스프링(Spring) - AOP 개념과 예제 - XML 기반으로 구현

abfc 2016. 11. 2.
반응형

예제파일 :

aopEx.zip



AOP(Aspect Oriented Programming) : 관점 지향 프로그래밍

 프로그래밍을 하다보면 공통적인 기능이 많이 발생합니다. 이러한 공통 기능은 상속을 통해서 모든 모듈에 적용을 시켜줄 수 있지만 몇 가지 문제가 있지요. 우선 JAVA에서는 다중 상속이 불가능하기 때문에 한계가 있고, 기능을 구현하는 부분에 핵심 기능 코드와 공통 기능 코드가 섞여 있어서 효율이 떨어집니다. 

 이러한 문제점때문에 핵심 기능과 공통 기능을 분리 시켜놓고, 공통 기능을 필요로 하는 핵심 기능들에서 사용하는 방식 AOP가 등장을 하게 되었습니다. 


AOP 관련 용어

  • Aspect : 공통 기능
  • Advice : Aspect의 기능 자체
    • Aspect를 공통 기능이라고 크게 묶었으면 Advice는 그 안의 세부적인, 주요 기능이라고 생각하시면 됩니다.
  • Joinpoint : Advice를 적용해야 되는 부분(ex 필드, 메소드 - 스프링에서는 메소드만 해당) 
    • 아래 그림을 보시면 핵심 기능들이 있는데 핵심 기능 하나하나를 Joinpoint라고 생각하시면 됩니다.
  • Pointcut : Joinpoint의 부분으로 실제로 Advice가 적용된 부분
  • Weaving : Advice를 핵심 기능에 적용 하는 행위





스프링에서 AOP 구현은 Proxy를 이용합니다. 예를 들어 공통 기능을 핵심 기능이 시작하기 전과 끝난 후에 사용을 하겠다고 설정을 해두었을 때 순서입니다.


공통 기능과 핵심 기능이 직접적으로 접촉하는 것이 아니라 Proxy를 통해서 수행을 해줍니다. 예제를 보시면 더 이해가 잘 가실거예요. AOP를 구현하는 방법은

 - XML 스키마 기반으로 구현

 - @Aspect 어노테이션 기반으로 구현

이렇게 두 가지가 있는데 일단 XML 스키마를 이용해서 구현을 해보겠습니다.



1. XML 기반의 AOP구현


일단 간단하게 고양이의 정보를 출력하는 클래스를 만든 후에 AOP를 적용해보도록 할게요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.aopEx;
 
public class Cats {
 
    private String name;
    private int age;
    private String color;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    public String getColor() {
        return color;
    }
 
    public void setColor(String color) {
        this.color = color;
    }
 
    public void getCatsInfo() {
        System.out.println("이름 : " + getName());
        System.out.println("나이 : " + getAge());
        System.out.println("색깔 : " + getColor());
    }
}
 
cs


 고양이의 이름, 나이, 색깔을 담을 변수와 setter(), getter()를 생성하고, 고양이의 정보를 출력해줄 getCatsInfo( ) 메서드를 생성했습니다. 이제 bean을 생성해줘야겠죠? 이전 DI관련 포스팅에서 bean생성에 관한 설명을 했으니 설명없이 코드만 보여드리고 넘어가겠습니다.


aopEx/src/main/resources/applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 
 
<bean id="myCat" class="com.aopEx.Cats">
    <property name="name" value="호랑이"/>
    <property name="age" value="1"/>
    <property name="color" value="yellow"/>
</bean>
 
</beans>
 
cs

 

생성한 bean을 가져와서 정보를 출력해줄 메인클래스를 생성 후 작성합니다.

aopEx/src/main/java/com/aopEx/MainClass.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.aopEx;
 
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
 
public class MainClass {
    public static void main(String[] args) {
        
        //bean을 설정한 xml파일이 있는 위치를 지정하여 설정파일을 얻어옴
        AbstractApplicationContext ctx = 
                new GenericXmlApplicationContext("classpath:applicationCtx.xml");
        
        //설정파일에서 bean을 가져옴
        Cats myCat = ctx.getBean("myCat",Cats.class);
        
        myCat.getCatsInfo();
    }
}
 
cs



메인 클래스를 실행하면 고양이의 정보가 나오겠죠?


이제 여기에 AOP를 적용시켜보겠습니다.


1) pom.xml파일에 의존 설정

1
2
3
4
5
6
<!-- AOP -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.7.4</version>
</dependency>
cs

pom..xml에 작성하면 관련 라이브러리가 다운로드됩니다. 잘 받아졌는지 확인해보세요.






2) 공통 기능의 클래스 제작(Advice 역할 클래스)


aopEx/src/main/java/com/aopEx/LogAop.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.aopEx;
 
import org.aspectj.lang.ProceedingJoinPoint;
 
public class LogAop {
 
    public Object loggerAop(ProceedingJoinPoint joinpoint) throws Throwable{
        
        //공통 기능이 적용되는 메서드가 어떤 메서드인지 출력하기 위해 메서드명을 얻어옴
        String signatureStr = joinpoint.getSignature().toShortString();
        System.out.println(signatureStr + "시작"); //메서드 실행
        
        //공통기능
        System.out.println("핵심 기능 전에 실행 할 공통 기능입니다. "+System.currentTimeMillis());
        
        try {
            Object obj = joinpoint.proceed(); //핵심 기능 실행
            return obj;
        } finally {
            //공통기능
            System.out.println("핵심 기능 후에 실행 할 공통 기능입니다. "+System.currentTimeMillis());
            
            System.out.println(signatureStr + "끝");
        }
    }
}
 
cs


loggerAop()메서드에서 핵심기능인 joinpoint를 매개변수로 받아옵니다. 그리고 받아온 핵심 기능(메서드명)이 무엇인지 출력해보기 위해서 getSignature() 메서드를 사용해서 signatureStr에 담아줬습니다. 공통 기능은 간단하게 시간을 출력하도록 구현했고, 아래에 joinpoint.procees() 메서드를 사용해서 핵심 기능을 실행해 준 뒤에 finally부분에서 공통 기능인 시간을 한번 더 출력을 해줍니다. 음.... 이 클래스를 위에서 설명드린 proxy라고 생각하시면 되겠네요. 



3) XML설정 파일에 Aspect 


aopEx/src/main/resources/applicationContext.xml파일의 Namespaces에서 aop를 선택합니다.


선택 후 Source를 보시면 윗부분에 aop태그를 사용할 수 있도록 되어있는 걸 확인하실 수 있습니다.


위에 공통 기능을 할 loggerAop() 메서드를 사용하기 위해서 bean으로 만들어주고, aop를 설정해줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?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.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
 
<bean id="logAop" class="com.aopEx.LogAop" />
 
<!-- AOP설정 -->
<aop:config>
    <!-- aspect id는 logger이고, logAop를 참조함 -->
    <aop:aspect id="logger" ref="logAop">
        <!-- pointcut(핵심 기능)의 id는 publicM이고, com.aopEx패키지에 있는 모든 클래스에 공통 기능을 적용 -->
        <aop:pointcut id="publicM" expression="within(com.aopEx.*)"/>
        <!-- loggerAop()라는 공통 기능을 publicM라는 pointcut에 적용 -->
        <aop:around pointcut-ref="publicM" method="loggerAop"/>
    </aop:aspect>
</aop:config>
 
 
<bean id="myCat" class="com.aopEx.Cats">
    <property name="name" value="호랑이"/>
    <property name="age" value="1"/>
    <property name="color" value="yellow"/>
</bean>
 
</beans>
 
cs



AOP설정은 이게 끝입니다. 간단하죠? 주석으로 설명으니 다른 설명없이 넘어가겠습니다.

설정이 끝났으니 다시 메인클래스를 실행해서 AOP가 잘 적용이 되었는지 예상을 한 번 해보고 확인해봅니다.



예상한대로 결과가 나왔나요? LogAop클래스에서 설정해놓은 대로 핵심 기능 시작전 실행 될 공통 기능이 실행되고, 위에서 작성해놓았던 핵심 기능(고양이의 정보 출력)이 실행, 그 뒤에 핵심 기능 후에 실행 될 공통 기능이 잘 실행되었습니다. 실제 핵심 기능에는 공통 기능을 추가하지 않고, 따로 클래스를 작성한 뒤 xml에 파일로 어떤 공통 기능을 어떤 범위에서 사용을 할건지만 설정을 해줬습니다. 이렇게 aop를 적용해서 사용하면 소스코드가 간결해지고, 나중에 유지보수를 하기에도 편하겠죠. 



**

참고 - advice 종류

<aop:before> : 메서드 실행 전에 advice실행

<aop:after-returning> : 정상적으로 메서드 실행 후에 advice실행

<aop:after-throwing> : 메서드 실행중 exception 발생 시 advice실행

<aop:after> : 메서드 실행중 exception이 발생하여도 advice실행

<aop:around> : 메서드 실행 전/후 밑 exception 발생시 advice 실행


보통 around나 before를 많이 사용한다고 하네요. 기억해 놓으셨다가 본인의 프로젝트나 기능에 맞는 advice를 사용하시면 되겠습니다. 설정 방법이 두 가지라고 했었는데 어노테이션을 사용한 AOP구현은 다음 포스팅에서 이어가겠습니다~!









반응형

댓글

💲 추천 글