Whiteship's Note


AspectJ로 final method에도 위빙하기

Spring/Chapter 6 : 2008.10.02 10:03


소스 코드는 이전과 동일합니다. 그 상태에서 프록젝트에 AspectJ Nature를 추가해줍니다.

사용자 삽입 이미지

그런 다음에 프로젝트 클린을 하여 기존의 클래스파일을 비우고 다시 컴파일하게 합니다.(Alt + P, N, 엔터) 지금, 저는 아무런 .aj 파일도 만들지 않았습니다. 그냥 이전에 만들어둔 Spring @AOP를 사용하고 있을 뿐입니다.

@Aspect
public class FinalHelloAspect {

    @Before("execution(* org.opensprout.sandbox.proxy.withfinal.FinalHello.*(..))")
    public void withFinalAdvice() {
        System.out.println("before advice from FinalHelloAspect");
    }

}

이 녀석이죠. AspecJ 프로젝트가 됐기 때문에, 빌드타임에 저 애스팩트의 포인트컷에 해당하는 클래스 파일을 조작해서 "새로운 .class 파일"을 생성하게 될 겁니다.

사용자 삽입 이미지

컴파일이 끝난 후 AspectJ IDE가 발동한 모습이니다. 포인트컷 마다 저런 화살표가 표시됩니다. 보시면 final 메소드에도 화살표가 표시되어 있습니다. 해당 코드와 Cross References 뷰를 동기화 시켜두면, 해당 지점에 적용될 어드바이스까지 표시됩니다.

사용자 삽입 이미지

놀랍습니다. 안 그러세요? @Aspect 캬오~ 하긴, 그리 놀랍지도... @Aspect 애노테이션이 aspectj 프로젝트에 속한 애노테이션이니까,..

사용자 삽입 이미지

이 결과를 보면 더이상 프록시를 사용하고 있는것이 아니기 때문에, Advised 라는 인터페이스는 사용할 수 없다는 것을 볼 수 있습니다. ClassCastException이 보이시죠?

흠냐;;

회사 청소해야되서 이만.. 황급히 마무리 합니다.
top


CGLIB 프록시 제약 사항 테스트.

Spring/Chapter 6 : 2008.10.02 09:10


@Component
public class FinalHello implements Hello {

    public String hi() {
        return "hi";
    }
    
    public final void finalHi(){
        System.out.println("안녕");
    }
    
}

위 클래스에 모든 메소드 호출을 포인트컷으로 간단한 Adivce를 적용하여 CGLIB 프록시를 생성했을 때, 다음과 같은 결과를 확인할 수 있었습니다.

1. final 메소드를 호출할 경우 Advice를 적용하지 않음.
2. final이 아닌 메소드에는 Advice가 적용됨.

이런 간단한 테스트를 해보기 전에는 막연히 final 메소드가 있으면 CGLIB 프록시를 못 만드는 건가 싶었는데, 그게 아니었습니다.

한 가지 더 확인해봤습니다. 기본 생성자(인자가 없는 생성자)가 반드시 있어야 한다고 본것 같아서, 그걸 확인해봤습니다. 위의 경우에는 기본 생성자가 있는 상태로 테스트를 했으니까 기본 생성자 없이 테스트를 한 번 더 해보면 되겠죠.

@Component
public class FinalHello implements Hello {
   
    String hi;
   
    public FinalHello(String hi) {
        this.hi = hi;
    }

    public String hi() {
        return hi;
    }
   
    public final void finalHi(){
        System.out.println("안녕");
    }
   
}

그리고 위에서 실행했던 테스트 코드와 애스팩트를 그대로 사용해봤습니다.


java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:203)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:109)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:255)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:93)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.invokeTestMethod(SpringJUnit4ClassRunner.java:130)
    at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUnit4ClassRunner.java:51)
    at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4ClassRunner.java:44)
    at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:27)
    at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:37)
    at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4ClassRunner.java:42)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:38)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'finalHello' defined in file [C:\workspace\osaf\target\test-classes\org\opensprout\sandbox\proxy\withfinal\FinalHello.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [org.opensprout.sandbox.proxy.withfinal.FinalHello]: No default constructor found; nested exception is java.lang.NoSuchMethodException: org.opensprout.sandbox.proxy.withfinal.FinalHello.<init>()
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:883)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:839)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:440)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:409)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:380)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:264)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:221)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:261)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:185)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:164)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:429)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:729)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:381)
    at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:84)
    at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:42)
    at org.springframework.test.context.TestContext.loadApplicationContext(TestContext.java:173)
    at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:199)
    ... 16 more
Caused by: org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [org.opensprout.sandbox.proxy.withfinal.FinalHello]: No default constructor found; nested exception is java.lang.NoSuchMethodException: org.opensprout.sandbox.proxy.withfinal.FinalHello.<init>()
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:58)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:877)
    ... 33 more
Caused by: java.lang.NoSuchMethodException: org.opensprout.sandbox.proxy.withfinal.FinalHello.<init>()
    at java.lang.Class.getConstructor0(Class.java:2706)
    at java.lang.Class.getDeclaredConstructor(Class.java:1985)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:54)
    ... 34 more

맞네요. 두 번쨰 제약 사항까지 살펴봤습니다.

CGLIB 프록시가 JDK 프록시에 비해 성능도 좋고, concrete 클래스의 프록시도 만들어 주기 때문에, 좋긴 한데, 위의 두 개의 제약 사항(final 메소드에는 어드바이스 적용 불가(상속을 못하니까.), 기본 생성자 필요.)에 주의 하면서 사용해야겠습니다.

만약에 이런 경우엔 어떻게 해야 할까요.

1. 구현하는 인터페이스 없음 ==> JDK 프록시 사용불가
2. 기본 생성자 없음 ==> CGLIB 사용불가
3. 어드바이스 적용하고자 하는 메소드가 fianl ==> 역시 CGLIB 사용불가
4. 하지만 AOP 하고파. ==> Spring AOP로는 불가능.

결론(지금은 머리로만 생각한 겁니다. 검증은 조금 뒤에..)
==> Spring AOP + AspectJ 연동해서, 빌드 타임에 aspectj-waever를 사용하던가, 클래스 로딩 타임에 loadtime-weaving을 하면 될 것입니다. AspectJ를 사용한 위빙은 프록시를 만드는게 아니라, 바이트코드랑 .aj 파일을 조작해서 타겟 클래스에 대한 .class파일을 다시 생성하고 그 코드를 사용하는 것이기 때문에 런타임시에 부하도 없고 위와 같은 Spring AOP 제약에서 벗어날 수 있으리라 봅니다.
top


@Configurable + 톰캣

Spring/Chapter 6 : 2008.01.16 10:55


테스트 코드는 다음과 같습니다.

public class MemberTestServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        Member member = new Member();
        if(member.getMemberRepository() == null)
            System.out.println("Opps Repository Null");
        if(member.getMemberRepository().getSessionFactory() == null)
            System.out.println("Opps SessionFactory Null");
        System.out.println("Good!!!");
    }

}

간단하죠. 뷰에 디스패칭을 하지도 않았습니다. 그냥 콘솔에 Good!!만 출력하도록 했습니다. 그 이외의 경우(Null)에는 화면에 뭐가 Null인지 출력하도록 했죠. 그리고 이 녀석을 web.xml에 등록했습니다.

    <servlet>
        <servlet-name>memberTest</servlet-name>
        <servlet-class>web.MemberTestServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>memberTest</servlet-name>
        <url-pattern>/memberTest.do</url-pattern>
    </servlet-mapping>

그리고 브라우저에서 /memberTest.do 를 호출하고 콘솔 창을 봤습니다.

사용자 삽입 이미지

사용자 삽입 이미지

결론 : @Configurable은 웹 서버에서도 잘 동작 합니다.
top


@Configurable + @Entity

Spring/Chapter 6 : 2008.01.16 10:23


참조 : Spring: Component Scan + Load Time Weaver (LTW)

아침에 올라온 댓글을 보고 확인해봤습니다.

질문은 @Configurable과 JPA 그리고 Jetty를 사용했을 때, @Configurable이 동작하지 않아서 도메인 객체가 가지고 있는 레퍼런스 타입의 객체들이 세팅되지 않고 null 인 상태라는 제보였습니다.

예상으로는 웹 서버를 동작 시키실 때, -javaagent 옵션을 주지 않으신 게 아닌가 싶습니다.

사용자 삽입 이미지
이클립스에서 톰캣을 사용하는 이런 화면에서 가운데 보이는 Open lunch configuration에서 옵션을 줄 수 있습니다.

사용자 삽입 이미지
일단 서버에서 테스트 하려면 Sevlet 에서 코드를 작성해서 확인해봐야겠지만, 그 전에 @Entity랑 @Configurable이 같이 묶여도 이상이 없다는 것은 확인하고 넘어가야겠기에 테스트를 해봤습니다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"file:web/WEB-INF/applicationContext.xml"})
public class MemberTest {

    @Test
    public void injectionTest() throws Exception {
        Member member = new Member();
        assertNotNull(member);
        assertNotNull(member.getMemberRepository());
        assertNotNull(member.getMemberRepository().getSessionFactory());
    }

}

물론 이 테스트를 돌릴 때에도 -javaagent 옵션을 주셔야 합니다. 이 경우에는 junit에 주어야겠죠.

사용자 삽입 이미지

테스트는 통관합니다. 설정은 다음과 같습니다.
    <tx:annotation-driven />

    <context:load-time-weaver />

    <context:spring-configured />

    <context:component-scan base-package="domain" />

    <bean id="member" class="domain.Member" abstract="true"
        scope="prototype" p:member-dao-ref="memberDao" />

이밖에도 sessionFactory, datasource, transactionManager 가 빈으로 등록되어 있지만 생략하겠습니다.

어쨋든 조금 쉬었다가 Servlet에서 위의 코드를 실행해보겠습니다.
top


@Configurable 사용하기

Spring/Chapter 6 : 2007.11.26 12:46


이제야... 이 글을 올릴 수 있게 됐네요. ㅎㅎㅎ 저번 주부터 올리고 싶었던 글인데, 별것도 아닌거 가지고 삽질을 하느라고 늦어졌습니다.

@Configurable이 뭔지, 왜 사용해야 하는지 궁금하시면 먼저 토비님이 마소에 기고하셨던 글인 "스프링프레임워크와 DDD(Driven Driven Design)"와 그 글을 보고 감명을 받은 찬욱군의 도메인 개체가 빈으로 선언되야 하는 걸까?와 제가 쓴 @Configurable을 사용해야 하는 이유를 읽으시는게 좋습니다.

1. 도메인 객체 구현. + 2. @Configurable 설정.
2. XML 설정.
3. java 아규먼트 설정.
4. 테스트.

0 순위가 빠졌네요. 테스트 코드부터 작성하겠습니다.


new를 사용해서 도메인 객체를 생성하고, 이 객체가 Repository라는 객체를 가지고 있나 확인하는 매우 간단한 코드입니다.

1. 도메인 객체 구현. + 2. @Configurable 설정.

구현은 간단합니다.


주목해서 봐야할 부분은 Repository 객체를 new 키워드로 생성하여 가지고 있지 않습니다. 스프링의 DI를  사용하여 주입받을 것이기 때문입니다.

그리고 클래스 선언 부 위에 @Configurable 애노테이션을 사용하여 이 객체를 스프링이 관리하도록 설정합니다. 괄호 안에는 스프링에서 이 객체를 나타낼 bean의 이름을 적어줍니다. 위와같이 클래스 명과 동일할 때는 생략해도 됩니다.

2.  XML 설정.

스프링 설정 파일은 다음과 같습니다.
gg

스프링 2.5에 새로 추가된 context 네임스페이스를 추가한 뒤, <context:load-time-weaver /> 엘리먼트와 <context:spring-configured /> 엘리먼트를 추가해줍니다. <context:spring-configured /> 이 녀석은 @Configurable이 붙어있는 bean을 찾아서 스프링이 관리하도록 설정합니다. 이 때 LTW(load time weaver)를 필요로 하는데, 이 녀석을 사용하도록 <context:load-time-weaver /> 엘리먼트를 등록해 줍니다.

그리고 member 객체는 명시적으로 컨테이너에서 만들 수 없도록 abstract 속성을 true로 해주고, 매번 다른 객체처럼 취급해야 하기 때문에 prototype Scope으로 설정해줍니다.

beans 엘리먼트에서 Autowiring 설정을 byName으로 해두었기 때문에, Member 클래스에 RepositoryImpl 클래스를 주입해 줄 것입니다.

3. java 아규먼트 설정.

Run -> Open Run Dialog를 클릭하고 JUnit을 우클릭하여 new를 선택하여 새로운 테스트 스크립트를 작성합니다.

이때 Test에서는 기존의 Test와 같은 값을 주고, Argument 탭에서 다음과 같이 -javaagent 옵션을 사용하여 spring-agent.jar 파일의 위치를 알려줍니다.

사용자 삽입 이미지

4. 테스트.
사용자 삽입 이미지

휴~ 이제 시작이네요. ㅎㅎ
top


@Configurable 왜이리 안 되지;

Spring/Chapter 6 : 2007.11.23 16:43


http://forum.springframework.org/showthread.php?t=43690
위 링크에 있는 것과 똑같은 현상이 벌어지고 있습니다.

1. @Confiurable 애노테이션을 도메인 객체위에 붙이고...
2. XML 설정에서 해당 도메인 객체를 등록하고 이 때 scope을 prototype으로...
3. <context:spring-configured /> 추가해주고.. (@Configurable 붙은 녀석이 애플리케이션에서 생성될 때 스프링이 관리하도록...)
4. <context:load-time-weaver/> 추가해주고.. (LTW 사용해야 3번일을 할 수 있으니까..)
끝..

이렇게 하면
    @Test
    public void testDI() throws Exception {
        Member member = new Member();
        assertNotNull(member.getRepository());
    }

이 테스트가 통과해야 하는데..
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.context.weaving.AspectJWeavingEnabler#0': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'loadTimeWeaver': Initialization of bean failed; nested exception is java.lang.IllegalStateException: ClassLoader [sun.misc.Launcher$AppClassLoader] does NOT provide an 'addTransformer(ClassFileTransformer)' method. Specify a custom LoadTimeWeaver or start your Java virtual machine with Spring's agent: -javaagent:spring-agent.jar

-javaagent:spring-agent.jar 옵션을 추가해서 실행시키라는 에러가 발생합니다.

그래서 이클립스의 Run -> Open Reun Dialog 클리갛고, JUnit 에서 우클릭 한다음 new로 새로 하나 만들고 아규먼트에 인자를 다음과 같이 줬습니다.

-javaagent:sd:\eclipse\workspace\spring2.5\lib\spring-agent.jar

하지만 에러 메시지는 동일합니다. OTL...
레퍼런스에서 Table 6.1. DefaultContextLoadTimeWeaver  LoadTimeWeavers 표를 보고 위버 설정 파일을 다음과 같이 수정했습니다.

<context:load-time-weaver weaver-class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>

자... 그랬더니 이번에는 레퍼런스를 보라는 에러가...ㄷㄷㄷ

java.lang.IllegalStateException: Must start with Java agent to use InstrumentationLoadTimeWeaver. See Spring documentation.



top


@Configurable 사용해야 하는 이유

Spring/Chapter 6 : 2007.11.22 21:14


DTO를 공부하다가 들었던 의문.. 그동안 내가 만들었던 DO들의 빈약함(Anemic Domain Model)에 대해 명쾌하고 깔끔하게 정리를 해주신 글을 발견했습니다. 그리고 그 대안으로 Rich 또는 Smart DO 그리고 DDD로 이어지는 흐름도 좋은 글입니다. 머리가 맑아지는 동시에 공부할 것들로 머리가 꽉차게 되는 그런 글이기도 합니다.

스프링프레임워크와 DDD(Driven Driven Design)

기존에는 도메인 객체를 모든 레이어에 걸쳐서 사용했었습니다. 그런데 DDD를 하면 도메인이 서비스 계층과 리파지토리 계층 사이에 끼이게 됩니다. 이제 하나의 계층으로 자리를 잡게 되는 것이죠.
사용자 삽입 이미지
출처: https://www.dbguide.net/know/know102001.jsp?mode=view&divcateno=9&divcateno_=9&pg=1&idx=3229

이렇게 되면 도메인 객체에 무언가(DAO나 Repository) 주입시켜주어야 합니다. 그런데.. 이 주입시키는 일 Dependency Injection을 사용하려면 스프링이 관리하는 bean이어야 합니다. 즉 스프링 설정 파일에 도메인 객체가 bean으로 등록되어 있어야 한다는 것이죠. 하지만 문제는 도메인 객체들은 bean으로 설정하여 종속성을 관리할 객체로는 적당하지 않습니다.

이 객체들의 생성을 스프링 컨테이너가 관리할 수 없습니다. 이 객체들은 애플리케이션 동작 중에 생성되기 때문에, 스프링이 생명주기를 관리할 bean으로 등록하여 사용한다는 것은 말이 안됩니다.

이럴 때 등장하는 녀석이 바로 저 @Configurable 애노테이션 입니다. 이녀석이 붙어있는 클래스를 스프링에 등록해두면, 스프링은 그 객체가 생성될 때 그 객체가 필요로 하는 bean들을 주입해 줍니다. 엄청나죠. 이런 일이 어떻게 가능할까요. 바로 AOP 때문에 가능하죠. method 호출 Joinpoint만을 지원하는 스프링 AOP로는 이런 일을 할 수 없습니다. 생성자 실행 Joinpoint를 지원하는 AspectJ가 할 수 있는 일이죠.

그래서 @Configurable을 사용하려면 AsepctJ Load Time Weaver가 필요합니다.

결국 스토리는 이렇게 됩니다.
  1. DDD를 하고싶어. (도메인 객체가 DAO를 필요로 하고 있어.)
  2. @Configurable을 사용해야 겠다. (애플리케이션 실행 도중 생기는 객체도 스프링에서 관리할꺼야.)
  3. LTW가 필요하구나.
아니면 간단하게 다음과 같이 해도 되죠.
  1. DDD를 하고싶어. (도메인 객체가 DAO를 필요로 하고 있어.)
  2. 도메인 생성자에서 new 써주지뭐.
아래의 스토리가 더 짧고 간단합니다. 그런데도 저는 첫 번째 스토리대로 하고 싶습니다. 왜냐면, 스프링이 도중에 생겨난 객체들 까지 관리하게 되면, 그 객체들(풍성해진 도메인)에 스프링의 DI와 AOP를 적용할 수 있기 때문이죠. 아래 스토리대로 하면, 도메인 객체가 참조하는 DAO가 바뀌면 소스코드를 매번 바꿔줘야 되겠죠. 그리고 다른 코드는 전부 DI랑 AOP 사용해놓고 도메인 계층만 왕따 시키는 것도 아니고.. 좀 그렇차나요.ㅎㅎ;

그래서.. 좀전까지 @Configurable과 LTW랑 팀을 맺고 저랑 2:1로 씨름을 하고 있었습니다. 제가 졌습니다. 오늘은..-_-;; 잘 안 되더라구요. Eclipse에서 JVM 아규먼트 설정해 주는게 틀렸나.. 왜 그러징;;

'Spring > Chapter 6' 카테고리의 다른 글

CGLIB 프록시 제약 사항 테스트.  (2) 2008.10.02
@Configurable + 톰캣  (2) 2008.01.16
@Configurable + @Entity  (0) 2008.01.16
@Configurable 사용하기  (6) 2007.11.26
@Configurable 왜이리 안 되지;  (2) 2007.11.23
@Configurable 사용해야 하는 이유  (5) 2007.11.22
6.6. Proxying mechanisms  (0) 2007.04.06
6.4. Choosing which AOP declaration style to use  (0) 2007.04.06
Schema 기반 Introduction  (0) 2007.04.06
Schema 기반 Advice parameters  (0) 2007.04.06
<aop:around> 어드바이스 예제  (0) 2007.04.06
top


6.6. Proxying mechanisms

Spring/Chapter 6 : 2007.04.06 14:11


Spring AOP는 JDK의 Dynamic Proxy 또는 CGLib을 사용하여 프록시를 만듭니다. 타겟이 되는 객체가 인터페이스를 하나라도 구현했다면 JDK의 Proxy를 사용할 것이고 구현한 인터페이스가 하나도 없을 경우에는 CGLib을 사용하게 됩니다. 원할 때는 명시적으로 CGLib을 사용하도록 할 수도 있지만 다음의 제약사항들이 있습니다.

1. final 메소드는 어드바이스가 적용되지 않습니다. - 오버라이딩 못하기 때문이죠.
2. CGLIB 라이러리가 추가로 필요합니다. - spring이 알려줄 겁니다.
3. 생성자가 두 번 호출 됩니다. - 상속해서 만든거니깐 그렇겠죠.

명시적으로 CGLIB을 사용하고 싶을 땐 다음 처럼 설정해 줍니다.

@AspectJ 기반
<aop:aspectj-autoproxy proxy-target-class="true"/>

Schema 기반
<aop:config proxy-target-class="true">  
</aop:config>

6.6.1. Understanding AOP proxies

사용자 삽입 이미지
일반적인 객체에 대한 호출을 그림으로 표현한 것입니다.

사용자 삽입 이미지
이건 프록시 객체를 호출했을 떄를 그림으로 표현한 것입니다. 프록시에 있는 foo()를 먼저 하고 그 다음에 타겟 객체의 foo()를 호출하고 있습니다.

이때 self-invocation issue가 발생합니다. 소스 코드로 확인 하는게 좋겠습니다.

public interface Pojo {
    public void foo();
    public void bar();
}

public class SimplePojo implements Pojo {
    public void foo(){
        System.out.println("this is foo and call bar()");
        bar();
    }
    public void bar() {
        System.out.println("this is bar");
    }
}

인터페이스와 타겟이 될 클래스가 있으며 이것을 프록시팩토리를 사용하여 apsect를 만들고 간단한 어드바이스를 추가합니다.

    @Test
    public void name() {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());

        Pojo pojo = (Pojo) factory.getProxy();
        pojo.foo();
    }

추가한 어드바이스는 다음과 같습니다.

    public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
        System.out.println("Before Adivce");
    }

테스트를 실행하면 결과는 다음과 같습니다.

Before Adivce
this is foo and call bar()
this is bar

원하던 결과는 이게 아니죠. 원래는 다음 처럼 나와야 합니다.

Before Adivce
this is foo and call bar()
Before Adivce
this is bar

어드바이스가 타겟에 적용되었고 별다른 포인트컷 표현식을 쓰지 않았기 떄문에 모든 메소드에 적용이 되어야 합니다. foo()를 호출 했고 foo()에서 bar()를 호출 했기 때문에 bar()를 호출 할 때도 적용이 됐어야 하는데 그렇치 않았습니다.

프록시 객체를 지나서 타겟 객체에 다다른 뒤에 타겟 객체에서 자기 자신의 메소드를 호출 할 때는 프록시를 거치지 않기 때문에 이렇게 됩니다. 이걸 self-invocation issue 라고 합니다.

이 이슈의 해결 방법은.. 다소 invasive 하지만 어쩔 수 없이 다음 처럼 프록시를 통해서 호출하도록 코드를 수정해야 합니다.

public class SimplePojo implements Pojo {
    public void foo(){
        System.out.println("this is foo and call bar()");
        ((Pojo) AopContext.currentProxy()).bar();
    }

    public void bar() {
        System.out.println("this is bar");
    }
}

레퍼런스에는 조금 메소드가 다르더군요. 레퍼런스에 오타도 있고 메소드 이름도 잘못 되어 있었지만 이클립스가 도와주고 있기 때문에 쉽게 고쳐 쓸 수 있었습니다.

    @Test
    public void name() {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());
        factory.setExposeProxy(true);
        Pojo pojo = (Pojo) factory.getProxy();
        pojo.foo();
    }
top


6.4. Choosing which AOP declaration style to use

Spring/Chapter 6 : 2007.04.06 13:02


6.4.1. Spring AOP or full AspectJ?

Spring AOP는 별도의 컴파일러나 위버가 필요 없으며 AspectJ 보다 단순합니다. 하지만 컨테이너에 의해 관리되는 bean에만 advice를 적용할 수 있고 적용되는 joinpoint가 메소드 실행 시점 뿐 입니다.

도메인 객체 또는 컨테이너에 의해 관리되지 않는 객체들에 Advice를 적용해야 하거나 더 다양한 joinpoint가 필요하다면 AspectJ를 사용하는 것이 좋겠습니다.

6.4.2. @AspectJ or XML for Spring AOP?

XML 기반의 Spring AOP는 JDK 버젼이랑 관계 없이 사용할 수 있다는 장점이 있으며 설정 파일을 보고 aspect가 적용된 상태를 파악할 수 있습니다. 하지만 필요한 정보를 두 곳에 나눠 놓는 다는 점에서 DRY(Don't Repeat Youeself) 원칙을 어긴다고 볼 수 있습니다. @AspectJ 에서는 && || ! 를 사용하여 포인트컷 끼리의 연산을 할 수가 있었는데 그걸 못합니다.

5.0 미만의 JDK를 사용해야 한다면 스키마 기반 Spring AOP를 사용하시고(유일한 선택 사항이죠.) 5.0 이상일 때 단순한 설정(예를 들어 선언적 트랙잭션 관리 같은 것) 이외에 애스팩트가 필요하다면 @AspectJ 를 사용하는게 좋겠습니다.

예제 만들면서 써보니까 전 @AspectJ가 훨씬 편하더군요.



top


Schema 기반 Introduction

Spring/Chapter 6 : 2007.04.06 12:29


Introduction 예제 와 동일한 예제입니다. <aop:declare-parents> 를 사용하였다는 것만 다르죠. 흐흣;;

새로 추가할 메소드를 가진 인터페이스와 그것을 구현한 클래스르 만듭니다.

public interface TicketTracked {
    void incrementTicketCount();
}

public class TicketTrackedImpl implements TicketTracked {
    static int count = 0;
    public void incrementTicketCount() {
        System.out.println("표 " + (++count) + " 장 팔았다.");
    }
}

그리고 this()를 사용하여 포인트컷을 만들어 줍니다.

<aop:pointcut id="countTicket"
            expression="execution(* sell*(..)) and this(ticketTracked)"/>

다음 이 포인트 컷을 사용할 introduction을 설정해 줍니다. 이 설정은 <aop:aspect> 바로 아래에 있어야 합니다. 안그럼 에러나 나더군요~
<aop:declare-parents
                types-matching="aop.newStyle.domain.KeesunCinema"
                implement-interface="aop.newStyle.aspect.TicketTracked"
                default-impl="aop.newStyle.aspect.TicketTrackedImpl" />
<aop:after method="ticketTtrack" pointcut-ref="countTicket"/>

그리고 테스트를 해보면 원하는 결과를 확인할 수 있습니다.
    @Test
    public void sellTicket() {
        cinema.sellTicket(movie, new Date());
    }

어서 오세요. 무엇을 도와드릴까요?
어서 오세요. 무엇을 도와드릴까요?
왔어? 영화 뭐 볼껀데?
하이 공공의적보려고?
표 1 장 팔았다.
감사합니다. 공공의적을 구매 하셨습니다.
쌩큐  공공의적잘봐!
top


Schema 기반 Advice parameters

Spring/Chapter 6 : 2007.04.06 12:09


Advice parameters 여기서 살펴봤던 것과 거의 동일합니다. args() 표현식을 사용하여 포인트컷을 정의합니다.

<aop:pointcut id="sellTicket2" expression="execution(* sell*(..)) and args(movie,..)"/>

movie라는 이름의 파라미터를 받는 메소드의 조인포인트를 가리키게 됩니다. 이걸 받아서 처리할 어드바이스를 만듭니다.

    public void veryWelcome(Movie movie){
        System.out.println("하이 " + movie.getName() + "보려고?");
    }

xml 파일에 설정해 줍니다.

<aop:aspect id="cinema" ref="aspect">
            <aop:before method="welcome" pointcut-ref="sellTicket" />
            <aop:before method="welcome" pointcut-ref="sellTicket" />
            <aop:after-returning method="afterSellTicket" pointcut-ref="sellTicket" returning="ticket"/>
            <aop:around method="aroundSellTicket" pointcut-ref="sellTicket"/>
            <aop:before method="veryWelcome" pointcut-ref="sellTicket2" arg-names="movie"/>
</aop:aspect>

args-names에 넣어준 값 movie는 어드바이스 역할을 하는 메소드에서 받는 메개변수 이름입니다. 테스트를 실행하면 원하던 결과를 확인할 수 있습니다.
    @Test
    public void sellTicket() {
        cinema.sellTicket(movie, new Date());
    }

어서 오세요. 무엇을 도와드릴까요?
어서 오세요. 무엇을 도와드릴까요?
왔어? 영화 뭐 볼껀데?
하이 공공의적보려고?
감사합니다. 공공의적을 구매 하셨습니다.
쌩큐  공공의적잘봐!

before중에서 가장 마지막이고 around에서 pjp.proceed 이전에 수행하는 작업을 before로 보면 예상할 수 있는 위치에 수행된 걸 확인할 수 있습니다.
top


<aop:around> 어드바이스 예제

Spring/Chapter 6 : 2007.04.06 11:44


around 어드바이스 역할을 메소드를 만듭니다. @Around 어드바이스 예제 의 예제와 거의 동일합니다.

메소드의 첫번째 인자로 ProceedingJoinPoint가 와야 하며 throws Throwable을 붙여줍니다.
    public Object aroundSellTicket(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("왔어? 영화 뭐 볼껀데?");
        Ticket ticket = (Ticket) pjp.proceed();
        System.out.println("쌩큐  " + ticket.getMovie().getName() + "잘봐!");
        return ticket;
    }

xml 설정 파일에 aound 어드바이스를 설정해 줍니다.

<aop:aspect id="cinema" ref="aspect">
            <aop:before method="welcome" pointcut-ref="sellTicket" />
            <aop:before method="welcome" pointcut-ref="checkTicket" />
            <aop:after-returning method="afterSellTicket" pointcut-ref="sellTicket" returning="ticket"/>
            <aop:around method="aroundSellTicket" pointcut-ref="sellTicket"/>
</aop:aspect>

테스트를 실행합니다.
    @Test
    public void sellTicket() {
        cinema.sellTicket(movie, new Date());
    }

어서 오세요. 무엇을 도와드릴까요?
왔어? 영화 뭐 볼껀데?
감사합니다. 공공의적을 구매 하셨습니다.
쌩큐  공공의적잘봐!

이 전에 사용했던 예제의 어드바이스가 같이 적용이 됐기 때문에 결과가 저러헤게 나왔습니다. 어드바이스가 적용되는 순서는 Advice ordering 여기서도 살펴봤지만 간단하게 aspect에 등록되어 있는 순이라고 생각해도 될 것 같습니다.


top


<aop:after-returning> 어드바이스에서 리턴값 받아오기

Spring/Chapter 6 : 2007.04.06 11:33


Aspect 역할을 할 클래스(이름을 Aspect로 했습니다. 꼭 그래야 하는건 아니죠;;)에 After Returning 때 weaving 될 어드바이스를 일반 메소드로 정의합니다.

public class Aspect {

    public void welcome(){
        System.out.println("어서 오세요. 무엇을 도와드릴까요?");
    }

    public void confirm(){
        System.out.println("확인하기");
    }

    public void afterSellTicket(Ticket ticket){
        System.out.println("감사합니다. " + ticket.getMovie().getName() + "을 구매 하셨습니다.");
    }
}

그리고 설정파일에 어드바이스를 등록합니다.

<aop:aspect id="cinema" ref="aspect">
            <aop:before method="welcome" pointcut-ref="sellTicket" />
            <aop:before method="welcome" pointcut-ref="checkTicket" />
            <aop:after-returning method="afterSellTicket" pointcut-ref="sellTicket" returning="ticket"/>
</aop:aspect>

@AfterReturning 어드바이스 만들기 이것과 거의 동일한 예제입니다. 결과는 무난히 리턴값을 가져온 것을 확인할 수 있습니다.

    @Test
    public void sellTicket() {
        cinema.sellTicket(movie, new Date());
    }

어서 오세요. 무엇을 도와드릴까요?
감사합니다. 공공의적을 구매 하셨습니다.
top


Schema 기반 Spring AOP 포인트컷 만들 때 주의 할 것

Spring/Chapter 6 : 2007.04.06 09:01


<aop:config>

        <aop:pointcut id="sellTicket"
            expression="execution(* sell*(..))" />

        <aop:aspect id="cinema" ref="aspect">

            <aop:pointcut id="sellTicket2"
                expression="execution(* sell*(..))" />

            <aop:pointcut id="sellTicket3"
                expression="execution(* sell*(..))" />

            <aop:before method="welcome" pointcut-ref="sellTicket" />

            <aop:before method="welcome" pointcut-ref="sellTicket2" />

        </aop:aspect>

    </aop:config>

<aop:aspect> 태그 안에 pointcut 정의는 하나밖에 올 수가 없습니다. 안그럼 어제 아침에 적었던 'Schema 기반 Spring AOP 희한한 것'에서 보았던 에러가 발생합니다.

대신 <aop:config> 태그 아래 <aop:aspect> 태그 밖에 정의를 하면 여러개를 정의할 수 있습니다.

<aop:config>

        <aop:pointcut id="sellTicket"
            expression="execution(* sell*(..))" />

        <aop:pointcut id="sellTicket2"
            expression="execution(* sell*(..))" />

        <aop:aspect id="cinema" ref="aspect">

            <aop:pointcut id="sellTicket3"
                expression="execution(* sell*(..))" />

            <aop:before method="welcome" pointcut-ref="sellTicket" />

            <aop:before method="welcome" pointcut-ref="sellTicket2" />

        </aop:aspect>

    </aop:config>

즉 이렇게 만들면 에러 없이 잘 돌아갑니다. aspect 안에는 최대 하나밖에 허용하지 않고 (물론 in-line 포인트컷을 만들면 여러 개의 포인트컷을 사용할 순 있습니다.) config 안에는 여러개가 있어도 되는 걸 알 수 있습니다.

아무래도 config에서 만드는 포인트컷들은 다른 애스팩트의 어드바이스들에서 사용할 것이기 때문에 독립적으로 여러개 존재 하도록 한 것 같습니다.

여기서 드러나는 스키마 기반 Spring AOP의 불편한 점
1. @AspectJ 에서는 하나의 애스팩트 내부에 여러 포인트컷이 존재할 수 있는 것과 매칭이 되지 않아 혼란스럽습니다.
2. @AspectJ에는 없는 config 라는 영역이 존재해서 혼란스럽습니다.
3. 더군다나 @AspectJ에서 포인트컷끼리 연산을 할 때 사용한 &&, ||, ! 을 사용할 수 없다는 것도 불편합니다.

top


Schema 기반 Spring AOP 희한한 것

Spring/Chapter 6 : 2007.04.05 08:47


    <aop:config>
        <aop:aspect id="cinema" ref="aspect">
            <aop:pointcut id="sellTicket" expression="execution(* sell*(..))" />
            <aop:before method="welcome" pointcut-ref="sellTicket" />
            <aop:before method="welcome" pointcut="execution(* sell*(..))"/>
        </aop:aspect>
    </aop:config>

이렇게 포인트컷 하나, 어드바이스 다수 일 경우에는 실행이 되는데요.
    <aop:config>
        <aop:aspect id="cinema" ref="aspect">
            <aop:pointcut id="sellTicket" expression="execution(* sell*(..))" />
            <aop:before method="welcome" pointcut-ref="sellTicket" />

            <aop:pointcut id="sellTicket2" expression="execution(* sell*(..))" />
            <aop:before method="welcome" pointcut-ref="sellTicket2" />
        </aop:aspect>
    </aop:config>

이렇게 포인트컷이 둘 이상일 경우에는 에러가 발생합니다.
org.springframework.beans.factory.BeanDefinitionStoreException: Line 42 in XML document from class path resource [aop/newStyle/schemaConfiguration.xml] is invalid; nested exception is org.xml.sax.SAXParseException: cvc-complex-type.2.4.a: Invalid content was found starting with element 'aop:pointcut'. One of '{"http://www.springframework.org/schema/aop":before, "http://www.springframework.org/schema/aop":after, "http://www.springframework.org/schema/aop":after-returning, "http://www.springframework.org/schema/aop":after-throwing, "http://www.springframework.org/schema/aop":around}' is expected.

흠...in-line 포인트컷은 되는데 왜 그냥 포인트컷은 하나밖에 못쓸까요. 희한하군요.
top


초간단 Schema 기반 Spring AOP

Spring/Chapter 6 : 2007.04.05 08:37


먼저 Aspect 역할을 할 클래스를 만듭니다. 어드바이스를 메소드로 정의해 둘 클래스입니다.

public class Aspect {

    public void welcome(){
        System.out.println("어서 오세요. 무엇을 도와드릴까요?");
    }
}

이제 xml 설정파일로 가서 이 클래스를 빈으로 등록을 하고 어스팩트를 만들 때 사용하도록 합니다.

    <!-- aop -->
    <bean id="aspect" class="aop.newStyle.aspect.Aspect" />

    <aop:config>
        <aop:aspect id="cinema" ref="aspect">
            <aop:pointcut id="sellTicket"
                expression="execution(* sell*(..))" />
            <aop:before method="welcome" pointcut-ref="sellTicket" />
        </aop:aspect>
    </aop:config>

포인트컷 표현식은 이전 @AspectJ 에서와 동일하게 사용할 수 있습니다.

    @Test
    public void sellTicket() {
        cinema.sellTicket(movie, new Date());
    }

다음과 같이 대상이 되는 메소드를 호출 하면 맨 위에 만든 애스팩트에 있는 메소드 중에서 advice에 등록한 메소드가 실행됩니다.
top


Introduction 예제

Spring/Chapter 6 : 2007.04.04 12:43


먼저 추가 할 기능의 명세서인 인터페이스가 필요하고 그것을 구현한 클래스가 필요합니다.

public interface TicketTracked {
    void incrementTicketCount();
}

public class TicketTrackedImpl implements TicketTracked {
    static int count = 0;
    public void incrementTicketCount() {
        System.out.println("표 " + (++count) + " 장 팔았다.");
    }
}

그리고 어스팩트에 @DeclareParents를 사용하여 static 필드를 추가합니다.

    /** introduction **/
    @DeclareParents(value="aop.newStyle.domain.KeesunCinema", defaultImpl=TicketTrackedImpl.class)
    public static TicketTracked mixin;

    @After("sellTicketPointcut() &&" + "this(ticketTracked)")
    public void ticketTtrack(TicketTracked ticketTracked){
        ticketTracked.incrementTicketCount();
    }

@DeclareParents 의 value 속성에는 새 기능(TIckTracked 인터페이스 구현체)을 추가할 클래스를 지정해 줍니다. * 을 사용하여 여러 클래스를 지정할 수도 있습니다. deaultImplt 에는 새 기능을 가진 클래스를 지정해 줍니다.

테스트를 해봅니다.
    @Test
    public void pointcutWithin(){
        cinema.sellTicket(movie, new Date());
        cinema.sellTicket(movie, new Date());
        cinema.sellTicket(movie, new Date());

        TicketTracked cinemaTicketTracked = (TicketTracked)cinema;
    }

표 세장을 사고 cinema가 제대로 TicketTracked 인터페이스를 추가로 구현했는지 확인해봤습니다. 테스트는 무난히 통과 하고 결과는 다음과 같습니다.
표 1 장 팔았다.
표 2 장 팔았다.
표 3 장 팔았다.



top


Advice ordering

Spring/Chapter 6 : 2007.04.04 11:59


@Aspect
public class CinemaAspect {

    @Pointcut("execution(aop.newStyle.domain.Ticket *.sell*(..))")
    public void sellTicketPointcut() {
    }

    @Before("sellTicketPointcut()")
    public void before1(){
        System.out.println("표 팔기 전 1");
    }

    @Before("sellTicketPointcut()")
    public void before2(){
        System.out.println("표 팔기 전 2");
    }

    @After("sellTicketPointcut()")
    public void after1(){
        System.out.println("표 팔고 나서 1");
    }

    @After("sellTicketPointcut()")
    public void after2(){
        System.out.println("표 팔고 나서 2");
    }
}

@Aspect
public class AnotherCinamaAspect {

    @Before("aop.newStyle.aspect.CinemaAspect.sellTicketPointcut()")
    public void before3(){
        System.out.println("표 사기 전 3");
    }

    @After("aop.newStyle.aspect.CinemaAspect.sellTicketPointcut()")
    public void after3(){
        System.out.println("표 사고 나서 3");
    }
}

다른 어스팩트 하나를 추가했습니다. 포인트컷은 이전 어스팩트와 같은 곳으로 지정했습니다. 결과는 다음과 같습니다.

이 두개의 어스팩트를 다음과 같은 순서로 등록해 두었습니다.
<bean id="anotherCinemaAspect" class="aop.newStyle.aspect.AnotherCinamaAspect" />
<bean id="cinemaAspect" class="aop.newStyle.aspect.CinemaAspect" />

결과는 다음과 같습니다.
표 팔기 전 3
표 팔기 전 1
표 팔기 전 2
표 팔고 나서 1
표 팔고 나서 2
표 팔고 나서 3

두 어스팩트를 등록한 순서를 바꿨더니 결과가 아래 처럼 바꼈습니다.
표 팔기 전 1
표 팔기 전 2
표 팔기 전 3
표 팔고 나서 3
표 팔고 나서 1
표 팔고 나서 2
before는 먼저 등록 bean에 있는 걸 먼저 실행하고 after는 나중에 등록된 bean에 있는 걸 먼저 실행합니다. 같은 bean 안에서는 before든 after든 등록 된 순서대로 실행됩니다.
두 개의 어스팩트 적용 순서를 Ordered 인터페이스나 @Order 어노테이션을 사용해서 getValue에서 낮은 값을 리턴할 수록 높은 우선 순위를 갖게 할 수 있다고 하는데 흠.. 테스트를 해봤는데 잘 안되는군요.

그렇게 하지 않아도 어차피 설정 파일에서 bean의 등록 순서가 위에 있을 수록 우선 순위가 높다는 걸 유념하면 되겠습니다.
top


Advice parameters

Spring/Chapter 6 : 2007.04.03 15:33


예전에 공부할 때 여기를 공부하다가 멈췄었네요.
Advice parameters 1 (2)
드디어 따라잡았습니다. 흐흣.

1. Access to the current JoinPoint

해당 Advice가 끼어드는 joinpoint에 대한 정보를 얻어 올 수 있습니다. @Around 어드바이스를 제외한 다른 어드바이스들의 첫번째 파라미터로 JoinPoint 를 만들어 주면 됩니다.

    @Before("aop.newStyle.aspect.CinemaAspect.sellTicketPointcut()")
    public void weblcomeAdvice(JoinPoint jp){
        System.out.println(jp.getClass());
        System.out.println("안녕하세요. 어떤 영화를 보시겠습니까?");
    }

실제 객체는 MethodInvocationProceedingJoinPoint 이 클래스라는 걸 확인할 수 있습니다.

@Around 어드바이는 이전 글에서 봤듯이 ProceedingJoinPoint가 와야 합니다.

2. Passing parameters to advice

    @Pointcut("execution(aop.newStyle.domain.Ticket *.sell*(..))")
    public void sellTicketPointcut() {
    }

    @Before("aop.newStyle.aspect.CinemaAspect.sellTicketPointcut() &&" +
            "args(movie, ..)")
    public void weblcomeAdvice(JoinPoint jp, Movie movie){
        System.out.println("안녕하세요." + movie.getName() + " 을(를) 보시겠습니까?");

이렇게 args()를 사용하여 대상 메소드가 받을 인자를 가져올 수 있습니다.

다른 방법으로는 @Pointcut을 정의할 때 args()를 사용해서 다음과 같이 하는 건데요. IllegalArgumentExecption이 발생하는데 아직 원인을 모르겠습니다. 레퍼런스에 나와있는 코드와 똑같이 친것 같은데 말이죠. ㅠ.ㅠ

3. Determining argument names

공부해야 할 부분

4. Proceeding with arguments

@Around 에서 proceed()를 실행 할 때 들어갈 인자값을 바꿀 수 있는데요 그때는 proceed(Object[]) 메소드를 사용하면 됩니다.

@Around("aop.newStyle.aspect.CinemaAspect.sellTicketPointcut() && " +
            "args(movie,..)")
    public Object ticketAround(ProceedingJoinPoint pjp, Movie movie) throws Throwable{
        System.out.println("안녕하세요." + movie.getName() + " (을)를 보시겠습니까?");
        Ticket ticket = (Ticket)pjp.proceed(new Object[]{movie, new Date()});
        System.out.println(ticket.getMovie().getName() + " 를 구매하셨습니다.");
        System.out.println("감사합니다. 다음에 또 오세요.");
        ticket.getMovie().setName("바뀐 영화 이름");
        return ticket;
    }

top


6.2. @AspectJ support

Spring/Chapter 6 : 2007.04.03 14:10


@AspectJ 는 Java 5에 추가된 어노테이션을 사용하여 aspect를 정의하는 스타일을 말하며 이것은 AspectJ 라이브러리를 사용하여 AspectJ 5와 동일한 방법으로 aspect와 pointcut을 정의할 수 있습니다.

하지만 Spring AOP를 사용하여 실행하기 때문에 AspectJ의 weaver나 complier는 사용하지 않아도 됩니다. 그것들을 사용하면 AspectJ의 모든 기능을 사용할 수 있습니다.

6.2.1. Enabling @AspectJ Support

autoproxing을 사용하여 자동으로 aspect가 적용될 bean의 프록시를 만들게 됩니다. 이렇게 하려면 설정파일에 간단한 설정을 추가해 주면 됩니다.

스키마 기반의 XML에서는 다음의 한 줄을 추가하면 됩니다.
<aop:aspectj-autoproxy/>

DTD 기반의 XML에서는 다음과 같이 적어 줍니다.
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />

아마도 내용은 동일한데 스키마 기반이라 네임스페이스를 달리하여 좀 더 간단하게 선언할 수 있는듯 합니다.

XML 스키마(Schema) (2)

6.2.2. Declaring an aspect

@Aspect를 클래스 선언 위에 붙여 줍니다. 그리고 bean으로 등록하여 주기만 하면BeanPostProcessor가 알아서 찾아서 Spring AOP 설정으로 사용하게 될 겁니다.

물론 일반 클래스기 때문에 평범한 클래스처럼 사용할 수 있습니다. 아마도 그렇기 때문에 투명성(transparency)을 보장하게 됐다는 것이 아닌가 싶습니다. 자기 자신이 Aspect로 사용되지만 일반 클래스와 다를바가 없기 때문이죠.(물론 어노테이션이 좀 붙는 건 어쩔 수 없겠지만요.ㅋㅋ)

Enabling @AspectJ Support

6.2.3. Declaring a pointcut

pointcut 선언은 두 부분으로 나눠집니다. pointcut 서명(signature) 부분과 표현(expression) 부분입니다.
서명 부분은 일반 메소드 선언 부분과 동일합니다.(반드시 리턴타입이 void 여야 합니다.)
표현 부분은 @Pointcut 을 사용하며 AspectJ 5의 형식을 사용합니다.

초간단 @Pointcut 과 @Advice 예제
@Pointcut Designator

6.2.4. Declaring advice

pointcut 표현식과 함께 사용할 수 도 있고 pointcut 이름을 사용하여 특정 메소드의 실행 전, 후, 예외가 발생했을 때 등에 할일을 추가할 수 있습니다.

@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
이런식으로 @Before 를 사용하여 before advice를 만들 때 이 advice가 적용될 포인트 컷의 이름을 줄 수 있으며

@Before("execution(* com.xyz.myapp.dao.*.*(..))")
이렇게 직접 pointcut 표현식을 사용하여 advice가 적용될 지점을 나타낼 수도 있습니다.(in-place pointcut)

같은 pointcut이 여러 advice에서 사용될 거라면 이름을 사용하는 방법이 좋겠죠.

@AfterReturning 어드바이스 만들기
@Around 어드바이스 예제
Advice parameters
Advice ordering

6.2.5. Introductions

@DeclareParents 을 사용하여 선언하며 지정된 타입들이 새로운 부모 클래스를 상속 받도록 합니다.

새로 추가 할 메소드나 필드를 모아놓은 인터페이스를 만들고 그 인터페이스를 구현한 클래스를 만듭니다. 그리고 이 인터페이스의 기능을 추가할 대상 클래스들을 지정해 주고 구현한 클래스를 묶어 주면 대상 클래스들도 해당 인터페이스 타입으로 사용할 수 있게 됩니다. 즉 새로운 메소드와 필드를 추가적으로 사용할 수 있게 됩니다.

Introduction 예제

6.2.6. Aspect instantiation models

@Aspect의 속성으로 perthis와 pertarget을 사용할 수 있습니다.

perthis는 해당 포인트컷에 매칭되는 메소드를 실행할 때 새로운 Aspect 객체를 만들고 그 메
소드를 가진 객체와 연관을 맺게 됩니다. 즉 타겟 객체와 묶이게 되는거죠. 해당 타겟 객체와 연관된 aspect 객체가 만들어 지기 전가진 advice가 적용되지 않습니다.

pertarget는 perthis와 다른건 포인트컷에 매칭되는 메소드를 가진 객체들 마다 새로운 aspect객체를 만들지 않고 딱 하나만 만든다는 것만 다르고 나머진 같습니다.

6.2.7. Example

around advice를 사용하여 PessimisticLockingFailureException을 잡아서 처리하고 사용자에게 안 보이도록 하는 aspect를 만들어 봅니다.

'Spring > Chapter 6' 카테고리의 다른 글

Schema 기반 Spring AOP 희한한 것  (0) 2007.04.05
초간단 Schema 기반 Spring AOP  (0) 2007.04.05
Introduction 예제  (0) 2007.04.04
Advice ordering  (0) 2007.04.04
Advice parameters  (0) 2007.04.03
6.2. @AspectJ support  (0) 2007.04.03
@Around 어드바이스 예제  (0) 2007.04.03
@AfterReturning 어드바이스 만들기  (0) 2007.04.03
@Pointcut Designator  (0) 2007.04.02
@Aspect 붙은 클래스끼리는 상속이 안 됩니다.  (0) 2007.04.02
초간단 @Pointcut 과 @Advice 예제  (0) 2007.04.02
top


@Around 어드바이스 예제

Spring/Chapter 6 : 2007.04.03 14:07


이전까지 사용했던 @Before와 @AfterReturning 대신 @Around 어드바이스 하나로 기존에 했던 인사말들을 모두 처리할 수 있습니다.

더불어 리턴값도 변경할 수 있으며 대상 메소드의 실행 여부도 결정할 수 있습니다. 제약조건은 어드바이스의 메소드 첫번째 인자로 ProceedingJoinPoint 가 와야 하며 실제로는 이 인터페이스를 구현한 MethodInvocationProceedingJoinPoint 이녀석의 객체가 넘어옵니다.
    @Around("aop.newStyle.aspect.CinemaAspect.sellTicketPointcut()")
    public Object ticketAround(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("안녕하세요. 어떤 영화를 보시겠습니까?");
        Ticket ticket = (Ticket)pjp.proceed();
        System.out.println(ticket.getMovie().getName() + " 를 구매하셨습니다.");
        System.out.println("감사합니다. 다음에 또 오세요.");
        return ticket;
    }

이렇게 만들면 이전에 만들었던 @Before와 @AfterReturning을 대체할 수 있는 어드바이스를 만들 수 있습니다.

신기한게 있는데요. 대상이 되는 메소드를 실행하고 그 결과 리턴되는 값을 바로 넘겨 주었더니 원래 넘겨줬던 티켓 객체와 같은 객체가 맞습니다.
    @Test
    public void sellTicket() {
        hein.buyTicket(cinema, movie);
        Ticket ticket = hein.getTicket();
        Movie movie2 = ticket.getMovie();
        assertEquals(movie2, movie);
    }

하지만 중간에 객체의 속성을 바꾼다던가 하면...다른 객체가 됩니다. 즉 @Around 에서 리턴값의 속성을 다음 처럼 바꾸는 일을 합니다.

    @Around("aop.newStyle.aspect.CinemaAspect.sellTicketPointcut()")
    public Object ticketAround(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("안녕하세요. 어떤 영화를 보시겠습니까?");
        Ticket ticket = (Ticket)pjp.proceed();
        System.out.println(ticket.getMovie().getName() + " 를 구매하셨습니다.");
        System.out.println("감사합니다. 다음에 또 오세요.");
        ticket.getMovie().setName("바뀐 영화 이름");
        return ticket;
    }

그리고 다음과 같은 테스트를 통해서 리턴값이 원래 객체와 다른 객체가 되고 값도 바뀐 것을 확인할 수 있습니다.
    @Test
    public void sellTicket() {
        hein.buyTicket(cinema, movie);
        Ticket ticket = hein.getTicket();
        Movie movie2 = ticket.getMovie();
        assertNotSame(movie2, movie);
        assertEquals("바뀐 영화 이름", movie2.getName());
    }


'Spring > Chapter 6' 카테고리의 다른 글

초간단 Schema 기반 Spring AOP  (0) 2007.04.05
Introduction 예제  (0) 2007.04.04
Advice ordering  (0) 2007.04.04
Advice parameters  (0) 2007.04.03
6.2. @AspectJ support  (0) 2007.04.03
@Around 어드바이스 예제  (0) 2007.04.03
@AfterReturning 어드바이스 만들기  (0) 2007.04.03
@Pointcut Designator  (0) 2007.04.02
@Aspect 붙은 클래스끼리는 상속이 안 됩니다.  (0) 2007.04.02
초간단 @Pointcut 과 @Advice 예제  (0) 2007.04.02
Enabling @AspectJ Support  (0) 2007.04.02
top


@AfterReturning 어드바이스 만들기

Spring/Chapter 6 : 2007.04.03 12:15


제일 간단한 @AfterrReturning 어드바이스는 다음과 같습니다.
    @AfterReturning("aop.newStyle.aspect.CinemaAspect.sellTicketPointcut()")
    public void thanksAdvice(){
        System.out.println("감사합니다. 다음에 또 오세요.");
    }

@AfterReturning 어드바이스는 대상 메소드의 리턴값을 받아 올 수 있습니다. @AfterReturning의 속성인 returning을 사용하면 됩니다. 다음과 같이 작성할 수 있습니다.
    @AfterReturning(pointcut="aop.newStyle.aspect.CinemaAspect.sellTicketPointcut()", returning="ticket")
    public void thanksAdvice(Ticket ticket){
        System.out.println(ticket.getMovie().getName() + " 를 구매하셨습니다.");
        System.out.println("감사합니다. 다음에 또 오세요.");
    }

이렇게 하면 될 것 같지만...에러가 나지요. asm 라이브러리를 추가해줘야 합니다.
사용자 삽입 이미지

저 두개의 라이브러리를 프로젝트에 추가하면 돌아갑니다~
안녕하세요. 어떤 영화를 보시겠습니까?
공공의적 를 구매하셨습니다.
감사합니다. 다음에 또 오세요.

'Spring > Chapter 6' 카테고리의 다른 글

Introduction 예제  (0) 2007.04.04
Advice ordering  (0) 2007.04.04
Advice parameters  (0) 2007.04.03
6.2. @AspectJ support  (0) 2007.04.03
@Around 어드바이스 예제  (0) 2007.04.03
@AfterReturning 어드바이스 만들기  (0) 2007.04.03
@Pointcut Designator  (0) 2007.04.02
@Aspect 붙은 클래스끼리는 상속이 안 됩니다.  (0) 2007.04.02
초간단 @Pointcut 과 @Advice 예제  (0) 2007.04.02
Enabling @AspectJ Support  (0) 2007.04.02
6.3. Schema-based AOP support  (0) 2007.04.01
top


@Pointcut Designator

Spring/Chapter 6 : 2007.04.02 19:02


@AspectJ의 Pointcut 살펴보기 1
@AspectJ의 Pointcut 살펴보기 2

이전 글에서 정리를 했지만 좀 더 간단하게 정리 하겠습니다. pointcut 종류에 따라 표현식이 조금씩 다르지만 크게 세분류로 나눌 수 있습니다.
  • execution(method signature), call(method signature)
  • target(type signature), this(type signature), agrs(type signature)
  • @target(annotation name), @args(annotation name), @within(annotation name), @annotation(annotation name)
1. method signature는 다음과 같습니다.
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)
         throws-pattern?)

접근지시자 패턴(modifiers-pattern)은 생략 가능합니다.
retyrn type 패턴은 * 을 사용하여 모든 리턴타입을 나타낼 수 있습니다.
타입 패턴(declaring-type-pattern)역시 생략 가능합니다.
메소드 이름 패턴(name-pattern)은 타입패턴과 붙여서 사용 합니다.

execution( aop.newStyle.domain.Ticket *.sell*(..))
=> 어떤 접근지시자든 상관없고 Ticket을 리턴하는 sell로 시작하는 메소드

execution(public aop.newStyle.domain.Ticket *.sell*(..))
=> public 접근지시자를 가지고 있고 Ticket을 리턴하고 sell로 시작하는 메소드

그럼 default 접근지시자는 어떻게 표현하지? - 접근지시자를 생략하면 모든 접근지시자로 인식하고 써주면 해당 접근지자면.. default라고 써주면 에러가 납니다. 흠.. 어떻게 하지..;;

execution(* set*(..))
=> 접근지시자와 리턴타입은 상관없고 set으로 시작하는 모든 메소드

2. type signature는 method signature에서dml type pattern과 같습니다.
AspectJ에서 this와 target은 같은 객체를 나타내지만 Spring AOP에서는 프록시를 사용하고 있기 때문에 this는 프록시 객체를 나타내고 target은 프록시 객체가 감싸는 객체를 나타냅니다. 즉 다른 객체 입니다.

3. annotaion name은 제일 상단 링크 중 두번째 것을 참조하시면 알 수 있습니다.

현재까지는 위에 적혀있는 designator들만 지원이 되며 차후에 AspectJ에 있는 다른 designator들(call, initialization, preinitialization, staticinitialization, get, set, handler, adviceexecution, withincode, cflow, cflowbelow, if, @this, @withincode)도 추가될 예정입니다.

'Spring > Chapter 6' 카테고리의 다른 글

Advice ordering  (0) 2007.04.04
Advice parameters  (0) 2007.04.03
6.2. @AspectJ support  (0) 2007.04.03
@Around 어드바이스 예제  (0) 2007.04.03
@AfterReturning 어드바이스 만들기  (0) 2007.04.03
@Pointcut Designator  (0) 2007.04.02
@Aspect 붙은 클래스끼리는 상속이 안 됩니다.  (0) 2007.04.02
초간단 @Pointcut 과 @Advice 예제  (0) 2007.04.02
Enabling @AspectJ Support  (0) 2007.04.02
6.3. Schema-based AOP support  (0) 2007.04.01
6.1. Introduction  (0) 2007.04.01
top


@Aspect 붙은 클래스끼리는 상속이 안 됩니다.

Spring/Chapter 6 : 2007.04.02 15:53


이전 글을 작성하다가 문든 들은 생각(@Aspect 가 붙은 클래스들끼리 상속을 하면 포인트컷과 어드바이스를 상속할 수 있는가?)을 실험해봤습니다.
//CinamaAspect
@Aspect
public class CinemaAspect {

    @Pointcut("execution(* sellTicket(..))")
    public void sellTicketPointcut() {
    }

    @Before("aop.newStyle.aspect.ChildAspect.sellTicketPointcut()")
    public void weblcomeAdvice(){
        System.out.println("안녕하세요. 어떤 영화를 보시겠습니까?");
    }
}

//ChildAspect
@Aspect
public class ChildAspect extends CinemaAspect {

}

그리고 이전에 테스트를 돌리면 다음과 같은 에러메시지를 볼 수 있습니다.

org.springframework.beans.factory.BeanCreationException: Error creating bean with name '혜인' defined in class path resource [aop/newStyle/watchingMovieConfiguration.xml]: Cannot resolve reference to bean 'KCcard' while setting bean property 'card'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'KCcard' defined in class path resource [aop/newStyle/watchingMovieConfiguration.xml]: Initialization of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: aop.newStyle.aspect.ChildAspect cannot extend concrete aspect aop.newStyle.aspect.CinemaAspect
...

concrete aspect를 상속할 수 없다고 하길래 인터페이스를 만들어서 실험해 봤습니다.
//AbstractAspect
@Aspect
public interface AbstractAspect {
    @Pointcut("execution(* sellTicket(..))")
    public void sellTicketPointcut();
}

//ChildAspect
@Aspect
public class ChildAspect implements AbstractAspect {

    /* (non-Javadoc)
     * @see aop.newStyle.aspect.AbstractAspect#sellTicketPointcut()
     */
    public void sellTicketPointcut() {
    }
}

//설정파일
    <bean id="cinemaAspect" class="aop.newStyle.aspect.CinemaAspect" />
    <bean id="childAspect" class="aop.newStyle.aspect.ChildAspect" />
    <bean id="abstractAspect" class="aop.newStyle.aspect.AbstractAspect" abstract="true"/>

하지만 역시 에러가 납니다.
org.springframework.beans.factory.BeanCreationException: Error creating bean with name '혜인' defined in class path resource [aop/newStyle/watchingMovieConfiguration.xml]: Cannot resolve reference to bean 'KCcard' while setting bean property 'card'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'KCcard' defined in class path resource [aop/newStyle/watchingMovieConfiguration.xml]: Initialization of bean failed; nested exception is java.lang.IllegalArgumentException: error at ::0 can't make static reference to abstract pointcut
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'KCcard' defined in class path resource [aop/newStyle/watchingMovieConfiguration.xml]: Initialization of bean failed; nested exception is java.lang.IllegalArgumentException: error at ::0 can't make static reference to abstract pointcut
Caused by: java.lang.IllegalArgumentException: error at ::0 can't make static reference to abstract pointcut...

흠.. ChildAspect에 있는 @Aspect 를 제거하고 다시 상속을 사용해봤습니다.
//CinemaAspect
@Aspect
public class CinemaAspect {

    @Pointcut("execution(* sellTicket(..))")
    public void sellTicketPointcut() {
    }

    @Before("aop.newStyle.aspect.ChildAspect.sellTicketPointcut()")
    public void weblcomeAdvice(){
        System.out.println("안녕하세요. 어떤 영화를 보시겠습니까?");
    }
}

//ChildAspect
public class ChildAspect extends CinemaAspect {

}

이렇게 하니까 제대로 동작을 합니다. 포인트컷은 상속이 되는 걸 확인했습니다. 하지만..advice를 ChileAspect로 옮겨 놓으니까 제대로 동작하지 않네요. 당연히...안되죠. ChileAspect는 이름만 Aspect일뿐 일반 클래스기 때문에 @Advice가 붙어 있다 하더라도 인식하지 않습니다.


'Spring > Chapter 6' 카테고리의 다른 글

Advice parameters  (0) 2007.04.03
6.2. @AspectJ support  (0) 2007.04.03
@Around 어드바이스 예제  (0) 2007.04.03
@AfterReturning 어드바이스 만들기  (0) 2007.04.03
@Pointcut Designator  (0) 2007.04.02
@Aspect 붙은 클래스끼리는 상속이 안 됩니다.  (0) 2007.04.02
초간단 @Pointcut 과 @Advice 예제  (0) 2007.04.02
Enabling @AspectJ Support  (0) 2007.04.02
6.3. Schema-based AOP support  (0) 2007.04.01
6.1. Introduction  (0) 2007.04.01
Spring 2.0 AOP  (2) 2007.03.27
top


초간단 @Pointcut 과 @Advice 예제

Spring/Chapter 6 : 2007.04.02 15:28


@Aspect
public class CinemaAspect {

    @Pointcut("execution(* sellTicket(..))")
    public void sellTicketPointcut() {
    }

    @Before("aop.newStyle.aspect.CinemaAspect.sellTicketPointcut()")
    public void weblcomeAdvice(){
        System.out.println("안녕하세요. 어떤 영화를 보시겠습니까?");
    }
}

설정파일에 이 Aspect를 bean으로 등록합니다. pointcut의 이름에 해당하는 public void sellTicketPointcut() 부분에서 리턴타입은 항상 void여야 합니다. 접근지시자는 다른 Aspect에서 해당 포인트컷을 참조 할 수 있는 범위를 나타내게 됩니다. 일반적인 메소드 접근지시자와 동일하게 사용됩니다.

@Aspect 가 붙은 클래스들 끼리 상속을 하게 되면 어떻게 되나? - 포인트 컷과 어드바이스도 상속이 되는건가? 일단 지금은 초간단 이니까 바로 다음 글에 이어서 실험하도록 하겠습니다. [각주:1]

    <!-- @Aspect 시작 -->
    <aop:aspectj-autoproxy/>

    <bean id="myAspect" class="aop.newStyle.aspect.CinemaAspect" />

테스트 코드에서 sellTicket을 호출하게 되는 buyTicket을 호출 합니다.

    @Test
    public void sellTicket() {
        hein.buyTicket(cinema, movie);
    }

안녕하세요. 어떤 영화를 보시겠습니까?

원하는 결과를 확인할 수 있습니다.

  1. 이건 마치 무슨 '호기심 천국'이나 '스펀지 연구소'가 된 분위기가 납니다. 물론 쏠로로 진행하지만 말이죠.ㅋㅋ [본문으로]

'Spring > Chapter 6' 카테고리의 다른 글

6.2. @AspectJ support  (0) 2007.04.03
@Around 어드바이스 예제  (0) 2007.04.03
@AfterReturning 어드바이스 만들기  (0) 2007.04.03
@Pointcut Designator  (0) 2007.04.02
@Aspect 붙은 클래스끼리는 상속이 안 됩니다.  (0) 2007.04.02
초간단 @Pointcut 과 @Advice 예제  (0) 2007.04.02
Enabling @AspectJ Support  (0) 2007.04.02
6.3. Schema-based AOP support  (0) 2007.04.01
6.1. Introduction  (0) 2007.04.01
Spring 2.0 AOP  (2) 2007.03.27
Aspect Oriented Programming with Spring  (0) 2007.03.26
top


Enabling @AspectJ Support

Spring/Chapter 6 : 2007.04.02 14:50


@AsprctJ 사용을 위한 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-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

    <!-- @Aspect 시작 -->
    <aop:aspectj-autoproxy/>

</beans>

찐한 색에 해당하는 부분이 기본적인 XML 설정에 추가 되는 부분입니다.

그리고 두개의 jar파일을 추가해 주면 준비가 끝납니다.
사용자 삽입 이미지

'Spring > Chapter 6' 카테고리의 다른 글

6.2. @AspectJ support  (0) 2007.04.03
@Around 어드바이스 예제  (0) 2007.04.03
@AfterReturning 어드바이스 만들기  (0) 2007.04.03
@Pointcut Designator  (0) 2007.04.02
@Aspect 붙은 클래스끼리는 상속이 안 됩니다.  (0) 2007.04.02
초간단 @Pointcut 과 @Advice 예제  (0) 2007.04.02
Enabling @AspectJ Support  (0) 2007.04.02
6.3. Schema-based AOP support  (0) 2007.04.01
6.1. Introduction  (0) 2007.04.01
Spring 2.0 AOP  (2) 2007.03.27
Aspect Oriented Programming with Spring  (0) 2007.03.26
top


6.3. Schema-based AOP support

Spring/Chapter 6 : 2007.04.01 23:32


Java 5 를 사용하지 못하거나 단순히 XML 설정을 좋아하는 분들은 스키마 기반의 AOP 설정을 사용하여 @AspectJ에서 했던 모든 것을 할 수 있습니다.

단 aop 네임스페이스를 사용하기 위해서는 설정 파일로 스키마 기반의 XML 파일을 사용해야 합니다.
<!-- XML Schema-style -->
<?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-2.0.xsd">

<!-- <bean/> definitions here -->

</beans>

aop 설정은 모두 <aop:config> </aop:config> 안에 들어가야 하며 이 태그는 <beans> </beans> 내부에서 여러 번 사용할 수 있습니다.

초간단 Schema 기반 Spring AOP

6.3.1. Declaring an aspect

aspect는 일반 클래스로써 그냥 bean으로 등록을 해둡니다. 포인트컷과 어드바이스에 대한 정보는 XML로 나타낼 수 있습니다.

포인트 컷은 @Pointcut의 표현식을 그대로 xml 설정에 적어 주면 되고, advice로 사용할 메소드의 이름을 역시 xml에 적어 주면 될 것 같습니다.

어찌됐든 aspect 역할을 할 bean을 <aop:config> 안에 <aop:aspect> 태그의 ref 속성으로 정의합니다.

Schema 기반 Spring AOP 희한한 것
Schema 기반 Spring AOP 포인트컷 만들 때 주의 할 것

6.3.2. Declaring a pointcut

pointcut은 <aop:config> 또는 <aop:aspect> 태그 안에 <aop:pointcut> 태그를 사용하여 정의할 수 있습니다.

<aop:aspect> 로 정의하면 그 aspect 내에서만 사용할 수 있으며 <aop:config> 바로 하위에 정의하면 여러 aspect에서 사용할 수 있습니다.

6.3.3. Declaring advice

@AspectJ 에서 사용 할 수 있었던 다섯 가지의 Advice 모두 정의할 수 있습니다.
<aop:after-returning> 어드바이스에서 리턴값 받아오기
<aop:around> 어드바이스 예제
Schema 기반 Advice parameters

6.3.4. Introductions

<aop:declare-parents> 를 사용합니다.

<aop:declare-parents
      types-matching="com.xzy.myapp.service.*+",
      implement-interface="UsageTracked"
      default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>
 
  <aop:before
    pointcut="com.xyz.myapp.SystemArchitecture.businessService()
              and this(usageTracked)"
    method="recordUsage"/>

Schema 기반 Introduction

6.3.5. Aspect instantiation models

스키마 기반에서 제공되는 건 singleton 모델 뿐이고 차후에 나머지도 제공될 예정입니다. 앞에서 살펴봤던 @Aspect 기반에서는 perTarget(singleton) 또는 perThis(peototype) 속성을 사용하여 선택할 수 있었습니다.

6.3.6. Advisors

Advisor 컨셉은 Spring 1.2에서 왔으며 AspectJ에 있는 개념은 아닙니다. AspectJ 스타일의 Pointcut 표현식을 사용할 수 있다는 장점이 있으며 Advisor에서 합 칠 Advice bean은 반드시 이전 유형의 Advice들이 구현해야 했던 인터페이스들을 구현하고 있어야 합니다.

6.3.7. Example

이전 글
의 예제를 스키마 기반으로 다시 작성합니다.

'Spring > Chapter 6' 카테고리의 다른 글

6.2. @AspectJ support  (0) 2007.04.03
@Around 어드바이스 예제  (0) 2007.04.03
@AfterReturning 어드바이스 만들기  (0) 2007.04.03
@Pointcut Designator  (0) 2007.04.02
@Aspect 붙은 클래스끼리는 상속이 안 됩니다.  (0) 2007.04.02
초간단 @Pointcut 과 @Advice 예제  (0) 2007.04.02
Enabling @AspectJ Support  (0) 2007.04.02
6.3. Schema-based AOP support  (0) 2007.04.01
6.1. Introduction  (0) 2007.04.01
Spring 2.0 AOP  (2) 2007.03.27
Aspect Oriented Programming with Spring  (0) 2007.03.26
top


6.1. Introduction

Spring/Chapter 6 : 2007.04.01 00:11


선언적 트랜잭선 관리(declarative transaction management)와 같이 EJB를 대체할 만한 선언적인 엔터프라이즈 서비스를 제공하기 위해 사용하며, 이것으로 OOP를 보완 하기 위해 사용자 정의 apect를 만들어 사용할 때 유용합니다.

6.1.1. AOP concepts

Aspect : 여러 객체를 관통하는 cross-concern을 구현한 것.
Join Point : 프로그램이 실행되는 동안의 여러 시점들.. 그 중에서 Spirng AOP는 메소드 실행 시점 만을 나타냅니다.
Advice : Aspect에 의해 제공되어 특정 join point에 끼어들어 수행하게 될 작업.
Pointcut : 특정 Join point들을 나타내는 표현 방법.
Introduction : 프록시 객체에 추가적인 메소드나 필드를 추가하는 것.
Target object : 하나 또는 여러개의 aspect에 의해 advice가 적용될 객체.
AOP proxy : Aspect를 구현하기 위해 Spring 프레임웤에서 만들어 내는 객체로, JDK의 Proxy인터페이스 또는 CGLib을 사용합니다. Spring 2.0을 사용하면 투명성을 유지하면서[각주:1] 프록시를 만들 수 있습니다.
Weaving : Aspect를 target 객체에 적용하는 걸 말하며, 컴파일, 클래스 로드, 런타임 때 할 수 있으나 Spring AOP는 런타임 때 합니다.

Advice Type
Before advice : 해당 join point 전에 실행 할 adive.
After returning advice : 해당 join point가 정상적으로 종료 된 뒤에 실행 할 advice
After throwing advice : 해당 join point에서 예외가 발생했을 때 실행 할 advice
After (finally) advice :  해당 join point가 정상적으로 끝나든 예외가 발생하든 무조건 실행 할 advice
Around advice :  위에서 언급한 모든 adivce의 일을 할 수 있고 해당 join point의 실행 여부도 결정할 수 있으며 join point가 반환할 값을 조작할 수 도 있습니다.

Using the most specific advice type provides a simpler programming model with less potential for errors.

6.1.2. Spring AOP capabilities and goals

Spring AOP는 자바로 구현했기 때문에 별도의 컴파일 처리가 필요로 하지 않습니다.

Spring AOP는 위에서도 잠시 언급했지만 메소드 실행 join point만 제공합니다.

완벽한 AOP구현이 목적이 아니라 다른 AOP 구현체와의 통합이 목표입니다. 하지만 대부분의경우 Spring AOP만으로도 충분합니다.

AspectJ와 경쟁을 하려는 것이 아니고 프록시 기반의 Spring AOP와 완전한 AOP 프레임워크인 AspectJ는 서로 상호 보완의 관계입니다.

6.1.3. AOP Proxies


6.6 에서 더 자세히 살펴 보겠지만 기본적으로 J2SE의 dynamic proxy[각주:2]를 사용합니다.

그밖에 CGLIB proxy를 사용하게 될 경우는 다음과 같습니다.
인터페이스를 구현하지 않은 클래스의 프록시를 만들 때
인터페이스에 없는 메소드를 가진 클래스의 프록시를 만들고 싶을 때
메소드의 인자로 인터페이스 타입이 아닌 특정 클래스 타입의 프록시를 넘겨주고 싶을 때
  1. 이전에는 일일히 ProxyFactoryBean을 사용하여 만들어 줬었는데 이제는 그런 일을 안해도 된다는 의미 인듯 합니다. [본문으로]
  2. 인터페이스를 사용하여 프록시를 만듭니다. [본문으로]

'Spring > Chapter 6' 카테고리의 다른 글

6.2. @AspectJ support  (0) 2007.04.03
@Around 어드바이스 예제  (0) 2007.04.03
@AfterReturning 어드바이스 만들기  (0) 2007.04.03
@Pointcut Designator  (0) 2007.04.02
@Aspect 붙은 클래스끼리는 상속이 안 됩니다.  (0) 2007.04.02
초간단 @Pointcut 과 @Advice 예제  (0) 2007.04.02
Enabling @AspectJ Support  (0) 2007.04.02
6.3. Schema-based AOP support  (0) 2007.04.01
6.1. Introduction  (0) 2007.04.01
Spring 2.0 AOP  (2) 2007.03.27
Aspect Oriented Programming with Spring  (0) 2007.03.26
top


Spring 2.0 AOP

Spring/Chapter 6 : 2007.03.27 18:33


참조 : 3월달 마소 AOP 특집 중 토비님의 Spring 2.0 AOP

AOP를 구현 하는 방법.

1. AOP 언어 자체를 확장하는 방법 - AspectJ - 전용 컴파일러 필요함.
2. 그냥 자바 클래스에 설정파일이나 어노테이션을 사용하는 방법
    2-1. 컴파일 된 클래스를 변환하는 방법
    2-2. 클래스 로딩 시 바이트 코드를 조작하는 방법
    2-3. 순수한 자바 언어와 API만을 사용하는 방법 - Spring AOP

Spring AOP는 프록시 기반의 AOP 구현 방법을 사용.
1. 인터페이스에 대한 프록시 만들 때 - JDK의 Dynamic Proxy사용
2. 클래스에 대한 프록시 만들 때 - CGLib 사용

기존 Spring AOP.
1. 포인트컷(Pointcut)
    조인 포인트-Spring AOP는 메소드 실행 시점만 지원-의 묶음
    Spring 1.X 버전에서는 스태틱 포인트컷과 다이내믹 포인트컷 방식이 있다.

2. 어드바이스(Advice)
    Interception arount 어드바이스 : 메소드 실행 전 후 모두, 대상이 되는 메소드의 실행 여부 결정 가능 (ex, MethodInterceptor)
    Before 어드바이스 : 메소드 실행 전에
    Throws 어드바이스 : 메소드에서 예외 발생 했을 때
    After Return 어드바이스 : 메소드 실행 후에
    Introduction 어드바이스 : 기본의 클래스에 동적으로 필드나 메소드 추가

3. 어드바이저(Advisor)
    Advice + Pointcut = Aspect = 어드바이져(ex. DefaultPointcutAdvisor)

4. 프록시
    ProxyFactoryBean : target, proxyIntergace, interceptorNames 속성을 주입하여 사용.
    AutoProxyCreator(ex. BeanNameAutoProxyCreator) : BeanPostProcessor를 사용하여 임의의 빈에 다이내믹하게 프록시 설정 부여함.

5. 한계와 단점
    5-1. Pointcut 인터페이스 구현해야함. 포인트컷 표현, 적용이 어렵다.
    5-2. XML 설정이 복잡해 진다.
    5-3. IoC 컨테이너에 빈으로 등록되지 않은 객체는 적용할 수 없다.
    5-4. 약간의 성능저하
    5-5. 타깃 객체와 프록시가 분리되어 있다,

Spring 2.0 AOP.
1. AspectJ의 애스펙트 선언과 포인트컷 언어 도입
    한계와 단점에서 첫 번째와 두 번째 문제 해결

2. AspectJ AOP 이용방법
    AspectJ를 사용하여 세번째 문제 해결 가능.(@Configurable 사용)
    2-1. Spring 설정과 무관하게 사용.
    2-2. 에스팩트 정의를 빈으로 등록.(factory-method="aspectOf" 사용)

'Spring > Chapter 6' 카테고리의 다른 글

6.2. @AspectJ support  (0) 2007.04.03
@Around 어드바이스 예제  (0) 2007.04.03
@AfterReturning 어드바이스 만들기  (0) 2007.04.03
@Pointcut Designator  (0) 2007.04.02
@Aspect 붙은 클래스끼리는 상속이 안 됩니다.  (0) 2007.04.02
초간단 @Pointcut 과 @Advice 예제  (0) 2007.04.02
Enabling @AspectJ Support  (0) 2007.04.02
6.3. Schema-based AOP support  (0) 2007.04.01
6.1. Introduction  (0) 2007.04.01
Spring 2.0 AOP  (2) 2007.03.27
Aspect Oriented Programming with Spring  (0) 2007.03.26
top


Aspect Oriented Programming with Spring

Spring/Chapter 6 : 2007.03.26 21:23


6.1. Introduction
AOP의 기본 개념과 Spring AOP의 기능과 목표를 이야기 합니다.
Spring AOP는 Proxy 기반입니다.

6.1. Introduction

6.2. @AspectJ support
어노테이션을 사용하여 AspectJ를 사용하는 방법입니다.
Spring AOP가 지원하는 포인트컷은 method 실행 시점뿐입니다.

6.2. @AspectJ support

6.3. Schema-based AOP support
6.2에서 한 내용을 어노테이션 기반이 아닌 XML에 설정을 사용하는 방법 도 있습니다.

6.3. Schema-based AOP support

6.4. Choosing which AOP declaration style to use
어노테이션을 사용할 것인가? XML 설정을 사용할 것인가?
Spring AOP를 사용할 것인가? AspectJ를 사용할 것인가?

6.4. Choosing which AOP declaration style to use

6.5. Mixing aspect types

어노테이션을 사용하는 방법과 XML 설정을 사용하는 방법을 섞어서 사용할 수 있습니다. 심지어 Spring 1.2 버젼 스타일의 프록시와도 같이 사용할 수 있습니다.

6.6. Proxying mechanisms
JDK 프록시 또는 CGLIB을 사용하는데요. 어떤 인터페이스도 구현하지 않았다면 CGLIB 프록시를 사용하고 그렇지 않은 경우에는 JDK 프록시를 사용합니다.

6.6. Proxying mechanisms

6.7. Programmatic creation of @AspectJ Proxies

AspectJProxyFactory를 사용하여 직접 프록시 클래스를 만들 수 있습니다.

6.8. Using AspectJ with Spring applications

AspectJ의 위버와 컴파일러를 사용할 수 있습니다.

6.9. Further Resources

AspectJ home page 여기서 더 많은 내용을 참조 할 수 있습니다.

'Spring > Chapter 6' 카테고리의 다른 글

6.2. @AspectJ support  (0) 2007.04.03
@Around 어드바이스 예제  (0) 2007.04.03
@AfterReturning 어드바이스 만들기  (0) 2007.04.03
@Pointcut Designator  (0) 2007.04.02
@Aspect 붙은 클래스끼리는 상속이 안 됩니다.  (0) 2007.04.02
초간단 @Pointcut 과 @Advice 예제  (0) 2007.04.02
Enabling @AspectJ Support  (0) 2007.04.02
6.3. Schema-based AOP support  (0) 2007.04.01
6.1. Introduction  (0) 2007.04.01
Spring 2.0 AOP  (2) 2007.03.27
Aspect Oriented Programming with Spring  (0) 2007.03.26
top