ASP.NET 웹 서비스, Enterprise Service 및 .NET Remoting의 성능
Ingo Rammer thinktecture.
Richard Turner Program Manager Microsoft Distributed Systems Group
2005년 8월
요약: ASP.NET 웹 서비스, .NET Enterprise Service 구성 요소 및 .NET Remoting 구성 요소의 성능 특성을 실제 상황에서 비교 및 대조하고, 이들 기술의 활용도를 극대화하는 방법에 대한 권장 사항을 확인하십시오(34페이지/인쇄 페이지 기준).
절대 성능은 여러 가지 기술 영역(장치, 하드웨어 컨트롤러, 생명 및 보건 서비스, 특정 재무 시스템)에서 최고의 관심사 중 하나지만 경시되는 경향이 있습니다. 대부분의 업무용 응용 프로그램의 1차적 목표는 "정확성", "배달 시간", 그리고 필요한 만큼 빠른 속도를 갖추는 것입니다. 절대 성능이 극대화된 엔지니어링 응용 프로그램을 만들려면 엄청난 비용과 노력이 필요할 수 있을 뿐 아니라, 업무 시스템에는 대개 이러한 성능 최적화를 위해 막대한 시간과 기술을 투입할 필요가 없는 경우가 많습니다. 그러나 절대 성능을 최적화하려는 노력이 과도한 경우도 있지만 투자 수익을 극대화하려는 대다수 기업에서는 여전히 전체 시스템 성능을 높은 수준으로 유지하고자 합니다.
이 백서에서는 .NET에서 사용할 수 있는 다음 세 개의 분산 구성 요소/서비스 기술 내에서 각각 호스트되는 실제 구성 요소/서비스의 상대 성능을 비교 분석하겠습니다.
"어떤 Microsoft 분산 응용 프로그램 기술이 가장 빠른가?"에 관한 논란이나 "특정 기술은 사용하기에 너무 느리다" 같은 의견은 끊임없이 제기되고 있습니다. 본 백서의 주된 목적은 Microsoft 분산 기술의 성능과 관련된 많은 문제, 오해, 정확하지 않은 부분, 그리고 잘못된 정보를 바로잡고 이를 명확히 설명하는 데 있습니다.
또한 이 백서를 통해 Microsoft의 각 분산 구성 요소/서비스 기술의 상대 성능 특성에 대해 현재 알려진 오해를 해결하는 것은 물론, 설명이 추가된 명확한 테스트 및 테스트 결과를 비롯하여 고객의 요구에 가장 적합한 기술을 선택하는 데 도움이 될 만한 간단한 지침을 제시하고자 합니다.
본 백서의 목적을 요약하면 다음과 같습니다.
대부분의 업무용 응용 프로그램에서 나타나는 이들 세 기술 간의 상대 성능 차이 확인
현재 알려진 기술 상호 간 성능 저하 문제에 대한 몇 가지 오해 정정
각 기술의 활용도를 최적화하는 상황, 시기 및 방법을 손쉽게 결정하도록 유도
이러한 테스트를 각자의 시스템과 환경에서 실행할 수 있도록 테스트 응용 프로그램 제공 가급적 이 테스트 환경을 직접 빌드하고 실행하여 이들 기술의 성능 특성을 확인 및 분석하는 것이 좋습니다. 이렇게 하면 분산 시스템의 성능에 영향을 미치는 많은 요소를 완벽하게 이해할 수 있습니다.
벤치마크 이상의 목표
이 백서에 나와 있는 테스트는 테스트 대상인 특정 기술들 사이에 일관된 비교 결과를 제공하기 위해 명시적으로 디자인되었습니다. 이러한 테스트는 각 기술의 최적화된 절대 성능을 측정하기 위한 것이 아닙니다.
테스트 드라이버(클라이언트) 응용 프로그램은 단일 스레드 응용 프로그램이므로 호출한 서비스가 응답하는 즉시 연속해서 동기식으로 호출합니다. 이 디자인의 경우 서버에서 CPU를 사용하는 과정에 병목 현상이 항상 발생하지는 않습니다.
여러 클라이언트 즉, 멀티스레드 클라이언트를 사용하면 서버측 처리에서 초당 호출 수가 늘어날 수 있습니다. CPU를 많이 사용하는 서버 응용 프로그램 테스트의 경우 여러 클라이언트를 사용해도 테스트하는 기술의 절대 또는 상대 성능이 크게 변화하지 않습니다.
여러 클라이언트를 사용하면 상당히 높은(2배) 집계 서버 처리량을 얻어 테스트를 간소화할 수 있으며 다양한 기술의 상대 성능이 어느 정도 변화하는 효과도 얻을 수 있습니다.
단일 클라이언트와 여러 클라이언트 중 어느 쪽이 더 현실적인지는 웹 서비스를 배포하는 상황과 방법에 따라 다릅니다. 단일 클라이언트로 측정하려는 결정이 본 백서의 결론에 영향을 미치지는 않습니다.
테스트
다음 성능 테스트에서는 종합 기준과 실제 시나리오를 모두 살펴보고 있습니다. 여기서 검토할 일반적인 질문은 다음과 같습니다.
.NET Remoting이 ASMX보다 빠릅니까?
ES는 .NET Remoting보다 느립니까?
ASMX가 많이 느려 실제 시나리오에서는 사용할 수 없습니까?
이러한 의견을 검토하기 위해 다양한 테스트를 수행했습니다. 첫 번째 테스트에서는 대규모의 요청을 받아들여 많은 작업을 수행하거나 많은 작업을 수행하여 소규모 또는 대규모의 결과 집합을 반환하도록 요청 받는 작업에 대한 성능을 검토합니다. 이러한 테스트를 수행하는 목적은 시스템을 빌드하면서 경험할 수 있는 일반적인 업무 시나리오의 성능을 설명하는 데 있습니다.
본 백서의 모든 테스트는 아래 나와 있는 각 기술의 프로토콜에 대해 실행되었습니다.
enterprise service(Just-In-Time 활성화 사용)
인증 없음
호출 수준 인증 및 역할 액세스 검사 적용
ASP.NET 웹 서비스
인증 없음
사용자 이름 및 암호 인증
통합 인증
.NET Remoting
TCP/Binary(보안되지 않음)
HTTP/Binary(보안되지 않음)
HTTP/SOAP(보안되지 않음)
IIS의 HTTP/SOAP(보안되지 않음)
IIS의 HTTP/Binary(보안되지 않음)
IIS의 HTTP/SOAP(HTTP 기본 인증)
IIS의 HTTP/Binary(HTTP 기본 인증)
IIS의 HTTP/SOAP(통합 인증)
IIS의 HTTP/Binary(통합 인증)
다음 테스트는 모두 서버측 메서드를 반복해서 호출하는 단일 클라이언트 응용 프로그램을 기반으로 하고 있습니다. 여기서는 집합의 각 테스트에 대해 초당 평균 원격 호출/메서드 수를 계산하고 전체 집합을 10번 반복하여 전체 다단계 테스트 실행에 대한 평균 및 표준 편차를 구했습니다.
결과
테스트 1: 주문 만들기 및 저장
이 첫 번째 테스트는 호출되는 작업으로 많은 작업을 수행하는 대부분의 최적화된 상황에서 기술별로 나타나는 성능 특성을 시뮬레이트하도록 디자인되었습니다. 호출되는 작업은 데이터베이스에 주문을 저장하고 트랜잭션 내의 두 테이블에 레코드를 작성하여 많은 작업을 수행합니다.
호출자는 다음과 같은 Order 클래스의 인스턴스를 만듭니다.
[Serializable]public class Order{ public int OrderID; public String CustomerID; public int EmployeeID; public DateTime OrderDate; public Address ShippingAddress; public Address BillingAddress; public int ShipVia; public decimal Freight; public LineItem[] LineItems;}
Order 클래스에는 Address 형식의 자식 개체 하나와 LineItem 20개로 구성된 배열이 포함되어 있습니다.
[Serializable]public class Address{ public String Name; public String Street; public string City; public string Region; public string PostalCode; public string Country;}[Serializable]public class LineItem { public int ProductID; public double UnitPrice; public short Quantity; public double Discount;}
이 데이터 개체는 메서드를 호출하는 동안 호출자에게서 서버로 전달되며 테스트 대상 기술에 의해 네트워크에서 serialize됩니다.
서버측 구현은 다음과 같습니다.
SqlConnection _conn;private void InitializeComponent(){ // ... 생성된 코드 대부분 생략 _conn = new SqlConnection(); // ... 생성된 코드 대부분 생략}public void StoreOrder(Order o){ using (_conn) { _conn.Open(); using (SqlTransaction tx = _conn.BeginTransaction()) { using (SqlCommand cmd = new SqlCommand(@"INSERT INTO Orders(EmployeeID, " + "CustomerID, OrderDate, RequiredDate, ShipVia, ShippedDate, " + "ShipName, Freight, ShipAddress, ShipCity, ShipRegion, " + "ShipCountry, ShipPostalCode) VALUES (@EmployeeID, @CustomerID, " + "@OrderDate, @RequiredDate, @ShipVia, @ShippedDate, @ShipName, " + "@Freight, @ShipAddress, @ShipCity, @ShipRegion, @ShipCountry, " + "@ShipPostalCode); SELECT @@IDENTITY", _conn,tx)) { cmd.Parameters.Add("@EmployeeID", o.EmployeeID); cmd.Parameters.Add("@CustomerID",o.CustomerID); cmd.Parameters.Add("@OrderDate",o.OrderDate); cmd.Parameters.Add("@RequiredDate",o.OrderDate.AddDays(10)); cmd.Parameters.Add("@ShipVia",o.ShipVia); cmd.Parameters.Add("@ShippedDate",o.OrderDate.AddDays(5)); cmd.Parameters.Add("@ShipName",o.ShippingAddress.Name); cmd.Parameters.Add("@Freight",o.Freight); cmd.Parameters.Add("@ShipAddress",o.ShippingAddress.Street); cmd.Parameters.Add("@ShipCity",o.ShippingAddress.City); cmd.Parameters.Add("@ShipRegion",o.ShippingAddress.Region); cmd.Parameters.Add("@ShipCountry",o.ShippingAddress.Country); cmd.Parameters.Add("@ShipPostalCode",o.ShippingAddress.PostalCode); decimal orderID = (decimal) cmd.ExecuteScalar(); o.OrderID = (int) orderID; } using (SqlCommand cmd = new SqlCommand(@"INSERT INTO [Order Details] (OrderID, " + "ProductID, UnitPrice, Quantity, Discount) VALUES (@OrderID, " + "@ProductID, @UnitPrice, @Quantity, @Discount)", _conn,tx)) { cmd.Parameters.Add("@OrderID",SqlDbType.Int); cmd.Parameters.Add("@ProductID",SqlDbType.Int); cmd.Parameters.Add("@UnitPrice",SqlDbType.Money); cmd.Parameters.Add("@Quantity",SqlDbType.SmallInt); cmd.Parameters.Add("@Discount",SqlDbType.Real); foreach (LineItem itm in o.LineItems) { cmd.Parameters["@OrderID"].Value = o.OrderID; cmd.Parameters["@ProductID"].Value = itm.ProductID; cmd.Parameters["@UnitPrice"].Value = itm.UnitPrice; cmd.Parameters["@Quantity"].Value = itm.Quantity; cmd.Parameters["@Discount"].Value = itm.Discount; cmd.ExecuteNonQuery(); } } tx.Commit(); } _conn.Close();(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.) }}
테스트 실행이 완료되면 서버측 논리에서는 두 번째 트랜잭션을 사용하여 삽입된 레코드를 삭제하고 테스트의 실행 횟수와 관계없는 "동등한 조건의 환경"을 제공합니다.
그림 1. 실제 서버측 구현으로 프로세스 간에 "주문" 전달
그림 1의 테스트 결과를 보면 73 CPS인 ES와 63 CPS인 ASP.NET 웹 서비스의 차이는 14%에 불과하며 최고 속도 프로토콜과 최저 속도 프로토콜의 최대 차이는 45%라는 것을 알 수 있습니다. 이 결과는 수행하는 작업량이 데이터를 전송하는 데 걸리는 시간에 비례하여 증가하면 다른 전송에 대한 특정 전송의 이점이 줄어든다는 것을 분명하게 보여 줍니다.
또한 그림 2에 정의된 DataTable이 들어 있는 형식화된 DataSet을 빌드하여 동일한 조건의 ADO.NET DataSet 테스트를 수행했습니다.
그림 2. 이 DataSet은 구매 주문서를 나타냅니다.
이 DataSet은 생성된 SqlDataAdapter와 함께 사용되며 여기에는 위의 코드 조각에 나타난 것과 유사한 SqlCommand가 들어 있습니다.
주문이 호출자에서 서버로 DataSet 형태로 전달된 후 저장되면 그림 3과 같은 결과를 얻게 됩니다.
그림 3. 구매 주문서를 DataSet으로 저장
여기서 DataSet을 사용하면 성능에 큰 영향을 미치게 되며(50% 속도 감소) 프로토콜 간의 차이는 계속해서 추가로 줄어든다는 점에 유의하십시오. 또한 데이터 개체를 전달하여 얻는 최저 속도의 결과가 ADO.NET DataSet을 전달할 때의 최고 성능을 뛰어넘는다는 점을 기억해야 합니다.
이 결과는 시스템 계층 사이에 ADO.NET DataSet을 전달하면 성능이 크게 저하된다는 사실을 분명하게 보여 줍니다.
테스트 2: Northwind 제품 데이터 검색
이 테스트의 목적은 많은 작업을 수행하지 않는 서비스에서 비교적 많은 분량의 데이터가 반환되는 경우의 기술별 성능 특성을 설명하는 데 있습니다.
이 시나리오를 모델링하기 위해 잘 알려진 SQL Server "Northwind" 샘플 데이터베이스에서 "product" 레코드 목록을 검색하여 반환하는 메서드를 빌드했습니다.
이 테스트에서는 이진 serialization 기술(ES/COM+ 및 Remoting Binary/TCP)의 성능이 가장 뛰어날 것으로 예상됩니다. SOAP 기반 기술은 많은 양의 데이터를 전송하는 데다 serialization 및 deserialization 메커니즘이 복잡하기 때문에 낮은 성능 수준을 나타낼 것으로 보고 있습니다.
첫 번째 테스트에서는 XML Serializer와 호환되는 공용 멤버가 포함된 [Serializable] 클래스를 정의했습니다.
[Serializable]public class Product{ public int ProductID; public string ProductName; public int SupplierID; public int CategoryID; public string QuantityPerUnit; public decimal UnitPrice; public short UnitsInStock; public short UnitsOnOrder; public short ReorderLevel; public bool Discontinued;}
서버측 구현에서는 SQL Server에 대한 데이터베이스 연결을 열고 데이터베이스에 대해 쿼리를 실행한 다음, ADO.NET SqlDataReader를 사용하여 이 클래스의 Product 인스턴스 77개가 포함된 ArrayList를 채웠습니다. 그런 다음 ArrayList는 호출자로 반환되었습니다.
결과는 그림 4에 나와 있습니다.
그림 4. Northwind 제품 카탈로그를 개체로 검색
이 결과는 Enterprise Service(DCOM 사용)와 TCP 기반의 이진 Remoting이 보안되지 않는 호출에 대해 동일한 수준의 성능을 제공한다는 것을 보여 줍니다. 또한 ASP.NET 웹 서비스의 성능이 Enterprise Service의 62% 수준이며 최저 속도 프로토콜은 최고 속도에 비해 초당 호출 수를 17%밖에 사용할 수 없다는 사실도 알 수 있습니다.
이 테스트와 이어지는 모든 테스트에서는 일반적으로 SOAP 서비스를 빌드하는 데 .NET Remoting SoapFormatter를 사용하는 것이 바람직하지 않은 이유가 ASP.NET Web Service 스택의 속도가 세 배 정도 빠르기 때문이라는 점을 분명하게 보여 줍니다.
이 테스트의 두 번째 접근 방식에서는 요청된 데이터를 원본으로 사용하는 테이블 레이아웃에 직접 기반을 둔 ADO.NET DataSet 개체에 반환하고 SqlDataAdapter를 사용하여 DataSet의 DataTable을 채우는 메서드를 구현했습니다. 테이블 정의는 그림 5에 설명되어 있습니다.
그림 5. 제품 카탈로그에 액세스하는 형식화된 DataSet
그림 6에는 다음과 같은 결과가 설명되어 있습니다.
그림 6. Northwind 제품 카탈로그를 DataSet으로 검색
이 테스트 결과는 서비스에서 호출자로 DataSet을 반환하면 성능이 크게 떨어진다는(이진 전송의 경우 약 75%) 것을 분명하게 보여 줍니다. 또한 네 개의 최고 속도 프로토콜 성능이 완전히 같다는 것은 네 기술의 성능이 모두 DataSet의 serialization, deserialization 및 전송에 의해 좌우됨을 나타냅니다. 게다가 최고 속도(ASMX : 39 cps)와 최저 속도(Remoting HTTP/SOAP(IIS) 통합 보안 : 25 cps) 사이의 성능 범위는 14 cps로 구분되며 이는 30%에 불과한 하강 수준을 나타냅니다.
이 결과를 통해 DataSet을 사용하여 응용 프로그램 계층 사이에 정보를 전달하면 편리하지만 그 과정이 성능에 큰 영향을 미치게 된다는 것을 확실히 알 수 있습니다. 이에 대해서는 나중에 다시 살펴보겠습니다.
또한 단순히 ADO.NET DataSet을 호출자에게 반환하기보다는 ADO.NET DataSet을 반복하고 데이터 개체 컬렉션을 만들어 이 컬렉션을 serialize하는 것이 더 빠른 방법이라는 점도 알아 두어야 합니다. 이것이 서버와 호출자 사이에 DataSet을 전달하기 위해 구현된 실제 시스템이었다면 작은 양의 작업으로도 이 부분의 시스템 성능을 4배 가까이 끌어올릴 수 있었을 것입니다.
테스트 3: 고객 정보 검색
다음 테스트에서는 "Northwind" SQL Server 샘플 데이터베이스에서 단일 고객 레코드를 검색하여 서버에서 클라이언트로 매우 작은 양의 데이터를 전송하는 경우의 기술별 상대 성능 특성을 살펴보기로 했습니다.
검색된 데이터를 다시 클라이언트에서 서버로 돌려보내기 위해 다음과 같은 [Serializable] Customer 클래스를 만들었습니다.
[Serializable]public class Customer{ public String CustomerID; public String CompanyName; public String ContactName; public String ContactTitle; public string Address; public string City; public string Region; public string PostalCode; public string Country; public string Phone; public string Fax;}
다음은 SqlDataReader를 사용하여 Customer 클래스의 새 인스턴스를 채우는 서버측 구현의 일부입니다.
public Customer GetCustomer(string id){ using (SqlConnection conn = new SqlConnection(...)) { conn.Open(); String sql = "SELECT CustomerID, CompanyName, ContactName, " "ContactTitle, Address, City, Region, " + "PostalCode, Phone, Fax, Country FROM " + "Customers WHERE (CustomerID = @CustomerID)"; using (SqlCommand cmd = new SqlCommand(sql,conn)) { cmd.Parameters.Add("@CustomerID", id); SqlDataReader rdr = cmd.ExecuteReader(); if (rdr.Read()) { Customer c = new Customer(); c.CustomerID = (string) rdr[0]; c.CompanyName = (String) rdr[1]; c.ContactName = (String) rdr[2]; c.ContactTitle = (String) rdr[3]; c.Address = (String) rdr[4]; c.City = (String) rdr[5]; c.Region = rdr.IsDBNull(6) ? "" : rdr[6] as string; c.PostalCode = (String) rdr[7]; c.Phone = (String) rdr[8]; c.Fax = (String) rdr[9]; c.Country = (String) rdr[10]; return c; } else { return null; } } }}
성능 비교 결과는 그림 7에 나와 있습니다.
그림 7. 고객 데이터를 개체로 검색
이 결과는 각 기본 기술 간의 성능 차이를 좀 더 확실하게 보여 줍니다. 이 테스트에서는 전체 호출 비용에서 단일 레코드를 검색하고 반환하기 위해 수행 중인 작업이 차지하는 비중이 훨씬 낮으며 그에 따라 전송 비용이 보다 중요한 역할을 하기 때문에 성능 차이가 이처럼 두드러지게 나타나는 것입니다. 따라서 SOAP/HTTP는 Binary/DCOM보다 전송 비용이 높기 때문에 SOAP 전송은 이진 메커니즘보다 처리량 수준이 훨씬 낮습니다.
다음에는 그림 8에서처럼 형식화된 DataSet을 반환하는 동일한 작업을 테스트했습니다.
그림 8. 고객 정보에 액세스하는 형식화된 DataSet
이 DataSet은 다음과 같은 서버측 구현을 사용하여 채웁니다.
SqlDataAdapter _customerAdapter;SqlCommand _customerSelect;SqlConnection _conn;private void InitializeComponent(){ // ... 생성된 코드 대부분 생략 _conn = new SqlConnection(); _customerAdapter = SqlDataAdapter(); _customerSelect = SqlCommand(); _customerSelect.CommandText = "SELECT CustomerID, CompanyName, " + "ContactName, ContactTitle, Address, City, Region, " + "PostalCode, Phone, Fax, Country FROM Customers WHERE " + "(CustomerID = @CustomerID)";_customerSelect.Connection = _conn;_customerSelect.Parameters.Add(new SqlParameter("@CustomerID", SqlDbType.NVarChar, 5, "CustomerID")); _customerAdapter.SelectCommand = this.sqlSelectCommand3; _customerAdapter.TableMappings.AddRange(new DataTableMapping[] { new DataTableMapping("Table", "Customers", new DataColumnMapping[] { new DataColumnMapping("CustomerID", "CustomerID"), new DataColumnMapping("CompanyName", "CompanyName"), new DataColumnMapping("ContactName", "ContactName"), new DataColumnMapping("ContactTitle", "ContactTitle"), new DataColumnMapping("Address", "Address"), new DataColumnMapping("City", "City"), new DataColumnMapping("Region", "Region"), new DataColumnMapping("PostalCode", "PostalCode"), new DataColumnMapping("Phone", "Phone"), new DataColumnMapping("Fax", "Fax"), new DataColumnMapping("Country", "Country")})}); // ... 생성된 코드 대부분 생략}public PerfTestDataSet GetCustomerAsDataset(string id){ using (_conn) { _conn.Open(); customerAdapter.SelectCommand.Parameters["@CustomerID"].Value = id; PerfTestDataSet ds = new PerfTestDataSet(); _customerAdapter.Fill(ds,"Customers"); return ds; }}
이 코드를 테스트 프레임워크 내에서 실행하자 그림 9와 같은 결과가 나타났습니다.
그림 9. 고객 데이터를 DataSet으로 검색
앞의 테스트와 마찬가지로 ADO.NET DataSet을 교환하면 성능에 좋지 않은 영향을 미칠 뿐 아니라, 모든 기술의 처리량 차이도 ADO.NET DataSet을 통해 처리량이 조절되고 있음을 나타내는 serialize된 개체를 반환하는 경우보다 줄어든다는 것을 확실히 알 수 있습니다.
.NET Framework 2.0 성능
이 백서를 작성할 당시 Microsoft는 다음과 같은 수많은 개선 및 향상된 기능과 새로운 기능이 포함된 .NET Framework 2.0(이전 코드 이름: "Whidbey")을 선보이고 있었습니다.
x64 및 IA64 기반 시스템 완벽 지원
System.Transactions를 통한 새로운 트랜잭션 지원
연결 인식 및 프록시 지원 같은 System.Net의 향상된 기능
Windows Form 및 Web Form의 대폭 향상된 기능
"ClickOnce" 자동 응용 프로그램 배포 인프라
.NET Framework 2.0는 이처럼 향상된 새로운 기능이 추가된 것 외에도 많은 개선 작업이 이루어져 BCL, XML Serialization, 네트워킹 처리량, ADO.NET, ASP.NET 등과 같은 많은 영역에서 성능이 대폭 개선되고 메모리 사용이 향상되었습니다.
위의 결과는 여러분이 실제 응용 프로그램에서 예상할 수 있는 성능 특성을 보여 주며, 이를 통해 실제 응용 프로그램에서는 웹 서비스(일반적으로 매우 느린 것으로 파악된)와 DCOM(이 테스트에서 가장 빠른 분산 응용 프로그램 기술 중 하나로 확인된) 간의 차이가 매우 작다는 것을 알 수 있습니다.
테스트 대상 기술에 대한 Microsoft의 견해는 다음 섹션에 보다 자세히 나와 있으며 요약하자면 다음과 같습니다.
ASMX가 현재 Microsoft에서 제공하는 기술 중 가장 빠른 것은 아니지만 그 성능은 대부분의 업무 시나리오에 매우 적합한 수준입니다. ASMX 서비스는 확장이 용이할 뿐 아니라 상호 운용성 및 향후 호환성 같은 다양한 이점을 제공하므로 오늘날 서비스를 빌드하는 데 선택할 수 있는 가장 적합한 기술은 ASMX입니다.
절대 성능이 주된 관심사인 경우 enterprise service 구성 요소를 사용하여 시스템에서 성능이 가장 중요한 부분을 빌드해야 합니다. COM+는 전체적으로 가장 뛰어난 성능을 나타냈으며 분산 구성 요소를 위한 안전하고 안정적인 보안 호스팅 환경으로 사용할 수 있습니다. 또한 ES/COM+는 Indigo 서비스와 완벽하게 통합되며 ES 구성 요소를 Indigo 서비스로 변환하는 과정도 비교적 간단히 수행할 수 있을 것입니다.
.NET Remoting은 TCP상에서 이진 serialization을 사용할 때 가장 성능이 뛰어나지만 Remoting 구성 요소를 IIS에서 호스트하거나 SOAP 메시지를 주고 받으면 성능이 저하됩니다. .NET Remoting 구성 요소는 .NET Remoting 끝점을 제외한 다른 기술과는 상호 운용할 수 없으므로 가능하면 Remoting보다는 ASMX나 ES를 고려하는 것이 좋습니다.
이 결과에서 도출한 중요한 견해 한 가지는 서버에서 수행되는 작업량이 각 호출의 전체 지속 시간에서 큰 비중을 차지하지 않는 경우 전송 시간에서 호출 시간이 상당 부분을 차지하게 된다는 점입니다. COM+ 기술에서의 DCOM 같은 "고속" 전송에서 ASMX 기술에서의 SOAP/HTTP 같은 "비용이 많이 드는" 전송으로 옮겨갈 경우 성능에 큰 영향을 미칠 수 있습니다. 이를 통해 메서드를 호출할 때마다 서버에서 가능한 많은 작업을 수행하고 불필요한 네트워크 이동을 피해야 한다는 점을 더욱 분명히 알 수 있습니다.
또한 이번 테스트를 통해 이들 기술 간의 성능 차이는 DataSet 또는 serialize된 구조를 사용하는 것 같은 응용 프로그램 관련 결정 간의 차이보다 작다는 것이 분명해졌습니다.
이 백서에서는 성능 수치에 큰 중점을 두었지만 개발 프로젝트를 계획할 때 고려해야 할 요소가 성능만 있는 것은 아닙니다. 보안, 확장성, 관리, 개발자 생산성, 상호 운용성, 그리고 Microsoft 분산 기술의 향방 같은 다른 요소도 모두 고려해야 합니다. Developing Distributed Services Today (영문)에는 각 기술의 활용 시기와 상황을 가장 효율적으로 선택하는 방법과 그 발전 가능성을 담은 규범적인 지침이 나와 있습니다.
권장 사항
이번 테스트 결과를 토대로 향후 기술의 방향과 최적의 방법에 따라 다음 권장 사항을 준수하면 성능 문제에 취약하지 않은 시스템을 빌드할 수 있을 것입니다.
가능한 한 ASMX를 사용합니다.
서비스 범위를 극대화하려면 가능한 한 ASMX를 사용하여 서비스를 게시하는 방법을 고려하십시오. ASMX를 사용하면 시스템에서 실행되거나 구현될 수 있는 플랫폼, 장치 또는 언어에 관계없이 모든 시스템에서 HTTP를 통해 SOAP를 전달하여 서비스를 호출할 수 있게 됩니다. 단일 스레드 응용 프로그램에서 호출하는 단일 프로세스 시스템의 경우에도 ASMX는 초당 63회의 많은 작업을 처리할 수 있으며, 이는 동일한 ES 구성 요소보다 초당 호출 수가 10회 적은 수준에 불과합니다. 프로덕션 환경의 경우 단일 시스템에서 ASMX 서비스를 호스트하면 훨씬 뛰어난 성능을 기대할 수 있으므로 ASMX를 채택하도록 유도하는 가장 큰 요인은 무엇보다도 성능이라 할 수 있습니다.
웹 서비스에는 다음을 포함하여 여러 가지 유용한 특징과 기능이 있습니다.
확장성-Windows 네트워크 로드 균형 조정 같은 로드 균형 조정 기술이나 Cisco 및 F5 같은 공급업체의 하드웨어 장치를 사용하면 웹 서비스를 매우 손쉽게 확장할 수 있습니다. 자세한 내용은 다음과 같습니다.
가용성-ASMX 웹 서비스는 로드 균형 조정 같은 기술과 함께 Windows 2003 Server에 내장된 IIS6 인프라의 강력한 기능(예: 실패한 서비스의 자동 재활용 및 다시 시작)을 사용하여 높은 가용성을 갖추도록 구성할 수 있습니다.
상호 운용성-웹 서비스는 개별적인 이종 시스템 간에 자유롭게 통신할 수 있도록 하는 상호 운용 가능한 안전하고 트랜잭션 처리된 통신 프로토콜을 표준화하는 움직임의 핵심입니다.
생산성-대부분의 개발자는 몇 가지 특성을 신중하게 사용하고 일부 권장 사항을 준수하여 ASMX 웹 서비스를 매우 손쉽게 빌드할 수 있습니다.
웹 서비스에 ASMX를 사용합니다.
prescriptive guidance (영문)에는 ASMX가 서비스를 빌드 및 배포하는 데 뛰어난 플랫폼인 다양한 이유가 나와 있으며, 본 백서의 결과를 통해서도 ASMX는 일반적으로 SOAP를 전달할 때 Remoting보다 뛰어난 성능을 발휘한다는 것을 알 수 있습니다.
.NET Remoting도 SoapFormatter 및 HttpChannel을 통해 SOAP/HTTP를 지원할 수 있지만 Remoting을 사용하여 웹 서비스를 노출하는 방법은 가급적 피하는 것이 좋습니다. .NET Remoting SoapFormatter는 더 이상 SOAP의 표준 인코딩 방식이 아닌 RPC-Encoding(대부분의 공급업체 기술 및 플랫폼에서 사용될 미래의 표준 인코딩 메커니즘은 문서-리터럴임)을 사용하므로 Remoting 서비스와의 상호 운용성이 매우 제한적입니다.
확장성을 고려하여 웹 서비스를 선택합니다.
위에서 언급하지 않았지만 이번 설명과 매우 관련성이 높은 내용으로, 웹 서비스는 주로 로드 균형 조정이 간단한 상태 비저장 통신 기술인 HTTP를 통해 통신합니다. 따라서 TCP 또는 HTTP(즉, IIS 내에서 호스트되는)를 사용해도 Remoting 구성 요소의 로드 균형 조정을 수행할 수 있지만 세션 상태가 필요한 경우 복잡해질 뿐 아니라 문제를 일으킬 수 있습니다.
ES/COM+ 구성 요소의 로드 균형 조정을 수행하려면 Application Center 2000의 기능인 CLB(구성 요소 로드 균형 조정)를 사용해야 합니다. 그러나 아쉽게도 AppCenter 2000은 2006년 7월 지원이 확장되며 2011년이면 지원이 종료됩니다. 따라서 더 이상 CLB에 의존하지 않기 위해 2011년 전에 시스템을 다시 설계하는 수고를 피하려면 새로운 개발에 이 기술을 사용하지 않고 가능한 한 빠른 시일 내에 다른 전략으로 마이그레이션해야 합니다. 효과적인 마이그레이션 전략은 현재 COM+ 구성 요소에 ASMX 웹 서비스를 연결하는 것입니다. 이렇게 하면 COM+ 구성 요소로 직접 호출을 전달하고 위에 설명된 것처럼 웹 서비스의 로드 균형 조정을 수행할 수 있습니다.
Enterprise Service는 서비스 내에서만 사용합니다.
.NET Enterprise Service는 COM+에서 제공하는 다양한 서비스에 대한 액세스 및 지원을 제공하는 .NET Framework 계층입니다. 여러 가지 개체/서비스, 미세 조정 보안, JITA 및 개체 풀링을 포괄하는 분산 트랜잭션 같은 다양한 COM+ 서비스가 필요한 경우에는 Enterprise Service를 선택해야 합니다. Enterprise Service와 COM+는 적절한 테스트를 거쳐 높은 수준으로 최적화된 기술로, 매우 빠르고 대기 시간이 낮은 크로스 프로세스 및 크로스 컴퓨터 호출을 제공합니다.
그러나 Enterprise Service 구성 요소를 광범위하게 노출해서는 안 되며 서비스 경계 내에서만 이 기술을 사용해야 합니다. 서비스에 대한 액세스를 제공하려면 ASMX를 사용한 웹 서비스를 통해 서비스의 기능을 노출하는 것이 좋습니다. ES/COM+ 코드 기반을 설정한 경우에는 가급적 COM+ 서비스와 ASMX 웹 서비스를 연결하는 것이 좋습니다.
직접 빌드하기보다는 호스트되는 인프라를 사용합니다.
위에서 설명한 것처럼 ASP.NET은 상호 운용성이 뛰어날 뿐 아니라 IIS(특히, Windows 2003 Server의 IIS6)에서 제공하는 뛰어난 호스팅 기능을 통해 이점이 발휘되는 탁월한 성능의 웹 서비스를 제공합니다.
COM+는 enterprise service 및 COM+ 구성 요소에 신뢰할 수 있고 안전하며 안정적인 호스팅 환경을 지원하고 Microsoft 플랫폼에 가장 빠른 구성 요소 성능을 제공합니다. 또한 분산 트랜잭션 지원, 보안(인증 및 암호화), 안정성, 가용성, 관리 서비스 등을 제공하므로 서비스 내에서 복잡한 구성 요소 기반 시스템을 호스트하는 데 매우 적합합니다.
TCP상에서 이진 serialization을 사용하는 .NET Remoting도 성능이 뛰어나지만 호스팅 환경, 보안, 안정성, 가용성 또는 "즉시 적용할 수 있는" 관리 기능은 제공하지 않습니다. TCP를 사용하는 Remoting 구성 요소를 호스트하려면 호스팅 응용 프로그램을 직접(보안, 확장성, 안정성) 작성해야 하는데, 이는 그리 간단한 작업이 아닙니다. HTTP상에서 Remoting을 사용하려는 경우 IIS를 호스트로 사용할 수도 있지만, 앞서 살펴보았듯이 IIS에서 Remoting 구성 요소를 호스트하여 SOAP/HTTP 통신을 수행하면 Remoting 성능이 크게 떨어지며 대개는 ASMX를 사용하는 것보다 못하게 됩니다!
따라서 Remoting을 사용하기보다는 IIS에서 호스트되는 ASMX나 COM+ 내의 enterprise service 구성 요소에 서비스를 전달하는 것이 바람직합니다.
서비스 간 DataSet을 전달하지 않습니다.
이번 테스트에서 서비스와 호출자의 데이터 교환하는 방법(DataSet 또는 serialize된 구조로)을 선택한 결과 특정 분산 시스템 기술을 서로 비교하여 선택하는 것보다 성능에 훨씬 더 많은 영향을 미쳤습니다.
성능 측면에서 보면 DataSet을 데이터 전송 메커니즘으로 사용하는 것은 응용 프로그램에서 꼭 필요한 부분으로 제한하고 가능한 [Serializable] 구조를 선택하는 것이 좋습니다.
ADO.NET DataSet에서는 데이터를 검색, 조작, 정렬하고 구조를 지정하며 오프라인에서 사용하도록 로컬에 저장하고 변경 내용을 중앙 데이터베이스와 다시 동기화하는 뛰어난 방식을 제공합니다. 이 방식이 응용 프로그램의 요구 사항이라면 당연히 선택해야 합니다. .NET Framework 1.x의 DataSet 값은 항상 XML로 serialize되고(이 값을 이진으로 serialize하거나 ES 또는 Remoting과 함께 사용하길 원하더라도) 데이터를 설명하는 XSD가 포함되어 있는데 이 역시 성능에 영향을 미칩니다.
또한 DataSet은 .NET 관련 기술이고 다른 플랫폼에서 serialize된 DataSet이 포함된 데이터를 반드시 serialize 및 구문 분석할 필요는 없으므로 상호 운용성을 크게 제한합니다.
대신 serialize된 데이터 구조를 교환하면 성능을 크게 개선하는 효과를 얻을 수 있습니다. 이 작업은 위의 테스트에서처럼 기본 제공된 런타임 서식 지정 및 XML serialization 프레임워크를 사용하여 간단히 수행할 수 있습니다.
인증된 연결 공유를 사용합니다.
통합 보안과 함께 IIS(Internet Information Server)를 호스트로 사용하는 모든 테스트는 ASP.NET 클라이언트 및 .NET Remoting에 사용할 수 있는 useAuthenticatedConnectionSharing 및 useUnsafeAuthenticatedConnectionSharing을 옵션으로 실행하도록 구성되어 있습니다. 이 설정을 사용하면 클라이언트와 서버에서 기존 NTLM 인증 연결을 다시 사용할 수 있습니다.
처음에 그림 1(구매 주문서를 개체로 저장)에서 소개한 테스트를 수행하면서 그림 10의 이 옵션을 설정한 경우 나타나는 성능 결과를 확인할 수 있습니다. 이 설정은 IIS를 서버로 사용하고 가상 디렉터리 구성에서 "Windows In Security"를 지정하는 경우에만 적용된다는 점에 유의하십시오.
이 테스트는 검토 대상인 기본으로 포함되는 기술들 간의 성능 차이에 대한 기준이 됩니다. 이러한 테스트에서는 매개 변수를 받아들이지 않고 작업을 수행하지 않으며 결과를 반환하지 않는 작업에 대해 호출합니다. 서버측 구현은 다음과 같이 간단합니다.
public void TransferEmpty(){ // 명령을 입력하지 마십시오.}
참고 이러한 종합 기준 수치는 검토 대상인 각 기술의 통신 인프라에 대한 상대 성능 수준을 나타내기 위해서만 제공되며, 실제 시스템에서 예상되는 성능 특성을 반영하지는 않습니다.
그림 11. 프로세스 간 매개 변수 없이 메서드 호출(종합 기준)
그림 11의 테스트 결과는 COM+ 및 DCOM을 비롯하여 고성능 LPC(Lightweight Procedure Call) 시스템상 통신 인프라를 사용하는 ES가 훨씬 최적의 성능을 제공한다는 일반적인 가정에 매우 근접하게 일치했습니다. .NET Remoting 통신 프로토콜은 2위를 차지했습니다. ASP.NET 웹 서비스는 3위를 차지했지만 여전히 IIS에서 호스트되는 .NET Remoting 구성 요소보다는 빠릅니다.
두 번째 단계로 이 테스트를 크로스 컴퓨터 환경에서 실행했습니다. 여기서는 "Url" 속성을 설정하여 생성된 ASP.NET을 리디렉션하고 COM+/enterprise service 구성 요소에 대해 응용 프로그램 프록시를 내보냈으며 이 테스트의 .NET Remoting 부분에 Activator.GetObject()를 사용했습니다. 그 결과 이전 테스트와 매우 유사했지만 네트워크 이동이 포함된 탓에 초당 호출 수가 약간 적었습니다.
그림 12. 컴퓨터 간 매개 변수 없이 빈 메서드 호출(종합 기준)
컴퓨터 간 네트워크 이동은 본래 가장 빠른 전송(ES 및 Remoting Binary/TCP)을 조절하는 한편 보다 느린 전송에는 좀처럼 영향을 미치지 않습니다. 이 결과는 주로 네트워크 트래픽 서비스를 위해 빌드된 기술(즉, IIS 및 ASMX)과 비교해 COM+의 크로스 프로세스 통신 메커니즘이 얼마나 효과적인지 분명하게 보여 줍니다.
또한 앞에서 언급한 것처럼 이 테스트는 매우 인위적이며 전송 성능을 설명하기 위한 것이라는 점에 유의합니다.
안녕하세요 이번 강좌에서는 네로버닝롬6을 이용한 동영상 파일을 비디오CD로 만들기에 대해 알아보도록 하겠습니다. 가정집에 보면 보통 오디오(전축?)의 기능중에 비디오시디 플레이가 가능한 기기들이 대부분일 것입니다. 네로를 이용하여 동영상을 비디오 시디로 만들어 놓으면 사용하기 편리하겠죠.
자 그럼 시작해 볼까요.
네로 스타트 스마트에 사진과 비디오에 보시면 비디오 CD만들기라는 ?이 보일 것입니다. 클릭하여 주세요.
클릭 하시면 내 비디오CD라는 창이 뜹니다. 그럼 추가 버튼으로 비디오 시디에 들어갈 동영상 파일을 추가 하셔야 합니다.
자 필자는 DTS_TESTMOVIE.AVI 파일을 추가하려 합니다. 추가 버튼을 눌러 주시면 됩니다.
그럼 파일 분석중이라는 창이 뜰 것입니다. 이부분도 동영상 파일 용량에 따라 분석 되는 시간이 달라집니다. 그럼 다음으로 넘어 갑니다.
레이아웃 설정 부분입니다. 배치 부분인데 머리/바닥의 줄을 삭제 하느냐 마느냐 하는 부분인데 신경쓸 부분 아닙니다.
디폴트 값은 지금 체크되어 있는 값입니다. 이것도 디폴트로 나두시면 됩니다.
배경을 설정하는 옵션입니다. 배경화면을 넣을 수도 있고 배경의 색을 바꾸어 줄 수도 있습니다. 배경모드도 맞춰줄 수 있구요.
문자를 설정하는 ?.
이제 다 설정을 하였다면 다음 버튼을 눌러 주세요.
그럼 레코더기를 선택하시고 디스크명을 써 주신다음 굽기 버튼을 눌러 주시면 됩니다. 그럼 인코딩 작업에 들어갑니다.
인코딩 작업입니다. 흐.... 이작업은 동영상 파일의 크기와 시스템의 사양에 따라 무쟈게 시간이 걸립니다. 그러므로 딴짓으로 시간을 때우시길....... 이 작업이 끝나면 곧바로 레코딩 작업으로 넘어 갑니다.
레코딩 작업중. 레코딩 작업이 끝나면 작업 OVER.
자 레코딩 작업이 끝났습니다. 다음 버튼을 눌러주세요.
그럼 RW의 트레이가 열리면서 끝내기 버튼을 눌러 주시면 됩니다. 자 그럼 재대로 구워졌는지 탐색기로 확인을 할까요.
수 없는 경우가 생긴다. 예를 들어 700메가나 650메가 CD에 용량을 초과하는 데이터를
레코딩 해야 하는 경우, 혹은 800메가 CD에 700메가 이상의 데이터를 레코딩 해야할 때도
오버버닝(Over Burning)을 해야 한다. 물론 네로 버닝롬을 사용하면 700메가를 약간 초과하는
경우에는 별도의 설정조정 없이도 오버 버닝(Over Burning)이 가능하다. 하지만 용량이 10메가 이상 초과되어 오버버닝을 자동으로 처리하지 못할 경우에는 옵션 설정을 변경해 주어야 한다.
그러나 항상 염두해야 할 점이 있다. 오버버닝은 아주 유용하게 사용되는 기능이지만, 역시 한계점과 단점은 존재한다는 점이다. 시디 공간 활용적인 장점도 있지만, 인식에 문제가 생기거나
에러가 발생할 확률이 높다.
여기서는 윈도우XP 운영체제 상에서의 Nero Burning Rom v6.0.0.23을 예로 설명하지만
다른 조건이라 하더라도 그 방식은 대동소이 하므로 참고 하기를 바란다.
간혹 CD-RW가 오버버닝을 지원하지 않는 경우가 있으므로 먼저 자신의 레코더가 오버버닝을
지원하는지 알아봐야 한다.
메뉴에서 레코더 | 레코더 선택을 통해서 간단하게 확인이 가능하다.
레코더 메뉴를 선택하면 빨간네모부분이 오버버닝의 지원 여부를 알려준다. 여기서 지원이 안된다고 나오면 오버버닝은 불가능하다. 참고로 부가적인 정보도 알려준다. (펌웨어 버전, CD Text 지원여부 등)
우선 네로버닝롬을 실행 시킨후에 파일 | 기본 설정 을 선택한다. 고급 기능을 선택한 후 디스크 기록 단위 오버버닝 레코딩을 활성화(아래 사항을 읽으십시오)에 체크하고 밑에 있는 CD 최대 길이에 시간을 입력한다. 이는 입력시간까지 오버버닝을 허용하겠다는 의미다. 870메가 이상의 시디를 구울 때를 대비해서 99분 정도로 입력한다.
위의 설정은 한번만 해주면 계속 유지된다. (버젼 업데이트 시 경우에 따라 재설정 필요함)
이제 700메가 이상의 시디를 구워보자.
레코딩 편집 창의 멀티세션 탭에서 멀티세션이 아님을 선택한다.
이번에는 굽기 탭에서 레코딩 방법을 꼭 디스크 단위 기록 으로 해야만 한다.
그리고 굽기 버튼을 누르면 오버버닝 경고 메시지가 나온다. 과감하게 오버버닝 CD 쓰기 를 누르자.
다음에 오버버닝을 할때에는 멀티세션이 아님과
디스크 단위 기록만 선택해주면 된다.
굽기 성공 후에 꼭 시디롬에 넣어서 이상이 없는지 확인하기 바란다. 왜냐하면 오버버닝을 하면 시디의 인식문제가 발생할 수 있기 때문이다..
역시 800메가 이상의 시디도 무조건 이와 같은 오버버닝으로 구워야 한다. 참고로 왠만한 시디레코더는 오버버닝이 지원되며 왠만한 공시디는10~20메가 정도는 오버버닝이 가능하다. 어떤 시디는 30메가 이상 오버버닝되는 경우도 있다.
지금은 웹 프레임워크의 전성시대라고 해도 과언이 아닐 것이다. 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를 따르고 있다.
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/>으로 추가할 수 있다.
트리 컴포넌트 트리 컴포넌트의 경우 <화면 3>처럼 보이는 전형적인 트리의 형태를 갖추고 있으며, <리스트 2>의 예제를 보면 벌써 몇몇 개발자의 입에서는 탄성이 나올 법한 코드로 이뤄져 있다. DefultMutableTree Node 등 기존 Swing에서 사용하던 트리 컴포넌트와 거의 동일한 형태로 제공되며 사용하기도 상당히 편리하다. JSP의 Scriptlet을 통해 트리 컴포넌트를 구성하는 각 노드들에 대해 생성하고, Root 트리 노드를 기준으로 insert 메쏘드를 통하여 추가할 수 있다. 또한 구성된 트리 구조를 pageContext의 Attribute에 넣어 페이지가 다시 리로딩되거나 호출됐을 때 기존에 구성된 트리 구조를 재사용하도록 하고 있다. 트리 컴포넌트는 <x:tree/> 태그를 부모로 하여 구성되며, value 항목에 스크립트릿으로 TreeModel을 구성하여 pageContext에 셋팅된 값이 보여진다.
<% 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:dataTable/>은 HTML의 Table 형태로 데이터를 표현하는 수단으로 상당히 유용하게 사용할 수 있는 태그이다. 정렬 가능한 테이블은 dataTable이라는 CustomTag에 sortColumn과 sortAscending이라는 속성을 부여해 줌으로써 정렬 가능한 테이블 형태로 보여주는 것이다. <화면 4>에서 Car Type과 Car Color 부분을 클릭하면 순방향 정렬과 역방향 정렬로 토글(toggle)되며 내용이 표시된다. dataTable의 Header 영역에 sort를 할 수 있는 링크를 주기 위해 command SortHeader를 사용하고 있고 화살표의 사용여부를 arrow=”true”로 설정하고 있다. <x:dataTable/>을 부모 태그로 사용하며 각 컬럼마다 <h:column/>을 사용하여 테이블 구조를 나타나게 된다.
HTML 에디터 컴포넌트 HTML 에디터 컴포넌트의 경우 Kupu라는 이름으로 존재하던 WYSIWYG XHTML 에디터를 MyFaces의 컴포넌트로 포함시킨 것이다. HTML 에디터의 경우 상당히 많은 사이트에서 사용되고 있지만 웹 프레임워크 단에서의 UI 컴포넌트로 제공되는 것은 처음이 아닌가 싶다. HTML 에디터의 경우 글자체, Bold, Underline 등의 폰트를 꾸밀 수 있는 버튼과 색상, 정렬 등 다른 상용 HTML 에디터와 비교해도 뒤떨어지지 않는 기능성을 보여주고 있다. HTML 에디터 커스텀 태그의 경우 필수 속성은 없다.
DataScroller 컴포넌트 DataScroller 컴포넌트의 경우 리스트를 표현하는 JSP 단에서 페이지 이동을 할 때 사용하는 컴포넌트이다. 페이지 이동의 경우 몇몇 사이트에서 커스텀 태그를 만들어 사용하는 경우를 보았으나, 거의 대부분은 특별한 표준이 없이 개발되고 있는 것 중에 하나이다. 하지만 리스트의 경우 페이지 이동은 필수적인 컴포넌트인 것을 보면, My Faces에 DataScroller가 있다는 것은 참으로 다행스러운 일이다. 또한 <x:dataScroller/>를 부모 태그로 하여 현재 페이지를 나타낼 pageCountVal 속성이 존재하며, 각 페이지의 navigation의 여부를 paginator 속성의 boolean 타입을 정의하고 보여질 총 페이지 수를 paginatorMaxPages로 정의할 수 있다.
여러 Validation 컴포넌트 MyFaces의 Validation의 경우 썬 JSF RI가 세 가지 정도의 기본적인 Validation을 제공하는 것에 추가적으로 이메일, 신용카드 , ISBN, Equal 같은 상당히 유용한 Validation을 제공한다. Valication을 넣는 방법으로는 <f:validator/> 태그에 validatorId로써 사용하고자 하는 Validator 클래스를 지정하는 방법과 <x:validateXXX/>와 같은 MyFaces의 확장 태그 형태로 사용할 수 있다.
MyFaces의 장밋빛 미래 JSF가 자바 표준 스펙으로써 향후 J2EE의 웹 프레임워크의 중심이 될 것이라는 생각은 그리 어렵지 않게 할 수 있다. 또한 MyFaces가 JSF의 오픈소스 구현체로써 상당히 중요하게 대접(?)받을 것도 쉽게 알 수 있다. MyFaces는 충실한 JSF의 구현체이면서 유용한 컴포넌트들로 무장하고 있다. 다만 조금 걱정이 되는 부분은 UI가 상당히 다이나믹한 국내의 실정에서 얼마나 적용 가능성이 있을지에 대한 우려와 개발자 입장에서 커스텀 태그에 대한 사용이 얼마나 쉽게 받아들일 수 있는가가 문제가 될 수 있을 것으로 보인다. MyFaces는 이클립스와 같은 IDE와의 연계성, J2EE 진영의 주요 벤더에서 JSF를 제대로 지원하는 순간부터 MyFaces의 활용도는 그 어떤 JSF의 구현체보다 활발할 것이다. 아직 제한된 오픈소스만을 사용하는 국내 실정에서 좀 더 많은 오픈소스를 사용하게 되는 계기가 MyFaces를 통해 시작되기를 간절히 바랄뿐이다. [maso]
HP가 국내 시장에서 처음으로 ’AMD 튜리온64’를 탑재하여 출시한 HP-컴팩 NX6125 비지니스 노트북은 업무용 환경에 최적화된 노트북답게 다양한 사양의 모델들이 출시된다. 액정은 제품에 따라 4:3 비율의 14인치 혹은 15인치 액정을 탑재하며 액정 해상도 역시 모델에 따라 XGA(1024*768)와 SXGA+(1400*1050)이 적용되어 있다. 리뷰 모델은 15인치 타입으로서 SXGA+(1400*1050) 해상도의 광시야각 액정이 채용되어 있다.
CPU 역시 제품에 따라 다양하게 구성되어 있는데, ML-40(2.2GHz/ L2 캐시 메모리 1MB), ML-37(2GHz/ L2 캐시 메모리 1MB), ML/MT-34(1.80GHz/L2 캐시 1MB), ML/MT-32(1.80GHz/L2 캐시 512KB), ML/MT-30(1.60GHz/L2 캐시 1MB) 가 제품에 맞춰 선택적으로 사용된다. 리뷰에 사용된 노트북에는 상위급에 해당하는 ML-37(2GHz/ L2 캐시 메모리 1MB)이 탑재되어 있다.
메모리는 DDR333 DDR SDRAM을 사용하는데, 역시 모델에 따라 256MB/512MB/1024MB/2048MB가 기본 탑재되며 리뷰 모델에는 1024MB가 기본으로 채용되어 있다. 본체 바닥에 2개의 메모리 슬롯을 제공하므로 최대 2GB까지 확장 가능하다. 메인보드로는 AMD의 새로운 모바일 CPU인 Turion 64를 위해 ATI가 새롭게 출시한 XPRESS 200M이 사용되었다. 이 칩셋은 명칭에서 짐작할 수 있듯이 소노마 플랫폼에 포함되는 인텔 915 칩셋과 마찬가지로 PCI Express를 지원한다. AMD 대응 칩셋으로는 최초로 PCI Express, DirectX 9에 대응하며 그래픽 코어를 내장하여 통합한 칩이다. 호환 가능한 CPU로는 Turion 64 외에도 모바일Athlon 64, 모바일 Sempron 등 AMD의 모바일 프로세서 제품군이며 칩셋에 내장된 비디오 코어는 RADEON X300에 상당하는 성능을 발휘한다. 그래픽 코어의 작동 클럭은 350MHz이며 메모리의 버스폭 128bit, 파워 매니지먼트 기능인 POWERPLAY 5.0 등을 기본 탑재하고 있다. 또 Dynamic Lane Count Switching 은 CPU의 소비 전력을 최대 30% 정도 줄여 배터리 사용을 최대한 늘려주는 것이 특징이다.
P-ATA 방식의 60GB, 5400rpm 하드디스크와 듀얼 레이어 대응의 DVD 슈퍼 멀티 드라이브가 기본 탑재되며 86키로 구성된 풀사이즈 키보드와 키보드 상단에 무선랜 on/off, 볼륨 조절키를 갖추고 있다. V.92 규격의 모뎀, 10/100Mbps 랜, IEEE802.11b/g 무선랜으로 구성된 네트워크 환경을 지원하고 총 6개 규격의 플래시 메모리(스마트미디어, xD픽쳐 카드, SD/MMC 카드, 메모리스틱/메모리스틱 프로)를 공용할 수 있는 6-in-1 통합 플래시 메모리 슬롯을 채용하고 있다. 또 강력한 보안 기능을 가능케 해주는 스마트 카드 슬롯을 채용하고 있으며 윈도우, 웹사이트 ID 접속 등을 간편하게 할 수 있도록 해주는 지문 인식 센서가 새롭게 추가되어 있다는 점도 빼놓을 수 없는 특징이다. 인터페이스로는 3개의 USB 2.0 단자, 외부 모니터 단자(D-SUB), S-VIDEO 단자, IEEE1394 단자, 헤드폰 단자, 마이크 입력 단자로 구성되어 있다.
6셀의 리튬이온 배터리가 기본 제공되며 본체 사이즈는 길이 328 x 폭 267 x 두께 31mm, 무게는 약 2.72kg이다.HP-컴팩 NX6125 시리즈는 기존 DV1000 및 V2000 시리즈에 별매품으로 제공되는 전용 도킹베이스인 익스펜션베이와 다른 형식의 전용 도킹 베이스(Advanced Docking Station)가 사용되며 노트북을 데스크탑 PC로 활용할 수 있도록 해주는 전용 모니터 스탠드와 무선 마우스/키보드 셋, 노트북을 세워놓을 수 있는 Adjustable Notebook Stand) 등의 다양한 옵션이 마련되어 있어 비즈니스 모델다운 뛰어난 융통성을 갖추고 있다는 점이 특징이다.
HP-컴팩 NX6125의 키보드 성능
NX6125 에는 키피치 19mm, 키스트로크 3mm, 86키로 구성된 풀사이즈 키보드가 탑재되어 있다. 모델명에 ’비즈니스’라는 말이 포함되어 있듯이 NX6125은 업무용 환경에 최적화시킨 모델 라인업이다. 각종 문서, 서식, 오피스 등 키보드 의존도가 높은 작업을 필요로 하는 비즈니스 모델은 키보드 구성 및 편의성에서 AV형 모델과 차별화된 특징을 갖추고 있어야 한다. 최근 거의 모든 제조사들이 키보드를 전문 업체에 외주하고 있기 때문에 기본적인 설계, 배열상의 특징을 제외하면 성능에 있어서 큰 차이를 보이지 않을 정도로 모듈화되고 있다. 이런 이유로 딱히 비즈니스 노트북이라고 하여 체감적으로 확연하게 다른 키보드 성능을 보여준다고는 할 수 없지만, 기본적으로 문서작업에 불편이 없는 안정적인 특징을 갖추고 있어야 함은 분명하다.
이 면에 있어서 NX6125의 키보드는 어떤 모습을 보여주고 있을까?
AMD 튜리온64 CPU를 탑재하여 출시된 NX6125는 인텔 소노마 플랫폼을 탑재하여 앞서 출시된 NX6100 시리즈를 베이스로 하고 있기 때문에 기본적인 키보드 성능은 NX6100 시리즈와 비슷하다. 일부 키보드 배열이 변경되기는 하였지만, 키보드는 비즈니스 모델로서 크게 손색이 없는 성능을 제공해준다고 평가할 수 있다. 풀사이즈 키보드로 규정되고 있는 키피치 19mm와 키스트로크 3mm의 사이즈를 갖추고 있을 뿐 아니라 반발력을 위한 러버돔의 탄성이 충분하기 때문에 각 키들이 충분히 눌리는 느낌을 준다. 러버돔은 전체적으로 탄성이 강한 편이고 누를 때의 압력 분배가 효과적으로 이루어져 있기 때문에 키운용시 경쾌한 느낌이 잘 전달된다.
NX6125의 키보드 내부 구조의 모습이다. 키를 고정시키는 팬터그라프 방식의 격자 구조물은 키보드의 각키를 키보드 판으로부터 든든하게 고정시켜주어 좌우 흔들림이 거의 느껴지지 않으며 어느부분을 눌러도 키가 고르게 눌려지도록 유도해 준다.
우리나라 노트북 사용자들이 민감해하는 부분인 키보드 들썩거림이나 울렁거림에 있어서도 별 문제 없는 모습을 보여주었다. 키보드판이 본체에 단단하게 고정되어 있고 손가락으로 꽤 힘있게 키보드를 눌러도 키보드 일부가 울렁거리는 불안한 모습을 보이지 않았다.
키보드에서 발생하는 소음을 알아보기 위해 노트북 전원을 끈 상태에서 분당 약 500 정도의 속도로 타이핑시 소음 수치를 측정해 본 결과 최대 61dB으로서 평균적인 정도를 나타냈다.
키배열에 있어서도 크게 신경 거슬리는 부분을 볼 수 없었다. 15인치 대형 모델답게 워드 작업시 빈번하게 사용하는 홈/앤드, 페이지 업/다운 키들이 오른쪽에 따로 분리되어 있으며 방향키는 보지 않고서도 조작이 가능하도록 독립적인 위치에 배치되어 있다. 엔터키보다도 큰 오른쪽 쉬프트키의 위치도 한글 키보드 구조에 적합하며 상단의 F키들도 적절한 크기를 유지하고 있어 손이 큰 사람들도 불편 없이 조작할 수 있게 되어 있다. 특히 데스크탑 키보드처럼 F열을 4조씩 구분해 놓았으며 fn와 연동되는 기능키들에 파란색 아이콘을 새겨놓아 가독성을 높여 놓았다. 페이지 업/다운, 홈/엔드 키열도 주키보드열과 구분되어 있어 빠른 조작시 키간의 간섭 현상이 없도록 한 점도 마음에 든다.
배열상 마음에 쏙 들지 않는 부분들도 눈에 띈다. 우선 캡쳐 버튼으로 활용도가 높은 prt sc키를 fn 키와 조합해서 사용해야 한다는 점, 오른쪽 부분에도 핫키열 사용시 필요한 fn 키를 하나쯤 배치하였다면 하는 아쉬움이 남는다. 상향키 좌우의 남는 공간을 적절히 활용했다면 보다 사용이 편리한 구성이 가능하였을 것이다. 몇 가지 세세한 부분들을 제외하면 NX6125의 키보드에서 크게 흠잡을만한 구석은 보이지 않는다.
NX6125는 비즈니스 모델답게 데이터를 보호하기 위한 첨단 안전장치들이 적용되었는데, 그중에 키보드와 관련된 기술이 바로 침수방지 기능이다. 키보드판 바닥면에 폴리에스테르 필름 처리가 되어 있는데, 이 폴리에스테르 필름은 갑작스러운 사고로 노트북 키보드 위에 물을 쏟았을 때, 물방울이 PC 내부에 침수되는 시간을 지연시켜 데이터를 안전하게 저장한뒤 종료하여 불의의 사고로 데이터를 잃어버리는 일이 없도록 해준다.
침수 방지 기능을 정확하게 표현하자면, 갑작스럽게 키보드 위로 물이 쏟아진 상황에서 시스템을 완전히 보호할 수 있다는 것이 아니라 작업중인 파일을 저장하고 시스템을 정상 종료하여 하드디스크에 저장되어 있는 데이터를 보호할 수 있도록 시스템 내부로 물이 침투하는 시간을 지연시켜 준다는 뜻이다. 즉 물이 쏟아진 후 어느정도 시간이 지나면 키보드 위로 쏟아진 물은 시스템 내부로 침투하여 노트북을 완전히 망가뜨릴 수도 있다는 점을 기억할 필요가 있다. 침수 방지 키보드의 목적은 ’데이터를 보호하자는 것이지 터프북처럼 시스템 자체를 보호하자는 의도’가 아니기 때문이다.
HP-컴팩 NX6125의 액정 성능
사진설명 : NX6125 액정의 자연색 이미지 출력 상태
NX6125에는 모델에 따라 14인치 또는 15인치 액정이 탑재되며 14인치의 경우 XGA(1024*768) 해상도를 지원하고 15인치의 경우 XGA(1024*768), 또는 SXGA+(1400*1050) 해상도를 지원한다. 리뷰에 사용된 제품은 NX6125의 상위 모델이며 4:3 비율의 15인치 SXGA+(1400*1050)액정이 액정이 탑재되었다.
얼마전까지만 해도 노트북 액정을 분류하는 기준이라봤자 사이즈를 표시하는 인치와 정보 표시량을 결정하는 해상도 정도가 고작이었으나 최근에는 제품 컨셉에 따라 다양한 종류의 액정이 탑재되는 추세이다. 먼저 우리가 흔히 광시야각 액정이라고 부르는 타입은 Fringe Field Switching 기술을 이용한 것으로서 TFT 액정의 약점으로 지적되는 시야각 성능을 높이기 위해 무광의 저반사 보상 필름류를 사용한 액정을 말한다. 시야각에 따라 액정의 가독성이 떨어지는 이유를 ’백라이트가 충분히 밝지 못해서’라고 생각하겠지만, 근본적인 이유는 백라이트의 내광과 외부로부터 유입되는 외광 사이의 간섭 현상으로 인해 콘트라스트 왜곡이 발생하기 때문이다. 따라서 저반사 보상 필름이 외부에서 유입되는 빛을 조절하여 액정 자체에서 표현되는 색감을 정확하게 식별할 수 있도록 조절해 주는 것이다.
사진설명 : NX6125 액정의 자연색 이미지 출력 상태
두 번째 타입은 HP 브라이트뷰 방식과 같이 액정 표면에 저반사 하드코팅이 적용된 액정이다. 즉 유광의 저반사 하드 코팅층으로 내부 백라이트 빛의 확산을 막아주고 저반사 코팅층이 추가되어 주변의 광원 유입에 따른 콘트라스트 왜곡 및 가독성 저하를 방지하여 선명한 영상을 가능케 하는 새로운 방식의 기능성 액정이다. 원래 저반사 하드코팅을 채용한 액정은 동영상 재생용으로 특화된 액정이기 때문에 기존 노트북용 LCD에 비해 매우 밝은 패널이 사용되며 색감의 선명도를 높이기 위해 하이콘트라스트 필터가, 외광 반사에 따른 시야각 저하를 방지하기 위해 반사 방지 코팅이 적용되어 있다. 보통 일반 노트북에 사용되는 액정의 평균 밝기는 170-180cd 정도이지만, 저반사 하드코팅층이 추가된 브라이트뷰 액정과 같은 기능성 패널은 제품에 따라 200-350cd의 고휘도 백라이트를 내장하고 있기 때문에 DVD-Video 재생시 진가를 발휘한다.
사진설명 : NX6125 액정의 자연색 이미지 출력 상태
세 번째 타입은 주로 보급형 노트북에 사용되고 있는 일반 형태의 액정으로서 평균 170-180cd 내외의 백라이트를 내장하고 있으며 저반사 보상 필름류가 추가되지 않은 일반적 형태의 TFT 액정이다. 특수 기능이 첨가된 광시야각 액정에 비해 시야각 성능 및 색간 표현력은 다소 떨어지지만 가격이 저렴한데다 일반적인 환경은 물론 멀티미디어 환경에서도 크게 문제없을 정도의 기본적인 성능은 갖추고 있다. 이 외에도 밝은 외부에서의 원활한 사용을 가능케해주는 투과형 광시야각 액정, 더블 백라이트 내장으로 휘도를 600cd까지 끌어올린 고휘도, 고선명 액정, 도시바 리브래또 U100에 최초로 적용된 LED 백라이트 방식의 액정 등 크기 만큼이나 액정 설계 또한 다양해지고 있는 추세이다.
사진설명 : NX6125 액정의 자연색 이미지 출력 상태
HP NX6125에는 무광의 저반사 보상 필름이 추가된 Fringe Field Switching 방식의 광시야각 액정이 탑재되어 있다. NX6125의 액정은 위의 사진들에서 볼 수 있는 것처럼 뛰어난 해상력과 색간 표현력을 갖추고 있다. 동영상 재생에 특화되어 있는 저반사 하드코팅 액정만큼의 화사함이 느껴지지는 않지만, 전체적으로 또렷하고 균일감 있는 출력 상태를 보여주고 있으며 원색의 표현이나 색간 표현력도 상당히 뛰어난 편이다. 특히 좌우 시야각에서 만족스러운 모습을 보여주었는데, 아래의 사진을 통해 NX6125의 시야각 성능에 대해 알아보도록 하자.
먼저 30도 각도에서 본 모습이다. 30도 각도에서 원색 이미지를 보았지만 중앙부에서 약간의 색감 과장 현상이 발견되는 것 외에는 정면에서 보는 것과 크게 다르지 않은 상태를 보여주고 있다.
이번에는 아이엠샘 DVD 타이틀의 한 장면을 통해 시야각 성능을 알아보자. 약 30도 각도의 측면에서 보았다. 각도에 따라 특성을 많이 타는 동영상이지만 정면에서 보는 것과 크게 차이 없는 출력 상태를 유지하고 있음을 볼 수 있다.
다음은 약 측면 60도 가까운 각도에서 원색 이미지를 본 모습이다. 30도 각도일 때보다 중앙부분의 색과장 현상이 좀 더 발생하지만 윤곽, 색감 등 이미지 식별에 큰 어려움이 없음을 볼 수 있다.
역시 아이엠샘 DVD 타이틀의 동일 장면을 측면 60도 가까운 각도에서 본 모습이다. 30도일 때보다 흐리게 보이기는 하지만 여전히 장면을 구분하는데 큰 어려움이 없는 모습이다.
측면 80도 가까운 각도에서 원색 이미지를 본 모습이다. 이미지의 색감이나 선명도, 배경의 디테일이 떨어지기는 하지만 출력물의 식별이 가능한 상태를 유지하고 있다.
측면 80도 가까운 각도에서 아이엠샘 DVD 타이틀의 동일 장면을 본 모습이다. 색감이 약화되고 선명도가 저하되어 쾌적한 식별 상태를 보여주지는 못하지만 여전히 눈에 띄는 색과장 현상이나 색반전 현상은 크게 나타나지 않으며 장면의 구분 역시 가능함을 확인할 수 있다.
이번엔 상위 30도에서 본 모습이다. 우수한 측면 시야각에 비해 상위 시야각은 어느 정도 떨어지는 편이지만 그 정도가 타 모델의 액정보다 꽤 양호함을 볼 수 있다. 선명도가 떨어지기는 하지만 여전히 출력 장면의 윤곽의 식별이 가능하며 표시되지 않고 있으나 조금 물빠진 옷감 같기는 해도 이미지의 색감 역시 구별이 가능한 정도를 보여주고 있다. 보통 상위 30도 각도에서는 이미지의 색감 구별도 어려운 것이 일반적이나 NX6125의 액정은 상위 시야각에서도 어느 정도 안정적인 성능을 확보하고 있다.
하위 30도에서 본 모습이다. 상위 시야각과는 반대로 전체적으로 색반전 현상이 발생하며 짙은 원색이 사용된 부분에는 콘트라스트 왜곡 현상으로 인해 명확한 색감 구별이 쉽지 않다. 그러나 타 모델에 비해서는 좀 더 나은 출력 상태를 유지하고 있음을 볼 수 있다. 종합적으로 NX6125 액정의 시야각 성능은 상당히 양호하다고 평가할 수 있겠다.
다음은 최대 밝기시와 최저 밝기시의 비교이다.
최고 밝기로 지정한 상태의 모습이다. AV형 액정과 같이 강렬한 대비의 색감을 보여주지는 못하지만, 전체적으로 색감을 또렷하게 출력하며 우수한 색간 표현력을 갖고 있음을 확인할 수 있다. 주변이 밝은 외부에서까지 쾌적한 가독성을 확보해주지는 못하지만, 전체적으로 밝고 선명한 출력 상태를 유지하고 있다. 특히 좌우상하 시야각 성능 역시 우수하기 때문에 멀티미디어 환경에서도 불편 없이 사용할 수 있을만한 성능을 갖추고 있다고 평가할 수 있다.
최저 밝기로 지정한 상태의 모습이다. 보통 15인치급 모델이라면 휴대성을 염두해 두고 설계되지 않는 것이 관례이지만, NX6125는 잦은 환경 변화가 요구되는 비즈니스 대응의 모델답게 배터리 전원 관리 기능이 강화되어 있다. 전원 절약을 위해 최저밝기의 휘도가 기본적인 화면 식별에 필요한 최소한의 상태로 지정되어 있음을 볼 수 있다. NX6125에서 액정 조절폭은 상당히 큰 편이며 최저 밝기로 지정했을 때에는 단순 작업 외 멀티미디어 작업은 사실상 어렵다고 볼 수 있다. 반면 최저 밝기로 지정한 상태에서 전원 관리 효과는 우수한 편이다.
HP-컴팩 NX6125의 내부구조
HP가 NX6125 시리즈에 AMD 튜리온64 플랫폼을 어떻게 구현하였는지 이제 내부구조를 알아보도록 하자.
NX615 내부를 구경하기 위해서는 키보드 상단의 버튼부를 포함하고 있는 커버를 먼저 제거해야 한다. 배터리 베이에 있는 3개의 고정 볼트를 제거한 뒤 - 자 드라이버로 조심스럽게 커버의 물림을 제거하면 분리가 된다. 주의할 점은 NX6125의 경우 일반 십자 볼트가 아닌, 별모양의 볼트를 사용하고 있다는 점이다. 규격에 맞는 소형 - 자 드라이버로도 볼트제거는 가능하지만 잘못하면 볼트를 망가뜨려 낭패를 당할 수 있음이다.
사진설명 : 본체 상단 케이스 재료는 강화 플라스틱이며 지문 인식 센서와 터치패드 기판을 포함하고 있다.
본체 상단 케이스를 제거하면 NX6125의 내부 구조가 한 눈에 들어온다. NX6125는 일반적인 노트북 설계 패턴을 잘 보여주고 있다. 메인보드를 왼쪽 부분에 배치하고 광학드라이브는 오른쪽에 위치해 있으며 두께 부담을 줄이기 위해 후면부를 배터리 베이로 이용하고 있다. 스피커는 음원 재생시 출력에 방해를 받지 않는 위치인 전면부에 배치되어 있고 주요 포트부는 후면 좌측과 왼쪽 측면부에 집중되어 있다.
주요 칩셋 부분으로 들어가기 앞서 NX6125의 보안 기능에 대해 먼저 짚고 넘어가보자. HP-컴팩 NX6125에는 "HP 프로텍트 툴스" 기능에 새롭게 크레덴셜 매니저(Credential Manager)가 통합되어 있다. 이 기능은 사용자가 웹사이트를 포함한 네트워크나 보안 관련 애플리케이션에 액세스할때 싱글사인온(SSO)를 포함한 각기 다른 비밀번호를 사용자의 노트북이 아닌 스마트 카드 또는 TCG(Trusted Computing Group)의 기준에 근거한 엠베디드(마이크로프로세서에 미리 정해진 특정한 기능을 수행하는 소프트웨어를 내장시킨 시스템 형식) 보안칩에 저장한다. 사진에 보이는 칩셋이 바로 NX6125의 보안을 책임지고 있는 엠베디드 보안 칩셋이며 PC와는 별도로 저장 관리되기 때문에 부정한 방법으로 노트북에 접근하려는 시도 자체를 원천적으로 봉쇄하여 데이터를 안전하게 지켜내는 역할을 담당한다.
NX6125의 보안 기능은 스마트 카드 프리부트(Pre-Boot) 인증과 드라이브락(DriveLock)이라고 불리는 하드 드라이브 보호 기능을 통해 하드 드라이브에 저장된 사용자의 데이터에 무단 접속하는 것을 막아주며, 하드 드라이브를 제거해 다른 PC에서 접속할 경우에도 무력화할 수 없다. 한편 무선 보안 기능으로는 AES(Advanced Encryption Standard) 암호화를 지원하는 WiFi 보안 액세스 버젼 2를 지원하며, 이 기능들은 시스코 무선 네트워크 환경에서 완벽하게 호환된다.
국내의 정보보안 관련 사업이 위축되고 있는 것과 대조적으로 기업들의 보안 사고와 정보의 해외 유출은 크게 증가하고 있다. 절대적인 작업이 PC를 기반으로 이루어지고 있는 현실에서 최종 단말기로 이용되고 있는 노트북 보안의 중요성은 아무리 강조해도 지나치지 않다. 휴대가 가능하다는 이점 때문에 이동성을 중시하는 영업 사원과 출장시 애용되는 노트북에 있어 보안상의 취약점은 분실 또는 도난으로 인한 데이터 탈취 및 위변조를 꼽을 수 있으며, 최근 들어 기업용 노트북 모델에 각종 보안 인증 장치가 내장되는 것도 이와 같은 현실을 반영한 것이라고 할 수 있다.
사진설명 : 네트워크 전문 회사인 Lankom 사의 기가비트 이더넷 - SQ-H40B-2 칩셋
NX6125에 탑재된 튜리온64 CPU, 메인보드 칩셋의 쿨링시스템 모습이다. 구조만 봐서는 인텔 센트리노 플랫폼이 사용된 것과 같은 착각이 들게 할 정도 작고 간단한 장치가 사용되어 있는데, 이는 그만큼 새로운 64비트 모바일 CPU인 튜리온64의 발열에 자신이 있다는 뜻으로 해석할 수 있다.
CPU와 메인보드 칩셋의 발열을 책임지고 있는 쿨링장치 - 인텔 센트리노 플랫폼을 탑재한 슬림형 모델의 것과 크게 다르지 않은 구조를 보여주고 있다.
NX6125에는 튜리온64 ML-37 CPU가 탑재되어 있는데, 이 제품은 동작 클럭 2GHz/ L2 캐시 메모리 1MB으로서 인텔 Pentium-M760(2.0GHz, L2 캐시 2MB)에 대응한다. 소비 전력은 35W로서 소노마에 포함되는 도선 CPU보다 약 10W 정도 높다.
AMD는 튜리온64가 64비트 환경을 지원한다는 점이 특징이다. 특히 CPU에 메모리컨트롤러가 내장되어 있어 DDR333으로도 빠른 데이터 처리가 가능하며 HyperTransport 기술로 최대 1600㎒ 입출력(I/O) 시스템버스를 제공한다는 점이 인텔 모바일 CPU와의 가장 큰 차별성이다.
HyperTransport 기술이란 새로운 고속력, 고성능의 point-to-point 링크로서 마더보드의 통합 circuits를 상호 연결하는 기술이며 같은 수의 핀이 사용된 PCI bus보다 훨씬 더 빠른 속도로 동작하는 것이 특징이다. HyperTransport 는 이전에 Lighting Data Tranport 혹은 LDT로 코드명명되었다. HyperTransport 기술은 AMD에 의해 개발되었으며 AMD의 협력 업체들의 도움으로 성능의 안정성을 이루었다. 이 기술은 주로 IT 와 Telecomm 산업을 위해 개발되었지만 고속력, 저위험성, scalability(반도체 소자가 비례 축소 가능한 성질)이 필요한 분야라면 응용이 가능하다. 또 HyperTransport 기술은 AMD 마이크로프로세서의 성능을 최대한 이끌어내기 위해 개발된 기술이기도 하다.
사진설명 : NX6125에 탑재된 튜리온 64 CPU는 자유로운 탈부착이 가능한 소켓 형식이기 때문에 상위 프로세서로 업그레이드도 가능하다.
제거 방식은 데스크탑 PC와 비슷한데, CPU 소켓 측면에 마련되어 있는 고정 레버를 위로 올리면 CPU가 제거되고 다시 장착하기 위해서는 CPU를 소켓에 넣고(삼각형 표시를 잘 맞춰서) 레버를 아래로 내리면 된다. 쉽게 교체 가능한 구조이지만, CPU 업그레이드를 하려면 노트북을 먼저 분해해야하니 CPU 업그레이드는 노트북 초보자들에게 언제나 요원한 작업이다. ^^;
통상적으로 인텔 계열에 비해 리테일 가격이 저렴한 AMD 제품답게 튜리온64의 단품이 저렴하게 판매된다면 국내에서도 노트북 PC 업그레이드가 저변확대되는 계기로 작용하지 않을까 기대된다.
NX6125의 메인보드는 Turion 64를 지원하기 위해 ATI가 새롭게 출시한 XPRESS 200M 칩셋이 사용되었다. 이 칩셋은 인텔 915 칩셋과 마찬가지로 PCI Express를 지원한다. DirectX 9에 대응하는 그래픽 코어를 내장한 통합하고 있다. Turion 64 외에도 모바일 Athlon 64, 모바일 Sempron 등 AMD의 모바일 프로세서 제품군의 CPU를 사용할 수 있다. 칩셋에 내장된 비디오 코어는 RADEON X300에 준하는 성능을 발휘한다고 ATi측은 밝히고 있으나 통합형 칩셋 답게 CPU 타입에 따라 성능이 많이 좌우된다. 그래픽 코어의 작동 클럭은 300MHz이며 메모리의 버스폭 128bit, 파워 매니지먼트 기능인 POWERPLAY 5.0 등을 기본 탑재하고 있다.
사진설명 : 15인치 모델답게 메인보드는 넉넉한 크기로 설계되어 있다.
사진설명 : NX6125 메인보드의 바닥면 - 대칭을 이루고 있는 2 개의 램슬롯과 도킹스테이션 접속 단자가 보인다.
본체 전면부 좌우로 스테레오 내장 스피커가 배치되어 있다. 스피커 출력구가 전면부를 하단을 향하고 있기 때문에 음원 출력에 방해 요소가 크지 않으며 출력만큼은 영화감상을 불편 없이 즐길 수 있는 수준 정도는 유지하고 있다. 제원상 NX6125의 내장 스피커는 노트북용으로서는 넉넉한 편인 1.5W이다.
큼지막한 캐비넷을 포함하고 있는 NX6125의 스테레오 스피커는 저음에서 고음까지 고른 음역 재생 성능을 갖추고 있으며 최대 음량시 음의 화울링 현상이나 음이 찌그러지는 현상을 볼 수 없었다. 비즈니스 모델로서는 불만 없는 스피커 성능을 갖추고 있다고 평가할 수 있다.
AMD 튜리온64의 장점이라면 인텔의 센트리노 플랫폼과는 달리 메인보드와 무선랜의 조합이 자유로운 단품구성이라는 점이다. AMD는 그래픽 콘트롤러, 무선랜 등을 자유롭게 구성할 수 있는 개방형 아키텍처를 채택하였기 때문에 AMD 튜리온 64를 채택한 노트북 제조업체들은 부품 구성에 있어 제약을 받지 않아 보다 융통성 있고 다양한 스펙 구성이 가능하다.
그동안 센트리노의 강력한 마케팅을 앞세운 인텔의 독점 정책으로 새롭게 주기판 사업에 뛰어든 엔비디아, ATI를 비롯하여 메인보드 전문 업체들인 비아(VIA), 시스(SIS) , 네트워크 전문 업체인 브로드컴, 아테로스, 마벨, 리얼텍 등 네트워크 전문 업체들이 공정한 경쟁의 기회를 상실했더 것이 사실이다. 따라서 부품 조합이 자유로운 튜리온64를 노트북 제조사들이 많이 선택할수록 이들 전문 업체들의 입지가 더욱 단단해질 것임은 당연하다.
NX6125의 무선랜은 브로드컴사의 802.11b.g 모듈이 탑재되어 있다. 802.11g는 802.11b와 같은 2.4GHz의 대역폭을 갖고 있지만 전송속도에 있어서는 802.11a와 동일한 최고 54Mbps의 속도를 발휘한다. 네트워크 전문 업체 모듈을 사용한 것은 좋은데, 802.11a/b/g 규격을 모두 지원하는 제품을 사용하지 않은 것은 아쉬운 부분.
HP-컴팩 NX6125에는 사용 빈도가 급격하게 떨어진 PCMCIA 슬롯을 과감하게 삭제하고 PCMCIA 규격을 대체하는 Express Card 슬롯을 새롭게 포함시켰다. Express Card 규격(최대 312Mbps)은 기존 PCMCIA 규격(최대 132Mbps)에 비해 2배 이상 향상된 전송 속도를 갖추고 있는 것이 특징이다. 두 개의 슬롯 중에서 상단이 Express Card 슬롯이고 하단은 강력한 보안 기능을 지원하는 스마트카드 슬롯이다.
HP-컴팩 NX6125에는 6in1 플래시메모리카드 슬롯을 내장하고 있다. 6가지 메모리카드를 하나의 슬롯에서 사용가능하다는 것인데, SD/MMC 카드, 메모리스틱/메모리스틱 프로, 스마트 미디어카드, xD 픽쳐 카드로 구성된 6가지 메모리카드 공용 슬롯을 지원한다. 최근 디지털 카메라, 메모리카드 슬롯을 갖춘 카메라폰, PDA의 활용도가 폭발적으로 증가하고 있으며 거의 대부분의 최신 노트북 PC에 2가지 혹은 4가지 규격의 플래시 메모리를 사용할 수 있는 공용 슬롯이 기본 채용되기 때문에 플래시 메모리 슬롯 채용만을 놓고 특별한 장점이라고 소개할 수는 없겠지만, 타 제조사의 모델에는 대부분 빠져있는 스마트미디어, xD 픽쳐 카드를 포함한 6가지 미디어를 복합적으로 사용할 수 있는 멀티 슬롯을 갖추고 있다는 것은 NX6125의 장점이다.
HP-컴팩 NX6125의 업그레이드
사진설명 : 상판과 마찬가지로 심플한 구성을 보여주고 있는 NX6125의 바닥면 모습
본체 바닥 중앙부 우측으로 메모리가 탑재되는 슬롯이 위치해 있다. 사진에서 볼 수 있는 것처럼 커버를 제거하면 메모리 슬롯이 대칭형태로 드러난다. 333MHz PC2700 DDR SDRAM이 사용되며 리뷰 모델에는 기본 1024MB가 탑재되어 있으나 국내 출시되는 제품에는 512MB가 기본 탑재된다. 최대 2GB까지 확장이 가능하며 두 개의 슬롯이 모두 노트북 바닥면에 위치해 있기 때문에 업그레이드가 용이하다.
본체 전면부 좌측으로 하드디스크 베이가 위치해 있다. 시게이트사의 모멘터스 제품이 기본 내장된다. 유체베어링 기술이 적용되었으며 5400RPM 속도, 소비전력 5V, 8MB의 버퍼메모리를 탑재하고 있는 고성능 모델이며 데이터 저장 용량은 60GB이다.
하드디스크 커버를 벗기고 사진에 보이는 회색 필름을 앞으로 잡아당기면 간단하게 분리되기 때문에 초보자들도 업그레이드가 용이하다.
HP-컴팩 NX6125의 배터리 성능
11페이지로 이어집니다. 다음을 클릭하십시오.
NX6125 에는 10.8V 4800mAH 용량의 6셀 리튬이온 배터리가 부속된다. 면적이 큰 15인치 액정을 감당하기에는 배터리팩 용량만보면 다소 작아보이지만, 용량에 비해 생각보다 우수한 배터리 구동 상태를 보여주었다. 액정 밝기를 최대로 지정하고 전원 관리 옵션을 사용하지 않은 상태에서 DVD 타이틀을 구동시켜 본 결과 약 1시간 45분의 연속 사용이 가능하였으며 액정 밝기를 최소로, 전원 관리 옵션을 최대로 지정한 상태에서는 약 2시간 25분의 연속 구동이 가능하였다.
액정을 가장 밝게 지정하고 전원관리 기능을 사용하지 않고 웹서핑, 텍스트 작성, 오피스 작업을 하였을 때에는 약 2시간 20정도의 사용시간을 나타내었으며 전원관리 옵션을 최대로 지정하고 액정의 밝기를 최소로 한 상태에서는 약 3시간 10분 정도 사용이 가능했다.
사진설명 : 외부에서 전원 없이 노트북을 장시간 사용해야할 필요가 있는 사람들을 위해 본체 바닥면으로 부착되는 추가 배터리를 옵션으로 마련하여 활용성을 높였다.
HP-컴팩 NX6125의 발열과 소음
AMD 계열의 CPU가 탑재되어 있는 모델이니만큼, 발열과 소음 정도에 대해서도 관심이 높을 것이다. 실내 온도 25도의 환경에서 약 3시간 동안 DVD를 구동시킨 상태에서 노트북 각 부분별 발열 상태를 적외선 온도계로 측정해 보았다. 노트북 팜레스트 오른편 상단의 발열은 33.6도를, 왼편 팜레스트 상단은 33도로 양호하였고 키보드 중앙 하단은 약 38.6도를 나타냈다.
노트북 바닥면으로는 CPU가 탑재되어 있는 부분이 42.6도로 가장 높았으며 메모리가 탑재되어 있는 부분의 발열이 42.4도를 나타냈고 광학 드라이브가 탑재되어 있는 부분이 37.8도를, 하드디스크가 탑재되어 있는 부분이 36.2도를 그리고 오른쪽 하단이 36.8도를 나타냈다. CPU 및 메모리 탑재 부위가 동급의 센트리노 탑재 모델에 비해 약간 높게 측정되었지만 그 외에 부분에서는 안정적인 수치를 나타냈음을 확인할 수 있다. 특히 그동안 발열에 취약한 것으로 알려져왔던 AMD 제품인데다 64비트 기반, 소비전력 35W, 코어클럭 2.0GHz의 고성능 CPU를 탑재한 모델임을 감안할 때 NX6125의 발열은 양호한 수준이라고 평가할 수 있겠다.
일단 NX6125의 팬소음의 정도를 알아보기 위해 ’데시벨 측정기’를 사용해 보았다. 보통 무소음실에서 측정이 되어야 정확한 소음치를 구할 수 있겠지만, 일반적인 환경이 무소음실과는 차이가 큰 만큼 일상적인 환경에서 소음 증가의 정도를 알아보는 정도로 의미를 두면 될 것이다.
보통 데시벨 측정기로 50dB 정도면 사무실, 식당, 백화점 내의 소음 정도로 규정하며 65dB은 큰 음성, 70dB은 전화벨, 도로변의 소음 정도로 규정한다. 소음에 따른 영향을 살펴보면 50dB 정도의 소음에서는 호흡, 맥박수 증가하고 계산력과 수면 깊이가 저하되며 65dB 이상이면 정신 집중력 저하되며 TV 시청, 라디오 청취, 전화 통화 등에 장애가 된다고 하는데, 생활 소음을 기준으로 규정된 결과치를 테스트 환경상 노트북 발열구에 근접시켜 측정되는 결과치에 대응할 수는 없다. 따라서 소음 테스트에서 도출되는 결과는 이전에 동일 환경에서 측정된 값들과 비교하여 상대적인 비교 자료로 사용하는 것이 바람직하다.
노트기어 사무실은 모든 동작을 멈췄을 경우 평균적으로 35dB 정도의 소음이 발생하는데, 이를 기준으로 NX6125의 구동에 따른 소음 증가율을 살펴보았다. 쿨링팬이 풀스피드로 동작할 때의 최대 소음 수치는 61.7dB로서 소노마 탑재의 NX6125과 별차이 없는 소음 상태를 나타냄을 볼 수 있다.
광학 드라이브가 구동할 때 발생되는 최대 소음은 61.2dB로서 팬소음보다 조금 낮은 정도를 나타냈다. 초기 액세스시에는 얼마간의 소음이 발생하지만 이후에는 비교적 정숙한 동작을 보여 준다.
HP-컴팩 NX6125의 실제무게
이번에는 전자저울을 사용하여 NX6125의 실제 무게를 측정해 보았다. 제조사에서는 NX6125의 무게를 2.72kg으로 규정하는데, 실제 측정해본 결과 규정 무게를 조금 초과하는 2.79kg을 나타냈다. 70g의 차이가 크다고 느껴지지는 않지만 하지만 실제 무게가 규정 무게보다 초과되는 것은 그다지 기분좋은 현상은 아니다. 물론 규정보다 모자라는 것은 얼마든지 환영이다. ^^;
HP-컴팩 NX6125의 시스템 성능
Athlon 64 전용 메인보드인 RADEON XPRESS 200M은 DirectX 9 대응의 비디오 코어를 내장하고 있는데 ATI는 RADEON XPRESS 200M에 탑재된 그래픽 코어가 RADEON X300을 베이스로 하였다고 밝히고 있다. 코어 클럭은 300MHz이며 파이프라인 수는 4개로서 기본적으로 X300 스펙과 동일하다.
비디오 메모리는 메인 메모리를 공유하는 UMA 방식이며 별도로 로컬 버퍼 탑재도 가능하다. 특히 UMA와 로컬 버퍼를 이용할 경우에는 HyperMemory라고 불리는 비디오 메모리 메니지먼트 기능이 texture 데이터의 사이즈, 사용 빈도, 메모리 밴드폭 등을 적절히 조율하여 효율적으로 데이터의 전송을 배분한다. 노트북용 64비트 CPU인 튜리온 전용 칩셋인 RADEON XPRESS 200M에 탑재되어 있는 그래픽 칩셋의 그래픽램은 메인메모리에서 공유하는 방식으로 최대 128MB까지 사용이 가능하다.
3D MARK 2001SE로 테스트 결과 800 X 600 32Bit 설정에서는 5309점을 기록하였고 1024 X 768 32Bit 설정에서는 4286점을 마크했는데, 일단 이는 ATI Radeon 7500 칩셋의 성능을 상회하는 결과치에 해당한다. 3DMARK 2001SE를 기준으로 볼 때 DDR2 533 메모리를 사용하는 소노마(공유 그래픽 내장) 모델에 비해 약 100-130여점 뒤지는 결과를 보였다. L2 캐시 1MB, 소비전력 35W 스펙의 튜리온 64 ML CPU를 기반으로한 Xpress200M의 내장 그래픽 코어의 성능만 놓고 보면 인텔 915GM에 내장되는 GMA900의 성능과 대등한 정도라고 볼 수 있다.
자바 플랫폼의 경우, 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을 입력한다. 또한 앞서 얘기했듯이 기본값으로 설정되어 있는 핸들러는 없다.
두 번째 쌍의 메소드는 각자가 관리하는 쿠키 캐시로부터 쿠키를 얻고 이를 설정할 수 있게 한다.
여기서 보듯이 핸들러를 작성하는 일은 실제로는 간단하다. 그러나 캐시를 정의하는 데는 약간의 추가 작업이 더 필요하다. 일례로, 커스텀 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을 아규먼트로 취하고 사이트로부터 수신한 모든 헤더를 표시한다. 각 헤더는 아래의 형태로 표시된다.
이는 해당 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-cookie나 Set-Cookie2 같은 다른 해당 헤더로 확장될 수 있다.
쿠키의 리스트를 확보한 후 각 쿠키를 반복(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(); } }
‘Cache’로 시작되는 행은 저장된 캐시를 나타낸다. 저장된 쿠키가 즉시 반환되지 않도록 put() 메소드 전에 get() 메소드가 어떻게 호출되는지에 대해 유의하도록 할 것.
쿠키와 URL 접속을 이용한 작업에 관해 자세히 알고 싶으면 자바 튜토리얼의 Custom Networking trail(영문)을 참조할 것. 이는 J2SE 1.4에 기반을 두고 있으므로 튜토리얼에는 아직 여기서 설명한 CookieHandler에 관한 정보가 실려 있지 않다. Java SE 6 ("Mustang")(영문) 릴리즈에서도 기본 CookieHandler 구현에 관한 내용을 찾아볼 수 있다.
지난 주 뉴스레터로 발송되었던 테크팁 퀴즈의 정답을 알려드립니다. 참여해주신 모든 분께 감사드리며, 당첨자는 10월 26일 SKDN 홈페이지에서 발표합니다.
다음 중 "pull"파서는?
Xerces
SSAX
Sun Java Streaming XML Parser (SJSXP)
모두 아님
정답 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 소개 참조
다음의 WSDL 바인딩 style/use 조합 중에서 WS-I 호환이 되는 것은?
RPC/encoded
RPC/literal
Document/encoded
Document/literal
정답 b와 d RPC/literal과 Document/literal은 WS-I 호환이 된다. style/use 조합에 관한 자세한 내용은 2005년 8월 23일자 J2EE 테크팁 JAX-RPC 바인딩 스타일과 사용 속성 참조
다음 중 JSF(JavaServer Faces) 기술에서 유효한 바인딩 식은?
#{invoice.customerName}
${invoice.date}
#[customer.status == 'VIP']
모두 아님
정답 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(영문)참조.
message-driven 빈에 관한 설명 중 맞는 것은?
message-driven 빈의 인스턴스는 특정 클라이언트의 데이터 또는 대화 상태를 유지하지 않는다.
message-driven 빈의 모든 인스턴스는 서로 동등하므로 컨테이너는 어떠한 message-driven 빈 인스턴스에도 메시지를 할당할 수 있다.
하나의 message-driven 빈이 복수 클라이언트의 메시지를 처리할 수 있다.
모두 맞음
모두 틀림
정답 d. 모두 맞음 모두 맞음. MDBs에 관한 자세한 내용은 2005년 5월 18일자 J2EE 테크팁 EJB 2.1로 메시지 구동 빈 이용하기의 내용 참조
스트링 리스트를 정렬하는 방법은?
TreeList 클래스를 사용한다
TreeMap 생성자에 List를 패스하고 맵을 반복한다
Arrays의 sort 메소드를 호출한다
Collections의 sort 메소드를 호출한다
정답 d. Collections의 정렬 메소드를 호출한다 자세한 관련 정보는 2004년 7월 16일자 J2SE 테크팁 리스트를 분류하고 섞기 위한 COLLECTIONS의 메소드 사용하기를 참조한다. Collections Framework에 관한 부분은 이미 테크 팁을 통해 여러 차례에 걸쳐 다룬 바 있다.
다음 중 Math 클래스로 할 수 없는 연산은?
특정 수의 기수 10 로그를 계산한다
특정 String에 대한 해시코드를 생성한다
유니코드 문자의 세제곱근을 계산한다
두 점간의 유클리드 거리를 계산한다
정답 b. 특정 String에 대한 해시코드를 생성한다. String의 해시코드 계산은 String 클래스에 의해 이루어지며, 다른 세 연산은 JDK 5.0에 추가된 Math 클래스 메소드를 통해 수행할 수 있다. 유니코드 문자의 세제곱근을 계산하려면 계산에 앞서 문자를 char에서 int로 변환한다. 자세한 내용은 2004년 11월 11일자 J2SE 테크팁 MATH 클래스에서의 새로운 점참조
/** * 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; }
/** * 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 {
/** * 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();
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;
/** * 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 {
// 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();
/** * 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);
/** * 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);