spring로 검색한 결과 :: 시소커뮤니티[SSISO Community]
 
SSISO 카페 SSISO Source SSISO 구직 SSISO 쇼핑몰 SSISO 맛집
추천검색어 : JUnit   Log4j   ajax   spring   struts   struts-config.xml   Synchronized   책정보   Ajax 마스터하기   우측부분

회원가입 I 비밀번호 찾기


SSISO Community검색
SSISO Community메뉴
[카페목록보기]
[블로그등록하기]  
[블로그리스트]  
SSISO Community카페
블로그 카테고리
정치 경제
문화 칼럼
비디오게임 스포츠
핫이슈 TV
포토 온라인게임
PC게임 에뮬게임
라이프 사람들
유머 만화애니
방송 1
1 1
1 1
1 1
1 1
1

spring로 검색한 결과
등록일:2008-06-10 17:44:09
작성자:
제목:자바의 Proxy 기능을 이용한 AOP 구현


최근 개발 트렌드 중의 하나는 AOP(Aspect Oriented Programming)를 이용하는 것이다. 특정 비즈니스 로직을 처리하는 프로그램은 자신이 처리해야 하는 비즈니스적인 기능뿐만 아니라 보안, 디버깅을 위한 실행 이력 정보(로깅), 트렌젝센 제어, 예외 처리 등과 같이 많은 부가적인 부분에 대해서도 처리를 해야만 한다.
앤터프라이즈 애플리케이션에서는 이러한 부가 기능들이 실제 프로그램이 처리해야 하는 기능보다 더 복잡하고 많은 코드를 필요로 한다. 따라서 구현된 코드를 보면 비즈니스 처리에 대한 코드는 이러한 코드들에 파묻혀 제대로 찾을 수도 없는 경우가 많다.

public List findEmps(String keyword) throws Exception
{
   return empDAO.findEmps(keyword);
}

위와 같이 간단한 코드에 대해 트렌젝션, 예외 두가지만 적용한다 하더라도 다음과 같이 복잡한 코드가 된다.
public List findEmps(String keyword) throws Exception
{
   Transaction tx = null;
   try
   {
     tx = session.beginTransaction();
     List result = empDAO.findEmps(keyword);
     tx.commit();
     return result;
   }
   catch(Exception e)
   {
     //예외에 대한 처리
     throw e;
   }
   finally
   {
     session.close();
   }
}

AOP는 보안, 예외 등과 같이 프로그램에 처리해야 하는 다양한 관점을 분리시킨 후 각각의 관점(Aspect) 별로 구현을 한 후 이들을 조합하여 완성된 프로그램을 만들고자 하는 개념이다.
이렇게 함으로써 개발자들은 비즈니스 기능 처리 관점(Aspect)에만 집중하여 프로그램을 할 수 있으며 코드에도 비즈니스 기능 처리에 대한 구현만 있게 하였다.
AOP의 다른 장점은 필요에 따라 다양한 관점을 언제든지 추가하거나 삭제할 수 있으며 이것을 전체 애플리케이션 또는 일부 클래스, 메소드에 적용할 수 있어 그동안 개발자들의 수작업에 의해 이루어진 많은 것들을 한순간에 쉽게 처리할 수 있다는 것이다.
이번 컬럼에서는 AOP에 대한 설명을 위한 것이 아니기 때문에 여기까지만 소개하기로 하고 AOP를 실제 프로그램에 적용하는 방법에 대해 알아보기로 하자.
프로젝트에서 AOP를 적용하기 위해서는 spring, AspectJ와 같이 AOP를 지원하는 프레임워크를 사용하는 것이 일반적이다. 굳이 이러한 프레임워크를 사용하지 않고 프로젝트 내에서 자바 기본 API에서 지원하는 Proxy 클래스를 이용하여 간단하게 AOP를 지원하게 할 수 있다. spring 프레임워크의 AOP 역시 자바의 Proxy 클래스를 이용하여 구현되어 있다.

먼저 Proxy 클래스의 사용에 대해 알아보자. Proxy 클래스는 자바에서 동적 또는 프로그램에서 클래스의 각종 정보 및 객체 생성을 가능하게 해주는 리플렉션 기능을 제공하는 java.lang.reflect 패키지에 포함되어 있다. Proxy는 JDK1.3 이후 버전부터 사용 가능하다.
Proxy 클래스와 함께 사용되는 클래스가 있는데 Proxy의 메소드의 호출에 대한 처리를 담당하는 InvocationHandler이다. InvocationHandler는 interface로 정의되어 있기 때문에 InvocationHandler를 implements하여 사용한다.

Proxy 클래스를 이용하여 만들어진 Porxy 객체는 다음 그림과 같이 실제 개발자가 정의한 클래스를 상속받은 또 다른 클래스에 의해 생성된 객체이다. 물론 이 클래스에 대해서는 개발자가 별도의 코드로 작성할 필요는 없으며 프로그램이 실행되는 시점(Runtime)에 JVM에 의해 클래스를 만든 후 객체를 생성시킨다.

일반적인 사용


Proxy 사용


다음은 로깅 처리를 하는 InvocationHandler와 이를 이용하는 Proxy 클래스를 사용하는 방법에 대한 예제이다.
성능 튜닝을 위해 클래스의 각 메소드의 실행시간을 측정하고자 할 경우에 대한 사례이다. 이것을 위해 우리는 메소드의 시작 시간과 종료시간을 System.out.println() 하고자 한다.

다음과 같이 사원에 대한 관리를 수행하는 기능을 구현하였다.

public interface EmpManager
{
   public String getGreetingMessage(String message) throws Exception;
}


public class EmpManagerImpl implements EmpManager
{
   public String getGreetingMessage(String message) throws Exception
{
     return "Hi: " + message;
   }
}

일반적인 프로그램의 경우 EmpManager를 이용하는 프로그램은 다음과 같이 될 것이다.

   EmpManager empManager = new EmpManagerImpl();
   empManager.getGreetingMessage("This is test");  

하지만 Proxy를 이용할 경우 new EmpManagerImpl()로 생성된 객체를 바로 사용하지 않는다. Proxy를 이용하도록 구현해보자.

먼저 InvocationHandler를 implements한 클래스를 만든다. InvocationHandler 인터페이스에는 invoke(...) 라는 메소드 하나만 선언되어 있다.
invoke(...) 메소드는 runtime에 우리가 만든 실제 비즈니스 처리를 수행하는 클래스의 각 메소드를 호출할 때마다 수행되게 된다. 따라서 이 invoke(...) 메소드의 구현에 우리가 필요로 하는 기능을 추가하면 된다.

public class LoggingHandler implements InvocationHandler
{

   protected Object targetObject;

   public LoggingHandler(Object targetObject)
{
     this.targetObject = targetObject;
   }

   public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
     try
{
        System.out.println("----------------------------------");
        System.out.println("메소드 호출 Start:[" +
                     method + "][" +
                     System.currentTimeMillis() + "]");
        return method.invoke(targetObject, args);
     }
     catch(Exception e)
     {
        throw e;
     }
     finally
     {
        System.out.println("메소드 호출 End:[" +
             method + "][" +
             System.currentTimeMillis() + "]");
        System.out.println("----------------------------------");
     }
   }
}


다음과 같은 코드를 작성하여 EmpManager의 기능을 수행시킬 수 있다.

public class ProxyTest
{
   public static void main(String[] args) throws Exception
   {
     LoggingHandler handler = new LoggingHandler(new EmpManagerImpl());
    
     EmpManager managerProxy = (EmpManager)Proxy.newProxyInstance(
                        EmpManager.class.getClassLoader(),
                        new Class[]{EmpManager.class},
                        handler);
    
     System.out.println("Message : " +
managerProxy.getGreetingMessage("This is test"));
   }
}


이 프로그램의 수행결과는 다음과 같다.

----------------------------------
메소드 호출 Start:[public abstract java.lang.String proxy.EmpManager.getGreetingMessage(java.lang.String) throws java.lang.Exception][1126762451505]
메소드 호출 End:[public abstract java.lang.String proxy.EmpManager.getGreetingMessage(java.lang.String) throws java.lang.Exception][1126762451505]
----------------------------------
Message : Hi: This is test


로깅뿐만 아니라 보안, 트렌젝션과 같은 다른 측면(Aspect)에 대해서도 적용할 수 있다. 이런 다양한 측면을 하나의 InvocationHandler로 구현하는 것은 문제가 있다. 각각 다른 InvocationHandler를 구현하여 우리가 원하는 타겟 클래스와 연결하면 된다.

다음은 예외에 대한 적용을 처리한 예제이다.

public class ExceptionHandler implements InvocationHandler
{

   protected Object targetObject;

   public ExceptionHandler(Object targetObject)
{
     this.targetObject = targetObject;
   }

   public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable
{
     try
{
        return method.invoke(targetObject, args);
     }
     catch(Throwable e)
{
        System.out.println("----------------------------------");          
        System.out.println("메소드 호출 예외발생:[" +
             method + "], 에러 메세지[" +
             e.getMessage() + "]");
        System.out.println("----------------------------------");    
        return null;
     }
   }
}


public class ProxyTest
{
   public static void main(String[] args) throws Exception
   {
     ExceptionHandler exceptionHandler =
new ExceptionHandler(new EmpManagerImpl());
    
     LoggingHandler logHandler = new LoggingHandler(Proxy.newProxyInstance(
           EmpManager.class.getClassLoader(),
           new Class[]{EmpManager.class},
           exceptionHandler));

     EmpManager managerProxy = (EmpManager)Proxy.newProxyInstance(
           EmpManager.class.getClassLoader(),
           new Class[]{EmpManager.class},
           logHandler);

     System.out.println("Message : " +
managerProxy.getGreetingMessage("This is test"));    
   }
}


public class EmpManagerImpl implements EmpManager
{
   public String getGreetingMessage(String message) throws Exception
{
     String test = null;
     test.length();
     return "Hi: " + message;
   }
}



이 코드에 대한 결과는 다음과 같다.

----------------------------------
메소드 호출 Start:[public abstract java.lang.String proxy.EmpManager.getGreetingMessage(java.lang.String) throws java.lang.Exception][1126764911963]
----------------------------------
메소드 호출 예외발생:[public abstract java.lang.String proxy.EmpManager.getGreetingMessage(java.lang.String) throws java.lang.Exception], 에러 메세지[null]
----------------------------------
메소드 호출 End:[public abstract java.lang.String proxy.EmpManager.getGreetingMessage(java.lang.String) throws java.lang.Exception][1126764911963]
----------------------------------
Message : null


실제 애플리케이션에서는 예외 발생시 어떤 형태로든지 처리해야 하겠지만 여기서는 프로그램을 단순하게 하기 위해 null을 반환하도록 하였다.
위의 예제에서 보듯이 하나의 클래스에 대해 여러개의 InvocationHandler를 연결함으로써 다양한 관점(Aspect)을 기존 프로그램(EmpManagerImpl)의 수정 없이 처리할 수 있게 되었다.



클래스 다이어그램 상에서도 Emp 컴포넌트를 나타내는 EmpManager, EmpManagerImpl와 애플리케이션의 로깅, 예외 처리와 관련된 클래스와는 의존관계(Dependency)로 연결되지 않고 별도로 표현된다.

위의 예제와 같이 구현할 경우 매번 Proxy 객체를 만들때 마다 Handler를 생성해야 하기 때문에 Aspect의 추가, 삭제를 위해서는 코드를 수정해야 하기 때문에 매우 불편하다.
실제 프로젝트에서 몇가지 유틸리티 클래스와 porperty 파일(XML파일)을 추가하여 사용할 수 있다. 이번 컬럼에서는 클래스 관련 정보를 관리하는 ClassInfo, ClassInfoUtil 클래스와 클래스 정보를 이용하여 Proxy 객체를 생성하는 ProxyFactory, InvocationHandler를 구현한 아무것도 수행하지 않는 DefaultInvocationHandler 클래스까지 모두 4개의 유틸리티 클래스를 만들었다. 다음은 이들 유틸리티 클래스에 대한 구현이다.

public class ClassInfo
{
   String classId;
   String classType;
   List handlers;
   public ClassInfo(String classId, String classType, List handler)
   {
     this.classId = classId;
     this.classType = classType;
     this.handlers = handler;
   }

   public String getClassId()
   {
     return classId;
   }

   public String getClassName()
   {
     return classType;
   }

   public List getHandler()
   {
     return handlers;
   }
}


public class ClassInfoUtil
{
   HashMap classInfos;

   static ClassInfoUtil instance;

   private ClassInfoUtil()
   {
     classInfos = new HashMap();
     loadClassInfos();
   }

   private void loadClassInfos()
   {
     try
     {
        DocumentBuilder documentBuilder =
DocumentBuilderFactory.newInstance().newDocumentBuilder();
        Document document = documentBuilder.parse(getClass().
getResourceAsStream("/properties/classInfo.xml"));

        NodeList classNodeList = document.getElementsByTagName("class");
        int nodeListLength = classNodeList.getLength();

        for (int i = 0; i < nodeListLength; i++)
        {
           // item은
로 정의되어 있는 내용을 가리키고 있음
           Element classElement = (Element) classNodeList.item(i);

           String classId =
classElement.getAttributes().getNamedItem("id").getNodeValue();
           String classType =
classElement.getAttributes().getNamedItem("type").getNodeValue();
           List handlers = getHandlers(classElement);
           classInfos.put(classId, new ClassInfo(classId, classType, handlers));
        }
     }
     catch (Exception e)
     {
        e.printStackTrace();
     }
   }

   private List getHandlers(Element classElement) throws Exception
   {
     List result = new ArrayList();
     NodeList managerNodes = classElement.getElementsByTagName("handler");
     for (int j = 0; j < managerNodes.getLength(); j++)
     {
        Element managerNode = (Element) managerNodes.item(j);
        result.add(managerNode.getAttributes().
getNamedItem("type").getNodeValue());
     }

     return result;
   }

   public static ClassInfoUtil getInstance()
   {
     if (instance == null)
     {
        instance = new ClassInfoUtil();
     }
     return instance;
   }

   public ClassInfo getClassInfo(String classId) throws Exception
   {
     if (classInfos == null)
     {
        throw new Exception("Class 관련 설정 정보가 없습니다.");
     }

     try
     {
        ClassInfo classInfo = (ClassInfo) classInfos.get(classId);

        if (classInfo == null)
throw new Exception(
"classInfo.xml파일에 [" + classId + "] 정보가 없습니다.");

        return classInfo;
     }
     catch (Exception e)
     {
        throw e;
     }
   }
}


public class DefaultInvocationHandler implements InvocationHandler
{
   protected Object targetObject;

   public void setTargetObject(Object targetObject)
   {
     this.targetObject = targetObject;
   }

   public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
   {
     return method.invoke(targetObject, args);
   }
}


public class ProxyFactory
{
   public static Object createProxyClass(String classId) throws Exception
   {
     ClassInfo classInfo = ClassInfoUtil.getInstance().getClassInfo(classId);
     Object targetObject = Class.forName(classInfo.getClassName()).newInstance();

     List handlers = classInfo.getHandler();

     if (classInfo.getHandler().size() == 0)
     {
        handlers.add("proxy.DefaultInvocationHandler");
     }

     Object handlerTargetObject = targetObject;
     Object proxy = null;
     for (Iterator it = handlers.iterator(); it.hasNext();)
     {
        String handlerType = (String) it.next();
        DefaultInvocationHandler handler =
(DefaultInvocationHandler) Class.forName(handlerType).newInstance();
        handler.setTargetObject(handlerTargetObject);

        proxy = Proxy.newProxyInstance(
targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),
handler);

        handlerTargetObject = proxy;
     }

     return proxy;
   }
}


이제 EmpManager를 이용하는 코드를 만들어 보면 다음과 같이 ProxyFactory를 사용하는 것으로 간단하게 구현할 수 있다.

public class ProxyTest2 {

   public static void main(String[] args) throws Exception
   {
     EmpManager manager = (EmpManager)ProxyFactory.createProxyClass("emp");
     System.out.println("Message : " +
manager.getGreetingMessage("This is test"));;    
   }
}


/properties/classinfo.xml
<?xml version="1.0" encoding="EUC-KR"?>
<classes>
  <class id="emp" type="proxy.sample2.emp.EmpManagerImpl">
    <handler type="proxy.sample2.ExceptionHandler"/>
    <handler type="proxy.sample2.LoggingHandler"/>
  </class>
</classes>


ProxyFactory를 이용할 경우 AOP를 적용하기 위한 모든 클래스에 대해 "new" 키워드를 이용하여 객체를 생성하는 것이 아니라 위의 ProxyTest2 클래스에 구현된 것과 같이 ProxyFactory를 이용하여 객체를 생성해야 하며 "classinfo.xml" 파일에 클래스 관련 정보를 설정해야 한다.

spring 프레임워크에서도 xml 파일에 정의된 bean 객체를 생성하기 위해 다음과 같이 사용한다.

WebApplicationContext wac =
WebApplicationContextUtils.getRequiredWebApplicationContext(application);    
EmpManager empManager = (EmpManager)wac.getBean("emp");


지금까지 JDK1.3 이상부터 지원되는 Proxy, InvocationHandler를 이용하여 실제 프로젝트에서 사용가능한 가장 간단한 AOP 프레임워크를 구성해보았다.
Proxy 클래스를 이용할 경우 메소드 call 단위로만 AOP를 적용할 수 있다는 단점이 있지만 시스템에서 필요한 대부분의 aspect에 대한 처리는 가능하다. 최근 많이 사용하고 있는 spring 프레임워크의 AOP 역시 이러한 기법으로 구현되어 있어 메소드 단위에만 적용 가능하도록 되어 있다.
이제 더 이상 AOP를 적용하기 위해 복잡한 XML 설정 및 AOP 지원 프레임워크의 동작원리를 이해하지 않아도 된다. 위의 코드를 조금만 수정하면 자바 기본 API에서 제공하는 Proxy 클래스를 이용하여 간단하지만 프로젝트에서 정말로 필요로 하는 관점(Aspect)을 쉽게 적용할 수 있을 것이다.

 

소스코드 다운로드 : proxy_sample.zip