달력

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

eclipse 프로파일러..

JAVA/JSE 2005. 12. 22. 12:10
Posted by tornado
|
Posted by tornado
|
This topic has 8 replies on 1 page.
 
Hi, I get the following error when I load a webpage that is on a tomcat 5.0 server.... It repeats about 3-5 times just from loading a page any ideas?

NotifyUtil::java.net.ConnectException: Connection refused: connect
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:305)
at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:171)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:158)
at java.net.Socket.connect(Socket.java:452)
at java.net.Socket.connect(Socket.java:402)
at sun.net.NetworkClient.doConnect(NetworkClient.java:139)
at sun.net.www.http.HttpClient.openServer(HttpClient.java:402)
at sun.net.www.http.HttpClient.openServer(HttpClient.java:618)
at sun.net.www.http.HttpClient.<init>(HttpClient.java:306)
at sun.net.www.http.HttpClient.<init>(HttpClient.java:267)
at sun.net.www.http.HttpClient.New(HttpClient.java:339)
at sun.net.www.http.HttpClient.New(HttpClient.java:320)
at sun.net.www.http.HttpClient.New(HttpClient.java:315)
at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:521)
at sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:498)
at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:569)
at org.netbeans.modules.web.monitor.server.NotifyUtil$RecordSender.run(NotifyUtil.java:237)
 
Hi, I'm fairly new to java and trying to download a web page page with th help of URL object and I get the following Exception:


java.net.ConnectException: Connection timed out: connect
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(Unknown Source)
at java.net.PlainSocketImpl.connectToAddress(Unknown Source)
at java.net.PlainSocketImpl.connect(Unknown Source)
at java.net.Socket.connect(Unknown Source)
at java.net.Socket.connect(Unknown Source)
at java.net.Socket.<init>(Unknown Source)
at java.net.Socket.<init>(Unknown Source)
at HttpConnection.conectare(HttpConnection.java:92)
at HttpConnection.incarca(HttpConnection.java:58)
at HttpConnection.main(HttpConnection.java:44)

Can anybody help me?
Shoud I know about a proxy or something like that? :)
thankx
 
it means that u can't connect to the server. try to ping to the server and see the result.
 
i don't know if that reply refeared to me or not... but in case it did thank you... I figured out what was wrong with it :)) i have a proxy and i did a small modification to my code i simply added the following lines before I used the URLConnection object:
// Modify system properties
Properties sysProperties = System.getProperties();

// Specify proxy settings
sysProperties.put("proxyHost", "10.9.1.1");
sysProperties.put("proxyPort", "3128");
sysProperties.put("proxySet", "true");


easy isn't it?
 
I had this problem from installing netbeans. I uninstalled it, but there was still some configurations in the <catilina home>/config/web.xml file. Remove the following:

If you want an IDE, perhaps you'd like to try eclipse.

edit your [$TOMCAT_HOME]/conf/web.xml file and rip out the following section from the top - where Netbeans snuck it in, and didn't remove - even if i uninstalled it

=========================================
<filter>
<filter-name>HTTPMonitorFilter</filter-name>
<filter-class>org.netbeans.modules.web.monitor.server.MonitorFilter</filter-class>
<init-param>
<param-name>netbeans.monitor.ide</param-name>
<param-value>127.0.0.1:8082</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>HTTPMonitorFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
=========================================
 
GO POST your QUESTION in your OWN thread!
 
Thanks jwolter, will try.
 
This worked. I did have NetBeans installed but thought the error was originating from Eclipse. Thanks for the help jwolter.
 
Yes, this works for me. I installed NetBeans but never notice that. I confused why get connetion refused even if access into 127.0.0.1. Now everything works fine. Thanks jwolter!
 

'JAVA > JSP_Servlet' 카테고리의 다른 글

http://zk1.sourceforge.net/  (0) 2006.05.10
[펌] 오픈소스 메일 서버/클라이언트 zimbra  (0) 2005.12.23
[펌] 자바툴(솔루션) 정리  (0) 2005.11.16
sitemesh ..  (0) 2005.11.09
[펌] JSF의 새로운 얼굴, MyFaces  (0) 2005.10.27
Posted by tornado
|
[펌] 자바툴(솔루션) 정리 | SE 포스트 삭제 2004/07/20 15:48
http://blog.naver.com/leschan/4210065
출처 : joomanba님의 블로그

Agents

Cougaar

Cougaar has a Java-based architecture for the construction of large-scale distributed agent-based applications. It is the product of a multi-year DARPA research project into large scale agent systems and includes not only the core architecture but also a variety of demonstration, visualization and management components to simplify the development of complex, distributed applications.

Aglets

IBM Aglet Kit

Hawaii Aglet Server

Artificial Intelligence/Expert Systems

JGAP: The Java Genetic Algorithms Package

JESS

JOONE - Java Object Oriented Neural Engine

JSR-94

Weka

Bayes & Naive-Bayes Classifiers

Classifier4J

Naiban

Authentication and Authorization

Acegi Security System for Spring

OSUser2

Tapestry

JAAS

JAAS in Tomcat

Clients

Virtual File System

Email

Java POP3 Email Proxy

MAILMILL

HTTP

HttpClient

URLFetch Java

HTTP Proxies

Muffin

Surfboard Proxy Framework

PAW

Internet Protocols

Jakarta Commons/Net

RSS

News retrieval from hundreds of websites via their RSS syndication files.

fetchrss

fetchrss polls weblogs (rss feeds) and e-mails the updates, one message per updated entry. The effect is comparable to the weblog author e-mailing you personally. fetchrss can run in the background on your desktop or server.

HotSheet

Informa

RSS4j

RSSLibJ

RSSLibJ is a Java class library designed primarily to generate RSS data in various formats, based on a simple object model.

Connected Graphs

GEF

JGraph

JGraphT

Arakne Network Editor

Documentation

Bouvard & Pecuchet

Xref-java2html

QDox

JEX

Encryption

The Bouncy Castle Crypto API

Cryptix

Frameworks

Components and Services

Carbon

HiveMind

Jicarilla

A compact, powerful, extensible and performant component-oriented software framework written in java, and a collection of components developed for this framework. Jicarilla utilizes and supports inversion of control, seperation of concerns, seperation of interface from implementation, contract-based programming, aspect-oriented programming and event-based programming. To get the buzzwords out of the way :D

In readable english, Jicarilla provides a platform for developing a wide range of applications and software components. Think of Jicarilla as the glue between your classes, beans, components and/or services. Jicarilla can be used for building and glueing together a wide variety of applications. Whether you build web services, server applications, servlets, enterprise javabeans, desktop applications, or applets, Jicarilla will make it easier.

Keel

Merlin

The Merlin project deals with the broad area of service and component management. The Merlin system is a container that provides comprehensive support for the management of complex component-based systems. Merlin uses a component meta-model to facilitate the automated assembly and deployment of components.

NanoContainer (see also PicoContainer)

PicoContainer

Spring

Mule

Mule is a light-weight messaging framework. It can be thought of as a highly distributable object broker that can seamlessly handle interactions with other applications using disparate technologies such as Jms, Http, Email, and Xml-Rpc.The Mule framework provides a highly scalable environment in which you can deploy your business components. Mule manages all the interactions between components transparently whether they exist in the same VM or over the internet and regardless of the underlying transport used.

Mule was designed around the Enterprise Service Bus enterprise integration pattern, which stipulates that different components or applications communicate through a common messaging bus, usually implemented using Jms or some other messaging server. Mule goes a lot further by abstracting Jms and any other transport technology away from the business objects used to receive messages from the bus.

Web Application

Cocoon

Struts

Tapestry

WebMacro

Socket Communications

SocketTalk

Graphics

Batik

OpenMap

Piccolo

TinyPTC

ZVTM: Zoomable Visual Transformation Machine

Barcode Generation

Barbecue

JBarcodeBean

Charting

Cewolf

JFreeChart

Chart 2D

JOpenChart

EXIF

Jhead

exifExtractor

Images

Links

PDF

iText

PDFBox

Icons

Links

http://sourceforge.net/projects/icon-collection/

http://developer.java.sun.com/developer/techDocs/hi/repository/

JDBC

Java based

Axion

McKoi

HSQLDB

Redbase

IDEs

DbVisualizer

Squirrel

Reporting

Generation of reports from databases or other data sources.

JFreeReport

DataVision

Mondrian

Intercepting and Modifying Commands

P6Spy

JSP

the display tag library

LDAP

http://salt.sourceforge.net/

Logs

Jestr

Jestr--pronounced like "jester"--is a Java Reflection-based library that provides an extensible framework for defining the way objects are "stringified"--that is, converted into String's for display and logging purposes. It allows the application to define how objects are stringified just by editing a properties file. The style of stringification can be adjusted at runtime, either in a blanket fashion or just for individual classes, class hierarchies, and package hierarchies. Jestr is configurable using a properties file called jestr.properties, which models log4j.properties and should look reasonably familiar to those accustomed to Log4J.

Logging

Just4log

Log4j

Log View/Filtering

Chainsaw

Lumbermill

Misc

Java Object Cache

JOCache is a Java library that implements strict object caching.

It's strict in that each cache enforces two limits in a very strict and predictable way.

Very Large Hashtable

JOTM

Java FEC (Forward Error Correction) Library

SiteMesh

SiteMesh is a web-page layout and decoration framework and web- application integration framework to aid in creating large sites consisting of many pages for which a consistent look/feel, navigation and layout scheme is required.

Xephyrus Flume Pipeline

Flume is a component pipeline engine. It allows you to chain together multiple workers into a pipeline mechanism. The intention of Flume is that each of the workers would provide access to a different type of technology. For example, a pipeline could consist of a Jython script worker followed by a BeanShell script worker followed by an XSLT worker.

The pipeline workers can contain any custom code, however the intention behind Flume is that the workers would implement different scripting languages. This provides a means of separating distinct functionality of the work-flow. For example, if Al understands the business flow and Betty is really good at making it look good, Al could write his piece in Jelly and Betty could do her work in XSL. The pipeline could then execute Al's script, then do Betty's transformation, spewing out some nice document.

JReleaseInfo

The JReleaseInfo AntTask generates a java source file with getter methods for the build date of the program, a build number or the version.

Multimedia

Peer-to-Peer

Blitz JavaSpaces

The purpose of the Blitz project is to further the use of JavaSpaces and JINITM through the provision of essential resources such as:

JXTA

JavaGroups

Instant Messaging

Echomine Muse

Echomine Muse is a library intended to make communication across a wide variety of protocols (including Jabber, Napster, Gnutella, and more) easy and somewhat consistent.

Object Persistence

Hibernate

Jing DAO Framework

Prevayler

SwarmCache

SwarmCache is a simple but effective distributed cache. It uses IP multicast to efficiently communicate with any number of hosts on a LAN. It is specifically designed for use by clustered, database-driven web applications. Such applications typically have many more read operations than write operations, which allows SwarmCache to deliver the greatest performance gains. SwarmCache uses JavaGroups internally to manage the membership and communications of its distributed cache.

Object-XML Binding

Castor

Digester

Jato

JiBX

XML Databases

eXist

Xindice

Program Distribution

IzPack

Packlet

Java Web Start/JNLP

Java Web Start

Netx

Reading and Writing XML

Utilities

txt2xml

NekoHTML

jaxen

Element Construction Set

The Element Construction Set makes it easy for you to build structured XML from a series of calls to objects. It tries to make sure you can't generate something that is not well-formed at the least, even if it is not necessarily well structured. I've used this in the past and found it easy to use but it seems to have fallen from favor. The last time I looked there wasn't much new activity and most people I work with tend to push for using JDOM for this purpose.

VMTools

Library functions to find differences between XML documents and represent those differences as a series of edit operations (again in XML form). Sample code is provided to make it easy to not only find the differences but also to patch an existing file to make it look like a new one.

Parsing

Binary XML

JAXP

JDOM

dom4j

Pipelines

xBeans

Clover ETL

Transmorpher

Remote Procedure Calls

Hessian

SwitchRMI

SOAP

Apache Axis

GLUE

XML-RPC

Apache XML-RPC (formerly the Helma XML-RPC library)

Scheduling

Jcrontab

Scripting

BeanShell

Bean Scripting Framework

Simkin

Searching

Lucene

Jakarta Lucene is a high-performance, full-featured text search engine written entirely in Java. It is a technology suitable for nearly any application that requires full-text search, especially cross-platform.

Relevant links

Server Administration

Servers

Java Service Wrapper

JMX

JMX offers a simple way for you to package server components so you can make them administrable locally or remotely. JBoss and many other server packages now make their components MBeans (managable beans) just to get this functionality.

Consider it seriously for any server software you might be building.

MC4J Management Console

XtremeJ

MX4j

JBossMX

JMX Homepage (Reference JMX Implementation Available Here)

AdventNet Agent Toolkit Java/JMX Edition

Modeler Component

JMX taglib

JMX4Ant

XMOJO

Email

MrPostman

HTTP

Jetty

Jo

J2EE

JBoss

SecurityFilter

Portal

eXo

Jetspeed

jportlet

Pluto

WebDAV

Slide

Swing

Date & Time Selector Component (commercial)

Can be customized to pick Date, Time or Date & Time.

Unavailable dates can be set. User selection is disabled for unavailable dates.

Clock needles can be dragged to change time.

Class Library includes separate Calendar Panel and Clock Panel for other use.

Unlimited runtime distrubution without any royalty

FoxTrot

Glazed Lists

Suppose you have a list of 1,000 objects to browse. With Glazed Lists, you can:

Java Bean Calendar Control

JCascadedPane

JCalendar

JCalendarCombo

JFontChooser

The Kiwi Toolkit

New controls for Swing (including a tree table, date control, etc.)

L2FProd.com Common Components

A set of new Swing components including a font chooser, directory chooser, property sheet panel, and a really cool task pane.

Embedded Editors

Ekit

Ekit is a free open source Java HTML editor applet and application. The Ekit standalone also allows for HTML to be loaded and saved, as well as serialized and saved as an RTF. It is approaching its first production release version.

JXMLPad

JXMLPad is a pure Swing java component/framework for editing XML/XHTML document.

Look and Feel

Want to change the look of your Java application. Here are some ways to do it.

Oyoaha lookandfeel

SkinLF

Metouia look & feel

Windows System Tray Integration

Java System Tray Manager

Windows Tray Icon - Java Implementation

Templates

Canvas

Canvas is a template generator based on the Groovy language. It uses the familiar Velocity Java API to bind variables and allows you to use the full expressivity of Groovy inside your templates.

FreeMarker

vDoclet

Velocity

XDoclet

XSLT

Tools

UI Compiler

Build

Ant

Anthill

CruiseControl

Maven

Profiling

Extensible Java Profiler

JAMon

JAMon offers a set of functions which can be called to record performance data (both duration and number of times executed). The data can be reviewed using an admin JSP.

JRat: the Java Runtime Analysis Toolkit

Version Control

JSVN

Testing

Customer Acceptance Testing

Unit Testing

Swing GUI Testing

Source Code Reformatting

BeautyJ

Jalopy

Jalopy is a source code formatter for the Sun Java programming language. It layouts any valid Java source code according to some widely configurable rules; to meet a certain coding style without putting a formatting burden on individual developers.

Workflow

Bonita

Bonita is a flexible cooperative workflow system, compliant to WfMC specifications, based on the workflow model proposed by the ECOO Team, which incorporates the anticipation of activities as a more flexible mechanism of workflow execution.

Bossa

JaWE

JaWE (Java Workflow Editor) is the first open source graphical Java workflow process editor fully according to WfMC specifications supporting XPDL as its native file format and LDAP connections. It can be used to edit / view every XPDL file which conforms to WfMC specifications.

jBpm

OpenEmcee

The "OpenEmcee Microflow Engine for Java" is an open source framework (Released under MPL 1.1) for developing flexible, manageable, and adaptable applications. It aims to allow application developers to develop well-engineered business-logic intensive application domain software. Drafting from the "Model" and "Controller" layers of the "Model-View-Controller" pattern, it allows developers to separate the business context of their application from their core business functions.

This separation of context from core implementation encourages development of software units which are easily reusable and require no or little modification to business critical code when implementing new functionality. Also, these units of work ("tasks") can be easily reused in future projects.

Open For Business

OpenWFE

Jakarta Commons Workflow Project

OSWorkflow

Shark

The Shark project delivers a workflow server with a difference. Shark is completely based on standards from WfMC and OMG using XPDL as its native workflow definition format. Storage of processes and activities is done using Enhydra DODS.

werkflow

Posted by tornado
|

sitemesh ..

JAVA/JSP_Servlet 2005. 11. 9. 22:51

http://www.opensymphony.com/sitemesh/

 

example 돌려봤는데... 굉장히 심플하게 레이아웃을 만들어주네.

설정도 어렵지 않고... 아주 편리하겠는걸~

주말에 낚시갔다와서 문서좀 봐야긋당....

Posted by tornado
|

JSF의 새로운 얼굴, MyFaces

 

 

지금은 웹 프레임워크의 전성시대라고 해도 과언이 아닐 것이다. MVC(Model -View-Controller) 형태의 모델2가 발표된 이후 수많은 웹 프레임워크가 발표됐고, 이런 웹 프레임워크의 난립(?)은 프로젝트 시작부터 어떤 것을 선택해야 할지에 대한 고민을 안겨주었다. 모든 웹 프레임워크가 나름대로 강조하는 장점들이 있지만, 왠지 ‘2%’ 부족하다는 느낌은 우리로 하여금 섣불리 그것을 선택하기를 주저하게 한다. 하지만 난세에 영웅이 난다고 했던가? JSF(Java Sever Faces)는 자바 표준 스펙(JCP-127)이라는 점에서 태생부터가 다른 웹 프레임워크와는 차별성을 보여주고 있다. 또한 스트럿츠 프로젝트의 리더인 Craig R. McClanahan 이 주도하고 스트럿츠의 커미터들이 대거 참여를 했다는 점에서 스트럿츠 이상의 그 무엇을 기대해도 좋을 것이라는 기대감을 갖게 한다.

JSF의 오픈소스 구현체, MyFaces
MyFaces는 JSF의 오픈소스 구현체이다. 인큐베이터에서 스트럿츠와 같은 위치인 최상위 프로젝트로 격상된 오픈소스 프로젝트인 것을 보면 ASF에서 MyFaces에 거는 기대가 얼마나 큰 것인지를 알 수 있다. MyFaces는 JSF의 구현체임으로 MVC 모델을 기본으로 한 최신의 웹 애플리케이션 프레임워크이며, 무엇보다 UI 단의 풍부한 컴포넌트는 그 어떤 웹 프레임워크와 비교해도 뒤떨어지지 않는다. 이번 기사에서는 JSF에 대한 설명보다 MyFaces에 포함된 쓸만한 컴포넌트들을 중심으로 설명하고자 한다. <표 1>에는 MyFaces의 동작 여부를 테스트한 리스트를 보여주고 있지만, 다른 버전의 WAS 등에서도 JSP 2.0이 지원될 수 있는 환경만 갖추어진다면 적용하기에는 큰 무리가 없다.

MyFaces 설치하기
MyFaces를 동작시켜보기 위해선 다음과 같은 소프트웨어가 필요하다.

◆ JDK 1.5 설치
◆ 톰캣 5.5.7 설치 : 기존의 톰캣을 사용할 경우 JSF 라이브러리가 있다면 삭제한다(jsf-api.jar, jsf-impl.jar).
◆ MyFaces Example 설치
      - <TOMCAT_HOME>/webapps 디렉토리 밑에 war를 설치한다.
      - 톰캣 실행 후  <TOMCAT_HOME>/webapps/myfaces-examples/WEB-INF /lib/commones-el.jar와 jsp-2.0.jar를 삭제한다(톰캣 5.5일 경우만).
      - 톰캣을 재시작한다.



MyFaces의 다양한 컴포넌트
MyFaces는 다양한 컴포넌트들로 무장하고 있다. 기존 웹 기반의 자바 프레임워크가 가지지 않는 이런 여러 가지 UI단 컴포넌트들은 MyFaces가 가는 길을 좀 더 편하게 만들 것이다. 많은 개발자들이 평소 프로젝트를 진행하면서, ‘아 이런 UI 컴포넌트들이 있었으면 좋겠다!’라고 생각했던 것의 대부분을 MyFaces에서 만나볼 수 있다. 또한 MyFaces는 JSF의 구현체로써 기본적인 JSF 관련 태그 선언부의 경우 다음과 같이 JSF를 따르고 있다.

<%@ taglib uri=”http://java.sun.com/jsf/html” prefix=”h”%>
<%@ taglib uri=”http://java.sun.com/jsf/core” prefix=”f”%>

MyFaces의 확장 컴포넌트를 사용하기 위해서는 다음과 같은 선언부를 사용해야 한다.

<%@ taglib uri=”http://myfaces.apache.org/extensions” prefix=”x”%>
<%@ taglib uri=”http://myfaces.apache.org/wap” prefix=”wap”%>

JSCook 메뉴 컴포넌트
JSCook 메뉴 컴포넌트는 <화면 2>에서 보듯이 웹 페이지의 메뉴를 만들어 낼 수 있는 컴포넌트이다. 커스텀 태그(custom tag)로 제공되고 실제 실행은 자바스크립트에 의해 동작된다. JSCook 메뉴는 Heng Yuan이라는 청년에 의해 개발된 자바스크립트로 작성된 메뉴 스크립트이다. 본래의 JSCook 메뉴의 경우 자바스크립트의 변수로 메뉴들을 정의하고, 이렇게 정의된 변수를 통해 자바스크립트가 메뉴를 표현하게 되어 있었다(변수로 메뉴를 정의하는 것은 상당히 번거로운 작업처럼 보였는데, JSCook의 홈페이지에서는 이런 메뉴 변수를 정의하는 Menu Builder를 제공하고 있다. Menu Builder는 브라우저상에서 메뉴의 이름과 아이콘 위치 등을 입력하면 자동으로 자바스크립트 소스를 만들어준다).

 


 

 

MyFaces에서는 커스텀 태그를 통해 정의된 메뉴의 URL과 레이블, 아이콘들을 정의한다. 실제 브라우저의 요청에 의해 커스텀 태그가 동작하게 되면(자바스크립트로써) 기존의 JSCook 메뉴가 원하는 형태의 소스로 변경된다. 즉 JSCook 메뉴의 경우 MyFaces의 커스텀 태그를 사용할 수도 있고, 자바스크립트 형태로도 사용할 수 있음으로 때에 따라 적절한 방법을 사용하면 된다. JSCook 메뉴를 구성하는 항목은 유일한 아이디와 아이템 레이블 그리고 링크 클릭시의 액션으로 구성되며, 모든 JSCook 메뉴는 <x:jscookMenu/> 태그를 부모 태그로 해야 하며, 각 메뉴 항목은 <x:navigationMenuItem/>으로 추가할 수 있다.

<리스트 1> JSCook 메뉴 예제
<%@ taglib uri="http://myfaces.apache.org/extensions" prefix="x"%>
                               :
<x:jscookMenu layout="hbr" theme="ThemeOffice" >
  <x:navigationMenuItem id="nav_1" itemLabel="#{example_messages['nav_Home']}" action="go_home" />
  <x:navigationMenuItem id="nav_2" itemLabel="#{example_messages['nav_Examples']}" >
    <x:navigationMenuItem id="nav_2_1" itemLabel="#{example_messages['nav_Sample_1']}" action="go_sample1" icon="images/myfaces.gif" />
  </x:navigationMenuItem>
</x:jscookMenu>

트리 컴포넌트
트리 컴포넌트의 경우 <화면 3>처럼 보이는 전형적인 트리의 형태를 갖추고 있으며, <리스트 2>의 예제를 보면 벌써 몇몇 개발자의 입에서는 탄성이 나올 법한 코드로 이뤄져 있다. DefultMutableTree Node 등 기존 Swing에서 사용하던 트리 컴포넌트와 거의 동일한 형태로 제공되며 사용하기도 상당히 편리하다. JSP의 Scriptlet을 통해 트리 컴포넌트를 구성하는 각 노드들에 대해 생성하고, Root 트리 노드를 기준으로 insert 메쏘드를 통하여 추가할 수 있다. 또한 구성된 트리 구조를 pageContext의 Attribute에 넣어 페이지가 다시 리로딩되거나 호출됐을 때 기존에 구성된 트리 구조를 재사용하도록 하고 있다. 트리 컴포넌트는 <x:tree/> 태그를 부모로 하여 구성되며, value 항목에 스크립트릿으로 TreeModel을 구성하여 pageContext에 셋팅된 값이 보여진다.

<리스트 2> 트리 예제
<%@ page import="org.apache.myfaces.custom.tree.DefaultMutableTreeNode,
                 org.apache.myfaces.custom.tree.model.DefaultTreeModel"%>
<%@ page session="true" contentType="text/html;charset=EUC-KR"%>
<%@ taglib uri="
http://myfaces.apache.org/extensions" prefix="x"%>

<%
   if (pageContext.getAttribute("treeModel", PageContext.SESSION_SCOPE) == null) {
      DefaultMutableTreeNode root = new DefaultMutableTreeNode("XY");
      DefaultMutableTreeNode a = new DefaultMutableTreeNode("A");
      root.insert(a);
      DefaultMutableTreeNode b = new DefaultMutableTreeNode("B");
      root.insert(b);
      DefaultMutableTreeNode c = new DefaultMutableTreeNode("C");
      root.insert(c);

      DefaultMutableTreeNode node = new DefaultMutableTreeNode("a1");
      a.insert(node);
      node = new DefaultMutableTreeNode("a2 ");
      a.insert(node);
      node = new DefaultMutableTreeNode("b ");
      b.insert(node);

      a = node;
      node = new DefaultMutableTreeNode("x1");
      a.insert(node);
      node = new DefaultMutableTreeNode("x2");
      a.insert(node);

      pageContext.setAttribute("treeModel", new DefaultTreeModel(root), PageContext.SESSION_SCOPE);
   }
%>

<x:tree id="tree" value="#{treeModel}"
        styleClass="tree"
        nodeClass="treenode"
        selectedNodeClass="treenodeSelected"
        expandRoot="true">
</x:tree>

정렬 가능한 테이블 컴포넌트
<x:dataTable/>은 HTML의 Table 형태로 데이터를 표현하는 수단으로 상당히 유용하게 사용할 수 있는 태그이다. 정렬 가능한 테이블은 dataTable이라는 CustomTag에 sortColumn과 sortAscending이라는 속성을 부여해 줌으로써 정렬 가능한 테이블 형태로 보여주는 것이다. <화면 4>에서 Car Type과 Car Color 부분을 클릭하면 순방향 정렬과 역방향 정렬로 토글(toggle)되며 내용이 표시된다. dataTable의 Header 영역에 sort를 할 수 있는 링크를 주기 위해 command SortHeader를 사용하고 있고 화살표의 사용여부를 arrow=”true”로 설정하고 있다. <x:dataTable/>을 부모 태그로 사용하며 각 컬럼마다 <h:column/>을 사용하여 테이블 구조를 나타나게 된다.

<리스트 3> 정렬 가능한 테이블 예제
<x:dataTable styleClass="standardTable"
        headerClass="standardTable_SortHeader"
        footerClass="standardTable_Footer"
        rowClasses="standardTable_Row1,standardTable_Row2"
        var="car"
        value="#{list.cars}"
        sortColumn="#{list.sort}"
        sortAscending="#{list.ascending}"
        preserveDataModel="true"
        preserveSort="true">

    <f:facet name="header">
        <h:outputText value="(header table)"  />
    </f:facet>
    <f:facet name="footer">
        <h:outputText value="(footer table)"  />
    </f:facet>

    <h:column>
        <f:facet name="header">
            <x:commandSortHeader columnName="type" arrow="true">
                <h:outputText value="#{example_messages['sort_cartype']}" />
            </x:commandSortHeader>
        </f:facet>
        <h:outputText value="#{car.type}" />
        <f:facet name="footer">
            <h:outputText id="ftr1" value="(footer col1)"  />
        </f:facet>
    </h:column>

    <h:column>
        <f:facet name="header">
            <x:commandSortHeader columnName="color" arrow="true">
                <h:outputText value="#{example_messages['sort_carcolor']}" />
            </x:commandSortHeader>
        </f:facet>
        <h:outputText value="#{car.color}" />
        <f:facet name="footer">
            <h:outputText id="ftr2" value="(footer col2)"  />
        </f:facet>
    </h:column>

</x:dataTable>

HTML 에디터 컴포넌트
HTML 에디터 컴포넌트의 경우 Kupu라는 이름으로 존재하던 WYSIWYG XHTML 에디터를 MyFaces의 컴포넌트로 포함시킨 것이다. HTML 에디터의 경우 상당히 많은 사이트에서 사용되고 있지만 웹 프레임워크 단에서의 UI 컴포넌트로 제공되는 것은 처음이 아닌가 싶다. HTML 에디터의 경우 글자체, Bold, Underline 등의 폰트를 꾸밀 수 있는 버튼과 색상, 정렬 등 다른 상용 HTML 에디터와 비교해도 뒤떨어지지 않는 기능성을 보여주고 있다. HTML 에디터 커스텀 태그의 경우 필수 속성은 없다.

<리스트 4> HTML 에디터 컴포넌트 예제
<x:htmlEditor value="#{editor.text}"
 style="height: 60ex;"
 formularMode="#{editor.formularMode}"
 allowEditSource="#{editor.allowEditSource}"
 showPropertiesToolBox="#{editor.showPropertiesToolBox}"
 showLinksToolBox="#{editor.showLinksToolBox}"
 showImagesToolBox="#{editor.showImagesToolBox}"
 showTablesToolBox="#{editor.showTablesToolBox}"
 showDebugToolBox="#{editor.showDebugToolBox}"/>

DataScroller 컴포넌트
DataScroller 컴포넌트의 경우 리스트를 표현하는 JSP 단에서 페이지 이동을 할 때 사용하는 컴포넌트이다. 페이지 이동의 경우 몇몇 사이트에서 커스텀 태그를 만들어 사용하는 경우를 보았으나, 거의 대부분은 특별한 표준이 없이 개발되고 있는 것 중에 하나이다. 하지만 리스트의 경우 페이지 이동은 필수적인 컴포넌트인 것을 보면, My Faces에 DataScroller가 있다는 것은 참으로 다행스러운 일이다. 또한 <x:dataScroller/>를 부모 태그로 하여 현재 페이지를 나타낼 pageCountVal 속성이 존재하며, 각 페이지의 navigation의 여부를 paginator 속성의 boolean 타입을 정의하고 보여질 총 페이지 수를 paginatorMaxPages로 정의할 수 있다.

<리스트 5> DataScroller 예제
<x:dataScroller id="scroll_1"
        for="data"
        fastStep="10"
        pageCountVar="pageCount"
        pageIndexVar="pageIndex"
        styleClass="scroller"
        paginator="true"
        paginatorMaxPages="9"
        paginatorTableClass="paginator"
        paginatorActiveColumnStyle="font-weight:bold;"
        >
    <f:facet name="first" >
        <h:graphicImage url="images/arrow-first.gif" border="1" />
    </f:facet>
    <f:facet name="last">
        <h:graphicImage url="images/arrow-last.gif" border="1" />
    </f:facet>
    <f:facet name="previous">
        <h:graphicImage url="images/arrow-previous.gif" border="1" />
    </f:facet>
    <f:facet name="next">
        <h:graphicImage url="images/arrow-next.gif" border="1" />
    </f:facet>
    <f:facet name="fastforward">
        <h:graphicImage url="images/arrow-ff.gif" border="1" />
    </f:facet>
    <f:facet name="fastrewind">
        <h:graphicImage url="images/arrow-fr.gif" border="1" />
    </f:facet>
</x:dataScroller>
<x:dataScroller id="scroll_2"
        for="data"
        rowsCountVar="rowsCount"
        displayedRowsCountVar="displayedRowsCountVar"
        firstRowIndexVar="firstRowIndex"
        lastRowIndexVar="lastRowIndex"
        pageCountVar="pageCount"
        pageIndexVar="pageIndex"
        >
    <h:outputFormat value="#{example_messages['dataScroller_pages']}" styleClass="standard" >
        <f:param value="#{rowsCount}" />
        <f:param value="#{displayedRowsCountVar}" />
        <f:param value="#{firstRowIndex}" />
        <f:param value="#{lastRowIndex}" />
        <f:param value="#{pageIndex}" />
        <f:param value="#{pageCount}" />
    </h:outputFormat>
</x:dataScroller>

여러 Validation 컴포넌트
MyFaces의 Validation의 경우 썬 JSF RI가 세 가지 정도의 기본적인 Validation을 제공하는 것에 추가적으로 이메일, 신용카드 , ISBN, Equal 같은 상당히 유용한 Validation을 제공한다. Valication을 넣는 방법으로는 <f:validator/> 태그에 validatorId로써 사용하고자 하는 Validator 클래스를 지정하는 방법과 <x:validateXXX/>와 같은 MyFaces의 확장 태그 형태로 사용할 수 있다.

<리스트 6> Validation 예제
<h:outputLabel for="form1:email" value="#{example_messages['validate_email']}" />
<h:inputText id="email" value="#{validateForm.email}" required="true">
    <f:validator validatorId="org.apache.myfaces.validator.Email"/>
</h:inputText>
<h:message id="emailError" for="form1:email" styleClass="error" />

<h:outputLabel for="form1:email2" value="#{example_messages['validate_email']}2" />
<h:inputText id="email2" value="#{validateForm.email2}" required="true">
    <x:validateEmail />
</h:inputText>
<h:message id="emailError2" for="form1:email2" styleClass="error" />

<h:outputLabel for="form1:creditCardNumber" value="#{example_messages['validate_credit']}" />
<h:inputText id="creditCardNumber" value="#{validateForm.creditCardNumber}" required="true">
    <x:validateCreditCard />
</h:inputText>
<h:message id="creditCardNumberError" for="form1:creditCardNumber" styleClass="error" />

     <h:outputLabel for="form1:regExprValue" value="#{example_messages['validate_regexp']}" />
<h:inputText id="regExprValue" value="#{validateForm.regExpr}" required="true">
    <x:validateRegExpr pattern='d{5}' />
</h:inputText>
     <h:message id="regExprValueError" for="form1:regExprValue" styleClass="error" />

     <h:outputLabel for="form1:isbn" value="#{example_messages['validate_isbn']}" />
<h:inputText id="isbn" value="#{validateForm.isbn}" required="true">
    <x:validateISBN />
</h:inputText>
     <h:message id="isbnError" for="form1:isbn" styleClass="error" />

<h:outputLabel for="form1:equal" value="#{example_messages['validate_equal']}" />
<h:inputText id="equal" value="#{validateForm.equal}" required="true"/>
<h:message id="equalError" for="form1:equal" styleClass="error" />

<h:outputLabel for="form1:equal2" value="#{example_messages['validate_equal']}2" />
<h:inputText id="equal2" value="#{validateForm.equal2}" required="true">
    <x:validateEqual for="form1:equal" />
</h:inputText>
<h:message id="equal2Error" for="form1:equal2" styleClass="error" />

<h:panelGroup/>
<h:commandButton id="validateButton" value="#{example_messages['button_submit']}" action="#{validateForm.submit}"/>
<h:panelGroup/>

MyFaces의 장밋빛 미래
JSF가 자바 표준 스펙으로써 향후 J2EE의 웹 프레임워크의 중심이 될 것이라는 생각은 그리 어렵지 않게 할 수 있다. 또한 MyFaces가 JSF의 오픈소스 구현체로써 상당히 중요하게 대접(?)받을 것도 쉽게 알 수 있다. MyFaces는 충실한 JSF의 구현체이면서 유용한 컴포넌트들로 무장하고 있다. 다만 조금 걱정이 되는 부분은 UI가 상당히 다이나믹한 국내의 실정에서 얼마나 적용 가능성이 있을지에 대한 우려와 개발자 입장에서 커스텀 태그에 대한 사용이 얼마나 쉽게 받아들일 수 있는가가 문제가 될 수 있을 것으로 보인다. MyFaces는 이클립스와 같은 IDE와의 연계성, J2EE 진영의 주요 벤더에서 JSF를 제대로 지원하는 순간부터 MyFaces의 활용도는 그 어떤 JSF의 구현체보다 활발할 것이다. 아직 제한된 오픈소스만을 사용하는 국내 실정에서 좀 더 많은 오픈소스를 사용하게 되는 계기가 MyFaces를 통해 시작되기를 간절히 바랄뿐이다. [maso]

 

 

출처 : http://www.imaso.co.kr/?doc=bbs/gnuboard.php&bo_table=article&wr_id=6094

 

'JAVA > JSP_Servlet' 카테고리의 다른 글

[펌] 자바툴(솔루션) 정리  (0) 2005.11.16
sitemesh ..  (0) 2005.11.09
[link] XMLC Site....  (0) 2005.10.25
[link] 자바 어플로 만든 쇼핑몰  (0) 2005.10.25
[펌] JSP 2.0에서 Custom Tag활용  (0) 2005.10.25
Posted by tornado
|

'JAVA > JSP_Servlet' 카테고리의 다른 글

sitemesh ..  (0) 2005.11.09
[펌] JSF의 새로운 얼굴, MyFaces  (0) 2005.10.27
[link] 자바 어플로 만든 쇼핑몰  (0) 2005.10.25
[펌] JSP 2.0에서 Custom Tag활용  (0) 2005.10.25
[link] ajaxfaces.com  (0) 2005.10.25
Posted by tornado
|
Posted by tornado
|
오늘 회사메일에 지방에 파견나가신 김모과장님이 보내신 글을 읽어보니...
유익한 정보일 거 같아서 끄적여 본다.
 
============================= 발췌 부분 시작 ======================================
JSP 2.0의 el( ${} )과 jstl(c, fmt, fn)을 쓰다보면 이렇게 편할수 있구나 하고 느꼇는데...
뭔가 부족한게 있어서 fn관련 Soruce를 보게 되었는데..
헉....  fn.tld를 보면 <function>이라는 tag에 class명과 호출 방식으로 선언되어 있고
Classs를 보면 일반 Static function을 바로 불러 쓰고 있었습니다.
다음은 fn.tld의 예 인데.. 너무 단순하지요...
taglib를 만든 다기 보다는 taglib에 기존 Class 함수를 정의만 해두면 되니까요.
 
예제
------------------- [mi-html-util.tld]----------------
 <function>
  <description>
   New line to br tag
  </description>
  <name>nl2br</name>
  <function-class>
   javacross.util.HTMLUtil
  </function-class>
  <function-signature>
   java.lang.String nl2br(java.lang.String)
  </function-signature>
  <example>
   ${mhtml:nl2br(String txt)}
  </example>
 </function>
 ---------------- HTMLUtil.java --------------
package javacross.util;
public class HTMLUtil {
    static final RE reg_NL = new RE("(\015\012)|(\015)|(\012)");
 
    public static String nl2br(String txt){
       if (txt == null) {
           return null;
       } else {
          return reg_NL.subst(txt,"<br />" );
      }
  }
 
public static String null2nbsp(String txt){
  if (txt == null) {
   return "&nbsp;";
  } else {
   return txt;
  }
 }
}
------------------- test.jsp ----------------------
<%@ page language="java" contentType="text/html; charset=MS949" %>
<%@ taglib prefix="mhtml" uri="/WEB-INF/tld/mi-html-util.tld" %>
<table>
 <tr>
  <th>테스트 해보기</th>
 </tr>
 <tr>
  <td>${mhtml:nl2br(course.first_remark)}</td>
 </tr> 
</table>
============================= 발췌 부분 끝 ========================================
 
김과장님... 항상 유익한 정보 감사드립니다. ^^
Posted by tornado
|
Posted by tornado
|

http://www.cloudgarden.com

 

한번도 안써본것!

 

뭐만 새로 보면 신기해서 해봐야 하는 이 성질 ㅡㅡ;

 

Posted by tornado
|

[링크] jndc

JAVA/JSE 2005. 10. 24. 14:56

https://jdnc.dev.java.net

 

잘 사용하면 쓸데가 많을듯.... 

DataSet 같은 거는 jsf 용으로 좀 개량좀 해주징~ 무지 편리한데..

 

Posted by tornado
|
Core Java Technologies Technical Tips
 Core Java Technologies Tech Tip에 오신 여러분을 환영합니다
Core Java Technologies
TECHNICAL TIPS
2005년 10월 18일자
 이번 호에서는,

» CookieHandler를 이용한 쿠키 관리
» 테크팁 퀴즈 정답

을 다루게 됩니다.



CookieHandler를 이용한 쿠키 관리
 

자바 플랫폼의 경우, URL을 통한 오브젝트 액세스는 일련의 프로토콜 핸들러에 의해 관리된다. URL의 첫 부분은 사용되는 프로토콜을 알려주는데, 예를 들어 URL이 file:로 시작되면 로컬 파일 시스템 상에서 리소스를 액세스할 수 있다. 또, URL이 http:로 시작되면 인터넷을 통해 리소스 액세스가 이루어진다. 한편, J2SE 5.0은 시스템 내에 반드시 존재해야 하는 프로토콜 핸들러(http, https, file, ftp, jar 등)를 정의한다.

J2SE 5.0은 http 프로토콜 핸들러 구현의 일부로 CookieHandler를 추가하는데, 이 클래스는 쿠키를 통해 시스템 내에서 상태(state)가 어떻게 관리될 수 있는지를 보여준다. 쿠키는 브라우저의 캐시에 저장된 데이터의 단편이며, 한번 방문한 웹 사이트를 다시 방문할 경우 쿠키 데이터를 이용하여 재방문자임을 식별한다. 쿠키는 가령 온라인 쇼핑 카트 같은 상태 정보를 기억할 수 있게 해준다. 쿠키에는 브라우저를 종료할 때까지 단일 웹 세션 동안 데이터를 보유하는 단기 쿠키와 1주 또는 1년 동안 데이터를 보유하는 장기 쿠키가 있다.

J2SE 5.0에서 기본값으로 설치되는 핸들러는 없으나, 핸들러를 등록하여 애플리케이션이 쿠키를 기억했다가 http 접속 시에 이를 반송하도록 할 수는 있다.

CookieHandler 클래스는 두 쌍의 관련 메소드를 가지는 추상 클래스이다. 첫 번째 쌍의 메소드는 현재 설치된 핸들러를 찾아내고 각자의 핸들러를 설치할 수 있게 한다.

  • getDefault()
  • setDefault(CookieHandler)

보안 매니저가 설치된 애플리케이션의 경우, 핸들러를 얻고 이를 설정하려면 특별 허가를 받아야 한다. 현재의 핸들러를 제거하려면 핸들러로 null을 입력한다. 또한 앞서 얘기했듯이 기본값으로 설정되어 있는 핸들러는 없다.

두 번째 쌍의 메소드는 각자가 관리하는 쿠키 캐시로부터 쿠키를 얻고 이를 설정할 수 있게 한다.

  • get(URI uri, Map<String, List<String>> requestHeaders)
  • put(URI uri, Map<String, List<String>> responseHeaders)

get() 메소드는 캐시에서 저장된 쿠기를 검색하여 requestHeaders를 추가하고, put() 메소드는 응답 헤더에서 쿠키를 찾아내어 캐시에 저장한다.

여기서 보듯이 핸들러를 작성하는 일은 실제로는 간단하다. 그러나 캐시를 정의하는 데는 약간의 추가 작업이 더 필요하다. 일례로, 커스텀 CookieHandler, 쿠키 캐시, 테스트 프로그램을 사용해 보기로 하자. 테스트 프로그램은 아래와 같은 형태를 띠고 있다.

   import java.io.*;   import java.net.*;   import java.util.*;   public class Fetch {     public static void main(String args[]) throws Exception {       if (args.length == 0) {         System.err.println("URL missing");         System.exit(-1);       }       String urlString = args[0];       CookieHandler.setDefault(new ListCookieHandler());       URL url = new URL(urlString);       URLConnection connection = url.openConnection();       Object obj = connection.getContent();       url = new URL(urlString);       connection = url.openConnection();       obj = connection.getContent();     }   }

먼저 이 프로그램은 간략하게 정의될 ListCookieHandler를 작성하고 설치한다. 그런 다음 URL(명령어 라인에서 입력)의 접속을 열어 내용을 읽는다. 이어서 프로그램은 또 다른 URL의 접속을 열고 동일한 내용을 읽는다. 첫 번째 내용을 읽을 때 응답에는 저장될 쿠키가, 두 번째 요청에는 앞서 저장된 쿠키가 포함된다.

이제 이것을 관리하는 방법에 대해 알아보기로 하자. 처음에는 URLConnection 클래스를 이용한다. 웹 상의 리소스는 URL을 통해 액세스할 수 있으며, URL 작성 후에는 URLConnection 클래스의 도움을 받아 사이트와의 통신을 위한 인풋 또는 아웃풋 스트림을 얻을 수 있다.

   String urlString = ...;   URL url = new URL(urlString);   URLConnection connection = url.openConnection();   InputStream is = connection.getInputStream();   // .. read content from stream

접속으로부터 이용 가능한 정보에는 일련의 헤더들이 포함될 수 있는데, 이는 사용중인 프로토콜에 의해 결정된다. 헤더를 찾으려면 URLConnection 클래스를 사용하면 된다. 한편, 클래스는 헤더 정보 검색을 위한 다양한 메소드를 가지는데, 여기에는 다음 사항들이 포함된다.

  • getHeaderFields() - 가용한 필드의 Map을 얻는다.
  • getHeaderField(String name) - 이름 별로 헤더 필드를 얻는다.
  • getHeaderFieldDate(String name, long default) - 날짜로 된 헤더 필드를 얻는다.
  • getHeaderFieldInt(String name, int default) - 숫자로 된 헤더 필드를 얻는다.
  • getHeaderFieldKey(int n) or getHeaderField(int n) - 위치 별로 헤더 필드를 얻는다.

일례로, 다음 프로그램은 주어진 URL의 모든 헤더를 열거한다

   import java.net.*;   import java.util.*;   public class ListHeaders {     public static void main(String args[]) throws Exception {       if (args.length == 0) {         System.err.println("URL missing");       }       String urlString = args[0];       URL url = new URL(urlString);       URLConnection connection = url.openConnection();       Map<String,List<String>> headerFields =          connection.getHeaderFields();       Set<String> set = headerFields.keySet();       Iterator<String> itor = set.iterator();       while (itor.hasNext()) {         String key = itor.next();         System.out.println("Key: " + key + " / " +            headerFields.get(key));       }     }   }

ListHeaders 프로그램은 가령 http://java.sun.com 같은 URL을 아규먼트로 취하고 사이트로부터 수신한 모든 헤더를 표시한다. 각 헤더는 아래의 형태로 표시된다.

   Key: <key> / [<value>]

따라서 다음을 입력하면,

  >> java ListHeaders http://java.sun.com

다음과 유사한 내용이 표시되어야 한다.

   Key: Set-Cookie / [SUN_ID=192.168.0.1:269421125489956;    EXPIRES=Wednesday, 31- Dec-2025 23:59:59 GMT;    DOMAIN=.sun.com; PATH=/]   Key: Set-cookie /    [JSESSIONID=688047FA45065E07D8792CF650B8F0EA;Path=/]   Key: null / [HTTP/1.1 200 OK]   Key: Transfer-encoding / [chunked]   Key: Date / [Wed, 31 Aug 2005 12:05:56 GMT]   Key: Server / [Sun-ONE-Web-Server/6.1]   Key: Content-type / [text/html;charset=ISO-8859-1]   

(위에 표시된 결과에서 긴 행은 수동으로 줄바꿈한 것임)

이는 해당 URL에 대한 헤더들만을 표시하며, 그곳에 위치한 HTML 페이지는 표시하지 않는다. 표시되는 정보에는 사이트에서 사용하는 웹 서버와 로컬 시스템의 날짜 및 시간이 포함되는 사실에 유의할 것. 아울러 2개의 ‘Set-Cookie’ 행에도 유의해야 한다. 이들은 쿠키와 관련된 헤더들이며, 쿠키는 헤더로부터 저장된 뒤 다음의 요청과 함께 전송될 수 있다.

이제 CookieHandler를 작성해 보자. 이를 위해서는 두 추상 메소드 CookieHandler: get() 과ㅓ put()을 구현해야 한다.

  •   public void put(    URI uri,    Map<String, List<String>> responseHeaders)      throws IOException
  •   public Map<String, List<String>> get(    URI uri,    Map<String, List<String>> requestHeaders)      throws IOException

우선 put() 메소드로 시작한다. 이 경우 응답 헤더에 포함된 모든 쿠키가 캐시에 저장된다.put()을 구현하기 위해서는 먼저 ‘Set-Cookie’ 헤더의 List를 얻어야한다. 이는 Set-cookieSet-Cookie2 같은 다른 해당 헤더로 확장될 수 있다.

   List<String> setCookieList =     responseHeaders.get("Set-Cookie");

쿠키의 리스트를 확보한 후 각 쿠키를 반복(loop)하고 저장한다. 쿠키가 이미 존재할 경우에는 기존의 것을 교체하도록 한다.

    if (setCookieList != null) {      for (String item : setCookieList) {        Cookie cookie = new Cookie(uri, item);        // Remove cookie if it already exists in cache        // New one will replace it        for (Cookie existingCookie : cache) {          ...        }        System.out.println("Adding to cache: " + cookie);        cache.add(cookie);      }    }

여기서 ‘캐시’는 데이터베이스에서 Collections Framework에서 List에 이르기까지 어떤 것이든 될 수 있다. Cookie 클래스는 나중에 정의되는데, 이는 사전 정의되는 클래스에 속하지 않는다.

본질적으로, 그것이 put() 메소드에 대해 주어진 전부이며, 응답 헤더 내의 각 쿠키에 대해 메소드는 쿠키를 캐시에 저장한다.

get() 메소드는 정반대로 작동한다. URI에 해당되는 캐시 내의 각 쿠키에 대해, get() 메소드는 이를 요청 헤더에 추가한다. 복수의 쿠키에 대해서는 콤마로 구분된(comma-delimited) 리스트를 작성한다. get() 메소드는 맵을 반환하며, 따라서 메소드는 기존의 헤더 세트로 Map 아규먼트를 취하게 된다. 그 아규먼트에 캐시 내의 해당 쿠키를 추가해야 하지만 아규먼트는 불변의 맵이며, 또 다른 불변의 맵을 반환해야만 한다. 따라서 기존의 맵을 유효한 카피에 복사한 다음 추가를 마친 후 불변의 맵을 반환해야 한다.

get() 메소드를 구현하기 위해서는 먼저 캐시를 살펴보고 일치하는 쿠키를 얻은 다음 만료된 쿠키를 모두 제거하도록 한다.

    // Retrieve all the cookies for matching URI    // Put in comma-separated list    StringBuilder cookies = new StringBuilder();    for (Cookie cookie : cache) {      // Remove cookies that have expired      if (cookie.hasExpired()) {        cache.remove(cookie);      } else if (cookie.matches(uri)) {        if (cookies.length() > 0) {          cookies.append(", ");        }        cookies.append(cookie.toString());      }    }

이 경우에도 Cookie 클래스는 간략하게 정의되는데, 여기에는 hasExpired()matches() 등 2개의 요청된 메소드가 표시되어 있다. hasExpired() 메소드는 특정 쿠키의 만료 여부를 보고하고, matches() 메소드는 쿠키가 메소드에 패스된 URI에 적합한지 여부를 보고한다.

get() 메소드의 다음 부분은 작성된 StringBuilder 오브젝트를 취하고 그 스트링필드 버전을 수정 불가능한 Map에 put한다(이 경우에는 해당 키 ‘Cookie’를 이용).

    // Map to return    Map<String, List<String>> cookieMap =      new HashMap<String, List<String>>(requestHeaders);    // Convert StringBuilder to List, store in map    if (cookies.length() > 0) {      List<String> list =        Collections.singletonList(cookies.toString());      cookieMap.put("Cookie", list);    }    return Collections.unmodifiableMap(cookieMap);

다음은 런타임의 정보 표시를 위해 println이 일부 추가되어 완성된 CookieHandler 정의이다.

   import java.io.*;   import java.net.*;   import java.util.*;   public class ListCookieHandler extends CookieHandler {     // "Long" term storage for cookies, not serialized so only     // for current JVM instance     private List<Cookie> cache = new LinkedList<Cookie>();     /**      * Saves all applicable cookies present in the response       * headers into cache.      * @param uri URI source of cookies      * @param responseHeaders Immutable map from field names to       * lists of field      *   values representing the response header fields returned      */     public void put(         URI uri,         Map<String, List<String>> responseHeaders)           throws IOException {       System.out.println("Cache: " + cache);       List<String> setCookieList =          responseHeaders.get("Set-Cookie");       if (setCookieList != null) {         for (String item : setCookieList) {           Cookie cookie = new Cookie(uri, item);           // Remove cookie if it already exists           // New one will replace           for (Cookie existingCookie : cache) {             if((cookie.getURI().equals(               existingCookie.getURI())) &&                (cookie.getName().equals(                  existingCookie.getName()))) {              cache.remove(existingCookie);              break;            }          }          System.out.println("Adding to cache: " + cookie);          cache.add(cookie);        }      }    }    /**     * Gets all the applicable cookies from a cookie cache for      * the specified uri in the request header.     *     * @param uri URI to send cookies to in a request     * @param requestHeaders Map from request header field names      * to lists of field values representing the current request      * headers     * @return Immutable map, with field name "Cookie" to a list      * of cookies     */    public Map<String, List<String>> get(        URI uri,        Map<String, List<String>> requestHeaders)          throws IOException {      // Retrieve all the cookies for matching URI      // Put in comma-separated list      StringBuilder cookies = new StringBuilder();      for (Cookie cookie : cache) {        // Remove cookies that have expired        if (cookie.hasExpired()) {          cache.remove(cookie);        } else if (cookie.matches(uri)) {          if (cookies.length() > 0) {            cookies.append(", ");          }          cookies.append(cookie.toString());        }      }      // Map to return      Map<String, List<String>> cookieMap =        new HashMap<String, List<String>>(requestHeaders);      // Convert StringBuilder to List, store in map      if (cookies.length() > 0) {        List<String> list =          Collections.singletonList(cookies.toString());        cookieMap.put("Cookie", list);      }        System.out.println("Cookies: " + cookieMap);    return Collections.unmodifiableMap(cookieMap);    }  }

퍼즐의 마지막 조각은 Cookie 클래스 그 자체이며, 대부분의 정보는 생성자(constructor) 내에 존재한다. 생성자 내의 정보 조각(비트)들을 uri 및 헤더 필드로부터 파싱해야 한다. 만료일에는 하나의 포맷이 사용되어야 하지만 인기 있는 웹 사이트에서는 복수의 포맷이 사용되는 경우를 볼 수 있다. 여기서는 그다지 까다로운 점은 없고, 쿠키 경로, 만료일, 도메인 등과 같은 다양한 정보 조각을 저장하기만 하면 된다.

   public Cookie(URI uri, String header) {     String attributes[] = header.split(";");     String nameValue = attributes[0].trim();     this.uri = uri;     this.name = nameValue.substring(0, nameValue.indexOf('='));     this.value = nameValue.substring(nameValue.indexOf('=')+1);     this.path = "/";     this.domain = uri.getHost();     for (int i=1; i < attributes.length; i++) {       nameValue = attributes[i].trim();       int equals = nameValue.indexOf('=');       if (equals == -1) {         continue;       }       String name = nameValue.substring(0, equals);       String value = nameValue.substring(equals+1);       if (name.equalsIgnoreCase("domain")) {         String uriDomain = uri.getHost();         if (uriDomain.equals(value)) {           this.domain = value;         } else {           if (!value.startsWith(".")) {             value = "." + value;           }           uriDomain =              uriDomain.substring(uriDomain.indexOf('.'));           if (!uriDomain.equals(value)) {             throw new IllegalArgumentException(               "Trying to set foreign cookie");           }           this.domain = value;         }       } else if (name.equalsIgnoreCase("path")) {         this.path = value;       } else if (name.equalsIgnoreCase("expires")) {         try {           this.expires = expiresFormat1.parse(value);         } catch (ParseException e) {           try {             this.expires = expiresFormat2.parse(value);           } catch (ParseException e2) {             throw new IllegalArgumentException(               "Bad date format in header: " + value);           }         }       }     }  }

클래스 내의 다른 메소드들은 단지 저장된 데이터를 반환하거나 만료 여부를 확인한다.

   public boolean hasExpired() {     if (expires == null) {       return false;     }     Date now = new Date();     return now.after(expires);   }   public String toString() {     StringBuilder result = new StringBuilder(name);     result.append("=");     result.append(value);     return result.toString();   }

쿠키가 만료된 경우에는 ‘match’가 표시되면 안 된다.

   public boolean matches(URI uri) {     if (hasExpired()) {       return false;     }     String path = uri.getPath();     if (path == null) {       path = "/";     }      return path.startsWith(this.path);   }

Cookie 스펙이 도메인과 경로 양쪽에 대해 매치를 수행할 것을 요구한다는 점에 유의해야 한다. 단순성을 위해 여기서는 경로 매치만을 확인한다.

아래는 전체 Cookie 클래스의 정의이다.

   import java.net.*;   import java.text.*;   import java.util.*;   public class Cookie {     String name;     String value;     URI uri;     String domain;     Date expires;     String path;     private static DateFormat expiresFormat1         = new SimpleDateFormat("E, dd MMM yyyy k:m:s 'GMT'", Locale.US);     private static DateFormat expiresFormat2        = new SimpleDateFormat("E, dd-MMM-yyyy k:m:s 'GMT'", Local.US);		     /**      * Construct a cookie from the URI and header fields      *      * @param uri URI for cookie      * @param header Set of attributes in header      */     public Cookie(URI uri, String header) {       String attributes[] = header.split(";");       String nameValue = attributes[0].trim();       this.uri = uri;       this.name =          nameValue.substring(0, nameValue.indexOf('='));       this.value =          nameValue.substring(nameValue.indexOf('=')+1);       this.path = "/";       this.domain = uri.getHost();       for (int i=1; i < attributes.length; i++) {         nameValue = attributes[i].trim();         int equals = nameValue.indexOf('=');         if (equals == -1) {           continue;         }         String name = nameValue.substring(0, equals);         String value = nameValue.substring(equals+1);         if (name.equalsIgnoreCase("domain")) {           String uriDomain = uri.getHost();           if (uriDomain.equals(value)) {             this.domain = value;           } else {             if (!value.startsWith(".")) {               value = "." + value;             }             uriDomain = uriDomain.substring(               uriDomain.indexOf('.'));             if (!uriDomain.equals(value)) {               throw new IllegalArgumentException(                 "Trying to set foreign cookie");             }             this.domain = value;           }         } else if (name.equalsIgnoreCase("path")) {           this.path = value;         } else if (name.equalsIgnoreCase("expires")) {           try {             this.expires = expiresFormat1.parse(value);           } catch (ParseException e) {             try {               this.expires = expiresFormat2.parse(value);             } catch (ParseException e2) {               throw new IllegalArgumentException(                 "Bad date format in header: " + value);             }           }         }       }     }     public boolean hasExpired() {       if (expires == null) {         return false;       }       Date now = new Date();       return now.after(expires);     }     public String getName() {       return name;     }     public URI getURI() {       return uri;     }     /**      * Check if cookie isn't expired and if URI matches,      * should cookie be included in response.      *      * @param uri URI to check against      * @return true if match, false otherwise      */     public boolean matches(URI uri) {       if (hasExpired()) {         return false;       }      String path = uri.getPath();       if (path == null) {         path = "/";       }       return path.startsWith(this.path);     }     public String toString() {       StringBuilder result = new StringBuilder(name);       result.append("=");       result.append(value);       return result.toString();     }   }

이제 조각들이 모두 확보되었으므로 앞의 Fetch 예제를 실행할 수 있다.

   >> java Fetch http://java.sun.com   Cookies: {Connection=[keep-alive], Host=[java.sun.com],     User-Agent=[Java/1.5.0_04], GET / HTTP/1.1=[null],     Content-type=[application/x-www-form-urlencoded],     Accept=[text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2]}   Cache: []   Adding to cache: SUN_ID=192.168.0.1:235411125667328   Cookies: {Connection=[keep-alive], Host=[java.sun.com],     User-Agent=[Java/1.5.0_04], GET / HTTP/1.1=[null],     Cookie=[SUN_ID=192.168.0.1:235411125667328],     Content-type=[application/x-www-form-urlencoded],     Accept=[text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2]}   Cache: [SUN_ID=192.168.0.1:235411125667328]

(위에 표시된 결과에서 긴 행은 수동으로 줄바꿈한 것임)

‘Cache’로 시작되는 행은 저장된 캐시를 나타낸다. 저장된 쿠키가 즉시 반환되지 않도록 put() 메소드 전에 get() 메소드가 어떻게 호출되는지에 대해 유의하도록 할 것.

쿠키와 URL 접속을 이용한 작업에 관해 자세히 알고 싶으면 자바 튜토리얼의 Custom Networking trail(영문)을 참조할 것. 이는 J2SE 1.4에 기반을 두고 있으므로 튜토리얼에는 아직 여기서 설명한 CookieHandler에 관한 정보가 실려 있지 않다. Java SE 6 ("Mustang")(영문) 릴리즈에서도 기본 CookieHandler 구현에 관한 내용을 찾아볼 수 있다.

Back to Top

테크팁 퀴즈 정답
 
지난 주 뉴스레터로 발송되었던 테크팁 퀴즈의 정답을 알려드립니다.
참여해주신 모든 분께 감사드리며, 당첨자는 10월 26일 SKDN 홈페이지에서 발표합니다.
  1. 다음 중 "pull"파서는?
    1. Xerces
    2. SSAX
    3. Sun Java Streaming XML Parser (SJSXP)
    4. 모두 아님

    정답 c. SJSXP(Sun Java Streaming XML Parser)
    SJSXP는 StAX(Streaming API for XML) 파서의 고속 구현으로서 일종의 pull 파서이다(이와 대조적으로, Xerces와 SSAX는 SAX 파서임). SJSXP는 파서가 문서 내에서 현재 스캔되는 장소에 sorts 포인터?이를 흔히 커서라고 한다?를 유지하는 pull 메소드를 구현한다. 우리는 SJSXP가 제공하는 2개의 API, 커서와 반복자(iterator) 중 하나를 이용하여 커서가 현재 가리키고 있는 노드를 파서에 대해 요구하면 된다. 커서 API는 XML 정보를 문자열로 반환하는 반면 반복자 API는 별개의 이벤트 오브젝트를 파서에서 읽히는 순서대로 반환한다. SJSXP에 관한 자세한 내용은 2005년 4월 29일자 J2EE 테크팁 Sun Java Streaming XML Parser 소개 참조

  2. 다음의 WSDL 바인딩 style/use 조합 중에서 WS-I 호환이 되는 것은?
    1. RPC/encoded
    2. RPC/literal
    3. Document/encoded
    4. Document/literal

    정답 b와 d
    RPC/literal과 Document/literal은 WS-I 호환이 된다. style/use 조합에 관한 자세한 내용은 2005년 8월 23일자 J2EE 테크팁 JAX-RPC 바인딩 스타일과 사용 속성 참조

  3. 다음 중 JSF(JavaServer Faces) 기술에서 유효한 바인딩 식은?
    1. #{invoice.customerName}
    2. ${invoice.date}
    3. #[customer.status == 'VIP']
    4. 모두 아님

    정답 a. #{invoice.customerName}
    바인딩 식의 신택스는 그 자체가 JavaScript의 오브젝트 액세스 신택스에 기초한 JSP(JavaServer Pages) 2.0 Expression Language에 기반을 두고 있다. JSP에서는 식들이 "${}"로 둘러싸이지만 JSF에서는 "#{}"이 사용된다. 자세한 내용은 2004년 10월 15일자 J2EE 테크팁 JSF의 값 바인딩 표현과 메서드 바인딩 표현의 내용을 참조할 것. Expression Language를 JSP 2.1로 통일하기 위해 JSF 1.2에 대해서 업데이트가 이루어졌다는 점에 유의한다. 새로이 통일된 Expression Language에 관한 자세한 내용을 보려면 테크니컬 아티클 Unified Expression Language(영문)참조.

  4. message-driven 빈에 관한 설명 중 맞는 것은?
    1. message-driven 빈의 인스턴스는 특정 클라이언트의 데이터 또는 대화 상태를 유지하지 않는다.
    2. message-driven 빈의 모든 인스턴스는 서로 동등하므로 컨테이너는 어떠한 message-driven 빈 인스턴스에도 메시지를 할당할 수 있다.
    3. 하나의 message-driven 빈이 복수 클라이언트의 메시지를 처리할 수 있다.
    4. 모두 맞음
    5. 모두 틀림

    정답 d. 모두 맞음
    모두 맞음. MDBs에 관한 자세한 내용은 2005년 5월 18일자 J2EE 테크팁 EJB 2.1로 메시지 구동 빈 이용하기의 내용 참조

  5. 스트링 리스트를 정렬하는 방법은?
    1. TreeList 클래스를 사용한다
    2. TreeMap 생성자에 List를 패스하고 맵을 반복한다
    3. Arrayssort 메소드를 호출한다
    4. Collectionssort 메소드를 호출한다

    정답 d. Collections의 정렬 메소드를 호출한다
    자세한 관련 정보는 2004년 7월 16일자 J2SE 테크팁 리스트를 분류하고 섞기 위한 COLLECTIONS의 메소드 사용하기를 참조한다. Collections Framework에 관한 부분은 이미 테크 팁을 통해 여러 차례에 걸쳐 다룬 바 있다.

  6. 다음 중 Math 클래스로 할 수 없는 연산은?
    1. 특정 수의 기수 10 로그를 계산한다
    2. 특정 String에 대한 해시코드를 생성한다
    3. 유니코드 문자의 세제곱근을 계산한다
    4. 두 점간의 유클리드 거리를 계산한다

    정답 b. 특정 String에 대한 해시코드를 생성한다.
    String의 해시코드 계산은 String 클래스에 의해 이루어지며, 다른 세 연산은 JDK 5.0에 추가된 Math 클래스 메소드를 통해 수행할 수 있다. 유니코드 문자의 세제곱근을 계산하려면 계산에 앞서 문자를 char에서 int로 변환한다. 자세한 내용은 2004년 11월 11일자 J2SE 테크팁 MATH 클래스에서의 새로운 점참조
Back to Top

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

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

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


'JAVA > JSP_Servlet' 카테고리의 다른 글

[펌] JSP 2.0에서 Custom Tag활용  (0) 2005.10.25
[link] ajaxfaces.com  (0) 2005.10.25
[펌] apache.common.DbUtil & Oracle 용 RowProcess  (0) 2005.10.17
jetty site  (0) 2005.10.12
[펌] myeclipse에서 xdoclet 설명  (0) 2005.10.04
Posted by tornado
|

Oracle의 숫자형 필드의 도메인은 Number로 잡는다.

그런데 java에서는 Integer와 Double로 명시적으로 사용하고 싶었다.

그리고, DATE 형은 oracle-jdbc에서 java.sql.TimeStamp 형으로 넘어와서

Bean에 자동으로 set이 안된다.

그래서 BasicRowProcess.java를 조금 바꿔서 OracleRowProcess.java로 하고

사용하고 있다.

사용방법은 Handler생성 할때 아래와 같이 하면된다.

h = new BeanListHandler( dto.getClass(), OracleRowProcess.instance() );

 

---------------------------

OracleRowProcess.java

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

 

 
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.dbutils.RowProcessor;
import org.apache.log4j.Logger;
/**
 * @author 박종복 (asdkf_2000@yahoo.co.kr)
 * @version 1.0,  2005. 4. 14
 *
 */
public class OracleRowProcess implements RowProcessor {

    /**
     * Set a bean's primitive properties to these defaults when SQL NULL
     * is returned.  These are the same as the defaults that ResultSet get*
     * methods return in the event of a NULL column.
     */
    private static final Map primitiveDefaults = new HashMap();

    static {
        primitiveDefaults.put(Integer.TYPE, new Integer(0));
        primitiveDefaults.put(Short.TYPE, new Short((short) 0));
        primitiveDefaults.put(Byte.TYPE, new Byte((byte) 0));
        primitiveDefaults.put(Float.TYPE, new Float(0));
        primitiveDefaults.put(Double.TYPE, new Double(0));
        primitiveDefaults.put(Long.TYPE, new Long(0));
        primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE);
        primitiveDefaults.put(Character.TYPE, new Character('\u0000'));
    }

    /**
     * Special array index that indicates there is no bean property that
     * matches a column from a ResultSet.
     */
    private static final int PROPERTY_NOT_FOUND = -1;

    /**
     * The Singleton instance of this class.
     */
    private static final OracleRowProcess instance = new OracleRowProcess();

    /**
     * Returns the Singleton instance of this class.
     *
     * @return The single instance of this class.
     */
    public static OracleRowProcess instance() {
        return instance;
    }

    /**
     * Protected constructor for BasicRowProcessor subclasses only.
     */
    protected OracleRowProcess() {
        super();
    }

    /**
     * Convert a <code>ResultSet</code> row into an <code>Object[]</code>.
     * This implementation copies column values into the array in the same
     * order they're returned from the <code>ResultSet</code>.  Array elements
     * will be set to <code>null</code> if the column was SQL NULL.
     *
     * @see org.apache.commons.dbutils.RowProcessor#toArray(java.sql.ResultSet)
     */
    public Object[] toArray(ResultSet rs) throws SQLException {
        ResultSetMetaData meta = rs.getMetaData();
        int cols = meta.getColumnCount();
        Object[] result = new Object[cols];

        for (int i = 0; i < cols; i++) {
            result[i] = rs.getObject(i + 1);
        }

        return result;
    }

    /**
     * Convert a <code>ResultSet</code> row into a JavaBean.  This
     * implementation uses reflection and <code>BeanInfo</code> classes to
     * match column names to bean property names.  Properties are matched to
     * columns based on several factors:
     * <br/>
     * <ol>
     *     <li>
     *     The class has a writable property with the same name as a column.
     *     The name comparison is case insensitive.
     *     </li>
     *
     *     <li>
     *     The property's set method parameter type matches the column
     *     type. If the data types do not match, the setter will not be called.
     *     </li>
     * </ol>
     *
     * <p>
     * Primitive bean properties are set to their defaults when SQL NULL is
     * returned from the <code>ResultSet</code>.  Numeric fields are set to 0
     * and booleans are set to false.  Object bean properties are set to
     * <code>null</code> when SQL NULL is returned.  This is the same behavior
     * as the <code>ResultSet</code> get* methods.
     * </p>
     *
     * @see org.apache.commons.dbutils.RowProcessor#toBean(java.sql.ResultSet, java.lang.Class)
     */
    public Object toBean(ResultSet rs, Class type) throws SQLException {

        PropertyDescriptor[] props = this.propertyDescriptors(type);

        ResultSetMetaData rsmd = rs.getMetaData();

        int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);

        int cols = rsmd.getColumnCount();

        return this.createBean(rs, type, props, columnToProperty, cols);
    }

    /**
     * Convert a <code>ResultSet</code> into a <code>List</code> of JavaBeans. 
     * This implementation uses reflection and <code>BeanInfo</code> classes to
     * match column names to bean property names. Properties are matched to
     * columns based on several factors:
     * <br/>
     * <ol>
     *     <li>
     *     The class has a writable property with the same name as a column.
     *     The name comparison is case insensitive.
     *     </li>
     *
     *     <li>
     *     The property's set method parameter type matches the column
     *     type. If the data types do not match, the setter will not be called.
     *     </li>
     * </ol>
     *
     * <p>
     * Primitive bean properties are set to their defaults when SQL NULL is
     * returned from the <code>ResultSet</code>.  Numeric fields are set to 0
     * and booleans are set to false.  Object bean properties are set to
     * <code>null</code> when SQL NULL is returned.  This is the same behavior
     * as the <code>ResultSet</code> get* methods.
     * </p>
     *
     * @see org.apache.commons.dbutils.RowProcessor#toBeanList(java.sql.ResultSet, java.lang.Class)
     */
    public List toBeanList(ResultSet rs, Class type) throws SQLException {
        List results = new ArrayList();

        if (!rs.next()) {
            return results;
        }

        PropertyDescriptor[] props = this.propertyDescriptors(type);
        ResultSetMetaData rsmd = rs.getMetaData();
        int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
        int cols = rsmd.getColumnCount();

        do {
            results.add(this.createBean(rs, type, props, columnToProperty, cols));

        } while (rs.next());

        return results;
    }

    /**
     * Creates a new object and initializes its fields from the ResultSet.
     *
     * @param rs The result set
     * @param type The bean type (the return type of the object)
     * @param props The property descriptors
     * @param columnToProperty The column indices in the result set
     * @param cols The number of columns
     * @return An initialized object.
     * @throws SQLException If a database error occurs
     */
    private Object createBean(
        ResultSet rs,
        Class type,
        PropertyDescriptor[] props,
        int[] columnToProperty,
        int cols)
        throws SQLException {

        Object bean = this.newInstance(type);

        for (int i = 1; i <= cols; i++) {

            if (columnToProperty[i] == PROPERTY_NOT_FOUND) {
                continue;
            }
           
            Object value = rs.getObject(i);

            PropertyDescriptor prop = props[columnToProperty[i]];
            Class propType = prop.getPropertyType();

            if (propType != null && value == null && propType.isPrimitive()) {
                value = primitiveDefaults.get(propType);
            }
           
            this.callSetter(bean, prop, value);
        }

        return bean;
    }

    /**
     * The positions in the returned array represent column numbers.  The values
     * stored at each position represent the index in the PropertyDescriptor[]
     * for the bean property that matches the column name.  If no bean property
     * was found for a column, the position is set to PROPERTY_NOT_FOUND.
     *
     * @param rsmd The result set meta data containing column information
     * @param props The bean property descriptors
     * @return An int[] with column index to property index mappings.  The 0th
     * element is meaningless as column indexing starts at 1.
     *
     * @throws SQLException If a database error occurs
     */
    private int[] mapColumnsToProperties(
        ResultSetMetaData rsmd,
        PropertyDescriptor[] props)
        throws SQLException {

        int cols = rsmd.getColumnCount();
        int columnToProperty[] = new int[cols + 1];
       
        for (int col = 1; col <= cols; col++) {
            String columnName = rsmd.getColumnName(col);
            for (int i = 0; i < props.length; i++) {

                if (columnName.equalsIgnoreCase(props[i].getName())) {
                    columnToProperty[col] = i;
                    break;

                } else {
                    columnToProperty[col] = PROPERTY_NOT_FOUND;
                }
            }
        }

        return columnToProperty;
    }

    /**
     * Convert a <code>ResultSet</code> row into a <code>Map</code>.  This
     * implementation returns a <code>Map</code> with case insensitive column
     * names as keys.  Calls to <code>map.get("COL")</code> and
     * <code>map.get("col")</code> return the same value.
     * @see org.apache.commons.dbutils.RowProcessor#toMap(java.sql.ResultSet)
     */
    public Map toMap(ResultSet rs) throws SQLException {
        Map result = new CaseInsensitiveHashMap();
        ResultSetMetaData rsmd = rs.getMetaData();
        int cols = rsmd.getColumnCount();

        for (int i = 1; i <= cols; i++) {
            result.put(rsmd.getColumnName(i), rs.getObject(i));
        }

        return result;
    }

    /**
     * Calls the setter method on the target object for the given property.
     * If no setter method exists for the property, this method does nothing.
     * @param target The object to set the property on.
     * @param prop The property to set.
     * @param value The value to pass into the setter.
     * @throws SQLException if an error occurs setting the property.
     */
    private void callSetter(
        Object target,
        PropertyDescriptor prop,
        Object value)
        throws SQLException {

        Method setter = prop.getWriteMethod();

        if (setter == null) {
            return;
        }

        Class[] params = setter.getParameterTypes();
        try {

            // Don't call setter if the value object isn't the right type
            if( params[0].equals( Date.class) && value instanceof Timestamp ){
                setter.invoke(target, new Object[] { (java.util.Date)value } );
            }else if (this.isCompatibleType(value, params[0])) {
                    setter.invoke(target, new Object[] { value });
            }else if( params[0].equals( Integer.class) && value instanceof Number ){
                setter.invoke(target, new Object[] { new Integer(value.toString()) } );
            }else if( params[0].equals( Double.class ) && value instanceof Number ){
                setter.invoke(target, new Object[] { new Double(value.toString()) } );
            }

        } catch (IllegalArgumentException e) {
            throw new SQLException(
                "Cannot set " + prop.getName() + ": " + e.getMessage());

        } catch (IllegalAccessException e) {
            throw new SQLException(
                "Cannot set " + prop.getName() + ": " + e.getMessage());

        } catch (InvocationTargetException e) {
            throw new SQLException(
                "Cannot set " + prop.getName() + ": " + e.getMessage());
        }
    }

    /**
     * ResultSet.getObject() returns an Integer object for an INT column.  The
     * setter method for the property might take an Integer or a primitive int.
     * This method returns true if the value can be successfully passed into
     * the setter method.  Remember, Method.invoke() handles the unwrapping
     * of Integer into an int.
     *
     * @param value The value to be passed into the setter method.
     * @param type The setter's parameter type.
     * @return boolean True if the value is compatible.
     */
    private boolean isCompatibleType(Object value, Class type) {
        // Do object check first, then primitives
        if (value == null || type.isInstance(value)) {
            return true;

        } else if (type.equals(Long.TYPE) && Long.class.isInstance(value)) {
            return true;

        } else if (type.equals(Float.TYPE) && Float.class.isInstance(value)) {
            return true;

        } else if (type.equals(Short.TYPE) && Short.class.isInstance(value)) {
            return true;

        } else if (type.equals(Byte.TYPE) && Byte.class.isInstance(value)) {
            return true;

        } else if (
            type.equals(Character.TYPE) && Character.class.isInstance(value)) {
            return true;

        } else if (
            type.equals(Boolean.TYPE) && Boolean.class.isInstance(value)) {
            return true;

        } else {
            return false;
        }

    }

    /**
     * Returns a new instance of the given Class.
     *
     * @param c The Class to create an object from.
     * @return A newly created object of the Class.
     * @throws SQLException if creation failed.
     */
    private Object newInstance(Class c) throws SQLException {
        try {
            return c.newInstance();

        } catch (InstantiationException e) {
            throw new SQLException(
                "Cannot create " + c.getName() + ": " + e.getMessage());

        } catch (IllegalAccessException e) {
            throw new SQLException(
                "Cannot create " + c.getName() + ": " + e.getMessage());
        }
    }

    /**
     * Returns a PropertyDescriptor[] for the given Class.
     *
     * @param c The Class to retrieve PropertyDescriptors for.
     * @return A PropertyDescriptor[] describing the Class.
     * @throws SQLException if introspection failed.
     */
    private PropertyDescriptor[] propertyDescriptors(Class c)
        throws SQLException {
        // Introspector caches BeanInfo classes for better performance
        BeanInfo beanInfo = null;
        try {
            beanInfo = Introspector.getBeanInfo(c);

        } catch (IntrospectionException e) {
            throw new SQLException(
                "Bean introspection failed: " + e.getMessage());
        }

        return beanInfo.getPropertyDescriptors();
    }

    /**
     * A Map that converts all keys to lowercase Strings for case insensitive
     * lookups.  This is needed for the toMap() implementation because
     * databases don't consistenly handle the casing of column names.
     */
    private static class CaseInsensitiveHashMap extends HashMap {
        /**
         * Logger for this class
         */
        private static final Logger logger = Logger
                .getLogger(CaseInsensitiveHashMap.class);

        /**
         * @see java.util.Map#containsKey(java.lang.Object)
         */
        public boolean containsKey(Object key) {
            return super.containsKey(key.toString().toLowerCase());
        }

        /**
         * @see java.util.Map#get(java.lang.Object)
         */
        public Object get(Object key) {
            return super.get(key.toString().toLowerCase());
        }

        /**
         * @see java.util.Map#put(java.lang.Object, java.lang.Object)
         */
        public Object put(Object key, Object value) {
            return super.put(key.toString().toLowerCase(), value);
        }

        /**
         * @see java.util.Map#putAll(java.util.Map)
         */
        public void putAll(Map m) {
            Iterator iter = m.keySet().iterator();
            while (iter.hasNext()) {
                Object key = iter.next();
                Object value = m.get(key);
                this.put(key, value);
            }
        }

        /**
         * @see java.util.Map#remove(java.lang.ObjecT)
         */
        public Object remove(Object key) {
            return super.remove(key.toString().toLowerCase());
        }
    }

}

Posted by tornado
|

jetty site

JAVA/JSP_Servlet 2005. 10. 12. 11:13
Posted by tornado
|

[펌]가비지 콜렉션

JAVA/JSE 2005. 10. 12. 10:40

가비지 콜렉션

2004년 1월의 테크팁이었던 Monitoring Class Loading and Garbage Collection에서는 자바 커맨드 라인 툴 (자바 애플리케이션 런쳐)를 이용하여 커맨드 라인 옵션인 -verbose:gc에 대해서 알아보았다. 표준이 아니라는 이유로 -XX 옵션에 대해 신경 쓰지 않는다거나, 옵션의 균질성을 중요시한다거나, 또는 현재 사용하고 있는 옵션에 대해서 알고 싶다면 다음과 같이 -verbose:gc 를 입력해보자.

     -XX:+PrintGC -XX:+TraceClassUnloading

이것은 -verbose:gc 옵션이 어떻게 번역되는지를 보여준다.

이 옵션은 애플리케이션이 실행되는 동안 가비지 콜렉션 이벤트의 리포트를 요구한다. J2SE v1.4.2에서는 가비지 콜렉터를 컨트롤하는 다른 많은 커맨드 라인이 있다. 코드의 어느 라인도 변경하지 않고 애플리케이션의 성능을 최대한으로 활용하려면, 이 옵션들을 최소한 하나 이상 사용할 수가 있다. 이번 팁에서는 가비지 콜렉터를 컨트롤하는 부수적인 많은 자바 커맨드 라인을 다룰 것이지만, 가비지 콜렉션 튜닝 옵션의 전체를 말하지는 않는다.

자바 애플리케이션 런쳐는 표준 커맨드-라인 스위치와 비표준 커맨드-라인 스위치를 모두 동반한다. -verbose:gc 옵션에 관한 정보에 대해 알아보려면, 특정 플랫폼별로 다음 웹페이지를 참고하자.

위의 참고 페이지에는 몇몇 비표준 옵션들에 대해서도 나와있다. 자바 커맨드 라인 툴에서 표준 옵션인 -X을 실행하면 사용자의 플랫폼을 위한 비표준 옵션을 보여준다.

솔라리스 플랫폼이라면, java -X 을 실행했을 경우 출력되는 내용은 다음과 같다.

   -Xmixed           mixed mode execution (default)   -Xint             interpreted mode execution only   -Xbootclasspath:<directories and zip/jar files separated by :>                     set search path for bootstrap classes and                      resources   -Xbootclasspath/a:<directories and zip/jar files separated by :>                     append to end of bootstrap class path   -Xbootclasspath/p:<directories and zip/jar files separated by :>                     prepend in front of bootstrap class path   -Xnoclassgc       disable class garbage collection   -Xincgc           enable incremental garbage collection   -Xloggc:<file>    log GC status to a file with time stamps   -Xbatch           disable background compilation   -Xms<size>        set initial Java heap size   -Xmx<size>        set maximum Java heap size   -Xss<size>        set java thread stack size   -Xprof            output cpu profiling data   -Xrunhprof[:help]|[:<option>=<value>, ...]                     perform JVMPI heap, cpu, or monitor profiling   -Xdebug           enable remote debugging   -Xfuture          enable strictest checks, anticipating future                      default   -Xrs              reduce use of OS signals by Java/VM (see                      documentation)   -Xcheck:jni       perform additional checks for JNI functions     The -X options are non-standard and subject to change without   notice.

위의 출력값의 마지막 줄을 주목하자. 그러한 비표준 옵션들을 사용하는 것은 사용자의 책임 하에 있음을 분명히 나타내고 있다.

가비지 콜렉션을 위한 세 가지 특정한 비표준 옵션은 -Xnoclassgc, -Xincgc, 그리고 -Xloggc:<file>이다. -Xnoclassgc 옵션을 지정한다면, 콜렉션이 발생하지만 클래스들은 permanent generation으로부터 수집되지 않는다. 이는 콜렉션이 수거하는 메모리가 없음을 의미한다. 만약 클래스를 로딩하려고 할 때 이미 로딩된 클래스들을 위해 메모리를 써버렸다면 콜렉션은 문제를 해결하지 않을 것이다.

썬의 1.4.2 구현에서 -Xincgc-XX:+UseTrainGC 옵션과 동등한 값을 가진다. 즉, -Xincgc은 old generation을 위해서 디폴트 값에서 주어진 "serial" 콜렉터를 사용하기 보다는 "train"콜렉터를 사용한다. ("generations"의 개념은 이번 팁의 후반에서 다루게된다.) train 콜렉터는 약 10%의 퍼포먼스 부하를 발생시킨다. 또한 old generation 전체를 한 번에 수집할 수 없기 때문에 약간의 공간적인 부하(space overhead)도 발생한다. train 콜렉터는 점진적으로 작동하는 장점을 가지기 때문에 휴지 시간이 상당히 짧다. J2SE 1.5.0 베타 릴리즈에서는 -Xincgc 플래그는 train 콜렉터보다는 CMS(concurrent mark sweep) 콜렉터를 인보킹하게 된다. 그 이유는, CMS 콜렉터가 train 콜렉터보다 더 균일하기 때문이다.

-Xloggc 옵션을 지정해줌으로써 -verbose:gc 출력값의 등가를 파일로 출력할 수 있다. -verbose:gc 에 의한 출력값과 비교해 보면, -Xloggc 에 의한 출력값은 한 가지 추가적인 항목을 포함하는데, 그것은 애플리케이션에서의 첫 콜렉션이 일어난 시간에서부터 가비지 콜렉션이 발생한 시간에 대한 정보이다. 다음의 -Xloggc 샘플 출력값에서 살펴보자.

   0.000: [GC 512K->153K(1984K), 0.0198483 secs]   0.372: [GC 506K->281K(1984K), 0.0206428 secs]   0.393: [Full GC 281K->281K(1984K), 0.0888926 secs]   0.519: [GC 947K->941K(1984K), 0.0045715 secs]   0.524: [Full GC 941K->941K(1984K), 0.0797666 secs]   0.650: [GC 2107K->1597K(2808K), 0.0013546 secs]   0.838: [GC 2107K->1700K(2808K), 0.0116557 secs

파일에 재전송할 필요없이 -XX:+PrintGCTimeStamps 옵션을 지정해줌으로써 -Xloggc 출력값에 시간 소인을 나타낼 수 있다.

-Xnoclassgc, -Xincgc-Xloggc:<file> 이외에도 가비지 콜렉터를 컨트롤하는 다른 옵션들이 존재한다. 예를 들자면, 콜렉터가 실행할 때 간접적으로 영향을 끼치도록 -Xms -Xmx 옵션을 사용하여 메모리 할당의 풀 사이즈를 변경할 수 있다. -Xms의 값을 크게 지정해주면, 그만큼 큰 자바 객체 힙으로 작업하게 된다. 이것은 힙을 채우는 데에 더 많은 시간이 걸린다는 것을 의미하고 결과적으로 콜렉션을 피하지는 않지만 미루게 된다는 것이다. 큰 -Xms 값은 또한 더 많은 시스템 리소스를 소모하게 된다. 반면 -Xmx의 값을 크게 지정하면, 자바 객체 힙의 사이즈가 필요 시에 커질 수 있게 된다. 이렇게 값이 큰 객체 힙은 조금 덜 빈번하게 가비지 콜렉트된다는 점 외에는 모든 점이 동일하다. 따라서 이 옵션을 통해 가비지 콜렉션이 빈번하지만 짧은 시간동안 일어나는 것과, 횟수는 적지만 오랜 시간동안 가비지 콜렉션이 일어나는 것 중 하나를 선택할 수 있다. 하지만 각각의 콜렉션이 부하를 발생하기 때문에, 사용자가 정의하는 요구조건에 따라 더 나은 접근법은 달라지게 되어있다.

-Xms-Xmx 외에도 가비지 콜렉션에 영향을 주는 비표준 옵션들이 존재한다. 그 옵션들은 java -X 도움말에서 볼 수 없는데, 그 이유는 이 옵션들은 비표준 옵션이기 때문에 두개의 X를 사용해야하기 때문이다.

만약 힙의 최소값과 최대값을 지정해주고자 한다면 (이 때, 디폴트 값의 범위는 40%~70%), -XX:MinHeapFreeRatio=<Minimum> 옵션과 -XX:MaxHeapFreeRatio=<Maximum> 옵션을 사용하면 된다. 그러나 최소값을 너무 작게 설정해놓으면, 힙은 콜렉션 후에 충분한 여유공간을 확보하지 못하게 되므로, 단기간 내에 또다시 콜렉팅을 해야할 수도 있다. 반면, 최소값을 크게 설정하면, 더 많은 "head room"이 생기므로 다음 콜렉션 때까지 시간을 지연시킬 수 있을 것이다. -XX:MinHeapFreeRatio 의 값을 크게 했을 때의 단점은 시스템 메모리가 이 가상 장치에 고정되기 때문에 사용자의 또 다른 애플리케이션에 사용될 수 없다는 점이다.

-XX 세트내의 다른 비표준 옵션들은 가비지 콜렉터의 작동 방법에 영향을 끼친다. -XX:+UseConcMarkSweepGC 커맨드 라인 플래그("concurrent mark sweep garbage collection"의 줄임말)는 병행(concurrent) 가비지 콜렉터를 실행시킨다. -XX:+UseConcMarkSweepGC 콜렉터는 old generation을 동시에 콜렉팅한다. 그 이유는 전반적인 콜렉션 같은 old generation 들은 콜랙팅할 때 대체적으로 시간이 오래 걸려서 병행 가비지 콜렉터를 실행시키는 것이 더 효과적이기 때문이다. 이는 애플리케이션에서 가비지 콜렉션 휴지 시간을 짧게 할 수 있도록 프로세싱 파워를 활용한다는 것을 말한다. -XX:+UseParallelGC 커맨드 라인 플래그는 병렬 가비지 콜렉팅을 가능하게 한다. -XX:+UseParallelGC 콜렉터는 오직 다중 프로세서를 이용한 young generation만을 콜렉팅한다. 이 접근법은 처리율은 높이지만, 전반적인 콜렉션의 휴지 시간을 줄이지는 못한다.

또 하나의 흥미로운 옵션인 -XX:+PrintGCDetails 는 각각의 콜렉션에서 각 generation에 어떤 일이 발생했는지를 보여준다. (단, permanent generation 에 대한 자세한 사항들은 보여주지 않는다. 이 점은 JDK 1.5.0.에서 수정되었다.)

SwingSet2의 데모에 사용된 -XX:+PrintGCDetails의 예제를 살펴보자. (출력값의 라인들은 테크팁 페이지의 경계선에 맞추기 위해 나누어주었다.)

   java -Xloggc:details.out -XX:+PrintGCDetails -jar    /home/eo86671/j2sdk1.4.2/demo/jfc/SwingSet2/SwingSet2.jar   0.000: [GC 0.000: [DefNew: 511K->64K(576K), 0.0182344 secs]         511K->153K(1984K), 0.0185255 secs]   1.387: [GC 1.387: [DefNew: 417K->64K(576K), 0.0192086 secs]        1.407: [Tenured: 217K->281K(1408K), 0.0725645 secs]         506K->281K(1984K), 0.0923346 secs]   1.559: [GC 1.559: [DefNew: 10K->3K(576K), 0.0044098 secs]        1.564: [Tenured: 937K->941K(1408K), 0.0741569 secs]        948K->941K(1984K), 0.0790573 secs]   1.703: [GC 1.703: [DefNew: 510K->0K(576K), 0.0011627 secs]         2107K->1597K(2808K), 0.0013820 secs]   2.210: [GC 2.210: [DefNew: 509K->64K(576K), 0.0112637 secs]         2107K->1710K(2808K), 0.0115942 secs]   2.927: [GC 2.927: [DefNew: 575K->64K(576K), 0.0170128 secs]        2222K->1841K(2808K), 0.0173293 secs]   8.430: [GC 8.430: [DefNew: 576K->64K(576K), 0.0142839 secs]         2353K->2025K(2808K), 0.0156266 secs]   8.823: [GC 8.823: [DefNew: 494K->64K(576K), 0.0164915 secs]         2456K->2243K(2808K), 0.0166323 secs]   8.856: [GC 8.856: [DefNew: 569K->41K(576K), 0.0058505 secs]        8.862: [Tenured: 2341K->1656K(2360K), 0.1464764 secs]         2749K->1656K(2936K), 0.1526133 secs]

변경내용이 그 결과에 어떤 영향 (예를 들어서, 콜렉션간의 시간 연장이나 각각 특정 휴지 시간의 감소)을 주는지를 알아보기 위해 -Xms 옵션과 -Xmx 옵션을 조정해보자.

앞서 'generations'의 힙 공간에 대해 언급한 바 있다. 이 말이 무슨 의미인지 궁금하다면, 여기 짤막한 설명을 참조하자. JRE 1.4.2 에서 힙은 young, old, 그리고 permanet와 같이 세 개의 generation으로 구분된다. young generation은 객체가 생성된 장소이다. 이 generation의 사이즈는 -XX:NewSize 옵션과 -XX:MaxNewSize 옵션에 의해 컨트롤된다. 새로운 객체를 보유하게 되면, old generation으로 승격된다. old generation의 사이즈는 전체 힙 사이즈 (-Xms-Xmx) 에서 young generation의 사이즈 (-XX:NewSize-XX:MaxNewSize)를 감한 사이즈로 계산된다. young generation은 명시적 크기보다는 -XX:NewRatio=를 이용하여 더 잘 지정할 수 있는데, -XX:NewRatio= 옵션은 힙 설정의 전체 사이즈를 설정해주기 때문이다. 선택한 사이즈는 콜렉팅 되는 시간에 대해 콜렉션의 횟수를 결정하게 된다. 어떤 애플리케이션들은 짧은 휴지 시간을 요구하는 한편, 콜렉터의 효율성을 필요로 하는 애플리케이션들도 있다. (많은 애플리케이션에서 이 단계의 튜닝을 해줄 필요는 전혀 없다.)

마지막으로 언급할 GC옵션은 -XX:+DisableExplicitGC이다. System.gc()의 호출을 이용하여 가비지 콜렉터를 실행하라는 명령을 했을 때, 시스템이 이를 무시하기를 원한다면 이 옵션을 사용해보자. 가비지 콜렉터는 프로그래머가 명령했을 때가 아닌, 필요시에 계속해서 실행되고 있을 것이다. 하지만 프로그래머의 명령을 무시하는 것이 좋은 생각일까? 아마도 아닐 것이다. 왜냐하면, 프로그래머가 가비지 콜렉터를 실행하기 적당한 시점이라고 생각할 수 있기 때문이다. 물론, 만약 공유 라이브러리를 사용하고 있고, 실행 콘텍스트가 프로그래머의 원래의 계획과 다르다면, 이러한 새로운 상황에 유효하지 않을 수도 있다.

가비지 콜렉션에 관련된 옵션들에 대해 좀더 자세한 정보를 원한다면, Tuning Garbage Collection with the 1.4.2 Java Virtual Machine를 읽어보기 바란다. 튜닝을 할 때는 튜닝의 목적이 성능 향상인지, 짧은 휴지 시간인지 아니면 작은 범위의 ㄴ메모리 사용을 위한 것인지 그 목적을 확실히 해야 한다. JVM이 업데이트 되면서 선택할 수 있는 튜닝 옵션들도 다시 한번 살펴 보아야 한다. 이번 팁에서 다룬 옵션들의 대부분은 썬의 런타임 환경 1.4.2 릴리즈에 한정됨을 기억하자. 1.5.0 릴리즈는 고유의 컨트롤 셋과 디폴트 셋을 가진다.

Posted by tornado
|

애플리케이션 서버의 성능과 확장성을 개선하기 위한 Java HotSpot VM vl.4.x Turbo-Charging (1)

 

 
 
 

Java 프로그래밍 언어는 많은 데이터 중심적인 애플리케이션용 선택 언어로 널리 받아들여지고 있다. Java의 성공은 언어의 초기 단계부터 설계된 다음과 같은 중요 기능을 토대로 만들어졌기 때문이다.

- WORA : Write Once Run Anywhere
- 자동 메모리 할당과 컬렉션
- 객체 지향 디자인
- 상속

그러나 결정적으로 소스 코드를 컴파일해서 만들어진 Java 바이트 코드를 하위단인 JVM이 실행시키는 구조는 시간을 다투는 환경에서 Java를 선택하는 데 있어 고민을 안겨주었던 것도 사실이다. 전통적으로 가비지 컬렉션(GC)은 사용자의 애플리케이션을 일시적으로 중단시키고 시스템이 메모리를 재순환시킨다. 이런 GC 중단은 밀리초에서 심한 경우에는 수초일 수도 있다.

통신 회사 장비 공급자는 통신 회사가 기업 고객과 엄밀한 수준의 서비스 협약이 돼 있을 뿐더러 부동산 시장에서도 기본적인 수준의 신뢰성과 반응 시간을 가지고 있어야 한다는 것을 잘 알고 있다. 수화기를 든 다음 10 또는 20초를 기다려야 신호음이 난다는 것은 용납될 수 없다.

통신 회사들은 여러 가지 이유로 자사의 환경에 Java를 사용하길 원한다.


특히 이미 존재하는 광범위하고 풍부한 애플리케이션을 생성할 수 있는 능력있는 수백만의 프로그래머들과 회사들이 Java를 기본 기술로 표준화하고 있다. 이는 새로운 서비스와 기술을 많이 구할 수 있다는 것이다. 거기에다가 J2EE에는 보편적인 표준 환경과 기하급수적으로 확장할 수 있는 잠재력이 있다. 그렇지만 예측할 수 없는 ‘중단’과 그로 인해 애플리케이션의 반응이 지연되는 것 때문에 어떤 회사라도 자사 네트웍의 핵심 기술로 Java를 사용하는 것이 타당한지 묻게 한다.

통신 회사는 수익을 얻기 위해서 완전한 통화와 서비스 세션에 의존한다. 불완전하거나 버림받은 세션은(예를 들어 통화와 인스턴트 메시지) 네트웍 자원과 전혀 사용되지 않는 대역폭을 제공하는 비용을 낭비하는 것이다.

이러한 환경에서 통신 회사뿐만 아니라 차세대의 통신 기반을 선도하는 3rd generation(3G) 회사들에게도 표준으로 채용된 SIP 프로토콜이 이상적인 선택일 것이다. SIP는 UDP와 TCP를 기반으로 신호를 보내고, 패킷(UDP) 재전송이 내장돼 있는 프로토콜이다. 만약 UDP 요청 패킷이 특정 시간 내(주로 500ms)에 반응이 없으면 재전송된다. 네트웍 불능이나 패킷 손실 등 다른 에러들에 대한 계산도 돼 있어야 한다.

즉 JVM은 500ms에 근접한 시간만큼 애플리케이션을 중단시키면 안된다는 의미다. 왜냐하면 네트웍 패킷 지연(가는 데만 50~100ms)과 CPU의 프로세싱 시간이 계산되면 재전송이 일어나기까지 시간이 많지 않는다. 계단식(Cascade) 실패는 주로 애플리케이션 서버에 높은 로드에 대한 대비를 하지 않은 불완전한 디자인이나 단일 쓰레드, 애플리케이션 쓰레드를 모두 중지시키는 ‘Stop-the-world 가비지 컬렉션’ 정책을 사용하는 JVM(J2SE 1.4 버전 이하)을 사용할 때 일어난다. 그리고 이 JVM들은 단일 쓰레드이고, 여러 CPU가 있더라도 오직 하나의 CPU에서만 가비지 컬렉션을 실행시킨다.

Sun HotSpot JVM(JDKTM 1.3+)에 사용된 알고리즘은 메모리 재사용과 객체 노화에 효과적이다. JVM heap은 객체의 나이에 따라 ‘신세대’와 ‘구세대’로 나뉘어진다.

신세대는 ‘에덴’과 2개의 ‘생존 공간(Survivor)’으로 나눠졌다. 대부분의 애플리케이션에서는 2/3의 객체는 ‘단기 객체’로 빨리 죽고, ‘신세대’에서 컬렉션할 수 있다. 주로 신세대의 객체는 총 heap 크기에 비해 작다. 따라서 신세대에서 짧은 멈춤이 빈번하게 일어나지만 한번의 컬렉션에 더 많은 메모리를 컬렉션할 수 있다. 그러나 신세대에서 여러 번 생존 공간에 있게 되면, 그것은 ‘오래된’ 또는 ‘장기 객체’로 인식하고 ‘구세대’로 ‘승진(promoted)’ 또는 ‘보유(tenured)’하게 된다. 비록 대개 ‘구세대’의 크기가 크기만, 결국은 모두 사용하게 되면 컬렉션이 필요하게 된다. 이로 인해 구세대에서 빈도수는 낮지만, 장시간의 멈춤이 일어난다. 보다 자세한 것은 ‘Tuning Garbage Collection with the 1.3.1 Java Virtual Machine’이라는 Sun HotSpot JVM 가비지 컬렉션 테크놀로지를 참조한다.

Ubiquity의 SIP 애플리케이션 서버인 Application Services Broker(ASB)는 100% 순수 Java 기술이며, JVM과 가비지 컬렉션에 대한 완벽한 테스트 환경을 제공한다. SIP 애플리케이션 서버로서, ASB는 신호가 오는 네트웍으로부터 서비스 요청을 받은 다음 수정하고, 최종적으로 답변해주는 책임이 있다. SIP 트래픽을 신호가 오는 네트웍으로부터 서비스 요소(SIP Servlets)로 연결해 책임을 이행한다. 이 서비스 요소는 특정 신호 메시지에 관심이 있다고 ASB에 등록한다. ASB는 대규모 호출을 다양한 로드 분산으로 생성한다. 이는 클러스터 기반으로 구현되어 한 대나 여러 대에서나, 여러 프로세서에서 잘 확장된다. 이런 때 JVM 디자인이 한계점까지 압력을 받으며, 이런 상태를 관찰하면서 다중 쓰레드의 JVM을 개선시키는 데 많은 통찰력을 얻게 된다.

예를 들어 Ubiquity ASB는 호출되는 하나의 프로세스당(일반적으로 한 호출은 하나의 세션이다) 약 220KB의 가비지를 생성한다. 10%의 데이터는 40초 가까이 생존하고, 나머지 90%는 곧바로 죽어버린다. 이 10%의 데이터는 단시간이지만 평균 초당 100번을 호출한다고 하면 대단히 많은 메모리를 사용하게 된다. 40초 안에 최소한 88MB의 활성 데이터를 갖게 된다. 만약 가비지 컬렉션이 5분에 한번씩 ‘구세대’에서 일어난다면 heap의 크기는 최소한 660MB가 돼야 한다. 이전의 가비지 컬렉션 엔진이 스캔하기에 매우 커 컬렉션하는 데 100~150ms 정도 걸린다.

통신 애플리케이션 서버는 애플리케이션을 멈추는 시간을 엄격히 제한해줄 수 있는 결정적인 GC 모델을 요구하고 여러 프로세스에서도 잘 확장돼야 한다.

J2SE 1.2와 1.3에서 GC는 단일 쓰레드이며, stop-the-world 형식이었다. 이 JVM의 가비지 컬렉션으로 인해 생기는 멈춤은 애플리케이션에 지연을 더해 처리량과 확장성으로 본 성능이 떨어지게 된다. 이런 아키텍처는 한대의 컴퓨터의 여러 CPU에서 확장하기 어럽게 만든다. 가비지 컬렉션이 여러 프로세서로 분산되지 않기 때문에, 매우 다중 쓰레드인 애플리케이션이 JVM에서 실행될 때, 애플리케이션 레벨의 작업을 이행하기에 충분한 프로세서를 갖고 있음에도 불구하고 JVM으로부터 컨트롤을 얻기 위해 기다리며, JVM은 다중 CPU 환경에서 많은 압력을 받는다. 다중 프로세서 시스템에서 단일 쓰레드 GC의 영향은 병렬 애플리케이션일수록 크게 나타난다. 만약 GC를 제외하고 애플리케이션이 완벽하게 확장한다고 가정하면, GC가 일어나는 동안 작업을 수행할 수 있는 다른 프로세서가 유휴 상태에 있게 됨으로써, GC가 확장성을 저해하는 원인(Bottleneck)이 되는 것이다.

이런 내용에서 Java를 Turbo-charge한다는 것은 많은 CPU 사용과 많은 메모리에 접근, 많은 동시 발생 소켓 연결의 처리를 의미한다.

J2SE 1.4는 새로운 기능과 버퍼 관리, 네트웍 확장성, 파일 I/O의 성능을 개선한 논블록킹 new I/O API를 구현했다.
- 새로운 네트웍 I/O 패키지는 접속할 때마다 쓰레드를 배정하는 방식을 제거함으로써 서버에 동시 접속할 수
  있는 수를 극적으로 증가시켰다.
- 새로운 파일 I/O는 읽기와 쓰기, 복사, 이동 작업에서 현재의 파일 I/O보다 2배 가까운 속도를 낸다.
  새 것은 file locking과 memory-mapped files, 동시 여러 읽기/쓰기 작업을 지원한다.


J2SE 1.4의 64비트의 JVM은 4G 이상 큰 heap을 가질 수 있을 뿐더러 300G 이상 가질 수도 있다. 만약 가비지의 생성과 배당하는 비율이 일정하다면 heap이 클수록 GC로 인한 멈춤은 더욱 빈도수가 낮아지지만 그 시간은 길어질 것이다.

애플리케이션의 효율성과 확장성을 정하는 중요한 요소는 ‘GC 순차 비용(GC sequential overhead)’이다. 이는 애플리케이션의 실행 시간 중에 애플리케이션이 멈춘 상태에서 JVM에서 GC가 실행되는 시간의 비율을 뜻한다. 다음과 같이 계산할 수 있다.

Avg. GC pause * Avg. GC frequency * 100 %.
‘GC 빈도수’는 주기적인 GC나 한 단위의 시간에 일어나는 GC수이다. ‘신세대’와 ‘구세대’의 평균 GC 멈춤과 빈도수가 매우 다르기 때문에 GC 순차 비용을 구하는 계산이 다른데, 즉 둘을 합해서 애플리케이션의 총 GC 순차 비용을 구할 수 있다. GC 순차 비용은 다음과 같이 계산될 수 있다.

Total GC time/Total wall clock run time.

총 GC 시간은 다음과 같이 계산할 수 있다.

Avg. GC pause * total no. of GCs

분명 적은 GC 순차 비용은 애플리케이션의 높은 처리량과 확장성을 의미한다.

J2SE 1.4.1은 JVM이 더 많은 CPU와 메모리를 사용할 수 있게 해, 애플리케이션의 성능과 확장성을 높일 수 있게 디자인한 두 개의 새 가비지 컬렉터를 소개했다. 이 컬렉터는 통신 업체의 도전과제를 충족할 수 있게 해준다.

- 최대의 처리량을 위해 시스템의 자원을 확장성과 최적화로 사용하기 위해, 시스템의 GC 순차 비용은 10% 이상
  될 수 없다.
- 클라이언트와 서버 간에 정해진 프로토콜에 정의된 지연에 부합되고 서버의 양호한 답변 시간을 보장하기
  위해서, 애플리케이션에서 발생되는 모든 GC 멈춤은 200ms 이상이 되면 안된다.


J2SE 1.4.1에서 소개한 두 개의 새로운 컬렉터는 Parallel Collector와 Concurrent mark-sweep(CMS) Collector이다.

-Parallel collector: Parallel collector는 신세대를 대상으로 구현되었다. 이는 다중 쓰레드이며, stop-the-world이다. 이 컬렉터는 다중 프로세서 컴퓨터에서 더 좋은 성능을 내도록 다중 쓰레드에서 GC를 할 수 있게 한다. 비록 모든 애플리케이션 쓰레드를 중지시키지만 시스템의 모든 CPU를 사용해 주어진 양의 GC를 더욱 빠르게 처리할 수 있다. 신세대 부분에서의 GC 멈춤을 많이 감소시킨다. 따라서 Parallel collector는 여러 CPU뿐만 아니라 더 많은 메모리에 애플리케이션이 확장할 수 있게 해준다.

-Concurrent mark-sweep(CMS) collector: CMS collector는 구세대를 대상으로 구현되었다. CMS collector는 애플리케이션과 함께 ‘mostly concurrently’하게 실행되어, 때로는 ‘mostly-concurrent garbage collector’로 불린다. GC 멈춤 시간을 짧게 하기 위해서 애플리케이션을 위해 사용할 처리 능력을 이용한다. CMS collection은 Initial mark와 Concurrent marking, Remark, Concurrent sweeping의 4단계로 나뉜다.

‘initial mark’와 ‘remark’ 단계는 CMS collector가 모든 애플리케이션 쓰레드를 일시적으로 중지시키는 stop-the-world 단계이다. initial mark 단계는 시스템의 ‘roots’에서 곧바로 접근가능하게 모든 객체를 기록한다. ‘concurrent marking’ 단계에서 모든 애플리케이션 쓰레드는 재시작되고 concurrent marking 단계가 초기화된다. ‘remark’ 단계에서 애플리케이션 쓰레드는 다시 중지되고 마지막 마킹을 끝낸다. ‘concurrent sweeping’ 단계에서 애플리케이션 쓰레드는 다시 시작되며 heap을 동시 스위핑하며 표기되지 않은 모든 객체는 회수된다. initial mark와 remark 단계는 상당히 짧다. 구세대의 크기가 1G라고 해도 200ms 이하의 시간이 걸린다. concurrent sweeping 단계는 mark-compact collector 만큼의 시간이 걸릴 것이지만 애플리케이션 쓰레드가 중지되지 않기 때문에, 멈춤이 숨겨져 있다.

 
‘mostly concurrent’인 CMS collector는 JVM이 큰 heap과 여러 CPU에 확장할 수 있게 해, 지연과 mark-compact stop-the-world collector로 발생한 처리량 문제를 해결한다.

표 1은 J2SE 1.4.1에 있는 여러 컬렉터의 기능을 비교해본다.
그림 1과 2, 3은 다른 여러 가비지 컬렉터를 그림으로 나타낸 것이다. 초록 화살표는 다중 CPU에서 실행되는 다중 쓰레드 애플리케이션을 뜻한다. 빨간 화살표는 GC 쓰레드를 뜻한다. GC 쓰레드의 길이는 GC 멈춤 시간을 대략적으로 나타낸다.

표 1.다른 여러 가비지 컬렉터의 기능 요약
신세대 컬렉터구세대 컬렉터
Copying collector collector
Default
Stop-the-
world
Single
threaded
All J2SEs
Mark-compact collector
Default
Stop-the-world
Single threaded
All J2SEs
Parallel collectorConcurrent mark-sweep collector
Stop-the-
world
Multi-
threaded
J2SE 1.4.1+
Mostly-concurrent
Single threaded
J2SE 1.4.1+

그림 1과 2, 3이 보여주듯, Parallel collector를 신세대에서 concurrent mark-sweep collector를 구세대에서 같이 사용하면 중지 시간과 GC 순차 비용을 줄일 수 있다. 이 두 컬렉터는 애플리케이션이 더 많은 프로세서와 메모리에 확장할 수 있도록 도와준다.

Posted by tornado
|

애플리케이션 서버의 성능과 확장성을 개선하기 위한 Java HotSpot VM vl.4.x Turbo-Charging (2)

 

 
 



J2SE 1.3의 Sun HotSpot JVM에 있는 heap 크기와 GC 튜닝에 잘 알려진 스위치들은 다음과 같다. 스위치에 대한 더 자세한 것은 JVM HotSpot VM options을 참조한다.

일반 스위치들
-server
-Xmx, -Xms
-XX:NewSize=, -XX:MaxNewSize=
-XX:SurvivorRatio=

J2SE 1.4.1에서 2개의 새로운 가비지 컬렉터를 활성화시키기 위한 스위치는 다음과 같다.


Parallel Collector
-XX:+UseParNewGC : 이 플래그는 신세대에 병렬 가비지 컬렉션을 실행시키게 한다. 구세대에 CMS collector와 함께 활성화할 수 있다.

 

-XX:ParallelGCThreads=n : 이 스위치는 JVM이 신세대 GC를 실행할 때 몇개의 병렬 GC 쓰레드를 사용할 것인지 지정해준다. 기본값은 시스템의 CPU 개수와 같다. 그렇지만 어떤 경우에는 이 수를 변경해 성능을 개선할 수 있다는 것을 관찰할 수 있다. 예를 들면 다중 CPU 컴퓨터에서 여러 JVM 인스턴트를 실행할 때이다. 이런 경우, 각 JVM이 사용하는 병렬 GC 쓰레드수를 이 스위치를 사용해 전체 CPU수보다 적게 지정해줘야 할 것이다.

-XX:+UseParallelGC : 이 플래그도 신세대에 병렬 가비지 컬렉션 정책을 사용하게 하지만, 이것은 구세대에서 CMS collector를 같이 사용하지 못한다. 이는 매우 큰 신세대 heap을 사용하는 기업환경용 애플리케이션에 적합하다.

Concurrent Collector
-XX:+UseConcMarkSweepGC : 이 플래그는 구세대에 동시 수행 가비지 컬렉션을 사용하게 한다.

-XX:CMSInitiatingOccupancyFraction=x : CMS collection을 동작시키는 구세대 heap의 threshold 비율을 지정한다. 예를 들어 60으로 지정하면, CMS collector는 구세대의 60%가 사용될 때마다 실행될 것이다. 이 threshold는 실행시 기본값으로 계산되며, 대개의 경우 구세대의 heap이 80~90%가 사용될 때만 CMS collector가 실행된다. 이 값은 튜닝을 통해 대부분의 경우 성능을 개선할 수 있다. CMS collector는 스위핑하고 메모리를 반환할 때 애플리케이션 쓰레드를 중지시키지 않기 때문에, 많은 메모리를 요구하는 애플리케이션의 경우 신세대로부터 넘어오는 객체를 위한 여유 메모리 공간을 최대한 확보할 수 있도록 해준다. 이 스위치가 최적화되지 않았을 때, CMS collection은 수행에 실패하는 경우가 생길 수도 있으며, 이 경우 기본인 stop-the-world mark-compact collector를 실행하게 된다.
다음과 같이 다른 스위치를 사용해 성능을 튜닝할 수도 있다.

-XX:MaxTenuringThreshold=y : 이 스위치는 신세대의 객체가 얼마만큼의 시간이 지나야 구세대로 승진되는지 지정할 수 있다. 기본값은 31이다. 꽤 큰 신세대와 ‘생존 공간’은 오래된 객체는 구세대로 승진되기까지 생존 공간 사이에 31번 복사가 이뤄진다. 대부분의 통신 애플리케이션에서는 호출 또는 세션마다 만들어진 80~90%의 객체는 생성 즉시 죽고, 10~20%정도가 호출이 끝날 때까지 생존한다. XX:MaxTenuringThreshold=0는 애플리케이션의 신세대에 배정한 모든 객체가 한번의 GC 주기를 생존하면 신세대의 생존 공간 사이에 복사되지 않고 곧바로 구세대로 옮겨진다. 구세대에서 CMS collector를 사용할 때, 이 스위치는 두 가지로 도움이 된다.

- 신세대 GC는 10~20%의 장기 객체를 생존 공간 사이에 여러 번 복사할 필요가 없고 곧바로 구세대로 승진시킨다.
- 추가적으로 이 객체는 구세대에서 대부분의 동시발생적인 컬렉션을 행할 수 있다. 이것은 GC 순차 비용을 추가적으로 감소시킨다.

이 스위치가 사용되면 -XX:SurvivorRatio를 128 정도의 매우 큰 값으로 정하길 권한다. 이는 생존 공간이 사용되지 않고, 매 GC 주기마다 신생에서 곧바로 구세대로 승진되기 때문이다. 생존 비율을 높임으로써 신세대의 heap은 대부분 신생에 배정된다.

-XX:TargetSurvivorRatio=z : 이 플래그는 객체가 구세대로 승진되기 전에 사용해야 하는 생존 공간 heap의 희망 비율이다. 예를 들어 z를 90으로 지정하면 생존 공간의 90%를 사용해야 신세대가 꽉 찼다고 생각해 객체가 구세대로 승진된다. 이것은 객체가 승진되기까지 신세대에서 더 오래 머무를 수 있게 한다. 기본값은 50이다.

 

JVM은 GC에 관련된 많은 유용한 정보를 파일에 로그할 수 있다. 이 정보는 애플리케이션과 JVM을 GC 시각에서 튜닝하고 측정하는 데 사용할 수 있다. 이 정보를 로그하기 위한 스위치는 다음과 같다.

verbose:gc : 이 플래그는 GC 정보를 기록하게 한다.
-Xloggc=filename : 이 스위치는 ‘verbose:gc’로 인한 정보를 표준 출력 대신 기록할 로그 파일명을 지정한다.
-XX:+PrintGCTimeStamps : 애플리케이션의 시작을 시점으로 GC가 언제 실행되었는지 출력한다.
-XX:+PrintGCDetails : GC에 대해 GC하기 전과 후의 신세대와 구세대의 크기, 총 heap 크기, 신세대와 구세대에서 GC가 실행되기까지의 시간, GC 주기마다 승진된 객체 크기와 같은 자세한 정보를 준다.
-XX:+PrintTenuringDistribution : 신세대에서 배정된 객체의 나이 분포를 알려준다. 앞서 설명한 -XX:NewSize, -XX:MaxNewSize, -XX:SurvivorRatio, -XX:MaxTenuringThreshold=0의 튜닝은 바로 이 스위치에서 나오는 데이터를 사용해서 객체가 구세대로 승진할 정도로 시간이 지났는지 판단하게 된다.

 

다음의 커맨드라인 스위치를 사용해 나타나는 GC 관련 출력을 살펴보자,

java -verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseParNewGC -XX:+UseConc MarkSweepGC -Xmx512m -Xms512m -XX:NewSize=24m -XX:MaxNew Size=24m -XX:SurvivorRatio=2 <app-name>
 
신세대 GC
311.649: [GC 311.65: [ParNew Desired survivor size 4194304 bytes, new threshold 3 (max 31)
- age 1: 1848472 bytes, 1848472 total
- age 2: 1796200 bytes, 3644672 total
- age 3: 1795664 bytes, 5440336 total
: 21647K->5312K(24576K), 0.1333032 secs] 377334K->362736K(516096K), 0.1334940 secs]
 
앞서의 GC 스냅샷은 신세대의 GC에 대한 설명이며, 이는 다음 정보를 제공한다.

- 이 GC를 실행할 때, 총 JVM heap은 516096K이며 신세대의 heap 크기는 24576K이다.
- 이 GC가 실행될 때, 구세대의 heap 크기는 516096K-24576K=491520K이다.
- 이 GC 주기의 마지막에 애플리케이션의 신세대에 배정받은 객체의 ‘나이 분포’를 설명한다. 이 GC 주기는 new threshold=3으로 설정돼 있으며, 이는 다음 GC 주기가 되기 전에 최대 3번 시간이 지나야 한다는 뜻이다.

- Desired survivor size가 4194304바이트라는 것은 SurvivorRatio=2를 통해 결정되었다. 이는 Eden/Survivor space=2로 지정한 것이다. 신세대의 heap 크기가 24MB이면, 24MB=2*Survivor space+Survivor space+Survivor space 또는 Survivor space=8MB를 뜻한다. 기본값인 TargetSurvivorRatio=50의 희망 생존 크기는 8*.5=4MB이다.
- 애플리케이션 시작을 시점으로 이 GC가 실행된 시간은 311.649초이다.
- 이 GC가 신세대에서 만든 멈춤 시간은 0.1334940초이다.
- GC를 하기 전에 사용중이던 신세대의 크기는 21647K이다.
- GC를 한 후 사용되는 신세대의 크기는 5312K이다.
- 신세대에서 377334K-362736K=14598K의 데이터가 GC되었다.
- 21647K-5312K-14598K=1737K가 구세대로 승진되었다.
- GC를 하기 전에 사용되고 있던 총 heap 크기는 377334K이었다.
- GC를 한 후에 사용되고 있는 총 heap 크기는 362736K이다.
- GC를 하기 전에 구세대에 사용중인 heap 크기는 377334K-21647K= 355687K이었다.
- GC를 한 후 구세대의 사용하는 heap 크기는 362736K-5312K= 357424K이다.
- 이 GC에 구세대로 승진한 객체의 총 크기는 357424K-355687K=1737K이다.
- ParNew 표기는 신세대에 Parallel collector를 사용했다는 것을 나타낸다. 기본인 Copying collector를 사용하면 ParNew 대신 DefNew로 표기한다.

verbose:gc 로그 형식에서 사용된 관례는 특정 세대에 대한 보고가 있다면 그 세대의 이름이 우선시되어 보고될 것이다.

[GC [gen1 info1] [gen2 info2] info]

info는 GC의 일반적인 것이고, 신세대와 구세대를 합한 것이다. info<n>는 gen<n>에 한정돼 있다. 그러므로 각 괄호의 배치를 잘 봐야 할 것이다.
 

구세대 GC

CMS Collector
구세대에서 concurrent mark-sweep collector를 사용하는 것을 스냅샷한 것이다. 앞서 언급했듯이 이 부분은 4단계로 나눠진다.

513.474: [GC [1 CMS-initial-mark: 335432K(491520K)] 340897K (516096K), 0.0482491secs]

- stop-the-world인 initial mark 단계는 0.048초 걸렸다.
- 애플리케이션 시작 시점에서 513.474초에 시작되었다.
- 사용중이었던 구세대의 heap 크기는 335432K이다.
- 신세대에서 사용하는 heap을 포함한 총 사용중인 heap 크기는 491520K이다.
- 구세대의 heap 총 크기는 340897K이다.
- 신세대 heap을 포함한 총 heap 크기는 516096K이다.
- 이 단계는 메모리를 재순환하지 않는다.
- ‘[GC’ 접두사는 stop-the-world 단계를 뜻한다.

513.523: [CMS-concurrent-mark-start]
514.337: [CMS-concurrent-mark: 0.814/0.814 secs]
514.337: [CMS-concurrent-preclean-start]
514.36: [CMS-concurrent-preclean: 0.023/0.023 secs]

이 concurrent mark 단계는 일초(0.814 + 0.023초)도 걸리지 않았다. 그러나 GC와 함께 애플리케이션도 동시에 실행되었다. 이 단계 또한 어떠한 가비지도 컬렉션되지 않았다.

514.361: [GC 514.361: [dirty card accumulation, 0.0072366 secs]
514.368: [dirty card rescan, 0.0037990 secs]
514.372: [remark from roots, 0.1471209 secs]
514.519: [weak refs processing, 0.0043200 secs] [1 CMS-remark: 335432K(491520K)] 352841K(516096K), 0.1629795 secs]

- stop-the-world인 remark 단계는 0.162초 걸렸다.
- 구세대에 사용중이던 heap 크기는 335432K이다.
- 구세대의 총 heap 크기는 491520K이다.
- 신세대를 포함한 총 사용중이던 heap 크기는 352841K이다.
- 신세대를 포함한 총 heap 크기는 516096K이다.
- 이 단계에서 어떠한 메모리도 재순환되지 않는다.
- 이미 말했듯이 ‘[GC’ 표기는 stop-the-world 단계라는 것을 알려준다.

514.525: [CMS-concurrent-sweep-start]
517.692: [CMS-concurrent-sweep: 2.905/3.167 secs]
517.693: [CMS-concurrent-reset-start]
517.766: [CMS-concurrent-reset: 0.073/0.073 secs]

이 concurrent sweep은 3초 걸렸다. 그러나 여러 프로세스가 있는 시스템에서 이 단계는 GC와 함께 애플리케이션 쓰레드가 실행될 수 있다. 4개의 CMS 단계에서 이 단계만이 heap을 스위핑하고 컬렉션한다.

Default Mark-Compact Collector
만약 CMS collector 대신에 구세대에서 기본인 mark-compact collector가 사용되었다면 GC 스냅샷은 다음과 같을 것이다.

719.2: [GC 719.2: [DefNew: 20607K->20607K(24576K), 0.0000341 secs]719.2: [Tenured: 471847K->92010K(491520K), 2.6654172 secs] 492454K->92010K(516096K), 2.6658030 secs]

- GC가 시작한 시작은 애플리케이션 시작 시점으로 719.2초였다.
- DefNew 표기는 신세대에서 기본인 copying collector가 사용되었음을 알려준다.
- ‘[GC’ 표기는 JVM이 stop-the-world GC를 사용했다는 것을 나타낸다. 구세대의 GC에서, 애플리케이션에서 System.gc()를 통해 시스템에 GC를 요청할 수 있는데, 이것은 ‘Full GC’로 표기된다.
- 신세대의 총 heap 크기는 24576K이다.
- 신세대에서 컬렉션은 단지 시도만 이뤄졌다. 구세대에서 잠재적인 데이터를 모두 흡수할 수 있다는 보장이 없어 컬렉션할 수 없다는 것을 알았기 때문이다. 그 결과 신세대의 컬렉션은 일어나지 않았고, 신세대에서는 어떠한 메모리 재순환도 이뤄지지 않았다고 보고했다(순간의 ms 내에 종료했다는 것을 주시한다). 이는 신세대에서 컬렉션이 이뤄지지 못한다는 것을 결정하는 것 외에는 별달리 한 것이 없기 때문이다.

[DefNew: 20607K->20607K(24576K), 0.0000341 secs]

신세대에서 승진되는 것을 흡수하지 못해 구세대가 꽉 찼기 때문에 GC가 필요하다는 알게 되었다. 그래서 full mark-compact collection이 실행되었다.

- 보유하고 있는 표기는 구세대에 full mark-compact GC가 실행되었다는 것을 나타낸다. ‘구세대’는 ‘Tenured generation’이라고도 한다.
- GC를 하기 전에 구세대의 사용중인 heap 크기는 471847K이었다.
- GC 후의 구세대의 사용 heap 크기는 92010K이다.
- GC가 실행될 때의 구세대의 총 heap 크기는 491520K이었다.
- GC 전에 신세대와 구세대의 합인 총 사용중인 heap 크기는 492454K이었다.
- GC 후에 신세대와 구세대의 합인 총 사용 heap 크기는 92010K이다.
- GC를 통해 컬렉션된 총 크기는 492454K-92010K=399837K이다.
- JVM의 총 heap 크기는 516096K이다.
- 이 GC로 총 2.6658030초동안 애플리케이션이 중지되었다.

 

verbose:gc 로그로부터 데이터 마이닝을 통해 애플리케이션
모델링하기


이 로그를 통해 가비지 컬렉션 시각에서 애플리케이션과 JVM 행동에 대한 다양한 정보가 파생될 수 있다. 이들은 다음과 같다.

- 신세대와 구세대에서의 평균 GC 멈춤 시간(Average GC pauses) : JVM에서 가비지 컬렉션이 이뤄지는 동안 애플리케이션이 중지되는 평균 시간이다.
- 신세대와 구세대에서의 평균 GC 빈도수(Average GC frequency) : 신세대와 구세대에서 실행된 가비지 컬렉터의 주기수이다. 이 값은 각 GC가 로그에 기록된 시간을 보면 구할 수 있다.
- GC 순차 비용(GC sequential overhead) : 가비지 컬렉션이 이뤄지면서 애플리케이션이 중지된 시스템 시간의 비율이다. Avg. GC pause*Avg. GC frequency*100%로 계산할 수 있다.
- GC concurrent overhead : 애플리케이션과 함께 가비지 컬렉션이 일어난 시스템 시간의 비율이다. Avg. concurrent GC time (sweeping phase) * Avg. concurrent GC frequency / no. of CPUs로 계산할 수 있다.
- 신세대와 구세대에서 각 GC에서 재순환된 메모리 : 각 GC에서 컬렉션된 총 가비지
- 할당 비율 : 애플리케이션의 신세대에서 데이터가 할당받는 비율을 말한다. 만약 heap이 occupancy_at_start_of_current_gc=x , occupancy_at_end_of_ previous_gc = y, GC 빈도수가 초당 1이면, 할당 비율은 약 초당 x-y이다.
- 승진 비율(Promotion rate) : 데이터가 구세대로 승진되는 비율. ‘신세대 GC’에서 설명한 것처럼 GC당 승진된 객체의 크기가 계산되었고, 예를 들어 신세대 GC 빈도수가 초당 1일 때 그 스냅샷에 의하면 승진 비율은 초당 1737K이다.
- 호출당 애플리케이션에 의해 할당된 총 데이터 : 이것은 앞서와 같이 할당 비율을 구하고, 애플리케이션 서버의 로드가 호출 비율이라고 할 때, 할당 비율/호출 비율(Allocation Rate/Call rate)로 계산될 수 있다. 즉 호출 비율은 요청 또는 오는 호출을 서버가 처리하는 비율을 말한다.
- 모든 데이터는 단기 데이터(short term)와 장기 데이터(long term data)를 나눌 수 있다 : 장기 데이터는 신세대 GC에서 생존해서 구세대로 승진한 것을 말한다. 승진 비율/호출 비율(Promotion Rate/Call Rate)로 계산할 수 있다.
- 호출당 단기 데이터(Short Term) : 단기 데이터는 매우 빨리 소멸하며, 신세대에서 컬렉션할 수 있다. 이것은 총 데이터-장기 데이터(Total Data-Long Term Data)로 계산할 수 있다.
- 호출당 총 활성화 데이터 : JVM heap 크기를 결정하는 데 매우 중요한 정보이다. 예를 들어 이 SIP 애플리케이션의 경우와 같이, 초당 100 호출의 로드에 초당 50K의 장기 데이터가 최소 40초 이상 지속되면 구세대의 최소 메모리 크기는 50K*40s*100=200M이어야 한다.
- ‘메모리 누출(Memory leaks)’ : 이것은 감지할 수 있으며 로그가 보여주는 각 GC를 모니터링에서 나오는 ‘out of memory’ 에러로 보다 쉽게 이해할 수 있을 것이다.

이런 종류의 정보는 GC의 시각에서 애플리케이션과 JVM 행동을 보다 잘 이해하고 가비지 컬렉션의 실행 성능을 튜닝하는 데 사용할 수 있다.

 

PrintGCStats(http://developer.java.sun.com/servlet/Turbo1EntryServlet에서 다운받을 수 있다)는 ‘verbose:gc’ 로그를 마이닝하는 쉘 스크립트이고 신세대와 구세대의 GC 중단 시간(총, 평균, 최대, 표준편차)의 통계를 낸다. 이는 GC 순차적 오버헤드, GC 동시발생 오버헤드, 데이터 배정, 승진 비율, 총 GC와 애플리케이션 시간과 같은 다른 중요한 GC 매개변수도 계산한다. 통계 외에도 PrintGCStats는 사용자가 설정한 주기에 맞춰 애플리케이션 실행에 대해 GC를 시간에 따라 분석한다.

 

입력
이 스크립트의 입력은 HotSpot VM이 다음의 하나 이상의 플래그를 사용했을 때 나오는 출력이다.

-verbose:gc : 최소의 출력을 생성한다. 제한적인 통계를 내지만, 모든 JVM에서 사용할 수 있다.
-XX:+PrintGCTimeStamps : 시간 단위의 통계를 활성화한다. 예를 들어 할당 비율과 주기 등이 있다.
-XX:+PrintGCDetails : 더 많은 통계 정보를 얻을 수 있다.
J2SE 1.4.1 이후부터 다음 명령어를 권한다.
java -verbose:gc -XX:+PrintGCTimeStamps -XX:+ PrintGCDetails ...

 

사용법
PrintGCStats -v ncpu=<n> [-v interval=<seconds>] [-v verbose=1] <gc_log_file >
·ncpu
Java 애플리케이션이 실행된 컴퓨터의 CPU 개수이다. 사용할 수 있는 CPU 시간과 GC ‘로드’ 요소를 계산하는 데 사용된다. 기본값이 없기 때문에 명령어에서 필히 지정해줘야 한다(기본값 1은 틀릴 때가 많다).

·interval
시간 단위 분석을 위해서 매 주기의 마지막에 통계를 출력한다 : -XX:+PrintGCTimeStamps의 출력이 필요하다. 기본값은 0(비활성화)

·verbose
만약 non-zero이면 통계 요약 후 추가적으로 각 아이템은 다른 줄에 출력한다.

출력 통계치
표 1은 PrintGCStats로부터의 통계치를 설명한 것이다.

 
표 1. PrintGCStats이 출력한 통계 요약
항목명 정의
  gen0(s) 신세대의 GC 시간을 초단위로 나타낸다.
  cmsIM(s) CMS의 initial mark 단계의 멈춤 시간을 초단위로 나타낸다.
  cmsRM(s) CMS의 remark 단계의 멈춤 시간을 초단위로 나타낸다.
  GC(s) 모든 stop-the-world GC의 멈춤 시간을 초단위로 나타낸다.
  cmsCM(s) CMS concurrent mark 단계를 초단위로 나타낸다.
  cmsCS(s) CMS concurrent sweep 단계를 초단위로 나타낸다.
  alloc(MB) 신세대에 객체 할당한 용량(MB)
  promo(MB) 구세대에 승진된 객체 용량(MB)
  elapsed_time(s) 애플리케이션이 실행되며 경과된 총 시간(초)
  tot_cpu_time(s) 총 CPU 시간 = CPU 개수 * 경과 시간
  mut_cpu_time(s) 애플리케이션이 사용한 총 CPU 시간.
  gc0_time(s) 신세대를 GC할 때 총 멈춰진 시간
  alloc/elapsed_time(MB/s) 경위 시간당 할당 비율(MB/초)
  alloc/tot_cpu_time(MB/s) 총 CPU 시간당 할당 비율(MB/초)
  alloc/mut_cpu_time(MB/s) 애플리케이션 총 시간당 할당 비율(MB/초)
  promo/gc0_time(MB/s) GC 시간당 승진 비율(MB/초)
  gc_seq_load(%) stop-the-world GC에 사용된 총 시간의 비율
  gc_conc_load(%) 동시발생적 GC에 사용된 총 시간의 비율
  gc_tot_load(%) GC 시간(순차적과 동시발생적)의 총 비율

- Solaris 8 이후로 사용할 수 있는 alternate thread library를 사용해라.
Solaris 8에서 LD_LIBRARY_PATH=/usr/lib/lwp:/usr/lib을 설정해서 사용할 수 있고, Solaris 9에서는 기본 라이브러리로 설정돼 있다. 이는 Java 쓰레드와 커널 쓰레드 사이에 ‘one-to-one threading model’을 사용한다. 이 라이브러리를 사용해서 대부분의 경우에 처리량이 5~10% 또는 그 이상으로 성능이 향상됐다.

- prstat -Lm -p <jvm process id>로 light-weight-process(LWP) 단위로 한 프로세스의 자원 사용을 분석하고 확장성과 성능 면에서의 병목 지점을 찾을 수 있게 한다. 예를 들어 애플리케이션의 확장성에 있어 병목 지점이 GC인지 확인하는 데 도움을 준다. 대부분의 경우, 애플리케이션이 자체적으로 제대로 쓰레드화하지 못해 큰 시스템으로 확장되지 못한다. 이 명령어는 시스템 자원 사용과 주어진 프로세스에서 LWP 단위의 활동을 보고한다. 더 자세한 사항은 man prstat에서 참조한다. 이 명령어의 결과 출력물의 단점은 보고된 LWP id가 어떤 Java 쓰레드인지 확인하기 어렵다는 것이다.

- ThreadAnalyser는 Java 프로세스를 분석하는 쉘 스크립트이고, Solaris의 LWPIDs 대신 prstat의 출력물에 있는 ‘쓰레드명’을 생성한다. Java 애플리케이션의 시스템 사용을 ‘쓰레드명’ 단위로 확인할 수 있게 해준다. 이 스크립트를 앞서 언급한 alternate thread library와 함께 사용하는 것이 가장 이상적이다.

ThreadAnalyser는 이미 실행돼 있는 Java 프로세스에 붙일 수 있거나(파일로 전환된 프로세스의 stderr 출력에 접근할 수 있는 한) Java 프로세스를 시동시킬 수 있는 스크립트 파일을 사용해 동작시킬 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 
사용 예
Java 프로세스를 시작하고 표준 에러를 java <app_name>>/ tmp/java.out 2>&1 으로 파일로 전환시킨다. Java 프로세스의 PID를 주의하고 다음과 같이 ThreadAnalyser(http://developer.java.sun.com/servlet/Turbo2EntryServlet에서 다운받을 수 있다.)을 붙인다.

ThreadAnalyser -f /tmp/java.out -p PID <interval>
또는
ThreadAnalyser -f /tmp/java.out <interval>


이 컴퓨터에 다른 Java 프로세스가 없으면 ps(1)을 사용해 PID를 구한다. ThreadAnalyser로 Java 프로세스를 실행하고자 한다면 다음과 같이 한다.

ThreadAnalyser -s <app_start_script_file> <interval>

이것은 이 시스템에 다른 Java 프로세스가 동작하고 있지 않을 때만 사용할 수 있다. app_start_script_file이 Java 프로세스을 실행하는 실행 스크립트 파일명이다. Interval은 스크립트로부터 출력되는 정보 갱신 주기이고, 기본값은 5이다.

보다 많은 옵션과 사용법은 ThreadAnalyser 스크립트의 README 부분을 읽어보기 바란다.
 

man prstat 명령어를 입력하면 다음과 같은 출력의 각 열이 의미하는 것을 자세히 알 수 있다.



- 실행되는 Java 애플리케이션의 모든 쓰레드의 덤프(dump)를 얻으려면, SIGQUIT 신호를 JVM 프로세스에 보낸다. 이는 kill - QUIT <JVM process pid> 명령어로 신호를 보낼 수 있다.
- JVM 프로세스 안의 각 LWP의 hex와 symbolic stack trace를 얻으려면 명령어 pstack <JVM process id>을 사용하면 된다. 이 출력은 LWP id와 Java 애플리케이션 쓰레드 id와의 관계를 알려준다. 만약 alternate thread library를 JVM에서 사용하면, 출력물은 프로세스의 각 LWP와 해당 묶여 있는 Java 애플리케이션 쓰레드의 일대일 관계를 보여준다. 명령어 man pstack을 입력하면 보다 자세한 것을 알 수 있다.
- JVM에 있는 -Xrunhprof 플래그를 사용해 불필요한 객체 보유(때로는 막연하게 ‘메모리 누수’라 부른다.)를 확인할 수 있다.
- UDP 기반의 애플리케이션에서는 DatagramSocket.connect (InetAddress address, int port)와 DatagramSocket.disconnect()의 호출 횟수를 최소화한다. J2SE 1.4 버전 이상의 JVM부터는 이것들이 네이티브한 호출이기 때문에, 여러 다른 이점을 가져다 주지만, 잘못된 사용은 애플리케이션의 성능 저하를 야기한다. API를 잘못 사용하면 애플리케이션의 애플리케이션 ‘시스템 시간’ 시점에서 매우 비싼 비용을 지불하게 된다.

 

다음에 보여줄 결과값은 2개의 새로운 GC 정책과 앞서 언급한 튜닝 기법을 사용해 얻은 것이다. 이 사례를 위해서 Ubiquity의 Application Services Broker(ASB)인 SIP Application Server가 사용되었다. 지난 호에 언급했듯이 ASB는 GC 시점에서 JVM을 광범위하게 사용하는 일반적인 통신 애플리케이션 서버를 대표한다.

 

실험적인 플랫폼
4개의 900MHz UltraSPARC Ⅲ 프로세서를 장착하고, Solaris 8이 설치된 Sun Fire V480을 사용했다. 물론 논의된 튜닝 기술들은 Linux와 Windows, Solaris(인텔 아키텍처)와 같은 다른 플랫폼에도 적용된다.
구세대 heap의 크기는 ASB 서버의 최대 로드에 기반해서 결정되었고, JVM은 튜닝이 적용되었다. 신세대 heap의 크기는 12MB에서 128MB 사이에 경험을 통해 계속적으로 변경해보았다. GC 순차적 오버헤드가 가장 낮고 신세대에 허용할 수 있는 GC 멈춤 정도를 갖는 최적의 크기는 24MB였다. 24MB 이상의 신세대는 GC 멈춤이 증가했고 성능의 차이가 없었다. 24MB 이하는 신세대의 GC 빈도수가 많아지면서 GC 순차적 오버헤드가 급증하고 짧은 생명주기를 갖은 데이터도 구세대로 승진되고 만다. 그림 1은 신세대의 heap 크기에 따른 다양한 GC 순차적 오버헤드와 GC 멈춤, GC 빈도수를 보여준다.

512MB의 구세대와 24MB의 신세대에서 여러 가지의 GC 정책과 튜닝 기술을 사용해 나온 결과값을 보여준다.

그림 1. 신세대의 다양한 GC 순차적 오버헤드와 GC 멈춤, GC 빈도수를 신세대의 heap 크기에 비례해서 보여준다.

단계 1 : J2SE 1.4.1에서 신세대와 구세대의 기본 GC를 사용할 때 가장 좋은 결과다.

java -Xmx512m -Xms512m -XX:MaxNewSize=24m -XX:NewSize=24m -XX:SurvivorRatio=2 <application>

구세대의 평균 GC pause: 3 secs.
신세대의 평균 GC pause: 110 ms.
GC sequential overhead: 18.9%

단계 2 : J2SE 1.4.1에서 구세대의 GC를 CMS 컬렉터로 설정하고 나온 가장 좋은 값이다.

java -Xmx512m -Xms512m -XX:MaxNewSize=24m -XX:NewSize=24m -XX:SurvivorRatio=128 -XX:+UseConcMarkSweepGC -XX:MaxTenuring Threshold=0 -XX:CMSInitiatingOccupancyFraction=60 <application>

-XX:MaxTenuringThreshold=0로 설정하면 성능이 증가한다. -XX:CMSInitiatingOccupancyFraction의 적절한 값은 60이다. 이보다 작으면 더 많은 CMS 가비지 컬렉션이 일어나고 많으면 CMS 컬렉션의 효율이 떨어진다.

구세대의 평균 GC 멈춤(stop-the-world init mark와 동시발생 컬렉션의 remark phase) : 115ms
신세대의 평균 GC 멈춤 : 100ms
GC 순차적 오버헤드 : 8.6%

단계 3 : J2SE 1.4.1에서 구세대는 CMS 컬렉터를 신세대는 새로운 패러럴 컬렉터를 사용할 때 가장 좋은 값이다.

java -Xmx512m -Xms512m -XX:MaxNewSize=24m -XX:NewSize=24m -XX:SurvivorRatio=128 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=0 -XX:CMSInitiatingOccupancyFraction=60 <application>

구세대의 평균 GC 멈춤(stop-the-world init mark와 동시발생 컬렉션의 remark phase) : 142ms
신세대의 평균 GC 멈춤 : 50ms
GC 순차적 오버헤드 : 5.9%

 
표 2. 사례를 통해 얻은 값을 정리
Summary Table 구세대의 평균
GC 멈춤(ms)
신세대의 평균
GC 멈춤(ms)
순차적
오버헤드(%)
  기본 컬렉터3000 110 18.9%
  CMS 컬렉터1151008.6%
  CMS와 패러럴 컬렉터142 50 5.9%
 

결과
CMS 컬렉터를 사용함으로써 구세대에서 GC 멈춤은 2000% 감소하고 GC 순차적 오버헤드는 220% 감소했다. GC 순차적 오버헤드의 감소는 애플리케이션 처리량의 성능 향상에 직접적으로 영향을 준다. 패러럴 컬렉터를 사용해 신세대에서 GC 멈춤은 100% 감소했다. 4개의 CPU가 장착된 시스템에서 4배 성능의 패러럴 컬렉터를 기대했을지도 모르겠다. 그러나 패러럴 컬렉션에 연관된 간접 비용에 의해, 가속도는 선형을 그리지 않지만 JVM이 선형을 그리도록 연구하고 있다.

기업에 적용
성능 튜닝 기술과 소개된 새로운 GC 정책은 통신업체에만 국한된 것이 아니고 웹 서버와 포털 서버, 애플리케이션 서버와 같이 비슷한 요구사항을 갖는 대기업용으로도 적용될 수 있다.

미래에는...
썬 JVM에서 하위 레벨의 명령어 플래그를 없애려고 연구중이다. 신세대와 구세대의 크기와 GC 정책 등을 설정하기보다는 최고 멈춤 시간 또는 지연, JVM의 메모리 사용량, CPU 사용법 등과 같은 상위 레벨의 요구사항을 설정하면 JVM이 자동적으로 그 설정에 맞게 애플리케이션을 위해 여러 하위 레벨의 값과 정책을 변경한다.

HotSpot JVM에 JVM의 조작 면에서 상위 레벨 단의 성능 정보를 제공할 수 있는 가볍고 항상 실행될 수 있는 것을 포함하려고 한다. 향후 출시되는 JVM에서 사용할 수 있겠지만, 현재로서는 툴과 기술은 아직 실험적이다.

Java Specification Request(JSR) 174는 JVM을 모니터링하고 관리하는 API 스펙이고, JSR 163은 시간과 메모리에 대한 프로파일링의 지원으로 실행되는 JVM의 프로파일링 정보를 추출해내는 API 스펙이다. 이 API는 프로파일에 영향을 주지 않는 구현이 되도록 설계될 것이다. 이 API는 프로파일링의 상호 운용성과 향상된 가비지 컬렉션 기술과 최대한 많은 JVM에서도 안정적으로 동작하는 구현을 고려할 것이고, Java 애플리케이션, 시스템 관리 툴, RAS 관련 툴에 JVM의 상태를 모니터할 수 있는 능력을 부여하며, 많은 런타임 컨트롤을 관리할 수 있게 할 것이다.

통신 애플리케이션 서버의 특수한 요구사항을 충족하기 위한 연구가 Sun ONE Application Server 7에서도 진행되고 있다. 통신업체의 경우 서비스와 데이터에 더 높은 가용성이 요구되는 것과 서비스 업그레이드를 제외하면 대기업과 유사하다. Sun ONE Application Server Enterprise Edition 7은 상태 확인 저장소와 세션, 빈 상태의 높은 유용성을 주는 Clustra ‘Always ON’ 기술이 포함돼 있다. Sun ONE Application server 7는 JAIN과 OSA/Parlay 프레임웍 기반의 호출 프로세싱 서버를 구축하는 데 사용할 수 있다. EJB 컨테이너 향상을 위해 SLEE 구현은 Sun ONE Application server에 통합될 수 있다. 추후 EJB를 사용할 때 지속적인 유용성과 어느 정도의 실시간, QoS 능력을 제공하기 위해 JSR 117 API 뿐만 아니라 통합된 SIP 스택도 포함될 수 있다.

통신 업체 사이에 Java의 인기가 증가하면서 썬 JVM은 그들의 요구 사항에 맞추어 발전해가고 있다. 대형 서버의 요구사항을 충족시키는 데 필요한 해결책으로 64비트 JVM과 대형 하드웨어에서(4G에 제한되지 않은) 대용량의 heap 크기를 설정할 수 있다. 여러 CPU 시스템에서 패러럴 컬렉터는 신세대에서 멈춤을 줄일 수 있고 동시발생 컬렉션은 구세대에 mark-compact collector로 인해 생긴 큰 GC 멈춤을 숨길 수 있다. 이로 인해 GC 순차적 오버헤드를 상당히 감소시켜서, 다수의 CPU가 장착된 시스템에서 애플리케이션의 성능과 확장성을 높여줄 수 있다.

JVM의 가비지 컬렉션은 verbose:gc의 로그를 보고 모니터하고, 분석하고 튜닝할 수 있다. GC 로그의 정보는 JVM의 크기를 조정하고 애플리케이션의 최적의 성능을 내기 위한 스위치와 매개변수를 설정하기 위해 분석된다.

머지않아 Sun ONE Application Server 7 Enterprise Edition은 최적의 통신 애플리케이션 서버로 구현될 것이다.

참고로 이글은 썬의 Alka Gupta 씨와 Ubiquity Software Corporation의 CTO인 Michael Doyle 씨가 쓴 ‘Turbo-Charging the Java HotSpot VM, 1.4.X to Improve the Performance and Scalability of Application Servers’를 정리한 것이다.

 

Posted by tornado
|
JAVA IDE
 Download News & Updates
 Support Contact Us
 FAQ Tutorials and Demos
 Search Member Services
  

MyEclipse XDoclet Web Development Tutorial

Eric Glass, Independent Consultant
karicg@worldnet.att.net

 

Overview

This tutorial provides a detailed example for using the XDoclet support within MyEclipse to speed web development through the use of attribute-oriented programming. Using XDoclet greatly reduces development time since it automates the generation of deployment descriptors and other support code. This tutorial is based on the concepts that are explained more fully in the IBM developerWorks article Enhance J2EE component reuse with XDoclets by Rick Hightower. If you're new to XDoclet, reading the developerWorks tutorial is highly recommended to provide you the background information needed to fully understand what is described here. Additionally, once you've read how to do it the "hard way" you'll really appreciate the integrated facilities provided by MyEclipse as we develop a sample web application composed of an HTML page, a servlet, a JSP, and a custom taglib.

   
   

Steps to Easy Web Development

This section will take you through the step-by-step process of web project creation, XDoclet configuration, deployment descriptor generation, and Tomcat 5 deployment.

1) Create a New Web Project

  1. Change to or open the MyEclipse Perspective (Window > Open Perspecitve > Other... > MyEclipse) .
  2. Select File > New... > Project > J2EE > Web Module Project, then press the Next button.
  3. For the project name use MyXDocletWeb and for the context root specify /MyXDocletWeb, as shown in Figure 1 below, and press the Finish button.  The resulting project structure will be the same as that shown in Figure 2.

    Creating the Web Project
    Figure 1. Creating the Web Project

    Web Project Layout
    Figure 2. Web Project Layout

2) Create a Servlet

  1. Select the project MyXDocletWeb in the Package Explorer
  2. Select File > New... > Servlet,
  3. Populate it with the package name com.myeclipse.tutorial.servlet and class name BasicServlet as shown in Figure 3, then press the Next button.

    Creating the Servlet
    Figure 3. Creating the Servlet

  4. When the second page of the servlet wizard is displayed, deselect the checkbox labeled Generate/Map web.xml File and select the finish button as shown in Figure 4.  We don't need to have the wizard map the web.xml file since we'll be generating it based on our XDoclet settings later.

    Creating the Servlet - Page 2
    Figure 4. Creating the Servlet - Page 2

  5. After the servlet is generated, it will be opened in the Java editor. Replace the generated source code completely with the following contents and save the file.

/*
 * BasicServlet.java
 * Created on Aug 7, 2003
 */
package com.myeclipse.tutorial.servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Basic Servlet Using XDoclet.
 *
 * @author Administrator
 * @version 1.0, Aug 7, 2003
 *
 * @web.servlet name = "BasicServlet"
 *              display-name = "Basic Servlet"
 *              load-on-startup = "1"
 * @web.servlet-init-param name = "hi"
 *                         value = "${basic.servlet.hi}"
 * @web.servlet-init-param name = "bye"
 *                         value = "${basic.servlet.bye}"
 * @web.servlet-mapping url-pattern = "/Basic/*"
 * @web.servlet-mapping url-pattern = "*.Basic"
 * @web.servlet-mapping url-pattern = "/BasicServlet"
 * @web.resource-ref description = "JDBC resource"
 *                   name = "jdbc/mydb"
 *                   type = "javax.sql.DataSource"
 *                   auth = "Container"
 */
public class BasicServlet extends HttpServlet {
    /**
     * Constructor of the object.
     */
    public BasicServlet() {
        super();
    }

    /**
     * Destruction of the servlet.
     */
    public void destroy() {
        super.destroy(); // Just puts "destroy" string in log
    }

    /**
     * The doGet method of the servlet.
     *
     * @param request the request send by the client to the server
     * @param response the response send by the server to the client
     *
     * @throws ServletException if an error occurred
     * @throws IOException if an error occurred
     */
    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
        processRequest(request, response);
    }

    /**
     * The doPost method of the servlet.
     *
     * @param request the request send by the client to the server
     * @param response the response send by the server to the client
     *
     * @throws ServletException if an error occurred
     * @throws IOException if an error occurred
     */
    public void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
        processRequest(request, response);
    }

    /**
     * Returns information about the servlet.
     *
     * @return String information about this servlet
     */
    public String getServletInfo() {
        return "Basic Servlet Using XDoclet";
    }

    /**
     * Initialization of the servlet.
     *
     * @throws ServletException if an error occurred
     */
    public void init() throws ServletException {
        // Put your code here
    }

    /**
     * Initialization of the servlet with the servlet's configuration.
     *
     * @param config the servlet's configuration
     *
     * @throws ServletException if an error occurred
     */
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
    }

    /**
     * Processes requests for both HTTP GET and POST methods.
     *
     * @param request servlet request
     * @param response servlet response
     *
     * @throws ServletException if an error occurred
     * @throws java.io.IOException if an I/O error occurred
     */
    protected void processRequest(HttpServletRequest request,
        HttpServletResponse response) throws ServletException, IOException {
        ServletConfig config = this.getServletConfig();
        String hi = config.getInitParameter("hi");
        String bye = config.getInitParameter("bye");

        try {
            response.setContentType("text/html");

            PrintWriter out = response.getWriter();
            out.println("<html>");
            out.println("<head>");
            out.println("<title>Basic Servlet</title>");
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>hi:  " + hi + "</h1>");
            out.println("<h1>bye:  " + bye + "</h1>");
            out.println("</body>");
            out.println("</html>");
            out.close();
        } catch (Exception e) {
            throw new ServletException(e);
        }
    }
}

Explanation of XDoclet Tags Used in the Servlet
The example tag illustrates the use of several helpful XDoclet tags. The tags and what they generate are displayed in the table below.
XDoclet TagGenerated Code
@web.servlet name = "BasicServlet"     display-name = "Basic Servlet"     load-on-startup = "1" Generated into the web.xml:

<servlet>
   <servlet-name>BasicServlet</servlet-name>
   <display-name>Basic Servlet</display-name>
   <servlet-class>com.myeclipse.tutorial.servlet.BasicServlet</servlet-class>
   ...
   <load-on-startup>1</load-on-startup>
</servlet>
@web.servlet-init-param name = "hi"
    value = "${basic.servlet.hi}"
@web.servlet-init-param name = "bye"
    value = "${basic.servlet.bye}"
Generated into the web.xml when Ant properties are set to: (basic.servlet.hi = Ant is cool!) and (basic.servlet.bye = XDoclet Rocks!):

<servlet>
   ...
   <init-param>
      <param-name>hi</param-name>
      <param-value>Ant is cool!</param-value>
   </init-param>
   <init-param>
      <param-name>bye</param-name>
      <param-value>XDoclet Rocks!</param-value>
   </init-param>
   ...
</servlet>
@web.servlet-mapping url-pattern = "/Basic/*"
@web.servlet-mapping url-pattern = "*.Basic"
@web.servlet-mapping url-pattern = "/BasicServlet"
Generated into web.xml:

<servlet-mapping>
   <servlet-name>BasicServlet</servlet-name>
   <url-pattern>/Basic/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
   <servlet-name>BasicServlet</servlet-name>
   <url-pattern>*.Basic</url-pattern>
</servlet-mapping>
<servlet-mapping>
   <servlet-name>BasicServlet</servlet-name>
   <url-pattern>/BasicServlet</url-pattern>
</servlet-mapping>
@web.resource-ref
    description = "JDBC resource"
    name = "jdbc/mydb"
    type = "javax.sql.DataSource"
    auth = "Container"
Generated into web.xml:

<resource-ref>
   <description>JDBC resource</description>
   <res-ref-name>jdbc/mydb</res-ref-name>
   <res-type>javax.sql.DataSource</res-type>
   <res-auth>Container</res-auth>
</resource-ref>

3) Create a Custom Tag Class

  1. Select the project MyXDocletWeb in the Package Explorer
  2. Select File > New... > Class,
  3. Populate it with the package name com.myeclipse.tutorial.customtag, class name BasicTag, and subclass javax.servlet.jsp.tagext.TagSupport as shown in Figure 5, then press the Finish button.

    Creating a Custom Tag
    Figure 5. Creating a Custom Tag

  4. After the tag is generated, it will be opened in the Java editor. Replace the generated source code completely with the following contents and save the file.

/*
 * BasicTag.java
 */
package com.myeclipse.tutorial.customtag;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;

/**
 * Basic Custom Tag Using XDoclet.
 *
 * @jsp.tag name="BasicTag"
 * @jsp.variable name-given="currentIter"
 *               class="java.lang.Integer"
 *               scope="NESTED"
 *               declare="true"
 * @jsp.variable name-given="atBegin"
 *               class="java.lang.Integer"
 *               scope="AT_BEGIN"
 *               declare="true"
 * @jsp.variable name-given="atEnd"
 *               class="java.lang.Integer"
 *               scope="AT_END"
 *               declare="true"
 *
 */
public class BasicTag extends TagSupport {
    /** Holds value of property includePage. */
    private boolean includePage = false;

    /** Holds value of property includeBody. */
    private boolean includeBody = false;

    /** Holds value of property iterate. */
    private int iterate = 0;

    /**
     * Creates a new BasicTag object.
     */
    public BasicTag() {
        super();
    }

    /**
     * @see javax.servlet.jsp.tagext.TagSupport#doStartTag()
     */
    public int doStartTag() throws JspException {
        pageContext.setAttribute("currentIter", new Integer(iterate));
        pageContext.setAttribute("atBegin", new Integer(0));

        return includeBody ? EVAL_BODY_INCLUDE : SKIP_BODY;
    }

    /**
     * @see javax.servlet.jsp.tagext.TagSupport#doEndTag()
     */
    public int doEndTag() throws JspException {
        pageContext.setAttribute("atEnd", new Integer(iterate));

        return includePage ? EVAL_PAGE : SKIP_PAGE;
    }

    /**
     * @see javax.servlet.jsp.tagext.TagSupport#doAfterBody()
     */
    public int doAfterBody() throws JspException {
        iterate -= 1;
        pageContext.setAttribute("currentIter", new Integer(iterate));

        if (iterate <= 0) {
            return SKIP_BODY;
        } else {
            return EVAL_BODY_AGAIN;
        }
    }

    /**
     * Getter for property includePage.
     *
     * @return Value of property includePage.
     *
     * @jsp.attribute required="true"
     *                rtexprvalue="true"
     *                description="The includePage attribute"
     */
    public boolean isIncludePage() {
        return includePage;
    }

    /**
     * Setter for property includePage.
     *
     * @param includePage New value of property includePage.
     */
    public void setIncludePage(boolean includePage) {
        this.includePage = includePage;
    }

    /**
     * Getter for property includeBody.
     *
     * @return Value of property includeBody.
     *
     * @jsp.attribute required="true"
     *                rtexprvalue="true"
     *                description="The includeBody attribute"
     */
    public boolean isIncludeBody() {
        return includeBody;
    }

    /**
     * Setter for property includeBody.
     *
     * @param includeBody New value of property includeBody.
     */
    public void setIncludeBody(boolean includeBody) {
        this.includeBody = includeBody;
    }

    /**
     * Getter for property iterate.
     *
     * @return Value of property iterate.
     *
     * @jsp.attribute required="true"
     *                rtexprvalue="true"
     *                description="The iterate attribute"
     */
    public int getIterate() {
        return iterate;
    }

    /**
     * Setter for property iterate.
     *
     * @param iterate New value of property iterate.
     */
    public void setIterate(int iterate) {
        this.iterate = iterate;
    }
}

Explanation of XDoclet Tags Used in the Custom Tag
The example tag  illustrates the use of several helpful XDoclet tags. The tags and what they generate are displayed in the table below.
XDoclet TagGenerated Code
@jsp.tag name="BasicTag" Generated into the taglib's .tld file:

<tag>
   <name>BasicTag</name>
   <tag-class>
       com.myeclipse.tutorial.customtag.BasicTag
   </tag-class>
   ...
</tag>
@jsp.variable 
    name-given="currentIter"
    class="java.lang.Integer"
    scope="NESTED"
    declare="true"
@jsp.variable 
    name-given="atBegin"
    class="java.lang.Integer"
    scope="AT_BEGIN"
    declare="true"
@jsp.variable 
    name-given="atEnd"
    class="java.lang.Integer"
    scope="AT_END"
    declare="true"
Generated into the taglib's .tld file:

<tag>
   ...
   <variable>
      <name-given>currentIter</name-given>
      <variable-class>java.lang.Integer</variable-class>
  <declare>true</declare>
      <scope>NESTED</scope>
   </variable>
   <variable>
      <name-given>atBegin</name-given>
      <variable-class>java.lang.Integer</variable-class>
  <declare>true</declare>
      <scope>AT_BEGIN</scope>
   </variable>
   <variable>
      <name-given>atEnd</name-given>
      <variable-class>java.lang.Integer</variable-class>
  <declare>true</declare>
      <scope>AT_END</scope>
   </variable>
   ...
</tag>
@jsp.attribute required="true"
    rtexprvalue="true"
    description="The includePage attribute"
@jsp.attribute required="true"
    rtexprvalue="true"
    description="The includeBody attribute"
@jsp.attribute required="true"
    rtexprvalue="true"
    description="The iterate attribute"
Generated into the taglib's .tld file:

<tag>
   ...
   <attribute>
      <name>includePage</name>
      <required>true</required>
      <rtexprvalue>true</rtexprvalue>
      <description><![CDATA[The includePage attribute]]></description>
   </attribute>
   <attribute>
      <name>includeBody</name>
      <required>true</required>
      <rtexprvalue>true</rtexprvalue>
      <description><![CDATA[The includeBody attribute]]></description>
   </attribute>
   <attribute>
      <name>iterate</name>
      <required>true</required>
      <rtexprvalue>true</rtexprvalue>
      <description><![CDATA[The iterate attribute]]></description>
   </attribute>
</tag>

4) Add a Folder Called 'merge' to the Project

  1. Select the project MyXDocletWeb in the Package Explorer
  2. Select File > New... > Folder,
  3. Populate it with the name merge, as shown in Figure 6, then press the Finish button.

    Creating the Merge Folder
    Figure 6. Creating the Merge Folder

5) Create the taglibs.xml file

  1. Select the folder MyXDocletWeb/merge in the Package Explorer
  2. Select File > New... > XML,
  3. Populate it with the name taglibs.xml, as shown in Figure 7, then press the Finish button.

    Creating the taglibs.xml file
    Figure 7. Creating the taglibs.xml file

Once taglibs.xml has been created and opened in the XML editor, completely replace the contents with the following:

<taglib>
   <taglib-uri>/mytaglib</taglib-uri>
   <taglib-location>/WEB-INF/mytaglib.tld</taglib-location>
</taglib>

6) Create the welcomefiles.xml file

  1. Select the folder MyXDocletWeb/merge in the Package Explorer
  2. Select File > New... > XML,
  3. Populate it with the name welcomefiles.xml then press the Finish button as you did for taglibs.xml.
Once welcomefiles.xml has been created and opened in the XML editor, completely replace the contents with the following:

<welcome-file-list>
   <welcome-file>index.jsp</welcome-file>
   <welcome-file>index.html</welcome-file>
</welcome-file-list>

7) Create the xdoclet-build.properties file

  1. Select the folder MyXDocletWeb in the Package Explorer
  2. Select File > New... > File,
  3. Populate it with the name xdoclet-build.properties, as shown in Figure 8, then press the Finish button.

    Creating the xdoclet-build.properties file
    Figure 8. Creating the xdoclet-build.properties file

    Once xdoclet-build.properties has been created and opened in the Text editor, completely replace the contents with the following:

basic.servlet.hi = MyEclipse Rocks!
basic.servlet.bye = Feel the power of XDoclet!

8) Configure the Project for XDoclet Usage

  1. Right-click on the folder MyXDocletWeb in the Package Explorer
  2. Select Properties > XDoclet Configurations
  3. Right-click and select Add Standard as shown in Figure 9.
  4. Select Standard Web from the resulting dialog box. The resulting configuration is shown in Figure 10.

    Adding a Standard XDoclet Configuration
    Figure 9. Adding a Standard XDoclet Configuration

    Adding Standard Web Configuration
    Figure 10. Adding Standard Web Configuration

  5. Select deploymentdescriptor under the webdoclet entry and select and set the following values as shown in the table below and in Figure 11.
    PropertyValue
    Servletspec2.3
    destDirWebRoot/WEB-INF
    displayNameXDoclet Web Tutorial
    mergeDirmerge

    Deployment Descriptor Settings
    Figure 11. Deployment Descriptor Settings

  6. Select jsptaglib under the webdoclet entry and select and set the following values as shown in the table below and in Figure 12.
    PropertyValue
    Jspversion1.2
    destDirWebRoot/WEB-INF
    destinationFilemytaglib.tld
    shortnamebasic

    JSP Taglib Settings
    Figure 12. JSP Taglib Settings

  7. Select the OK button at the bottom of the Properties dialog to save the XDoclet Configurations. This will generate the file xdoclet-build.xml into the project folder.

9) Setting XDoclet Ant Properties

  1. Right-click on the generated xdoclet-build.xml file in the Package Explorer and select Run Ant....
  2. On the dialg, titled MyXDocletWeb xdoclet-build.xml, select the Properties tab.
  3. Select the Add... button next to the Property files: list
  4. Browse to the project folder select xdoclet-build.properties.
  5. The resultant configuration will look like that in Figure 13.

    Adding Xdoclet Properties File
    Figure 13. Adding the Properties File

  6. Select the Refresh tab on the dialog and ensure that Refresh references after running tool is checked.

    Checking the Refresh Settings
    Figure 14. Checking the Refresh Settings

  7. Select the Apply button to save the changes.
  8. Select the Run button to process the XDoclet JavaDoc statements in the Java source with the Ant webdoclet task.
  9. After XDoclet runs, the files web.xml and mytaglib.tld will have been added to the WEB-INF directory as shown in Figure 15 below.

    Generated Files
    Figure 14. Generated Files

10) Adding a JSP Page

  1. Select the project MyXDocletWeb in the Package Explorer
  2. Right-click and select New... > JSP,
  3. Populate the wizard page with the file name TestJSP.jsp, as shown in Figure 15, then press the Finish button.

    Creating a JSP
    Figure 15. Creating a JSP

  4. After the JSP is generated, it will be opened in the JSP editor. Replace the generated source code completely with the following contents and save the file.

<%@ page language="java" %>
<%@ taglib uri="/mytaglib" prefix="mytag" %>

<html>
  <head>
    <title>I am a happy JSP page. Yeah!</title>
  </head>
  <body>
    <mytag:BasicTag includePage="true" includeBody="true" iterate="3">
      Current iteration is <%=currentIter%> <br/>
    </mytag:BasicTag>
  </body>
</html>

10) Adding an HTML Page

  1. Select the project MyXDocletWeb in the Package Explorer
  2. Right-click and select New... > HTML,
  3. Populate the wizard page with the file name index.html, as shown in Figure 16, then press the Finish button.

    Creating an HTML Page
    Figure 16. Creating an HTML Page

  4. After the HTML page is generated, it will be opened in the HTML editor. Replace the generated source code completely with the following contents and save the file.

<html>
  <head>
    <title>XDoclet Web Tutorial</title>
  </head>
  <body>
    <br />
    <blockquote>
      <h3>XDoclet Web Tutorial</h3>
      <ul>
        <li><a href="TestJSP.jsp">Test Basic JSP Custom Tag</a></li>
        <li><a href="BasicServlet">Test Basic Servlet</a></li>
      </ul>
    </blockquote>
  </body>
</html>

11) Verify Project

The project is now complete. To verify that the structure is complete, please compare your project to the one shown in Figure 17.

Final Project Structure
Figure 17. Final Project Structure

12) Deploy the Project and Test

  1. Right-click on the MyXDocletWeb project and select MyEclipse > Add and Remove Project Deployments as shown in Figure 18.

    Opening the Deployment Dialog
    Figure 18. Opening the Deployment Dialog

  2. Select the Add button as shown in Figure 19.

    Adding a Deployment
    Figure 19. Adding a Deployment

  3. Select whatever server you've got configured as an exploded archive as shown in Figure 20.

    Picking the Application Server
    Figure 20. Picking a Server

  4. Select the OK button as shown in Figure 21.

    Completing Deployment
    Figure 21. Completing Deployment

  5. Start the server as shown in Figure 22.

    Starting the Application Server
    Figure 22. Starting the Server

  6. Open a browser and test the application. The results are shown in Figure 23.

    Testing the Application 1 Testing the Application 2 Testing the Application 3
    Figure 23. Testing the Application

    Conclusion

    This has been an introduction to XDoclet use for web applications within MyEclipse. Although the example application was a simple one, it was a complete application that included an HTML page, a JSP page, an XDoclet-based Servlet, and and XDoclet-based Tag library. Now that you know how the basics work, get out there and start simplifying your web development with MyEclipse and XDoclet!
 
Genuitec Offers a broad range of consulting services including an Eclipse practice for plugin development, training and customization. Genuitec Eclipse services cover all aspects of the Eclipse framwork and include:
Genuitec Solutions and Services
  • Custom Plugin Development for Eclipse-Based Applications
  • Eclipse Development Courses and Workshops
  • Custom Widget Development and SWT/JFaces Extensions
  • Business Desktop Application or Suite Development
  • Productivity Tools Development and Branding
  • J2EE Consulting and Out-sourcing

 
MyEclipse mission is to deliveran affordable and a true end-to-end seamless development environment for Web, J2EE, XML, JSP, Struts, and application server integration.
MyEclipse Features
  • Web Development Tools
    • Smart editors - JSP, HTML, Struts, XML, CSS, and J2EE deployment descriptors
    • XML editing
    • Struts Support
    • XDoclet Support
  • Productivity Wizards
    • Creation of Web, EAR and EJB projects.
    • Java Project to Web Project enablements.
    • EJB Wizards
    • Automated deployment.
  • Application Server Integration
    • 20 application server connectors
    • Integrated controls
    • Full hot swap debugging
 
  
   
   

'JAVA > JSP_Servlet' 카테고리의 다른 글

[펌] apache.common.DbUtil & Oracle 용 RowProcess  (0) 2005.10.17
jetty site  (0) 2005.10.12
[펌] 웹개발문서모음 - iHelpers  (0) 2005.08.24
[openlaszlo] 아래에 푸터 붙어있을때...  (0) 2005.07.05
jwebunit  (0) 2005.06.03
Posted by tornado
|

A look inside the Glassbox Inspector with AspectJ and JMX

 

Level: Intermediate

Ron Bodkin (ron.bodkin@newaspects.com), Founder, New Aspects of Software

13 Sep 2005

Say goodbye to scattered and tangled monitoring code, as Ron Bodkin shows you how to combine AspectJ and JMX for a flexible, modular approach to performance monitoring. In this first of two parts, Ron uses source code and ideas from the Glassbox Inspector open source project to help you build a monitoring system that provides correlated information to identify specific problems, but with low enough overhead to be used in production environments.
About this series

The AOP@Work series is intended for developers who have some background in aspect-oriented programming and want to expand or deepen what they know. As with most developerWorks articles, the series is highly practical: you can expect to come away from every article with new knowledge that you can put immediately to use.

Each of the authors contributing to the series has been selected for his leadership or expertise in aspect-oriented programming. Many of the authors are contributors to the projects or tools covered in the series. Each article is subjected to a peer review to ensure the fairness and accuracy of the views expressed.

Please contact the authors individually with comments or questions about their articles. To comment on the series as a whole, you may contact series lead Nicholas Lesiecki. See Resources for more background on AOP.

Modern Java™ applications are typically complex, multithreaded, distributed systems that use many third-party components. On such systems, it is hard to detect (let alone isolate) the root causes of performance or reliability problems, especially in production. Traditional tools such as profilers can be useful for cases where a problem is easy to reproduce, but the overhead imposed by such tools makes them unrealistic to use in production or even load-test environments.

A common alternative strategy for monitoring and troubleshooting application performance and failures is to instrument performance-critical code with calls to record usage, timing, and errors. However, this approach requires scattering duplicate code in many places with much trial and error to determine what code needs to be measured. This approach is also difficult to maintain as the system changes and is hard to drill into. This makes application code challenging to add or modify later, precisely when performance requirements are better known. In short, system monitoring is a classic crosscutting concern and therefore suffers from any implementation that is not modular.

As you will learn in this two-part article, aspect-oriented programming (AOP) is a natural fit for solving the problems of system monitoring. AOP lets you define pointcuts that match the many join points where you want to monitor performance. You can then write advice that updates performance statistics, which can be invoked automatically whenever you enter or exit one of the join points.

In this half of the article, I'll show you how to use AspectJ and JMX to create a flexible, aspect-oriented monitoring infrastructure. The monitoring infrastructure I'll use is the core of the open source Glassbox Inspector monitoring framework (see Resources). It provides correlated information that helps you identify specific problems but with low enough overhead to be used in production environments. It lets you capture statistics such as total counts, total time, and worst-case performance for requests, and it will also let you drill down into that information for database calls within a request. And it does all of this within a modest-sized code base!

In this article and the next one, I'll build up from a simple Glassbox Inspector implementation and add functionality as I go along. Figure 1 should give you an idea of the system that will be the end result of this incremental development process. Note that the system is designed to monitor multiple Web applications simultaneously and provide correlated statistical results.


Figure 1. Glassbox Inspector with a JConsole JMX client
Glassbox Inspector monitoring multiple applications

Figure 2 is an overview of the architecture of the monitoring system. The aspects interact with one or more applications inside a container to capture performance data, which they surface using the JMX Remote standard. From an architectural standpoint, Glassbox Inspector is similar to many performance monitoring systems, although it is distinguished by having well-defined modules that implement the key monitoring functions.


Figure 2. The Glassbox Inspector architecture
Glassbox Inspector architecture

Java Management Extensions (JMX) is a standard API for managing Java applications by viewing attributes of managed objects. The JMX Remote standard extends JMX to allow external client processes to manage an application. JMX management is a standard feature in Java Enterprise containers. Several mature third-party JMX libraries and tools exist, and JMX support has been integrated into the core Java runtime with Java 5. Sun Microsystems's Java 5 VM includes the JConsole JMX client.

You should download the current versions of AspectJ, JMX, and JMX Remote, as well as the source packet for this article (see Resources for the technologies and Download for the code) before continuing. If you are using a Java 5 VM, then it has JMX integrated into it. Note that the source packet includes the complete, final code for the 1.0 alpha release of the open source Glassbox Inspector performance monitoring infrastructure.

The basic system

I'll start with a basic aspect-oriented performance monitoring system. This system captures the time and counts for different servlets processing incoming Web requests. Listing 1 shows a simple aspect that would capture this performance information:


Listing 1. An aspect for capturing time and counts of servlets
/** * Monitors performance timing and execution counts for  * <code>HttpServlet</code> operations */public aspect HttpServletMonitor {   /** Execution of any Servlet request methods. */  public pointcut monitoredOperation(Object operation) :     execution(void HttpServlet.do*(..)) && this(operation);   /** Advice that records statistics for each monitored operation. */  void around(Object operation) : monitoredOperation(operation) {      long start = getTime();       proceed(operation);       PerfStats stats = lookupStats(operation);      stats.recordExecution(getTime(), start);  }   /**   * Find the appropriate statistics collector object for this   * operation.   *    * @param operation   *            the instance of the operation being monitored   */  protected PerfStats lookupStats(Object operation) {      Class keyClass = operation.getClass();      synchronized(operations) {          stats = (PerfStats)operations.get(keyClass);          if (stats == null) {                              stats = perfStatsFactory.                createTopLevelOperationStats(HttpServlet.class,                        keyClass);              operations.put(keyClass, stats);          }      }      return stats;          }   /**   * Helper method to collect time in milliseconds. Could plug in   * nanotimer.   */  public long getTime() {      return System.currentTimeMillis();  }   public void setPerfStatsFactory(PerfStatsFactory     perfStatsFactory) {      this.perfStatsFactory = perfStatsFactory;  }   public PerfStatsFactory getPerfStatsFactory() {      return perfStatsFactory;  }   /** Track top-level operations. */   private Map/*<Class,PerfStats>*/ operations  =     new WeakIdentityHashMap();  private PerfStatsFactory perfStatsFactory;} /** * Holds summary performance statistics for a  * given topic of interest * (e.g., a subclass of Servlet). */ public interface PerfStats {  /**   * Record that a single execution occurred.   *    * @param start time in milliseconds   * @param end time in milliseconds   */  void recordExecution(long start, long end);   /**   * Reset these statistics back to zero. Useful to track statistics   * during an interval.   */  void reset();   /**   * @return total accumulated time in milliseconds from all   *         executions (since last reset).   */  int getAccumulatedTime();   /**   * @return the largest time for any single execution, in   *         milliseconds (since last reset).   */  int getMaxTime();   /**    * @return the number of executions recorded (since last reset).    */  int getCount();} /** * Implementation of the *  * @link PerfStats interface. */public class PerfStatsImpl implements PerfStats {  private int accumulatedTime=0L;  private int maxTime=0L;  private int count=0;   public void recordExecution(long start, long end) {      int time = (int)(getTime()-start);      accumulatedTime += time;      maxTime = Math.max(time, maxTime);      count++;  }   public void reset() {      accumulatedTime=0L;      maxTime=0L;      count=0;  }   int getAccumulatedTime() { return accumulatedTime; }  int getMaxTime() { return maxTime; }  int getCount() { return count; }} public interface PerfStatsFactory {    PerfStats       createTopLevelOperationStats(Object type, Object key);} 

As you can see, this first version is fairly basic. HttpServletMonitor defines a pointcut called monitoredOperation that matches the execution of any method on the HttpServlet interface whose name starts with do. These are typically doGet() and doPost(), but it also captures the less-often-used HTTP request options by matching doHead(), doDelete(), doOptions(), doPut(), and doTrace().

Managing overhead

I'll focus on techniques to manage the monitoring framework's overhead in the second half of the article, but for now, it's worth noting the basic strategy: I'll do some in-memory operations that take up to a few microseconds when something slow happens (like accessing a servlet or database). In practice, this adds negligible overhead to the end-to-end response time of most applications.

Whenever one of these operations executes, the system executes an around advice to monitor performance. The advice starts a stop watch, and then it lets the original request proceed. After this, it stops the stop watch and looks up a performance-statistics object that corresponds to the given operation. It then records that the operation was serviced in the elapsed time by invoking recordExecution() from the interface PerfStats. This simply updates the total time, the maximum time (if appropriate), and a count of executions of the given operation. Naturally, you could extend this approach to calculate additional statistics and to store individual data points where issues might arise.

I've used a hash map in the aspect to store the accumulated statistics for each type of operation handler, which is used during lookup. In this version, the operation handlers are all subclasses of HttpServlet, so the class of the servlet is used as the key. I've also used the term operation for Web requests, thus distinguishing them from the many other kinds of requests an application might make (e.g., database requests). In the second part of this article, I'll extend this approach to address the more common case of tracking operations based on the class or method used in a controller, such as an Apache Struts action class or a Spring multiaction controller method.



Back to top


Exposing performance data

Thread safety

The statistics-capturing code for the Glassbox Inspector monitoring system isn't thread safe. I prefer to maintain (potentially) slightly inaccurate statistics in the wake of rare simultaneous access to a PerfStats instance by multiple threads, rather than adding extra synchronization to program execution. If you prefer improved accuracy, you can simply make the mutators synchronized (for example, with an aspect). Synchronization would be important if you were tracking accumulated times more than 32 bits long, since the Java platform doesn't guarantee atomic updates to 64-bit data. However, with millisecond precision, this would give you 46 days of accumulated time. I recommend aggregating and resetting statistics far more frequently for any real use, so I've stuck with the int values.

Once you've captured performance data, you have a wide variety of options for how to make it available. The easiest way is to write the information to a log file periodically. You could also load the information into a database for analysis. Rather than add the latency, complexity, and overhead of summarizing, logging, and processing information, it is often better to provide direct access to live system performance data. I'll show you how to do this in this next section.

I want a standard protocol that existing management tools can display and track, so I'll use the JMX API to share performance statistics. Using JMX means that each of the performance-statistics instances will be exposed as a management bean, thus yielding detailed performance data. Standard JMX clients like Sun Microsystems's JConsole will also be able to show the information. See Resources to learn more about JMX.


Figure 3 is a screenshot of the JConsole showing data from the Glassbox Inspector monitoring the performance of the Duke's Bookstore sample application (see Resources). Listing 2 shows the code that implements this feature.


Figure 3. Using Glassbox Inspector to view operation statistics
Using Glassbox Inspector to view operation statistics

Traditionally, supporting JMX involves implementing patterns with boilerplate code. In this case, I'll combine JMX with AspectJ, which enables me to write the management logic separately.


Listing 2. Implementing the JMX management feature
/** Reusable aspect that automatically registers *  beans for management */public aspect JmxManagement {    /** Defines classes to be managed and      *  defines basic management operation     */    public interface ManagedBean {        /** Define a JMX operation name for this bean.          *  Not to be confused with a Web request operation.         */        String getOperationName();        /** Returns the underlying JMX MBean that          *  provides management         *  information for this bean (POJO).         */        Object getMBean();    }     /** After constructing an instance of      *  <code>ManagedBean</code>, register it     */    after() returning (ManagedBean bean):       call(ManagedBean+.new(..)) {        String keyName = bean.getOperationName();        ObjectName objectName =           new             ObjectName("glassbox.inspector:" + keyName);        Object mBean = bean.getMBean();        if (mBean != null) {            server.registerMBean(mBean, objectName);        }    }    /**      * Utility method to encode a JMX key name,      *  escaping illegal characters.     * @param jmxName unescaped string buffer of form      * JMX keyname=key      * @param attrPos position of key in String     */     public static StringBuffer       jmxEncode(StringBuffer jmxName, int attrPos) {        for (int i=attrPos; i<jmxName.length(); i++) {            if (jmxName.charAt(i)==',' ) {                jmxName.setCharAt(i, ';');            } else if (jmxName.charAt(i)=='?'                 || jmxName.charAt(i)=='*' ||                 jmxName.charAt(i)=='\\' ) {                jmxName.insert(i, '\\');                i++;            } else if (jmxName.charAt(i)=='\n') {                jmxName.insert(i, '\\');                i++;                jmxName.setCharAt(i, 'n');            }        }        return jmxName;    }    /** Defines the MBeanServer with which beans     *   are auto-registered.     */    private MBeanServer server;    public void setMBeanServer(MBeanServer server) {        this.server = server;    }    public MBeanServer getMBeanServer() {        return server;    }}

JMX tools

Several good JMX implementation libraries support remote JMX. Sun Microsystems provides reference implementations of JMX and JMX Remote under a free license. Some open source implementations also exist. MX4J is a popular one that includes helper libraries and tools like a JMX client. Java 5 integrates JMX and JMX remote support into the virtual machine. Java 5 also introduced management beans for VM performance in the javax.management package. Sun's Java 5 virtual machines include the standard JMX client JConsole.

You can see that this first aspect is reusable. With it, I can automatically register an object instance for any class that implements an interface, ManagedBean, using an after advice. This is similar to the AspectJ Marker Interface idiom (see Resources) in that it defines the classes whose instances should be exposed through JMX. However, unlike a true marker interface, this one also defines two methods.

The aspect provides a setter to define which MBean server should be used for managing objects. This is an example of using the Inversion of Control (IOC) pattern for configuration and is a natural fit with aspects. In the full listing of the final code, you'll see I've used a simple helper aspect to configure the system. In a larger system, I would use an IOC container like the Spring framework to configure classes and aspects. See Resources for more about IOC and the Spring framework and for a good introduction to using Spring to configure aspects.


Listing 3. Exposing beans for JMX management
/** Applies JMX management to performance statistics beans. */public aspect StatsJmxManagement {    /** Management interface for performance statistics.      *  A subset of @link PerfStats     */    public interface PerfStatsMBean extends ManagedBean {        int getAccumulatedTime();        int getMaxTime();        int getCount();        void reset();    }        /**      * Make the @link PerfStats interface      * implement @link PerfStatsMBean,      * so all instances can be managed      */    declare parents: PerfStats implements PerfStatsMBean;    /** Creates a JMX MBean to represent this PerfStats instance. */     public DynamicMBean PerfStats.getMBean() {        try {            RequiredModelMBean mBean = new RequiredModelMBean();            mBean.setModelMBeanInfo              (assembler.getMBeanInfo(this, getOperationName()));            mBean.setManagedResource(this,               "ObjectReference");            return mBean;        } catch (Exception e) {            /* This is safe because @link ErrorHandling            *  will resolve it. This is described later!            */            throw new               AspectConfigurationException("can't                 register bean ", e);        }    }    /** Determine JMX operation name for this     *  performance statistics bean.      */        public String PerfStats.getOperationName() {                StringBuffer keyStr =           new StringBuffer("operation=\"");        int pos = keyStr.length();        if (key instanceof Class) {            keyStr.append(((Class)key).getName());        } else {            keyStr.append(key.toString());        }                JmxManagement.jmxEncode(keyStr, pos);                keyStr.append("\"");        return keyStr.toString();    }    private static Class[] managedInterfaces =       { PerfStatsMBean.class };    /**      * Spring JMX utility MBean Info Assembler.      * Allows @link PerfStatsMBean to serve      * as the management interface of all performance      * statistics implementors.      */    static InterfaceBasedMBeanInfoAssembler assembler;    static {        assembler = new InterfaceBasedMBeanInfoAssembler();        assembler.setManagedInterfaces(managedInterfaces);    }}

Listing 3 contains the StatsJmxManagement aspect, which defines concretely which objects should expose management beans. It outlines an interface, PerfStatsMBean, that defines the management interface for any performance-statistics implementation. This consists of the statistics values for counts, total time, maximum time, and the reset operation, which is a subset of the PerfStats interface.

PerfStatsMBean itself extends ManagedBean, so that anything that implements it will automatically be registered for management by the JmxManagement aspect. I've used the AspectJ declare parents form to make the PerfStats interface extend a special management interface, PerfStatsMBean. As a result, the JMX Dynamic MBean approach manages these objects, which I prefer to using JMX Standard MBeans.

Using Standard MBeans would require me to define a management interface with a name based on each implementation class of performance statistics, such as PerfStatsImplMBean. Later, when I add subclasses of PerfStats to the Glassbox Inspector, the situation would get worse because I would be required to create corresponding interfaces such as OperationPerfStatsImpl. The standard MBeans convention would make the interfaces depend on the implementations and represents a needless duplication of the inheritance hierarchy for this system.

Deploying these aspects

The aspects used in this article need to be applied only to each application they are monitoring, not to third-party libraries or container code. As such, you could integrate them into a production system by compiling them into an application, by weaving into an already compiled application, or by using load-time weaving, which I prefer for this use case. You'll learn more about load-time weaving in the second half of this article.

The rest of the aspect is responsible for creating the right MBean and object name with JMX. I reuse a JMX utility from the Spring framework, the InterfaceBasedMBeanInfoAssembler, which makes it easier to create a JMX DynamicMBean that manages my PerfStats instance using the PerfStatsMBean interface. At this stage, I've exposed only PerfStats implementations. This aspect also defines helper methods using inter-type declarations on managed bean classes. If a subclass of one of these classes needed to override the default behavior, it could do so by overriding the method.

You may be wondering why I've used an aspect for management rather than simply adding support directly into the PerfStatsImpl implementation class. While adding management to this one class wouldn't scatter my code, it would entangle the performance-monitoring system's implementation with JMX. So, if I wanted to use the system in an application without JMX, I would be forced to include the libraries and somehow disable the service. Moreover, as I expand the system's management functionality, I expect to expose more classes for management with JMX. Using aspects keeps the system's management policy modular.



Back to top


Database request monitoring

Distributed calls are a common source of slow application performance and errors. Most Web-based applications do a significant amount of database work, making monitoring queries and other database requests an especially important area for performance monitoring. Common issues include ill-written queries, missing indexes, and excessive numbers of database requests per operation. In this section, I'll expand the monitoring system to track database activity, correlated with operations.

Distributed calls

In this section, I present an approach to handling distributed calls to a database. While databases are typically hosted on a different machine, my technique also works for a local database. My approach also extends naturally to other distributed resources, including remote object invocations. In the second part of the article, I'll show you how to apply this technique to Web services invocations using SOAP.

To start with, I'll monitor database connection times and the execution of database statements. To support this effectively, I need to generalize my performance monitoring information and allow for tracking performance nested within an operation. I'll want to extract the common elements of performance into an abstract base class. Each base class is responsible for tracking performance before and after certain operations and will need to update system-wide performance statistics for that information. This lets me track nested servlet requests and will be important to support tracking controllers in Web application frameworks (discussed in Part 2).

Because I want to update database performance by request, I'll use a composite pattern to track statistics held by other statistics. This way, statistics for operations (such as servlets) hold performance statistics for each database. The database statistics hold information about connection times and aggregate additional statistics for each individual statement. Figure 4 shows how the overall design fits together. Listing 4 has the new base monitoring aspect that supports monitoring different requests.


Figure 4. Generalized monitoring design
Revised monitoring design

Listing 4. The base monitoring aspect
/** Base aspect for monitoring functionality.  *  Uses the worker object pattern. */public abstract aspect AbstractRequestMonitor {    /** Matches execution of the worker object     *  for a monitored request.     */    public pointcut       requestExecution(RequestContext requestContext) :        execution(* RequestContext.execute(..))           && this(requestContext);        /** In the control flow of a monitored request,     *  i.e., of the execution of a worker object.      */    public pointcut inRequest(RequestContext requestContext) :        cflow(requestExecution(requestContext));    /** establish parent relationships     *  for request context objects.     */     // use of call is cleaner since constructors are called    // once but executed many times    after(RequestContext parentContext)       returning (RequestContext childContext) :       call(RequestContext+.new(..)) &&         inRequest(parentContext) {        childContext.setParent(parentContext);    }    public long getTime() {        return System.currentTimeMillis();    }    /** Worker object that holds context information     *  for a monitored request.     */    public abstract class RequestContext {        /** Containing request context, if any.          *  Maintained by @link AbstractRequestMonitor         */        protected RequestContext parent = null;                /** Associated performance statistics.          *  Used to cache results of @link #lookupStats()         */        protected PerfStats stats;        /** Start time for monitored request. */         protected long startTime;        /**          * Record execution and elapsed time          * for each monitored request.         * Relies on @link #doExecute() to proceed         * with original request.          */        public final Object execute() {            startTime = getTime();                        Object result = doExecute();                        PerfStats stats = getStats();            if (stats != null) {                stats.recordExecution(startTime, getTime());            }                        return result;        }                /** template method: proceed with original request */        public abstract Object doExecute();        /** template method: determines appropriate performance         *  statistics for this request          */        protected abstract PerfStats lookupStats();                /** returns performance statistics for this method */         public PerfStats getStats() {            if (stats == null) {                stats = lookupStats(); // get from cache if available            }            return stats;        }        public RequestContext getParent() {            return parent;                    }                public void setParent(RequestContext parent) {            this.parent = parent;                    }    }}

As you might expect, I had a number of choices for how to store the shared performance statistics and per-request state for the base monitoring aspect. For example, I could have used a singleton with lower-level mechanisms like a ThreadLocal holding a stack of statistics and context. Instead, I chose to use the Worker Object pattern (see Resources), which allows for a more modular, concise expression. While this imposes a bit of extra overhead, the additional time required to allocate a single object and execute the advice is generally negligible compared to the time servicing Web and database requests. In other words, I can do some processing work in the monitoring code without adding overhead because it runs only relatively infrequently and generally is dwarfed by the time spent sending information over networks and waiting for disk I/O. This would be a bad design for a profiler, where you would want to track data about many operations (and methods) per request. However, I'm summarizing statistics about requests, so the choice is a reasonable one.

In the above base aspect, I've stored transient state about the currently monitored request in an anonymous inner class. This worker object is used to wrap any execution of monitored requests. The worker object, a RequestContext, is defined in the base class and provides a final execute method that defines the flow for monitoring any request. The execute method delegates to an abstract template method, doExecute(), which is responsible for proceeding with the original join point. The doExecute() method also acts as a natural point to set up statistics before proceeding with the monitored join point based on context information, such as the data source being connected to, and to associate returned values such as database connections after returning from the join point.

Each monitor aspect is also responsible for providing an implementation of an abstract method, lookupStats(), to determine what statistics object is updated for a given request. lookupStats() needs to access information based on the monitored join point. In general, the context captured must vary in each monitoring aspect. For example, in HttpServletMonitor, the context needed is the class of the currently executing operation object. For a JDBC connection, the context needed is the data source being acquired. Because the requirements will differ according to context, the advice to set up the worker object is best contained in each sub-aspect rather than in the abstract base aspect. This arrangement is cleaner, it allows type checking, and it performs better than writing one piece of advice in the base class that passes the JoinPoint to all the children.



Back to top


Servlet request tracking

The AbstractRequestMonitor does contain one concrete after advice to track the parent contexts of request contexts. This allows me to associate the operation statistics for nested requests with the statistics of their parents (for example, what servlet request caused this database access). For my monitoring example system, I explicitly do want nested worker objects and do not limit myself to handling just top-level requests. For example, all the Duke's Bookstore Servlets call a BannerServlet as part of rendering a page. It is useful that I can break out the times for these calls separately, as Listing 5 shows. I don't show the supporting code for looking up nested statistics in an operation statistics here (you can see it in the article source code). In part two, I'll revisit this topic to show how I update the JMX support to display nested statistics like this.


Listing 5. Updated servlet monitoring
Listing 5 should now readpublic aspect HttpServletMonitor extends AbstractRequestMonitor {    /** Monitor Servlet requests using the worker object pattern */  Object around(final Object operation) :     monitoredOperation(operation) {      RequestContext requestContext = new RequestContext() {          public Object doExecute() {              return proceed(operation);          }                    public PerfStats lookupStats() {                              if (getParent() != null) {                  // nested operation                  OperationStats parentStats =(OperationStats)getParent().getStats();                  returnparentStats.getOperationStats(operation.getClass());              }              return lookupStats(operation.getClass());          }        };        return requestContext.execute();    } ... 

Listing 5 shows the revised monitoring advice for servlet request tracking. All the rest of the code remains the same as in Listing 1: it is either pulled up into the base AbstractRequestMonitor aspect or remains the same.



Back to top


JDBC monitoring

Having set up my performance monitoring framework, I am now ready to track database connection times and the time for database statements. Moreover, I want to be able to correlate database statements with the actual database I have connected to (in the lookupStats() method). To do this, I create two aspects to track information about JDBC statements and connections: JdbcConnectionMonitor and JdbcStatementMonitor.

One of the key responsibilities for these aspects is to follow chains of object references. I would like to track requests by the URL I used to connect to the database, or at least the database name. This requires me to track the data source used to acquire a connection. I would further like to track performance of prepared statements by the SQL strings that was prepared prior to executing to them. Finally, I need to track the JDBC connection associated with statements that are executing. You'll note that JDBC statements do provide an accessor for their connection; however, application servers and Web application frameworks frequently use the decorator pattern to wrap JDBC connections. I want to make sure I can correlate statements with the connection I have a handle to and not a wrapped connection.

The JdbcConnectionMonitor is responsible for measuring performance statistics for connections to the database, and it also associates connections with their metadata (for example, a JDBC URL or a database name) from data sources or connection URLs. The JdbcStatementMonitor is responsible for measuring performance statistics for executing statements, tracks the connections used to acquire statements, and tracks the SQL strings associated with prepared (and callable) statements. Listing 6 shows the JdbcConnectionMonitor aspect.


Listing 6. The JdbcConnectionMonitor aspect
/**  * Monitor performance for JDBC connections,  * and track database connection information associated with them.  */ public aspect JdbcConnectionMonitor extends AbstractRequestMonitor {        /** A call to establish a connection using a      *  <code>DataSource</code>      */    public pointcut dataSourceConnectionCall(DataSource dataSource) :         call(Connection+ DataSource.getConnection(..))           && target(dataSource);    /** A call to establish a connection using a URL string */    public pointcut directConnectionCall(String url) :        (call(Connection+ Driver.connect(..))  || call(Connection+           DriverManager.getConnection(..))) &&         args(url, ..);    /** A database connection call nested beneath another one     *  (common with proxies).     */        public pointcut nestedConnectionCall() :         cflowbelow(dataSourceConnectionCall(*) ||           directConnectionCall(*));        /** Monitor data source connections using     *  the worker object pattern      */    Connection around(final DataSource dataSource) :       dataSourceConnectionCall(dataSource)         && !nestedConnectionCall() {        RequestContext requestContext =           new ConnectionRequestContext() {            public Object doExecute() {                                accessingConnection(dataSource);                 // set up stats early in case needed                Connection connection = proceed(dataSource);                return addConnection(connection);            }                    };        return (Connection)requestContext.execute();    }    /** Monitor url connections using the worker object pattern */    Connection around(final String url) : directConnectionCall(url)       && !nestedConnectionCall() {        RequestContext requestContext =           new ConnectionRequestContext() {            public Object doExecute() {                accessingConnection(url);                Connection connection = proceed(url);                                return addConnection(connection);            }        };        return (Connection)requestContext.execute();    }    /** Get stored name associated with this data source. */     public String getDatabaseName(Connection connection) {        synchronized (connections) {            return (String)connections.get(connection);        }    }    /** Use common accessors to return meaningful name     *  for the resource accessed by this data source.     */    public String getNameForDataSource(DataSource ds) {        // methods used to get names are listed in descending        // preference order         String possibleNames[] =           { "getDatabaseName",               "getDatabasename",               "getUrl", "getURL",               "getDataSourceName",               "getDescription" };        String name = null;        for (int i=0; name == null &&           i<possibleNames.length; i++) {            try {                            Method method =                   ds.getClass().getMethod(possibleNames[i], null);                name = (String)method.invoke(ds, null);            } catch (Exception e) {                // keep trying            }        }        return (name != null) ? name : "unknown";    }        /** Holds JDBC connection-specific context information:     *  a database name and statistics     */    protected abstract class ConnectionRequestContext      extends RequestContext {        private ResourceStats dbStats;                /** set up context statistics for accessing         *  this data source         */         protected void           accessingConnection(final DataSource dataSource) {            addConnection(getNameForDataSource(dataSource),               connection);        }                /** set up context statistics for accessing this database */         protected void accessingConnection(String databaseName) {            this.databaseName = databaseName;            // might be null if there is database access            // caused from a request I'm not tracking...            if (getParent() != null) {                OperationStats opStats =                   (OperationStats)getParent().getStats();                dbStats = opStats.getDatabaseStats(databaseName);                            }        }        /** record the database name for this database connection */         protected Connection           addConnection(final Connection connection) {            synchronized(connections) {                connections.put(connection, databaseName);            }            return connection;        }        protected PerfStats lookupStats() {            return dbStats;        }    };        /** Associates connections with their database names */        private Map/*<Connection,String>*/ connections =       new WeakIdentityHashMap();}

Listing 6 shows my aspect for tracking database connections using AspectJ and the JDBC API. It has a map to associate database names with each JDBC connection.

Inside jdbcConnectionMonitor

Inside the JdbcConnectionMonitor shown in Listing 6, I've defined pointcuts to capture two different ways of connecting to a database: through a data source or directly through a JDBC URL. The connection monitor contains monitoring advice for each case, both of which set up a worker object. The doExecute() methods start by proceeding with the original connection and then pass the returned connection to one of two helper methods named addConnection. In both cases, the pointcut being advised excludes connection calls that result from another connection (for example, if connecting to a data source results in establishing a JDBC connection).

The addConnection() for data sources delegates to a helper method, getNameForDataSource(), to try to determine the name of a database from the data source. The DataSource interface doesn't provide any such mechanism, but almost every implementation provides a getDatabaseName() method. getNameForDataSource() uses reflection to try this and a few other common (and less common) methods that provide a useful identifier for a data source. This addConnection() method then delegates to the addConnection() method that takes a string for a name.

The delegated addConnection() method retrieves the operational statistics from its parent-request context and looks up database statistics based on the database name (or other description string) associated with the given connection. It then stores that information in the dbStats field on the request-context object to update performance information about acquiring the connection. This lets me track the time required to connect to the database (often this is really the time required to get a connection from a pool). The addConnection() method also updates the connections map of connections to database names. This map is used subsequently when JDBC statements are executed to update statistics for the appropriate request. The JdbcConnectionMonitor also provides a helper method, getDatabaseName(), which looks up the string name for a connection from the connections map.

Weak identity maps and aspects

The JDBC monitoring aspects use weak identity hash maps. These maps hold weak references to allow tracked objects like connections to be garbage collected when they are referenced only by the aspect. This is important because the singleton aspects are typically not garbage collected. If the references weren't weak, the application would have a memory leak. The aspects use identity maps to avoid calling the hashCode or equals methods on connections or statements. This is important because I want to track the connections and statements regardless of their state: I don't want to encounter exceptions from the hashCode method, nor should I rely on the hash code of the object remaining the same when its internal state changes (for example, when closed). I experienced this issue when working with dynamic proxy-based JDBC objects (like those from iBatis), which threw exceptions when any method on them was called after a connection had closed. This led to errors in trying to record statistics after finishing an operation.

One lesson to learn from this is to minimize the assumptions you make about third-party code. Using identity maps is a good way to avoid making assumptions about the implementation logic in advised code. In this case, I'm using an open source implementation of a WeakIdentityHashMap from the Emory DCL Java Utilities (see Resources). Tracking metadata information about connections or statements lets me group statistics across each request for equivalent connections or statements. This means I can track based on just object instances; I don't need to use object equality to track these JDBC objects. Another lesson to keep in mind is that JDBC objects are frequently wrapped by decorators (increasingly with dynamic proxies) by various frameworks. It's a bad idea to assume that you're working with a simple, raw implementation of such an interface!

Inside jdbcStatementMonitor

Listing 7 shows the JdbcStatementMonitor aspect. This aspect has two primary responsibilities: tracking information about creating and preparing statements and then monitoring performance statistics for executing JDBC statements.


Listing 7. The JdbcStatementMonitor aspect
/** * Monitor performance for executing JDBC statements,  * and track the connections used to create them,  * and the SQL used to prepare them (if appropriate). */public aspect JdbcStatementMonitor extends AbstractRequestMonitor {        /** Matches any execution of a JDBC statement */    public pointcut statementExec(Statement statement) :         call(* java.sql..*.execute*(..)) &&           target(statement);        /**     * Store the sanitized SQL for dynamic statements.      */    before(Statement statement, String sql,       RequestContext parentContext):       statementExec(statement) && args(sql, ..)         && inRequest(parentContext) {        sql = stripAfterWhere(sql);        setUpStatement(statement, sql, parentContext);    }        /** Monitor performance for executing a JDBC statement. */    Object around(final Statement statement) :      statementExec(statement) {        RequestContext requestContext =           new StatementRequestContext() {            public Object doExecute() {                return proceed(statement);            }        };        return requestContext.execute();    }           /**     * Call to create a Statement.     * @param connection the connection called to     * create the statement, which is bound to      * track the statement's origin      */    public pointcut callCreateStatement(Connection connection):        call(Statement+ Connection.*(..))           && target(connection);    /**     * Track origin of statements, to properly      * associate statistics even in     * the presence of wrapped connections      */    after(Connection connection) returning (Statement statement):      callCreateStatement(connection) {        synchronized (JdbcStatementMonitor.this) {            statementCreators.put(statement, connection);        }    }    /**      * A call to prepare a statement.     * @param sql The SQL string prepared by the statement.      */    public pointcut callCreatePreparedStatement(String sql):        call(PreparedStatement+ Connection.*(String, ..))           && args(sql, ..);    /** Track SQL used to prepare a prepared statement */    after(String sql) returning (PreparedStatement statement):       callCreatePreparedStatement(sql) {        setUpStatement(statement, sql);    }                protected abstract class StatementRequestContext       extends RequestContext {        /**          * Find statistics for this statement, looking for its          * SQL string in the parent request's statistics context          */        protected PerfStats lookupStats() {            if (getParent() != null) {                Connection connection = null;                String sql = null;                synchronized (JdbcStatementMonitor.this) {                    connection =                       (Connection) statementCreators.get(statement);                    sql = (String) statementSql.get(statement);                }                if (connection != null) {                    String databaseName =                       JdbcConnectionMonitor.aspectOf().                        getDatabaseName(connection);                    if (databaseName != null && sql != null) {                        OperationStats opStats =                           (OperationStats) getParent().getStats();                        if (opStats != null) {                            ResourceStats dbStats =                               opStats.getDatabaseStats(databaseName);                            return dbStats.getRequestStats(sql);                        }                    }                }            }            return null;        }    }    /**      * To group sensibly and to avoid recording sensitive data,     * I don't record the where clause (only used for dynamic     * SQL since parameters aren't included     * in prepared statements)     * @return subset of passed SQL up to the where clause     */    public static String stripAfterWhere(String sql) {        for (int i=0; i<sql.length()-4; i++) {            if (sql.charAt(i)=='w' || sql.charAt(i)==              'W') {                if (sql.substring(i+1, i+5).equalsIgnoreCase(                  "here"))                  {                    sql = sql.substring(0, i);                }            }        }        return sql;    }        private synchronized void       setUpStatement(Statement statement, String sql) {        statementSql.put(statement, sql);    }    /** associate statements with the connections     *  called to create them     */    private Map/*<Statement,Connection>*/ statementCreators =       new WeakIdentityHashMap();    /** associate statements with the     *  underlying string they execute     */    private Map/*<Statement,String>*/ statementSql =       new WeakIdentityHashMap();}

The JdbcStatementMonitor maintains two weak identity maps: statementCreators and statementSql. The first tracks the connections used to create a statement. As I noted earlier, I don't want to rely on the getConnection method of the statement because it could refer to a wrapped connection for which I don't have metadata. Note the callCreateStatement pointcut, which I advise to monitoring JDBC statement execution. This matches any call to a method defined on a JDBC connection that returns a Statement or any subclass thereof. This matches the 12 different ways that a statement can be

Posted by tornado
|