전에 잠깐 눈으로만 알고 있었는데, 한번 셈플링 해보았다.

자세한것은 모르지만 Selenium 과 WebDriver 의 장점을 살려서 Selenium 2 로 탄생했단다.


우선 편리한 레코딩을 위해서 Firefox와 Firefox Plugin으로 Selenium IDE를 설치한다.

http://docs.seleniumhq.org/download/


아래 IDE로 자동 레코딩된 소스를 조금 수정해서 돌려보았다.


Firefox 이외의 브라우저에서는 alert 클릭이 부분이 정상적으로 되지 않았다...


추 후에 연구해봐야 할듯...(단순하게 WebDriver만 바꾸는게 아닌건가...)


또한 IDE로 레코딩 할때에 frameset 기반의 홈페이지는 레코딩이 잘 되지 않았고, javascript로 레이어로 펼쳐서 클릭하는 메뉴의 경우에도 Firefox를 제외하고는 정상적으로 되지 않았다.


[LoginTest.java]

import static org.junit.Assert.fail;

import java.io.File;
import java.util.concurrent.TimeUnit;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.remote.DesiredCapabilities;

public class LoginTest {
	private WebDriver driver;
	private String baseUrl;
	private boolean acceptNextAlert = false;
	private StringBuffer verificationErrors = new StringBuffer();
	private int browser = 1;
	
	@Test
	public void loginTest() throws Exception {
		baseUrl = "http://localhost:8080/";
		driver.get(baseUrl + "/login");
		driver.findElement(By.name("j_username")).clear();
		driver.findElement(By.name("j_username")).sendKeys("1");
		driver.findElement(By.name("j_password")).clear();
		driver.findElement(By.name("j_password")).sendKeys("2");
		driver.findElement(By.name("submit")).click();
		driver.findElement(By.cssSelector("button")).click();
		Assert.assertEquals("이구용", closeAlertAndGetItsText());
	}
	
	@Before
	public void setUp() throws Exception {
		DesiredCapabilities caps = null;
		File f = null;
		
		if( browser == 0 ){
			driver = new HtmlUnitDriver(true);
		}else if( browser == 1 ){
			driver = new FirefoxDriver();
		}else if( browser == 2 ){
			f = new File("E:\\webdriver\\IEDriverServer32\\IEDriverServer.exe");
			System.setProperty("webdriver.ie.driver", f.getAbsolutePath());
			
			caps = DesiredCapabilities.internetExplorer();
			caps.setCapability(InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS, true);
			driver = new InternetExplorerDriver(caps);
		}else if( browser == 3 ){
			f = new File("C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe");
			System.setProperty("webdriver.chrome.driver", f.getAbsolutePath());
			
			driver = new ChromeDriver();
		}
		
		
		driver.manage().timeouts().implicitlyWait(300, TimeUnit.SECONDS);
	}
	
	@After
	public void tearDown() throws Exception {
		System.out.println("@After");
		driver.quit();
		String verificationErrorString = verificationErrors.toString();
		if (!"".equals(verificationErrorString)) {
			fail(verificationErrorString);
		}
	}

	private String closeAlertAndGetItsText() {
		try {
			Alert alert = driver.switchTo().alert();
			String alertText = alert.getText();
			if (acceptNextAlert) {
				alert.accept();
			} else {
				alert.dismiss();
			}
			return alertText;
		} finally {
			acceptNextAlert = true;
		}
	}
}



[build.gradle]

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

sourceCompatibility = 1.5
version = '1.0'
jar {
    manifest {
        attributes 'Implementation-Title': 'Gradle Quickstart', 'Implementation-Version': version
    }
}

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
    testCompile group: 'junit', name: 'junit', version: '4.+'
    //testCompile 'org.seleniumhq.selenium:selenium-java:2.28.0'
	//testCompile 'org.seleniumhq.selenium:selenium-firefox-driver:2.39.0'
	//testCompile 'org.seleniumhq.selenium:selenium-server:2.39.0'
	//testCompile 'org.seleniumhq.selenium:selenium-server-standalone:2.39.0'
	testCompile 'org.seleniumhq.selenium:selenium-java:2.39.0'
	testCompile 'org.seleniumhq.selenium:selenium-firefox-driver:2.39.0'
	testCompile 'org.seleniumhq.selenium:selenium-chrome-driver:2.39.0'
	testCompile 'org.seleniumhq.selenium:selenium-IE-driver:2.39.0'
	testCompile 'org.seleniumhq.selenium:selenium-HtmlUnit-driver:2.39.0'
}




ViewResolver 중에 ContentNegotiatingViewResolver 에대한 메모를 한다.

아마 활용도가 가장 많을듯 싶다.


기존에 "Spring Security(스프링 시큐리티) Exception(인증, 허가)에 따른 공통 처리" 에서 살짝 소스로만 남겨놨었는데 그때는 호출 경로(확장자)에 따른 View선택 방식이였다.

(http://localhost/test.do HTML 데이터 리턴, http://localhost/test.json JSON 데이터 리턴, http://localhost/test.xml XML 데이터 리턴)


그런데 위 Security 셈플링을 하였을때는 더 세부적으로 하지 않아서 몰랐었는데 인증 및 권한획득 과정에서 문제가 발생하였다.


예를 들어 Security를 이용하여 로그인을 시도 하였을때, 인증 실패등의 Exception 상황에 맞는(html,json,xml)을 정보를 리턴해주어야 하는데 Security에서 제공하는 "http://..../j_spring_security_check" 에서는 json, xml 확장자를 붙일수 없다는것이었다.

form-login 의 login-processing-url 속성으로 변경은 가능하지만 현재 do, json, xml 등 1개 이상 등록이 설정파일에서는 못 하는것 같았다.(나만 모를수도...)


그래서 확장자를 통한 방법이 아닌 mediaType 따른 분기 방법을 셈플링 해보았다.

인터넷 상에 MediaTypes 를 ContentNegotiationManagerFactoryBean 에 바로 setter 하는 셈플들이 많을텐데, 3.2 버전인가 어디에서 충돌 버그인가 머시기인가 문제가 있어서 최근 버전에서는 setMediaTypes가 @Deprecated 되었다고 한다.

만약 문제 발생시 디컴파일러를 통해서 해당 함수의 @Deprecated 여부를 확인해보세요!!


MediaType 방식을 사용할경우 Request Header 정보에 따라서 뷰를 결정하게 된다.

Accept 값이 = application/json ....

application/xml ....

= text/html ... 등등등


이러한 값들은 jQuery를 사용할 경우 별도 셋팅이 필요 없이, jQuery.ajax({dataType: "json"....)  값에 따라 자동 셋팅됩니다.



[servlet-context.xml]
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
 
    <mvc:annotation-driven />
    <context:component-scan base-package="pe.kr.ddakker" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
        <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
    </context:component-scan>
 
    <mvc:interceptors>
        <bean class="pe.kr.ddakker.dakmoney.web.servlet.interceptor.TestInterceptor"/>
    </mvc:interceptors>
 
    <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
    	<property name="order" value="1" />
	    <property name="contentNegotiationManager">
	        <bean class="org.springframework.web.accept.ContentNegotiationManager">
	        	<constructor-arg>
		            <bean class="org.springframework.web.accept.HeaderContentNegotiationStrategy">
		            </bean>
	            </constructor-arg>
	        </bean>
	    </property>
	    <property name="defaultViews">
	        <list>
	            <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
	        </list>
	    </property>
	</bean>

	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    	<property name="order" value="2" />
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

spring 3.2.9 에서 테스트


 menuCd

 highMenuCd

 menuNm

 orderNo

 

 1001

 0

 고객

 1

 1002

 1001

 고객리스트

 2
 1003 1002 대기리스트 3
 1004 0 게시판 4
 1005 1004 자유게시판 5
 1006 1004 유머게시판 6


위와 같은 형식의 데이터를 DB에서 뽑아 냈을때 오른쪽과 같이 화면에 표현하고 싶었다.


우선 Tree는 jQuery기반의 드래그앤랍이 기능이 되는 "jquery.dynatree.js" 를 선택했다.


이제 위 2차원 배열형태의 데이터를 dynatree가 요구하는 데이터형태로 변환을 해야 한다.

요구하는 데이터형태는 트리형식으로 아래와 같은 스타일로 만들어주면 된다.


[

{"isFolder":"true","title":"고객","children":[

{"isFolder":"true","title":"고객리스트" }

...

    ]

....

]


위와 같은 형태로 만들려고 삽질 좀 하다가 javascript 기반으로 잘 만들어져있는 소스를 구글링을통해 발견하여 그것을 java기반으로 수정 하였습니다.

(http://programmingsummaries.tistory.com/250)






import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * JSON관련 가공에 필요한 함수들
 * @auther ddakker 2013. 12. 12.
 */
public class JsonUtil {
	private static final Log log = LogFactory.getLog(JsonUtil.class);
	/**
	 * 2차원 배열의 부모/자식 관계의 데이터를 트리형식으로 재나열 한다.
	 * @param list			2차원 배열
	 * @param rootId		최상위 id
	 * @param idKey			유니크한 키(id가 될 필드명)
	 * @param pIdKey		부모키(pId가 될 필드명)
	 * @param titleKey		메뉴명이 표시될 필드명
	 * @return
	 * @auther ddakker 2013. 12. 12.
	 */
	public static List<Map<String, Object>> convertorTreeMap(final List<Map<String, Object>> list, String rootId, final String idKey, final String pIdKey, final String titleKey){
		return convertorTreeMap(list, rootId, idKey, pIdKey, titleKey, null);
	}
	/**
	 * 2차원 배열의 부모/자식 관계의 데이터를 트리형식으로 재나열 한다.
	 * @param list			2차원 배열
	 * @param rootId		최상위 id
	 * @param idKey			유니크한 키(id가 될 필드명)
	 * @param pIdKey		부모키(pId가 될 필드명)
	 * @param titleKey		메뉴명이 표시될 필드명
	 * @param orderKey		정렬이 필요한경우 정령 필드명
	 * @return
	 * @auther ddakker 2013. 12. 12.
	 */
	public static List<Map<String, Object>> convertorTreeMap(List inList, String rootId, final String idKey, final String pIdKey, final String titleKey, final String orderKey){
		List<Map<String, Object>> treeList = new ArrayList<Map<String,Object>>();	// 최종 트리
		
		if( inList == null || inList.size() == 0 ) 	throw new RuntimeException("List<Map> 데이터가 없습니다.");
		if( inList.get(0) == null ) 				throw new RuntimeException("Map 데이터가 없습니다.");
		
		final List<Map<String, Object>> list = new ArrayList<Map<String,Object>>();	// 원본데이터(Bean일경우 Map으로 변환)
		Iterator iter;
		for( iter=inList.iterator(); iter.hasNext(); ) {
			try{
				Object obj = iter.next();
				if( obj instanceof Map ) {
					list.add((Map<String, Object>) obj);
				}else{
					list.add((Map<String, Object>) BeanUtils.describe(obj));
				}
			}catch (Exception e) {
				throw new RuntimeException("Collection -> List<Map> 으로 변환 중 실패: " + e);
			}
		}
		
		
		int listLength = list.size();
		int loopLength = 0;
		final int[] treeLength = new int[] { 0 };
		
		while ( treeLength[0] != listLength && listLength != loopLength++ ) {
			for ( int i=0; i<list.size(); i++ ) {
				Map<String, Object> item = list.get(i);
				if ( rootId.equals((String)item.get(pIdKey)) ) {
					Map<String, Object> view = new HashMap<String, Object>(item);
					view.put("title", item.get(titleKey));
					view.put("children", new ArrayList<Map<String,Object>>());
					
					treeList.add(view);
					list.remove(i);
					
					treeLength[0]++;
					
					
					if( orderKey != null ){
						Collections.sort(treeList, new Comparator<Map<String, Object>>(){
							public int compare(Map<String, Object> arg0, Map<String, Object> arg1) {
								// TODO Auto-generated method stub
								return ((String)arg0.get(orderKey)).compareTo((String)arg1.get(orderKey));
							}
						});
					}
					view.put("isFolder", "true");
					
					break;
				}else{
					new InnerClass(){
			            public void getParentNode(List<Map<String, Object>> children, Map<String, Object> item ) {
			            	for ( int i=0; i<children.size(); i++ ) {
			    				Map<String, Object> child = children.get(i);
			    				if ( child.get(idKey).equals(item.get(pIdKey)) ) {
			    					Map<String, Object> view = new HashMap<String, Object>(item);
			    					view.put("title", item.get(titleKey));
			    					view.put("children", new ArrayList<Map<String,Object>>());
			    					((List<Map<String,Object>>) child.get("children")).add(view);
			    					
			    					treeLength[0]++;
			    					
			    					list.remove(list.indexOf(item));
			    					view.put("isFolder", "true");
			    					
			    					if( orderKey != null ){
				    					Collections.sort(((List<Map<String,Object>>) child.get("children")), new Comparator<Map<String, Object>>(){
				    						public int compare(Map<String, Object> arg0, Map<String, Object> arg1) {
				    							// TODO Auto-generated method stub
				    							return ((String)arg0.get(orderKey)).compareTo((String)arg1.get(orderKey));
				    						}
				    					});
			    					}
			    					break;
			    				}else{
			    					if( ((List<Map<String,Object>>) child.get("children")).size() > 0 ){
			    						getParentNode((List<Map<String,Object>>) child.get("children"), item);
			    					}
			    				}
			            	}
			            }
			        }.getParentNode(treeList, item);
				}
			}
		}
		return treeList;
	}
	
	public interface InnerClass {
		public void getParentNode(List<Map<String, Object>> list, Map<String, Object> item );
    }
	
}

Spring Security 를 이용해서 셈플링을 해보면 인증 및 허가에 대한 Exception 이 발생시 LoginForm으로 Redirect 하게 된다.


하지만 나는 그렇게 하고 싶지 않다!!!

일반적인 경우에는 별도의 페이지로 분기하고 싶고, AJAX나 외부 통신시에는 해당 메시지를 XML이나 JSON으로 리턴해주고 싶다!!!


방법을 고민해보다가.. 책도 찾아보고, 인터넷도 뒤져보고.. 방법을 찾아 적어놔본다...




위 그림에서와 같이 ExceptionTranslationFilter 에서 인증 및 권한에 대한 검사를 한다고 한다.


인증이 되지 않았을 경우(비로그인)에는 AuthenticationEntryPoint 부분에서 AuthenticationException 을 발생 시키고, 인증(로그인)은 되었으나 해당 요청에 대한 권한이 없을 경우에는 AccessDeniedHandler 부분에서 AccessDeniedException 이 발생된다.


그렇다면 AuthenticationEntryPoint  부분과 AccessDeniedHandler 부분을 재정의해서 처리해주면 될듯하다.


나의 경우 재정의 한부분에서 특정 Controller 로 Forward 처리 했다.

호출 케이스별로 jsp View, xml, json 으로 출력해줘야 하는데, 재정의 한부분에서 출력해주는 것보다 특정 Controller Method로 하게 되면 MVC 공통으로 처리한것으로 인해 알아서 케이스별로 출력될것이기에...




[build.gradle]

apply plugin: 'war'
apply plugin: 'eclipse-wtp'
apply plugin: 'eclipse'

// JAVA Version 1.6
sourceCompatibility = '1.6'
targetCompatibility = '1.6'

eclipse {
    wtp {
        component {
            contextPath = "/"
        }
        facet {
            facet name: 'jst.web', version: '2.5'
            facet name: 'jst.java', version: '1.6'
        }
    }
}

// 의존성 설정에 사용할 프로퍼티
springVersion = '3.2.0.RELEASE'
springSecurityVersion = '3.1.3.RELEASE'
slf4jVersion = '1.7.5'
logbackVersion = '1.0.13'

// 메이븐 Central 저장소 사용
repositories {
    mavenCentral()
}

List loggers = [
    "ch.qos.logback:logback-classic:1.0.13",
    "org.slf4j:jcl-over-slf4j:1.7.5",
]

// 의존성 설정
dependencies {
	compile "org.springframework:spring-webmvc:$springVersion"
	compile "org.springframework:spring-oxm:$springVersion"

    compile "org.springframework.security:spring-security-web:$springSecurityVersion"
    compile "org.springframework.security:spring-security-taglibs:$springSecurityVersion"
    compile "org.springframework.security:spring-security-config:$springSecurityVersion"

    compile "cglib:cglib-nodep:2.2.2"
	compile "javax.servlet:jstl:1.2"

	compile "org.codehaus.jackson:jackson-mapper-asl:1.9.13"
	compile "net.sf.json-lib:json-lib:2.2.1:jdk15"
	compile "net.sf.json-lib:json-lib-ext-spring:1.0.2"
	compile "xom:xom:1.2.5"
	compile "commons-lang:commons-lang:2.6"

	compile loggers

    testCompile "org.springframework:spring-test:$springVersion"
	testCompile 'junit:junit:4.8.2'
	testCompile 'org.mockito:mockito-core:1.9.0'
}

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


[application-context-security.xml]

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/security
                        http://www.springframework.org/schema/security/spring-security.xsd">

    <security:http auto-config="true" use-expressions="true" entry-point-ref="authenticationEntryPoint">
        <!-- <security:access-denied-handler error-page="/accessIsDenied" /> -->
        <security:access-denied-handler ref="acessDeniedHandler" />
        <security:intercept-url pattern="/login" access="permitAll" />
        <security:intercept-url pattern="/accessIsDenied*" access="permitAll" />
        <security:intercept-url pattern="/isNotLogin*" access="permitAll" />
        <security:intercept-url pattern="/admin*" access="hasRole('admin')" />
        <security:intercept-url pattern="/**" access="hasRole('user')" />
        <security:form-login login-page="/login" default-target-url="/" />
        <security:logout logout-url="/logout" logout-success-url="/login" invalidate-session="true" />
    </security:http>

	<!-- 권한 없는 요청일 경우 -->
	<bean id="acessDeniedHandler" class="pe.kr.ddakker.framework.security.web.access.AccessDeniedHandlerImpl">
		<property name="redirect" value="false" />
		<property name="errorPage" value="/assessDenied.do" />
	</bean>
	
	<!-- 인증전 일 경우 -->
	<bean id="authenticationEntryPoint" class="pe.kr.ddakker.framework.security.web.AuthenticationEntryPointImpl">
		<property name="redirect" value="false" />
		<property name="errorPage" value="/isNotLogin.do" />
	</bean>
    <bean id="encoder" class="org.springframework.security.crypto.password.StandardPasswordEncoder"/>

    <security:authentication-manager>
        <security:authentication-provider>
            <security:password-encoder ref="encoder"/>
            <security:user-service>
                <!-- Password: "koala" for both -->
                <security:user name="user"
                      password="4efe081594ce25ee4efd9f7067f7f678a347bccf2de201f3adf2a3eb544850b465b4e51cdc3fcdde"
                      authorities="user"/>
                <security:user name="admin"
                      password="4efe081594ce25ee4efd9f7067f7f678a347bccf2de201f3adf2a3eb544850b465b4e51cdc3fcdde"
                      authorities="admin"/>
            </security:user-service>
        </security:authentication-provider>
    </security:authentication-manager>
</beans>


[AccessDeniedHandlerImpl.java]

package pe.kr.ddakker.framework.security.web.access;

import static pe.kr.ddakker.framework.web.servlet.view.ServletHelper.convertorViewTypeErrorPage;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

/**
 * 권한이 없는 요청시 발생
 * @auther ddakker 2013. 12. 6.
 */
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
	private Boolean redirect = true;
	public Boolean getRedirect() {
		return redirect;
	}
	public void setRedirect(Boolean redirect) {
		this.redirect = redirect;
	}

	private String errorPage;
	public void setErrorPage(String errorPage) {
		this.errorPage = errorPage;
	}
	public String getErrorPage() {
		return errorPage;
	}

	@Override
	public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
		// 에러 페이지에 대한 확장자를 현재 호출한 확장자와 마추어준다.
		String goErrorPage = convertorViewTypeErrorPage(request, errorPage);
		if( redirect ){
			response.sendRedirect(goErrorPage);
		}else{
			RequestDispatcher dispatcher = request.getRequestDispatcher(goErrorPage);
			dispatcher.forward(request, response);
		}
	}

}


[AuthenticationEntryPointImpl.java]

package pe.kr.ddakker.framework.security.web;

import static pe.kr.ddakker.framework.web.servlet.view.ServletHelper.convertorViewTypeErrorPage;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

/**
 * 인증하지 않은 상황에서 호출시 발생
 * @auther ddakker 2013. 12. 6.
 */
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
	private Boolean redirect = true;
	public Boolean getRedirect() {
		return redirect;
	}
	public void setRedirect(Boolean redirect) {
		this.redirect = redirect;
	}

	private String errorPage;
	public void setErrorPage(String errorPage) {
		this.errorPage = errorPage;
	}
	public String getErrorPage() {
		return errorPage;
	}

	@Override
	public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
		// 에러 페이지에 대한 확장자를 현재 호출한 확장자와 마추어준다.
		String goErrorPage = convertorViewTypeErrorPage(request, errorPage);
		if( redirect ){
			response.sendRedirect(goErrorPage);
		}else{
			RequestDispatcher dispatcher = request.getRequestDispatcher(goErrorPage);
			dispatcher.forward(request, response);
		}
	}

}


[servlet-context.xml]

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<mvc:annotation-driven />
	<context:component-scan base-package="pe.kr.ddakker" use-default-filters="false">
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
		<context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
	</context:component-scan>

	<mvc:interceptors>
        <bean class="pe.kr.ddakker.dakmoney.web.servlet.interceptor.TestInterceptor"/>
    </mvc:interceptors>

	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

	
	<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
	    <property name="contentNegotiationManager">
	        <bean class="org.springframework.web.accept.ContentNegotiationManager">
	            <constructor-arg>
	            <bean
	                class="org.springframework.web.accept.PathExtensionContentNegotiationStrategy">
	                <constructor-arg>
	                    <map>
	                        <entry key="xml" value="application/xml" />
	                        <entry key="json" value="application/json" />
	                        <entry key="jsonp" value="application/javascript" />
	                    </map>
	                </constructor-arg>
	            </bean>
	            </constructor-arg>
	        </bean>
	    </property>
	    <property name="defaultViews">
	        <list>
	            <bean class="pe.kr.ddakker.framework.web.servlet.view.xml.MappingXmlView">
	            	<property name="contentType" value="application/xml"/>
	            </bean>
	            <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
	            	<property name="contentType" value="application/json"/>
	            </bean>

	            <bean class="pe.kr.ddakker.framework.web.servlet.view.json.MappingJsonpView">
	                <property name="contentType" value="application/javascript"/>
	            </bean>
	        </list>
	    </property>
	    <property name="ignoreAcceptHeader" value="false" />
	</bean>
</beans>


[HomeController.java]

package pe.kr.ddakker.dakmoney;

import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import pe.kr.ddakker.framework.web.servlet.exception.SessionNotException;

@Controller
public class HomeController {

	private final Logger logger = LoggerFactory.getLogger(getClass());

	/**
	 * Simply selects the home view to render by returning its name.
	 */
	@RequestMapping(value = "/")
	public String index(Locale locale, Model model) {
		logger.info("Welcome home! The client locale is {}.", locale);
		HomeBean helloBean = new HomeBean();
		helloBean.setNm("이름 index");
		helloBean.setAge(22);
		model.addAttribute("helloBean", helloBean);

		Date date = new Date();
		DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);

		String formattedDate = dateFormat.format(date);

		model.addAttribute("serverTime", formattedDate + " Hello 안녕 하세요" );

		return "home";
	}

	/**
	 * Simply selects the home view to render by returning its name.
	 * @throws Exception
	 */
	@RequestMapping(value = "/json", method = RequestMethod.GET)
	public String index2(Locale locale, Model model) throws Exception {
		logger.info("Welcome home! The client locale is {}.", locale);
		HomeBean helloBean = new HomeBean();
		helloBean.setNm("이름 index2");
		helloBean.setAge(22);
		model.addAttribute("helloBean", helloBean);

		Date date = new Date();
		DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);

		String formattedDate = dateFormat.format(date);

		model.addAttribute("serverTime", formattedDate + " Hello 안녕 하세요" );

		return "home";
	}

	/**
	 * Simply selects the home view to render by returning its name.
	 * @throws Exception
	 */
	@RequestMapping(value = "/json2", method = RequestMethod.GET)
	public String index22(Locale locale, Model model) throws Exception {
		logger.info("Welcome home! The client locale is {}.", locale);
		HomeBean helloBean = new HomeBean();
		helloBean.setNm("이름 index22");
		helloBean.setAge(22);
		model.addAttribute("helloBean", helloBean);

		Date date = new Date();
		DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);

		String formattedDate = dateFormat.format(date);
		if( true ){
			throw new RuntimeException("와우");
		}

		model.addAttribute("serverTime", formattedDate + " Hello 안녕 하세요" );

		return "home";
	}

	@RequestMapping(value = "/login", method = RequestMethod.GET)
	public String login(Locale locale, Model model) throws Exception {
		logger.debug("loginForm");
		return "loginForm";
	}

	@RequestMapping(value = "/isNotLogin", method = RequestMethod.GET)
	public void isNotLogin(Model model, @RequestParam Map map) throws Exception {
		logger.debug("---------- isNotLogin()");
		logger.debug("---------- isNotLogin() params: " + map);
		if( true ) throw new SessionNotException("로그인 되지 않았습니다.");
	}

	@RequestMapping(value = "/assessDenied", method = RequestMethod.GET)
	public String assessDenied(Locale locale, Model model) throws Exception {
		logger.debug("---------- assessDenied()");
		if( true ) throw new SessionNotException("권한이 없는 요청입니다.");
		return "assessDenied";
	}

	@RequestMapping(value = "/admin", method = RequestMethod.GET)
	public String admin(Locale locale, Model model) throws Exception {
		logger.debug("---------- admin()");
		return "admin";
	}
}


[DefaultControllerAdvice.java]

package pe.kr.ddakker.dakmoney;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

import pe.kr.ddakker.framework.web.servlet.exception.SessionNotException;

@ControllerAdvice
public class DefaultControllerAdvice {
	private final Logger logger = LoggerFactory.getLogger(DefaultControllerAdvice.class);

	@ExceptionHandler(Exception.class)
	public ModelAndView handleException(HttpServletRequest request, Exception ex) {
		logger.error("에러임 Exception: {}", ex.toString());
		printExceptionInfo(request, ex);
        ModelAndView mv = new ModelAndView("error");
        mv.addObject("result", "9999");
        mv.addObject("message", ex.getMessage());
        return mv;
    }

	@ExceptionHandler(RuntimeException.class)
	public ModelAndView handleRuntimeException(HttpServletRequest request, RuntimeException ex) {
		logger.error("에러임 RuntimeException: {}", ex.toString());
		printExceptionInfo(request, ex);
        ModelAndView mv = new ModelAndView("error");
        mv.addObject("result", "8888");
        mv.addObject("message", ex.getMessage());
        return mv;
    }
	
	@ExceptionHandler(SessionNotException.class)
	public ModelAndView handleSessionNotException(HttpServletRequest request, SessionNotException ex) {
		logger.error("에러임 SessionNotException: {}", ex.toString());
		printExceptionInfo(request, ex);
        ModelAndView mv = new ModelAndView("error");
        mv.addObject("result", "7777");
        mv.addObject("message", ex.getMessage());
        return mv;
    }
	
	private void printExceptionInfo(HttpServletRequest request, Exception ex){
		logger.error("PARMAS: {}", request.getParameterMap());
		for( StackTraceElement s : ex.getStackTrace() ){
			logger.error("STACK: {}", s);
		}
	}

}


최근에 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.


이전 버전에는 개별적인 Controller에서 @ExceptionHandler 를 활용해서 Exception을 모았었다.


공통화를 위해 고민해보다가 @ExceptionHandler 함수 구현을 상위 클래스에 두고 Controller에서 해당 클래스를 모조건 상속을 받게끔 하는 방법으로 해보았었는데.. 좀 찜짐한감이 없잖아 있었다..(불필요한 코딩 한자라도 줄이고 싶었다.., 또한 특정 템플릿을 강요하고 싶지도 않았다..)


아니면 Interceptor 에서 Exception을 Cache 하여 처리하는 방법으로 했다던가..


셈플링 해보던중.. @ControllerAdvice를 발견!!


상속, Interceptor 없이 모든 Exception을 모을 수 있다!!



@ControllerAdvice
public class DefaultControllerAdvice {
	private static final Logger logger = LoggerFactory.getLogger(DefaultControllerAdvice.class);

	@ExceptionHandler(RuntimeException.class)
	public ModelAndView handleRuntimeException(RuntimeException ex) {
		logger.error("에러임 RuntimeException: {}", ex);
        ModelAndView mv = new ModelAndView("error");
        mv.addObject("result", "999");
        mv.addObject("message", ex.getMessage());
        return mv;
    }

}


	
	<mvc:annotation-driven />
	<context:component-scan base-package="pe.kr.ddakker" use-default-filters="false">
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
		<context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
	</context:component-scan>

propagation="REQUIRED" read-only="true"


Spring AOP Transaction 과 관련하여 와와 같이 설정 하였을 경우 해당 설정이 적용되는 트랜잭션의 경우 insert/update/delete가 일어난다면 ".. TransientDataAccessResourceException .. java.sql.SQLException: Connection is read-only. .." 와 같은 예외가 나면서 중지되어야 한다고 인터넷, 책 등등에 설명이 잘 되어있다.


하지만... 안됐다.. 개삽질 했다.. 몇시간을 삽질한지 모르겠다..

별 셈플링을 다 해봤다.. 안됐다..


그런데 DB가 MYSQL로 셈플링 한 프로젝트에서 동일하게 적용해보았으나 잘 되었다.


이것저것 찾아보니 DBMS Vendor따라서.. 안될 수도 있단다..



[참고] https://groups.google.com/forum/#!searchin/ksug/AOP$20트랜잭션$20설정에$20대해서../ksug/tVLRWx8R_B4/iCL_Cz6VWzMJ

  • 기본 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
    import org.apache.commons.beanutils.BeanUtils;
    ..
    ..
    
    	public <T> T setParamegerBean(Class<T> clz) throws InstantiationException, IllegalAccessException, InvocationTargetException{
    		T t = clz.newInstance();
    		BeanUtils.copyProperties(t, ServletActionContext.getRequest().getParameterMap());
    		return t;
    	}
    

    + Recent posts