로그성 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);
		}
	}
}


web.xml 의 Filter 부분을 동적으로 처리해야 할 일이 생겼다.


Spring 3.1 이상을 사용하게 되면 org.springframework.web.WebApplicationInitializer 를 이용하게되면 더 심플하겠지만 현재 상황에서는 Spring 2.5 환경이여서 불가능!!


Servlet 3.x (javax.servlet.ServletContainerInitializer) 만으로 처리해보자.



유의할 점은 Tomcat 8.x 에서는 아래와 같이 그대로 해도 정상적으로 작동하지만 JBoss와 같이 Java EE 환경에서는 

jar cvf webxml.jar META-INF/services/javax.servlet.ServletContainerInitializer pe.kr.ddakker.WebXml.class

와 같이 jar 생성 후 WEB-INF/lib 하위에 위치 시켜야 정상 작동합니다.


build.gradle

dependencies {
	providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
}


WebXml.java

package pe.kr.ddakker;

import java.util.EnumSet;
import java.util.Set;

import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import pe.kr.ddakker.filter.TestFilter;

public class WebXml implements ServletContainerInitializer {

	@Override
	public void onStartup(Set> c, ServletContext servletContext) throws ServletException {
		System.out.println("ServletContainerInitializer");


		TestFilter testFilter = new TestFilter();
        FilterRegistration.Dynamic test = servletContext.addFilter("testFilter", testFilter);
        test.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");

	}
}


TestFilter.java

package pe.kr.ddakker.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class TestFilter implements Filter {

    public TestFilter() {
    }

	public void destroy() {
	}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		// pass the request along the filter chain
		System.out.println("TestFilter S");
		chain.doFilter(request, response);
		System.out.println("TestFilter E");
	}

	public void init(FilterConfig fConfig) throws ServletException {
	}

}


classpath 하위 META-INF/services/javax.servlet.ServletContainerInitializer

pe.kr.ddakker.WebXml


Redis 를 활용해서 Cache를 적용해보자..


우선 Redis Client는 http://www.redis.io/clients 에서 확인되는바와 같이 종류가 여러가지다.

그런데 Spring Data Redis 를 사용하면 별도의 Client가 필요 없네...(Interface만 있고 구현체는 가져다 쓰는줄 알았더니 아니네요.)


Source Code에서 사용법과 Method Annotataion 사용법을 보자.


[build.gradle]

dependencies {
	compile springDependencies // Spring 3.2.x
	compile 'org.springframework.data:spring-data-redis:1.3.4.RELEASE'
}


[context-cache.xml]



 
	
 
	
		
		
		
	
 
	
		
		
		
	
 
	
		
	
	
	
	
		
	
 
	
		
		
	    
	    
	    
		    
				
				
			
		
		
		
			
	            BANNER
	            EVENT
	        
		
	
 
	
	    
	    
	
 
	
	    
	    
	
 


[RedisTestCase.java]

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
		"classpath*:config/spring/context-common.xml",
		"classpath*:config/spring/context-cache.xml",
		"classpath*:config/spring/context-datasource.xml"
})
public class RedisTestCase {
    @Resource Cache cacheEvent;
    @Resource Cache cacheBanner;
 
    @Resource AuthService authService; 
 
    @Test
    public void test() {
    	User user = new User();
		user.setUserNm("ddakker");
	 
		cacheEvent.put("a", "zzz");
		String a = cacheEvent.get("a");
		System.out.println("a: " + a);
	 
		cacheBanner.put("user", user);
		User resultUser = cacheBanner.get("user");
		System.out.println("user: " + resultUser);
    }
 
}
 
@Test
public void testAnnotation() {
    // DB 쿼리 호출
    List authList = authService.getList();
    System.out.println("authList: " + authList);
 
    // DB 쿼리 호출 않음
    authList = authService.getList();
    System.out.println("authList: " + authList);
 
    Auth auth = new Auth();
    auth.setSearchKey("AUTH_CD");
    auth.setSearchKwd("ROLE_USER");
 
    // DB 쿼리 호출
    authList = authService.getList(auth);
    System.out.println("authList user: " + authList);
 
    // DB 쿼리 호출 않음
    authList = authService.getList(auth);
    System.out.println("authList user: " + authList);
}


[AuthService.java]

// @Cacheable 의 key나 조건의 디테일한 제어에 대한 설멍은 - http://dev.anyframejava.org/docs/anyframe/plugin/optional/cache/1.0.3/reference/html/ch01.html
@Service
public class AuthService {
	@javax.annotation.Resource AuthMapper authMapper;
 
	@Cacheable(value="BANNER")
	public List getList() {
		return authMapper.getList(new Auth());
	}
 
	@Cacheable(value="EVENT")
	public List getList(Auth authVo) {
		return authMapper.getList(authVo);
	}
}

Spring MyBatis 환경에서 아래와 같은 메시지가 나오면서 트랜잭션 정상적으로 작동 안 하는 상황이 있다.


was not registered for synchronization because synchronization is not active

will not be managed by Spring 


TestCase로 Service 계층에서부터 테스트 하면 정상적으로 처리 되는 모습을 보였고, Controller 부분 부터 테스트 하면 실패하는 모습을 보였다.


우선 정상 로그와 비교해보았을때 아래와 같았고, Servlet MVC 부분에 문제로 보여 살펴보았는데 눈에 잘 띄지 않아.. 삽질 하다.. 찾음;;


문제는 <context:component-scan base-package="pe.kr.ddakker">부분에서 하위에 Controller이 아닌 Service 계층이 포함되면 문제가 생기는것이였다.

그리하여 해상 설정에서 Controller 이 아닌 Annotation들을 exclude-filter 해주거나, base-package 부분을 명확하게 구분되게 해주면 되겠다.



[servlet-mvc.xml]


	
	

	
	


[Log]

-- 비 정상
[DEBUG] org.mybatis.spring.SqlSessionUtils[getSqlSession:140] - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4c1c68d3] was not registered for synchronization because synchronization is not active 
[DEBUG] org.mybatis.spring.transaction.SpringManagedTransaction[openConnection:86] - JDBC Connection [jdbc:oracle:thin:@ip:1521:sid, UserName=id, Oracle JDBC driver] will not be managed by Spring 

-- 정상
[DEBUG] org.mybatis.spring.SqlSessionUtils[getSqlSession:120] - Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@49ffa050] 
[DEBUG] org.mybatis.spring.transaction.SpringManagedTransaction[openConnection:86] - JDBC Connection [jdbc:oracle:thin:@ip:1521:test, UserName=STAT, Oracle JDBC driver] will be managed by Spring 

.properties 의 경우 ISO-8859-1 문자셋이기때문에 한글 사용에 문제가 있어, IDE Tool 을 사용하여 자동으로 변환하는 방법을 이용하곤 했다.

하지만 이 방법의 경우에도 서버상에 올라가있는 설정 파일을 확인 및 직접 수정할 경우 에로 사항이 발생한다.

또한 개개인별 .properties 를 ms949, euc-kr 등으로 변경 해 놓는 사태도 발생해버린다. OTL...


이러한 불편함을 해결 하기 위해 .xml 확장자를 이용하면 좋을것이다.



[applicationContext.xml]


	
	
        
            
                classpath:/properties/globals-properties.xml
            
        
    

	
		
			
				classpath:properties/messages-properties
			
		
	

	
	
		
			
		
	


[Sample.java]

@Controller
public class Sample {
	
	@Value("#{global['server.type']}") private String serverType;
	@Resource GlobalsProperties globalsProperties;

	@RequestMapping(value="/sample")
	public void test(ModelMap model) {
		// Properties Case 1
		System.out.println("serverType: " + serverType);

		// Properties Case 2
		System.out.println("server.type: " + globalsProperties.getProperty("server.type"));
			
		//MessageSource Case 1
		System.out.println("msg.test1: " + messageSource.getMessage("msg.test1", null, Locale.getDefault()));

		// MessageSource Case 2
		System.out.println("msg.test2: " + messageSource.getMessage("msg.test2", new String[]{"첫번째", "두번째"}, Locale.getDefault()));
	}
}


[sample.jsp]

	
	
	
	
		로컬임
	

	
	

	
	


globals-properties.xml




	globals-properties local
	local								

	result					
	message				


[messages-properties_ko.xml]




	messages-properties_ko


	ko 한글 정보는...
	ko 파라미터1 = {0}, 파라미터2 = {1}...


지금까지 Spring 설정파일이나 Web.xml 파일의 JavaConfig형태로 사용하는것에 대해서 큰 필요성을 느끼지 못해 굳이 사용하고 있지 않았었다.


그런데 최근 Filter를 운영환경에서만 추가로 설정해야 할 필요가 생겨났다.

Filter를 상속받아서 처리해도 되겠지만 생각난김에 web.xml을 Java Code로 변경해 보았다.


[WebXml.java]

package config;

import java.util.EnumSet;

import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.multipart.support.MultipartFilter;
import org.springframework.web.servlet.DispatcherServlet;

import pe.kr.ddakker.core.framework.web.GlobalsProperties;
import pe.kr.ddakker.core.support.util.StringUtils;

/**
 * /WEB-INF/Web.xml 설정
 * @author ddakker 2014. 10. 13.
 */
public class WebXml implements WebApplicationInitializer {
	private static Logger log = LoggerFactory.getLogger(WebXml.class);

	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		// 스프링 설정
		XmlWebApplicationContext rootContext = new XmlWebApplicationContext();
        rootContext.setConfigLocations(new String[] { "classpath:config/spring/context-*.xml" });
        rootContext.refresh();
        rootContext.start();


        GlobalsProperties globalsProperties = rootContext.getBean("globalsProperties", GlobalsProperties.class);
        String serverType 	= StringUtils.defaultString(globalsProperties.getProperty("server.type"), "local")
        String domain		= GlobalsProperties.REAL.equals(serverType)?"board.ddakker.pe.kr":serverType + "-board.ddakker.pe.kr";
        log.debug("========== domain: {} ========== ", domain);
        log.debug("========== serverType: {} ========== ", serverType);


        // SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS Filter 영역 SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
        if (GlobalsProperties.STAGE.equals(serverType) || GlobalsProperties.REAL.equals(serverType)) {
			// 상황에 따른 필터 추가
        }

        // 인코딩 설정
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceEncoding(true);
        FilterRegistration.Dynamic characterEncoding = servletContext.addFilter("characterEncodingFilter", characterEncodingFilter);
        characterEncoding.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");

        // 스프링 RestFull 설정
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        FilterRegistration.Dynamic hiddenHttpMethod = servletContext.addFilter("hiddenHttpMethodFilter", hiddenHttpMethodFilter);
        hiddenHttpMethod.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");

        // 파일업로드 설정
        MultipartFilter multipartFilter = new MultipartFilter();
        FilterRegistration.Dynamic multipart = servletContext.addFilter("multipartFilter", multipartFilter);
        multipart.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");

        // String Security 설정
        DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy();
        FilterRegistration.Dynamic springSecurity = servletContext.addFilter("springSecurityFilterChain", springSecurityFilterChain);
        springSecurity.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");



        // 사이트메쉬 설정
        SiteMeshFilter siteMeshFilter = new SiteMeshFilter();
        FilterRegistration.Dynamic siteMeshEncoding = servletContext.addFilter("siteMeshFilter", siteMeshFilter);
        siteMeshEncoding.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD), true, "/*");
        // EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE Filter 영역 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE




        // SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS Listener 영역 SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
        // String Core 설정
        servletContext.addListener(new ContextLoaderListener(rootContext));

        // Spring Security 증복로그인 관련 처리 여부(resources/config/spring/context-security.xml session-management->concurrency-control 부분)
        //servletContext.addListener(new HttpSessionEventPublisher());
        // EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE Listener 영역 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE





        // SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS Servlet 영역 SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
        // 스프링 MVC 설정
        XmlWebApplicationContext xmlWebApplicationContext = new XmlWebApplicationContext();
        xmlWebApplicationContext.setConfigLocation("classpath:config/spring/servlet-mvc.xml");
        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(xmlWebApplicationContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
        /* EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE Servlet 영역 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE */


	}
}

[web.xml]





	...
	
	
		contextConfigLocation
		classpath:config/spring/context-*.xml
	


	
		monitoring
		net.bull.javamelody.MonitoringFilter
	
	
		monitoring
		/*
	
	
		net.bull.javamelody.SessionListener
	
	
	
		encodingFilter
		org.springframework.web.filter.CharacterEncodingFilter
		
			encoding
			UTF-8
		
		
			forceEncoding
			true
		
	
	
		encodingFilter
		/*
	
	
		org.springframework.security.web.session.HttpSessionEventPublisher
	
	
		httpMethodFilter
		org.springframework.web.filter.HiddenHttpMethodFilter
	
	
		httpMethodFilter
		/*
	
	
		multipartFilter
		org.springframework.web.multipart.support.MultipartFilter
	
	
		multipartFilter
		/*
	
	
		springSecurityFilterChain
		org.springframework.web.filter.DelegatingFilterProxy
	
	
		springSecurityFilterChain
		/*
	
	
		sitemesh
		com.opensymphony.sitemesh.webapp.SiteMeshFilter
	
	
		sitemesh
		/*
		REQUEST
		FORWARD
	
	
		org.springframework.web.context.ContextLoaderListener
	
	
		action
		org.springframework.web.servlet.DispatcherServlet
		
			contextConfigLocation
			classpath:config/spring/servlet-mvc.xml
		
		1
	
	
		action
		/
	


Eclipse TestCase 실행 - 정상

Eclipse Gradle TestCase 실행 - 정상

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


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


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


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

[SampleDaoCase.java]

import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;

import java.util.List;

import javax.annotation.Resource;

import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath*:config/spring/context-*.xml"})
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SampleDaoCase {
	private Logger log = LoggerFactory.getLogger(SampleDaoCase.class);
	@Resource SampleDao sampleDao;

	private String name = "이름 테스트";

	@Test
	public void seq01_addSample_글_등록() {
		Sample sampleVo = new Sample();
		sampleVo.setName(name);
		sampleVo.setEmail("ddakkerABC@gmail.com");

		int insertCnt = sampleDao.addSample(sampleVo);
		log.debug("insertCnt: {}", insertCnt);

		assertThat("글 등록 갯수", 1, is(insertCnt));

	}

	@Test
	public void seq02_getSamplesXml_조회() {
		log.debug("sampleDao: " + sampleDao);
		Sample sampleVo = new Sample();
		sampleVo.setEmail("ddakker@gmail.com");

		List list = sampleDao.getSamplesXml(sampleVo);
		log.debug("list: {}", list);
		log.debug("seq: {}", list.get(0).getSeq());
		log.debug("name: {}", list.get(0).getName());
		if( list.get(0).getName() == null )	log.debug("name NULL");
		else								log.debug("name NOT NULL: {}", list.get(0).getName().length());

		assertThat("리스트 갯수 체크", 0, not(list.size()));
		assertThat("리스트 첫번째 배열의 네임값 체크", list.get(0).getName(), not(nullValue()));

		assertThat("리스트 첫번째 배열의 네임값 체크 다른 방법", list.get(0),hasProperty("name", not(nullValue())));

	}

	@Test
	public void seq03_getSamplesXml_조회_검색() {
		log.debug("sampleDao: " + sampleDao);
		Sample sampleVo = new Sample();
		sampleVo.setEmail("ddakker@gmail.com");
		sampleVo.setSeq(30);

		List list = sampleDao.getSamplesXml(sampleVo);
		log.debug("list: {}", list);
		log.debug("seq: {}", list.get(0).getSeq());
		log.debug("name: {}", list.get(0).getName());
		if( list.get(0).getName() == null )	log.debug("name NULL");
		else								log.debug("name NOT NULL: {}", list.get(0).getName().length());

		assertThat("리스트 갯수 체크", 0, not(list.size()));
		assertThat("리스트 첫번째 배열의 네임값 체크", list.get(0).getName(), not(nullValue()));
		assertThat("리스트 첫번째 배열의 네임값 체크 다른 방법", list.get(0),hasProperty("name", not(nullValue())));

	}

	@Test
	public void seq04_deleteSample_삭제() {
		Sample sampleVo = new Sample();
		sampleVo.setName(name);

		Sample sample = sampleDao.getSample(sampleVo);

		assertThat("글", sample, not(nullValue()));
		assertThat("글번호", sample.getSeq(), not(nullValue()));

		sampleVo = new Sample();
		sampleVo.setSeq(sample.getSeq());

		int deleteCnt = sampleDao.deleteSample(sampleVo);
		assertThat("글 삭제 갯수", 1, is(deleteCnt));
	}

}

[SampleControllerCase.java]

import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import pe.kr.ddakker.core.support.web.tags.pagination.Paging;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration("classpath*:config/spring/context-*.xml"),
    @ContextConfiguration("file:webapp/WEB-INF/config/springmvc/name-servlet.xml")
})
public class SampleControllerCase {
	private Logger log = LoggerFactory.getLogger(SampleControllerCase.class);

	@Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup() {
    	this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }


	@Test
	public void listCase_페이징_리스트() throws Exception{
		System.out.println("mockMvc1: " + mockMvc);
		MvcResult mvcResult = this.mockMvc.perform(get("/sample/list").param("a", "1"))
					.andDo(print())
					.andExpect(status().isOk())
					.andExpect(model().attributeExists("paging"))
					.andExpect(view().name("sample/list"))
					.andReturn();

		Paging samplePaging = (Paging) mvcResult.getModelAndView().getModelMap().get("paging");

		assertThat("게시물 정상 여부", samplePaging, not(nullValue()));
		assertThat("게시물 리스트 정상 여부", samplePaging.getList(), not(nullValue()));

		if( samplePaging.getList().size() > 0 ){
			log.debug("seq={}, name={}", samplePaging.getList().get(0).getSeq(), samplePaging.getList().get(0).getName());
			assertThat("리스트 첫번째 배열의 네임값 체크 다른 방법", samplePaging.getList().get(0),hasProperty("name", not(nullValue())));
		}
	}

	@Test
	public void listCase_페이징_리스트_JSON() throws Exception{
		System.out.println("mockMvc1: " + mockMvc);
		this.mockMvc.perform(get("/sample/list").accept(MediaType.APPLICATION_JSON))
					.andDo(print())
					.andExpect(status().isOk())
					.andExpect(jsonPath("$.result").value("0000"))
					;
	}

}


dependencies {
	compile 'org.mybatis:mybatis:3.2.7'
	compile 'org.mybatis:mybatis-spring:1.2.2'
	testCompile 'junit:junit:4.11'
	testCompile 'org.hamcrest:hamcrest-all:1.3'
	testCompile 'com.jayway.jsonpath:json-path-assert:0.9.1'
	testCompile 'org.springframework:spring-test:3.2.9.RELEASE'
}

Struts2 + Spring + iBatis 환경에서 MVC 테스트 방법을 찾아봤다.

POJO(s/getter) 방식과 Request s/getAttribute 방식 두가지로 결과값을 받아 데이터를 검증해보자.


[GoodsActionCase.java]

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;

import java.util.HashMap;
import java.util.Map;

import org.apache.struts2.ServletActionContext;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;

import com.opensymphony.xwork2.ActionProxy;;

public class GoodsActionCase extends StrutsTestCaseSupport {

	@Test
	public void 상품상세() throws Exception {
		Map sessionMap = new HashMap();

		// /view/goodsDetail.action
		ActionProxy proxy = StrutsTestCaseSupport.getInstance().createActionProxy ("detail", "/goods", sessionMap);
		MockHttpServletRequest request = new MockHttpServletRequest();
		request.setParameter("코드", "123456");
		ServletActionContext.setRequest(request);


		GoodsAction goodsAction = (GoodsAction) proxy.getAction();
		goodsAction.detail();

		GoodsInfoBean memberFieldData 	= goodsAction.getDataBean();
		GoodsInfoBean setAttributeData 	= (GoodsInfoBean) ServletActionContext.getRequest().getAttribute("goodsInfo");

		assertThat("널이 아니겠지?", memberFieldData, is(notNullValue()));
		assertThat("널이 아니겠지?", setAttributeData, is(notNullValue()));
		assertThat("앞뒤가 같겠지?", memberFieldData.getGoodsNm(), equalTo(setAttributeData.getGoodsNm()));
	}
}

[환경]

Struts2 2.0.x

Spring 2.5.x

StrutsTestCaseSupport.java - http://fassisrosa.blogspot.kr/2007/09/unit-testing-struts-20-part-3.html


[TestSchedule.java]


import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class TestSchedule {

	@Scheduled(cron = "*/5 * * * * *")	// second, minute, hour, day, month, weekday
	public void test() throws InterruptedException{
		System.out.println("-- test");
	}

	@Scheduled(fixedDelay=1000)			// 이전에 실행된 Task의 종료 시간부터
	public void test2() throws InterruptedException{
		Thread.sleep(5000);
		System.out.println("-- test2");
	}

	@Scheduled(fixedRate=1000)			// 이전에 실행된 Task의 시작 시간부터
	public void test3() throws InterruptedException{
		Thread.sleep(5000);
		System.out.println("-- test3");
	}
}

[applicationContext.xml]





	
	
	

[web.xml]





	Board

	
	
		contextConfigLocation
		classpath:config/applicationContext*.xml
	
	
	
		org.springframework.web.context.ContextLoaderListener
	



[build.gradle]


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

sourceCompatibility = 1.7
version = '1.0'
jar {
    manifest {
        attributes 'Implementation-Title': 'Issue', 'Implementation-Version': version
    }
}

repositories {
    mavenCentral()
}

// 의존성 설정에 사용할 프로퍼티
springVersion = '3.2.0.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 "commons-lang:commons-lang:2.6"
    compile "commons-httpclient:commons-httpclient:3.1"

	compile "net.sf.json-lib:json-lib:2.2.1:jdk15"
    compile "org.jsoup:jsoup:1.7.3"
    compile "com.taskadapter:redmine-java-api:1.23"

    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'
    }
}

uploadArchives {
    repositories {
       flatDir {
           dirs 'repos'
       }
    }
}

+ Recent posts