본문으로 바로가기

[AOP] AOP 사용해보기

category 스프링 2020. 1. 31. 14:31

@Aspect / @Component / @Before / @After / Aspectj Expression


Java project를 만들고 옆의 사진 처럼 패키지와 클래스를 만들었다.

[PenAspect.java] Aspect 메소드가 있는 클래스

[Mainclass] main() 메소드가 있는 클래스

[init.xml] Spring으로 객체를 생성해서 관리할 클래스들을 정의하는 문서

[WritingUtil] main()메소드 수행시 실행될 메소드가 있는 클래스

 

 

[WritingUtil]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package test.mypac;
 
import org.springframework.stereotype.Component;
 
@Component
public class WritingUtil {
    public void write1() {
        System.out.println("편지를 써요");
    }
    
    public void write2() {
        System.out.println("일기를 써요");
    }
    
    public void write3() {
        System.out.println("소설을 써요");
    }
}
 
cs

 

메인 메소드가 실행될 때 사용될 메소드들을 모아 놓은 클래스 이다.

5행 : 메소드를 사용하기 위해서는 WritingUtil 의 객체가 필요하다.

       Component scan시 bean으로 만들어 주기 위 해 @Component 어노테이션을 작성해주었다.

 

[init.xml]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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:context="http://www.springframework.org/schema/context"
    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/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
    
    <!-- 컴포넌트 스캔을 해서 bean 으로 만들 객체는 만들어 준다. -->
    <!-- 패키지 하위까지 모두 스캔하고, bean을 만든다. -->
    <context:component-scan base-package="test.mypac" />
    <context:component-scan base-package="test.aspect" />
    <aop:aspectj-autoproxy />
</beans>
 
cs

12, 13 행 : base-package 속성의 값으로 명시한 패키지에 있는 클래스들을 스캔해서 bean으로 만들으라고 명시함.

              어노테이션(@)으로 표시 해준 클래스들은 bean으로 만들라고 명시.

14 행 : <aop:aspectj-autoproxy /> 선언

AspectJ를 위한 태그이며, 먼저 Spring AOP 때 이용한 ProxyFactoryBean에 해당하는 것을 자동으로 생성하는 태그이다. 이를 기술하게 되면 ProxyFactoryBean으로 준비된 기능이 자동으로 포함된다.

 

[PenAspect Class]

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

package test.aspect;

 

import org.aspectj.lang.annotation.After;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.springframework.stereotype.Component;

 

@Aspect

@Component

public class PenAspect {

 

    @Before("execution(void write*())")

    public void prepare() {

        System.out.println("Pen을 준비해요!");

    }

    

    @After("execution(void write*())")

    public void after() {

        System.out.println("Pen을 마무리해요!");

    }

    

}

Colored by Color Scripter

cs

8행 : Aspect 역할을 할 수 있도록 정의해준다.

9행 : 객체를 생성해야 해당 메소드들을 사용할 수 있으므로 init.xml 문서에서

       Component scan시 bean이 될 수 있도록 명시해준다.

12행 : @Before [Mainclass] 의 main() 메소드가 수행되기 전에 실행될 메소드라는 것을 명시해준다.

        ("execution(void write*())")의 의미는 

          (1) spring 이 관리 하는 객체의 메소드 중에서 리턴 type 은 void - void

          (2) 메소드 명은 write로 시작 - write*

          (3) 전달되는 인자가 없는 - ( )
        이다.

17행 : @After [Mainclass] 의 main() 메소드가 수행된 후에 실행될 메소드라는 것을 명시해준다.

 

[Mainclass]

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

package test.main;

 

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

 

import test.mypac.WritingUtil;

 

public class Mainclass {

    public static void main(String[] args) {

        ApplicationContext context=new ClassPathXmlApplicationContext("test/main/init.xml");

        WritingUtil util2=context.getBean(WritingUtil.class);

        util2.write1();

        util2.write2();

        util2.write3();

    }

}

Colored by Color Scripter

cs

10행 : test/main/init.xml 문서를 읽으면서 Spring이 bean을 생성하도록 작성

11행 : spring bean context로  부터 'WritingUtil' type을 찾아서 해당 객체의 참조값을 리턴해준다

12-14행 : 객체의 참조값을 사용해서 메소드 사용

 

 

Run 해서 console을 찍어 보면 아래와 같이 결과가 표시 된다.

Run을 하면 [WritingUtil] [PenAspect]를 합쳐서 aop가 적용된 새로운 클래스를 만든다.

[Mainclass]에서는 새로만들어진 클래스의 메소드를 사용한다.

 

따라서 왼쪽과 같이 콘솔에 찍히게 되는 것이다.

 

 

 

 

 

 

 

 

 

@Around / Aspectj Expression


위의 예제를 위해 만들었던 java project 클래스를 추가하였다.

[MessengerAspect] Aspect 메소드가 있는 클래스

[Mainclass2] main() 메소드가 있는 클래스

[Mainclass3] main() 메소드가 있는 클래스

[init.xml] Spring으로 객체를 생성해서 관리할 클래스들을 정의하는 문서

[Messenger] main()메소드 수행시 실행될 메소드가 있는 클래스

 

 

 

 

 

 

[Messenger]

1

2

3

4

5

6

7

8

9

10

11

12

13

14

package test.mypac;

 

import org.springframework.stereotype.Component;

 

@Component

public class Messenger {

    public void sendGreeting(String msg) {

        System.out.println("오늘의 인사 : "+msg);

    }

    

    public String getMessage() {

        return "힘을 내자!";

    }

}

Colored by Color Scripter

cs

메인 메소드가 실행될 때 사용될 메소드들을 모아 놓은 클래스 이다.

5행 : 메소드를 사용하기 위해서는 WritingUtil 의 객체가 필요하다.

       Component scan시 bean으로 만들어 주기 위 해 @Component 어노테이션을 작성해주었다.

6-9행 : String type의 인자가 오면 그 문자열을 콘솔에 출력하는 메소드

11-13행 : 메소드를 호출하면 String type의 문자열을 리턴하는 메소드

 

 

[init.xml]

1

2

3

4

5

6

7

8

9

10

11

12

<?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:context="http://www.springframework.org/schema/context"

    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/context http://www.springframework.org/schema/context/spring-context-4.0.xsd

        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">

    

    <context:component-scan base-package="test.aspect" />

    <aop:aspectj-autoproxy />

</beans>

Colored by Color Scripter

cs

10 행 : base-package 속성의 값으로 명시한 패키지에 있는 클래스들을 스캔해서 bean으로 만들으라고 명시함.

              어노테이션(@)으로 표시 해준 클래스들은 bean으로 만들라고 명시.

11 행 : <aop:aspectj-autoproxy /> 선언

AspectJ를 위한 태그이며, 먼저 Spring AOP 때 이용한 ProxyFactoryBean에 해당하는 것을 자동으로 생성하는 태그이다. 이를 기술하게 되면 ProxyFactoryBean으로 준비된 기능이 자동으로 포함된다.

 

 

[MessengerAspect]

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

40

41

42

43

44

45

package test.aspect;

 

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.springframework.stereotype.Component;

 

@Aspect

@Component

public class MessengerAspect {

 

    @Around("execution(* send*(..))")

    public void around(ProceedingJoinPoint joinPoint) throws Throwable {

        System.out.println("---수행이전---");

    

        Object[] args=joinPoint.getArgs();

        //반복문 돌면서 찾고 싶은 type을 찾는다.

        for(Object tmp:args) {

            if(tmp instanceof String) {    

                String msg=(String)tmp;

                System.out.println("aop 에서 읽어낸 내용 : "+msg);

                // 특정 메서드는 호출하지 않기

                if(msg.contains("바보")) {

                    System.out.println("바보라고 하기 없기!");

                    return;

                }

            }

        }

        

        Object obj=joinPoint.proceed();

    

        System.out.println("---수행직후---");

    }

    

    

    @Around("execution(String getMessage())")

    public Object around2(ProceedingJoinPoint joinPoint) throws Throwable {

        //aop가 적용된 메소드를 수행하고 리턴되는 값을 얻어낸

        Object obj=joinPoint.proceed();

        

        obj="공부 안 해!";

        

        return obj;

    }

}

Colored by Color Scripter

cs

8행 : Aspect 역할을 할 수 있도록 정의해준다.

9행 : 객체를 생성해야 해당 메소드들을 사용할 수 있으므로 init.xml 문서에서

       Component scan시 bean이 될 수 있도록 명시해준다.

12행 : @Around 는 메서드의 실행 전/후에 공통로직을 적용하고 싶을 때 사용한다.
         ("execution(* send*(..))")의 의미는
          (1) spring 이 관리 하는 객체의 메소드 중에서 리턴 type 상관 없이 - *

          (2) 메소드 명은 send로 시작 - send*

          (3) 전달되는 메소드 인자 상관 없음 - (..)
        이다.

14행 : 매소드가 수행되기 전에 콘솔에 출력될 내용

16행 : ProceedingJoinPoint 객체를 사용하여 getArgs()메서드를 호출하면

        ("execution(* send*(..))") 를 충족하는 메소드 호출시 전달된 인자들을 모두 Object type으로 배열에 담는다.

 

16-28행 : 반복문을 돌면서 배열을 살펴본다.

            이때, instanceof 예약어를 사용해서 배열에 있는 data들 중 원래 type이 String인를 data 찾고, casting 해준다. 

            만약, 전달된 인자들 중 "바보"라는 문자열이 있으면 메소드 실행을 종료한다.

30행 : proceed()를 사용해서 aop가 적용된 메소드 수행하고 리턴되는 값 받아온다.(void 면 null 이다.) 
32행 : 매소드가 수행된 후에 콘솔에 출력될 내용

41행 : proceed()를 사용해서 리턴 되는 결과값을 수정한다.

 

 

[Mainclass2]

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

package test.main;

 

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

 

import test.mypac.Messenger;

 

public class MainClass2 {

    public static void main(String[] args) {

        ApplicationContext context=new ClassPathXmlApplicationContext("test/main/init.xml");

        Messenger m=context.getBean(Messenger.class);

        m.sendGreeting("좋은 아침!");

        m.sendGreeting("바보야!좋은 아침!");

    }

}

 

Colored by Color Scripter

cs

10행 : test/main/init.xml 문서를 읽으면서 Spring이 bean을 생성하도록 작성

11행 : spring bean container로  부터 'Messenger' type을 찾아서 해당 객체의 참조값을 리턴해준다

12-13행 : 객체의 참조값을 사용해서 메소드 사용

 

Run 해서 console을 찍어 보면 아래와 같이 결과가 표시 된다.

Run을 하면 [Messenger] [MessengerAspect]를 합쳐서 aop가 적용된 새로운 클래스를 만든다.

[Mainclass2]에서는 새로만들어진 클래스의 메소드를 사용한다.

sendGreeting("좋은 아침!");

인자로 "좋은 아침!" 이라는 문자열을 보낸경우,

Object[]에 있는 data들 중 원래 type이 String인를 data 찾아서 console에 출력하고, 

proceed( ) 메소드를 사용하여 aop가 적용된 메소드 수행하고 리턴되는 값 받아온다.

 

sendGreeting("바보야!좋은 아침!");

인자로 "바보야!좋은 아침!" 이라는 문자열을 보낸경우,

Object[]에 있는 data들 중 원래 type이 String인를 data 찾아서 console에 출력하였다. 

 

문자열 중에 "바보"가 포함되어 있었으므로 "바보라고 하기 없기!" 문자열을 출력한 뒤 메소드가 종료되었다.

 

 

 

[Mainclass3]

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

package test.main;

 

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

 

import test.mypac.Messenger;

 

public class MainClass3 {

    public static void main(String[] args) {

        ApplicationContext context=new ClassPathXmlApplicationContext("test/main/init.xml");

        Messenger m=context.getBean(Messenger.class);

        

        String result=m.getMessage();

        

        System.out.println("result : "+result);

    }

}

Colored by Color Scripter

cs

10행 : test/main/init.xml 문서를 읽으면서 Spring이 bean을 생성하도록 작성

11행 : spring bean container로  부터 'Messenger' type을 찾아서 해당 객체의 참조값을 리턴해준다

13행 : 객체의 참조값을 사용해서 메소드 사용

15행 : 수행한 결과를 콘솔에 출력.

 

Run 해서 console을 찍어 보면 아래와 같이 결과가 표시 된다.

Run을 하면 [Messenger] [MessengerAspect]를 합쳐서 aop가 적용된 새로운 클래스를 만든다.

[Mainclass3]에서는 새로만들어진 클래스의 메소드를 사용한다.

 

 콘솔창을 보면 getMessage()메소드에서 리턴되는 "힘을 내자!" 대신  "공부 안 해! 라는 문자열이 찍힌것을 확인할 수 있다. 그 이유는 [MessengerAspect] 클래스의 41번행 때문이다.

 

 

41번행을 삭제하면 위의 결과가 나온다.