2010년 6월 29일 화요일

MTOM 가능한 웹서비스 개발(CXF)

Resources:
JAX-WS에서 MTOM 사용 가능

바이너리 파일 첨부를 사용하는 서비스에서 요청 객체에 DataHandler를 프로퍼티를 사용하여 바이너리 데이터를 전송할 수 있다. 해당 프로퍼티에 XmlMimeType을 "application/octet-stream"으로 지정한다.

요청 객체 샘플


package mypackage;

import javax.activation.DataHandler;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlMimeType;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
@XmlType
public class UploadImeiFileRequest {

private String uploadUserId;


private DataHandler imeiFile;

public String getUploadUserId() {
return uploadUserId;
}
public void setUploadUserId(String uploadUserId) {
this.uploadUserId = uploadUserId;
}
@XmlMimeType("application/octet-stream")
public DataHandler getImeiFile() {
return imeiFile;
}
public void setImeiFile(DataHandler imeiFile) {
this.imeiFile = imeiFile;
}

}



Service Endpoint Interface


CXF에서는 BindingType을 어노테이션을 반드시 지정해 주어야 할 필요는 없지만 웹서비스를 명시적으로 MTOM 가능하도록 나타내도록 한다. 또한 MTOM 어노테이션을 사용하기도 한다.
package com.mycompany.ws;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.xml.ws.BindingType;

import com.sds.shp.webservice.message.UploadImeiFileRequest;
import com.sds.shp.webservice.message.UploadImeiFileResponse;

@WebService(name="imeiFileWebService", 
serviceName="imeiFileWebService",
targetNamespace="http://mocompany.com/ws/fileupload")
@BindingType(javax.xml.ws.soap.SOAPBinding.SOAP11HTTP_MTOM_BINDING)  
@javax.xml.ws.soap.MTOM
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)  
public interface FileUploadWebService{

@WebMethod(operationName="uploadImeiFile")
@WebResult(name="UploadUserImageResponse")
UploadImeiFileResponse uploadImeiFile(@WebParam(name="UploadImeiFile") UploadImeiFileRequest request) throws Exception;


}



서비스 cxf 설정 파일


jaxws:endpoint의 jaxws:properties 의 "mtom-enabled" 속성을 "true"로 지정한다.

<jaxws:endpoint id="fileUploadService" 
implementor="com.mycompany.ws.FileUploadWebServiceImpl"
address="/fileUploadWebService">
<jaxws:properties>
<entry key="mtom-enabled" value="true"/>
</jaxws:properties>
</jaxws:endpoint>


클라이언트 샘플1. Dispatch 사용


CXF를 전혀 사용하지 않고 Java 표준 api만으로 파일 첨부 클라이언트를 구현한 예제다.

클라이언트 샘플2. CXF의 JaxWsProxyFactoryBean 사용


CXF 환경 설정 없이 SEI 만으로 클라이언트를 생성하여 사용한다.

클라이언트 샘플3. 스프링 빈 등록 후 클라이언트 사용


스프링 설정 파일에 클라이언트를 등록한 후 Injection 객체를 사용한다.

package com.sds.shp.webservice.server;

import java.io.File;
import java.net.URL;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.annotation.Resource;
import javax.xml.namespace.QName;
import javax.xml.soap.AttachmentPart;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPBodyElement;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import javax.xml.ws.soap.SOAPBinding;

import org.apache.cxf.common.util.SOAPConstants;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import mypackage.LogManager;
import mypackage.PropertyNames;
import mypackage.UploadImeiFileRequest;
import mypackage.UploadImeiFileResponse;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:/spring/context-*.xml" })
public class ImeiFileWebServiceClientTest {


String endpointAddress = "http://mycompany.com/endpointuri";

String nsURI = "http://mycompany.com/ws/fileupload";

@Test
public void testDispatch() throws Exception {
MessageFactory factory = MessageFactory.newInstance();

QName serviceName = new QName(nsURI, "imeiFileWebService");
QName portName = new QName(nsURI, "imeiFileWebServicePort");

Service service = Service.create(new URL(endpointAddress + "?wsdl"), serviceName);
service.addPort(portName, SOAPBinding.SOAP11HTTP_MTOM_BINDING, endpointAddress);

SOAPMessage soapRequest = factory.createMessage();
SOAPBody body = soapRequest.getSOAPBody();

QName uploadImeiFileQName = new QName(nsURI, "uploadImeiFile", "ime");
SOAPBodyElement payload = body.addBodyElement(uploadImeiFileQName);

QName inParamName = new QName("UploadImeiFile");
SOAPElement inParamElement = payload.addChildElement(inParamName);

QName imeiName = new QName("imeiFile");
SOAPElement imeiElement = inParamElement.addChildElement(imeiName);

QName xopIncludeName = new QName("http://www.w3.org/2004/08/xop/include", "Include", "xop");
SOAPElement includeElement = imeiElement.addChildElement(xopIncludeName);

String cid = "" + System.currentTimeMillis();
includeElement.addAttribute(new QName("href"), "cid:" + cid);

QName uploadUserIdName = new QName("uploadUserId");
SOAPElement uploadUserIdElement = inParamElement.addChildElement(uploadUserIdName);
uploadUserIdElement.setTextContent("xxxx");



Dispatch dispatch = service.createDispatch(portName, SOAPMessage.class, Service.Mode.MESSAGE);

BindingProvider bp = (BindingProvider) dispatch;
((SOAPBinding) bp.getBinding()).setMTOMEnabled(true);

DataSource ds = new FileDataSource("d:/test2.zip");
DataHandler dataHandler = new DataHandler(ds);

AttachmentPart attachmentPart = soapRequest.createAttachmentPart(dataHandler);
attachmentPart.setContentId(cid);
soapRequest.addAttachmentPart(attachmentPart);


//    System.out.println("=== SOAP REQUEST ===");
//    soapRequest.writeTo(System.out);
//    System.out.println("=====================");

long start = System.currentTimeMillis();

dispatch.invoke(soapRequest);

System.out.println("==========> elapsed: " + (System.currentTimeMillis() - start));
}

@Test
public void testClientProxyFactoryBean() throws Exception {
long start = System.currentTimeMillis();
JaxWsProxyFactoryBean factoryBean = new JaxWsProxyFactoryBean();

java.util.Map props = new java.util.HashMap();
props.put(SOAPConstants.MTOM_ENABLED, Boolean.TRUE.toString());

factoryBean.setProperties(props);
factoryBean.setServiceClass(IImeiFileWebService.class);
factoryBean.setAddress(endpointAddress);

IImeiFileWebService service = (IImeiFileWebService) factoryBean.create();


// start.

boolean useProxy = false;

if(useProxy) {
String httpProxyServer = "proxy-ip";
int httpProxyServerPort = 8080;
int httpConnectionTimeout = 30000;
boolean httpAllowChunking = false;


Client client = ClientProxy.getClient(service);
client.getEndpoint().put( "mtom-enabled", "true" );

if(LogManager.isDebugEnabled()) {
String ln = System.getProperty("line.separator");
StringBuffer sb = new StringBuffer(ln);

sb.append("###############################################").append(ln);
sb.append(PropertyNames.HTTP_PROXY_SERVER).append(": ").append(httpProxyServer).append(ln);
sb.append(PropertyNames.HTTP_PROXY_SERVER_PORT).append(": ").append(httpProxyServerPort).append(ln);
sb.append(PropertyNames.HTTP_CONNECTION_TIMEOUT).append(": ").append(httpConnectionTimeout).append(ln);
sb.append(PropertyNames.HTTP_ALLOW_CHUNKING).append(": ").append(httpAllowChunking).append(ln);
sb.append("###############################################").append(ln);

LogManager.debug(getClass(), sb.toString());
}

HTTPConduit http = (HTTPConduit) client.getConduit();
HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
httpClientPolicy.setConnectionTimeout(httpConnectionTimeout);
httpClientPolicy.setAllowChunking(httpAllowChunking);
httpClientPolicy.setProxyServer(httpProxyServer);
httpClientPolicy.setProxyServerPort(httpProxyServerPort);
http.setClient(httpClientPolicy);

}   

// end.

UploadImeiFileRequest request = new UploadImeiFileRequest();
request.setUploadUserId("abc");

//    DataSource dataSource = new FileDataSource(new File("d:/test2.zip"));
DataSource dataSource = new FileDataSource(new File("d:/test2.zip"));
DataHandler dataHandler = new DataHandler(dataSource);

request.setImeiFile(dataHandler);

UploadImeiFileResponse response = service.uploadImeiFile(request);
System.out.println((System.currentTimeMillis() - start) + " milli seconds" );
}

@Resource(name="imeiFileClient")
IImeiFileWebService client;
@Test
public void testImeiFileWebServiceClient() throws Exception {
try {
long start = System.currentTimeMillis();
UploadImeiFileRequest request = new UploadImeiFileRequest();
request.setUploadUserId("xx");

DataSource dataSource = new FileDataSource(new File("d:/test2.zip"));
DataHandler dataHandler = new DataHandler(dataSource);

request.setImeiFile(dataHandler);

UploadImeiFileResponse response = client.uploadImeiFile(request);

System.out.println((System.currentTimeMillis() - start) + " milli seconds" );

} catch(Exception ex) {
ex.printStackTrace();
throw ex;
}
}

}



Injection된 객체를 사용하기 위해서는 스프링 설정파일에 아래와 같이 클라이언트를 등록해야 한다.

아래와 같이 클라이언트 빈을 지정해 주면 된다.
mtom-enabled속성을 true로 지정한다.
주의할 점은 endpointName과 serviceName을 namespace를 포함하여 반드시 지정하기를 권장한다. 개발 환경(개발자피씨)에서는 endpointName(PortName)와 serviceName을 지정하지 않아도 서비스와 연동하는데 문제가 되지 않지만, 외부 서버로 서비스를 디플로이 한 후에 연동하고자 할 때 문제가 발생한다.

<jaxws:client id="imeiFileClient" xmlns:tns="http://mocompany.com/ws/fileupload"
serviceClass="com.mycompany.ws.FileUploadWebService"
address="http://mycompany.com/ws/fileUploadWebService"
endpointName="tns:imeiFileWebServicePort"
serviceName="tns:imeiFileWebService">
<jaxws:properties>
<entry key="mtom-enabled" value="true"/>
</jaxws:properties>

</jaxws:client>




Sample Soap message


Payload: 
--uuid:08654ae5-c060-47dd-9bee-48dff0090a65
Content-Type: application/xop+xml; charset=UTF-8; type="text/xml";
Content-Transfer-Encoding: binary
Content-ID: <root.message@cxf.apache.org>

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:uploadImeiFile 
xmlns:ns2="http://socialhub.samsungmobile.com/store/ws/imeifile" 
xmlns:ns3="http://socialhub.samsungmobile.com/store/ws">
<UploadImeiFile><imeiFile>
<xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" 
href="cid:3381b3c2-d57e-490a-bccb-7c91487f24b2-1@http%3A%2F%2Fcxf.apache.org%2F"/></imeiFile>
<uploadUserId>xx</uploadUserId></UploadImeiFile>
</ns2:uploadImeiFile></soap:Body>
</soap:Envelope>
--uuid:08654ae5-c060-47dd-9bee-48dff0090a65
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
Content-ID: <3381b3c2-d57e-490a-bccb-7c91487f24b2-1@http://cxf.apache.org/>

0? ?0? ??      K? ?0   *?H?8   

--uuid:08654ae5-c060-47dd-9bee-48dff0090a65--

2010년 6월 23일 수요일

CXF 에서 HttpProxy 설정

프로그래밍으로 설정



package com.sds.shp.cxf.util;

import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;

import com.sds.shp.cxf.interceptor.SevenClientHandler;

public class ServiceClientUtil {
  @SuppressWarnings("unchecked")
  public static <T> T createClientProxy(Class<T> serviceEndpointInterface, String endpointUri) {
    
    JaxWsProxyFactoryBean proxyFactoryBean = new JaxWsProxyFactoryBean();
    
    proxyFactoryBean.setServiceClass(serviceEndpointInterface);
    proxyFactoryBean.setAddress(endpointUri);

    T service = (T) proxyFactoryBean.create();
    Client client = ClientProxy.getClient(service);
    
    String proxyServer = "serverip";
    int proxyServerPort = 10000;
    
    HTTPConduit http = (HTTPConduit) client.getConduit();
    HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
    httpClientPolicy.setConnectionTimeout(36000);
    httpClientPolicy.setAllowChunking(false);
    httpClientPolicy.setProxyServer(proxyServer);
    httpClientPolicy.setProxyServerPort(proxyServerPort);
    http.setClient(httpClientPolicy);


    return service;
  }
}




Resources:
http://markmail.org/message/fir6oi62eivaoxuu

2010년 6월 21일 월요일

RESTful 서비스의 파라미터 바인딩

RESTful 클라이언트로부터 요청을 받게 될 경우 서버측 엔드포인트에서는 클라이언트가 전달한 파라미터에 대한 값을 파싱해야 한다.

RESTful에서 사용할 수 있는 파라미터 javax.ws.rs 패키지 내에 정의되어 있으며 아래와 같다.
- CookieParam (쿠키 값의 name=value)
- FormParam (Form의 name=value)
- HeaderParam (Http Header의 name=value)
- MatrixParam (뭥미?)
- PathParam (URI의 토큰 eg. getUser/{uid})
- QueryParam (Request Query의 name=value eg. getUser?uid=1234)

또한 웹서비스에서 정의되어 있는 파라미터도 있다.
- javax.jws.WebParam (Http Request Body에 XML 또는 json 등으로 객체에 대한 데이터를 전달)

위의 파라미터 중에 WebParam을 제외하고는 모두 name=value 형태로 데이터가 전달되어 진다.
따라서 신규 데이터 추가나 변경등과 같이 다수의 파라미터가 전달되는 경우에는 각 파라미터의 name, value pair를 전달하려면 메서드의 파라미터가 많아지기 때문에 편의상의 이유로 WebParam으로 전달받도록 서비스를 개발하는데, 이로 인해 클라이언트에서 대상 객체에 대한 메시지 포맷을 생성한 후 서비스를 요청해야 하는 부담이 있다. 예를 들면 사용자 추가 서비스를 호출하기 위해서 아래와 같이 HTTP Request Body를 구성해야 한다.
<user>
<userId>xxx</userId>
<userName>Kim</userName>
<password>zzz</password>
...
</user>

이는 서비스 Consumer 입장에서는 적지 않은 부담일 뿐만 아니라 content type 변경(xml에서 json으로 또는 vice versa)하기로 결정했다면 또다시 xml을 json구문으로 변경해야 할 경우도 발생한다.

따라서 RESTful로 서비스를 개발할 경우에 클라이언트 부담도 줄이고, 서비스 메서드의 파라미터도 간단하게 개발할 수 있는 방법이 있다면 굳이 WebParam을 사용할 필요는 없다.

아래의 내용은 QueryParam, PathParam, FormParam(CookieParam, HeaderParam도 가능함)을 이용해서 객체로 바인딩하는 방법을 기술해 놓은 것이다. 파라미터 처리 방식은 유사하기 때문에 POST방식의 FormParam으로 샘플코드를 작성한다.

1. WebParam을 이용하는 경우
- WebParam으로 전달되는 객체는 XmlRootElement 어노테이션으로 정의가 되어 있어야 한다.

@POST
@Path("/insertUser")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
boolean insertUser(@WebParam("user") User user) throws Exception;


2. FormParam을 이용하는 경우
- User 객체에 포함되어야 하는 모든 파라미터를 FormParam으로 전달되어야 한다.
@POST
@Path("/insertUser")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
boolean insertUser(@FormParam("userId") String userId, @FormParam("password") String password) throws Exception;

3. FormParam의 바인딩을 이용하는 경우
- User 객체의 프로퍼티가 Form Param으로 전달되는 파라미터와 동일한 이름인 경우에는 바인딩된다.
- XmlRootElement 어노테이션을 사용할 필요가 없다(XmlBinding을 사용하지 않기 때문에 jaxb 어노테이션은 필요치 않다.)
- FormParam의 이름을 빈문자열 ("")으로 설정해 주어야 한다.
@POST
@Path("/insertUser")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
boolean insertUser(@FormParam("") User user) throws Exception;

위의 방법 중에서 3번 FormParam의 바인딩을 이용하는 것이 RESTful 서비스의 클라이언트와 서버 모두에게 가장 편리한 개발 방법을 제공하므로 RESTful에서는 WebParam을 사용하지 않도록 서비스를 설계해야 한다.

2010년 6월 18일 금요일

비대칭키를 이용한 암-복호화

1. keystore에 키 생성
2. keystore의 키로 인증서 파일 생성
3. 공개키암호화, 비밀키복호화 코드 작성

Keystore에 키 생성

D:\jks>keytool -genkey -alias test -keyalg RSA -keystore test.jks
keystore 암호를 입력하십시오:
새 암호를 다시 입력하십시오:
이름과 성을 입력하십시오.
[Unknown]: Beauty Kim
조직 단위 이름을 입력하십시오.
[Unknown]: Dev
조직 이름을 입력하십시오.
[Unknown]: MyCompany
구/군/시 이름을 입력하십시오?
[Unknown]: Seoul
시/도 이름을 입력하십시오.
[Unknown]: Seoul
이 조직의 두 자리 국가 코드를 입력하십시오.
[Unknown]: KR
CN=Beauty Kim, OU=Dev, O=MyCompany, L=Seoul, ST=Seoul, C=KR이(가) 맞습니까?
[아니오]: y

에 대한 키 암호를 입력하십시오.
(keystore 암호와 같은 경우 Enter를 누르십시오):



인증서 파일 생성
D:\jks>keytool -export -alias test -keystore test.jks -file test.cer
keystore 암호를 입력하십시오:
인증서가 파일에 저장되었습니다.

공개키암호화, 비밀키복호화 코드



package jca.test;

import java.io.FileInputStream;
import java.security.Key;
import java.security.KeyStore;
import java.security.PublicKey;
import java.security.cert.Certificate;

import javax.crypto.Cipher;

import org.junit.Test;

import com.lowagie.text.pdf.codec.Base64;

public class KeyStoreTest {

@Test
public void testKeyStore() throws Exception {
KeyStore keyStore = KeyStore.getInstance("JKS");
char[] password = "changeit".toCharArray();

FileInputStream in = null;
try {
in = new FileInputStream("d:/jks/test.jks");
keyStore.load(in, password);
} finally {
if(in != null) {
in.close();
}
}

byte[] data = "this is sample data".getBytes("utf8");

Certificate certificate = keyStore.getCertificate("test");

System.out.println("certificate: ");
System.out.println(certificate);

PublicKey publicKey = certificate.getPublicKey();

Key key = keyStore.getKey("test", "changeit".toCharArray());

System.out.println("key");
System.out.println(key);

Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);

byte[] cipherText = cipher.doFinal(data);
System.out.println("cipher text length: " + cipherText.length);
System.out.println("cipher text: " + Base64.encodeBytes(cipherText));

cipher.init(Cipher.DECRYPT_MODE, key);
byte[] result = cipher.doFinal(cipherText);

System.out.println("result: " + new String(result));
}
}

2010년 6월 17일 목요일

DBCP 환경설정

커넥션 풀 속성 지정하기

앞에서 살펴본 커넥션 풀 설정 파일인 pool1.jocl은 커넥션 풀과 관련된 속성을 지정하지 않고 있다. DBCP의 커넥션 풀은 최대 커넥션 개수, 최소 유휴 커넥션 개수, 최대 유휴 커넥션 개수, 유휴 커넥션 검사 여부 등의 속성을 지정할 수 있다. pool1.jocl을 보면 다음과 같은 코드가 있는데,

<object class="org.apache.commons.pool.impl.GenericObjectPool">
<object class="org.apache.commons.pool.PoolableObjectFactory" null="true" />
</object>

이 코드에 커넥션 풀과 관련된 속성 정보를 추가하면 된다. 예를 들면, 아래 코드와 같이 커넥션 풀 속성 정보를 추가하면 된다.


파일명: pool\WEB-INF\classes\pool2.jocl
<object class="org.apache.commons.dbcp.PoolableConnectionFactory"
xmlns="http://apache.org/xml/xmlns/jakarta/commons/jocl">

<object class="org.apache.commons.dbcp.DriverManagerConnectionFactory">
<string value="jdbc:mysql://localhost:3306/chap11?..." />
<string value="jspexam" />
<string value="jspex" />
</object>

<object class="org.apache.commons.pool.impl.GenericObjectPool">
<object class="org.apache.commons.pool.PoolableObjectFactory" null="true" />
<int value="10" /> <!-- maxActive -->
<byte value="1" /> <!-- whenExhaustedAction -->
<long value="10000" /> <!-- maxWait -->
<int value="10" /> <!-- maxIdle -->
<int value="3" /> <!-- minIdle -->
<boolean value="true" /> <!-- testOnBorrow -->
<boolean value="true" /> <!-- testOnReturn -->
<long value="600000" /> <!-- timeBetweenEvctionRunsMillis -->
<int value="5" /> <!-- numTestsPerEvictionRun -->
<long value="3600000" /> <!-- minEvictableIdleTimeMillis -->
<boolean value="true" /> <!-- testWhileIdle -->
</object>
<object class="org.apache.commons.pool.impl.GenericKeyedObjectPoolFactory"
null="true" />

<string null="true" />

<boolean value="false" />

<boolean value="true" />
</object>

굵게 표시한 부분이 커넥션 풀의 속성과 관련된 부분인데, 각 속성의 값이 무엇을 의미하는 지 우측에 주석으로 표시하였다. 각 속성이 의미하는 것은 다음표와 같다.

속성 설명
maxActive 커넥션 풀이 제공할 최대 커넥션 개수
whenExhaustedAction 커넥션 풀에서 가져올 수 있는 커넥션이 없을 때 어떻게 동작할지를 지정한다. 1일 경우 maxWait 속성에서 지정한 시간만큼 커넥션을 구할 때 까지 기다리며, 0일 경우 에러를 발생시킨다. 2일 경우에는 일시적으로 커넥션을 생성해서 사용한다.
maxWait whenExhaustedAction 속성의 값이 1일 때 사용되는 대기 시간. 단위는 1/1000초이며, 0 보다 작을 경우 무한히 대기한다.
maxIdle 사용되지 않고 풀에 저장될 수 있는 최대 커넥션 개수. 음수일 경우 제한이 없다.
minIdle 사용되지 않고 풀에 저장될 수 있는 최소 커넥션 개수.
testOnBorrow true일 경우 커넥션 풀에서 커넥션을 가져올 때 커넥션이 유효한지의 여부를 검사한다.
testOnReturn true일 경우 커넥션 풀에 커넥션을 반환할 때 커넥션이 유효한지의 여부를 검사한다.
timeBetweenEvctionRunsMillis 사용되지 않은 커넥션을 추출하는 쓰레드의 실행 주기를 지정한다. 양수가 아닐 경우 실행되지 않는다. 단위는 1/1000 초이다.
numTestsPerEvictionRun 사용되지 않는 커넥션을 몇 개 검사할지 지정한다.
minEvictableIdleTimeMillis 사용되지 않는 커넥션을 추출할 때 이 속성에서 지정한 시간 이상 비활성화 상태인 커넥션만 추출한다. 양수가 아닌 경우 비활성화된 시간으로는 풀에서 제거되지 않는다. 시간 단위는 1/1000초이다.
testWhileIdle true일 경우 비활성화 커넥션을 추출할 때 커넥션이 유효한지의 여부를 검사해서 유효하지 않은 커넥션은 풀에서 제거한다.

몇몇 속성은 성능에 중요한 영향을 미치기 때문에 웹 어플리케이션의 사용량에 따라서 알맞게 지정해주어야 하는데, 다음과 같이 고려해서 각 속성의 값을 지정하는 것이 좋다.

* maxActive - 사이트의 최대 커넥션 사용량을 기준으로 지정. 동시 접속자수에 따라서 지정한다.
* minIdle - 사용되지 않는 커넥션의 최소 개수를 0으로 지정하게 되면 풀에 저장된 커넥션의 개수가 0이 될 수 있으며, 이 경우 커넥션이 필요할 때 다시 커넥션을 생성하게 된다. 따라서 커넥션의 최소 개수는 5개 정도로 지정해두는 것이 좋다.
* timeBetweenEvctionRunsMillis - 이 값을 알맞게 지정해서 사용되지 않는 커넥션을 풀에서 제거하는 것이 좋다. 커넥션의 동시 사용량은 보통 새벽에 최저이며 낮 시간대에 최대에 이르게 되는데 이 두 시간대에 필요한 커넥션의 개수 차이는 수십개에 이르게 된다. 이때 최대 상태에 접어들었더가 최소 상태로 가게 되면 풀에서 사용되지 않는 커넥션의 개수가 점차 증가하게 된다. 따라서 사용되지 않는 커넥션은 일정 시간 후에 삭제되도록 하는 것이 좋다. 보통 10~20분 단위로 사용되지 않는 커넥션을 검사하도록 지정하는 것이 좋다.
* testWhileIdle - 사용되지 않는 커넥션을 검사할 때 유효하지 않은 커넥션은 검사하는 것이 좋다.


Resources
http://javacan.tistory.com/82
http://www.docjar.org/docs/api/org/apache/commons/dbcp/BasicDataSource.html
http://wiki.oracleclub.com/display/SWDEV/DBCP+%28Database+connection+pooling+services%29