정보 공학 에서 리플렉션 ( reflection )는 프로그램 의 실행 과정에서 프로그램 자체의 구조를 읽고 수정하거나하는 기술이다. 일반적 반사라고하면 동적 (런타임) 리플렉션을 가리 키지 만, 정적 (컴파일시) 리플렉션을 지원하는 프로그래밍 언어 도있다. 리플렉션은 Smalltalk , Java , . NET Framework 와 같은 가상 기계 나 인터프리터 에서 실행되는 것을 상정 한 언어에서 지원되는 경우가 많으며, C 언어 같은 기계어 로 출력되는 것을 상정 한 언어 에서 지원되는 것은 적다.

일반적으로 반사와 객체 가 자신의 구조와 계산의 의미를 얻을 수 있도록하는 것이다. 리플렉션의 프로그래밍 패러다임 을 리후 레크 티브 프로그래밍 ( reflective programming )이라고한다.

일반적으로 프로그램의 소스 코드 가 컴파일 되면 프로그램 구조 등의 정보는 저수준 코드 ( 어셈블리 언어 )로 변환되는 과정에서 손실되어 버린다. 리플렉션을 지원하는 경우, 이러한 정보는 생성 된 코드에 메타 데이터 로 저장된다.

LISP 와 Forth 같은 런타임 및 컴파일시를 구분하지 않는 언어에서는 코드의 해석과 반성 사이에 차이는 없다.

목차

  [ 숨기기 ] 

예 편집 ]

Java 편집 ]

다음 코드는 java.lang.reflect 패키지를 사용한 Java 6 이후의 예이다.

/ / 리플렉션없이 
Foo foo =  New Foo ( ) ; 
foo. hello ( ) ;
/ / 리플렉션 
Class CL =  Class . forName ( "Foo" ) ; 
Method Method = cl. getMethod ( "hello" ) ; 
method. invoke ( cl. newInstance ( ) ) ;

어느 코드에서도 Foo 클래스 의 인스턴스 를 만들고 인스턴스의 hello () 메소드 를 부르고있다. 전자의 예는 클래스와 메소드 이름이 하드 코딩되어 있기 때문에 실행시 다른 클래스로 변경하는 것은 불가능하다. 리플렉션을 이용한 후자의 예는 그들을 실행할 때 쉽게 변경할 수있다. 그러나 반면에, 후자는 어지럽고 또한 컴파일 시 검사의 혜택도 얻을 수 없다. 즉, 만약 Foo 클래스가 존재하지 않았다고하면 전자의 코드는 컴파일시에 오류가되지만, 후자의 코드가 실행될 때까지 오류가 발생하지 않는다.

Perl 편집 ]

다음 코드는 같은 예를 Perl 로 쓴 것이다.

# 리플렉션없이 
Foo -> new -> hello ( ) ;
# 리플렉션 
my  $ class  =  "Foo" ; 
my  $ method  =  $ class -> can ( "hello" ) ; 
$ class -> new -> $ method ( ) ;

Objective-C 편집 ]

다음 코드는 같은 예를 Objective-C 로 쓴 것이다.

/ / 리플렉션없이 
[ [ [ Foo alloc ] init ] hello ] ;
/ / 리플렉션 
ID AClass = objc_getClass ( "Foo" ) ;
 SEL aSelector = NSSelectorFromString ( @ "hello" ) ;
objc_msgSend ( [ [ aClass alloc ] init ] , aSelector, nil ) ;

ActionScript 편집 ]

다음은 같은 예제를 ActionScript 로 쓴 것이다.

/ / 리플렉션없이 
var foo : Foo = New Foo ( ) ;
foo. hello ( ) ;
/ / 리플렉션 
var ClassReference : Class = flash. utils . getDefinitionByName ( "Foo" ) as Class ;
 var instance : Object = New ClassReference ( ) ;
instance. hello ( ) ;

JavaScript 편집 ]

다음은 같은 예제를 JavaScript 로 쓴 것이다.

/ / 리플렉션없이 
var foo =  New Foo ( ) ; 
foo. hello ( ) ;
/ / 리플렉션 
var foo =  this [ 'Foo' ] ; 
var methodName =  'hello' ; 
( New foo ( ) ) [ methodName ] ( ) ;

C # 편집 ]

다음 예제는 C # 의 예에서보다 진전 된 반성의 용법을 보여주고있다. 프로그램은 명령 줄 에서 어셈블리 이름을 입력으로 취한다. 어셈블리는 클래스 라이브러리 와 같은 것이다. 어셈블리가로드되면 어셈블리에 정의 된 방법을 찾기 위해 리플렉션을 사용한다. 발견 된 각 메소드에 대해 최근 변경이 있었는지 여부를 리플렉션을 사용하여 조사하고있다. 만약 변경이 있어야하며 인수를 취하지 않는 방법이면 메서드 이름과 반환 형식을 표시한다.

최근 변경되었는지 여부를 확인하기 위해 개발자는 사용자 정의 속성 을 사용할 필요가있다.

using  System ; 
using  System.Collections.Generic ; 
using  System.Text ; 
using  System.Reflection ; 
using  Recent ;
 
namespace Reflect
 { 
    class Program
     { 
        private Assembly A ; 
        Program ( String assemblyName ) 
        { 
            a = Assembly . Load ( New AssemblyName ( assemblyName ) ) ; 
            / / 지정된 어셈블리에 SupportsRecentlyModified 특성이 적용되어 있는지 확인. 
            Attribute c = Attribute . GetCustomAttribute ( a, typeof ( SupportsRecentlyModifiedAttribute ) ) ; 
            if  ( c ==  null ) 
            { 
                 / / "SupportsRecentlyModified"속성 가져 오지 못했습니다. 
                 / / 즉, 어셈블리는 "RecentlyModified"속성을 지원하지 않는다. 
                 throw  New Exception ( "어셈블리"  + assemblyName +  "필요한 속성을 지원하지 않습니다." ) ; 
            } 
            Console . WriteLine ( "읽기 완료 :"  + a . FullName ) ; 
        }
 
        public  void FindNewMethodsWithNoArgs ( ) 
        { 
            / / 어셈블리에 정의 된 형식을 모두 취득하는 
            Type [ ] t = a . GetTypes ( ) ; 
            foreach  ( Type type in T ) 
            { 
                / /이 형식이 클래스이어야 건너 뜀. 
                if  ( ! type . IsClass ) 
                    continue ; 
                Console . WriteLine ( "클래스 이름 :"  + type . FullName ) ; 
                MethodInfo [ ] methods = type . GetMethods ( ) ; 
                foreach  ( MethodInfo Method in Methods ) 
                { 
                    object [ ] ab = method . GetCustomAttributes ( typeof ( RecentlyModifiedAttribute ) , true ) ; 
                    / / 속성이 하나를 얻을 수없는 경우에,이 메소드는 이전하는 것이다. 
                    if  ( ab . Length  ! =  0 ) 
                    { 
                        / / 그렇지 않으면 하나의 속성 만 검색된다. 
                        / / 이는 "RecentlyModified"속성은 다른 속성과 함께 수 없기 때문이다.
 
                        Console . Write ( " \ t 업데이트 된 메소드 : "  + method . Name ) ; 
                        if  ( method . GetParameters ( ) . Length  >  0 ) 
                            break ;
 
                        / / 개발자가 지정한 코멘트를 얻기 위해, 
                        / / "RecentlyModifiedAttribute"속성의 인스턴스를 사용한다. 
                        Console . WriteLine ( " \ t "  +  ( ab [ 0 ]  as RecentlyModifiedAttribute ) . comment ) ; 
                        Console . WriteLine ( " \ t \ t 반환 값 : "  + method . ReturnType . Name ) ; 
                    } 
                } 
            } 
        }
 
        static  void Main ( string [ ] args ) 
        { 
            try 
            { 
                Program reflector =  New Program ( "UseAttributes" ) ; 
                reflector . FindNewMethodsWithNoArgs ( ) ; 
            } 
            catch  ( Exception e ) 
            { 
                Console . Error . WriteLine ( e . Message ) ; 
            } 
        } 
    } 
}

에서 사용하는 사용자 지정 특성의 구현을 다음.

using  System ; 
using  System.Collections.Generic ; 
using  System.Text ;
 
namespace Recent
 { 
    / /이 특성은 메서드에만 적용 할 수 없으며 다른 특성과 함께 할 수 없다. 
    [ AttributeUsage ( AttributeTargets . Method , AllowMultiple = false , Inherited = true ) ] 
    public  class RecentlyModifiedAttribute : Attribute
     { 
        / /이 특성은 메서드에 적용된다. 
        / / 인수없이 (RecentlyModified), 
        / / 또는 코멘트와 함께 (RecentlyModified (comment = "<someComment>")) 적용 할 수있다.
 
        private  String Comment =  "이 방법은 최근에 변경되었습니다." ;
 
        public RecentlyModifiedAttribute ( ) 
        { 
            / / 속성의 인스턴스를위한 빈 생성자. 
            / / 필수 인수는 없기 때문에 생성자는 하늘. 
        }
 
        / / 선택적 인수 "comment"의 정의. 속성이 사용되는 때 명명 된 인수로 지정된다. 
        public  String comment
         { 
            get 
            { 
                return Comment ; 
            } 
            set 
            { 
                Comment = comment ; 
            } 
        } 
    }
 
    [ AttributeUsage ( AttributeTargets . Assembly , AllowMultiple = false ) ] 
    public  class SupportsRecentlyModifiedAttribute : Attribute
     { 
        / /이 특성은 인수없이 어셈블리에 적용된다. 
        / / as in [SupportsRecentlyModified] 
        public SupportsRecentlyModifiedAttribute ( ) 
        { 
            / / 필수 인수는 없기 때문에 생성자는 하늘. 
        } 
    } 
}

또한 사용자 지정 특성을 사용하여 클래스 정의 예를 들면.

using  System ; 
using  System.Collections.Generic ; 
using  System.Text ; 
using  Recent ;
 
/ / 어셈블리가 "RecentlyModified"속성을 지원하는 것을 보여주기 위해 
/ / "SupportsRecentlyModified"속성을 적용한다. 
[ assembly : SupportsRecentlyModified ]
 
namespace Reflect
 { 
    class UseAttributes
     { 
        private  Object info ;
 
        public UseAttributes ( ) 
        { 
            info =  ( object )  "Hello World" ; 
        }
 
        public  void OldMethodWithNoArgs ( ) 
        { 
            Console . WriteLine ( "이것은 인수를 취하지 않는 오래된 방법이다." ) ; 
        }
 
        / / 메소드가 최근 변경되었다는 것을 나타내는 "RecentlyModified"속성을 적용한다. 
        [ RecentlyModified ] 
        public  void NewMethodWithNoArgs ( ) 
        { 
            Console . WriteLine ( "이것은 인수를 취하지 않는 새로운 방법이다." ) ; 
        }
 
        public  void OldMethodWithOneArg ( object something ) 
        { 
            info = something ; 
            Console . WriteLine ( "이것은 인수를 1 개 취하는 오래된 방법이다." ) ; 
        }
 
        [ RecentlyModified ] 
        public  void NewMethodWithOneArg ( object something ) 
        { 
            info = something ; 
            Console . WriteLine ( "이것은 인수를 1 개 취하는 새로운 방법이다." ) ; 
        }  
    } 
}


===================================================================================


컴퓨터 과학에서, 반영(Reflection)은 컴퓨터 프로그램에서 런타임 시점에 사용되는 자신의 구조와 행위를 관리(type introspection)하고 수정할 수 있는 프로세스를 의미한다. “type introspection”은 객체 지향 프로그램언어에서 런타임에 객체의 형(type)을 결정할 수 있는 능력을 의미한다.

많은 컴퓨터 아키텍처에서, 프로그램 명령은 데이터와 같이 메모리에 적재되므로, 명령과 데이터 사이의 구분은 단지 컴퓨터와 프로그램 언어에 의하여 어떻게 정보가 처리되느냐의 문제이다. 일반적으로, 명령은 실행되고, 데이터는 (명령의 자료로서) 처리된다. 그렇지만, 어떤 언어에서는, 프로그램은 명령 역시 데이터와 같은 방식으로 처리 할 수 있기 때문에, Reflective 수정이 가능하게 된다. 가장 일반적으로 반영은 스몰토크, 스크립트 언어와 같이 높은 수준의 가상 머신 프로그램 언어에서 주로 많이 사용되고, 자바C 언어와 같이 선언적이거나 정적 형식의 프로그램 언어에서는 드물게 사용된다.

[편집]이용

반영은 런타임에 프로그램의 수행을 수정하고, 관찰하기 위하여 사용할 수 있다. 반영 지향적인 프로그램 구성 요소는 내부 코드의 실행을 감시할 수 있고, 구성 요소 자신의 궁극적인 목표에 맞도록 내부를 수정 할 수 있다. 이는 전형적으로 런타임에 프로그램 코드를 동적으로 할당하여 이루어진다.

반영은 프로그램을 서로 다른 상황에서 동적으로 사용할 수 있게 한다. 예를 들어, 비슷한 동작을 수행하는 서로 다른 클래스 ‘X’와 클래스 ‘Y’를 교체하여 사용하는 응용 프로그램을 고려해보자. 반영 지향적이 아닌 프로그램 개발에서는, 응용 프로그램은 클래스 ‘X’와 클래스 ‘Y’의 함수를 호출하기 위하여, 함수(이름)을 직접 코드로 작성해야 한다. 반면, 반영 지향적인 프로그램 패러다임에서, 응용 프로그램은 함수(이름)을 직접 코드로 작성하지 않고, 클래스 ‘X’와 클래스 ‘Y’의 함수를 호출하기 위하여, 반영을 이용하여 설계되고 작성될 수 있다. 반영 지향적인 프로그램은 보다 보편적인(Generic) 코드의 수행의 기능을 사용하기 위하여, 거의 언제나 추가적인 정보, 프레임워크, 연관 사상, 객체 관계 등을 필요로 한다. 반영 지향적인 프로그램 기술을 확장하면, 하드 코딩을 방지할 수 있다.

[편집]기능

반영이 지원되는 언어는, 낮은 수준의 언어의 처리와는 다른, 런타임에 사용할 수 있는 매우 모호한 몇 가지 특성을 제공한다. 이런 특성들은 다음과 같다.

  • 런타임에 First-Class 객체로서 소스 코드의 생성자(코드 블록, 메소드, 프로토콜, ……)를 인식하고 수정할 수 있다.
  • 참조 및, 호출되는 클래스 또는 함수와 일치하는 문자열을 클래스 또는 함수의 기호 이름으로 변환할 수 있다.
  • 런타임에 소스 코드 구문으로 구성된 문자열을 평가할 수 있다.
  • 프로그래밍 구성에 새로운 의미 또는 의도를 부여하여, 프로그램 언어의 바이트 코드에 대한 새로운 번역기를 생성할 수 있다.

컴파일 언어들은 소스 코드에 대한 정보를 제공하기 위하여, 자신들의 런타임 시스템에 의존한다. 예를 들어, 컴파일 된 오브젝티브 C 실행 모듈은, 프로그램 컴파일 과정을 통하여 소스 코드와 메소드가 상응하는 테이블을 제공하여, 실행 모듈의 블록 안에 모든 메소드의 이름을 기록한다. 커먼 리스프(Common Lisp) 언어와 같이, 함수의 런타임 생성을 지원하는 컴파일 언어는 런타임 환경은 컴파일 또는 인터프리터를 포함해야 한다.

반영은 자동화된 소스 코드 변환 체계를 정의한 프로그램 변환 시스템을 사용하여, 반영 기법이 내장되지 않은 언어에서 구현될 수 있다.

[편집]예제

다음은 여러 언어에서 구현된 반영의 사용 예를 표시한다.

C#:

Int32 i = 1234;
Console.WriteLine(String.Format("{0}", i.ToString("F")));
 
Type t = typeof(Int32);
// Reflection 1
Object res1
    = t.InvokeMember("ToString", 
        System.Reflection.BindingFlags.InvokeMethod, 
        null, 
        i, 
        new Object[]{"C", CultureInfo.CreateSpecificCulture("ko-KR")});
Console.WriteLine(String.Format("{0}", res1));
 
// Reflection 2
System.Reflection.MethodInfo method 
    = t.GetMethod("ToString", new Type[] { typeof(String) });
Object res2 = method.Invoke(i, new Object[] {"X"});
Console.WriteLine(String.Format("{0}", res2));

Objective-C

NSString* str = [[NSString alloc] initWithString:@"1234"];
NSLog(@"%d", (int)[str length]);
 
// Reflection
Class class = NSClassFromString(@"NSString");
id idOfClass = [[class alloc] initWithString:@"Reflection"];
SEL selector = NSSelectorFromString(@"length");
NSLog(@"%d", (int)[idOfClass performSelector:selector]);
 
[str release];
[idOfClass release];

Python:

>>> class MyClass:
...     def Hello(self, sText):
...             return 'Hello ' + sText
...
>>> MyClass().Hello('Peter')
'Hello Peter'
>>> 
>>> getattr(globals()['MyClass'](), 'Hello')('Peter')
'Hello Peter'
>>>


===================================================================================


RTTI(Run-Time Type Information)을 이용하면 실행중에 타입 정보를 알아내고 사용할 수 있다. 이는 매우 까다롭고 복잡한 문제점들을 쉽고 빠르게 해결 할 수 있고, 굉장히 강력한 프로그램을 작성 할 수 있게 도와준다. 하지만 이는 객체 지향적인 프로그래밍 방법을 위배하고, 코드의 가독성을 떨어트린다는 의견도 있다. 그렇지만 이런 강력한 기능의 유혹을 벗어나긴 힘들다. 그리고 실제 여러 Framework에서 RTTI를 잘 활용 하고 있다. (특히나 Spring Framework)



자바에서는 모든 .class 파일 하나당 java.lang.Class 객체 하나씩 생성된다. Class는 모든 .class들의 정보를 가지고 있으며 .class파일에 같이 저장된다. 모든 .class들은 이 클래스를 최초로 사용하는 시점에서 동적으로 ClassLoader을 통해 JVM에 로드된다. 


최초로 사용하는 시점이라면 해당 .class에서 static을 최초로 사용할때를 말한다. (생성자도 static 메소드이다.  그렇기 때문에 new를 하게 되면 로드된다고 보면 된다. 이를 동적 로딩이라 한다.) 


이렇게 .class의 정보와 Class객체는 JVM에 Run Time Data Area의 Method Area에 저장된다.


그리고 이러한 정보들을 java.lang.reflect 에서 접근할 수 있게 도와 준다.  이 패키지에서는 Field, Method, Constructor같은 class들이 있고,  Constructor를 통해 새로운 객체를 생성하고, getter method와 setter method를 통해 Field의 값을 읽거나 수정할 수 있다. 또 invoke method를 통해 method를 호출한다. 그 외에도 parameter, return type, modifier 등 class의 관련된 모든 정보를 알아올 수 있고 조작할 수 있다. 심지어 private으로 Encapsulation된 immutable한 것들 까지도 setAccessible을 통해 접근, 조작 할 수 있다. 


출처 - http://blog.naver.com/PostView.nhn?blogId=cracker542&logNo=40160053694


===================================================================================


Java 란 언어는 참 보면 볼 수록 재미난 언어다. 기반 라이브러리가 참 충실하다보니 이것 저것 생각나는걸 해볼 수 있다는게 참 맘에 든다. 하기사... 다른 언어들도 그렇지만 암튼 그렇다 치자. 
  오늘은 얼마전에 개발을 시작한 모 프로그램에 들어가는 기능에 삘받아 간단한 리플렉션 예제를 실어 볼까한다. 리플렉션을 쓰다보면 참 노가다도 이런 상노가다가 없다. 물론 인터페이스를 사용할 수 없을 때 얘기다. 이게 뭔고 하니 아래의 코드로 비교를 해보자. 일단은 인터페이스를 쓰지않은 리플렉션 코드다.

우선 리플렉션을 통해 실행할 클래스를 소개한다.


public class HandlerA{
  private String msg;
  
  public void setMessage(String msg){
    this.msg = msg;
  }
  
  public void handle(){
    System.out.println(msg);
  }
}

아~ 주 심플하다. 메시지를 설정하고 표준출력으로 뱉는 단순한 기능을 하는 클래스다. 

그러면 저 녀석을 로드해서 실행하는 클래스를 보자.


import java.lang.reflect.Method;

public class DynaLoad{
  public static void main(String[] args) throws Exception{
    if(args.length < 1){
      System.out.println("DynaLoad classname");
    }else{
      loadAndExecute(args[0]);
    }
  }
  
  public static void loadAndExecute(String className) throws Exception{
    Class c = null;
    c = Class.forName(className);
    Object target = c.newInstance();
    Method m = c.getDeclaredMethod("setMessage", String.class);
    m.invoke(target, "Hello");
    m = c.getDeclaredMethod("handle");
    m.invoke(target);
  }
}



뭐시가 아주 길다. java.lang.reflect.Method 라는 놈도 임포트 되어 있다. 요녀석이 실행되는 과정은 다음과 같다. 

ex) java DynaLoad HandlerA 라고 실행을 하면
녀석이 인자를 하나 받아서 해당 인자를 클래스 명으로 간주하고 로드를 한다. 그리고 나서 해당 Class 를 통해 객체를 하나 생성한다. 근데 그 객체가 뭔 타입인지 미리 알 수가 없으니 Object 로 일단 놓고 아까 로드한 클래스를 통해 우리가 호출할 객체의 메서드를 얻는다. Object 에는 setMessage 는 물론이거니와 handle 이라는 메서드도 없으니 클래스를 통해 호출할때 쓸 메서드를 얻어야 한다. 우선 setMessage 를 호출해야 하니까. setMessage 라는 메서드명과 setMessage 의 인자의 타입을 통해 해당 메서드를 얻는다. 그리고 나서 얻어낸 메서드에 생성한 객체와 인자를 넣고 invoke 를 호출해 주면 해당 객체에대해 setMessage 를 호출한 것과 동일한 효과를 볼 수있다. 뭐 이런식으로 해서 handle 도 호출하면 Hello 라고 출력되는걸 볼 수 있다. 아~ 뭔가 길고, 지루하지 않은가 게다가 메서드를 호출할 때마다 이짓을 해야하니 정말 죽을 맛이다. 그러면 저기다가 인터페이스를 이용하면 어떨까? 

일단 우리가 로드할 클래스는 어떠한 인터페이스를 구현한 클래스라고 가정하자. 이래야 뭔가 문제가 쉬워진다. 그 인터페이스는 아래와 같다.


public interface HandlerInterface {
  public void handle();
  public void setMessage(String msg);
}

간단하다. 호출할 메서드를 다 넣어놨다. 그러면 이녀석을 구현한 클래스는 어떨까? 아래를 보자

public class HandlerB implements HandlerInterface{
  private String msg;
  public void setMessage(String msg){
    this.msg = msg;
  }
  public void handle(){
    System.out.println(msg);
  }
}

implements 만 붙었을 뿐 HandlerA 와 똑같다! 그러면 위에서 썻던 DynaLoad 클래스는 어떻게 바뀔까?

import java.lang.reflect.Method;
public class DynaLoad{
  public static void main(String[] args) throws Exception{
    if(args.length < 1){
      System.out.println("DynaLoad classname");
    }else{
      loadAndExecute(args[0]);
    }
  }
  
  public static void loadAndExecute(String className) throws Exception{
    Class c = null;
    c = Class.forName(className);
    HandlerInterface target = (HandlerInterface)c.newInstance();
    target.setMessage("Hello");
    target.handle();
  }
}



main 메서드는 그대로고 loadAndExecute 라는 메서드는 좀 짧아졌다. 지금은 로드해서 실행할 클래스가 간단해서 그렇지 메서드가 수십개씩 되는 클래스라고 생각해 보라. 코드가 얼마나 줄겠는가?!! 위의 코드는 딱히 설명할 것도 없다. 인터페이스를 이용함 으로써 로드된 클래스의 모든 기능을 맘편히 사용할 수 있다.


출처 - http://lemonfish.egloos.com/4087285


===================================================================================


(1) 리플렉션(Reflection)

 

자바에서 리플렉션은 유연성을 제공하기위해 필수적인 기법이다물론 리플렉션이 없더라도 훌륭한 코드를
작성할 수 있다하지만 리플렉션을 사용하면 좀더 유연한 프로그램을 작성할 수 있다자바에서 리플렉션을 
이해하기 위해서자바 클래스 파일은 바이트 코드로 컴파일 되며 실행시간에 이 바이트 코드가해석되어
 
실행된다는 것을 아는 것이 첫 출발점이 된다이 바이트 코드에는 클래스에 대한 모든 정보를포함하고 있다

클래스 파일이 있는 위치와 이 클래스 파일의 이름을 알수 있다면 언제든지 바이트 코드를 뒤져서 클래스에 대한
 
정보를 얻어낼 수 있다이제부터 리플렉션을 통해 어떻게,어떤 정보를 얻을 수 있는지 알아보자.




1. 
리플렉션(Reflection)

리플렉션은 구체적인 클래스 타입을알지 못해도컴파일된 바이트 코드를 통해 역으로 클래스에 정보를 알아내어
클래스를 사용할 수 있는 기법을 의미한다마치 거울에 비친 모습과 유사하여 리플렉션이란이름을 붙힌 것 같다

2. 
리플렉션을사용하는 이유

리플렉션은 조합(Composition)과 함께 사용되어 다형성을 구현하는 강력한 도구이다조합을사용하여 교체할 수 있는 
위임 클래스를 리플렉션을 통해 동적/정적으로 생성하고 교체하는 방식으로 사용된다프레임워크에서 유연성이 있는 동작을

위해 자주 사용되는 방식이기도 하다.



3. 
리플렉션을 통해 얻을 수 있는 정보

리플렉션을 통해 얻을 수 있는 정보에대해서 알아보자.

 

ClassName

 

Class Modifiers (public, private, synchronized )

 

Package Info

 

Superclass

 

Implemented Interfaces

 

Constructors

 

Methods

 

Fields

 

Annotations

 



이외에도 더 많은 클래스 정보를 얻을 수 있다.java.lang.Class 클래스에 대한 JavaDoc 문서를 참고하자

3.1 Class Object

클래스 정보를 얻기 위해 가장 먼저해야할 일은 정보를 담고 있는 java.lang.Class 객체를 획득하는 것이다.
프리미티 타입과 배열 타입을 포함하여 자바의 모든 타입들은 연관된 Class 객체를 가지고있으며컴파일 타임에
 
클래스의 이름을 알수 있다면다음과 같이 Class 객체를얻을 수 있다.

 ClassmyObjectClass = MyObject.class

 

컴파일 타임에 이름을 알수 없다면런타임에 문자열로 된 이름으로부터 클래스 객체를 아래와 같이 얻을 수 있다.

String className =  ... // 클래스 풀네임

 

Class myObjectClass = Class.forName(className);

 

이때 문자열로 된 클래스 이름은 패키지경로까지 포함한 풀네임이여야 하며해당 패키지에 클래스가 존재하지 않으면
Class.forName 
메소드는 ClassNotFoundException 예외를 던지게된다.



3.1 Class Name

Class객체로부터 2가지버전의 클래스 이름을 얻을 수 있다. getName() 메소드를 사용하면 패키지까지 포함한 풀네임을얻을 수
있고, getSimpleName() 을 사용하여 패키지가 포함되지 않은 클래스 이름을 얻을수 있다.

// 클래스 풀네임

 

Class aClass = ... // 이전에 얻은 클래스객체

 

String className = aClass.getName();

 

 

// 클래스 심플 네임

 

Class  aClass =... // 이전에 얻은 클래스 객체

 

String simpleClassName = aClass.getSimpleName();

 



3.2 Modifier

Class객체로부터 변경자에 접근할 수 있다클래스 변경자는 public, private, static 과 같은키워드를 의미한다.

클래스에 대한 플래그 비트가 설정된 int 값을 얻을 수 있으며 java.lang.reflect.Modifier 클래스에있는 메소드를 
통해 해당 플래그가 켜져있는지 확인할 수 있다.

// 변경자 얻기

 

Class  aClass =... // 이전에 얻은 클래스 객체

 

int modifiers = aClass.getModifiers();

 

 

// 변경자 플래그 확인 메소드들

 

Modifier.isAbstract(int modifiers)

 

Modifier.isFinal(int modifiers)

 

Modifier.isInterface(int modifiers)

 

Modifier.isNative(int modifiers)

 

Modifier.isPrivate(int modifiers)

 

Modifier.isProtected(int modifiers)

 

Modifier.isPublic(int modifiers)

 

Modifier.isStatic(int modifiers)

 

Modifier.isStrict(int modifiers)

 

Modifier.isSynchronized(int modifiers)

 

Modifier.isTransient(int modifiers)

 

Modifier.isVolatile(int modifiers)

 



3.3 Package Info
다음과 같이 Class 객체로부터 패키지에 대한 정보를얻는다.

// 패키지 정보 얻기

 

Class  aClass =... // 이전에 얻은 클래스 객체

 

Package package = aClass.getPackage();

 

 

Package객체로부터 패지지 이름과 같은 정보에 접근할 수 있다또한 패키지가 위치한 classpath에 있는jar 파일의 
Manifest 
파일에서도 이 패키지에 대한 특정한 정보를 얻을 수 있다

(
예를 들면 Manifest 파일에 지정된 패지키 버전 번호 같은...)



3.4 Superclass
아래와 같이 수퍼클래스의 class 객체를 얻을 수 있다.

// 수퍼 클래스의 class 객체 얻기

 

Class superclass = aClass.getSuperclass();

 



3.5 Implemented Interfaces
클래스 객체에 의해 구현된 인터페이스의 목록을 얻어보자.

// 구현한 인터페이스 목록 얻기

 

Class  aClass =... // 이전에 얻은 클래스 객체

 

Class[] interfaces = aClass.getInterfaces();

 

 

수퍼클래스가 구현한 인터페이스지만자식클래스가 특별히 해당 인터페이스를 구현한다고 명시하지 않으면
해당 인터페이스는 목록에 포함되지 않는 것에 주의하자구현하는 완전한 인터페이스의 목록을얻기 위해서는
 
자신의 수퍼클래스의 구현 인터페이스 목록을 재귀적으로 확인해야 한다.



3.6 Constructors
다음과 같이 클래스의 생성자 목록에 접근한다.

// 클래스 생성자 목록 얻기

 

Constructor[] constructors =aClass.getConstructors();

 




3.7 Methods 
다음과 같이 클래스의 메소드들에 접근한다.

// 메소드 목록 얻기

 

Method[] methods = aClass.getMethods();

 




3.8 Fields
다음과 같이 클래스의 멤버 변수들에 접근한다.

// 필드 목록 얻기

 

Field[] fields = aClass.getFields();

 


3.9 Annotations
다음과 같이 클래스의 어노테이션에 접근한다.

// 어노테이션 목록 얻기

 

Annotation[] annotations = aClass.getAnnotations();

 


※ 
리플렉션을 사용하여 Annotation을 처리하는 것은아래 포스트를 참고!!
Java-어노테이션(Annotation)
Java-어노테이션 사용하기


4. 
리플렉션 사용 예

게시물 정보를 갖는 Board 클래스에 대한 정보를 리플렉션을 사용하여 출력하는 예를 보자
Board 
클래스에 대한 정의는 아래와 같다.

package com.tistory.hiddenviewer.reflection;

 

import java.awt.event.ActionEvent;

 

import java.awt.event.ActionListener;

 

import java.util.ArrayList;

 

public class Board implements ActionListener{

 

         publicfinal static String boardName = "MyBoard";

 

         publicArrayList boardList;

 

        

 

         publicint seq;

 

         protectedString title;

 

         privateString contents;

 

        

 

         publicBoard() {

 

                 this(10);

 

         }

 

        

 

         publicBoard(int count) {

 

                 this.boardList= new ArrayList(count);

 

         }

 

        

 

        

 

         publicint getSeq() {

 

                 returnseq;

 

         }

 

         publicvoid setSeq(int seq) {

 

                 this.seq= seq;

 

         }

 

         public StringgetTitle() {

 

                 returntitle;

 

         }

 

         publicvoid setTitle(String title) {

 

                 this.title= title;

 

         }

 

         publicString getContents() {

 

                 returncontents;

 

         }

 

         publicvoid setContents(String contents) {

 

                 this.contents= contents;

 

         }

 

         @Override

 

         publicvoid actionPerformed(ActionEvent e) {

 

                 //TODO Auto-generated method stub

 

                

 

         }

 

}

 



Board 
클래스에 대한 정보를 리플렉션을 사용하여 출력하였다.

package com.tistory.hiddenviewer.reflection.executor;

 

import java.io.BufferedReader;

 

import java.io.IOException;

 

import java.io.InputStreamReader;

 

import java.lang.annotation.Annotation;

 

import java.lang.reflect.Constructor;

 

import java.lang.reflect.Field;

 

import java.lang.reflect.Method;

 

import java.lang.reflect.Modifier;

 

public class BoardReflectionExecutor {

 

         publicstatic void main(String[] args) throws IOException, ClassNotFoundException {

 

                

 

                 BufferedReaderbr = new BufferedReader(new InputStreamReader(System.in));

 

                

 

                 System.out.print("생성할 클래스 이름을 입력하세요(패키지 포함): ");

 

                 StringclassName = br.readLine();

 

                

 

                 Classcls = Class.forName(className);

 

                

 

                 //클래스 이름얻기

 

                 StringclassFullName = cls.getName();

 

                 StringclassSimpleName = cls.getSimpleName();

 

                

 

                 System.out.println("classfull name: " + classFullName);

 

                 System.out.println("classsimple name: " + classSimpleName);

 

                

 

                 //변경자 얻기

 

                 intmodifiers = cls.getModifiers();

 

                

 

                 if(Modifier.isPublic(modifiers)) {

 

                          System.out.println("classis public class");

 

                 }

 

                 if(Modifier.isFinal(modifiers)) {

 

                          System.out.println("classis final class");

 

                 }

 

                 //패키지 얻기

 

                 Packagepkg = cls.getPackage();

 

                 System.out.println("packagename: " + pkg.getName());

 

                

 

                

 

                 //수퍼클래스 얻기

 

                 ClasssuperCls = cls.getSuperclass();

 

                 System.out.println("superclass name :" + superCls.getName());

 

                

 

                

 

                 //구현 인터페이스 목록 얻기

 

                 Class[]interfaces = cls.getInterfaces();

 

                 for(Class cs : interfaces) {

 

                          System.out.println("thisclass implements " + cs.getName() + " interface");

 

                 }

 

                

 

                 //생성자 목록 얻기

 

                 Constructor[]conturctors = cls.getConstructors();

 

                 for(Constructor constructor : conturctors) {

 

 

                          System.out.println("Constructor:" + constructor.getName());

 

                 }

 

                //메서드 목록 얻기

                 Method[]methods = cls.getMethods();

 

                 for(Method method : methods) {

                          System.out.println(method.getReturnType()+ " " + method.getName() + "(...)");

                 }

 

                 //프로퍼티 목록 얻기

 

                 Field[]fields = cls.getFields();

 

                 for(Field field : fields) {

 

                          System.out.println(field.getType()+ " " + field.getName());

 

                 }

 

                 //어노테이션 얻기

 

                 Annotation[]annotations = cls.getAnnotations();

 

                 for(Annotation annotation : annotations) {

 

                          System.out.println(annotation.toString());

 

                 }

 

         }

 

}

 



출력결과

 

///////////////////////////////////////////////////////////////////////////////////////////


(2) 리플렉션(Reflection) 사용하기

 

지난 포스팅에 이어 리플렉션을 사용하여객체를 생성하고, private 필드에 접근하는 방법 그리고 메서드를 호출하는 
방법에 대해 알아보자.



1. 리플렉션을 사용하여 객체생성

리플렉션을 사용하여 런타임에 클래스에생성자들을 검사하고생성자 객체를 통해 객체를 생성하는 과정을 알아보자
아래와 같이 3가지 단계를 거치게 되며 가장 먼저 클래스의 생성자 객체 java.lang.reflect.Consturctor 를 얻어야 한다.

 

1.1 Constructor 객체 획득하기
다음과 같이 Class 객체로부터 Constructor 클래스를 얻는다.

// 생성자 목록 얻기

 

Class aClass = ... // 이전에 얻은 클래스객체

 

Constructor[] constructors = aClass.getConstructors();

 



Constructor[] 
배열은 클래스에 선언된 모든public 생성자의 Constructor 인스턴스를 가집니다특정한 파라미터를 갖는 
특정한 생성자는 다음과 같이 얻을 수 있다.

// 특정 파라미터를 갖는 생성자 얻기

 

Class aClass = ... // 이전에 얻은 클래스객체

 

Constructor constructor =

 

aClass.getConstructor(new Class[]{String.class});

 



위 예는 하나의 String 타입 파라미터를 갖는 생성자를반환하는데일치하는 파라미터를 갖는 생성자가 없으면 
NoSuchMethodException 
예외가 발생한다.

1.2 Constructor 파라미터 얻기 
다음과 같이 생성자에 포함된 파라미터 타입 목록을 얻을수 있다.

// 생성자의 파라미터 타입목록 얻기

 

Class aClass = ... // 이전에 얻은 클래스객체

 

Class[] parameterTypes =constructor.getParameterTypes();

 



1.3 Constructor 객체를 사용하여 객체 생성하기
다음과 같이 생성자 객체로부터 객체를 생성한다.

// 하나의 String 파라미터를 갖는 생성자를 얻는다.

 

Constructor constructor =MyObject.class.getConstructor(String.class);

 

// 생성

 

MyObject myObject = (MyObject)constructor.newInstance("constructor-arg1");

 


Constructor.newInstance() 
메서드는 선택적인 개수의 파라미터를취한다하지만 반드시 해당 생성자에 맞는 개수와 타입의

파라미터를 제공해야 한다는 것에 주의하자.





2. 리플렉션을 사용하여 Field 에 접근하기

리플렉션을 사용하여 클래스의 모든멤버 변수를 검사할 수 있으며런타임에 값을 얻어오거나 설정할 수 있다.

이때 하나의 프로퍼티 당 하나의 java.lang.reflect.Field 클래스 객체를 사용하게 된다.

 

2.1 Field 객체 얻기

다음과 같이 Field 객체를 얻는다.

// 클래스에 선언된 public 속성의 Field 객체얻기

 

Class aClass = ... // 이전에 얻은 클래스객체

 

Field[] fields = aClass.getFields();

 

 

Field[]배열은 클래스에 선언된 각 public field 당 하나의 Field 객체를 갖는다.(public field 만을 갖는다는 것에 주의)
접근하려는 필드의 이름을 안다면다음과 같이 접근할 수 있다.

// Field에 접근하기

 

Class  aClass =MyObject.class

 

Field field = aClass.getField("someField");

 

 

위 예제는 아래 MyObject 에 선언된 someField 에 대응하는 Field 인스턴스를 반환합니다.

public class MyObject{

 

public String someField = null;

 

}

 


getField() 
메서드의 파라미터에 해당하는 이름의 필드가 클래스에 존재하지않으면 NoSuchFieldException 예외가발생한다.

2.2 Field 이름 얻기

Field인스턴스를 획득하면다음과같이 Field.getName()을 사용하여 이름을 얻을 수 있다.

// Field 이름 얻기

 

Field field = ... // Field 객체를 얻는다.

 

String fieldName = field.getName();

 

 

2.3 Field 타입 얻기

Field.getType()메서드를 사용하여 필드의 타입을 얻을 수 있다.

// Field 타입 얻기

 

Field field = aClass.getField("someField");

 

Object fieldType = field.getType();

 

 

2.4 Field 값 조회하고 설정하기 
Field 
에 대한 참조를 얻게되면, Field.get(), Field.set() 메소드를 사용하여 값을 얻거나 설정할수 있다.

// Field 값 설정하고 조회하기

 

Class  aClass =MyObject.class

 

Field field = aClass.getField("someField");

 

MyObject objectInstance = new MyObject();

 

Object value = field.get(objectInstance);

 

field.set(objetInstance, value);

 


field.get(), set() 
메서드에는 해당 필드를 소유하는 객체가 인자로전달되야 하며 만일 static 필드라면 null을전달한다.

2.5 Private Field 에 접근하기

클래스의 Private 필드는 외부클래스에서 접근 할 수 없지만리플렉션을사용하면 접근이 가능하다캡슐화를 깨는 동작일 
수 있지만단위테스트와 하이버네이트와 같은 프레임워크에서 유용하게 사용되기도 한다
private 
필드에 접근하기 위해서는 Class.getDeclaredField(Stringname) Class.getDeclaredFields() 메서드를 사용한다. (Class.getField(String name) Class.getFields() 메서드는 public 필드만을 반환한다.)


다음은 privatefield에 접근하는 예이다.

 

Board board = new Board();

 

board.setContents("test contents...");

 

                

 

Field field = cls.getDeclaredField("contents");

 

field.setAccessible(true);

 

String contents = (String) field.get(board);

 

                

 

System.out.println("Private Contents Field:" + contents);

 


Field 
객체의 setAccessible(true)를 호출하지 않고, private 필드값을 조회하려고 하면 IllegalAccessException 예외가 발생한다




3. 리플렉션을 사용하여 Method 호출하기

java.lang.reflect.Method클래스를 사용하여 메서드를 검사하고 호출 하는 방법을 알아보자.



3.1 Method 객체 얻기

Method클래스를 다음과 같이 획득한다.

// Method 객체목록 얻기

 

Class aClass = ... // 이전에 얻은 클래스객체

 

Method[] methods = aClass.getMethods();

 

 

역시 클래스에 선언된 public 메서드당 하나의 Method 인스턴스를 갖으며메서드의 구체적인 파라미터 타입들을 알고 있으면 
해당 메서드의 인스턴스를 얻을 수 있다다음은String 파라미터 하나를 갖는 doSomething 메소드의 Method 객체를 얻는다.

// 파라미터를 갖는 메소드의 Method 객체 얻기

 

Class aClass = ... // 이전에 얻은 클래스객체

 

Method method = aClass.getMethod("doSomething",new Class[]{String.class});

 

 

만일 일치하는 메서드가 없으면NoSuchMethodException이 발생한다파라미터가없는 메서드를 얻기 위해서는 파라미터 
배열 대신 null을 전달한다.

// 파라미터가 없는 메서드의 Method 객체 얻기

 

Class aClass = ... // 이전에 얻은 클래스객체

 

Method method =aClass.getMethod("doSomething", null);

 



3.2 Method 파라미터와 반환값 얻기

다음과 같이 해당 메서드의 파라미터들을얻을 수 있다.

// 메서드의 파라미터 타입목록 얻기

 

Method method = ... //

 

Class[] parameterTypes = method.getParameterTypes();

 


메서드의 반환타입은 다음과 같이 접근한다.

// 메서드의 반환값 타입 얻기

 

Method method = ... //

 

Class returnType = method.getReturnType();

 



3.3 Method 객체를 사용하여 메서드 호출하기(Invoking)

다음과 같이 메서드를 호출할 수 있다.

// 메서드 호출

 

Method method =MyObject.class.getMethod("doSomething", String.class);

 

Object returnValue = method.invoke(null,"parameter-value1");

 

 

invoke()메소드에는 호출하기를 원하는 객체를 전달하며, static 메서드이면 null을 대신 전달한다
Method.invoke(Object target, Object...parameters) 
메서드는 선택적인 개수의 파라미터를 취하지만메서드가 필요로 하는 
정확한 개수의 파라미터를 전달해야 한다

3.4 Private Method 에 접근하기 
Private Field 
에 접근하는 것과 유사하게 Class.getDeclaredMethod(String name) Class.getDeclaredMethods()메서드를 사용한다
(Class.getField(String name) 
 Class.getFields() 메서드는 public 필드만을 반환한다.)


/////////////////////////////////////////////////////////////////////////////////////////////////


리플렉션으로 Getter 와 Setter 검사하기

 

리플렉션을 사용하여 해당 클래스가 어떤 getter setter를 갖는지 검사하는 예를 알아보자.



클래스에 gettersetter 메서드만을 검사할 수는 없으며모든 메서드들을 스캔하여 getter인지 setter인지를 확인해야 한다.

getter setter 메서드가나타내는 특성은 다음과 같다.

 

Getter  “get” 이라는 이름으로 시작하며파라미터가 없고 하나의 값을 반환한다.

Setter  “set” 이라는 이름으로 시작하며하나의 파라미터를 취한다.


몇몇 setter는 값을 반환하지 않을 수도 있고 또 어떤 것은집합값을 반환하거나 어떤 것들은 메서드 chaining을 위해 
값을 반환할 수 있기 때문에 setter의 반환타입에 대한 가정을 해서는 안된다.
다음은 getter setter를 찾아 출력하는예이다.

package com.tistory.hiddenviewer.reflection.executor;

 

import java.lang.reflect.Method;

 

import com.tistory.hiddenviewer.reflection.Board;

 

public class GetterSetterPrinter {

 

        publicstatic void main(String[] args) {

 

               printGettersSetters(Board.class);

 

        }

 

        publicstatic void printGettersSetters(Class aClass){

 

               Method[]methods = aClass.getMethods();

 

               for(Methodmethod : methods){

 

                       if(isGetter(method)){

 

                               System.out.println("getter:" + method);

 

                       }

 

                       if(isSetter(method)){

 

                               System.out.println("setter:" + method);

 

                       }

 

               } //for

 

        }

 

       

 

        publicstatic boolean isGetter(Method method){

 

               //get 으로 시작하지 않으면 반환

 

               if(!method.getName().startsWith("get")){

 

                       returnfalse;

 

               }

 

               // 파라미터가 있으면 반환

 

               if(method.getParameterTypes().length!= 0) {

 

                       returnfalse;

 

               }

 

               // 반환값이 없으면 반환

 

               if(void.class.equals(method.getReturnType())){

 

                       returnfalse;

 

               }

 

               returntrue;

 

        }

 

        publicstatic boolean isSetter(Method method){

 

               //set 으로 시작하지 않으면 반환

 

               if(!method.getName().startsWith("set")){

 

                       returnfalse;

 

               }

 

               // 파라미터가 개수가 1이 아니면 반환

 

               if(method.getParameterTypes().length!= 1) {

 

                       returnfalse;

 

               }

 

               returntrue;

 

        }

 

}

 



출력결과



출처 - http://blog.naver.com/PostView.nhn?blogId=kimgas2000&logNo=90133009397







제공 : 한빛 네트워크
저자 : Russ Olsen
역자 : 백기선
원문 : Reflections on Java Reflection 

일상에서의 리플렉션(reflection)이란 거울 속에 비친 자신의 모습입니다. 프로그래밍 세상에서의 리플렉션은 프로그램이 자신의 모습을 보고 심지어 수정하기 까지 하는 것을 말합니다. Java reflection API는 바로 그런 기능을 언어의 기본 요소인 클래스, 필드, 메소드를 들여다 볼 수 있는 평범한 Java API를 통해 제공합니다. 리플렉션을 이해하는 것은 여러분이 자주 사용하는 툴을 이해하는데 도움이 됩니다. Eclipse가 어떻게 자동완성으로 메소드 이름을 만들어 줄까? Tomcat은 web.xml파일에 있는 클래스 이름을 가지고 웹의 요청을 처리할 서블릿을 실행하는 걸까? Spring은 어떻게 마술 같은 dependency injection을 하는 것일까? 여러분의 프로그램에서도 리플렉션을 사용하여 보다 동적이고 유연한 코드를 작성하실 수 있습니다. 리플렉션을 사용하면 이전에 본적 없는 클래스들을 매우 멋지게 처리할 수 있습니다. 

클래스 만들기 

이미 말했듯이 리플렉션의 기본 아이디어는 프로그램이 동작하는 내부에 집어 넣을 API를 제공하는 것입니다. Java에서 가장 기본이 되는 사상이 바로 클래스기 때문에(클래스 없이 자바 프로그램을 만들어 보세요) Class 클래스부터 살펴보는 것이 좋겠습니다. 이것의 객체는 Class 타입일 것입니다. 일단 Class객체를 가지게 되면 그것으로부터 클래스에 관련된 모든 정보를 뽑아낼 수 있습니다. 클래스의 이름, 그것이 public 인지 abstract 인지 final 인지 그리고 심지어 상위 클래스까지 말이죠. 

이 정도면 이론은 충분합니다. 자, 이제 리플렉션 현미경을 가지고 아래에 있는 매우 간단한 Employee 클래스를 살펴봅시다.

package com.russolsen.reflect;

public class Employee
{
   public String _firstName;
   public String _lastName;
   private int _salary;

      public Employee()
   {
      this( "John", "Smith", 50000);
   }
 
   public Employee(String fn, String ln, int salary)
   {
      _firstName = fn;
      _lastName = ln;
      _salary = salary;
   }
   
      public int getSalary()
   {
      return _salary;
   }
   
   public void setSalary(int salary)
   {
      _salary = salary;
   }
   
   public String toString() 
   {
      return "Employee: " + _firstName +  " "
             + _lastName + " " + _salary;
   }

}

Class 객체를 만드는 가장 쉬운 방법은 해당 클래스 객체의 getClass 메소드를 호출하는 것입니다. 아래에 있는 코드는 Employee 객체를 만들고 그것의 Class 객체를 만들어서 클래스에 대한 다양한 정보들을 출력합니다.

package com.russolsen.reflect;

import java.lang.reflect.Modifier;

public class GetClassExample
{
   public static void main(String[] args)
   {
 
      Employee employee = new Employee();
      
      Class klass = employee.getClass();
      
      System.out.println( "Class name: " + klass.getName());
      System.out.println( 
            "Class super class: " + klass.getSuperclass());
      
      int mods = klass.getModifiers();
      System.out.println( 
            "Class is public: " + Modifier.isPublic(mods));
      System.out.println( 
            "Class is final: " +  Modifier.isFinal(mods));
      System.out.println( 
            "Class is abstract: " + Modifier.isAbstract(mods)); 

   }

}

코드를 실행하면 다음과 같은 결과를 확인할 수 있습니다.

Class name: com.russolsen.reflect.Employee
Class super class: class java.lang.Object
Class is public: true
Class is final: false
Class is abstract: false

예제에서 보이듯이 클래스의 이름과 상위 클래스를 알아내는 것은 다른 접근 메소드들(Getters or Accessors)을 호출하는 것처럼 쉬운 일입니다. 만약 해당 클래스가 public 인지 abstract 인지 final 인지 알고 싶다면 약간 복잡해 집니다. 이 모든 정보가 getModifires에 의해 하나의 int 값으로 패키징되어 넘어오기 옵니다. 다행히 Java는 Modifier 클래스를 통해 getModifiers에서 넘어온 숫자를 가지고 여러 일을 할 수 있는 static 메소드들을 제공해 줍니다. 

객체에 getClass를 호출하는 것 만이 Class 객체를 얻을 수 있는 유일한 방법은 아닙니다. 클래스 이름을 사용하여 직접 얻을 수도 있습니다.

Class klass = Employee.class;

세 번째 방법은 좀 더 흥미로운 방법입니다. 문자열을 통해서 Class 객체를 생성할 수 있습니다. 물론 그 문자열이 클래스 이름을 포함하고 있을 때 말이죠. 다음과 같이 Class 클래스에 있는 forName 을 호출하여 얻을 수 있습니다.

      Class klass = Class.forName("com.russolsen.reflect.Employee");
      
      System.out.println( "Class name: " + klass.getName());
      System.out.println( 
            "Class super class: " + klass.getSuperclass());
      
      // Print out the rest...

forName을 사용할 때 한 가지 주의할 것은 클래스 이름 앞에 완전한 패키지 경로를 붙여줘야 한다는 것입니다. 평범하고 늙은 “Employee” 말고 “com.russolsen.reflect.Employee" 여야만 합니다. forName을 통해 리플렉션 API의 기본적인 강력함(그리고 멋진 것)을 살펴봤습니다. 클래스 이름을 포함한 문자열로 시작할 수도 있고 class로 끝낼 수도 있습니다. 

바로 인스턴스 만들기 

class 객체를 가져오고 그것에 관한 정보를 찾는 것은 그것 자체로도 흥미롭고 유용합니다. 하지만 진짜 재미있는 것은 리플렉션을 사용하여 무언가를 실제 해보는 것입니다. Class 객체를 가지고 할 수 있는 가장 눈에 띄는 작업은 그 클래스의 새로운 객체를 만드는 것입니다. newInstance 메소드를 사용하여 간단하게 만들 수 있습니다. 실제 사용하는 것을 보여 주기 위하여 아래에 있는 간단한 프로그램은 커맨드 라인 인자(command line argument)로 Class 객체를 만들고 그 클래스 타입의 객체를 만드는 프로그램을 보여줍니다.

package com.russolsen.reflect;

public class NewInstanceExample
{
   public static void main(String[] args)
      throws ClassNotFoundException,
      InstantiationException, IllegalAccessException
   {

      Class klass = Class.forName(args[0]);
      Object theNewObject = klass.newInstance();
      System.out.println("Just made: " + theNewObject);
   }
}

위 코드를 "com.russolsen.reflect.Employee" 인자와 함께 실행하면 새로운 Employee 객체를 만들게 됩니다.

Just made: Employee: John Smith 50000

Run it again, but this time feed it "java.util.Date" and you will get:

Just made: Tue Feb 27 20:25:20 EST 2007

간단한 코드 몇 줄로 얼마나 많은 유연성을 얻게 되었는지 생각해보세요. 위에 있는 프로그램은 실제 Employee 나 Date 클래스에 관해 아는 것이 하나도 없지만 각각의 새로운 객체들을 만들 수 있습니다. 이것이야 말로 Java 프로그래밍을 하는 또 다른 방법입니다. 

인자 없는 생성자 너머에 

Class.newInstance 메소드를 호출하는 것은 인자 없이 new를 사용하는 것과 동일합니다. 그러나 만약 인자가 없는 생성자 즉 default 생성자가 없는 클래스에 newInstance를 호출하면 어떻게 될까요? 좋을 일 없습니다. 별로 맘에 안 드는 InstantiationException을 받게 됩니다. 

좋은 소식이 있습니다. 생성자 인자를 필요로 하는 클래스의 객체를 동적으로 만들 수 있습니다. 하지만 약간 더 힘든 작업을 해야 합니다. 그건 바로 클래스에서 해당 생성자를 찾고 적당한 인자를 사용하여 그것을 호출하는 일입니다. 적당한 생성자를 찾는 일은 여러분이 찾고자 하는 생성자에 대한 정보를 가지고 getConstrucor 메소드를 사용하면 됩니다. 그럼 Constuctor 객체를 얻게 되고 그것을 사용하여 새로운 객체를 만들 수 있습니다.

Let's see how this all works in code:
      Class klass = Class.forName("com.russolsen.reflect.Employee");

      Class[] paramTypes = {
            String.class, 
            String.class, 
            Integer.TYPE };
      
      Constructor cons = klass.getConstructor(paramTypes);
      
      System.out.println( "Found the constructor: " + cons);

      
      Object[] args = { 
            "Fred", 
            "Fintstone", 
            new Integer(90000) };
      
      Object theObject = cons.newInstance(args);
      System.out.println( "New object: " + theObject);

생성자들 사이의 차이점은 오직 그것들이 가지고 있는 매개 변수들입니다. 따라서 getConstructor 메소드에 찾고자 하는 생성자에 들어갈 매개변수 각각의 Class들 객체의 배열을 넘겨줍니다. 위에 있는 예제에서는 두 개의 String 그리고 하나의 int를 받는 생성자를 찾게 됩니다. Constructor 객체를 얻은 뒤 새로운 객체를 생성하는 일은 간단합니다. 실제 인자로 들어갈 객체의 배열을 newInstance 메소드를 호출하면서 넘겨주면 됩니다. 

geConstructor에는 조그만 지뢰가 하나 있습니다. 파라미터의 타입으로 생성자를 식별하여 원하는 것을 찾을 때 primitive 인자와 그것의 wrapper 클래스를 잘 구별해야 합니다. 생성자가 인자로 primitive 타입인 int를 받는 것인가 아니면 그것의 삼촌 격인 java.lang.Integer 클래스의 객체를 받는 건가요? 만약 java.lang.integer 같은 wrapper 타입의 객체를 받는 생성자라면 Integer.class처럼 wrapper 클래스를 사용하면 됩니다. primitive 타입인 int를 사용하는 생성자라면 Integer.TYPE을 사용합니다. TYPE은 primitive을 위해 마련한 것입니다. 모든 wrapper 클래들은 static 타입인 TYPE 필드를 가지고 있고 이것을 사용하여 primitive 타입이라는 것을 알려줄 수 있습니다. 역자 백기선님은 AJN(http://agilejava.net)에서 자바 관련 스터디를 하고 있는 착하고 조용하며 점잖은 대학생입니다. 요즘은 특히 Spring과 Hibernate 같은 오픈소스 프레임워크를 공부하고 있습니다. 공부한 내용들은 블로그(http://whiteship.tistory.com)에 간단하게 정리하고 있으며 장래 희망은 행복한 개발자입니다.



출처 - http://www.hanb.co.kr/network/view.html?bi_id=1369







Posted by linuxism
,


Enumeration VS Iterator

콜렉션 프레임워크의 클래스들은 객체를 저장하고 정리하기 위해 사용된다. 
콜렉션 클래스들 내에 저장된 객체들을 차례로 접근하기 위한 방법을 콜렉션 뷰라고 한다.
 자바 2 이전 버전에서 사용되던 Vector, Hashtable의 뷰 객체는 Enumeration 객체이며, 
자바 2의 콜렉션 프레임워크에서 콜렉션 뷰는 Iterator와 ListIterator 객체이다.
 

Vector 클래스의 객체인 v 객체의 모든 요소(저장된 객체)들을 Enumeration 뷰를 이용하여 프린트하려면 다음과 같다.

for (Enumeration e = v.elements() ; e.hasMoreElements() ;) {
    System.out.println(e.nextElement());
}

같은 Vector 클래스의 객체 v 객체를 Iterator 뷰를 이용하여 모든 요소를 프린트하려면 다음과 같다.

for (Iterator it = v.iterator() ; it.hasNext() ;) {
    System.out.println(it.next());
}

Iterator 객체가 Enumeration 객체와 다른 점은 Iterator 객체는 콜렉션에 대하여 remove() 메소드를 제공한다는 점이다.
 또한 메소드의 이름이 훨씬 명시적이다. 
그리고, 자바 2 버전에서는 Enumeration 보다는 잘 정의된 콜렉션 뷰 객체인 Iterator를 사용할 것을 권하고 있다.


Fail-fast

그러나, Enumeration 객체와 Iterator 객체의 뷰 방식에는 또 다른 차이점이 있다. 
자바 2 이전 버전에서 사용되던 Vector, Hashtable의 뷰 객체인 Enumeration은 fail-fast 방식이 아니었으나, 
자바 2의 콜렉션 프레임워크에서 콜렉션 뷰인 Iterator, ListIterator 객체는 fail-fast 방식이라는 점이다.

콜렉션 뷰는 콜렉션 객체에 저장된 객체들에 대한 순차적 접근을 제공한다. 그러나,
 뷰 객체인 Enumeration 또는 Iterator 객체를 얻고 나서 순차적 접근이 끝나기 전에 뷰 객체를 얻은 하부
 콜렉션 객체에 변경이 일어날 경우, 순차적 접근에 실패하게 된다.
 여기서 변경이라는 것은 콜렉션에 객체가 추가되거나 제거되는 것과 같이 콜렉션 구조의 변경이 일어나는 경우를 말한다.

이런 상황은 멀티쓰레드 구조와 이벤트 구동 모델에서 일어날 수 있으며, 
개발자가 혼자 테스트할 경우 발견하기 어려운 문제이다. 따라서 정확한 이해와 예상이 필요하며,
 이에 대한 대처 방안을 마련해야 한다.

하부 콜렉션 객체에 변경이 일어나 순차적 접근에 실패하면 Enumeration 객체는 실패를 무시하고 
순차적 접근을 끝까지 제공한다. Iterator 객체는 하부 콜렉션 객체에 변경이 일어나 순차적 접근에 실패하면 ConcurrentModificationException 예외를 발생한다. 
이처럼 순차적 접근에 실패하면 예외를 발생하도록 되어 있는 방식을 fail-fast라고 한다.

Iterator는 fail-fast 방식으로 하부 콜렉션에 변경이 발생했을 경우, 신속하고 결함이 
없는 상태를 만들기 위해 Iterator의 탐색을 실패한 것으로 하여 예외를 발생하며,
 이렇게 함으로써 안전하지 않을지도 모르는 행위를 수행하는 위험을 막는다.

왜냐하면 이러한 위험은 실행 중 불특정한 시간에 멋대로 결정되지 않은 행위를 할 가능성이 있기 
때문에 안전하지 않다고 할 수 있기 때문이다.


출처 - http://darkmirr.egloos.com/1227598











Posted by linuxism
,


2012/03/12 04:41

@ModelAttribute와 @SessionAttributes의 이해와 한계 Spring MVC

@MVC에는 개발자들에게 프로그래밍을 예술의 경지까지 승화시켜주는 다양한 기술들이 존재하지만 그 중에서도 가장 아름다운 것을 꼽으라면 어노테이션을 통한 자동 객체변환을 꼽을 수 있겠다. 그리고 그 자동 객체변환 기술 중에서도 가장 아름다운 것은 @SessionAttributes와 @ModelAttribute… 개인적인 느낌으론 그야말로 객체변환의 결정체라고 할 수 있겠다.


@ModelAttribute

먼저 @ModelAttribute를 살펴보자. 필자가 @MVC를 처음 접했을 때는 어노테이션이라는 것 마저도 굉장히 생소했고 어노테이션만으로 이런 말도 안되는 기술이 구현가능하다는 사실에 깜짝 놀랐었다. 몸만 안 자빠졌을 뿐이지 정신은 안드로메다로 내달리는 정도라고나 할까? 굳이 예를 들자면… 당신이 90년대 편지 밖에 없는 세상에서 살다가 다음날 갑자기 아이폰4S를 만지작 거리고 있는 수준이다.

@ModelAttribute는 다른 말로 커맨드 오브젝트라고도 불리는데 그 이유는 클라이언트가 전달하는 파라미터를 1:1로 전담 프로퍼티에 담아내는 방식이 커맨드 패턴 그 자체이기 때문이다. 위의 이미지와 같이 @ModelAttribute는 클라이언트의 파라미터를 완벽하게 이해하고 있는 자바빈 객체를 요구하며 이 객체에 @ModelAttribute란 어노테이션를 붙이면 자동으로 바인딩해주고 중간 과정은 모두 자동으로 생략 해준다.

@Controllerpublic class HomeController {

@RequestMapping(value="/", method=RequestMethod.GET)
public String home(@ModelAttribute Command command) {
...
}
}

위의 예를 보면 바인딩 과정이 코드에 전혀 나타나있지 않고 전달인자에 @ModelAttribute를 넣는 것 만으로 모두 생략이 가능해 졌다. 만약 위의 코드를 서블릿으로 대체하자면 아래와 같이 작성할 수 있을 것이다.

public class Controller extends HttpServlet {
private static final long serialVersionUID = 1L;

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

String parameter1 = request.getParameter("parameter1");
String parameter2 = request.getParameter("parameter2");
String parameter3 = request.getParameter("parameter3");
String parameter4 = request.getParameter("parameter4");

...

}
}

사실 위의 작성한 서블릿 코드도 @ModelAttribute의 기능 중 고작 파라미터를 바인딩하는 정도만 구사해낸 저급 수준의 코드이다. 과거에는 이렇듯 파라미터로 전송받는 데이터를 처리하고 DB에 넣는 과정이 상당히 까다로왔다. 간단한 회원가입 조차도 데이터를 처리하는데 복잡한 로직이 필요했으니 말이다. 특히 핸드폰이나 집전화 같은 경우는 하나의 값에 3개의 <input>을 사용하는데다 생년월일 같은 값은 년, 월, 일을 다 각자 받아내 Date형태로 변화하려고 했으니 꽤나 애를 먹을 수 밖에 없었다. (여기다 에러처리까지 포함하면 코드가 순식간에 산더미가 되곤 한다.) 헌데 이런 모든 과정을 스프링이 이해하고 있고 자동으로 모든 작업 대신 해주니 얼마나 감사한 일인가.

@ModelAttribute를 사용하는 방법은 다음과 같다. 우선 클라이언트가 전송할 파라미터를 하나씩 바인딩 할 수 있는 커맨드형 자바빈 클래스를 하나 만든 뒤 파라미터의 이름과 커맨드 오브젝트의 프로퍼티 명을 맞춰준다. 그리고 해당 리소스를 처리할 컨트롤러를 만든 뒤 파라미터를 @ModelAttribute를 이용해 커맨드 오브젝트에 바인딩 시켜주면 된다. 이게 지금은 이해가 잘 가지 않아도 알고나면 굉장히 쉽다. 그래도 처음 접하는 사람에겐 전혀 상식적이지 않은 기술이므로 @ModelAttribute가 이해하는 바인딩 규칙의 예를 좀 더 꼼꼼히 들어보도록 하겠다.

public class Command {
String id;
String pass;
String name;

--* <input name="id" /> = *-- public void setId(String id) { this.id = id }
--* <input name="pass" /> = *-- public void setPass(String pass) { this.pass = pass }
--* <input name="name" /> = *-- public void setName(String name) { this.name = name }

(get~ 생략)

}

위의 예제를 보면 조금 @ModelAttribute의 동작원리를 이해할 수 있을 것이다. name="id"는 setId() 메서드와 관계를 맺고 있고 name="pass"는 setPass() 메서드와 밀접한 관계를 맺고 있다. 스프링은 이런 명명규칙만으로도 해당 폼에서 전송되는 파라미터와 커맨드 오브젝트의 관계를 이해해 낼 수 있다. 결론은 이런 식으로 일일이 대입되는 커맨드 오브젝트만 있으면 나머지는 스프링 컨테이너가 알아서 바인딩 과정을 처리해준다는 뜻이다. 게다가 이게 끝이 아니다. @ModelAttribute는 BindingResult 전달 인자와 @Valid와 합쳐서 더욱 멋진 기술을 구현해 낼 수 있다.

@Controllerpublic class Controller {
@RequestMapping(value="/", method=RequestMethod.GET)
public String home(@ModelAttribute @Valid Command command, BindingResult result) { if(result.hasErrors()) ...
else ...
}
}

BindingResult 전달인자는 자동으로 커맨드 오브젝트에 내장되 있는 검증 어노테이션을 기준으로 에러를 판독해 그 결과값을 가져와 주고 오류가 있을 경우 손쉬운 후처리 기능을 제공해 해준다. 그 뿐인가. 바인딩의 에러 내용을 다시 뷰에 전송해주고 <form:form> 태그와 같은 SpringEL을 이용해 바인딩 에러 결과를 브라우져를 통해 클라이언트에게 보여주는 작업 또한 간편하게 작성할 수 있게 해준다. 게다가 정말 신기한 것은 enum 객체같은 경우라도 파라미터의 이름만 알고 있다면 클래스명.파라미터명과 같이 매핑해준다는 사실이다.

여기까지 설명을 들었다면 이제 @ModelAttribute가 얼마나 활용도 높은 기술인지 대략 짐작할 수 있을 것이다. 토비의 스프링에서도 딱 이정도 수준까지 설명됬던지라 (물론 이것보다 자세하게…) 여기까지 이해가 됬다면 @ModelAttribute가 세상에서 제일 좋은 킹 오브 킹 어노테이션으로 생각할 수도 있겠다. 헌데 아쉽게도 @ModelAttribute에는 분명한 한계가 존재한다. 물론 단점보다 장점이 많은 기술이므로 이정도 단점에 사용이 꺼려질 정도가 되는 것은 아니지만 @ModelAttribute도 모든 바인딩 과정에서 완벽할 수는 없다는 결론 정도로 생각할 수 있다.


배열 방식 객체의 까다로움

필자는 어느날 @ModelAttribute를 사용해 개발을 하다가 일순 멈칫하고 말았다. 이유는 @ModelAttribute를 활용하면 배열로 전달되는 파라미터의 처리가 상당히 까다로워 진다는 인식을 받았기 때문이다. 솔직히 기존의 서블릿과 자바 애플릿으로 했던 노가다에 비하면 세발의 피같은 어려움이긴 하지만 그래도 이런 기술을 좀 더 깔끔하게 사용하고 싶다는 욕구 때문인지 많은 아쉬움이 남았다. 내용은 바로 아래와 같다.

@RequestMapping(value="/", method=RequestMethod.POST)
public String home(@ModelAttribute List<Command> command) {
...
}

왜 위와 같은 형태로 전달인자를 받고 싶었냐면 (아직 @SessionAttributes로 넘어가지 않아 자세히 설명할 수는 없지만…) 만약 동일한 커맨드 오브젝트가 2개 이상 전달될 것으로 예상되는 폼 구성이라면 넘어오는 커맨드 오브젝트를 하나씩 DB에 저장시키기엔 낭비와 후처리가 상당히 까다로웠기 때문이다. 만약 입력 중간에 사용자가 변심한다면 DB의 자료를 롤백시켜주는 기능도 필요했고 추가/변경 기능까지 구성하려다 보면 그 과정이 상당히 번거로워지기 마련이었다. 그렇기 때문에 이런 처리는 가급적 세션으로 사용자의 파라미터를 List 형태로 저장시키고 사용자의 입력이 완료되면 한번에 DB에 입력하고 싶었다. 그러면 불필요한 입출력을 줄일 수 있을테고 불량 데이터로 인한 시스템 리소스의 낭비도 덜할테니 말이다.

헌데 @ModelAttribute는 위와같은 형태의 데이터를 허용하지 않았다. 사실 @ModelAttribute 뿐만이 아니라 @SessionAttributes도 그렇고 @MVC의 어노테이션 대부분이 위와 같은 컬렉션프레임워크 형태의 데이터를 처리하지 못한다. 어찌보면 당연한 일이지만 사뭇 당연히 될 것이라 기대했던 나로서는 한풀 꺽인 경우가 됬다.

@ModelAttribute는 매우 뛰어난 바인딩 기술로 정평이 나있지만 해당 값을 복수의 형태로 받고 싶다면 오로지 자바빈 객체를 배열로 선언하는 방법 밖에 없다.

public class Command {
private String[] id;
private String[] pass;
private String[] name;
...
}

물론 이렇게 처리하는  방법도 결코 나쁜 방식은 아니다. 문제는 기본 배열로 객체를 다루다 보니 처리방식이 너무 정형화되어 유동적으로 변하는 <form>에는 대응하기가 조금 까다로워진데다 우리가 이용하려던 @Valid를 통한 검증기능이 많이 축소되었다는 것이다. 왜냐하면 @Valid는 단일 객체에 대한 검증기능은 풍부해도 배열형태의 자료를 검증하는 기능은 고작 길이 정도를 검증하는 기능 밖에 지원되지 않기 때문이다.

그러나 아무리 생각해도 파라미터 배열 바인딩은 포기할 수 없는 필수 요소 중 하나이다. 예를 들어 한 사람의 경력을 폼으로 입력받는다 할 때 모든 사람이 평생 하나의 직업과 직장을 가지는 것은 아니므로 유동적으로 배열 파라미터를 처리할 수 있는 폼 설계가 필요하다. 그리고 해당 파라미터 값이 문제가 있을 경우 다른 단일 객체와 똑같이 배열이 아닌 배열 속의 객체에 바인딩 에러를 처리해주는 기술이 필요하다. (@Valid의 기능만으론 이 조건을 충족시킬 수 없었다. 물론 확장한다면 상황이 달라질 수도 있겠지만…)

그렇다고 이런 제약 때문에 @ModelAttribute를 포기할 수도 없는 노릇이고, 안되는 걸 어거지로 끼워맞추자니 뭔가 앞뒤가 안 맞는 것 같은 기분이 들었다. 이런 모든 상황에 맞는 처리 방법은 @ModelAttribute가 List<Command> command와 같은 컬렉션프레임워크 형태를 지원하는 것 뿐이었으니 아쉬움은 더 클 수밖에 없었다. 그래도 포기하는 것은 이르고 아직 이 문제에 대한 해결법을 찾지 못했지만 추후에 좀 더 심층적으로 연구한 뒤에 해결법을 공개해보도록 하겠다.


@SessionAttributes과 SessionStatus

@SessionAttributes를 이해하기 위해선 아래의 참고 UML을 이해하는 과정이 필요하다. 물론 아직 아래의 UML만으로는 이해가 쉽지 않다는 것을 잘 알고 있다. 왜냐하면 우리는 모델 오브젝트 준비가 뭔지도… 프로퍼티 바인딩이 뭔지도, WebBindingInitializer라는게 뭔지도 제대로 이해하지 않고 있기 때문이다. 아직 아래의 UML을 이해하지 못하는 것은 당연한 일이며 억지로 이해하려하기 보다는 당장 우리가 이해하려는 @SessionAttribute 부분만 살펴보고 대략적으로 이해할 수 있도록 해보자.

클라이언트 -> 스프링 컨테이너 -> 서버의 과정
클라이언트 <- 스프링 컨테이너 <- 서버의 과정
위의 UML은 필자가 만든 것은 아니며 토비의 블로그에서 퍼온 것인데 @Controller가 동작하는 과정을 너무 자세히 설명해주고 있어서 참고자료로 삽입하게 되었다. 많은 사람들이 이 UML을 보기 전 만하더라도 기존의 @MVC가 어떤 방식으로 동작하는지 감잡았다고 생각했다가 UML을 잠깐 훑어보고는 그동안의 이해가 싹 사라지고 눈앞이 껌껌해지는 느낌을 받을 수도 있을 것이다. 필자도 이 UML을 처음 본 순간… 이제 좀 알았다는게 정말 알긴 개뿔이라는 수준 밖에 못된다는 사실을 깨달았다.

여하튼 차츰차츰 알아가는 것이 공부이므로 위의 @Controller의 동작과정은 나중의 확장할 때에 좀 더 자세히 공부하도록 하고 일단은 우리의 과제인 @SessionAttributes에 대해 이해해보도록 하자. 이 어노테이션은 스프링에서 상태유지를 위해 제공되는 어노테이션인데 대충 객체의 위치가 뷰와 컨트롤러의 사이에 존재한다고 생각하면 좋다.

우선 @SessionAttributes는 항상 클래스 상단에 위치하며 해당 어노테이션이 붙은 컨트롤러는 @SessionAttributes("세션명")에서 지정하고 있는 "세션명"을 @RequestMapping으로 설정한 모든 뷰에서 공유하고 있어야 한다는 규칙을 갖고 있다. 예를 들어 위와 같이 @SessionAttributes("command") 라는 어노테이션이 붙은 클래스라면 하위의 종속되있는 모든 뷰가 "command"라는 모델 값을 공유하고 있어야 한다는 것이다. 만약 이 조건을 충족하지 못하면 다음과 같은 에러가 발생하게 된다.

org.springframework.web.HttpSessionRequiredException: Expected session attribute 'command'

RequestMapping(value="/", method=RequestMethod.GET)
public String home(Model model) {
model.addAttribute("command", new Command());
}

// 이런 방식으로 SessionAttributes를 이용하는 것은 옳지 않다.

이 문제를 해결하기 위해 사용할 수 있는 방법은 2가지 인데 첫째는 해당 컨트롤러에서 맨 처음 읽어들일 것으로 예상되는 뷰의 Model 객체를 통해 수동적으로 "command"란 파라미터를 보내주는 것이다. 이 방식은 클라이언트가 해당 클래스로 뷰어에 접근할 때 반드시 첫번째로 해당 뷰를 통해야만 한다는 제약조건을 갖게 되며 그렇지 않을 경우 또다시 위의 에러가 발생할 수 있으므로 결코 추천할 수 없는 방식이다.

@SessionAttributes("command")
@Controllerpublic class Controller {

@ModelAttribute("command")
public Command command() {
return new Command();
}

@RequestMapping(value="/", method=RequsetMethod.POST)
public String home(@ModelAttribute Command command) {
...
}
}

이런 불필요한 에러를 보고 싶지 않다면 @ModelAttributes를 붙인 메서드를 이용할 것을 적극 권장한다. 위의 예제를 보면 @ModelAttribute가 붙은 command()메서드를 볼 수 있는데 이 메서드는 해당 컨트롤러로 접근하려는 모든 요청에 @ModelAttribute가 붙은 메서드의 리턴 값을 설정된 모델명으로 자동 포함해주는 역할을 담당해준다. 물론 이미 동일한 이름의 모델이 생성되었있다면 위의 메서드 값은 포함되지 않으며 오로지 설정한 모델명과 일치하는 객체가 존재하지 않는 경우에만 메서드의 리턴 값을 서버의 응답과 함께 클라이언트에게 전송하는 역할을 담당한다.

말이 조금 어렵긴 한데 단순하게 요약하자면 해당 컨트롤러로 클라이언트가 접근할 때 반드시 @ModelAttribute가 붙은 메서드의 리턴 값을 보장받는 다는 소리다. 지금은 단순하게 return new Command(); 정도로 마무리 지었지만 원한다면 해당 객체에 기본 값을 포함할 수도 있다.

@SessionAttributes의 기본 충족조건을 이해했으므로 이제 사용 용도에 대해 조금 생각해보자. 필자가 생각하는 @SessionAttribute의 사용 용도는 다음과 같다.

1. 스프링에서 제공하는 form 태그라이브러리를 이용하고 싶을 때.
2. 몇 단계에 걸쳐 완성되는 폼을 구성하고 싶을 때
3. 지속적으로 사용자의 입력 값을 유지하고 싶을 때

아마 첫번째 이유가 가장 절실할 것 같다. 필자도 스프링에서 제공하는 폼태그를 자주 활용하는데 이 태그라이브러리를 활용하면 폼 작성이 정말 쉬워지는데다 검증 바인딩 기술은 한번 쓰면 헤어나올 수 없는 마약과도 같아서 쉽게 떨쳐버리기가 힘들다 :(
@SessionAttributes는 해당 어노테이션에 설정한 값과 동일한 이름의 모델객체를 발견하면 이를 캐치하여 세션값으로 자동 변경시켜준다. 그리고 해당 모델객체가 세션값으로 대체되면 앞으로 세션값을 지우기 전까지 해당 이름의 모델명 호출시 세션에 저장된 값을 불러오게 된다.

@Controller
@SessionAttributes("command")
public class Controller {

@ModelAttribute("command")
public Command command() {
return new Command();
}

@RequestMapping(value="/", method=RequestMethod.GET)
public String home() {
return "home";
}

@RequestMapping(value="/", method=RequestMethod.POST)
public String home(Model model, @ModelAttribute Command command) { model.addAttribute("command", command);
return "home";
}
}
위와 같은 소스를 예로 들어 설명한다면 "/"란 경로로 POST 방식을 통해 클라이언트가 파라미터를 보낼 경우 서버는 해당 값을 세션에 저장되어 있는 "command"객체에 저장시켜 해당 세션을 종료하기 전까지 값을 유지해준다. 물론 이 예제만으로 @SessionAttributes는 동작과정을 판단하기가 매우 어렵기 때문에 @SessionAttributes를 사용하기 전에 미리 필요한 학습테스트를 거친 후에 사용할 것을 권장한다.

이제 세션 값이 더이상 필요 없어질 경우 이를 지우는 방법도 알아야 하겠다. 세션값을 제때 지우지 않고 계속 쌓아둔다면 메모리에 무리가 생길 수 있으므로 불필요해질 경우 제거해주는 것도 중요하다. 제거법은 매우 간단한데

@RequestMapping(value="/", method=RequestMethod.POST)
public String home(Model model, @ModelAttribute Command command,SessionStatus session) {
model.addAttribute("command", command);
session.setComplete();
return "home";
}

위와 같이 종료가 필요한 URL매핑 메서드에 SessionStatus란 세션관리 인자를 전달받아 종료시켜주면 된다.


SessionAttributes의 한계

이 어노테이션도 위의 ModelAttribute와 마찬가지고 컬렉션 프레임워크를 지원하지 않는다는 단점이 있다. 만약 이 값이 List형태의 데이터를 지원했다면 정말 최고였겠지만 아쉽게도 그렇지가 못하다. SessionAttributes와 ModelAttribute는 밀접한 관계를 가진 어노테이션이며 어느 한쪽만 List 형태를 지원한다고 해결될 일이 아니므로 이 해결법에 대해서는 좀 더 심층적으로 연구하고 일반화된 해결법이 필요하겠다.

스프링 컨트롤러를 이용하면서 얻게된 습관이 가급적 코드를 짧게 쓰려는 습성이 생겼다는 것이다. 스프링 프레임워크를 활용하면 본래 엄청나게 길어질 코드들도 단 몇줄의 코드로 똑같은 적용이 가능하다보니 코딩을 하면서 괜히 더 짧게 할 수 있는 방법은 없나… 꼼수를 부리게 되곤 한다. 게다가 괜히 작성하는 코드가 좀 길어지다보면 문득 내가 무언가 잘못하고 있다는 압박감을 받기도 한다.

위의 @ModelAttribute가 List 형태를 지원하지 않는 것도 이런 압박감의 일종일지 모른다. 이렇게나 저렇게나 해결할 방법은 분명 존재하지만 뭔가 깔끔하게 떨어지지 않는다는 것에 미련에 베스트 프렉티스가 존재하지는 않나 인터넷을 뒤져보는 것들 말이다.

더욱이 스프링을 이용하면서 이런 어노테이션을 자주 활용하다보니 많은 기술을 함축시켜 사용할 수 있다는 장점 때문에 개발자를 게으르게 하고 손수 기능을 구현하는데 선뜻 나서지 못하는 상황을 만들어 낸다는 것이다. 더욱이 어노테이션 기술은 한가지 엄청나게 치명적인 단점을 가지고 있는데 그것은 바로 스프링의 뚜렷한 장점이었던 확장이 굉장히 힘들어진다는 것이다.

만약에 위와 같은 문제로 @SessionAttributes를 조금 손보고 싶다고 하자. 가히 만만치 않은 작업인데다 어쩌면 @MVC에 해당하는 클래스 대부분을 손봐야 할지도 모른다. 그렇다고 이제와서 어노테이션을 사용 안할 수도 없는 노릇인데다 이미 스프링 @MVC가 대부분의 핵심기술을 어노테이션을 이용해 구현하고 있는 것도 문제다.

물론 스프링 자체에서도 이러한 문제점에 대해 잘 인식하고 있으며 지속적인 해결책을 강구하고 있긴 하다. 뭐 이런 확장이니 뭐니해도 어찌됬든 개발자가 원하는 기술을 구현하기만 하면 장땡이다. 필자가 원하는 기술이 제대로 구현할 수가 없으니 이런 못된 심보의 글도 나오는 것 아니겠는가.


출처 - http://springmvc.egloos.com/535572



Posted by linuxism
,