달력

42024  이전 다음

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  Core Java Technologies Tech Tip에 오신 여러분을 환영합니다
Core Java Technologies
TECHNICAL TIPS
2006년 4월 21일자
  이번 호에서는,

» 애플릿 콘텍스트 스트림으로 작업하기
» Singleton 패턴에 대한 재고찰

에 대해 다룹니다.

이 문서는 Java 2 Platform, Standard Edition Development Kit 5.0 (JDK 5.0)을 기반으로 개발되었습니다. PDF 파일받기   

애플릿 콘텍스트 스트림으로 작업하기
 

요즘 애플릿에 대한 논의가 많지는 않으나 J2SE 1.4에서는 java.applet 패키지의 AppletContext 클래스에 세 가지 메소드가 추가되었다. 많은 사람들이 추가된 사실을 알아채지 못할 수도 있지만, 이 메소드들은 유용한 기능을 제공한다. 즉, 데이터를 스트림에 저장하고 각 스트림을 지정된 키(named key)에 매핑하는 것이다.

정보 저장을 위한 메인 메소드는 setStream()이다.
public void setStream(String name, InputStream stream) 스트림은 저장 시 키값 (Map과 유사한) 구조의 키에 연결되고, 매핑은 애플릿의 코드베이스에 한정된다. 이 말은 하나의 호스트에서 나온 애플릿이 다른 호스트의 스트림에 액세스하지 못한다는 것을 의미한다.

스트림이 저장된 후에는 싱글 스트림 또는 스트림 전체를 이용하여 이를 검색할 수 있다. 싱글 스트림을 얻으려면 getStream() 메소드를 이용하여 이름별로 요청을 한다.
public InputStream getStream(String name) getStreamKeys() 메소드를 이용하여 모든 스트림을 검색하는데, 이 작업을 수행할 때는 Map으로 돌아가지 않는다. 대신, 다음과 같은 String 이름의 Iterator를 얻는다.
public Iterator getStreamKeys() 사용자가 원하는 특정 스트림의 키 이름을 얻은 후에는 getStream() 메소드를 이용하여 해당 스트림을 얻는다. 이 때, 일반적으로 사용되는 패턴은 다음과 같다.
Iterator iter = getAppletContext().getStreamKeys(); if (iter != null) { while (iter.hasNext()) { String name = iter.next(); InputStream stream = getAppletContext().getStream(name); // read stream... } } 이들이 인풋 스트림이라는 점을 유념할 것. 문자로 작업하고자 하는 경우에는 반드시 문자 세트를 이용해야 한다. 예를 들어 String 오브젝트를 저장하려면 바이트를 얻어 AppletContext 내의 ByteArrayInputStream에 저장한다.
String message = ...; ByteArrayInputStream bais = new ByteArrayInputStream(message.getBytes("UTF-8")); getAppletContext().setStream("key-name", bais); 이 오브젝트를 읽으려면 스트림을 얻어 이를 InputStreamReader로 변환할 때 동일 문자 세트를 전달한다. Reader 오브젝트를 얻은 후에는 다음 예제처럼 문자를 읽을 수 있다.
InputStream stream = getAppletContext().getStream("key-name"); InputStreamReader isr = new InputStreamReader(stream, "UTF-8"); BufferedReader reader = new BufferedReader(isr); String line = reader.readLine(); API는 사실상 이것이 전부라 할 수 있다. 'setStream() 메소드를 이용하여 새로운 스트림을 저장하고, getStream()을 이용하여 스트림을 되돌린다. getStreamKeys()를 이용하여 스트림 키 세트를 얻는다. ' 이것이 스트림에 관해 알아 두어야 할 전부인 것처럼 보일지 모르지만, 그렇다면 스트림 컨텐츠를 제거하려면 어떻게 해야 할까? 그 답은 다음과 같다. 특정 키와 연결된 InputStream으로 null을 전달한다. 이렇게 하면 스트림의 컨텐츠가 시스템에서 제거된다.
getAppletContext().setStream("key-name", null); 이번에는 API의 데모를 살펴보기로 하자. 먼저, 프로그램 로더인 HTML 파일을 생성해야 한다. 200x200의 디스플레이 면적을 필요로 하는 StreamsApplet으로 명명된 애플릿의 경우, HTML 파일에는 다음과 같은 애플릿 태그가 포함되어야 한다.
<applet code=StreamsApplet width=200 height=200> </applet> StreamsApplet 애플릿은 키값 쌍을 위해 2개의 텍스트 필드를 제공하는데, 이 때 키는 lookup name이며 값은 저장될 InputStream 컨텐츠이다. 아울러 애플릿은 2개의 버튼을 디스플레이한다. 첫 번째 버튼은 명명된 스트림을 추가하고(또는 기존의 것을 업데이트한다), 두 번째 버튼은 명명된 스트림을 제거한다. 애플릿은 JList에 현재의 이름 세트를 표시한다.

Streams Demo


다음은 사용자 인터페이스 생성에 사용되는 코드이다.
import javax.swing.*; import javax.swing.event.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; public class StreamsApplet extends JApplet { private static final String CHARSET = "UTF-8"; JButton add; JButton remove; JList list; JTextField key; JTextField value; public void init() { JLabel keyLabel = new JLabel("Key"); keyLabel.setDisplayedMnemonic('K'); key = new JTextField(); keyLabel.setLabelFor(key); JLabel valueLabel = new JLabel("Value"); valueLabel.setDisplayedMnemonic('V'); value = new JTextField(); valueLabel.setLabelFor(value); JPanel topPanel = new JPanel(new GridLayout(2,2)); topPanel.add(keyLabel); topPanel.add(key); topPanel.add(valueLabel); topPanel.add(value); add(topPanel, BorderLayout.NORTH); list = new JList(); list.setSelectionMode (ListSelectionModel.SINGLE_SELECTION); JScrollPane pane = new JScrollPane(list); add(pane, BorderLayout.CENTER); add = new JButton("Add/Update"); add.setDisplayedMnemonic('A'); remove = new JButton("Remove"); remove.setDisplayedMnemonic('R'); JPanel bottomPanel = new JPanel(); bottomPanel.add(add); bottomPanel.add(remove); add(bottomPanel, BorderLayout.SOUTH); } } 이제 스트림 추가와 업데이트를 위한 액션을 추가해 보자. Add/Update 버튼 뒤의 ActionListener는 각각의 텍스트 필드에서 이름과 스트림 컨텐츠를 획득한 다음 이를 AppletContext에 저장해야 한다. 스트림을 추가한 후에는 JList에 스트림 목록이 표시되고 ActionListener가 텍스트 필드를 소거해야 한다.
String keyText = key.getText(); String valueText = value.getText(); try { ByteArrayInputStream bais = new ByteArrayInputStream(valueText.getBytes(CHARSET)); getAppletContext().setStream(keyText, bais); } catch (IOException ioe) { JOptionPane.showMessageDialog(StreamsApplet.this, "Unable to save", "Error", JOptionPane.ERROR_MESSAGE); } updateList(); key.setText(""); value.setText(""); updateList() 메소드는 상당히 간단한데, 단순히 이름 목록을 얻어 이를 JList에 넣기만 하면 된다.

DefaultListModel model = new DefaultListModel(); Iterator<String> iter = getAppletContext().getStreamKeys(); if (iter != null) { while (iter.hasNext()) { model.addElement(iter.next()); } } list.setModel(model); Remove 버튼 뒤의 ActionListener는 키 텍스트 필드의 모든 이름에 대해 단순히 스트림을 null로 설정한다. 이 경우에도 제거 후에 이름 목록을 업데이트하고 텍스트 필드를 소거해야 한다.

String keyText = key.getText(); try { getAppletContext().setStream(keyText, null); } catch (IOException ioe) { JOptionPane.showMessageDialog(StreamsApplet.this, "Unable to clear", "Error", JOptionPane.ERROR_MESSAGE); } updateList(); key.setText(""); value.setText(""); 이것으로 작업이 모두 완료된 것은 아니다. 목록에서 이름을 선택하면 현재의 값이 표시되는데, 이 작업은 ListSelectionListener를 통해 이루어진다. 리스너는 JList에서 선택된 값을 획득한 다음 애플릿 컨텍스트에서 스트림을 룩업한다. 이름을 찾을 수 없으면 getStream() 메소드는 null을 리턴한다. 단, JList에는 스트림과 일치하는 이름만 포함되므로 확인 작업이 필요하지는 않다.
String selection = (String)list.getSelectedValue(); try { InputStream stream = getAppletContext().getStream(selection); InputStreamReader isr = new InputStreamReader(stream, CHARSET); BufferedReader reader = new BufferedReader(isr); String line = reader.readLine(); key.setText(selection); value.setText(line); } catch (IOException ioe) { JOptionPane.showMessageDialog(StreamsApplet.this, "Unable to read", "Error", JOptionPane.ERROR_MESSAGE); } 이것을 모두 합치면 애플릿 컨텍스트에 명명된 스트림을 저장할 완벽한 애플릿이 구현된다. 다음은 모든 리스너를 각각의 해당 컴포넌트에 첨부하기 위한 소스의 전체 내용이다.
import javax.swing.*; import javax.swing.event.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; public class StreamsApplet extends JApplet { private static final String CHARSET = "UTF-8"; JButton add; JButton remove; JList list; JTextField key; JTextField value; public void init() { JLabel keyLabel = new JLabel("Key"); keyLabel.setDisplayedMnemonic('K'); key = new JTextField(); keyLabel.setLabelFor(key); JLabel valueLabel = new JLabel("Value"); valueLabel.setDisplayedMnemonic('V'); value = new JTextField(); valueLabel.setLabelFor(value); JPanel topPanel = new JPanel(new GridLayout(2,2)); topPanel.add(keyLabel); topPanel.add(key); topPanel.add(valueLabel); topPanel.add(value); add(topPanel, BorderLayout.NORTH); list = new JList(); list.setSelectionMode( ListSelectionModel.SINGLE_SELECTION); JScrollPane pane = new JScrollPane(list); add(pane, BorderLayout.CENTER); add = new JButton("Add/Update"); add.setMnemonic('A'); remove = new JButton("Remove"); remove.setMnemonic('R'); JPanel bottomPanel = new JPanel(); bottomPanel.add(add); bottomPanel.add(remove); add(bottomPanel, BorderLayout.SOUTH); ActionListener addListener = new ActionListener() { public void actionPerformed(ActionEvent e) { String keyText = key.getText(); String valueText = value.getText(); try { ByteArrayInputStream bais = new ByteArrayInputStream( valueText.getBytes(CHARSET)); getAppletContext().setStream(keyText, bais); } catch (IOException ioe) { JOptionPane.showMessageDialog(StreamsApplet.this, "Unable to save", "Error", JOptionPane.ERROR_MESSAGE); } updateList(); key.setText(""); value.setText(""); } }; add.addActionListener(addListener); ActionListener removeListener = new ActionListener() { public void actionPerformed(ActionEvent e) { String keyText = key.getText(); try { getAppletContext().setStream(keyText, null); } catch (IOException ioe) { JOptionPane.showMessageDialog(StreamsApplet.this, "Unable to clear", "Error", JOptionPane.ERROR_MESSAGE); } updateList(); key.setText(""); value.setText(""); } }; remove.addActionListener(removeListener); ListSelectionListener selectListener = new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { String selection = (String)list.getSelectedValue(); try { InputStream stream = getAppletContext().getStream(selection); InputStreamReader isr = new InputStreamReader(stream, CHARSET); BufferedReader reader = new BufferedReader(isr); String line = reader.readLine(); key.setText(selection); value.setText(line); } catch (IOException ioe) { JOptionPane.showMessageDialog(StreamsApplet.this, "Unable to read", "Error", JOptionPane.ERROR_MESSAGE); } } }; list.addListSelectionListener(selectListener); updateList(); } private void updateList() { DefaultListModel model = new DefaultListModel(); Iterator<String> iter = getAppletContext().getStreamKeys(); if (iter != null) { while (iter.hasNext()) { model.addElement(iter.next()); } } list.setModel(model); } } Java 플러그인 기술 덕분에, 대부분의 데스크톱에서도 애플릿을 브라우저에서 실행할 수 있게 되었다. 최신 자바 소프트웨어를 확인하고 새로운 애플릿을 시험해보고 싶다면 java.com을 방문하기 바란다.

맨위로

Singleton 패턴에 대한 재고찰
 

2006년 2월 8일자 테크 팁 Singleton 패턴에 대해 많은 피드백이 접수되었다. 이 피드백 중에는 Singleton 클래스가 단일 클래스 로더를 통해 공유되지 않으면 Singleton이 아니라는 사실을 강조하는 내용도 포함되어 있다.

사실인즉, 서로 다른 클래스 로더에서 로드된 클래스들은 이름이 같고 동일한 패키지에 속하더라도 동일한 클래스가 아니다.

이를 이해하는 것이 중요한 이유는 무엇일까? 일부 환경에서는 흔히 복수 클래스 로더를 사용하고 있기 때문이다. 예를 들어, Java 2 Platform, Enterprise Edition (J2EE) 애플리케이션 서버는 클래스가 더 이상 필요치 않게 되면 클래스를 언로드할 수 있도록 복수의 클래스 로더를 사용한다(클래스에 대한 클래스 로더를 제거하면 메모리에서 클래스가 제거된다). 이 외에도, J2EE 애플리케이션 서버는 보안상의 이유로 클래스 격리를 위해 복수의 클래스 로더를 사용하기도 한다.

따라서, Singleton 클래스가 단일 클래스 로더(가령 시스템 클래스 로더)를 통해 공유되지 않는다면 그것은 Singleton이라고 할 수 없다.

맨위로

본 메일은 수신을 동의한 회원님에게만 발송됩니다.
본 메일의 수신을 거부하거나 수신주소를 변경하려면 SKDN@Sun.com으로 문의 주시기 바랍니다.

SKDN(Sun Korea Developers Network)에서 J2EE/J2SE 테크팁 등 다양한 아티클들을 참고하세요.

Copyright 2003-2006 Sun Korea, Ltd. All rights reserved.


Posted by tornado
|