ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • AOP소개
    Spring_FrameWork 2019. 3. 19. 19:52
    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
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
            http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>sp4</groupId>
        <artifactId>sp4-chap07</artifactId>
        <version>0.0.1-SNAPSHOT</version>
     
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>4.1.0.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.8.2</version>
            </dependency>
        </dependencies>
     
        <build>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <source>1.7</source>
                        <target>1.7</target>
                        <encoding>utf-8</encoding>
                    </configuration>
                </plugin>
            </plugins>
        </build>
     
    </project>
    cs

    aspectjwever 의존을 추가한다,

    이 모듈은 스프링이 AOP 를 구현할때 사용하는 모듈이다.


    다음 코드를 작성


    1
    2
    3
    4
    5
    6
    package chap07;
     
    public interface Calculator {
        public long factorial(long num);
    }
     
    cs

    for문을 이용해서 팩토리얼을 구하는 클래스

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package chap07;
     
    public class ImpeCalculator implements Calculator{
     
        @Override
        public long factorial(long num) {
            long result = 1;
            for(int i = 1 ; i<=num ; i++) {
                result*=i;
            }
            return result;
        }
        
    }
     
    cs

    재귀호출을 이용해서 펙토리얼 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package chap07;
     
    public class RecCalculator implements Calculator{
     
        @Override
        public long factorial(long num) {
            if(num==0)
                return 1;
            else
            return num*factorial(num-1);
        }
        
    }
     
    cs


    02 프록시와 AOP

    앞에서 구현한 두 클래스의 구현 클래스의 실행 시간을 출력하고 싶다고 치자
    쉬운방법은 시작과 끝 시간을 구하고 출력하는 것이다.

    메서드 실행 전후에 값을 구하는 방법도 있다.

    기존 코드를 수정하지 않으면서 , 코드 중복을 하지 않는 방법은 없을까?
    이때 출현하는 것이 프록시 객체이다. 



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package chap07;
     
    public class ExeTimeCalculator implements Calculator{
        
        private Calculator delegate;
     
      public ExeTimeCalculator(Calculator delegate) { this.delegate = delegate; }
        
        @Override
        public long factorial(long num) {
            long start = System.nanoTime();
            long result = delegate.factorial(num);
            long end = System.nanoTime();
            System.out.printf("%s factorial(%d) 실행시간 = %d\n",delegate.getClass().getSimpleName(),num,(end-start));
            return result;
        }
        
        
    }
     
    cs

    ExeTimeCalculator 클래스는 Calculator 인터페이스를 구현하고 있다.

    이 클래스의 특이점은 생성자를 통해 다른 Culculaotr 객체를 전달받아 delegate 필드에 할당한뒤

    15행 코드처럼 delegate.factorial() 메서드를 실행한다는 점임

    그리고 delegate.factorial()의 코드를 실행하기 전후 14,16에 현재시간을 구해서 delegate factorail의 실행시간을 구해서 출력한다.

    ExeTimeCalculator 클래스를 사용하면 다음과 같은 방법으로 ImpeCalculaotr클래스의 실행시간을 측정할 수 있다.


    ImpeCalculaotor impeCal = new ImpeCalculaotr();

    ExeTimeCalculator calculator = new ExeTimeCalculator(impeCal);

    long result = calculator.factorial(4)


    위의 코드의 실행순서는 다음과 같다 .


    실행흐름을 보면 impeCalculator의 factorial() 메서드의 실행 기간을 구해서 콘솔에 출력하게 된다.

    확인해보기위해 아래 코드를 실행

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package main;
     
    import chap07.ExeTimeCalculator;
    import chap07.ImpeCalculator;
     
    public class MainProxy {
        
        public static void main(String[] args) {
            ExeTimeCalculator ttCal1 = new ExeTimeCalculator(new ImpeCalculator());
            System.out.println(ttCal1.factorial(20));
            
            ExeTimeCalculator ttCal2 = new ExeTimeCalculator(new ImpeCalculator());
            System.out.println(ttCal2.factorial(20));
            
        }
        
    }
     
    cs


    위 결과로 알 수있는 점은 다음과 같다 .

    - 기존 코드를 변경하지 않고 실행 시간을 출력할 수있다.  즉 ImpeCalculator 클래스나 RecCalculator 클래스의 코드를 변경하지 않고 , 이 두 클래스의    factorial() 메서드 실행시간을 출력할 수 있게 되었다.


    - 실행시간을 구하는 코드의 중복을 제거 했다. 만약 나노초 대신에 밀리초를 사용해서 실행시간을 구하고 싶다면 ExeTimeCalulator 클래스의 코드만 변경하면 된다.


    여기서 두가지가 가능한 이유는 ExeTimeCalculaotr 클래스를 다음과 같이 구현 했기 때문이다.


    - factorial() 기능 자체를 구현하기 보다는, 다른 객체에 factorial() 의 실행을 위임한다.

    - 계산 기능 외에 다른 부가적인 기능을 시행한다. 여기서 부가적인 기능은 실행 시간 측정 기능이다.


    이렇게 핵심 기능의 실행은 다른 객체에 위임하고 부가적인 기능을 제공하는 개체를 프록시(proxy)라고 부르고, 실제 핵심기능을

    실행하는 객체를 대상 객체라고 부른다. 

    위 의경우 ImpeCalculator 객체가 프록시의 대상 객체가 된다.


    위의 코드의 경우 프록시라기보다는 데코레이터 객체에 가깝다. 프록시는 접근 제어 관점에 초점이 맞춰져 있다면

    데코레이터는 기능 추가와 확장에 초점이 맞춰져 있기 때문이다.

    위 예의 경우에는 기존 기능에 시간 측정 기능을 추가하고 있기 때문에 데코레이터에 가깝지만 , 스프링의

    레퍼런스 문서에서 AOP를 설명할 때 프록시라는 용어를 사용하기 때문에 프록시라는 용어를 사용한다.


    프록시의 특징은 핵심 기능을 구현하지 않는다는 점이다. ImpeCalculator 나 RecCalculator는 팩토리얼 연산이라는 핵심기능을

    구현 하고 있다. 반면에 ExeTimeCalculator 클래스의 경우는 팩토리얼 연산자체를 구현하고 있지 않다.


    프록시는 핵심기능 구현하지 않고 여러 객체에 공통ㅌ으로 적용될수 있는 기능을 구현한다.


    정리

    핵심기능을 구현하고 있는 클래스와

    공통기능을 구현하고 있는 클래스 로 나뉨

    공통구현기능과 핵심 기능 구현을 분리하는 것이 AOP의 핵심이다.


    AOP


    AOP 는 Aspect Oriented Programing의 약자로, 여러 객체에 공통으로 적용할 수잇는 기능을 구분함으로써 재사용성을 
    높여주는 프로그래밍 기법이다. AOP는 핵심 기능과 공통 기능의 구현을 분리함으로써 핵심 기능을 구현한 코드의 수정없이
    공통기능을 적용할 수 있게 만들어 준다.


    AOP의 기본개념은 핵심기능에 공통기능을 삽입하는 것이다. 
    핵심기능의 코드를 수정하지 않음녀서 공통기능의 구현을 추가하는 것이 AOP 이다.
    핵심 기능에 공통 기능을 삽입하기 위해한 방법에는 크게 다음 세가지가 있다.

    - 컴파일 시점에 코드에 공통기능을 추가하는 방법
    - 클래스로딩시점에 바이트코드에 공통기능을 추가하는 방법
    - 런타임에 프록시 객체를 생성해서 공통기능을 추가하는 방법

    첫번째 방법은 AOP 개발도구가 소스코드를 컴파일 하기 전에 공통 구현 코드를 소스에 삽입하는 방식으로 동작하고
    두번째 방법은 클래스를 로딩할 때 바이트코드에 공통기능을 클래스에 삽입하는 방식으로 동작한다.

    이 두방법은 AspectJ와 같이 AOP 를 위한 전용도구를 사용해서 적용할 수 있다.

    스프링이 제공하는 AOP 방식은 프록시를 이용하는 세번째 방식이다. 
    스프링이 두 번째 방식을 일부 지원하고 있지만, 널리 사용되는 방법은 프록시를 이용하는 세번째 방식이다.

    프록시를 이용하는 방식은 앞서 살펴본 것처럼 중간에 프록시 객체를 생성한다.



    스프링 AOP는 프록시 객체를 자동으로 만들어준다.

    상위 타입의 인터페이스를 상속받은 프록시 클래스를 직접 구현할 필요가 없다.


    공통기능을 Aspect라고 하는 데 

    이외에 알아두어야 할 용어를 정리

     용어

    의미 

    JointPoint 

    Advice를 적용 가능한 지점을 의미. 메서드의 호출 , 필드값의 변경등이 JointPoint에 해당한다. 

    스프링은 프록시를 이용해서 AOP를 구현하기 때문에 메서드 호출에대한 JointPoint만 지원한다. 

    PointCut

    JointPoint의 부분 집합으로 실제로 Advice가 적용되는 JointPoint 

    스프링에서는 정규 표현식이나 AspectJ의 문법을 이용하여 PointCut을 정의할 수 있다. 

    Advice 

    언제 공통 관심 기능을 핵심로직에 적용할 지를 정의하고 있다. 

    예를 들어 메서드를 호출하기 전(언제) 트랜젝션 시작(공통기능)  기능을 적용한다는 것을 정의하고 있다.

    Weaving

    Advice를 핵심 로직 코드에 적용하는 것을 weaving이라고 한다. 

    Aspect

    여러 객체에 공통으로 적용되는 기능을 Aspect라고 한다. 트랜잭션이나 보안등이 Aspect의 좋은 예 이다. 


    Advice의 종류

    스프링은 프록시를 이용해서 메서드 호출 시점에 Aspect를 적용하기 때문에 구현 가능한 Advice의 종류는 다음과 같다.


    • Before : 메서드 호출 전
    • After Returning : 메서드가 예외없이 실행된 이 후
    • After Throwing : 메서드 예외가 발생했을 때 이 후
    • After : 예외 여부 상관없이 메서드 호출 후
    • Around : 메서드의 실행 전과 후, 예외 발생 시점 전부 (가장 범용적)

    03 스프링 AOP 구현


    스프링 AOP를 이용해서 공통기능을 구현하고 적용하는 방법은 단순하다.

    1. 공통 기능을 제공하는 Aspect를 구현 
    2. Aspect를 어디(PointCut)에 적용할지 설정한다. 즉 Advice를 설정한다.

    Aspect를 구현하는 방법은 두가지
    1. XML 스키마 기반의 자바 POJO 클래스를 이용하는 방법과 
    2. @Aspect 애노테이션을 이용하는 방법 두가지 


    3.1 AOP구현 : XML 스키마 기반

    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
    package aspect;
     
     
     
    import java.util.Arrays;
     
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.Signature;
     
    public class ExeTimeAspect {
        public Object measure(ProceedingJoinPoint joinPoint) throws Throwable{
            long start = System.nanoTime();
            try {
                Object result = joinPoint.proceed();
                return result;    
            }finally {
                long finish = System.nanoTime();
                Signature sig = joinPoint.getSignature();
                System.out.printf("%s.%s(%s) 실행시간 : %d ns\n",
                joinPoint.getTarget().getClass().getSimpleName(),
                sig.getName(),Arrays.toString(joinPoint.getArgs())
                ,finish-start);
            }
        }
    }
     
    cs

    이제 Aspect를 구현했으므로 적용할 위치를 설정한다.

    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:aop="http://www.springframework.org/schema/aop"
        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
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
     
        <!-- 공통 기능을 제공할 클래스를 빈으로 등록 -->
        <bean id="exeTimeAspect" class="aspect.ExeTimeAspect" />
     
        <!-- Aspect 설정: Advice를 어떤 Pointcut에 적용할 지 설정 -->
        <aop:config>
            <aop:aspect id="measureAspect" ref="exeTimeAspect">
                <aop:pointcut id="publicMethod"
                    expression="execution(public * chap07..*(..))" />
                <aop:around pointcut-ref="publicMethod" method="measure" />
            </aop:aspect>
        </aop:config>
     
        <bean id="impeCal" class="chap07.ImpeCalculator">
        </bean>
     
        <bean id="recCal" class="chap07.RecCalculator">
        </bean>
     
    </beans>
    cs

    17~18 : 이름이 publicMethod 인 PointCut을 정의한다. 이 Pointcut의 범위는 chap07 패키지 및 그 하위 패키지에 있는 모든 public 메서드가 된다.

    19행 : publicMethod Pointcut에 공통 기능 "measure" 메서드를 적용하는 Around Advice를 정의한다. 즉 16행의 ref 속성에서 정의한 exeTimeAspect 빈객체의 measure() 메서드를 이용해서 공통기능으로 사용한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package main;
     
    import org.springframework.context.support.GenericXmlApplicationContext;
     
    import chap07.Calculator;
     
    public class MainXmlPojo {
        
        public static void main(String[] args) {
            GenericXmlApplicationContext ctx =
                    new GenericXmlApplicationContext("classpath:aopPojo.xml");
     
            Calculator impeCal = ctx.getBean("impeCal", Calculator.class);
            long fiveFact1 = impeCal.factorial(5);
            System.out.println("impeCal.factorial(5) = " + fiveFact1);
            
            Calculator recCal = ctx.getBean("recCal", Calculator.class);
            long fiveFact2 = recCal.factorial(5);
            System.out.println("recCal.factorial(5) = " + fiveFact2);
        }
     
    }
     
    cs


    실행결과

    1
    2
    3
    4
    5
    6
    7
    8
    9
    3월 21, 2019 9:47:34 오전 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
    정보: Loading XML bean definitions from class path resource [aopPojo.xml]
    3월 21, 2019 9:47:35 오전 org.springframework.context.support.GenericXmlApplicationContext prepareRefresh
    정보: Refreshing org.springframework.context.support.GenericXmlApplicationContext@cc34f4d: startup date [Thu Mar 21 09:47:35 KST 2019]; root of context hierarchy
    ImpeCalculator.factorial([5]) 실행시간 : 32456 ns
    impeCal.factorial(5) = 120
    RecCalculator.factorial([5]) 실행시간 : 10575 ns
    recCal.factorial(5) = 120
     
    cs


    AOP : 구현 @Aspect 애노테이션 이용

    POJO방식과 크게 다르지 않지만 
    @Aspect 애노테이션을 적용한 크래스에 공통기느오가 PonintCut을 설정한다는 점이다.
    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
    package aspect;
    import java.util.Arrays;
     
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
     
    @Aspect
    public class ExeTimeAspect2 {
        
        @Pointcut("execution(public * chap07..*(..))")
        private void publicTarget() {
            
        }
        @Around("publicTarget()")
        public Object measure(ProceedingJoinPoint joinPoint) throws Throwable{
            long start = System.nanoTime();
            try {
                Object result = joinPoint.proceed();
                return result;
            }finally {
                long finish = System.nanoTime();
                Signature sig = joinPoint.getSignature();
                System.out.printf("%s.%s(%s) 실행시간 : %d ns\n",
                        joinPoint.getTarget().getClass().getSimpleName(),
                        sig.getName(),Arrays.toString(joinPoint.getArgs())
                        ,finish-start);
                
            }
            
        }
    }
     
    cs

    앞서 구현한 코드와 비슷하다.

    차이점


    1. 클래스에 @Aspect 어노테이션을 붙이다.

    2. 13행에 @Pointcut 어노테이션으로 Pointcut을 설정했다.

    3. 17행에 @Around 어노테이션을 사용해서 메서드가 Around Advice로 사용된다고 설정했다.


    어노테이션을 사용하면 한번에 Pointcut과 Advice 설정 그리고 Aspect 구현을 함께 제공한다.

    17~30행의 메서드는 Aspect가 제공할 공통 기능의 구현에 해당한다.

    이 공통 기능을 언제 어디에 적용할지 여부는 메서드에 붙은 @Around 애노테이션을 이용해서 설정하고 있다.


    @Around 애노테이션은 AroundAdvice를 지정할 때 사용됨 

    @Around의 설정값으로는@Pointcut으로 설정한 메서드의 이름을 사용한다.


      @Pointcut("execution(public * chap07.*(..))")
        private void publicTarget() {
            
        }
        @Around("publicTarget()")
        public Object measure(ProceedingJoinPoint joinPoint) throws Throwable{
           

    위 코드는 다음과 같은 의미를 갖는다.

    - Around Advice 의 PointCut 설정의로 "publicTarget()"메서드에 정의한 값을 사용

    - 해당 Pointcut에 적용할 공통기능으로 @Around 애노테이션을 적용한 measure() 메서드를 사용 

    @Aspect 애노테이션을 이용해서 공통으로 적용할 기능을 구현한 경우 , XML 설정에서 이를 인식 할 수 있도록 <aop:aspectj-autoproxy/> 태그를 추가해주어야한다.


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <?xml version="1.0" encoding="UTF-8"?>
     
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:aop="http://www.springframework.org/schema/aop"
        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
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
     
        <aop:aspectj-autoproxy />
        
        <bean id="exeTimeAspect" class="aspect.ExeTimeAspect2" />
     
        <bean id="impeCal" class="chap07.ImpeCalculator">
        </bean>
     
        <bean id="recCal" class="chap07.RecCalculator">
        </bean>
     
    </beans>
    cs

    <aop:aspectj-autoproxy> 태그를 사용하면 @Aspect 애노테이션이 적용된 빈객체를 Advice로 사용한다.

    exeTimeAspect 빈을  Advice로 사용해서 공통기능을 지정한 빈 객체에 적용하게 된다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package main;
     
    import org.springframework.context.support.GenericXmlApplicationContext;
     
    import chap07.Calculator;
     
    public class MainXmlAspect {
     
        public static void main(String[] args) {
            GenericXmlApplicationContext ctx =
                    new GenericXmlApplicationContext("classpath:aopAspect.xml");
     
            Calculator impeCal = ctx.getBean("impeCal", Calculator.class);
            long fiveFact = impeCal.factorial(5);
            System.out.println("impeCal.factorial(5) = " + fiveFact);
        }
    }
    cs

    3월 21, 2019 1:01:36 오후 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions

    정보: Loading XML bean definitions from class path resource [aopAspect.xml]

    3월 21, 2019 1:01:36 오후 org.springframework.context.support.GenericXmlApplicationContext prepareRefresh

    정보: Refreshing org.springframework.context.support.GenericXmlApplicationContext@cc34f4d: startup date [Thu Mar 21 13:01:36 KST 2019]; root of context hierarchy

    ImpeCalculator.factorial([5]) 실행시간 : 25892 ns

    impeCal.factorial(5) = 120


    ExeTimeAspect2 클래스의 공통 기능인 measure() 메서드가 impeCal  빈 객체에 적용된 것을 확인 할 수 있다.

    ProceedingJointPoint의 메서드

    Around Advice에서 사용할 공통 기능 메서드는 대부분 파라미터로 전달 받은 ProceedingJoinPoint의 proceed() 메서드만 호출하면 된다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class ExeTimeAspect {
        public Object measure(ProceedingJoinPoint joinPoint) throws Throwable{
            long start = System.nanoTime();
            try {
                Object result = joinPoint.proceed();
                return result;    
            }finally {
                long finish = System.nanoTime();
                Signature sig = joinPoint.getSignature();
                System.out.printf("%s.%s(%s) 실행시간 : %d ns\n",
                joinPoint.getTarget().getClass().getSimpleName(),
                sig.getName(),Arrays.toString(joinPoint.getArgs())
                ,finish-start);
            }        
        }
    }
     
    cs

    개발도중 호출되는 대상 객체에 대한 정보, 실행되는 메서드에 대한 정보,

     메서드를 호출할 때 전달된 인자에 대한 정보가필요할 때가 있다. 

    이들 정보에 접근할 수 있도록 ProceedingJoinPoint 인터페이스는 다음의 메서드를 제공하고 있다.


    ProceedingJoinPoint 인터페이스의 제공 메서드

     메서드

     설명 

     Signature getSignature() 

     호출되는 메서드에 대한 정보를 구한다. 

     Object getTarget() 

     대상 객체를 구한다. 

     Object[] getArgs() 

     파라미터의 목록을 구한다. 


    org.aspectj.lang.Signature 인터페이스는 호출되는 메서드와 관련된 정보를 제공하며 다음과 같은 메서드를 정의하고 잇다.

      메서드

     설명 

     String getName 

     메서드의 이름을 구한다. 

     String toLongString() 

     메서드를 완전하게 표현한 문장을 구한다. (메서드의 리턴 타입, 파라미터 타입이 모두 표시된다.) 

     String toShortString() 

     메서드를 축약해서 표현한 문장을 구한다. (기본 구현은 메서드의 이름만을 구한다. 




    프록시 생성 방식

    MainXmlPojo 클래스의 코드를 다음과 같이 변경한다.

    수정전

    1
    Calculator impeCal = ctx.getBean("impeCal", Calculator.class);
    cs

    수정후

    1
    Calculator impeCal = ctx.getBean("impeCal", ImpeCalculator.class);
    cs


    변경된것은 Calculator 타입대신 ImpeCalculator 타입을 사용했다.

    impeCal 빈을 생성할때 사용한 타입이므로 문제가 없어 보인다.

    1
    2
    3
    4
     
        <bean id="impeCal" class="chap07.ImpeCalculator">
        </bean>
     
    cs

    코드수정후 MainXmlPojo 클래스를 실행하면 다음과 같은 익셉션이 발생한다.

    1
    Exception in thread "main" org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'impeCal' must be of type [chap07.ImpeCalculator], but was actually of type [com.sun.proxy.$Proxy2
    cs


    메세지를 보면 getBea() 메서드에 사용한 타입이 ImpeCalculator 인데 반해 실제타입은 $Proxy2 라는 메시지가 나온다. 

    $Poxy2 클래스는 스프링 런타입에 생성한 프록시 객체의 클래스 이름인다.

    이 $Proxy2 클래스는 ImpeCalculaotor 클래스가 상속받은 Calculator 인터페이스를 상속받게 된다.

    빈이 인터페이스를 상속받는 경우 인터페이스를이용해서 프록시를 생성한다


    생성하는 빈객체가 인터페이스를 상속하고 있으면 인터페이스를 이용해서 프록시를 생성한다.

    설정, AOP 적용시 ImpeCalculator가 상속받은 Calculator 인터페이스를 이용해서 프록시를 생성

    1
    2
    3
    4
     
        <bean id="impeCal" class="chap07.ImpeCalculator">
        </bean>
     
    cs


    //자바코드 impeCal 프록시 객체의 실제 타입은 Calculator를 상속 받았으므로 ImpeCalculator로 타입 변환이 불가능 그러므로 익셉션 발생

    1
    Calculator impeCal = ctx.getBean("impeCal", ImpeCalculator.class);
    cs

    빈객체가 인터페이스 상속 받고 있을때 인터페이스가 아닌 클래스를 이용해서 프록시를 생성하고 싶다면 다음과 같이 설정하면된다. 


    POJO방식 XML 설정

    1
    2
    3
    4
    5
    6
    7
    8
        <aop:config proxy-target-class="true">
            <aop:aspect id="measureAspect" ref="exeTimeAspect">
                <aop:pointcut id="publicMethod"
                    expression="execution(public * chap07..*(..))" />
                <aop:around pointcut-ref="publicMethod" method="measure" />
            </aop:aspect>
        </aop:config>
     
    cs

    @Aspect방식 XML 설정

    1
        <aop:aspectj-autoproxy proxy-target-class="true" />
    cs


    이렇게하면 프록시를 생성할때 인터펭디스가 아닌 자바클래스를 상속받아 생성한다. 


    execution 명시자 표현식


    Aspect를 적용할때 위치를 지정한 Pointcut 설정을 보면 다음과같이 execution 명시자를 사용했다.
    1
    2
    3
    4
    5
        
        @Pointcut("execution(public * chap07..*(..))")
        private void publicTarget() {
            
        }
    cs

    execution 명식자는 Advice를 적용할 메서드를 지정할 때 사용되며 , 기본형식은 다음과 같다.

    execution(수식어패턴? 리턴타입패턴 클래스이름패턴?메서드이름패턴(파라미터 패턴)


    수식어는 생략가능 public protected 등이 온다.

    스프링 AOP경우 public 메서드만 적용가능하기 때문에 public이외의 값은 의미가 없다.


    리턴타입패턴 부분은 리턴 타임을 명식한다. 

    클래스이름패턴과 이름패턴 부분은 클래스 이름및 메서드 이름을 패턴으로명시한다. 

    파라미터 패턴을 매칭될 파라미터에 대해 명시한다.


    각패턴은 * 을 이용해서 모든 값을 표현할수 있다. '..'을 이용하여 0개 이상이라는 의미를 표현할 수있다.


    Examples.

    execution( public void set*(..) )

    수식어패턴 : public

    리턴타입 : void

    클래스이름 : 생략됨

    메서드이름 : setter (set으로 시작하는 메서드)

    파라미터패턴 : 파라미터가 0개 이상 '(..)'


    execution( * chap07.*.*() )

    수식어패턴 : 생략됨

    리턴타입 : *   ← 전부

    클래스이름 : chap07.*  ← chap07패키지에 있는 모든 클래스. chap07 하위패키지까지는 아님. (chap07 : 패키지명.)

    메서드이름 : *   ← 전부

    파라미터패턴 : 공란  ← 없음


    execution( * chap07..*.*(..) )

    수식어패턴 : 생략됨

    리턴타입 : *   ← 전부

    클래스이름 : chap07..*  ← chap07패키지 및 해당 패키지 하위의 모든 클래스 (chap07 : 패키지명.)

    메서드이름 : *   ← 전부

    파라미터패턴 : (..)  ← 0개 이상


    execution( Long chap07.Calculator.factorial(..) )

    수식어패턴 : 생략됨

    리턴타입 : Long

    클래스이름 : chap07.Calculator

    메서드이름 : factorial

    파라미터패턴 : (..)  ← 0개 이상


    execution( * get*(*) )

    수식어패턴 : 생략됨

    리턴타입 : *   ← 전부

    클래스이름 : 생략됨

    메서드이름 : getter (get으로 시작하는 메서드)

    파라미터패턴 : *  ← 1개


    execution( * get*(*, *) )

    수식어패턴 : 생략됨

    리턴타입 : *   ← 전부

    클래스이름 : 생략됨

    메서드이름 : getter (get으로 시작하는 메서드)

    파라미터패턴 : *, * ← 2개


    execution( * read*(Integer, ..) )

    수식어패턴 : 생략됨

    리턴타입 : *   ← 전부

    클래스이름 : 생략됨

    메서드이름 : read로 시작하는 모든 메서드

    파라미터패턴 : Integer, ..  ← 1개 이상의 파라미터를 가짐. 첫번째 파라미터형은 Integer형이여야 함


    Advice 적용순서

    하나의 JoinPoint에 한개 이상의 Advice 가 적용될 경우, 순서를 명시적으로 지정할 수 있음.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package aspect;
     
    import java.util.HashMap;
    import java.util.Map;
     
    import org.aspectj.lang.ProceedingJoinPoint;
     
    public class CacheAspect {
     
        private Map<Long, Object> cache = new HashMap<>();
     
        public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
            Long num = (Long) joinPoint.getArgs()[0];
            if (cache.containsKey(num)) {
                System.out.printf("CacheAspect: Cache에서 구함[%d]\n", num);
                return cache.get(num);
            }
     
            Object result = joinPoint.proceed();
            cache.put(num, result);
            System.out.printf("CacheAspect: Cache에 추가[%d]\n", num);
            return result;
        }
    }
    cs

    캐시를 구현한 Aspect 

    다음에 동일한 요청이 들어오면 처음 요청시 보관해두었던 결과를 리턴한다.

    캐시로 시간이 줄어드는 것을 확인 하고 싶다면 

    다음과 같이 설정해야한다.


    시간측정 -> 캐시 프록시 -> 실제 대상 객체

    Aspect의 적용순서가 중요하면 직접 순서를 지정해햐한다.

    XML기반 스키마 기반 AOP를 설정을 사용하는 경우 

    <aop:aspect>태그의 order 속성을 이용해서 순서를 적용한다.

    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
    <?xml version="1.0" encoding="UTF-8"?>
     
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:aop="http://www.springframework.org/schema/aop"
        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
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
     
        <!-- 공통 기능을 제공할 클래스를 빈으로 등록 -->
        <bean id="exeTimeAspect" class="aspect.ExeTimeAspect" />
        <bean id="cacheAspect" class="aspect.CacheAspect" />
     
        <!-- Aspect 설정: Advice를 어떤 Pointcut에 적용할 지 설정 -->
        <aop:config>
            <aop:aspect id="calculatorCache" ref="cacheAspect" order="1">
                <aop:pointcut id="calculatorMethod"
                    expression="execution(public * chap07.Calculator.*(..))" />
                <aop:around pointcut-ref="calculatorMethod" method="execute" />
            </aop:aspect>
     
            <aop:aspect id="measureAspect" ref="exeTimeAspect" order="0">
                <aop:pointcut id="publicMethod"
                    expression="execution(public * chap07..*(..))" />
                <aop:around pointcut-ref="publicMethod" method="measure" />
            </aop:aspect>
        </aop:config>
     
        <bean id="impeCal" class="chap07.ImpeCalculator">
        </bean>
     
    </beans>
    cs

    order의 값이 작은 순으로 적용됨 

    @Aspect 애노테이션사용시 @Order 애노테이션으로 적용순서를 지정할 수있다.

    1
    2
    3
    4
    5
    6
    7
    8
    @Aspect
    @Order(1)
    public class ExeTimeAspect2 {
        
        @Pointcut("execution(public * chap07..*(..))")
        private void publicTarget() {
            
        }
    cs


    'Spring_FrameWork' 카테고리의 다른 글

    스프링 부트에서 JPA 데이터베이스 사용하기  (0) 2020.06.06
    DB연동  (0) 2019.03.22
    빈 라이프사이클과 범위  (0) 2019.03.19
    DI정리  (0) 2019.03.18
    의존 자동 주입  (0) 2019.03.15
Designed by Tistory.