AOP Example - Design Level Assertions
개발 표준이라 함은 각종 명명 표준 및 해당 프로젝트에서 기 검증한 소프트웨어 아키텍처 스타일에 맞춰 개발 작업을 수행할 수 있도록 가이드한다.
따라서, 개발자들이 개발 초기에 겪게 되는 혼선을 줄이고 비즈니스 로직에만 집중할 수 있도록 하며, 동일한 표준을 준수한 코드에 대해서는 유지보수 및
변경이 용이하다.
대부분의 프로젝트에서는 어플리케이션을 본격적으로 개발하기에 앞서 상당한 시간을 들여 해당 프로젝트에 적합한 개발 표준을 별도 문서로 정의하고
개발자들이 이를 준수하여 개발 작업을 수행할 것을 권장하나, 제대로 지켜지지 않고 있으며 이에 대한 검증 또한 한계가 있는게 사실이다.
만일 코드 컴파일시에 개발 표준을 적용할 수 있다면, 코딩시에 손쉽게 표준에 부적합한 코드를 인식하고 수정할 수 있게 될 것이다.
이를 위해 본 문서에서는 Design Rule을 declare error/warning 문으로 구성된 Aspect로 정의하고, 부적합 사항을 찾아 수정하는 방법에 대해 알아보기로 하자.
먼저, declare error/warning 문은 다음과 같이 정의하며, Pointcut Expression에 해당하는 JoinPoint가 있을 경우 정의된 메시지를 보여준다.
@DeclareWarning ("Pointcut Expressions")
static final String varialableName = "msg...";
다음은 Anyframe 기반 어플리케이션 개발시 가장 흔하게 볼 수 있는 일부 소프트웨어 아키텍처 그림이다.

해당 어플리케이션의 패키지는 com.sds.emp로 시작하며, 프리젠테이션 레이어는 com.sds.emp.서브모듈명.web, 비즈니스 레이어는 com.sds.emp.서브모듈명.services 내에
위치한다라고 가정하자.
정의 가능한 Design Rule은 크게
Interaction Rule
,
Naming Rule
로 구분해 볼 수 있으며,
이제부터 위 그림을 기반으로 Design Rule을 하나씩 정의해 보도록 하자.
Interaction Rule 정의 예제
패키지 레벨, 클래스 레벨 등에서 필요한 Pointcut을 정의하고 Declare 문에서 앞서 정의한 여러 Pointcut을 조합하여 클래스간 Interaction Rule을 정의하였다.
이는 기 정의된 Pointcut을 다른 Declare 문에서 재사용하기 위함이다.
다음은
DevStandard
Aspect에 정의된
Interaction Rule의 일부이다.
- 프리젠테이션 레이어에 속하지 않은 클래스에서 Action 또는 Form 클래스를 호출할 수 없다.
// 패키지명이 com.sds.emp로 시작하고 중간에 web을 포함하는 모든 패키지에 속한 JoinPoint
@Pointcut("within(com.sds.emp..web..*)")
public void inWebPkg() {}
// 클래스명이 Action 또는 Form으로 끝나는 클래스의 모든 메소드 호출하는 JoinPoint
@Pointcut("call(* com.sds.emp..web.*Action.*(..)) && call(* com.sds.emp..web.*Form.*(..))")
public void callToWeb() {}
// web 패키지에 속하지 않은 클래스에서 web 패키지 내의 Action 또는 Form 클래스를 호출하는 경우
// 다음과 같은 Error 메시지를 보여준다.
@DeclareError("!inWebPkg() && callToWeb()")
static final String irMsg5 = "web 패키지에 속한 모든 클래스에 접근할 수 없습니다.";
- 프리젠테이션 레이어에서는 반드시 Interface를 통해 특정 서비스에 접근해야 한다.
// 패키지명이 com.sds.emp로 시작하고 중간에 web을 포함하는 모든 패키지에 속한 JoinPoint
@Pointcut("within(com.sds.emp..web..*)")
public void inWebPkg() {}
// 클래스명이 DAO로 끝나는 클래스의 모든 메소드 호출하는 JoinPoint
@Pointcut("call(* com.sds.emp..services.impl.*DAO.*(..))")
public void callToDAO() {}
// 클래스명이 Impl로 끝나는 클래스의 모든 메소드 호출하는 JoinPoint
@Pointcut("call(* com.sds.emp..services.impl.*Impl.*(..))")
public void callToImplementation() {}
// web 패키지에서 DAO 또는 Impl 내의 메소드를 직접 호출하는 경우 다음과 같은 Error 메시지를 보여준다.
@DeclareError("inWebPkg() && ( callToDAO() || callToImplementation())")
static final String irMsg1 = "Action 클래스에서는 특정 서비스의 구현 클래스나 DAO 클래스에 직접 "
+ "접근할 수 없습니다.";
- 특정 객체(java.sql.Connection)를 직접 사용하지 않도록 한다.
// 패키지명이 integration 또는 unit으로 시작하는 모든 테스트 패키지에 속한 JoinPoint
@Pointcut("within(integration..* || unit..*)")
public void inTestPkg() {}
// java.sql.Connection 클래스의 모든 메소드를 호출하는 JoinPoint
@Pointcut("call(* java.sql.Connection.*(..))")
public void callToConnection() {}
// 테스트 패키지를 제외한 모든 패키지에서 Connection 객체를 직접 호출하는 경우 다음과 같은
// Error 메시지를 보여준다.
@DeclareError("callToConnection() && !inTestPkg()")
static final String irMsg2 = "java.sql.Connection 객체에 직접 접근할 수 없습니다. "
+ "Anyframe Technical Service를 이용하세요.";
- 생성자를 직접 호출하여 DAO 인스턴스를 생성할 수 없다. Dependency Injection을 통해 객체간 참조 관계를 정의해야 한다.
// DAO 클래스의 Constructor를 호출하는 JoinPoint
@Pointcut("call(com.sds.emp..services.impl.*DAO.new(..))")
public void callToDAOConstructor() {}
// DAO 클래스를 Constructor를 직접 호출하는 경우 다음과 같은 Error 메시지를 보여준다.
@DeclareError("callToDAOConstructor()")
static final String irMsg3 = "DAO 인스턴스를 직접 생성하실 수 없습니다. "
+ "객체간 참조 관계는 서비스 속성 정의 XML에 정의하여 사용하세요.";
Naming Rule 정의 예제
패키지 레벨, 클래스 레벨 등에서 필요한 Pointcut을 정의하고 Declare 문에서 앞서 정의한 여러 Pointcut을 조합하여 Naming Rule을 정의하였다.
이는 기 정의된 Pointcut을 다른 Declare 문에서 재사용하기 위함이다.
다음은
DevStandard
Aspect에 정의된
Naming Rule의 일부이다.
- com.sds.emp.서브모듈명.web 패키지 내에 존재하는 클래스명은 Action 또는 Form으로 끝내야 한다.
// 패키지명이 com.sds.emp로 시작하고 중간에 web을 포함하는 모든 패키지에 속한 JoinPoint
@Pointcut("within(com.sds.emp..web..*)")
public void inWebPkg() {}
// 메소드나 Constructor, 메소드 로직이 아닌 모든 JoinPoint 즉, 클래스 정의 부분만 해당
@Pointcut("!(execution(* *(..)) || withincode(*.new(..)) || withincode(* *(..)))")
public void clazz(){}
// 패키지명이 com.sds.emp로 시작하고 중간에 web을 포함하는 모든 패키지에 속한 JoinPoint 중
// 클래스명이 Action으로 끝나는 클래스에 속한 모든 JoinPoint
@Pointcut("within(com.sds.emp..web..*Action)")
public void actionName() {}
// 패키지명이 com.sds.emp로 시작하고 중간에 web을 포함하는 모든 패키지에 속한 JoinPoint 중
// 클래스명이 Form으로 끝나는 클래스에 속한 모든 JoinPoint
@Pointcut("within(com.sds.emp..web..*Form)")
public void formName() {}
// web 패키지에 속하면서 클래스명이 Action이나 Form으로 끝나지 않는 클래스 정의 부분이 있을 경우,
// 다음과 같은 Warning 메시지를 보여준다.
@DeclareWarning ("inWebPkg() && clazz() && !(actionName() || formName())")
static final String nrMsg2 = "web 패키지에 속한 모든 클래스의 이름은
Action 또는 Form으로 끝나야 합니다.";
- com.sds.emp.서브모듈명.services.impl 패키지 내에 존재하는 클래스명은 Impl 또는 DAO로 끝내야 한다.
// 패키지명이 com.sds.emp로 시작하고 중간에 services.impl을 포함하는 모든 패키지에 속한 JoinPoint
@Pointcut("within(com.sds.emp..services.impl..*)")
public void inImplementationPkg() {}
// 메소드나 Constructor, 메소드 로직이 아닌 모든 JoinPoint 즉, 클래스 정의 부분만 해당
@Pointcut("!(execution(* *(..)) || withincode(* *(..)) || withincode(*.new(..)))")
public void clazz(){}
// 패키지명이 com.sds.emp로 시작하고 중간에 services.impl을 포함하는 모든 패키지에 속한 JoinPoint 중
// 클래스명이 Impl로 끝나는 클래스에 속한 모든 JoinPoint
@Pointcut("within(com.sds.emp..services.impl..*Impl)")
public void implementationName() {}
// 패키지명이 com.sds.emp로 시작하고 중간에 services.impl을 포함하는 모든 패키지에 속한 JoinPoint 중
// 클래스명이 DAO로 끝나는 클래스에 속한 모든 JoinPoint
@Pointcut("within(com.sds.emp..services.impl..*DAO)")
public void daoName() {}
// services.impl 패키지에 속하면서 클래스명이 Impl이나 DAO로 끝나지 않는 클래스 정의 부분이 있을 경우,
// 다음과 같은 Warning 메시지를 보여준다
@DeclareWarning ("inImplementationPkg() && clazz() && !(implementationName() || daoName())")
static final String nrMsg4 = "services 패키지에 속한 모든 클래스의 이름은
Impl 또는 DAO로 끝나야 합니다.";
Refactoring
Eclipse 기반하에 어플리케이션을 개발하면 Design Rule을 위반한 코드를 보다 손쉽게 수정할 수 있다.
- Eclipse 작업 공간 내에 Problems View가 없다면, Eclipse 메뉴 Window > Show View > Problems를 선택하여 Problems view를 오픈한다.
- Problems View를 통해 Design Rule을 위반한 항목들을 확인한다.

- Problems View에서 수정할 항목을 더블 클릭하여 대상 코드로 이동한다.

- Design Rule을 준수한 코드로 수정함으로써 Problem을 제거한다.
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
public Page getUserList(SearchVO searchVO) throws EmpException {
//UserDAO dao2 = new UserDAO();
try {
// 중략
return userDAO.getUserList(searchVO);
}
// 중략
}
- Problems View에서 해당 항목이 제거되었는지 확인한다.
Resources
다운로드
샘플 Aspect 코드를 포함하고 있는 anyframe-aoptest-src.zip 파일을 다운받은 후 위에서 제시한 예제 코드를 실행해 볼 수 있다.
| Name |
Download |
| anyframe-aoptest-src.zip |
Download
|
참고자료