서버 모니터링 시스템을 만들고 있다.


구조는 서버의 MBean 및 Background 테스팅 도구가 실행한 정보를 각각의 N대의 서버에서 Message Qeue에 전달 하고, 모니터링 WAS에서 Queue 를 Receive 하여 Tomcat Long Pooling 을 활용하여 N대의 Client Browser 에 전송 해 준다.


public static List connections = new ArrayList<>(); 부분이 접속된 Client Browser 정보이므로, Queue Receive Event 시점에 connections Client 갯수만큼 PrintWriter 활용 해서 write 하고, connections 의 CometEvent 객체를 Close 하고, connections에서 Remove() 시킨다.


접속된 Client 정보를 public static 변수로 다른 Thread 에서 제어 하는게 잘 하는건지는 모르겠지만. 잘 된다...


사실 처음에 Tomcat Comet 방식을 이용했지만 지금은 WebSocket 방식으로 동일 하게 활용중이다.

다음 글은 동일한 상황에서 Comet 를 Websocket로 변환.. 거의 동일함..


import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.comet.CometEvent;
import org.apache.catalina.comet.CometProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@WebServlet("/comet/testCase")
public class TestCaseServlet extends HttpServlet implements CometProcessor {
	private static Logger log = LoggerFactory.getLogger(TestCaseServlet.class);
	
	public static List connections = new ArrayList<>();

	public void init() throws ServletException {
        log.info("----------- thread start");
    }

    public void destroy() {
    	log.info("---------- thread stop");
    	
    	synchronized (connections) {
			connections.clear();
		}
    }

	@Override
	public void event(CometEvent event) throws IOException, ServletException {
		log.info("testCase event: " + event.getEventType());
		HttpServletRequest request = event.getHttpServletRequest();
		HttpServletResponse response = event.getHttpServletResponse();
		response.setCharacterEncoding("UTF-8");
		event.setTimeout(1000*60*60);
		
		if (event.getEventType() == CometEvent.EventType.BEGIN) {
            log.info("Begin for request: {}, response: {}, session: {}", request, response, request.getSession(true).getId());
            synchronized(connections) {
            	connections.add(event);
            }
        } else if (event.getEventType() == CometEvent.EventType.ERROR) {
            log.info("Error for session: {}", request.getSession(true).getId());
            synchronized(connections) {
            	connections.remove(event);
            }
            PrintWriter writer = response.getWriter();
            writer.println("{result: '9999', msg: 'timeout'}");
            event.close();
        } else if (event.getEventType() == CometEvent.EventType.END) {
            log.info("End for session: " + request.getSession(true).getId());
            synchronized(connections) {
            	connections.remove(event);
            }
            PrintWriter writer = response.getWriter();
            writer.println("{result: '0000', msg: 'end'}");
            event.close();
        } else if (event.getEventType() == CometEvent.EventType.READ) {
        	 log.info("read");
            while (true) {
            	;
            }
        }
	}
}


로그성 DB INSERT로 인해서 전체적인 DB성능 이슈가 발생하면서 어플리케이션까지 서능에 영향이 미치는 상황이 발생하였다.


처음에는 Logging 라이브러리를 활용하여 Text형태로 쌓아놓고 한가한 시간에 배치로 DB에 INSERT하려고 하다가 공부 겸 JMS(java message service)를 적용해보기로 하였다.


Message Queue 관련 방법은 여러가지가 있던데 선택은 Apache ActiveMQ 를 선택 하였다.

관련 장단점에 대해서는 인터넷 참고!!





우선 Apache ActiveMQ 설치


context-jms.xml


	



	



	



	
    




	
	
	



	
	
	
		
	


MemberMessageConverter.java

package pe.kr.ddakker.jms;

import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.Session;

import org.springframework.jms.support.converter.MessageConversionException;
import org.springframework.jms.support.converter.MessageConverter;

public class MemberMessageConverter implements MessageConverter {

	public MemberMessageConverter(){}
	
	@Override
	public Object fromMessage(Message message) throws JMSException,
			MessageConversionException {
		if(!(message instanceof MapMessage)){
			throw new MessageConversionException("Messaeg isn't a MapMessage");
		}
		
		MapMessage mapMessage = (MapMessage) message;
		Member member = new Member();
		member.setName(mapMessage.getString("name"));
		member.setEmail(mapMessage.getString("email"));
		return member;
	}

	@Override
	public Message toMessage(Object object, Session session) throws JMSException,
			MessageConversionException {
		if(!(object instanceof Member)){
			throw new MessageConversionException("Messaeg isn't a Member");
		}
		
		Member member = (Member) object;
		MapMessage message = session.createMapMessage();
		message.setString("name", member.getName());
		message.setString("email", member.getEmail());
		return message;
	}

}


ReceiverMessageListenerImpl.java

package pe.kr.ddakker.jms;

import javax.annotation.Resource;
import javax.jms.Message;
import javax.jms.MessageListener;

import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.stereotype.Component;

@Component
public class ReceiverMessageListenerImpl implements MessageListener {


	@Resource
	private MessageConverter memberConverter;

	@Override
	public void onMessage(Message message) {
		Member member = null;
		try {
			member = (Member) memberConverter.fromMessage(message);

			if (member != null) {
				System.out.println("member.getEmail(): " + member.getEmail());
			}
		} catch (Exception e) {
			System.err.println(e);
		}

	}

}


JmsTest .java

package pe.kr.ddakker.jms;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath*:config/spring/context-jms.xml"})
public class JmsTest {
	@Resource
	private JmsTemplate jmsTemplate;
	
	@Test
	public void test() {
		
		for (int i = 0; i < 5; i++) {
			Member member = new Member();
			member.setName("ddakker" + i);
			member.setEmail("ddakker" + i + "@gmail.com");
			
			jmsTemplate.convertAndSend(member);
		}
	}
}


Eclipse TestCase 실행 - 정상

Eclipse Gradle TestCase 실행 - 정상

Linux 64/Tomcat 7.x 배포 실행 후 서비스 - 정상


Linux 64/Jenkins Gradle test Task 실행 - 실패


TestMapper.java 일 경우 testMapper.xml 일때 위와 같은 정상/실패 케이스가 생긴다.


xml 파일명은 무조건 java파일명과 대소문자까지 일치 시켜주자!!

최근에 Logback 로깅 라이브러리에 대해서 알게되었다.

검색 시 많은 자료가 나오는것을 보니 단순 트랜드가 아닌 현재 실무에서도 많이들 사용하는듯 싶습니다.


우선 Log4j,SLF4J를 만든 사람이 만들었다니 더 좋을 수밖에 없다는 생각이듭니다.

(맘에 안들고 불편했던 부분을 개선하고, 여러 실무 의견들을 반영했을 테니까요.)


좋은점에 대한 정보가 많이 있었으나 우선 제 시점에서 좋겠구나 하는것만 나열 하겠고, 나머지는 직접 검색해보시기 바랍니다.(자료 많네요.)


첫째로, 성능이 약 10배 향상, 메모리 점유율도 낮아졌음

우선 메모리야 요즘 넘처나기도 하고, 현재까지 Log4j 사용하면서 메모리 문제는 없었던 상황이니 잘 와닫지 않고, 모바일 기기의 출현으로 0.01 초의 차이도 의미 있는 수치일수 있으므로 성능 향상은 아주 좋은 장점인것 같습니다.


둘째로, 설정파일 자동으로 Reloading

간혹 개발 및 스테이지에서는 확인이 안되고 운영상황에서만 문제가 발생할때 DEBUG모드로 변경후 모니터링 해봐야 하는 경우가 있습니다.

이럴때 Log4j는 서버를 재기동해야 하는 부담이 있는데 Logback을 사용하므로 인해서 부담이 줄겠네요.


세번째로, "logback-access" 라고 HTTP 디버깅

웹개발자로서 무슨소린지 몰라도 유용할듯 싶은데... 셈플링 해봐야할것 같네요.


네번째로, 자동삭제

서버관리상의 기능이긴 하지만 유용한 기능인것 같습니다.

굳이 배치나 주기적으로 삭제관련 모니터링을 하지 않아도 되니까요.


다섯째로, Prudent mode

다수의 JVM일 경우 하나의 파일에 쌓을수 있다는데..

한장비안에서 쌓을 일은 없을듯 하고, 별도의 장비에서 동일한 NAS경로 셋팅되어 있어도 가능한지 모르것네;;


여섯째로, 분기 스트립트 작성

if,else 와 같은 문법 사용이 가능하므로, 개발, 스테이지, 운영에 따른 처리가 한 파일로 가능하겠군요.

기존에는 파일을 각각 두어서 시스템프로퍼티에 해당 정보를 셋팅해서 선택하게끔 하거나, 배포시 파일명을 변경하곤 하였는데 말이죠. ㅎㅎ


일곱째로, 특정 분류별 로그파일 기록

특정 사용자별로 하고 싶은데.. 가능하려나 모르것네..


여덜째로, Stack Trace 출력

외부 라이브러리에서 발생한 Exception에 대해서 참조되는 라이브러리 버전까지 출력을 해준다네..

전에 버전이 틀린 동일한 라이브라리가 두개 올라가져 있어서 개발,스테이지,운영간에 두개중 참조하는게 틀려서 한참 삽질 했던적 있는데 그러한경우 디버깅이 그나마 쉬워지겠군요.



우선 간단한 셈플링부터.. 나머지는 차차차~


[build.gradle]

apply plugin: 'java'
apply plugin: 'eclipse'

sourceCompatibility = 1.6

repositories {
    mavenCentral()
}


// SLF4J + Logback
List loggerSlf4jAndLogback = [
    "ch.qos.logback:logback-classic:1.0.13",
    "org.slf4j:jcl-over-slf4j:1.7.5"
]

dependencies {
    compile loggerSlf4jAndLogback
}

// commons-logging 의존성 제거
configurations {
    all.collect { configuration ->
        configuration.exclude group: 'commons-logging', module: 'commons-logging'
        configuration.exclude group: 'log4j', module: 'log4j'
    }
}


[logback.xml]



	
		
			%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
		
	

	
		
	


[Foo.java]

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Foo {
	static final Logger logger = LoggerFactory.getLogger(Foo.class);

	public void doIt() {
		logger.debug("안녕 {}", "하세요.");
	}
}


SLF4J는 Apache commons-logging과 같이 SLF4J도 Facade 라이브러입니다.

그 말은 인터페이스만 있고, 해당 구현체는 별도의 라이브러리를 사용한다는 말입니다.


이에 대한 잇점은 시스템 전반적으로 사용되어진 로깅시스템의 교체가 수월해진다는 장점이 있습니다.

(사실 한번 적용된 로깅 시스템을 교체하는 경우는 특별한 경우가 아니고서야 별로 없긴 합니다.)


※ 하지만 SLF4J는 별도의 구현체 없이 단독으로도 사용가능하긴 합니다. ㅎ



SLF4J의 탄생 이유는 Aapche Commons-logging 의 비효율적인 문제들이라고 합니다.

여러가지가 있겠지만 잘 모르겠고..;; 개인적으로 편리한 기능 및 셈플 남겨봅니다.


logger.debug("param: " + param var); 


대부분 위와 같이 로그를 남길것이다.

하지만 운영의 DEBUG 하위 모드에서는 로그는 찍히지 않겠지만 로그 문자열에 대한 연산을 이루어져 그에 대한 비용이 발생한다고 합니다.

별거 아닌것 같지만 이에 대한 비용이 찝집하다네요 ㅎㅎ



if( logger.isDebugEnabled() ){

    logger.debug("param: " + param var);

}


그래서 대부분 위와 같이 특정 로그레벨일 때만 처리 되도록 하긴 하지만 소스양이 늘어나고 귀찮아지죠.

그에 따라 위와 같이 하지 않고 첫번째 방법으로만 하는 경우가 파다합니다.


SLF4J는 이러한 비용을 절약되도록 설계되었다고 합니다.



 logger.debug("param: {}", param var);


위와 같이 문자열을 연산하지 않고, 해당 함수 내부에서 상황에 따라 연산이 이루어지게 하는것 같습니다.


[build.gradle]

apply plugin: 'java'
apply plugin: 'eclipse'

sourceCompatibility = 1.6

repositories {
    mavenCentral()
}

// SLF4J 단독
List loggerSlf4j = [
    "org.slf4j:slf4j-api:1.7.5",
    "org.slf4j:slf4j-simple:1.7.5",
    "org.slf4j:jcl-over-slf4j:1.7.5"
]

// SLF4J + Log4j
List loggerSlf4jAndLog4j = [
    "org.slf4j:slf4j-log4j12:1.7.5",
    "org.slf4j:jcl-over-slf4j:1.7.5"
]

dependencies {
    //compile loggerSlf4j
    compile loggerSlf4jAndLog4j
}

// commons-logging 의존성 제거
configurations {
    all.collect { configuration ->
        configuration.exclude group: 'commons-logging', module: 'commons-logging'
    }
}


[log4j.xml]




	
		
			
		
	

	
		
		
	


[Foo.java]

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Foo {
	static final Logger logger = LoggerFactory.getLogger(Foo.class);

	public void doIt() {
		logger.debug("안녕 {}", "하세요.");
	}
}


예제 소스에서 볼수 있듯이, SLF4J단독으로 사용하든, Log4j를 구현체로 사용하든 소스상에 변화는 없습니다.


설정 중 한가지 눈여겨볼 수항은 "jcl-over-slf4j" 입니다.

"jcl-over-slf4j' 추가 목적은 기존 Springfrawork 와 같이 Apache commons-logging를 사용하는 라이브러리를 사용하고 있을 경우 소스상은 commons-logging이지만 실제 동작은 SLF4J쪽을 호출 하도록 하는 역할을 하도록 합니다.



SLF4J + Log4j 에 대한 예제만 작성하였지만 "http://www.slf4j.org/manual.html" 사이트를 참고하시면 아래와 같이 각 구현체 마다 사용해야 하는 라이브러리 정보가 있습니다.


slf4j-log4j12-1.7.5.jar
Binding for log4j version 1.2, a widely used logging framework. You also need to place log4j.jar on your class path.

slf4j-jdk14-1.7.5.jar
Binding for java.util.logging, also referred to as JDK 1.4 logging

slf4j-nop-1.7.5.jar
Binding for NOP, silently discarding all logging.

slf4j-simple-1.7.5.jar
Binding for Simple implementation, which outputs all events to System.err. Only messages of level INFO and higher are printed. This binding may be useful in the context of small applications.

slf4j-jcl-1.7.5.jar
Binding for Jakarta Commons Logging. This binding will delegate all SLF4J logging to JCL.

logback-classic-1.0.13.jar (requires logback-core-1.0.13.jar)
NATIVE IMPLEMENTATION There are also SLF4J bindings external to the SLF4J project, e.g. logback which implements SLF4J natively. Logback'sch.qos.logback.classic.Logger class is a direct implementation of SLF4J's org.slf4j.Logger interface. Thus, using SLF4J in conjunction with logback involves strictly zero memory and computational overhead.


  • 기본 Bean
      heaer, resultList 와 맵핑될 Bean Class가 업무별로 달라지므로 우선 HashMap에 담고, getter 에서 특정 Bean으로 컨버팅해준다.
  • 
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    import org.apache.commons.beanutils.BeanUtils;
    
    /**
     * DB 아니고 외부 인터페이스에서 받아온 정보
     * @auther ddakker 2013. 6. 12.
     */
    public class ExternalBean {
    
    	private String resultCode;
    	private String errMsg;
    
    	private Map header;
    	private List<Map> resultList;
    	public String getResultCode() {
    		return resultCode;
    	}
    	public void setResultCode(String resultCode) {
    		this.resultCode = resultCode;
    	}
    	public String getErrMsg() {
    		return errMsg;
    	}
    	public void setErrMsg(String errMsg) {
    		this.errMsg = errMsg;
    	}
    	public Map getHeader() {
    		return header;
    	}
    	public void setHeader(Map header) {
    		this.header = header;
    	}
    	public List<Map> getResultList() {
    		return resultList;
    	}
    	public void setResultList(List<Map> resultList) {
    		this.resultList = resultList;
    	}
    
    	/**
    	 * 헤더 정보를 특정 Bean에 담아 리턴한다.
    	 * @param clz
    	 * @return
    	 * @auther ddakker 2013. 6. 14.
    	 */
    	public <T> T getHeader(Class<T> clz) {
    		Object objBean = null;
    		try{
    			objBean = clz.newInstance();
    			BeanUtils.copyProperties(objBean, header);
    
    			return (T) objBean;
    		}catch(Exception e){
    			throw ExceptionManager.createException("JSON 데이터를 Bean으로 맵핑[Header]하는 도중 에러가 발생하였습니다." + e);
    		}
    	}
    
    	/**
    	 * 배열 정보를 특정 List<Bean>에 담아 리턴한다.
    	 * @param clz
    	 * @return
    	 * @auther ddakker 2013. 6. 14.
    	 */
    	public <T> List<T> getResultList(Class<T> clz) {
    		List<T> list = new ArrayList<T>();
    		try{
    			Object objBean;
    			for( Map map : resultList ){
    				objBean = clz.newInstance();
    				BeanUtils.copyProperties(objBean, map);
    
    				list.add((T) objBean);
    			}
    		}catch(Exception e){
    			throw ExceptionManager.createException("JSON 데이터를 Bean으로 맵핑[ResultList]하는 도중 에러가 발생하였습니다." + e);
    		}
    		return list;
    	}
    }
    

  • 헤더 Bean
      각 업무에 맞는 Bean을 생성한다.
  • public class UserHeaderBean {
    	private String userNm;
    
    	public String getUserNm() {
    		return userNm;
    	}
    
    	public void setUserNm(String userNm) {
    		this.userNm = userNm;
    	}
    }
    

  • 배열 Bean
      각 업무에 맞는 Bean을 생성한다.
  • public class ListBean {
    	private String dlvrAddr1;
    	
    	public String getDlvrAddr1() {
    		return dlvrAddr1;
    	}
    }
    

    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import net.sf.json.JSONObject;
    
    public class TestCase {
            @Test
    	public void jsonTestCase() {
    		String jsonStr = "{'resultCode':'success', 'errorMsg': '','header':{'userNm':'ddakker'},'resultList':[{'dlvrAddr1': '서울~'},{'dlvrAddr1': '충남~'}]}";
    		
    		Map classMap = new HashMap();
    		classMap.put("header", HashMap.class);
    		classMap.put("resultList", HashMap.class);
    
    		JSONObject json = JSONObject.fromObject(jsonStr);
    
    		ExternalBean exBean = (ExternalBean) JSONObject.toBean(json, ExternalBean.class, classMap);
    		
    		if( exBean.getResultCode().equals("success") ){
    			UserHeaderBean hBean = exBean.getHeader(UserHeaderBean.class);
    			System.out.println(hBean.geUserNm());
    								
    			List<ListBean> lBeanList = exBean.getResultList(ListBean.class);
    			System.out.println(lBeanList.size());
    			System.out.println(lBeanList.get(0).getDlvrAddr1());
    		}
    	}
    }
    
    • json-lib-2.4-jdk15.jar
    • ezmorph-1.0.6.jar
    • commons-lang-2.6.jar
    • commons-logging-1.1.3.jar
    • commons-collections-3.2.1.jar
    • commons-beanutils-1.8.3.jar

    [설정]

    struts.xml

    <struts>

    ...

    <constant name="struts.custom.i18n.resources" value="프로퍼티 파일명(확장자는 빼고, 국가 문자열 빼고(_ko 등)" />

    ...

    </struts>


    [JSP]

    ...

    <%@ taglib prefix="s" uri="/struts-tags" %>

    ...

    <s:text name="err.msg" />

    ...


    [Action]

    ...

    getText("err.msg"); // ActionSupport 를 상속받아야함

    ...

    WAS 기동 시 시스템 프로퍼티에 아래와 같이 등록한다.


    -Dlog4j.configuration=log4j 설정 파일.xml


    
    import java.io.File;
    import java.io.FileInputStream;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.apache.poi.ss.usermodel.Cell;
    import org.apache.poi.ss.usermodel.FormulaEvaluator;
    import org.apache.poi.ss.usermodel.Row;
    import org.apache.poi.ss.usermodel.Sheet;
    import org.apache.poi.ss.usermodel.Workbook;
    import org.apache.poi.ss.usermodel.WorkbookFactory;
    import org.apache.poi.xssf.usermodel.XSSFCell;
    
    /**
     * Excel Read 한다. xls, xlsx 가능
     * ex) 	ExcelGenerator.excelToList(new File("path..xls,xlsx");
     * 		ExcelGenerator.excelToList(new File("path..xls,xlsx", new Integer[]{x, y, width, height});
     * ExcelGenerator.excelToList(new File("path..xls,xlsx", x, y, width, height);
     * 		java.awt.Rectangle(int x, int y, int width, int height) 와 같은 형태로 왼쪽 위 모서리 좌표와, 오른쪽 아래 모서리 좌표 정보 
     * @author 	ddakker
     * @version 1.0
     * @date	2012.05.04
     *
     */
    public class ExcelGenerator {
    	private static final Log logger = LogFactory.getLog(ExcelGenerator.class);
    	
    	public static Object[][] excelToList(File file ) throws Exception {
    		return excelToList(file, 0, null);
    	}
    	
    	/**
    	 * 엑셀 데이터를 Object[colunm][row] 형태로 변환한다.
    	 * @param file			엑셀 파일(xls, xlsx)
    	 * @param x				왼쪽 위 모서리 x
    	 * @param y				왼쪽 위 모서리 y
    	 * @param width			오른쪽 아래 모서리 x
    	 * @param height		오른쪽 아래 모서리 y
    	 * @return
    	 * @throws Exception
    	 */
    	public static Object[][] excelToList(File file, int x, int y, int width, int height) throws Exception {
    		Integer [] colRow = {x, y, width, height};
    		return excelToList(file, 0, colRow);
    	}
    	
    	/**
    	 * 엑셀 데이터를 Object[colunm][row] 형태로 변환한다.
    	 * @param file			엑셀 파일(xls, xlsx)
    	 * @param colRow		범위 지정 new Integer[]{왼쪽 위 모서리 x, 왼쪽 위 모서리 y, 오른쪽 아래 모서리 x, 오른쪽 아래 모서리 y}
    	 * @return
    	 * @throws Exception
    	 */
    	public static Object[][] excelToList(File file, Integer [] colRow) throws Exception {
    		return excelToList(file, 0, colRow);
    	}
    	
    	/**
    	 * 엑셀 데이터를 Object[colunm][row] 형태로 변환한다.
    	 * @param file			엑셀 파일(xls, xlsx)
    	 * @param sheetIndex	시트 번호
    	 * @return
    	 * @throws Exception
    	 */
    	public static Object[][] excelToList(File file, int sheetIndex) throws Exception {
    		return excelToList(file, sheetIndex, null);
    	}
    	
    	/**
    	 * 엑셀 데이터를 Object[colunm][row] 형태로 변환한다.
    	 * @param file				엑셀 파일(xls, xlsx)
    	 * @param sheetIndex		시트 번호
    	 * @param colRow			범위 지정 new Integer[]{왼쪽 위 모서리 x, 왼쪽 위 모서리 y, 오른쪽 아래 모서리 x, 오른쪽 아래 모서리 y}
    	 * @return
    	 */
    	public static Object[][] excelToList(File file, int sheetIndex, Integer [] colRow) throws Exception {
    		Object [][] contentData = null;
    		
    		FileInputStream fis			= null;
    		Workbook 		workBook	= null;
            Sheet			sheet		= null;
            
            try {
            	if( colRow != null ){
    	        	if( colRow.length != 4 )					throw new Exception("colRow[] 무조껀 셋팅 해야 합니다. ex) new Integer[]{0,0,6,2}");
    	        	if( !(colRow[0] >= 0 && colRow[1] >= 0) )	throw new Exception("colRow[] 0 이상 입력 하시요. ex) new Integer[]{1,1,6,2}");
    	        	if( !(colRow[2] >= 0 && colRow[3] >= 0) )	throw new Exception("colRow[] 0 이상 입력 하시요. ex) new Integer[]{1,1,6,2}");
            	}
            	fis			= new FileInputStream(file);
            	workBook	= WorkbookFactory.create(fis);
        		sheet		= workBook.getSheetAt(sheetIndex);
                
                int minCol = colRow==null?0:colRow[0];
                int maxCol = colRow==null?-1:colRow[2]+1;
                
                int minRow = colRow==null?0:colRow[1];
                int maxRow = colRow==null?sheet.getPhysicalNumberOfRows():colRow[3]+1;
                
                FormulaEvaluator evaluator = workBook.getCreationHelper().createFormulaEvaluator();
                
                for( int i=minRow; i<maxRow; i++ ) {
                	Row row = sheet.getRow(i);
                	if( i-minRow == 0 ){
    	            	if( maxCol == -1 ){
    	            		maxCol = row.getPhysicalNumberOfCells();
    	            	}
    	            	contentData = new Object[maxRow-minRow][maxCol-minCol];
                	}
                    for( int j=minCol; j<maxCol; j++ ) {
                    	Cell cell = row.getCell(j)==null?row.createCell(j):row.getCell(j);
                    	
                    	Object value = null;
                        switch( cell.getCellType() ) {
                             case XSSFCell.CELL_TYPE_FORMULA :
                            	 if( !(cell.toString() == "") ){
    	                             if(evaluator.evaluateFormulaCell(cell)==XSSFCell.CELL_TYPE_NUMERIC){
    	                            	 //value = cell.getNumericCellValue();         
    	                            	 value = String.valueOf(new Double(cell.getNumericCellValue()).intValue());
    	                             }else if(evaluator.evaluateFormulaCell(cell)==XSSFCell.CELL_TYPE_STRING){
    	                            	 value = cell.getStringCellValue();
    	                             }else if(evaluator.evaluateFormulaCell(cell)==XSSFCell.CELL_TYPE_BOOLEAN){
    	                                  value = String.valueOf(cell.getBooleanCellValue());         
    	                             }
                            	 }
                            	 break;
                             case XSSFCell.CELL_TYPE_NUMERIC :
                            	 //value = cell.getNumericCellValue();
                            	 value = String.valueOf(new Double(cell.getNumericCellValue()).intValue());
                                 break;
                             case XSSFCell.CELL_TYPE_STRING :
                            	 value = cell.getStringCellValue()==null?new String(""):cell.getStringCellValue();
                            	 break;
                             case XSSFCell.CELL_TYPE_BOOLEAN :
                            	 value = Boolean.toString(cell.getBooleanCellValue()); //boolean
                            	 break;
                             default :
                        }
                        
                        logger.debug("xlsx ["+i+"]["+j+"]=" + ((cell==null || value==null)?"":value));
                        contentData[i-minRow][j-minCol] = (value==null)?new String(""):value;
                    }
                }
            } catch (Exception e) {
            	logger.error(e);
                throw new Exception("POI Excel Read 중 오류가 발생하였습니다.");
            } finally {
            	if( fis != null ) fis.close();
            }
            return contentData;
        }
    
        /**
    	 * List 데이터를 엑셀 형식으로 변환한다.
    	 * 		* String만 테스트 해봄.. 다른 데이터형은 직접 테스트 해보실것!!![2013.08.22]
    	 * @param list
    	 * @return
    	 * @throws Exception
    	 * @auther ddakker 2013. 8. 22.
    	 */
    	public static HSSFWorkbook listToExcel(List> list) throws Exception {
    		HSSFWorkbook wb = null;
    		try{
    
    			wb = new HSSFWorkbook();
    			HSSFRow row = null;
    			HSSFCell cell = null;
    
    			HSSFSheet sheet = wb.createSheet();
    
    			wb.setSheetName(0, "Sheet1");
    			sheet.setColumnWidth(0, sheet.getDefaultColumnWidth() * 700);
    			sheet.setColumnWidth(1, sheet.getDefaultColumnWidth() * 4300);
    
    			HSSFCellStyle titleCellStyle = wb.createCellStyle();
    			titleCellStyle.setBorderBottom(HSSFCellStyle.BORDER_THICK);
    			titleCellStyle.setBorderLeft(HSSFCellStyle.BORDER_THICK);
    			titleCellStyle.setBorderRight(HSSFCellStyle.BORDER_THICK);
    			titleCellStyle.setBorderTop(HSSFCellStyle.BORDER_THICK);
    			titleCellStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
    			titleCellStyle.setFillForegroundColor(HSSFColor.GREY_25_PERCENT.index);
    
    			Iterator it = list.get(0).keySet().iterator();
    			int haderIdx = 0;
    			HSSFRow titleRow = sheet.createRow(0);
    			while (it.hasNext()) {
    				String key = it.next();
    
    				cell = titleRow.createCell(haderIdx++);
    				cell.setCellValue(key);
    				cell.setCellStyle(titleCellStyle);
    			}
    
    			HSSFCellStyle contentCellStyle = wb.createCellStyle();
    			contentCellStyle.setBorderBottom(HSSFCellStyle.BORDER_THIN);
    			contentCellStyle.setBorderLeft(HSSFCellStyle.BORDER_THIN);
    			contentCellStyle.setBorderRight(HSSFCellStyle.BORDER_THIN);
    			contentCellStyle.setBorderTop(HSSFCellStyle.BORDER_THIN);
    			contentCellStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
    			contentCellStyle.setFillForegroundColor(HSSFColor.WHITE.index);
    
    			for( int i=0,size=list.size(); i<size; i++ ){
    				row = sheet.createRow((i+1));
    
    				it = list.get(0).keySet().iterator();
    				haderIdx = 0;
    				while (it.hasNext()) {
    					String key = it.next();
    
    					cell = row.createCell(haderIdx++);
    					cell.setCellStyle(contentCellStyle);
    
    					if( list.get(i).get(key) instanceof String ) 			cell.setCellValue( (String) list.get(i).get(key) );
    					if( list.get(i).get(key) instanceof Integer ) 			cell.setCellValue( (Integer) list.get(i).get(key) );
    					if( list.get(i).get(key) instanceof Long ) 				cell.setCellValue( (Long) list.get(i).get(key) );
    					if( list.get(i).get(key) instanceof Float ) 			cell.setCellValue( (Float) list.get(i).get(key) );
    					if( list.get(i).get(key) instanceof Double ) 			cell.setCellValue( (Double) list.get(i).get(key) );
    					if( list.get(i).get(key) instanceof Date ) 				cell.setCellValue( (Date) list.get(i).get(key) );
    					if( list.get(i).get(key) instanceof java.sql.Date ) 	cell.setCellValue( (java.sql.Date) list.get(i).get(key) );
    				}
    			}
    		}catch(Exception e){
    			logger.error("POI List에서 Excel로 변환 중 오류가 발생하였습니다.");
    			logger.error(e);
    		}
    		return wb;
    	}
    }
    

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


    dom4j-1.6.1.jar

    poi-3.6-20091214.jar

    poi-ooxml-3.6-20091214.jar

    poi-ooxml-schemas-3.6-20091214.jar

    xmlbeans-2.3.0.jar

    환경 : Struts2 + Spring + Sitemesh 등

    기존 개발된 환경에서 Source 는 그대로 유지하면서 MVC Action 단 Result View를 XML, JSON 등의 형태로 받고 싶었다.

    아래 아키텍처를 보고 고민하던 중 Result 단을 좀 꺽으면 될듯 싶어 작업해 보았다.

    우선 Default Result 인 org.apache.struts2.dispatcher.ServletDispatcherResult 를 상속받아서 doExecute 함수를 재정의 해서 호출확장자가 .action 일때는 기존과 그대로 처리 하고, action.xml, action.json 일때는 request.getAttributeNames() 배열에 있는 데이터를 xml, json 문자열로 만들어서 response.getWriter() 이용해서 출력하면 되겠네요.
    (해당 정보를 파라미터로 해도 될듯하고, Request Header 정보를 활용해도 되겠습니닷.)


    신경써야 할 점은 getAttribute 배열에 들어있는 불필요한 데이터는 무시합니다.
    또한 Struts2 가 POJO 기반이여서 Action 단의 데이터들도 뽑아와야 하는데 파마리터 ActionInvocation 객체에 getAction() 함수를 통해서 얻을 수 있습니다.
    (getAttribute에 "struts.valueStack" 에 OgnlValueStack 객체에도 Action  정보가 담겨 있습니다. OgnlValueStack 는 배열로서 해당 Action 객체 일때 필요한 getter 메소드 호출해서 사용하면 되겠습니다.  ActionInvocation  에서 뽑아 올 경우 getAttributeNames() 이용 시  "struts.valueStack" 정보는 제외 시켜야 합니다.)


    필요에 따라 다른 ResultType 들도 수정하시면 되겠습니다.

    수정 하시고, struts 설정파일 package 하위 result-type 에 등록하시면 되겠습니다.

    또한 super.doExceute() 함수에 try ~ catch 거시면 JSP Runtime Exception 에 대한 처리도 할 수 있겠네요.


    JSON, XML 변환은 아래 package를 사용했습니다.
    ezmorph-1.0.4.jar
    json-lib-2.2.1-jdk15.jar
    xom-1.1.jar
    * 위 jar 최신버전으로 하면 오류나네요...
    * 또한 XML Element Name이 숫자로 생성되면 파싱 오류납니다.

     



    + Recent posts