Languages/Java2014.09.05 12:23

이펙티브 자바(Effective Java)의 2판이 다시 번역되어 재출간되었습니다. 출판사는 인사이트입니다. 





원래 대웅출판사에서 번역되어 출간되었다가 절판된 것을 이번에 인사이트에서 다시 번역해서 출간했습니다. 원래 번역본을 전혀 참고하지 않은, 전면적인 재 번역입니다. 


참고하시라고, 118페이지의 본문을 발췌해 보았습니다. 


규칙 16은 계승을 위한 설계와 문서를 갖추지 않은 “이질적(foreign)” 클래스의 하위 클래스를 만들 때 생기는 문제점을 설명하고 있다. 그렇다면 계승을 위한 설계와 문서를 갖춘다는 것은 어떤 의미일까?


우선, 메서드를 재정의하면 무슨 일이 생기는지 정확하게 문서로 남겨야 한다. 다시 말해, 재정의 가능 메서드를 내부적으로 어떻게 사용하는지(self-use) 반드시 문서에 남기라는 것이다. public이나 protected로 선언된 모든 메서드와 생성자에 대해, 어떤 재정의 가능 메서드를 어떤 순서로 호출하는지, 그리고 호출 결과가 추후 어떤 영향을 미치는지 문서로 남기라는 것이다. (재정의 가능하다는 것overridable은 public 또는 protected로 선언된 비-final 메서드라는 뜻이다.) 좀 더 일반적으로 이야기하자면, 재정의 가능 메서드가 호출되는 모든 상황을 문서로 남기라는 것이다. 예를 들어, 후면(background) 스레드가 호출할 수도 있고, static 초기화 구문(initializer) 안에서 호출할 수도 있다.


관습적으로, 재정의 가능 메서드를 어떤 식으로 호출하는지는 메서드 주석문 마지막에 명시한다. 주석은 “이 구현은”이라는 문구로 시작한다. 릴리스에 따라서 달라질 수 있다는 뜻으로 하는 말은 아니며, 메서드 내부 동작 원리에 관한 주석이라는 뜻이다. 아래에 java.util.AbstractCollection 명세에서 가져온 예제를 보였다.


170페이지, 규칙 26 관련 본문도 한번 보겠습니다.


컴파일러는 프로그램의 형 안전성을 입증할 수 없을지 모르지만, 프로그래머는 할 수 있다. 무점검 형변환(unchecked cast)을 하기 전에 개발자는 반드시 그런 형변환이 프로그램의 형 안전성을 해치지 않음을 확실히 해야 한다. 위에서 문제가 되고 있는 배열 elements는 private 필드이고 클라이언트에 반환되지 않으며 다른 어떤 메서드에도 전달되지 않는다. push 메서드에 전달되는 원소만이 배열에 저장되며, 그 타입은 전부 E다. 따라서 무점검 형변환을 해도 아무런 문제가 없다.


무점검 형변환이 안전함을 증명했다면, 경고를 억제하되 범위는 최소한으로 줄여야 한다(규칙 24). 위의 예제의 경우, 생성자에 있는 코드라고는 무점검 배열 생성을 하는 코드가 전부이므로 생성자 전체적으로 경고를 억제해도 무방하다. 경고를 억제하는 어노테이션을 추가하고 나면 Stack 클래스는 아무 문제없이 컴파일 될 것이며, 명시적인 형변환이나 ClassCastException이 발생할 걱정없이 사용할 수 있게 된다.


Java 1.8이 공개된 시점이지만, 이 책의 많은 부분은 아직도 유효한 교훈들을 담고 있습니다. 최신 Java와 많이 달라진 부분에는 제한적이지만 주석이 달려 있어서, 1.8에서 해결된 것이 무엇인지 살펴볼 수 있도록 약간의 배려도 하고 있습니다.


저작자 표시 비영리 변경 금지
신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 지나가던개발자

    예로 보여주신, Item17대목요...

    Inheritance를 계승이라 번역하시고, foreign class를 '이질적 클래스'라고 하셨는데..
    inheritance는 이제 '상속'이라는 말로 통용되고,
    foreign class는 이전 아이템(16)의 주제를 보면 '패키지 외부의 클래스' 정도로 이해되는데..

    다른 의도가 있으셨던 건가요?

    2014.09.22 15:19 신고 [ ADDR : EDIT/ DEL : REPLY ]
    • 내가 설계하지 않은 클래스라는 의미를 좀 더 잘 전달할 용어를 찾았던 것인데, 지금 다시 살펴보니 좀 오버한 것 같기도 합니다. 그래서 가급적이면 영어 원문을 병기하였사오나, 혼란이 있으셨다면 사과드립니다.

      2014.09.24 11:54 신고 [ ADDR : EDIT/ DEL ]
  2. 여전히 지나가는 개발자

    2017년임에도 이 책을 가지고 스터리를 하고 있습니다. 몇몇 오타가 보이는데 오정표가 따로 있는지요? 아니면 재판계획이 있으신지요? 워낙에 명저라 관심이 많습니다.

    2017.03.19 17:54 신고 [ ADDR : EDIT/ DEL : REPLY ]

Languages/Java2014.08.04 23:44

Java 1.8부터 ::를 통한 메서드 참조가 가능하기 때문에 (이런 참조는 전부 Functional 계열의 인터페이스를 통해 처리됩니다) 자료형 토큰 (MyClass.class 같은 것들) 사용을 줄일 수 있습니다. 


다음의 enum 자료형 예제를 보시죠. 각 상수에 해당 상수가 나타내는 Mode의 객체를 반환하는 기능이 들어 있는데, 자료형 토큰 없이도 생성자를 넘기고, 해당 생성자에 따라 객체를 만들 수 있습니다.


생성자는 Supplier<T> 인터페이스를 통해 참조할 수 있습니다. 이 인터페이스는 T get() 메서드를 가지고 있습니다. 


interface Encoder {

void init();

void setQuality(int quality);

int getFrameSize();

void encode(Bits bits, float[] data);

}


class NarrowBandEncoder implements Encoder {

@Override

public void init() {

// whatever

}


@Override

public void setQuality(int quality) {

// TODO Auto-generated method stub

}


@Override

public int getFrameSize() {

// TODO Auto-generated method stub

return 0;

}


@Override

public void encode(Bits bits, float[] data) {

// TODO Auto-generated method stub

}

}


class WidebandEncoder implements Encoder {

@Override

public void init() {

// whatever

}


@Override

public void setQuality(int quality) {

// TODO Auto-generated method stub

}


@Override

public int getFrameSize() {

// TODO Auto-generated method stub

return 0;

}


@Override

public void encode(Bits bits, float[] data) {

// TODO Auto-generated method stub

}

}


class UltraWidebandEncoder implements Encoder {

@Override

public void init() {

// whatever

}


@Override

public void setQuality(int quality) {

// TODO Auto-generated method stub

}


@Override

public int getFrameSize() {

// TODO Auto-generated method stub

return 0;

}


@Override

public void encode(Bits bits, float[] data) {

// TODO Auto-generated method stub

}

}


enum EncodingMode {

NARROW_BAND(NarrowBandEncoder::new),

WIDE_BAND(WidebandEncoder::new),

ULTRA_WIDE_BAND(UltraWidebandEncoder::new);

private Supplier<? extends Encoder> encoder;

private EncodingMode(Supplier<? extends Encoder> supplier) {

this.encoder = supplier;

}

public Encoder createEncoder() {

return this.encoder.get();

}

}



저작자 표시 비영리 변경 금지
신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 잘 보고 갑니다. 오늘도 활기찬 하루 되시길요. ^^

    2014.08.04 23:54 신고 [ ADDR : EDIT/ DEL : REPLY ]

Languages/Java2013.12.31 11:56

1. Garbage Collection이 필요하다면


메모리 할당/반환을 처리하는 것이 너무 지겹고 고단하다면, Java를 배워야 할 필요가 있을지 모릅니다. 잘 잘려진 대로, Java는 메모리 할당과 반환에 대한 작업을 Garbage Collector를 통해 알아서 처리해 줍니다. 그 성능이 걱정되신다구요? Java는 만들어진 지 오래된 언어이고, JVM의 성능을 최적화하기 위해 오랫동안 애써 왔습니다. 그 이야기는, Java의 JVM이 제공하는 Garbage Collector의 성능이 이제 믿을만한 수준까지 도달했다는 의미이기도 합니다. (물론 Java에서도 메모리 누수 현상, 즉 Memory Leak은 발생할 수 있으므로 이를 피하기 위해서는 코딩할 때 주의해야 합니다. Effective Java 2nd Edition을 참고하세요.) 


물론 잘 최적화된 C/C++ 바이너리와의 성능을 비교하는 것은 어불성설입니다. 하지만 C++로 작성한다고 무조건 성능이 더 나을거라는 생각은 버리는 것이 좋습니다. 대체적으로, 높은 성능을 내는 것은 무슨 무슨 언어를 쓴다고 공짜로 따라오는 것이 아니라, 프로그래머의 노하우, 패턴, 최적화 등등이 함께 결합되어야 가능하기 때문입니다. 


http://neuroph.sourceforge.net/index.html



2. 어떤 라이브러리를 쓸까 고민하기 싫다면


Java에는 이미 굉장히 큰 규모의 라이브러리가 번들링되어 있습니다. 이 라이브러리들만 잘 사용해도 대다수의 작업은 무리없이 처리할 수 있습니다. 게다가, 관련된 오픈소스 프로젝트들도 많아서, 용도에 맞는 써드 파티 라이브러리를 선택할 때 자유도가 굉장히 높습니다. '무슨 무슨 일을 하는 라이브러리는 파이썬이나 C++ 밖에 없어요. 그러니 우리는 프로젝트를 C++로 진행해야...'와 같은 상황이 생길 여지가 별로 없다는 것이죠. 


굳이 예를 하나 들자면.... 여러분은 GPU 코어를 사용해 시스템 처리 성능을 높이는 방법론인 CUDA를 알고 계실 겁니다. 예전 같으면 이처럼 시스템에 아주 가깝게 다가가 있는 기능을 사용하는 프로그램을 작성할 때 C/C++ 말고는 선택할 수 있는 언어가 거의 없었겠지만, 이제 자바 사용자는 JCUDA(http://www.jcuda.org/)를 사용해서 CUDA 프로그래밍을 할 수 있습니다. 


3. 높은 이식성이 필요하다면


JVM마다 성능이 조금씩 달라지는 일은 있습니다만, 대체로 Java 프로그램의 이식성은 JVM에 의해 보장됩니다. Java 표준을 충실히 따르는 프로그램을 개발했다면, 거의 모든 플랫폼에서 재컴파일 없이도 프로그램을 돌릴 수 있습니다. 아, 물론 Microsoft VM을 사용하는 프로그램을 짰다면 그것은 예외. (묵념) 


4. 수평적 규모 확장성이 요구된다면


Hadoop을 아십니까? 이제 데이터 처리에 있어 수평적 규모확장성(horizontal scalability)이 필요할 때, Hadoop 기반의 플랫폼은 무슨 업계 표준인 것 처럼 받아들여지고 있는 실정이죠. 놀라운 것은, Hadoop이라는 플랫폼이 Java로 작성되어 있다는 것입니다. 그 말은, 시스템에 요구되는 높은 확장성을 달성할 때 중요한 것이 더 이상 언어가 아니라는 점이며, Java가 그러한 성능 요구사항을 달성할 수 있는 수준으로 진화했다는 사실입니다. 어쨌든, Hadoop이 필요하다면 여러분은 Java를 배우는 것이 좋습니다. 물론 다양한 언어 바인딩(binding)들이 나오고 있는 실정이지만 말이죠. 


물론 수평적 규모 확장성을 달성하는 방법이 Hadoop만 있는 것은 아닙니다. Hazelcast(http://www.hazelcast.com/)도 한번 구경해 보세요. Java 기반의 미들웨어가 어디까지 진화해 있는지 느끼실 수 있을 겁니다.


5. 좀 더 편하게 개발하고 싶다면 


Java 개발자들에게 있어서 Eclipse란 어떤 존재인가요? (물론 요즘은 C++/Python 등 다양한 언어의 개발 환경도 Eclipse로 통합되고 있는 실정이긴 합니다만.) 아마 대다수의 Java 개발자는 (저 포함) Eclipse 없는 개발은 상상도 하지 못할지 모르겠군요. 이 놀라운 IDE 덕분에, Java 개발자들의 개발 생산성은 vi로 코딩하고 Make로 빌드하던 초창기에는 상상도 할 수 없을 차원으로 높아졌습니다. 


게다가 여러분은 지금, Android 개발 까지도 진행할 수 있을 만큼 진보된 Eclipse를 사용하고 있습니다. 게다가 Marketplace 기능을 통합한 Eclipse는, 새로운 개발 지원 기능의 통합을 믿을 수 없을 만큼 신속하게 수행할 수 있도록 해주죠. Eclipse는 Java 개발자들의 생산성을 높여줄 뿐 아니라, Java 언어에 대한 진입 장벽 또한 낮추고 있습니다. 


SEE ALSO: 파이썬(Python)을 배워야 할 다섯가지 이유



저작자 표시 비영리 변경 금지
신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 비밀댓글입니다

    2013.12.31 18:22 [ ADDR : EDIT/ DEL : REPLY ]
  2. 어? 왜 비밀글이 되었지??

    2013.12.31 18:23 신고 [ ADDR : EDIT/ DEL : REPLY ]
    • 항상 좋은 글 잘 보고있습니다. 이번 주제는 JAVA의 필요성에 대해 말씀해 주셨는데요 위와같은 사항으로 고민중인 C/C++ 프로그래머라면 개인적인 소견이지만 JAVA보다는 C#도 어떨까 싶습니다.
      1) 자동화된 메모리관리
      2) JAVA와 유사한 라이브러리들과 MSDN
      3) C/C++과 유사한 문법
      4) C/C++의 dll링크가 가능 (C++의 속도와 C#의 편의성 두 이득을 동시에...)
      5) 윈도우 뿐 아니라 리눅스에도 모노를 통해 점점 확장성이 생기고 있음
      6) 스마트폰 게임을 개발중이라면 유니티에서 제공하는 기본 언어(물론 자바도 가능하지만 C#쪽이 더 낫다더군요.. JAVA쪽 유니티는 경험하지 못했습니다.)
      ---
      코멘트 해주신 내용 다시 붙였습니다. 감사합니다.

      2013.12.31 18:38 신고 [ ADDR : EDIT/ DEL ]

Languages/Java2013.04.17 11:51

JAR file can be dynamically loaded from a running Java program. The dynamic loading is relatively easy. 


public class JarLoader {


@SuppressWarnings("unchecked")

private static Class<OFController> loadJar(String path) {

if ( !path.endsWith(".jar") ) {

// this is not a jar file.

return null;

}

String basename = Basename.get(path, "jar");

File file = new File(path);

if ( file.exists() ) {

ClassLoader loader;

try {

loader = URLClassLoader.newInstance(

new URL[] { file.toURI().toURL() },

JarLoader.class.getClassLoader()

);

return (Class<OFController>)
                                    Class.forName( basename, true, loader );

} catch (MalformedURLException e1) {

return null;

} catch (ClassNotFoundException e) {

Logger.stderr("basename " + basename + " is not found");

e.printStackTrace();

return null;

}

}

return null;

}


public static OFController getController(String path, int num_of_instances) {

Class<OFController> ctrl = loadJar(path);

if ( ctrl == null ) {

return null;

}

try { 

Constructor<OFController> constructor = 

ctrl.getConstructor(new Class[]{ int.class });

return constructor.newInstance( num_of_instances );

} catch (NoSuchMethodException e) {

Logger.stderr("Cannot find constructor for " + path);

return null;

} catch (SecurityException e) {

Logger.stderr("You are not authorized to open " + path);

return null;

} catch (InstantiationException e) {

Logger.stderr("Cannot instantiate controller from the given jar "
                                          + path);

return null;

} catch (IllegalAccessException e) {

Logger.stderr("You are not authorized to access constructor for "
                                           + path);

return null;

} catch (IllegalArgumentException e) {

Logger.stderr("You have passed wrong argument to " + path);

return null;

} catch (InvocationTargetException e) {

Logger.stderr("Wrong invocation target for " + path);

return null;

}

}

}


Above code is from a working system. Basically, all you need to look is the URLClassLoader, loadJar method, and codes  that actually call the constructor of the loaded class. 




In implementing this class, We have used a utiliy class called 'Basename'. As we set the name of the Jar file to the complete path of the class including package name (for example, xxx.yyy.SimpleClass), the class 'Basename' is critical in extracting the class name that would be instantiated. 


public class Basename {

public static String get(String path, String extension) {

String[] delimited = path.split("/");

String filename = delimited[ delimited.length - 1 ];

return filename.substring(0, filename.lastIndexOf("." + extension));

}

}


May the force of JAR files be with you!


저작자 표시 비영리 변경 금지
신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Languages/Java2013.04.15 11:16


Java NIO and reactor pattern.Java NIO and reactor pattern. http://www.moparscape.org/smf/index.php?topic=460976.0


The key component of java NIO is java.nio.channels.Selector. With this, you can easily monitor a specific set of events happening from underlying channels. With this, you can easily write codes to create a socket, bind the socket, listen the socket, and accept a new client connection. 


Following is an example code from a working system. 


Selector accept_selector = Selector.open();


ServerSocketChannel tcp_server = ServerSocketChannel.open();

tcp_server.socket().bind(new InetSocketAddress(6633));

tcp_server.configureBlocking(false);

tcp_server.register( accept_selector, SelectionKey.OP_ACCEPT );


//

// start accept loop

// 

int accept_seq = 0;

while ( !quit ) {

int r = accept_selector.select();


if ( r > 0 ) {

// accept set is ready

Set<SelectionKey> keys = accept_selector.selectedKeys();

for ( Iterator<SelectionKey> i = keys.iterator(); i.hasNext(); ) {

SelectionKey key = i.next();

i.remove();


if ( key.isAcceptable() ) {

int seq = ++accept_seq;

SocketChannel sw_channel = tcp_server.accept();

sw_channel.configureBlocking(false);

sw_channel.socket().setTcpNoDelay(true);

sw_channel.socket().setPerformancePreferences(0,2,3);


some_thread_object.addClient( sw_channel );

}

}

}

}



Above code is very simple, so I think no further explanation is needed. One thing to note is that you can also apply this kind of convention to the socket read. After creating sw_channel, you might pass the channel to a thread that monitors a set of connections. Then, the thread does 'select' on the set of connections, check if there are some readable connections. On the readable connections, you can actually perform 'read'. 


void addClient(SocketChannel client) {

synchronized ( guard ) {

try {

// ...

client.register( 

read_selector.wakeup(), 

SelectionKey.OP_READ | SelectionKey.OP_WRITE, 

null /* attachment */

);

} catch (ClosedChannelException e) {

// channel is closed. 

try {

client.close();

} catch (IOException e1) {

// does nothing.

}

}

}

}


Above code is to register a ClientChannel object to a 'read_selector' object, that the thread object has as its private member. One thing to note is, how the 'guard' object is used to prevent deadlock at client.register() and read_selector.select() call. This is an idiom, so please follow it. 


while ( !quit ) {

try {

// guard idiom to prevent deadlock at client.register() call

synchronized (guard) {}


int r = read_selector.select();

if ( r > 0 ) { // there's something to read.


Set<SelectionKey> keys = read_selector.selectedKeys();

for ( Iterator<SelectionKey> i = keys.iterator(); i.hasNext(); ) {

SelectionKey key = i.next();

i.remove();

try { 

if ( !key.isValid() ) {

// do something

key.cancel();

conn.close();

continue;

}

if ( key.isWritable() ) {

// do something

}

if (  key.isReadable() && !handleReadEvent(conn) ) {

// do something

key.cancel();

conn.close();

}

} catch ( CancelledKeyException e ) {

e.printStackTrace();

continue;

}

}

}

} catch (IOException e) {

e.printStackTrace();

// just break this watcher.

return;

}

}


Above codes are from the run loop of  'some_thread_object'. Basically, this loop monitors a set of channels that read and write is possible. Once the selector selected a set of keys (each key is mapped to one SocketChannel object) that are readable or writable, you should check teach channel is actually readable using key.isReadable(), or writable using key.isWritable(). If they return true, that means you are able to read from or write to the channel. 


May the NIO be with you!


저작자 표시 비영리 변경 금지
신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Languages/Java2011.07.12 18:45
SNMP4J: Cannot Handle InetAddress null response

SNMP4J를 사용해서 SNMP Response를 처리하다 보면, 분명히 있는 테이블의 있는 컬럼인데 그 값을 제대로 읽어오지 못하고 timeout이 나버리는 경우가 있습니다. 가령 다음 그림을 보시면...

SNMP4J library does not handle null values for InetAddress columns or scalar values. Therefore, if you do GET or GETNEXT or GETBULK for the fields, your request will be timed out. But most of the MIB browsers do not. They handle the case without error.


MIB 브라우저로 보면 InetAddress 타입의 Table Column 값이 없을 경우 null 응답을 받아서 잘 보여주고 있는 점을 보실 수 있습니다. 그런데 SNMP4J 라이브러리는 InetAddress 타입 컬럼의 값이 null인 경우를 제대로 처리하지 못합니다. 즉, InetAddress 타입 컬럼의 값이 null로 채워진 응답을 받으면, 그냥 무시해버립니다. 그러다보니 SNMP Agent에서는 응답을 보냈어도 클라이언트 쪽에서는 Timeout 나 버리는 경우가 발생하죠.

Above picture shows that MIB browsers correctly handle null-value for InetAddress fields. But, as I already tolds you, SNMP4j does not.



이 문제를 교정하려면 org.snmp4j.smi.IpAddress 클래스를 다음과 같이 수정해야 합니다.

To remedy this problem, you should fix org.snmp4j.smi.IpAddress class as follows.


이렇게 수정하고 실행해보면 이제 SNMP Agent가 InetAddress 타입 column의 값을 null로 보내도 오류 없이 처리하는 것을 보실 수 있습니다. SNMP4J는 소스코드와 함께 Maven 프로젝트 형식으로 배포되기 때문에, 간단히 재컴파일해서 쓰실 수 있습니다.

As SNMP4J is distributed as a Maven project with source codes, you can easily recompile and detour the timeout problems. :-)


신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 좋은 정보 감사합니다..ㅎㅎ 이 부분은 시행착오를 격지 않을듯..

    2013.12.11 17:20 신고 [ ADDR : EDIT/ DEL : REPLY ]
  2. 그러시다니 다행입니다. :-)

    2013.12.11 17:21 신고 [ ADDR : EDIT/ DEL : REPLY ]

Languages/Java2011.03.14 17:04
64비트 환경에 64비트 Java SE JDK를 깔고 Android SDK를 설치하려면 제일 먼저 마주치게 되는 문제가 바로
Java SE Development Kit (JDK) not found 라는 오류 메시지.

이 오류 메시지는 말 그대로 JDK를 찾지 못하는 데서 오는 오류. 이 오류에 대한 해결책은 http://codearetoy.wordpress.com/2010/12/23/jdk-not-found-on-installing-android-sdk/ 에 가 보면 장황하게 나열 되어 있으나 그 아래 부분에도 적혀 있듯이 가장 최근에 보고된 바에 따르면 이 문제를 해결하는 최고로 단순한 해결책은...




Back 버튼을 한 번 눌렀다가 다시 Next 버튼을 누르는 것. -_-



그러면 거짓말처럼 다음으로 넘어갑니다. http://codearetoy.wordpress.com/2010/12/23/jdk-not-found-on-installing-android-sdk/ 에 제시된 Registry-based solution은 그대로 안될 경우에 적용해 보실 것.

참고로 현재 인스톨을 시도하고 있는 플랫폼은 Windows 7 64bit 플랫폼인데, 여기에 SDK를 설치한 후 다른 모든 부분이 정상동작하는 지는 테스트 한 뒤에 다시 포스팅하도록 하겟습니다.

웹을 뒤지다 보면 안드로이드 폰을 Windows 7에 USB로 연결하면 인식이 안된다는 이야기를 접하게 되는데, 같은 문제에 직면하셨다면 http://www.luismajano.com/2010/08/11/install-android-usb-drivers-windows-7-6432-bit/ 이 링크를 참고하셔서 PDANet을 설치하는 것을 고려해 보실 것. 제품에 따라 인식이 바로 될 수도 있고 안 될 수도 있습니다. 안되는 경우에만 도전해보세요.



신고
Posted by 이병준
TAG 64bit, Android, SDK

소중한 의견, 감사합니다. ^^

  1. 우오옷~~ 탁월한 해결책입니다. ㅋㅋ

    2011.03.15 10:29 신고 [ ADDR : EDIT/ DEL : REPLY ]
  2. 오우 정말 탁월합니다~ 저는 jdk 몇번이나 재설치해보고 검색해서 들어왔습니다.. ㅎㅎ

    2011.03.22 19:47 신고 [ ADDR : EDIT/ DEL : REPLY ]
  3. 호잉

    아 이글 보고 해결했네요..
    이거때문에 하루를 삽질했는데.. 이방법 보고 해결되는거 보고 빵터졌습니다..

    2011.03.25 14:17 신고 [ ADDR : EDIT/ DEL : REPLY ]
  4. 낭만고양이님도 IT 쪽인 것을 잊고 있었네요. 저도 스마트폰 APP 관련된 일을 하고 있어서 더 반갑네요...

    2011.04.18 11:30 신고 [ ADDR : EDIT/ DEL : REPLY ]
  5. 요다

    치트키두 아니구..ㅎㅎ 감사합니다.

    2011.06.07 13:39 신고 [ ADDR : EDIT/ DEL : REPLY ]
  6. 궁금이

    허무해요..... ㅠㅠ

    2011.07.04 11:58 신고 [ ADDR : EDIT/ DEL : REPLY ]
  7. 봉팔이

    감사합니다... 한참 웃었네요..

    2011.07.11 12:43 신고 [ ADDR : EDIT/ DEL : REPLY ]
  8. 롸키

    감사합니다^^ 이글 보고 너무 쉽게 해결했네요ㅋㅋㅋ
    큰웃음 주셔서 감사합니다ㅋㅋㅋ

    2011.07.14 12:19 신고 [ ADDR : EDIT/ DEL : REPLY ]
  9. 와우 !

    검색해서 처음 본 글이 이거라 다행이에요 ! ㅋㅋㅋㅋ
    한참 고민할 뻔한걸 금방 해결했습니다 ~ 정말 감사해요 !
    근데 정말 웃겨요 ㅋㅋㅋㅋ

    2011.07.15 10:22 신고 [ ADDR : EDIT/ DEL : REPLY ]
  10. 와하하..

    진짜 이 문제 때문에 더운 여름밤에 땀 삐질삐질 흘리고 있었는데...
    완전 빵 터지며 시원~하게 해결됐습니다..ㅋㅋㅋㅋ
    감사합니다..

    2011.07.23 02:44 신고 [ ADDR : EDIT/ DEL : REPLY ]
  11. 굿`~

    당신은 능력자~~

    2011.08.02 00:10 신고 [ ADDR : EDIT/ DEL : REPLY ]
  12. 헐....

    이걸로 며칠을 삽질을 했는데....jdk, 이클립스 미친듯이 깔고 지우고를 몇번이나 했는데 말이에요....ㅠㅜ

    2011.08.23 22:52 신고 [ ADDR : EDIT/ DEL : REPLY ]
  13. 호호호

    정말 감사해요:) 이것땜에 완전 짜증났었는뎅

    2011.08.27 19:26 신고 [ ADDR : EDIT/ DEL : REPLY ]
  14. 이럴수가

    이럴수가..... JDK를 세번 재 설치 했는데..ㅋㅋ
    너무 확실한 해결책입니다 ㅎㅎ

    2011.09.06 11:11 신고 [ ADDR : EDIT/ DEL : REPLY ]
  15. 아아...

    감사합니다... 한번에 풀리네요 ㅠㅠ JDK만 몇번 재설치했는데..

    2011.09.10 07:42 신고 [ ADDR : EDIT/ DEL : REPLY ]

Languages/Java2010.09.02 16:20
Java의 Reflection을 사용하면 속도는 뭐 그저 그럴지 모르지만 귀찮은 작업이 줄어들 때가 있습니다. 가령 어떤 객체의 모든 필드와 그 값의 쌍을 Map 객체 안에 저장하고 싶다거나 할때 특히 유용하죠. (그 역방향 작업이 필요할 때도 있겠죠.)

그런 작업이 필요할 때 어떤 코드를 작성하면 되는지 보시면...


import java.lang.reflect.Field;

import java.util.HashMap;

import java.util.Map;



class A {

private String p;

private String q;

A() {

p = "foo";

q = "bar";

}

public String getP() {

return p;

}


public String getQ() {

return q;

}


public Map<String, Object> toMap() {

Field[] fields = this.getClass().getDeclaredFields();

Map<String, Object> ret = new HashMap<String, Object>();

for ( int i = 0; i < fields.length; ++i ) {

try {

ret.put(fields[i].getName(), fields[i].get(this));

} catch (IllegalArgumentException e) {

e.printStackTrace();

return null;

} catch (IllegalAccessException e) {

e.printStackTrace();

return null;

}

}

return ret;

}

}



public class Test {


/**

* @param args

*/

public static void main(String[] args) {

A a = new A();

Map<String, Object> r = a.toMap();

for ( String s : r.keySet() ) {

Object o = r.get(s);

System.out.println("key:" + s + ", value:" + o.toString());

}

}


}


뭐 보시다시피 별로 어렵지는 않습니다. 그런데 이런 코딩이 대체 언제 필요하느냐...

XML-RPC 위에서 API를 구현한다거나 할 때 유용하죠. ㅋㅋ XML-RPC의 struct 타입을 Apache XML-RPC 라이브러리에서는 Map Object 형태로 처리하거든요.

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 는 뭐 그저 그럴지 모르지만 귀찮은 작업이 줄어들 때가 있습니다. 가령 어떤 객체의 모든 필드와 그 값의 쌍을 Map 객체 안에 저장하고 싶다거나 할때 특히 유용하죠. (그 역방향 작업이 필요할 때도 있겠죠.)

    2010.09.29 14:19 신고 [ ADDR : EDIT/ DEL : REPLY ]
  2. 푸른컴돌

    Reflection을 사용하면 속도상의 손해를 많이 보나요?

    2011.10.05 13:58 신고 [ ADDR : EDIT/ DEL : REPLY ]

Languages/Java2010.01.29 19:29
요즘 시스템 퍼포먼스 성능 튜닝을 하고 있습니다. 제가 개발에 참여한 시스템이 Java로 구현되어 있어서, Java 성능 튜닝을 하는 중입니다.

그런데 작업을 하다가 아주 재미난 현상을 발견했습니다. 쓰레드 (아주 작습니다) 를 아주 빠른 주기로 생성해서 프로세싱을 하는데 (하다가 성능이 잘 안나올 것 같아서 쓰레드 풀을 만들어 쓰고 있습니다) 쓰레드 객체가 new되어서 실제로 run 되는데 까지 16ms가 (정확하게는 15.5ms쯤 되는 것 같습니다) 걸리는 현상이 1초당 20~40회 정도 목격된 것이죠.

이 현상이 최초로 목격된 것은 Windows 2003 서버였는데, 비스타나 Windows 7에서도 똑같았습니다.

그래서 쓰레드를 만드는 부분의 코드만 아주 작은 테스트 프로그램으로 분리한 다음에 Windows 시스템들에서 각각 돌려봤습니다. 똑같더군요. ㅋㅋ

같은 프로그램을 Mac OS X에서 돌려봤습니다. 거기서는 그런 현상이 관측되지 않았습니다. Mac OS X에서 실행한 그래프는 다음과 같습니다. Eclipse를 썼습니다. 프로그램 초반에는 쓰레드 풀과 객체 풀이 초기화되느라 약간의 삽질을 하는 것이 보입니다만, sample 수를 감안한다면 적당한 성능을 보입니다.


Windows 플랫폼에서는 가로줄이 0과 16ms 위치에 두 줄 그어집니다. 이 문제를 해결하기 위해서 웹 써치를 좀 했는데, 아무래도 실제로 성능이 그렇게 나오기 때문에 그런 그래프가 그려진다기 보다는, System.currentTimeMillis() 의 resolution이 Windows에서 떨어지기 때문인 것 같더군요.

이 문제를 workaround하기 위한 한 가지 방법은, (measurement가 중요한 환경이라면) System.currentTimeMillis()를 쓰는 대신 System.nanoTime()을 쓰는 것입니다. 그러면 Windows XP 상에서 다음과 같이 성능 측정 결과가 개선되는 것을 볼 수 있습니다.


Y 축은 nanosecond를 millisecond로 변환한 결과입니다. 측정된 성능은 확연하게 나아졌습니다. (Mac OS X보다 낫다고 하긴 뭐합니다. 이 위에 Mac OS X에 대해 그린 그래프는 System.nanoTime을 써서 그린 그래프가 아니라서 정확한 비교자료가 될 수 없거든요.)

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. Richpapa

    딴 얘기지만, 1. 퍼포먼스 테스트 툴은 무엇으로 하셨나요? 2. 떡밥보다 밑밥에 관심이 가네요. 그래프는 무엇으로 만들었지요?

    2010.01.29 20:50 신고 [ ADDR : EDIT/ DEL : REPLY ]

Languages/Java2008.11.12 15:57

대략적인 작업 순서는 다음과 같다. (이 글은 "까먹기 전에 적어둔다"는 원칙을 지키기 위해 적은 것임을 미리 밝힌다. 그러므로 설명이 좀 덜 자세할 수도 있다. 필요하면 요청하시길...)

SVN 설치

sudo apt-get install subversion

SVN 저장소 생성

편한 곳으로 가서 svnadmin create repository 라고 치면, 그 디렉터리 아래에 repository라는 이름의 Subversion 저장소가 생성된다. 여기에 프로젝트 소스 코드를 등록하면 공유된다. 이 디렉터리 아래에 가면 conf 디렉터리가 있는데, 이 디렉터리에 있는 파일들을 수정하면 접속 가능한 사용자와 그 사용자의 패스워드를 설정할 수가 있다. 다음의 파일을 참고하자.

1. authz파일

### This file is an example authorization file for svnserve.
### Its format is identical to that of mod_authz_svn authorization
### files.
### As shown below each section defines authorizations for the path and
### (optional) repository specified by the section name.
### The authorizations follow. An authorization line can refer to a
### single user, to a group of users defined in a special [groups]
### section, or to anyone using the '*' wildcard.  Each definition can
### grant read ('r') access, read-write ('rw') access, or no access
### ('').

[groups]
developers=bjlee,ngen

[repository:/]
@developers = rw
* = r

[/]
@developers = rw
* = r

# [/foo/bar]
# harry = rw
# * =

# [repository:/baz/fuz]
# @harry_and_sally = rw
# * = r

2. passwd 파일

### This file is an example password file for svnserve.
### Its format is similar to that of svnserve.conf. As shown in the
### example below it contains one section labelled [users].
### The name and password for each user follow, one account per line.

[users]
bjlee=xxx
ngen=yyy

# harry = harryssecret
# sally = sallyssecret

3. svnserve.conf 파일

### This file controls the configuration of the svnserve daemon, if you
### use it to allow access to this repository.  (If you only allow
### access through http: and/or file: URLs, then this file is
### irrelevant.)

### Visit http://subversion.tigris.org/ for more information.

[general]
### These options control access to the repository for unauthenticated
### and authenticated users.  Valid values are "write", "read",
### and "none".  The sample settings below are the defaults.
anon-access = read
auth-access = write
### The password-db option controls the location of the password
### database file.  Unless you specify a path starting with a /,
### the file's location is relative to the conf directory.
### Uncomment the line below to use the default password file.
password-db = passwd
### The authz-db option controls the location of the authorization
### rules for path-based access control.  Unless you specify a path
### starting with a /, the file's location is relative to the conf
### directory.  If you don't specify an authz-db, no path-based access
### control is done.
### Uncomment the line below to use the default authorization file.
authz-db = authz
### This option specifies the authentication realm of the repository.
### If two repositories have the same authentication realm, they should
### have the same password database, and vice versa.  The default realm
### is repository's uuid.
# realm = My First Repository

SVN Server 실행

svnserve라는 프로그램을 실행하면 SVN 서버를 외부에서 svn://으로 시작하는 URL로 참조하여 사용할 수가 있다. 아까 생성한 repository 디렉터리로 가서, svnserve -d -r . 이라고 해 주자. -d는 daemon으로 띄우라는 소리이고, -r . 은 바로 여이가 repository 디렉터리란 뜻이다.

Maven2 설치

설치법은 인터넷에 다른 좋은 문서들이 많이 있으니 알아서 찾아보시길. ^^;

Maven2 프로젝트 생성

Maven 프로젝트는 다음과 같이 생성한다.

mvn archetype:create -DgroupId=com.mycompany.app -DartifactId=my-app

artifact id는 내 프로젝트의 ID라고 생각하면 된다. 위와 같이 실행하면 다음과 같은 디스크 레이아웃이 만들어지게 된다.

my-app
|-- pom.xml
`-- src
    |-- main
    |   `-- java
    |       `-- com
    |           `-- mycompany
    |               `-- app
    |                   `-- App.java
    `-- test
        `-- java
            `-- com
                `-- mycompany
                    `-- app
                        `-- AppTest.java
이제 여기에 소스 코드를 추가해 나가기만 하면 된다. 프로젝트가 여러 가지 다른 jar 파일에 의존하고 있다면, 그 파일에 대한 의존성도 역시 추가해 나가기만 하면 된다. Maven이 좋은 점은, 내 프로젝트가 의존하는 jar 파일을 어떻게 다운 받아서 어떻게 설치할 것인지를 고민하지 않아도, 대부분 알아서 다 해준다는 점이다. 의존성은 pom.xml 파일에 추가한다.

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.5</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>maven-plugins</groupId>
      <artifactId>maven-findbugs-plugin</artifactId>
      <version>1.4</version>
    </dependency>
    <dependency>
      <groupId>jalopy</groupId>
      <artifactId>jalopy</artifactId>
      <version>1.5rc3</version>
    </dependency>
    <dependency>
      <groupId>com.thoughtworks.xstream</groupId>
      <artifactId>xstream</artifactId>
      <version>1.3</version>
    </dependency>
    <dependency>
      <groupId>org.jmock</groupId>
      <artifactId>jmock-junit4</artifactId>
      <version>2.5.1</version>
    </dependency>
    <dependency>
      <groupId>org.jmock</groupId>
      <artifactId>jmock-legacy</artifactId>
      <version>2.5.1</version>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.15</version>
    </dependency>
    <dependency>
      <groupId>commons-net</groupId>
      <artifactId>commons-net</artifactId>
      <version>2.0</version>
    </dependency>
    <dependency>
      <groupId>xpp3</groupId>
      <artifactId>xpp3_min</artifactId>
      <version>1.1.4c</version>
    </dependency>
  </dependencies>

필자의 프로젝트는 저렇게 많은 3rd-party 프로젝트들에 의존하고 있다. 의존 대상이 되는 라이브러리의 group id와 artifact id를 적어주고, 버전 정보만 정확하게 적어주면 이 파일들을 어떻게 다운받아 어떻게 설치할 것인지는 Maven이 다 알아해 준다. 그런데 group id와 artifact id 등의 정보는 어떻게 알아내느냐고? www.mvnrepository.com 에 가서 프로젝트 이름으로 검색을 해 보면, 여러분의 POM 파일에 끼워넣어야 할 dependency 태그 코드를 버전 별로 다 알려준다.

그런데 한가지 귀찮은 것은, sun에서 배포하는 jar 파일 중에는 자동 설치가 되지 않는 놈이 있다는 사실이다. 그런 것이 발견될 경우, Maven은 친절하게 "그런 파일은 어떻게 어떻게 처리해 주십사"는 안내 메시지까지 보여준다.[각주:1]

코드를 적절히 작성했다면 컴파일 해 보고 싶을 것이다. mvn compile 하면 컴파일 되고, mvn test 하면 jUnit 테스트 코드가 실행된다.

SVN 서버에 프로젝트 코드 올리기

SVN 서버에 프로젝트 코드를 올리는 것은 간단하다.

svn import <ProjectName> svn://<서버주소>/<프로젝트 이름>

위와 같이 하면 된다. 프로젝트 이름을 URL 위에 꼭 붙여주는 것이 좋은데, 아까 만들었던 repository가 범용 리파지토리, 그러니까 여러가지 프로젝트가 동시에 등록될 수 있는 리파지토리이기 때문이다. 이 이름을 주지 않으면 나중에 프로젝트를 Eclipse를 통해 직접 체크아웃한 다음 빌드하려고 할 때 애로사항이 꽃피게 될 수도 있다. 그러니 반드시 주자. 아이디와 패스워드를 묻거든, authz파일과 passwd 파일에 설정한 대로 입력하자. 그러면 성공적으로 등록될 것이다.

SVN 서버에 올린 프로젝트 다운 받아 빌드하기

Eclipse에서 다운받아 빌드하는 것에는 대략 두 가지 방법이 있다. TortoiseSVN같은 툴을 써서 임의의 디렉터리에 프로젝트 코드를 통째로 checkout한 다음 eclipse 프로젝트로 임포트 해서 빌드하는 방법이 있고, Eclipse 안에서 SVN 연동기능을 사용해 직접 checkout 받아 빌드하는 방법이 있다.

첫 번째 방법을 쓰던 두 번째 방법을 쓰던 방법 자체가 너무 뻔해서 따로 설명하진 않겠다. 다만 Eclips에 Maven 2 플러그인을 설치해야 한다는 것에만 주의하자.

SVN 서버에 올린 프로젝트를 Hudson으로 빌드하기

이게 SVN 서버에 올라간 프로젝트를 Hudson을 통해 CI 할 수 있다. Hudson 설치는 너무 간단한 관계로 따로 설명하지 않겠지만 한가지만 언급하자면, 굳이 Hudson을 다른 웹 서버에 붙여 돌리려고 애쓰지 말기 바란다. 그래봐야 별 차이가 없는데다, standalone으로 돌리는 편이 훨씬 간단하다.

java -jar hudson.war 만 하면 hudson 서버가 실행되기 때문.

이렇게 실행시키고, 실행시킨 서버의 URL의 8080 포트번호로 웹 브라우저를 통해 접속해보면, 허드슨 main 페이지를 보게 된다. 여기에 새로운 죠브를 등록한다.

새로운 죠브를 등록할 때 SVN Repository의 URL을 입력하게 되는데, 여기에는 아까 SVN 서버에 프로젝트 코드를 올릴때 썼던 URL을 주면 된다. svn://<서버주소>/<프로젝트 이름> 이렇게 주면 된다는 것. 그러면 Hudson은 해당 이름의 프로젝트로 등록된 코드들을 쭉 받아서 빌드하게 될 것이다.

한가지 유의할 것은, compile 이외의 maven goal들을 실행하고자 하는 경우에는 Build 섹션의 goals and options 부분에 여러 가지 goal 들을 나열해주어야 한다는 것. 필자는 compile test findbugs:findbugs 를 적어주었다. compile은 컴파일하기 위한 것이고, test는 테스트를 수행하기 위한 것이고, findbugs:findbugs는 findbugs 리포트를 생성하기 위한 것이다. findbugs는 Maven 플러그인 중 한가지로, 이 플러그인을 사용하면 코드에 존재하는 잠재적인 버그를 검사할 수 있다. Maven 프로젝트를 정의할 때 pom.xml에 다음과 같이 적어주면 작동하도록 할 수 있다.

<project>
  [...]
  <reporting>
    <plugins>
      <plugin>
        <groupId>maven-plugins</groupId>
        <artifactId>maven-findbugs-plugin</artifactId>
        <version>1.4</version>
        <configuration>
          <findbugsXmlOutput>true</findbugsXmlOutput>
          <findbugsXmlWithMessages>true</findbugsXmlWithMessages>
          <xmlOutput>true</xmlOutput>
        </configuration>
      </plugin>
    </plugins>
  </reporting>
</project>

이렇게 한 다음에 Hudson의 project configuratiojn 부분에서는 Build Setting 섹션에 가서 Publish Findbugs Analysis Result를 세팅한다. 이렇게 하고 나면 빌드가 끝난 후에 그 결과가 성공이었는지 실패였는지의 여부 이외에도 다양한 정보와 버그 추이를 쉽게 확인할 수 있을 것이다. Publish Findbugs Analysis Result 항목이 보이지 않는 경우에는 Hudson에 findbugs 플러그인이 설치되어 있지 않은 것이므로, 설치해 주어야 한다. (정리해보자면, findbugs를 돌리기 위해서는 Maven 프로젝트에 설정을 해 주어야 하고, Hudson에도 플러그인을 설치해 주어야 하고, 최종적으로 Hudson 프로젝트 설정을 수정해야 하는 셈.) 플러그인 설치는 Hudson 관리 메뉴쪽으로 들어가보면 할 수 있다.

[그림이 업로드가 안되어서 (무슨조환지) 참고할만한 그림은 못올렸습니다. 나중에 시간나면 업데이트..]
  1. Windows 환경에서 Eclipse 위에 Maven 2 플러그인을 돌리고 있는 사용자가 Maven 2 프로젝트를 임포트받아 컴파일하는 경우에는 그런 메시지를 받았을 경우 처리하기가 좀 난감한데, 다음과 같이 처리하면 된다. 우선, 자동 설치가 안되는 파일을 sun의 홈페이지에 가서 다운 받는다. (어디서 다운받아야 하는지는 Maven이 알려준다.) 다운 받은 파일을 C:Document and Settings&lt;자기 사용자 이름&gt;.m2repository 디렉터리의 적절한 위치에 옮긴다. 어느 위치로 옮겨야 하느냐는 역시 Maven이 보여주는 안내메시지를 보면 대략적으로 짐작할 수 있다. 옮긴 뒤에는, 해당 디렉터리 안에 있는 pom 파일명에 포함된 버전 번호와 같은 버전 번호를 갖도록 파일 이름을 적절히 바꾸어 주어야 한다. [본문으로]
신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 좋은 조합이네요.

    2008.11.12 16:16 신고 [ ADDR : EDIT/ DEL : REPLY ]

Languages/Java2008.07.16 16:46

SVN에 등록된 Maven 프로젝트를 checkout 받아서 빌드하는 과정은 아주 단순하다.

우선 다음과 같이 프로젝트를 checkout 한다.

svn co http://..../svn/pe/testapp testapp

이전 글과 마찬가지로, 붉은색으로 표시된 부분은 자신의 환경에 맞추어 바꾸면 된다.

co는 checkout의 약자이고, 그 뒤에 나오는것은 project의 URL이며, (이를 위해서 Apache에 SVN 서버가 통합되어 있어야 한다. 그 통합 방법은 이전 글에서 다룬 바 있다.) testapp는 checkout 받은 소스가 들어갈 디렉터리 이름이다.

이렇게 하면 testapp 디렉터리 안에 소스코드들이 쫙 깔린다.

이 디렉터리로 가서

mvn compile

위와 같이 하면 코드가 컴파일된다. 다음은 실행 결과이다.

bjlee@bjlee-ubuntu804:~/work/maven/testapp$ mvn compile
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building testapp
[INFO]    task-segment: [compile]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Nothing to compile - all classes are up to date
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1 second
[INFO] Finished at: Wed Jul 16 16:44:00 KST 2008
[INFO] Final Memory: 2M/6M
[INFO] ------------------------------------------------------------------------

테스트를 실행하는 방법은 다음과 같다.

mvn test

위와 같이 하면 테스트가 실행된다. 다음은 실행 결과이다.

bjlee@bjlee-ubuntu804:~/work/maven/testapp$ mvn test
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building testapp
[INFO]    task-segment: [test]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Nothing to compile - all classes are up to date
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile]
[INFO] Nothing to compile - all classes are up to date
[INFO] [surefire:test]
[INFO] Surefire report directory: /home/bjlee/work/maven/testapp/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running re.etri.moncp.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.042 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 seconds
[INFO] Finished at: Wed Jul 16 16:44:51 KST 2008
[INFO] Final Memory: 4M/8M
[INFO] ------------------------------------------------------------------------


이제 테스트 결과 fail된 테스트가 발견되었을 시, 그에 대한 issue를 Trac에 자동 등록하도록 하는 부분을 구성해야 한다. 그 부분이 가장 어렵다.

<self-question>
지금 생각으로서는 Maven 플러그인을 구현하면 좋을것 같기도 한데, 그 방법이 워낙 까다로와서 엄두가 나질 않는다. 하지만 시도해 볼 만한 가치는 있을 듯... (아니면 google 검색을 잘 하면 나올지도 *쿨럭*)
</self-question>

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Languages/Java2008.07.16 12:44

SVN 서버 준비 (with apache2)

sudo apt-get install libapache2-svn
위의 절차는 /usr/lib/apache2/modules/ 디렉터리에 mod_svn_dav.so(?) 파일을 깔기 위한 것.

그 다음에 /etc/apache2/conf.d/svn 파일 생성 후 다음과 같이 입력. 붉은 색으로 표시된 부분은 각자 입맛에 따라 바꾸면 됨.

<Location /svn/pe>
    DAV svn
    SVNPath /home/bjlee/work/svn/pe
        AuthType Basic
        AuthName "PE svn repository"
        AuthUserFile /home/bjlee/work/trac/pe/trac.htpasswd
        Require valid-user
</Location>

필자는 Trac 패스워드를 재사용하였다. 'developer'같은 멍청한 이름의 사용자를 두지 않는 한 괜찮을 듯. 어차피 Trac 시스템을 사용하는 사람들이 Eclipse를 써서 소스를 고치고 등록할테니.

그런 다음 /home/bjlee/work/svn/pe 디렉터리의 권한을 다음과 같이 수정.

addgroup bjlee www-data
sudo chown -R bjlee:www-data pe
sudo chmod -R g+w pe/
sudo chmod 444 pe/format pe/db/format

여기까지 한 다음 sudo apache2ctl restart 한다.
간혹 /etc/apache2/conf.d/svn 파일에 문제가 있다는 오류 메시지가 뜨기도 할텐데, 대부분 LoadModule를 추가하지 않아서 생기는 문제일 것. http://www.pyrasis.com/main/Subversion-HOWTO#s-4.2 이 문서를 참고해서 /etc/apache2/conf.d/svn 맨 윗줄에 추가해주면 된다. 필자는 안넣어도 되길래 그냥 넣지 않았다.

Subversion, m2clipse 플러그인 설치 (+with Eclipse)

Subversion plugin을 Eclipse에 설치하면 SVN 서버와 연동할 수 있다. 물론 SVN 서버는 상기 절차를 거쳐 apache2 서버를 통해 외부와 연동할 준비가 되어 있어야 한다.

ubversion 설치를 위해서는 update 사이트에 http://subclipse.tigris.org/update_1.4.x 를 추가. 그런 다음 m2clipse plugin 설치를 위한  http://m2eclipse.sonatype.org/update/ 사이트를 역시 update 사이트에 추가.

그런데 설치하려고 하다보면 equinox라는 플러그인에 대한 의존성을 해결할 수 없다고 불평할 때가 있음. 그런 경우에는 관련 플러그인들을 찾아서 설치하거나 Maven Integration for AJDT를 선택하지 않으면 문제를 해결할 수 있음.

Eclise에서 Maven 프로젝트를 생성하고 SVN 저장소에 등록해보자

프로젝트 생성 과정은 다음과 같다.
File->New->Maven Project
첫 번째 창에서 use default workspace location을 선택하고, 
두 번째 창에서 maven-archetype-quickstart를 선택한다. (Nexus Indexer가 선택된 상태라야 가능)
세 번째 창에서 필요한 값들을 입력하고 finish를 누르면 프로젝트가 생성된다.

프로젝트를 SVN 서버에 올리는 과정은 다음과 같다.
프로젝트 생성 후 프로젝트 이름 위에서 마우스 오른쪽 버튼 클릭 -> team -> share 선택하면
첫 번째 창에서 SVN 선택,
두 번째 창에서 리파지토리 위치 선택 (http://xxxx.../svn/pe)
그러고 나면 아이디랑 패스워드를 물어볼텐데, 그때는 적절히 아이디랑 패스워드를 입력해 주면 됨. (그에 대해서는 맨 위의 "SVN 서버 준비"의 관련 부분 참고.

맺으며

여기까지 하면 Eclipse를 통해 Maven 프로젝트를 생성하고, 중앙 저장소에 등록하는 준비가 끝난 셈이다. 이전 글에서 설명했던 MyLyn + Trac 연동만 제대로 되면, "이슈 추적 + 코드 공유"라는 큰 그림을 Eclipse를 통해 그리는 준비작업은 대략 끝난 셈이다.

이제 필요한 것은 SVN 저장소에 저장된 공유 코드들을 매일 빌드하고, 그 결과를 새로운 이슈로 만들어 Trac에 등록하는 작업을 어떻게 처리하느냐다.

그 부분은 다음 글에서 다루도록 하겠다.

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Languages/Java2008.07.15 10:29

Trac 설치

Trac 설치는 간단. sudo apt-get install trac

이렇게 설치하면 0.10 버전이 깔리므로 직접 설치하고 싶은 분들은...

svn co http://svn.edgewall.org/repos/trac/branches/0.11-stable 0.11dev
cd 0.11dev/
python setup.py install

위와 같이 해도 된다. 다만 주의할 것은... 이렇게 인스톨하면 egg cache를 Trac 프로젝트 디렉터리 (이 글의 경우에는 /home/bjlee/work/trac/pe) 아래에 .egg-cache라는 이름으로 만들어주어야 한다는 것. 권한은 777로 주면 된다.

trac을 설치한 다음에는 실행해주어야 할 명령들이 있는데 필자의 경우에는 대략 다음과 같았다. (붉은색으로 표시된 부분은 여러분들이 알아서 정해야 하는 부분.)

trac-admin /home/bjlee/work/trac/pe initenv

기존에 trac을 깔았는데 업그레이드 하신 분들은 다음과 같이.

trac-admin /home/bjlee/work/trac/pe upgrade

제대로 설치가 되었는지 확인해보려면 다음과 같이 해 보면 된다.

tracd --port 8000 /home/bjlee/work/trac/pe

그리고 나서 웹 브라우저를 띄워 접속해서 화면이 정상적으로 나오면 trac 설치는 제대로 된 것이다. tracd는 경량의 httpd 서버로, 엔간한 작업은 이것으로 할 수 있다. 하지만 mod_python등을 설치해서 뭔가 다른 작업을 하려면 한계가 있으므로, apache2를 설치한다. tracd는 ctrl+C로 종료시킬 수 있다.


Apache2 설치

Apache2 설치는 조금 복잡. 다음의 패키지들을 순서대로 설치한다.

sudo apt-get install apache2
sudo apt-get install apache2-threaded-dev
sudo apt-get install libapache2-mod-python
sudo apt-get install python-dev


그런 다음 mod_python 설치. 필자는 다음 명령을 순서대로 실행하여 설치하였다.

wget http://apache.cdnetworks.co.kr/httpd/modpython/mod_python-3.3.1.tgz
tar zxvf mod_python-3.3.1.tgz
cd mod_python-3.3.1
./configure
make
sudo make install

그런 다음 cd /etc/apache2/conf.d 하여 다음과 같이 두 파일(mod_python, trac)을 만들어 준다.

mod_python:
LoadModule python_module /usr/lib/apache2/modules/mod_python.so

trac:
<Location /pe>
    SetHandler mod_python
    PythonInterpreter main_interpreter
    PythonHandler trac.web.modpython_frontend
    PythonOption TracEnv /home/bjlee/work/trac/pe
    SetEnv PYTHON_EGG_CACHE /usr/share/apache2/python_egg_cache
    PythonOption TracLocal "en_US.utf8"
</Location>

<Location /pe/login>
    AuthType Basic
    AuthName "pe"
    AuthUserFile /home/bjlee/work/trac/pe/trac.htpasswd
    Require valid-user
</Location>

위의 설정 파일을 보면 PYTHON_EGG_CACHE라는 환경변수를 정의하는 부분을 볼 수 있다. 나중에 Trac Plugin을 설치할 때 필요한 부분이다. 저 곳 말고 다른 곳으로 지정할 수도 있는데, 편한 곳으로 하면 된다. 단, 해당 디렉터리의 권한은 chmod 777로 설정해 주어야 한다는 점이 귀찮은 점. 단, 0.11대의 최신 버전의 trac을 설치한 경우에는 이 줄은 둘 필요가 없고, 생략하면 된다.

그 아래 줄에 보면 locale을 지정한 부분이 있는데, 그 줄은 날짜 형식 관련해서 trac 시스템이 오동작할 때 (무슨 말인지는 겪어보면 안다 ㅋㅋ) 해결해주기 위한 부분이다. 웹을 찾아보면 한글 로케일인 ko_KR.utf8을 시도해보라는 글이 있는데, 필자의 경우에는 그렇게 해도 해결이 되질 않아서 저렇게 해 두었다. 다행히 지금은 문제없이 동작한다.

또한 위의 설정 파일을 보면 trac.htpasswd라는 파일을 참조하는 부분을 볼 수 있다. trac 사용자와 그 패스워드를 설정하는 부분이다. 다음과 같이 하여 이 파일을 생성한다.

$ htpasswd -c /home/bjlee/work/trac/pe/trac.htpasswd bjlee
New password: <type password>
Re-type new password: <type password again>
Adding password for user bjlee

다른 사용자의 설정을 추가하려면 위의 과정을 계속 반복하면 된다. 두번째 부터는 -c 플래그를 둘 필요는 없다.

이렇게 해서 만들어진 사용자의 권한 설정은 다음과 같이 하면 된다.

trac-admin /home/bjlee/work/trac/pe permission add bjlee TRAC_ADMIN

TRAC_ADMIN 이외의 다른 권한을 주려면 http://<서버주소>/pe/wiki/TracPermissions에 접속하여 가능한 권한을 살펴보면 된다. trac이 깔리면 기본적으로 wiki 문서들이 같이 깔리므로, 브라우저로 문서들을 살펴볼 수 있다. 아. 브라우저로 보려면 아파치 웹 서버를 띄워줘야 한다. 다음과 같이 띄운다.

sudo apache2ctl start

이제 Trac 플러그인 설치를 위한 준비작업이 필요하다. (플러그인 설치는 반드시 해야할 사항은 아님) 이에 관해서는 http://<Trac 서버 주소>/pe/wiki/TracPlugins를 참고하면 된다. 하지만 간단히 요약해보면,

wget http://peak.telecommunity.com/dist/ez_setup.py
sudo python ez_setup.py

위의 과정을 순서대로 수행하면, egg 기반의 Trac 플러그인을 설치하기 위한 준비작업은 완료된다. 어떤 플러그인을 내려받아 설치할 것인지는 알아서 정하도록 하자.

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Languages/Java2008.07.09 22:54
인터넷 찾아보면 여러가지 문서들이 나오지만... '가장 간단한 방법'은 3.3.x 대의 Eclipse 플랫폼을 사용하고, http://www.eclipse.org/mylyn/downloads/ 에 가서 download.eclipse.org/tools/mylyn/update/e3.3  (Eclipse 3.3)과 download.eclipse.org/tools/mylyn/update/extras  (Eclipse 3.3 and 3.4) 옆의 디스크 모양 버튼을 눌러, 관련 zip 파일을 다운받아 설치하는 것이다.

(Eclipse 3.4의 경우에도 크게 다르진 않은데, MyLyn 코어에 해당하는 부분은 그냥 Help->Software Update에서 Avaliable Software탭을 누르면 나오는 맨 윗 필드에 MyLyn을 입력하면 필터링 되어 나오는 플러그인들을 전부 설치하는 식으로 하는 것이 젤 낫고, 위 download.eclipse.org/tools/mylyn/update/extras에 해당하는 부분은 Add Site 버튼을 누른 뒤 http://download.eclipse.org/tools/mylyn/update/extras를 등록한 다음에 설치하는 것이 낫다. extras에 해당하는 부분 만이라면, 직접 받아서 아래와 같이 깔아도 되긴 된다. ^^;;)

다운받은 파일들을 각각 압축을 풀면, 그 안에 features와 plugins 디렉터리가 만들어지는 것을 볼 수 있다.

사용자 삽입 이미지

이 두 디렉터리'만' (site.xml은 제외) 카피하여 eclipse가 깔린 디렉터리 바로 아래에 붙여넣기 한다. 이미 그 디렉터리 안에 features 디렉터리와 plugins 디렉터리가 있으므로, 파일들이 해당 디렉터리 아래로 옮겨질 것이다.

이렇게 한 다음에 eclipse를 다시 띄운다.

그런 다음 Window->Show View->MyLyn->Task Repositories를 선택하고 OK 버튼을 누른다. 그러면 화면 하단에 Task Repository들의 목록이 출력된다.

Trac과 연동하기 위해서는 이 목록의 한 항목으로 Trac Repository를 추가하여야 한다. Task Repository 창에서 마우스 오른쪽 버튼을 누르고 Add Task Repository...를 선택하면 다음과 같은 창이 뜬다.

사용자 삽입 이미지

여기서 Trac을 선택하고 Next > 를 누르면, 다음과 같은 창이 뜨는 것을 볼 수 있다.

사용자 삽입 이미지

여기서 Server에는 http://<호스트 주소>/... 로 표기되는 Trac Server URL을 끝까지 적어주면 된다. Label에는 아무 말이나 Repository 이름을 적어주면 되는데, 관계된 프로젝트 이름을 적어주면 좋다. 그 아래쪽에는 Trac Server에 접속하는 데 필요한 사용자 ID와 패스워드를 적어주면 되고, 나머지는 특별히 건드릴 일이 없을 것이다.

유의할 것은, Additional Settings에 보면 Trac 서버와 연동하는 방식으로 Automatic, XML-RPC plugin, Web의 세가지 중 하나를 선택할 있게 되어 있는데, 보통은 Automatic으로 두면 무방하지만 Trac Server에 XML-RPC 플러그인을 설치하지 않은 경우에는 Web 연동이 기본적으로 선택되리라는 점이다.

Web을 통해 Trac과 연동하게 되면 Trac의 기능을 Eclipse와 매끄럽게 연동하여 쓰는 것이 불가능해지므로 (궁금하면 한번 확인해보기 바란다. 거의 웹 브라우저를 한쪽에 따로 띄워놓고 Eclipse로는 코딩만 하는 것과 별반 차이가 없어진다) 가능하면 Trac 서버에 XML-RPC 플러그인을 설치하기 바란다.

이 플러그인의 설치 방법에 대해서는 http://theeye.pe.kr/tag/XML-RPC 여기에 정리가 잘 되어 있으니 참고.

이 이후에 해야 할 일들에 대해서는 http://dogfeet.tistory.com/7 여기에 대충 나와 있으니 역시 참고. 단 Europa DIscovery Site에 관계된 부분은 건너뛸 것. Subclipse 설치를 마친 다음에는 Team Synchronize view에 관계된 부분부터 읽어나갈 것. 그 다음 부터는 최근의 MyLyn + Trac 상황과 조금씩 차이가 있을 수 있으므로 (버전업 때문) 대충 감안해서 읽어나갈 것.


신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Languages/Java2008.06.26 17:52
Java에서 MD5 hash를 하려면 MessageDigest 클래스를 이용합니다. MessageDigest 클래스에는 update 메소드가 있는데, 이 메소드를 호출할 때 마다 객체 내에 저장된 MD5 digest 값이 계속해서 갱신됩니다. 최종적으로 digest를 호출하면 그 값을 가져올 수 있습니다.

그렇기 때문에 digest를 계산하기 위해 미리 그 입력 값을 바이트 배열에 큼지막하게 잡아놓고 준비할 필요가 없습니다. 그 사실을 보여주는 테스트 코드를 잠시 보시면...

byte[] testArray = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
  String testString = "testsecret";
  byte[] testArray2 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
   't', 'e', 's', 't', 's', 'e', 'c', 'r', 'e', 't'
  };
 
  byte[] dst1 = new byte[16];
  byte[] dst2 = new byte[16];
 
  MessageDigest md = null, md2 =  null;
  try {
   md = MessageDigest.getInstance("MD5");
   md2 = MessageDigest.getInstance("MD5");
  } catch (NoSuchAlgorithmException e) {
   assertTrue(false);
  }
  md.update(testArray, 0, testArray.length);
  md.update(testString.getBytes());
  md2.update(testArray2, 0, testArray2.length);
  try {
   md.digest(dst1, 0, 16);
   md2.digest(dst2, 0, 16);
  } catch (DigestException e) {
   assertTrue(false);
  }
 
  for ( int i = 0; i < 16; ++i ) {
   assertTrue( dst1[i] == dst2[i] );
  }

결국 해시값을 점진적으로 계산할 수 있다는 이야기인데, 이런 특성이 프로그래밍을 좀 더 편하게 해 줍니다. MessageDigest로는 MD5만 계산할 수 있는 것은 아니고, 다른 종류의 digest도 계산할 수 있습니다. (그건 Java 레퍼런스를 참고하시길 ㅋㅋ)

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^