스프링 AOP에서 this와 target 포인트컷 표현식 구분 하시는 분?

Pro Spring 2.5에서 this 표현식에 대한 정의입니다.

the semantics of the this pointcut are such that it would match any method execution on an object whose class matches the specified expression, but how could we match any class in the com package and its subpackages? Therefore, the only allowed syntax is this(class-name),

Pro Spring 2.5에서 target 표현식에 대한 정의 입니다.

Because the target expression defines a pointcut that would match execution of any method on an object whose class matches the specified expression, we cannot use the wildcards.

뭐가 달라 보이나요??? 앞 부분에 어순이 약간 바뀐것 빼고는 도무지 차이를 알 수가 없습니다.

레퍼런스를 보죠.

this – limits matching to join points (the execution of methods when using Spring AOP) where the bean reference (Spring AOP proxy) is an instance of the given type

target – limits matching to join points (the execution of methods when using Spring AOP) where the target object (application object being proxied) is an instance of the given type

레퍼런스는 그나마 좀 차이가 보입니다. this일 때는 프록시 객체고 target 일 때는 프록시를 적용할 타겟 객체가 주체가 됩니다.

즉, this 표현식은 주어진 타입에 해당하는 스프링 AOP Proxy의 조인포인트에 대응하는 표현식이고.. target 표현식은 주어진 타입에 해당하는 타겟 객체의 조인포인트에 대응하는 표현식이라는 것인데..

어차피 프록시 객체의 타입이 곧 타겟 객체의 타입과 동일하기 때문에 결과는 똑같을 것 같은데 말이죠. 차이를 모르겠습니다. 아흐.. 머리야..

마지막으로 AspectJ 문서를 보겠습니다.

this(Type or Id)
    Picks out each join point where the currently executing object (the object bound to this) is an instance of Type, or of the type of the identifier Id (which must be bound in the enclosing advice or pointcut definition). Will not match any join points from static contexts.

target(Type or Id)
    Picks out each join point where the target object (the object on which a call or field operation is applied to) is an instance of Type, or of the type of the identifier Id (which must be bound in the enclosing advice or pointcut definition). Will not match any calls, gets, or sets of static members.

this는 동적이고 target은 정적으로 타입을 지칭하는 차이밖에 없어보입니다. 흠.. 과연..

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=”applicationContext.xml”)
public class FooAspectTest {

    @Autowired
    FooBean fooBean;

    @Test
    public void isProxy(){
        assertTrue(fooBean instanceof Advised);
        fooBean.hi();
    }

}

@Aspect
public class FooAspect {

    @Pointcut(“this(org.opensprout.spring.aop.thisandtarget.FooBean)“)
    public void thisPointcut(){}

    @Before(“thisPointcut()”)
    public void say(){
        System.out.println(“this pointcut executed.”);
    }

}

스프링 AOP에서는 this를 target으로 고쳐도 똑같이 동작합니다. 콘솔에 메시지가 한 번 출력되죠. 그러나 AJDT로 실행하면, this의 경우 같은 메시지가 세 번 찍히고, target의 경우 같은 메시지가 네 번찍힙니다. 일단 두 번은 클래스 로딩이나 생성자 호출 조인포인트에서 찍혔다고 생각하고, 하나는 테스트에서 hi 호출 할 때 찍힌거고 가장 의아한건 target 표현식일 때의 마지막 한 번.

디버깅을 돌려본 결과 저 테스트에 두 번 들어가던데;; 왜 그런지 몰겠네요. 아~ 미궁이로 구나..

AspectJ의 @DeclareError를 사용해서 컴파일 시점에 아키텍처 에러 검증하자.

참조: http://www.parleys.com/display/PARLEYS/Home#slide=1;title=Spring%20Architectures;talk=20676612

위 발표자료 내용 주에 아주 잼나는 코드를 건졌습니다. 지난 번 KSUG에서 발표한 내용과 겹치는데 아래 코드는 그때 제가 보여드린 코드보다 좀 더 좋은 것 같아서 가져왔습니다.

@Aspect
public class SystemArchitecture {
  @Pointcut(“execution(* configurator.*.*(..))”)
  public void configuratorLogic () {}
  @Pointcut(“execution(* dao.*.*(..))”)
  public void dao() {}
  @Pointcut(“within(*.dao.*)”)
  public void inDaoLayer() {}
  @Pointcut(“call(* *.service.*.*(..))”)
  public void callServiceLayer() {}
}
@Aspect
public class Layering {
  @DeclareError(“SystemArchitecture.inDaoLayer() && “+
   “SystemArchitektur.callServiceLayer() “)
  public static final String DAOsNotFromServices =
   “DAO must not call Service!”;
 @DeclareError(” (call(* java.sql.*.*(..)) && ” +
  “!within(*.dao.*) ) “)
  public static final String JdbcOnlyInDAOs =
   “JDBC only in DAOs!”;
}

좋은 건 이Aspectj를 사용하면 @DecalreError를 사용해서, 컴파일 시점에 아키텍처 에러를 검증할 수 있다는 것입니다. 제가 준비했던 코드는 cflow를 사용해서 런타임에 검증하는 방법이었습니다. 따라서 테스트 하지 않고 그냥 커밋하면 뭐 어떻게 찾아낼 방법이 없었습니다. 그런데 이 방법을 쓰면 코딩할 때 문제되는 코드를 발견할 수 있으니 훨씬 좋은 것 같습니다. 캬.. 귿..

AOP를 설명하는 그림 두 장

역사, 이론, 개념… 등등도 중요하지만, 무엇보다.. ‘감’이 중요한거 아닐까요. 딱 보고 ‘감’이 잡힐 만하면 충분하다고 봅니다. 그 뒤에 정말 궁금해서 역사, 이론, 개념들을 살펴보면 되겠죠. 처음부터 장황하게 이러 저러해서 이러 저러한걸 말들었고 어쩌구 저쩌구…  제가 봤었던 AOP 관련 자료 중에 가장 AOP에 대한 ‘감’을 잡게 해준 그림은 아래와 같은 그림입니다.

사용자 삽입 이미지애니매이션 기능을 사용해서, 각각의 (횡단)로직들이 여러 클래스에 분산되어 들어가는 모습을 보여주면 더 멋질 것 같습니다.

사용자 삽입 이미지캬오.. AOP에 대한 ‘감’을 잡기엔 충분한 그림이 아닌가 생각해봅니다. 이 그림을 보고도 AOP가 OO를 대체하는 프로그래밍 패러다임으로 인식한다면… 그건 좀.. 흠…

4. @AspectJ 사용하는 초간단 AOP 예제

사용자 삽입 이미지
JDK 6에서도 테스트 해봤습니다.
Spring 2.5 jar 파일을 사용했으며, AspectJ 관련 라이브러리는 lib폴더에 있는 것들을 사용했습니다.

결과화면
사용자 삽입 이미지오랜만에 배치기 노래도 듣을 수 있고 좋군요.

저 예제를 돌리고 나서 얼마나 신났었는지 그 때의 기분을 고대로 느낄 수 있었습니다.
댓글 주셔서 감사합니다.
================================================================================

Spring Reference 6장에 있는 코드들을 테스트 해보기 위해 초간단 예제를 만들어 봅니다.
참조 : http://www.infoq.com/articles/Simplifying-Enterprise-Apps

소스 코드 보기

[#M_ more.. | less.. |

// Human.java
public interface Human {    
     
public void sayName();

}


// Keesun.java

public class Keesun implements Human {

       public void sayName() {

             System.out.println(저는 ~~선입니다.”);

       }

}


//MannerAOP.java

@Aspect

public class MannerAOP {

 

       @Pointcut(“execution(public * Human.sayName(..))”)

       public void greeting() {

       }

 

       @Before(“greeting()”)

       public void hi() {

             System.out.println(만나서 ~~습니다.”);

       }

 

       @AfterReturning(“greeting()”)

       public void doBeforeOne() {

             System.out.println(“AOP ~ ~ 줍니다~”);

       }

}


//aopAppContext.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”>

 

       <!– this is the object that will be proxied by Spring’s AOP infrastructure –>

       <bean id=“keesun” class=“firstSpringAOP.Keesun” />

 

       <aop:aspectj-autoproxy />

 

       <!– this is the actual advice itself –>

       <bean id=“mannerAOP” class=“firstSpringAOP.MannerAOP” />

</beans>


// TestFirstAOP.java

public class TestFirstAOP {

       public static void main(String[] args) {

 

             BeanFactory bf = new ClassPathXmlApplicationContext(

                           “firstSpringAOP/aopAppContext.xml”);

             Human human = (Human) bf.getBean(“keesun”);

             human.sayName();

       }

}


_M#]실행 결과

만나서 반~갑~습니다.
저는 백~기~선입니다.
AOP 죽~ 여~ 줍니다~

이 프로그램이 돌아가려면 Spring을 사용하기 때문에 이 전 글에서 추가 했던 spring.jar파일과 commons-logging.jar가 필요하며 AspectJ를 사용하고 있기 때문에 ‘AspectJ 설치 폴더’/lib or Spring-with-dependencies를 설치하셨다면 ‘Spring 설치 폴더’/lib/aspectj/ 안에 있는 aspectjrt.jar 와 aspectjweaver.jar 파일을 classpath에 추가해야 합니다.

예제를 돌렸네요. 아고 기뻐라.


bl162.mp3bl163.zip


JDBC 함수 실행 전에, Hibernate 세션 플러시하기

요약/번역/참조 : Before a JDBC operation, flush the Hibernate Session (includes TSE example code)

문제 상황

ORM을 사용하는 코드와 ORM을 사용하지 않는 코드(생 SQL)를 한 뭉탱이 한 트랜잭션으로 처리할 때, 데이터베이스에 있는 데이터를 사용할 수 없는 이슈가 발생할 수 있다.

섞어서 사용해야 하는 이유

많은 엔터프라이즈 애플리케이션에서 도메인 모델을 저장하고 (심지어 복잡한 도메인 모델을) 가져올 때 ORM을 사용하고 있다.

그렇다고해서 일반 SQL을 사용하는 것이 완전히 사라지진 않을 것이며, 다음의 경우에는 일반 SQL이 필요하다.
– 테스트 코드 : ORM 툴을 사용하여 객체를 DB에 넣었을 때, SQL을 사용하여 전체 레코드 수를 가져와서 비교하는 등의 테스트를 할 때 용이하다.
– 스토어드 프로시져 사용시 : 프로젝트에서 스토어드 프로시져를 사용하는 경우가 많았고 이것을 ORM과 혼용하길 원하는 경우가 많았다. 예를 들어, 새로운 객체들을 집어넣은 뒤에, 가용한 레코드들과 새로운 넣은 레코드들의 연관이 필요한 경우가 있다.(뭔소린지..흠;;)
– 상당히 많은 객체에 대한 함수 사용시 : 백만개의 주문을 true 에서 false로 변경할 때, 저자는 ORM보다 SQL을 선호한다고 합니다.(하긴 뭐 이게 더 편할 수도 있겠지..)

ORM과 SQL 혼용시 발생하는 이슈

DB는 비어있다고 가정하고, 다음의 수도Pseudo 코드를 보자.

start transaction

create part with name Bolt
associate with ORM engine (i.e. save using entity manager)

update part set stock = 15 where name=’Bolt’

end transaction

위에서 두 수도 코드의 update문(SQL문)은 위에서 Bolt라는 부품을 Entity Manager에게 저장하라고 했는데도 불구하고 실패하게 됩니다.

Entity Manager가 저장을 요청한 순간에 부품 객체를 저장하는 것이 아니라 트랜잭션이 끝날 때 저장하기 때문입니다.

ORM의 이런 속성을 write-behind 라고 합니다. 이 개념때문에, 사용할 수 있을 것 같다고 생각한 순간인데도 실제로는 사용하지 못하는 경우가 발생합니다.(위 상황으로 유추할 때, write-behind는 DB에 접속하는 빈도수를 줄이기 위해 사용하는 기술 같습니다.)

올바른 해결책

단순하게 다음과 같이 위의 수도 코드를 두 개의 트랜잭션으로 나누는 것을 생각할 수 있습니다.

start transaction
create part with name Bolt
associate with ORM engine (i.e. save using entity manager)
end transaction

start transaction
update part set stock = 15 where name=’Bolt’
end transaction

하지만 이 것은 틀린 답입니다. 원자성을 깨트렸습니다. (그렇다면, 위의 두 트랜잭션을 nested transaction으로 가지는 하나의 트랜잭션으로 묶으면 되지 않을까?)

올바른 해결책은 SQL 쿼리를 실행하기 전에, ORM 엔진이 변경사항을 DB에 저장하도록 하는 것입니다. JPA도 하이버네이트 처럼 이런 방법을 제공하는데, 이게 바로  Flushing 입니다. 이것을 사용하여 수도 코드를 다음과 같이 고칠 수 있습니다.

start transaction

create part with name Bolt
associate with ORM engine (i.e. save using entity manager)

*** flush

update part set stock = 15 where name=’Bolt’

end transaction

해결책 적용하기

사용자 삽입 이미지수도 코드를 시퀀스 다이어그램으로 나타낸 것입니다.

자 이제 flush() 메소드를 추가해야 하는데, 어디에 추가해야 할까요? addPart() 메소드 안의 맨 뒤에 추가할까요? 아니면 updateStock() 메소드 안에서 UPDATE 문 바로 뒤에 둘 까요?

두 방법 다 안 좋습니다.
– addPart() 메소드 안에 넣는 방법은 write-behind 방식을 깨트립니다. 그러면 여러 개의 부품을 하나의 트랜잭션에서 추가할 때 최적화를 할 수 없습니다.
– updateStock() 메소드 안에 넣는 방법이 조금 더 나아보이겠지만, 만약 한 트랜잭션에 SQL 문이 여러 개면 어떨까요? flush()가 의미가 있나요?
사용자 삽입 이미지결론적으로 필요한 것은 세 가지(부품 추가, 부품 수정, 세션 플러시)인데, 이 요구 사항을 만족시키기 위해 코드를 수정할 수 있는 부분은 두 곳 뿐입니다. 바로 여기가 Aspect-Oriented 가 제 몫을 하는 지점입니다. AOP 기술은 코드를 추가할 수 있는 추가적인 공간을 제공합니다. 즉 별도의 모듈로 위의 요구사항을 해결할 수 있도록 해줍니다.

세 개의 요구사항을 각각의 모듈로 처리하기

새로운 부품 추가하기

private SessionFactory sessionFactory;

public void insertPart(Part p) {
        sessionFactory.getCurrentSession().save(p);
}

부품 가격 수정하기

private SimpleJdbcTemplate jdbcTemplate;

public void updateStock(Part p, int stock) {
        jdbcTemplate.update(“update stock set stock = stock + ? where number=?”,
                stock, p.getNumber());
}

세션 동기화하기

정리해보면, ‘JDBC을 할 떄마다, 세션에 dirty 상태이면 flush해야한다.’ 이것을 좀 더 정리하면, ‘JDBC 작업을 수행하기 전에, 세션이 dirty 상태이면 flush 하라.’ 이 것을 다음과 같이 언제, 어디서, 무엇을로 쪼갤 수 있습니다.

    * 언제: before
    * 어디서: a call to a JDBC operation
    * 무엇을: flush a dirty Hibernate session

이렇게 쪼개두면, AspectJ로 구현하기 쉽습니다. 이것을 AspectJ로 구현하면 다음과 같습니다.

public aspect HibernateStateSynchronizer {

        private SessionFactory sessionFactory;
      
        public void setSessionFactory(SessionFactory sessionFactory() {
                this.sessionFactory = sessionFactory;
        }

        pointcut jdbcOperation() :
                call(* org.springframework.jdbc.core.simple.SimpleJdbcTemplate.*(..));
              
        before() jdbcOperation() {
                Session session = sessionFactory.getCurrentSession();
                if (session.isDirty()) {
                        session.flush();
                }
        }
}

생각해 볼 것

먼저, 위의 Aspect는 SimpleJdbcTemplate의 모든 메소드 호출에 적용을 했는데, 특정 애노테이션이 붙은 메소드에만 적용할 수 있도록 포인트컷을 수정할 수 있습니다. (예) execution(@JdbcOperation *(..))

두 번째로 생각해 볼 것은 가용한 하이버네이트 세션이 없을 때 입니다. SessionFactory.getCurrentSession() 은 항상 새로운 세션을 생성해 줍니다. 그런데 SessionFactory 없거나, 세션이 아예 만들어지지 않았을 때에도 위의 Aspect가 동작하려면, 스프링에서 제공하는 SessionFactoryUtils 를 사용해서 세션을 가져오도록 할 수 있습니다. (어지럽…)

소스 코드

HibernateStateSynchronizer 는 AspectJ를 사용해서 구현했고, 간단하게 Spring AOP를 사용하여 구현할 수도 있습니다.

HibernateCarPartsInventoryTests 는 위의 수도 코드를 테스트 했고, aspect가 가용하면 테스트가 통과하고 가용하지 않다면 테스트는 실패합니다.

현재는 before 어드바이스를 주석처리 해놨기 때문에 테스트가 fail 할 것이고, pom.xml을 보면 Maven AspectJ 플러그인이 있는데, 이게 버전 충돌이 나지만 무시해도 됩니다.

ek21.zip
감상문

대단합니다. 어쩜 이리 깔끔하게 정리할 수 있는지, 사진도 멋있네요.(전 여자를 좋아합니다.) Spring AOP를 공부하면서 AspectJ로 살짝 공부하긴 했지만, 실제 언제 어떻게 Aspect를 적용할지는 많이 고민해보지를 않았었습니다. 그냥 막연하게 레퍼런스에 나와있는 정도의 로깅, 트랜잭션, 보안.. 그나마 이러한 것들도 이미 스프링에서 만들어서 제공해주고 있기 때문에 더더욱 별 고민이 없었죠. 이 글은 AOP 초딩같은 저에게 진짜 Aspect Orient Programming이 뭔지 한 수 보여주는 것 같습니다. Alef Arendsen 당케쉔.