본문 바로가기

IT 살이/04. 기술 - 프로그래밍

개발 프레임워크 만들기 대장정 39 - 화면 객체 생성

이전 포스트에서는 Spring.NET에서 제공하는 XmlObjectFactory를 이용해서 화면 객체에 대한 정보를 로딩하는 작업을 했다. 이번 포스트에서는 XmlObjectFactory( Spring.NET 컨테이너 )에 화면 객체를 요구하는 작업을 하겠다. 아래 그림의 붉은 색 부분이 오늘 포스트의 주제이다.

객체생성기

컨테이너에 객체를 요구할때는 GetObject(“객체ID”)를 호출해서 그 참조를 얻을 수 있다.

 

dalbong2ObjectFactory.GetObject("01");

 

이 메소드 내부 소스를 분석해보면 이전 포스트에서 로딩한 객체 정보를 이용해서 해당 어셈블리를 로딩한 후 그 어셈블리를 통해서 동적으로 객체를 생성해낸다. 문제는 그 어셈블리를 어디에서 로딩하느냐이다. 기본적으로 Spring.NET은 현재 애플리케이션 도메인으로부터 어셈블리를 로딩한다.

그러나 기업형 애플리케이션에서는 그렇게 달갑지 않은 방식이다. 이렇게 로컬 머신에서만 어셈블리를 찾는 다는 것은 애플리케이션을 설치할때 모든 화면단 어셈블리들을 사용자 로컬 머신으로 다운로드받고 시작해야 한다는 의미이다. 그러나 기업 애플리케이션에서는 사용자가 자신의 작업을 하는데 필요한 화면을 포함하는 어셈블리만 사용자 머신으로 다운로드하면 된다. 다른 사람들 작업에 필요한 어셈블리까지 다운로드받는 것은 비효율적이다.

더욱 더 큰 문제는 운영시에 있다. 운영시 사용자의 요구로 인한 화면단의 변경은 수시로 있게 된다. 변경된 화면이 포함된 어셈블리만 copy&paste로 서버에 올려 놓는 구조로 가면 편리할 것이다. 그러나 화면단의 일부 변경이 있을때마다 전체 애플리케이션의 게시 버전을 올려 다시 서버로 게시해야 한다는 것은 운영하는 입장에서는 무리가 아닐 수 없다. 변수 하나 바뀌고 데이터베이스 테이블 컬럼하나 변경되어도 다시 전체 애플리케이션을 게시해야 하다니… 현재 모 기업의 운영 시스템이 이렇게 운영되고 있다.

따라서 달봉이는 화면이 포함된 어셈블리를 원격 서버로부터 다운받아서 로딩할 수 있는 구조로 XmlObjectFactory의 기본 구조를 확장 하고 싶다. 물론 환경 설정에 따라서 로컬에서 로딩할 수도 있도록 할 것이다.

XmlObjectFactory의 기본적인 객체 생성 전략은 소스를 추적해 들어가다 보니, 다음 클래스에서 지정한 SimpleInstantiationStrategy라는 녀석이 담당하고 있었다.

simple instantiation strategy

XmlObjectFactory는 MethodInjectionInstantiationStrategy라는 녀석을 사용하고 있지만, 실질적으로 객체를 생성하는 로직은 SimpleInstantiationStrategy에 있었다. 이 녀석은 XmlObjectFactory의 조상 클래스인 AbstractAutowireCapableObjectFactory에서 InstanticationStrategy라는 속성으로 노출되어 있었다.

instantiationstrategy

 

이 속성은 protected로 지정되어 있고 set, get이 가능하도록 되어 있다. 이것이 의미하는 바는? 바로 AbatractAutowireCapableObjectFactory를 상속하는 자식 클래스에서 새로운 객체 생성 전략 클래스를 만들어서 기본 객체 생성 전략을 대체할 수 있다는 의미이다. 새로운 객체 생성 전략 클래스를 하나 만들어서 XmlObjectFactory의 InstantiationStrategy 속성에 지정하면 된다는 것이다.

이제 달봉이는 Dalbong2InstantiationStrategy라는 클래스를 만들었다.

Dalbong2InstatiationStrategy

 

 

/// <summary>

/// 원격의 서버로부터 어셈블리를 다운받아,

/// 타입의 인스턴스를 생성할 수 있는 인스턴싱 전략클래스

/// </summary>

public class Dalbong2InstantiationStrategy : MethodInjectingInstantiationStrategy

{

    public Dalbong2InstantiationStrategy()

        : base()

    {

    }

 

    #region "IInstantiationStrategy"

 

    /// <summary>

    /// IInstatiationStrategy 인터페이스 구현

    /// </summary>

    /// <param name="definition">The definition of the object that is to be instantiated. </param>

    /// <param name="name">

    /// The name associated with the object definition.

    /// The name can be the null or zero length string

    /// if we're autowiring an object that doesn't belong to the supplied factory.

    /// </param>

    /// <param name="factory">The owning IObjectFactory</param>

    /// <returns></returns>

    public override object Instantiate(RootObjectDefinition definition, string name, IObjectFactory factory)

    {

        PropertyValue pv = definition.PropertyValues.GetPropertyValue("dalbong2ElementInfo");

        if (pv != null)

        {

            return InstantiateFrom(definition);

        }

        else

        {

            return base.Instantiate(definition, name, factory);

        }

    }

    <중략>…

 

    #endregion

 

    /// <summary>

    /// Dalbong2ControlBase 객체를 실제로 생성하는 메소드

    /// </summary>

    /// <param name="definition"></param>

    /// <returns></returns>

   private Dalbong2ControlBase InstantiateFrom(RootObjectDefinition definition)

    {

        object instance = null;

        if (!definition.ObjectType.IsAssignableFrom(Type.GetType("Dalbong2.Win.Dalbong2ControlBase"))

            && !definition.ObjectType.IsAssignableFrom(Type.GetType("Dalbong2.Win.Dalbong2PageBase")))

            throw new Exception(String.Format("생성하려는 객체가 적절한 타입이 아닙니다. 타입:{0}", definition.ObjectTypeName));

 

 

        bool devMode = true;

        string fullyQualifiedTypeName= "";

        string fileName = "";

        string assemblyPath = "";

 

        //element 정보를 조회한다.

        Dalbong2ElementInfo elInfo = null;

        elInfo = (Dalbong2ElementInfo)(definition.PropertyValues.GetPropertyValue("dalbong2ElementInfo").Value);

 

        fileName = (String.IsNullOrEmpty(elInfo.FileName) ? "" : elInfo.FileName);

        assemblyPath = (String.IsNullOrEmpty(elInfo.LoadUrl) ? "" : elInfo.LoadUrl);

        fullyQualifiedTypeName= (String.IsNullOrEmpty(elInfo.FullyQualifiedTypeName) ? "" : elInfo.FullyQualifiedTypeName);

 

        // 원격 서버에서 어셈블리를 가져오는 경우는 devMode를 false로 한다.

        if (assemblyPath.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) || assemblyPath.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))

        {

            devMode = false;

        }

 

        //확장자 ".dll"이 있는지를 확인한다.

        string assemblyFile = fileName.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase) ? fileName : fileName + ".DLL";

 

        // devMode가 true인 경우 어셈블리를 로컬머신( 애플리케이션 도메인)에서 로딩하도록 경로를 지정한다.

        if (!devMode)

        {

           assemblyPath = System.IO.Path.Combine(assemblyPath, fileName);

        }

        else

        {

            assemblyPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);

        }

 

        try

        {

            Assembly assembly = Assembly.LoadFrom(assemblyPath);

            instance = assembly.CreateInstance(fullyQualifiedTypeName, true);

        }

        catch (System.IO.FileNotFoundException ex0)

        {

            throw new System.IO.FileNotFoundException(String.Format("파일({0})을 찾을 수 없습니다.", assemblyPath), ex0);

        }

 

        <중략>…

 

 

        //Dalbong2ControlBase의 ElementInfo 속성을 이용해서 화면객체에 대한 정보를 저장해둔다.

        Dalbong2ControlBase dalbong2Control = null;

        dalbong2Control = (Dalbong2ControlBase)instance;

        dalbong2Control.ElementInfo = elInfo;

 

        return dalbong2Control;

    }

}

 

Dalbong2InstantiationStrategy는 MethodInjectingInstantiationStrategy를 상속받아서 기존의 객체 생성 전략도 그대로 유지할 수 있도록 하고 있다. XmlObjectFactory는 객체를 생성하기 위해서 인터페이스 IInstantiationStrategy의 멤버 Instantiate() 메소드를 호출할 것이다.

그 메소드를 호출할때 넘어오는 인자중에서 RootObjectDefinition 타입의 definition객체가 넘오는데, 이곳에 객체 타입에 대한 정보가 포함되어 있다. 이 정보에서 우리가 이전 포스트에서 넘겨 주었던 “dalbong2ElementInfo”라는 이름의 속성값이 존재한다면 이 녀석은 우리가 직접 생성해야 하는 화면 객체라고 생각할 수 있을 것이다.

만약 “dalbong2ElementInfo”라는 이름의 속성값이 존재한다면 InstantiateFrom() 메소드를 호출하고 있다.

이 메소드에서는 객체 정보 등록시 건네주었던, 클래스명( FullyQualifiedTypeName ), 어셈블리명( FileName), 어셈블리를 가져올 원격 주소( LoadUrl )값을 이용해서 원격서버에서의 어셈블리 파일의 위치를 결정한다. 그런 다음 Assembly.LoadFrom() 메소드를 통해서 해당 어셈블리를 가져와서 메모리로 로딩한다. 그런 다음 assembly.CreateInstance()를 통해서 동적으로 해당 화면 객체를 생성해서 반환한다.

반환되는 화면 객체는 XmlObjectFactory가 받아서 singleton객체로 컨테이너에 캐싱하게 될 것이다.

이상!

아니다. 이 Dalbong2InstantiationStrategy를 기본 생성 전략 객체로 대체하는 곳을 보여주지 않았다. 이전 포스트에서 보여주었던 Dalbong2XmlObjectFactory 정의 코드를 다시 일부 보도록 하자.

public class Dalbong2XmlObjectFactory : XmlObjectFactory

{

    public Dalbong2XmlObjectFactory(IResource resource ) : base(resource)

    {

        base.InstantiationStrategy = new Dalbong2InstantiationStrategy();

    }

<이하 생락>…

 

이렇게 XmlObjectFactory를 상속, 확장한 클래스의 생성자에서 새로운 InstantiationStrategy 인스턴스를 넘겨주면 된다.

이제 xmlObjectFactory.GetObject(“객체ID”)를 통해서 호출되는 객체가 화면 객체인 경우는 달봉이의 새로운 생성 전략을 따라서 생성되게 될 것이다.

이상.