Thymeleaf : http://www.thymeleaf.org
Thymeleaf Spring mail 샘플 : http://www.thymeleaf.org/springmail.html
Thymeleaf 태그 설명 : http://www.thymeleaf.org/standarddialect5minutes.html
git : https://github.com/thymeleaf
Thymeleaf Tiles 결합 : https://github.com/thymeleaf/thymeleaf-extras-tiles2


[참고]
james 메일 보내기 : http://tyboss.tistory.com/entry/Java-James-서버-사용하여-메일mail-보내기


Inputstream 사용 : https://stackoverflow.com/questions/53323313/java-thymeleaf-how-to-process-an-inputstream-in-templateengine-stand-alon


1. Maven으로 Thymeleaf Library 추가하기

 <dependency>
      <groupId>org.thymeleaf</groupId>
      <artifactId>thymeleaf</artifactId>
      <version>2.0.16</version>
 </dependency>
 <dependency>
      <groupId>org.thymeleaf</groupId>
      <artifactId>thymeleaf-spring3</artifactId>
      <version>2.0.16</version>
 </dependency>




2. Spring에 Thymeleaf 설정하기

 <bean id="emailTemplateResolver" class="org.thymeleaf.templateresolver.ClassLoaderTemplateResolver">
   <!--
    ClassLoaderTemplateResolver의 기본 경로는 소스 경로 즉 /WEB-INF/classes 부터 시작이다.
    -->
   <property name="prefix" value="templates/" />
   <property name="suffix" value=".html" />
   <property name="templateMode" value="HTML5" />
   <property name="characterEncoding" value="UTF-8" />
   <!-- <property name="cacheable" value="false" /> -->
   <property name="order" value="1" />
  </bean>
  
   <!--
   ServletContextTemplateResolver는 sitemesh나 tiles와 경로 설정은 똑같이 하면 된다.
   Jrebel 적용시 ClassLoaderTemplateResolver는 cacheable를 false로 해도 적용이 되지 않기 때문에 ServletTemplateResolver로 테스트 후 바꿔도 된다.

   -->
   <bean id="webTemplateResolver" class="org.thymeleaf.templateresolver.ServletContextTemplateResolver">
   <property name="prefix" value="/WEB-INF/presentation/jsp/web/templates/" />
   <property name="suffix" value=".html" />
   <property name="templateMode" value="HTML5" />
   <property name="characterEncoding" value="UTF-8" />
   <!-- <property name="cacheable" value="false" /> -->
   <property name="order" value="2" />
  </bean>  
  
  
  <bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine">
    <property name="templateResolvers">
      <set>
         <ref bean="emailTemplateResolver" />
         <ref bean="webTemplateResolver" />
      </set>
    </property>
    <!-- Resolver를 한개만 사용할 경우
    <property name="templateResolver" ref="emailTemplateResolver" />
    -->
  </bean>



3. Template 만들기
    다음에서 설명하겠지만 Context.setVariable(key, value) 로 변수값을 설정 후에 아래 빨간색 부분처럼 사용할 수 있다. 일반 텍스트는 th:text="${key}" 로 사용하고 URL은 th:href="@{${key}}" 로 사용한다.

- template.html

 <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title th:remove="all">Template for HTML email with inline image</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <span th:text="${name}"></span>
        <p th:text="#{greeting(${name})}">
            Hello, Peter Static!
        </p>
        <p th:if="${name.length() > 10}">
            Wow! You've got a long name (more than 10 chars)!
        </p>
        <p>
            You have been successfully subscribed to the <b>Fake newsletter</b> on
            <span th:text="${#dates.format(subscriptionDate)}">28-12-2012</span>
        </p>
        <p>Your hobbies are:</p>
        <ul th:remove="all-but-first">
            <li th:each="hobby : ${hobbies}" th:text="${hobby}">Reading</li>
            <li>Writing</li>
            <li>Bowling</li>
        </ul>
        <p>
            You can find <b>your inlined image</b> just below this text.
        </p>
        <p>
            <img src="sample.png" th:src="'cid:' + ${imageResourceName}" />
        </p>
        <p>
                 <a th:href="@{${homepageUrl}}" style="text-decoration:none;">내 홈페이지</a><br />
                 <a th:href="@{'http://' + ${naverUrl}}" style="text-decoration:none;">네이버</a>
        </p>
        <p>
            Regards, <br />
            &emsp; <em>The Thymeleaf Team</em>
        </p>
    </body>
</html>



4. Controller에서 Template 불러오기

    4-1. ClassLoaderTemplateResolver 사용시

@RequestMapping("mail.do")
 public void mail(@RequestParam Map<String, Object> paramMap,
   @Value("#{global['server.host']}") String serverHost,
   HttpServletRequest request,
   final Locale locale,
   ModelMap model) throws Exception {
  log.debug("***************************************************************************************");
  log.debug("*** paramMap : " + paramMap);
  log.debug("***************************************************************************************");
  
  final Context ctx = new Context(locale);

// 변수 설정
ctx.setVariable("name", "홍길동");

/*
  Map<String, String> mailMap = new HashMap<String, String>();
  String usrKey = RandomStringUtils.random(20, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
  mailMap.put("usrKey", usrKey);
  mailMap.put("serverUrl", "http://" + serverHost + request.getContextPath());
  mailMap.put("usrNm", "홍길동");
  mailMap.put("loginId", "honggd");

  if(paramMap != null) {
   Iterator<String> iter = paramMap.keySet().iterator();
   String key = null;
   while(iter.hasNext()) {
    key = iter.next();
    ctx.setVariable(key, paramMap.get(key));
   }
  }
*/

  String template = templateEngine.process("template", ctx);  
  log.debug(template);  // html template가 변수로 치환되어 String으로 반환한다. 이 String을 메일 보낼 때 내용으로 사용하면 된다.
 }


    4-2. ServletContextTemplateResolver 사용시

@Autowired
private ServletContext servletContext;

@RequestMapping(value="/send.do", method=RequestMethod.POST)
 public void send(@RequestParam Map<String, Object> paramMap,
   @Value("#{global['server.host']}") String serverHost,
   @Value("#{global['mail.admin.email']}") String mailAdminEmail,
   @Value("#{global['mail.admin.name']}") String mailAdminName,
   HttpServletRequest request,
   HttpServletResponse response,
   final Locale locale,
   ModelMap model) throws Exception{
  log.debug("***************************************************************************************");
  log.debug("*** paramMap : " + paramMap);
  log.debug("***************************************************************************************");
  
  model.addAttribute("paramMap", paramMap);
  
  // ClassLoaderTemplateResolver 를 사용할 때와 Context를 생성하는 부분만 다르다.
  final WebContext ctx = new WebContext(request, response, servletContext, locale);
 
  ctx.setVariable("usrNm", "홍길동");  

  String template = templateEngine.process("template", ctx); 
  log.debug(template);
}


  4-3. StringTemplateResolver 사용시

StringTemplateResolver templateResolver = new StringTemplateResolver();
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setCacheable(false);

TemplateEngine templateEngine = new TemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
IContext context = new Context(SpringUtil.getLocale(), dataBox);
String out = templateEngine.process(html, context);


 

 

nstore_app_list.zip


참고 : http://nstore.naver.com


 

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Insert title here</title>

<style>
body, input, textarea, select, button, table {
    font-family: '돋움',Dotum,Helvetica,sans-serif;
    font-size: 12px;
}
ul {list-style:none;}
a {text-decoration:none;}

.lst_thum_wrap {}
.lst_thum_wrap .lst_header {height:35px; margin:22px 19px 0;}
.lst_thum_wrap .lst_thum {width:670px; margin:0; padding:0 0 0 43px; position:relative; border-bottom:1px solid #e1e1e1;}
.lst_thum_wrap .lst_thum:after {display:block; clear:both; content:'';}
.lst_thum_wrap .lst_thum li {width:108px; margin:0; padding:0 26px 23px 0; float:left; position:relative; vertical-align:top;}
.lst_thum_wrap .lst_thum li .mask {display:block; overflow:hidden; position:absolute; width:102px; height:102px; background:url('images/sp_mask.png') no-repeat -325px 0;}
.lst_thum_wrap .lst_thum li .ico {top:-10px; left:-12px; position:absolute; z-index:20; width:41px; height:41px; background:url('images/sp_sub_common.png') no-repeat; font-style:normal;}
.lst_thum_wrap .lst_thum li .ico_new {background-position: -184px -299px;}
.lst_thum_wrap .lst_thum li .ico_free {background-position: -92px -299px;}
.lst_thum_wrap .lst_thum li .ico_gift {background:url('images/sp_ico.png') no-repeat -120px 0;}
.lst_thum_wrap .lst_thum li .ico_vari {line-height:22px !important; font-family:Tahoma !important; font-size:10px; font-weight:bold; padding-top:8px; color:#fff; height:41px; background-position:-46px -299px; text-align:center;}
.lst_thum_wrap .lst_thum li a img {width:101px; height:101px; margin:1px 0 5px 1px;}
.lst_thum_wrap .lst_thum li a img + strong {padding:5px 0 0; line-height:18px; word-break:break-all; display:block; color:#000; white-space:normal;}
.lst_thum_wrap .lst_thum li .writer {display:block; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;}
.lst_thum_wrap .lst_thum li .score {margin:0 0 0 -1px; display:inline-block; position:relative; width:61px; height:12px; background:url('images/sp_sub_common.png') no-repeat -732px -249px; vertical-align:middle; font-size:11px; font-weight:bold; color:#333;}
.lst_thum_wrap .lst_thum li .score span {display:block; overflow:hidden; position:relative; height:12px; background:url('images/sp_sub_common.png') no-repeat -732px -232px; line-height:999px;}
.lst_thum_wrap .lst_thum li .score_num {display:inline-block; position:relative; top:1px; font-weight:bold; font-size:11px; color:#333; vertical-align:middle; font-style:normal; left:-2px;}
.lst_thum_wrap .lst_thum li .price {display:block; margin: 4px 0 0 -2px; line-height:16px; text-align:left; white-space: normal;}
.lst_thum_wrap .lst_thum li .price strong {word-break:break-all; color:#1b8ffc;}
</style>

</head>
<body>

<div class="lst_thum_wrap">
 <div class="lst_header">
  <h3>금주의 인기게임</h3>
 </div>
 <ul class="lst_thum">
  <li>
   <a href="#" title="앱이름">
    <span class="mask"></span>
    <em class="ico ico_new"></em>
    <img src="images/app1.png" />
    <strong class="bb">[건비트2] 음악 게임의 신세계</strong>
   </a>
   <span class="writer">카테고리</span>
   <span class="score">
    <span style="width:84%;">평점</span>
   </span>
   <em class="score_num">4.2</em>
   <span class="price">
    <strong>무료</strong>
   </span>
  </li>
  <li>
   <a href="#" title="앱이름">
    <span class="mask"></span>
    <em class="ico ico_new"></em>
    <img src="images/app2.png" />
    <strong class="bb">[건비트2] 음악 게임의 신세계</strong>
   </a>
   <span class="writer">카테고리</span>
   <span class="score">
    <span style="width:84%;">평점</span>
   </span>
   <em class="score_num">4.2</em>
   <span class="price">
    <strong>무료</strong>
   </span>
  </li>
  <li>
   <a href="#" title="앱이름">
    <span class="mask"></span>
    <em class="ico ico_free"></em>
    <img src="images/app3.png" />
    <strong class="bb">[건비트2] 음악 게임의 신세계</strong>
   </a>
   <span class="writer">카테고리</span>
   <span class="score">
    <span style="width:84%;">평점</span>
   </span>
   <em class="score_num">4.2</em>
   <span class="price">
    <strong>무료</strong>
   </span>
  </li>
  <li>
   <a href="#" title="앱이름">
    <span class="mask"></span>
    <em class="ico ico_gift"></em>
    <img src="images/app4.png" />
    <strong class="bb">[건비트2] 음악 게임의 신세계</strong>
   </a>
   <span class="writer">카테고리</span>
   <span class="score">
    <span style="width:84%;">평점</span>
   </span>
   <em class="score_num">4.2</em>
   <span class="price">
    <strong>무료</strong>
   </span>
  </li>
  <li>
   <a href="#" title="앱이름">
    <span class="mask"></span>
    <img src="images/app5.png" />
    <strong class="bb">[건비트2] 음악 게임의 신세계</strong>
   </a>
   <span class="writer">카테고리</span>
   <span class="score">
    <span style="width:84%;">평점</span>
   </span>
   <em class="score_num">4.2</em>
   <span class="price">
    <strong>무료</strong>
   </span>
  </li>
  <li>
   <a href="#" title="앱이름">
    <span class="mask"></span>
    <em class="ico ico_new"></em>
    <img src="images/app6.png" />
    <strong class="bb">[건비트2] 음악 게임의 신세계</strong>
   </a>
   <span class="writer">카테고리</span>
   <span class="score">
    <span style="width:84%;">평점</span>
   </span>
   <em class="score_num">4.2</em>
   <span class="price">
    <strong>무료</strong>
   </span>
  </li>
  <li>
   <a href="#" title="앱이름">
    <span class="mask"></span>
    <em class="ico ico_vari">51%</em>
    <img src="images/app7.png" />
    <strong class="bb">[건비트2] 음악 게임의 신세계</strong>
   </a>
   <span class="writer">카테고리</span>
   <span class="score">
    <span style="width:84%;">평점</span>
   </span>
   <em class="score_num">4.2</em>
   <span class="price">
    <strong>무료</strong>
   </span>
  </li>
  <li>
   <a href="#" title="앱이름">
    <span class="mask"></span>
    <img src="images/app8.png" />
    <strong class="bb">[건비트2] 음악 게임의 신세계</strong>
   </a>
   <span class="writer">카테고리</span>
   <span class="score">
    <span style="width:84%;">평점</span>
   </span>
   <em class="score_num">4.2</em>
   <span class="price">
    <strong>무료</strong>
   </span>
  </li>
  <li>
   <a href="#" title="앱이름">
    <span class="mask"></span>
    <em class="ico ico_new"></em>
    <img src="images/app9.png" />
    <strong class="bb">[건비트2] 음악 게임의 신세계</strong>
   </a>
   <span class="writer">카테고리</span>
   <span class="score">
    <span style="width:84%;">평점</span>
   </span>
   <em class="score_num">4.2</em>
   <span class="price">
    <strong>무료</strong>
   </span>
  </li>
  <li>
   <a href="#" title="앱이름">
    <span class="mask"></span>
    <em class="ico ico_new"></em>
    <img src="images/app10.png" />
    <strong class="bb">[건비트2] 음악 게임의 신세계</strong>
   </a>
   <span class="writer">카테고리</span>
   <span class="score">
    <span style="width:84%;">평점</span>
   </span>
   <em class="score_num">4.2</em>
   <span class="price">
    <strong>무료</strong>
   </span>
  </li>
  <li>
   <a href="#" title="앱이름">
    <span class="mask"></span>
    <em class="ico ico_new"></em>
    <img src="images/app11.png" />
    <strong class="bb">[건비트2] 음악 게임의 신세계</strong>
   </a>
   <span class="writer">카테고리</span>
   <span class="score">
    <span style="width:84%;">평점</span>
   </span>
   <em class="score_num">4.2</em>
   <span class="price">
    <strong>무료</strong>
   </span>
  </li>
  <li>
   <a href="#" title="앱이름">
    <span class="mask"></span>
    <img src="images/app12.png" />
    <strong class="bb">[건비트2] 음악 게임의 신세계</strong>
   </a>
   <span class="writer">카테고리</span>
   <span class="score">
    <span style="width:84%;">평점</span>
   </span>
   <em class="score_num">4.2</em>
   <span class="price">
    <strong>무료</strong>
   </span>
  </li>
 </ul>
</div>

</body>
</html>

 

 


 

nstore_app_slide.zip


참고 : http://nstore.naver.com


 

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Insert title here</title>

<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css" />
<style>
body, input, textarea, select, button, table {
    font-family: '돋움',Dotum,Helvetica,sans-serif;
    font-size: 12px;
}
ul {list-style:none;}
a {text-decoration:none;}

.lst_thum_wrap {width:670px; float:left; margin:0 30px;}
.lst_thum_wrap .lst_thum {width:100%; margin:0; padding:16px 0 0 16px; position:relative;}
.lst_thum_wrap .lst_thum:after {display:block; clear:both; content:'';}
.lst_thum_wrap .lst_thum li {width:108px; margin:0; padding:0 26px 10px 0; float:left; position:relative; vertical-align:top;}
.lst_thum_wrap .lst_thum li .mask {display:block; overflow:hidden; position:absolute; width:102px; height:102px; background:url('images/sp_mask.png') no-repeat -325px 0;}
.lst_thum_wrap .lst_thum li .ico {top:-10px; left:-12px; position:absolute; z-index:20; width:41px; height:41px; background:url('images/sp_sub_common.png') no-repeat; font-style:normal;}
.lst_thum_wrap .lst_thum li .ico_new {background-position: -184px -299px;}
.lst_thum_wrap .lst_thum li .ico_free {background-position: -92px -299px;}
.lst_thum_wrap .lst_thum li .ico_gift {background:url('images/sp_ico.png') no-repeat -120px 0;}
.lst_thum_wrap .lst_thum li .ico_vari {line-height:22px !important; font-family:Tahoma !important; font-size:10px; font-weight:bold; padding-top:8px; color:#fff; height:41px; background-position:-46px -299px; text-align:center;}
.lst_thum_wrap .lst_thum li a img {width:101px; height:101px; margin:1px 0 5px 1px;}
.lst_thum_wrap .lst_thum li a img + strong {padding:5px 0 0; line-height:18px; word-break:break-all; display:block; color:#000; white-space:normal;}
.lst_thum_wrap .lst_thum li .writer {display:block; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;}
.lst_thum_wrap .lst_thum li .score {margin:0 0 0 -1px; display:inline-block; position:relative; width:61px; height:12px; background:url('images/sp_sub_common.png') no-repeat -732px -249px; vertical-align:middle; font-size:11px; font-weight:bold; color:#333;}
.lst_thum_wrap .lst_thum li .score span {display:block; overflow:hidden; position:relative; height:12px; background:url('images/sp_sub_common.png') no-repeat -732px -232px; line-height:999px;}
.lst_thum_wrap .lst_thum li .score_num {display:inline-block; position:relative; top:1px; font-weight:bold; font-size:11px; color:#333; vertical-align:middle; font-style:normal; left:-2px;}
.lst_thum_wrap .lst_thum li .price {display:block; margin: 4px 0 0 -2px; line-height:16px; text-align:left; white-space: normal;}
.lst_thum_wrap .lst_thum li .price strong {word-break:break-all; color:#1b8ffc;}


.flick-carousel {width:730px; position:relative; height:244px;}
.flick-carousel .flick-carousel-outer-container {overflow:hidden; position:absolute; width:730px; height:230px;}
.flick-carousel .flick-carousel-outer-container .flick-carousel-content-container {height:230px; width:10000px; white-space:nowrap;}
.flick-carousel a.flick-button-previous,
.flick-carousel a.flick-button-next {position:absolute; top:65px; width:27px; height:43px; background:url('images/sp_alsobought_bs_v2.png') no-repeat; line-height:999px; overflow:hidden; z-index:30; display:none;}
.flick-carousel a.flick-button-previous {background-position:5px -108px;}
.flick-carousel a.flick-button-previous:hover {background-position:-22px -108px;}
.flick-carousel a.flick-button-next {background-position:-49px -108px; right:0;}
.flick-carousel a.flick-button-next:hover {background-position:-76px -108px;}
.flick-carousel .flick-nav {position:absolute; bottom:6px; left:0; width:100%; margin:0; text-align:center; display:none;}
.flick-carousel .flick-nav strong,
.flick-carousel .flick-nav a {display:inline-block; overflow:hidden; width:7px; height:7px; margin:0; line-height:999px; vertical-align:middle; background:url('images/sp_alsobought_as.png') no-repeat;}
.flick-carousel .flick-nav strong {background-position:-108px -46px;}
.flick-carousel .flick-nav a {background-position:-119px -46px;}
</style>

<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="http://code.jquery.com/ui/1.10.2/jquery-ui.js"></script>
<script>
jQuery(function($) {
 $.fn.extend({
  flick : function(opt) {
   var settings = {
    speed : 400
   }
   $.extend(true, settings, opt);
   
   var _this = $(this);
   var _outer = _this.find('.flick-carousel-outer-container');
   var _content = _outer.find('.flick-carousel-content-container');
   var _items = _content.find('.lst_thum_wrap');
   var itemsLen = _items.length;
   if(itemsLen < 2) {
    return false;
   }
   var itemsAvg = Math.floor(itemsLen/2);
   var _btnPrev = _this.find('.flick-button-previous');
   var _btnNext = _this.find('.flick-button-next');
   var _btnNav = _this.find('.flick-nav');
   var pos = 0;
   var idx = 0;
   var posIdx = 0;
   var moveItem = 0;
   var movePos = 0;
   var outerWidth = _outer.width();
   var itemWidth = _items.eq(0).outerWidth(true);
   var contentWidth = itemWidth * _items.length;
   var lastPos = contentWidth - outerWidth;
   _content.width(contentWidth);
   _btnPrev.add(_btnNext).add(_btnNav).show();
   
   _btnPrev.on('click', function(e) {
    e.preventDefault();
    
    var nIdx = idx-1;
    if(nIdx < 0) {
     nIdx = itemsLen - 1;
    }
    setNav(nIdx);
   });
   
   _btnNext.on('click', function(e) {
    e.preventDefault();
    
    var nIdx = idx+1;
    if(nIdx >= itemsLen) {
     nIdx = 0;
    }
    setNav(nIdx);
   });
   
   function prev() {
    if(moveItem > 0) {
     for(var i=0; i<moveItem; i++) {
      _content.find('.lst_thum_wrap').last().prependTo(_content);
     }
     _outer.scrollLeft(pos + itemWidth * movePos);
     //console.log('이전 이동 > moveItem : ' + moveItem + ', pos : ' + (pos + itemWidth * movePos) + ' > ' + pos);
    }
    
    _outer.stop().animate({
     scrollLeft : pos
    }, {
     duration:settings.speed,
     easing : 'easeOutExpo'
    });
   }
   
   function next() {
    if(moveItem > 0) {
     for(var i=0; i<moveItem; i++) {
      _content.find('.lst_thum_wrap').first().appendTo(_content);
     }
     _outer.scrollLeft(pos - itemWidth * movePos);
     //console.log('다음 이동 > moveItem : ' + moveItem + ', pos : ' + (pos - itemWidth * movePos) + ' > ' + pos);
    }
    
    _outer.stop().animate({
     scrollLeft : pos
    }, {
     duration:settings.speed,
     easing : 'easeOutExpo'
    });
   }
   
   function setNav(nIdx) {
    var _navBefore = _btnNav.children().eq(idx);
    _navBefore.replaceWith($('<a />', {href : '#'}).text(idx));
    var _navAfter = _btnNav.children().eq(nIdx);
    _navAfter.replaceWith($('<strong />').text(nIdx));
    
    movePos = nIdx - idx;
    if(movePos > 0) {
     if(movePos <= itemsAvg) {
      posIdx += movePos;
      if(posIdx >= itemsLen) {
       moveItem = posIdx - itemsLen + 1;
       posIdx = itemsLen - 1;
       pos = lastPos;
      } else {
       moveItem = 0;
       pos += itemWidth * movePos;
      }
      //console.log('다음(큰) > moveItem : ' + moveItem + ', posIdx : ' + posIdx);
      next();
     } else {
      var befPosIdx = posIdx;
      posIdx = posIdx + movePos - itemsLen;
      movePos = befPosIdx - posIdx;
      if(posIdx < 0) {
       moveItem = Math.abs(posIdx);
       posIdx = 0;
       pos = 0;
      } else {
       moveItem = 0;
       pos -= itemWidth * movePos;
      }
      //console.log('이전(큰) > moveItem : ' + moveItem + ', posIdx : ' + posIdx);
      prev();
     }
    } else {
     movePos = Math.abs(movePos);
     if(movePos > itemsAvg) {
      var befPosIdx = posIdx;
      posIdx = posIdx - movePos + itemsLen;
      movePos = posIdx - befPosIdx;
      if(posIdx >= itemsLen) {
       moveItem = posIdx - itemsLen + 1;
       posIdx = itemsLen - 1;
       pos = lastPos;
      } else {
       moveItem = 0;
       pos += itemWidth * movePos;
      }
      //console.log('다음(작은) > moveItem : ' + moveItem + ', posIdx : ' + posIdx);
      next();
     } else {
      posIdx -= movePos;
      if(posIdx < 0) {
       moveItem = Math.abs(posIdx);
       posIdx = 0;
       pos = 0;
      } else {
       moveItem = 0;
       pos -= itemWidth * movePos;
      }
      //console.log('이전(작은) > moveItem : ' + moveItem + ', posIdx : ' + posIdx);
      prev();
     }
    }
    idx = nIdx;
   }
   
   $(_btnNav).on('click', 'a', function(e) {
    e.preventDefault();
    var _this = $(this);
    setNav(parseInt(_this.text(), 10));
   });
   
  }
 });
 
 $('.flick-carousel').flick();
});
</script>

</head>
<body>

<div class="flick-carousel">
 <a href="#" class="flick-button-previous"></a>
 <div class="flick-carousel-outer-container">
  <div class="flick-carousel-content-container">
   <div class="lst_thum_wrap a0">
    <ul class="lst_thum">
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <em class="ico ico_new"></em>
       <img src="images/app1.png" />
       <strong class="bb">000</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <img src="images/app2.png" />
       <strong class="bb">[건비트2] 음악 게임의 신세계</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <em class="ico ico_gift"></em>
       <img src="images/app3.png" />
       <strong class="bb">이츄, 사랑의 홍차 연구소</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <em class="ico ico_free"></em>
       <img src="images/app4.png" />
       <strong class="bb">미투데이</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <img src="images/app5.png" />
       <strong class="bb">앱</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
    </ul>
   </div>
   <div class="lst_thum_wrap a1">
    <ul class="lst_thum">
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <em class="ico ico_new"></em>
       <img src="images/app6.png" />
       <strong class="bb">111</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <img src="images/app7.png" />
       <strong class="bb">네이버 블로그</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <em class="ico ico_gift"></em>
       <img src="images/app8.png" />
       <strong class="bb">이츄, 사랑의 홍차 연구소</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <em class="ico ico_free"></em>
       <img src="images/app9.png" />
       <strong class="bb">미투데이</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <img src="images/app10.png" />
       <strong class="bb">앱</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
    </ul>
   </div>
   <div class="lst_thum_wrap a2">
    <ul class="lst_thum">
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <em class="ico ico_new"></em>
       <img src="images/app11.png" />
       <strong class="bb">222</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <img src="images/app12.png" />
       <strong class="bb">네이버 블로그</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <em class="ico ico_gift"></em>
       <img src="images/app13.png" />
       <strong class="bb">이츄, 사랑의 홍차 연구소</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <em class="ico ico_free"></em>
       <img src="images/app14.png" />
       <strong class="bb">미투데이</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <img src="images/app15.png" />
       <strong class="bb">앱</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
    </ul>
   </div>
   <div class="lst_thum_wrap a3">
    <ul class="lst_thum">
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <em class="ico ico_new"></em>
       <img src="images/app11.png" />
       <strong class="bb">333</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <img src="images/app12.png" />
       <strong class="bb">네이버 블로그</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <em class="ico ico_gift"></em>
       <img src="images/app13.png" />
       <strong class="bb">이츄, 사랑의 홍차 연구소</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <em class="ico ico_free"></em>
       <img src="images/app14.png" />
       <strong class="bb">미투데이</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <img src="images/app15.png" />
       <strong class="bb">앱</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
    </ul>
   </div>
   <div class="lst_thum_wrap a4">
    <ul class="lst_thum">
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <em class="ico ico_new"></em>
       <img src="images/app11.png" />
       <strong class="bb">444</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <img src="images/app12.png" />
       <strong class="bb">네이버 블로그</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <em class="ico ico_gift"></em>
       <img src="images/app13.png" />
       <strong class="bb">이츄, 사랑의 홍차 연구소</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <em class="ico ico_free"></em>
       <img src="images/app14.png" />
       <strong class="bb">미투데이</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
     <li>
      <a href="#" title="앱이름">
       <span class="mask"></span>
       <img src="images/app15.png" />
       <strong class="bb">앱</strong>
      </a>
      <span class="writer">카테고리</span>
      <span class="score">
       <span style="width:84%;">평점</span>
      </span>
      <em class="score_num">4.2</em>
      <span class="price">
       <strong>무료</strong>
      </span>
     </li>
    </ul>
   </div>
  </div>
 </div>
 <a href="#" class="flick-button-next"></a>
 <span class="flick-nav">
  <strong>0</strong>
  <a href="#">1</a>
  <a href="#">2</a>
  <a href="#">3</a>
  <a href="#">4</a>
 </span>
</div>


</body>
</html> 

 

 

 

google_screenshot.zip

index.html

출처 : https://play.google.com/

 

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Insert title here</title>

<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css" />
<style>
.screenshot-carousel {position:relative; height:400px; width:650px;}
.screenshot-carousel .screenshot-carousel-outer-container {height:400px; overflow:hidden; position:absolute; width:650px;}
.screenshot-carousel .screenshot-carousel-outer-container .screenshot-carousel-content-container {height:400px; width:10000px; white-space:nowrap;}
.screenshot-carousel .screenshot-carousel-outer-container .screenshot-carousel-content-container .screenshot-image-wrapper {
 display:inline-block; float:left;
}
.screenshot-carousel .screenshot-carousel-outer-container .screenshot-carousel-content-container .screenshot-image-wrapper .screenshot-image {
 display:inline-block; max-height:400px; max-width:250px; vertical-align:bottom; padding-right:10px; border:0; margin:0;
}
.screenshot-carousel .screenshot-carousel-button-previous,
.screenshot-carousel .screenshot-carousel-button-next,
.screenshot-carousel .screenshot-carousel-left-fade,
.screenshot-carousel .screenshot-carousel-right-fade {position:absolute;}
.screenshot-carousel .screenshot-carousel-button-previous,
.screenshot-carousel .screenshot-carousel-button-next {margin-top:184px; z-index:20; cursor:pointer;}
.screenshot-carousel .screenshot-carousel-button-previous {background:no-repeat url('images/sprites.png') -248px 0; height:32px; width:32px; display:none;}
.screenshot-carousel .screenshot-carousel-button-next {background:no-repeat url('images/sprites.png') -91px -266px; height:32px; width:32px; right:0; display:none;}
.screenshot-carousel .screenshot-carousel-left-fade,
.screenshot-carousel .screenshot-carousel-right-fade {width:50px; height:400px; z-index:10; cursor:pointer;}
.screenshot-carousel .screenshot-carousel-left-fade {display:none;}
.screenshot-carousel .screenshot-carousel-right-fade {right:0; display:none;}
/* -moz-opacity:0.4;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=40)";filter:alpha(opacity=40);margin-top:70px;opacity:0.4 */
</style>

<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="http://code.jquery.com/ui/1.10.2/jquery-ui.js"></script>
<script>
(function($) {
 $.fn.extend({
  screenshot : function(opt) {
   var settings = {
    speed : 400
   }
   $.extend(true, settings, opt);
   
   var _this = $(this);
   var _outer = _this.find('.screenshot-carousel-outer-container');
   var _content = _outer.find('.screenshot-carousel-content-container');
   var _wrapper = _content.find('.screenshot-image-wrapper');
   var _leftFade = _this.find('.screenshot-carousel-left-fade');
   var _rightFade = _this.find('.screenshot-carousel-right-fade');
   var _btnPrev = _this.find('.screenshot-carousel-button-previous').add(_leftFade);
   var _btnNext = _this.find('.screenshot-carousel-button-next').add(_rightFade);
   var pos = 0;
   var outerWidth = _outer.width();
   //var wrapperWidth = _wrapper.eq(0).outerWidth(true);
   var wrapperWidth = _wrapper.eq(0).width();
   var contentWidth = wrapperWidth * _wrapper.length;
   _content.width(contentWidth);
   
   if(contentWidth > outerWidth) {
       _btnNext.show();
   } else {
       return false;
   }
   
   _btnPrev.on({
    'mouseenter' : function() {
     _leftFade.stop().animate({width:75}, {duration:100});
    },
    'mouseleave' : function() {
     _leftFade.stop().animate({width:50}, {duration:100});
    },
    'click' : function() {
     if(pos == contentWidth - outerWidth) {
      pos -= 450;
     } else {
      pos -= wrapperWidth * 2;
     }
     
     if(pos < 0) {
      pos = 0;
      _btnPrev.hide();
     }
     
     _outer.stop().animate({
      scrollLeft : pos
     }, {
      duration:settings.speed,
      easing : 'easeOutExpo'
     });
     _btnNext.show();
    }
   });
   
   _btnNext.on({
    'mouseenter' : function() {
     _rightFade.stop().animate({width:75}, {duration:100});
    },
    'mouseleave' : function() {
     _rightFade.stop().animate({width:50}, {duration:100});
    },
    'click' : function() {
     if(pos == 0) {
      pos += 450;
     } else {
      pos += wrapperWidth * 2;
     }
     
     if(pos + outerWidth > contentWidth) {
      pos = contentWidth - outerWidth;
      _btnNext.hide();
     }
     
     _outer.stop().animate({
      scrollLeft : pos
     }, {
      duration:settings.speed,
      easing : 'easeOutExpo'
     });
     _btnPrev.show();
    }
   });
   
  }
 });
})(jQuery);

jQuery(function($) {
 $('.screenshot-carousel').screenshot();
});
</script>

</head>
<body>

<div class="screenshot-carousel">
 <div class="screenshot-carousel-button-previous"></div>
 <img src="images/left_fade.png" class="screenshot-carousel-left-fade" />
 <div class="screenshot-carousel-outer-container">
  <div class="screenshot-carousel-content-container">
   <div class="screenshot-image-wrapper"><img src="images/screenshot5.png" class="screenshot-image" /></div>
   <div class="screenshot-image-wrapper"><img src="images/screenshot1.png" class="screenshot-image" /></div>
   <div class="screenshot-image-wrapper"><img src="images/screenshot2.png" class="screenshot-image" /></div>
   <div class="screenshot-image-wrapper"><img src="images/screenshot3.png" class="screenshot-image" /></div>
   <div class="screenshot-image-wrapper"><img src="images/screenshot4.png" class="screenshot-image" /></div>
   <div class="screenshot-image-wrapper"><img src="images/screenshot1.png" class="screenshot-image" /></div>
   <div class="screenshot-image-wrapper"><img src="images/screenshot2.png" class="screenshot-image" /></div>
   <div class="screenshot-image-wrapper"><img src="images/screenshot3.png" class="screenshot-image" /></div>
   <div class="screenshot-image-wrapper"><img src="images/screenshot4.png" class="screenshot-image" /></div>
   <div class="screenshot-image-wrapper"><img src="images/screenshot1.png" class="screenshot-image" /></div>
   <div class="screenshot-image-wrapper"><img src="images/screenshot2.png" class="screenshot-image" /></div>
   <div class="screenshot-image-wrapper"><img src="images/screenshot3.png" class="screenshot-image" /></div>
  </div>
 </div>
 <img src="images/right_fade.png" class="screenshot-carousel-right-fade" />
 <div class="screenshot-carousel-button-next"></div>
</div>

</body>
</html>

 

 

tstore_screenshot1.zip  

 tstore_screenshot2.zip


tstore_screenshot1 : tstore에 있는 방식대로 처리, 속도가 느리고 이미지 하나씩 슬라이딩 됨
tstore_screenshot2 : tstore에 있는 방식을 개선하여 처리, 속도 및 이미지 맞춤 개선

출처 : http://www.tstore.co.kr 

 

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Insert title here</title>

<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css" />
<style>
.scnshot_wrap {
 width:532px;
 height:350px;
 position:relative;
}
.scnshot_wrap .scnshot_outer {
 width:530px;
 border-left:1px solid #3a3a3a;
 overflow:hidden;
}
.scnshot_wrap .scnshot_outer ul {
 width:10000px;
 margin:0;
 padding:0;
}
.scnshot_wrap .scnshot_outer ul li {
 float:left;
 display:inline-block;
 overflow:hidden;
}
.scnshot_wrap .scnshot_outer img {
 width:220px;
 height:350px;
 border:0;
 margin-right:5px;
}
.scnshot_wrap .scnshot_prev {
 position:absolute;
 top:145px;
 left:0;
 z-index:10;
 display:none;
}
.scnshot_wrap .scnshot_next {
 position:absolute;
 top:145px;
 right:0;
 z-index:10;
 display:none;
}
</style>

<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="http://code.jquery.com/ui/1.10.2/jquery-ui.js"></script>
<script>
(function ($) {
 $.fn.extend({
  thumbnailSlide : function(opt) {
   var settings = {
    speed : 400
   }
   $.extend(true, settings, opt);
   
   var _this = $(this);
   var _outer = _this.find('.scnshot_outer');
   var _content = _outer.find('ul');
   var _item = _content.find('li');
   if(_item.length < 1) {
    return false;
   }
   var _btnPrev = _this.find('.scnshot_prev');
   var _btnNext = _this.find('.scnshot_next');
   var pos = 0;
   var outerWidth = _outer.width();
   var itemWidth = _item.eq(0).width();
   var contentWidth = itemWidth * _item.length;
   _content.width(contentWidth);
   
   if(contentWidth - 5 > outerWidth) {
    _btnNext.show();
   } else {
    return false;
   }
   _outer.css({borderRight:'1px solid #3a3a3a'});
   
   _btnPrev.on('click', function() {
    if(pos == contentWidth - outerWidth - 5) {
     pos -= 420;
    } else {
     pos -= itemWidth * 2;
    }
    
    if(pos < 0) {
     pos = 0;
     _btnPrev.hide();
    }
    
    _outer.stop().animate({
     scrollLeft : pos
    }, {
     duration:settings.speed,
     easing : 'easeOutExpo'
    });
    _btnNext.show();
   });
   
   _btnNext.on('click', function() {
    if(pos == 0) {
     pos += 420;
    } else {
     pos += itemWidth * 2;
    }
    
    if(pos + outerWidth > contentWidth) {
     pos = contentWidth - outerWidth - 5;
     _btnNext.hide();
    }
    
    _outer.stop().animate({
     scrollLeft : pos
    }, {
     duration:settings.speed,
     easing : 'easeOutExpo'
    });
    _btnPrev.show();
   });
  }
 });
})(jQuery);

jQuery(function($) {
 $('.scnshot_wrap').thumbnailSlide();
});
</script>

</head>
<body>

<div class="scnshot_wrap">
 <a href="#" class="scnshot_prev">
  <img src="images/btn_prev05.png" alt="이전" />
 </a>
 <div class="scnshot_outer">
  <ul>
   <li><img src="images/screenshot1.png" /></li>
   <li><img src="images/screenshot2.png" /></li>
   <li><img src="images/screenshot3.png" /></li>
   <li><img src="images/screenshot4.png" /></li>
   <li><img src="images/screenshot5.png" /></li>
   <li><img src="images/screenshot1.png" /></li>
   <li><img src="images/screenshot2.png" /></li>
   <li><img src="images/screenshot3.png" /></li>
   <li><img src="images/screenshot4.png" /></li>
   <li><img src="images/screenshot5.png" /></li>
  </ul>
 </div>
 <a href="#" class="scnshot_next">
  <img src="images/btn_next05.png" alt="다음" />
 </a>
</div>


</body>
</html>

 

 

참고 : http://buildinternet.com/2009/03/sliding-boxes-and-captions-with-jquery/
데모 : http://s3.amazonaws.com/buildinternet/live-tutorials/sliding-boxes/index.htm

Source : slidingBoxes.zip

 

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Insert title here</title>

<style>
.img_box{}
.img_box article {width:300px; height:250px; position:relative; float:left; margin:10px; overflow:hidden; border:solid 2px #8399af; background:#161613;}
.img_box article img {width:300px; height:250px; position:absolute; top:0; left:0; border:0;}
.img_box article .boxcaption {width: 100%; height:100px; position:absolute; top:250px; left:0; background: #000; opacity:.8; /* For IE 5-7 */ filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80 ); /* For IE 8 */-MS-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";}
.img_box article.caption .boxcaption {top:208px;}
.img_box article.captionfull .boxcaption {top:250px;}
.img_box article .boxcaption h3,
.img_box article.slideright h3,
.img_box article.thecombo h3,
.img_box article.slideup h3 {margin:10px 10px 4px 10px; color:#fff; font:22px Arial, sans-serif; letter-spacing:-1px; font-weight:bold;}
.img_box article .boxcaption p,
.img_box article.slideright p,
.img_box article.thecombo p,
.img_box article.slideup p {padding:0 10px; color:#afafaf; font-weight:bold; font:12px "Lucida Grande", Arial, sans-serif;}
.img_box article .boxcaption p a,
.img_box article.slideright p a,
.img_box article.thecombo p a,
.img_box article.slideup p a {color:#666;}
</style>

<script src="http://code.jquery.com/jquery-latest.js"></script>
<script>
jQuery(function($) {
 $('.img_box article.caption').hover(function() {
  $('.cover', this).stop().animate({
   top : 150
  }, {
   queue : false,
   duration : 160
  });
 }, function() {
  $('.cover', this).stop().animate({
   top : 208
  }, {
   queue : false,
   duration : 160
  });
 });
 
 $('.img_box article.captionfull').hover(function() {
  $('.cover', this).stop().animate({
   top : 150
  }, {
   queue : false,
   duration : 160
  });
 }, function() {
  $('.cover', this).stop().animate({
   top : 250
  }, {
   queue : false,
   duration : 160
  });
 });
 
 $('.img_box article.slideright').hover(function() {
  $('.cover', this).stop().animate({
   left : 300
  }, {
   queue : false,
   duration : 300
  });
 }, function() {
  $('.cover', this).stop().animate({
   left : 0
  }, {
   queue : false,
   duration : 300
  });
 });
 
 $('.img_box article.thecombo').hover(function() {
  $('.cover', this).stop().animate({
   left : 300,
   top : 250
  }, {
   queue : false,
   duration : 300
  });
 }, function() {
  $('.cover', this).stop().animate({
   left : 0,
   top : 0
  }, {
   queue : false,
   duration : 300
  });
 });
 
 $('.img_box article.slideup').hover(function() {
  $('.cover', this).stop().animate({
   top : -250
  }, {
   queue : false,
   duration : 300
  });
 }, function() {
  $('.cover', this).stop().animate({
   top : 0
  }, {
   queue : false,
   duration : 300
  });
 });
 
 $('.img_box article.peek').hover(function() {
  $('.cover', this).stop().animate({
   top : 90
  }, {
   queue : false,
   duration : 160
  });
 }, function() {
  $('.cover', this).stop().animate({
   top : 0
  }, {
   queue : false,
   duration : 160
  });
 });
 
});
</script>

</head>
<body>

<section class="img_box">
 <article class="captionfull">
  <img src="images/florian.jpg" />
  <div class="cover boxcaption">
   <h3>타이틀1</h3>
   <p>
    내용입니다.<br />
    <a href="">내용입니다.</a>
   </p>
  </div>
 </article>
 
 <article class="caption">
  <img src="images/jareck.jpg" />
  <div class="cover boxcaption">
   <h3>타이틀2</h3>
   <p>
    내용입니다.<br />
    <a href="">내용입니다.</a>
   </p>
  </div>
 </article>
 
 <article class="slideright">
  <img src="images/kamil.jpg" class="cover" />
  <div>
   <h3>타이틀3</h3>
   <p>
    내용입니다.<br />
    <a href="">내용입니다.</a>
   </p>
  </div>
 </article>
 
 <article class="thecombo">
  <img src="images/martin.jpg" class="cover" />
  <div>
   <h3>타이틀4</h3>
   <p>
    내용입니다.<br />
    <a href="">내용입니다.</a>
   </p>
  </div>
 </article>
 
 <article class="slideup">
  <img src="images/nonsense.jpg" class="cover" />
  <div>
   <h3>타이틀5</h3>
   <p>
    내용입니다.<br />
    <a href="">내용입니다.</a>
   </p>
  </div>
 </article>
 
 <article class="peek">
  <img src="images/birss.jpg" style="height:90px;" />
  <img src="images/buildinternet.jpg" class="cover" />
 </article>
 
</section>

</body>
</html> 





 


소스 파일 목록 출력

GenerationFileList.java
 

package com.web.test;

import java.io.File;
import java.io.FilenameFilter;

public class GenerationFileList {

    private static final String path = "D:/workspaces/eclipse-jee-juno-SR1-win32/ewppis";
    
    private static void searchFile(File file) throws Exception {
        File[] files = file.listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                if(dir.toString().endsWith("ewppis\\log")) {
                    return false;
                } else if(dir.toString().endsWith("ewppis\\ref")) {
                    return false;
                } else if(dir.toString().endsWith("ewppis\\source")) {
                    return false;
                } else if(dir.toString().endsWith("WebContent\\resources\\temp")) {
                    return false;
                } else if(dir.toString().endsWith("WebContent\\html")) {
                    return false;
                } else if(dir.toString().endsWith("ewppis\\src\\com\\web\\test")) {
                    return false;
                } else if(dir.toString().endsWith("WebContent\\META-INF")) {
                    return false;
                } else if(dir.toString().endsWith("WebContent\\test")) {
                    return false;
                } else if(dir.toString().endsWith("WebContent\\WEB-INF\\classes")) {
                    return false;
                } else if(dir.toString().endsWith("WebContent\\WEB-INF\\upload")) {
                    return false;
                } else if(dir.toString().endsWith(".svn")) {
                    return false;
                }
                
                if (name.startsWith(".")) {
                    return false;
                } else if (name.equals("rebel.xml")) {
                    return false;
                } else if (name.equals("build.xml")) {
                    return false;
                }
                return true;
            }
        });

        for (int i = 0; i < files.length; i++) {
            getFile(files[i]);
        }
    }
    
    private static void getFile(File file) throws Exception {
        if (file.isDirectory()) {
            searchFile(file);
        } else {
            //System.out.println(file.getCanonicalPath());
            String path = file.getCanonicalPath().replaceAll("\\\\", "/");
            path = path.replaceAll("D:/workspaces/eclipse\\-jee\\-juno\\-SR1\\-win32/ewppis/", "");
            int idx = path.lastIndexOf("/");
            System.out.println(path.substring(0, idx) + " : " + path.substring(idx+1));
        }
    }
    
    public static void main(String[] args) throws Exception {
        File file = new File(path);
        if (file.isDirectory()) {
            searchFile(file);
        }
    }
    
}


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


패키지 클래스 / 메소드 출력

 import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import org.junit.Test;

public class MethodPrintTest {

 @Test
 public void test1() throws Exception {
  Class[] classes = getClasses("패키지경로");
  for(int i=0; i<classes.length; i++) {
   Class clazz = classes[i];
   
   Method[] methods = clazz.getDeclaredMethods();
   for(int j=0; j<methods.length; j++) {
    Method method = methods[j];
    System.out.println(method.toString());
   }
  }
 }
 
 
 private static Class[] getClasses(String packageName) throws ClassNotFoundException, IOException {
     ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
     assert classLoader != null;
     String path = packageName.replace('.', '/');
     Enumeration<URL> resources = classLoader.getResources(path);
     List<File> dirs = new ArrayList<File>();
     while (resources.hasMoreElements()) {
         URL resource = resources.nextElement();
         dirs.add(new File(resource.getFile()));
     }
     ArrayList<Class> classes = new ArrayList<Class>();
     for (File directory : dirs) {
         classes.addAll(findClasses(directory, packageName));
     }
     return classes.toArray(new Class[classes.size()]);
 }

 private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
     List<Class> classes = new ArrayList<Class>();
     if (!directory.exists()) {
         return classes;
     }
     File[] files = directory.listFiles();
     for (File file : files) {
         if (file.isDirectory()) {
             assert !file.getName().contains(".");
             classes.addAll(findClasses(file, packageName + "." + file.getName()));
         } else if (file.getName().endsWith(".class")) {
             classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
         }
     }
     return classes;
 }
 
}



시스템 테이블 사용 (MASTER.DBO.SPT_VALUES)

SELECT CONVERT(VARCHAR, DATEADD(D, NUMBER, '20120115'), 112) DATE
FROM MASTER..SPT_VALUES
WHERE TYPE = 'P'
AND NUMBER <= DATEDIFF(D, '20130115', '20130122')

 

RECURSIVE 를 사용

WITH A AS (
 SELECT LVL = 1
   , CAST('20130115' AS DATETIME) DT
 UNION ALL
 SELECT LVL + 1
   , DATEADD(D, 1, DT)
   FROM A
  WHERE DT < CAST('20130122' AS DATETIME)
)
SELECT LVL
  , DT
  , YEAR = DATEPART(YEAR, DT)
  , MONTH = DATEPART(MONTH, DT)
  , DAY = DATEPART(DAY, DT)
  , WEEK_OF_YEAR = DATEPART(WK, DT)
  , WEEK_OF_MONTH = DATEPART(WK, DT) - DATEPART(WK, LEFT(CONVERT(VARCHAR, DT, 112), 6) + '01') + 1
  , DAY = DATEPART(DW, DT)
  , 요일 = DATENAME(W, DT)
  , 분기 = DATEPART(Q, DT)
  , 반기 = CASE WHEN DATEPART(MONTH, DT) BETWEEN 1 AND 6 THEN '상반기' ELSE '하반기' END
  FROM A
OPTION (MAXRECURSION 0) 

 

휴일테이블의 휴일과 토/일요일 일자만 조회

 휴일테이블
INSERT INTO PIS_HOLIDAY (HOLI_DATE, HOLI_DESC) VALUES ('99990101', '신정');
INSERT INTO PIS_HOLIDAY (HOLI_DATE, HOLI_DESC) VALUES ('9999016', '테스트');
INSERT INTO PIS_HOLIDAY (HOLI_DATE, HOLI_DESC) VALUES ('20130117', '창립기념일');

WITH A AS (
 SELECT CAST('20130115' AS DATETIME) DT
 UNION ALL
 SELECT DATEADD(D, 1, DT)
   FROM A
  WHERE DT < CAST('20130122' AS DATETIME)
), B AS (
 SELECT CAST('20130115' AS DATETIME) DT
   , LVL = 1
 UNION ALL
 SELECT DATEADD(YEAR, 1, DT)
   , LVL + 1
   FROM B
  WHERE LVL <= DATEDIFF(YEAR, '20130115', '20130122')
), C AS (
 SELECT HOLI_DATE
   , HOLI_DESC
      FROM (
   SELECT SUBSTRING(CONVERT(VARCHAR, B.DT, 112), 1, 4) + SUBSTRING(A.HOLI_DATE, 5, 4) HOLI_DATE
     , A.HOLI_DESC
     FROM PIS_HOLIDAY A
     , B
    WHERE A.HOLI_DATE LIKE '9999%'
   ) A
  WHERE HOLI_DATE BETWEEN '20130115' AND '20130122'
)
SELECT HOLI_DATE
  , MAX(CASE WHEN GUBUN = 1 THEN HOLI_DESC ELSE DATENAME(W, HOLI_DATE) END) HOLI_DESC
  FROM (
 SELECT 1 GUBUN
   , HOLI_DATE
   , HOLI_DESC
   FROM PIS_HOLIDAY
     WHERE HOLI_DATE BETWEEN '20130115' AND '20130122'
 UNION ALL
 SELECT 1
   , HOLI_DATE
   , HOLI_DESC
   FROM C
 UNION ALL
 SELECT 2
   , CONVERT(VARCHAR, DT, 112)
   , NULL
   FROM A
  WHERE DATEPART(DW, DT) IN (1, 7)
  ) A
 GROUP BY HOLI_DATE
OPTION (MAXRECURSION 0) 

 

 

 

프로시저 생성

ALTER PROCEDURE [dbo].[SP_LEGACY_INS]
--ALTER PROCEDURE [dbo].[SP_LEGACY_INS]
    @LOG_SYSTEM VARCHAR(10),
    @LOG_MODE VARCHAR(2),
    @LOG_USER VARCHAR(20),
    @LOG_NAME VARCHAR(40),
    @LOG_PRG VARCHAR(40),
    @LOG_PRG_NAME VARCHAR(100),
    @LOG_FIELD VARCHAR(2),
    @LOG_IP VARCHAR(30),
    @LOG_DATA VARCHAR(100),
    @O_CODE INT OUTPUT,
    @O_MSG VARCHAR(255) OUTPUT
AS
BEGIN

    SET @O_CODE = 0
    SET @O_MSG = '정상 처리 되었습니다.'
   
    IF @LOG_SYSTEM IS NULL OR @LOG_SYSTEM = ''
        BEGIN
            SET @O_CODE = -101
            SET @O_MSG = '시스템명은 필수 입력항목입니다.'
        END
    ELSE IF @LOG_MODE IS NULL OR @LOG_MODE = ''
        BEGIN
            SET @O_CODE = -102
            SET @O_MSG = '접근모드는 필수 입력항목입니다.'
        END
    ELSE IF @LOG_USER IS NULL OR @LOG_USER = ''
        BEGIN
            SET @O_CODE = -103
            SET @O_MSG = '접근사용자ID는 필수 입력항목입니다.'
        END
    ELSE IF @LOG_NAME IS NULL OR @LOG_NAME = ''
        BEGIN
            SET @O_CODE = -104
            SET @O_MSG = '접근사용자명은 필수 입력항목입니다.'
        END
    ELSE IF @LOG_PRG IS NULL OR @LOG_PRG = ''
        BEGIN
            SET @O_CODE = -105
            SET @O_MSG = '접근프로그램ID는 필수 입력항목입니다.'
        END   
    ELSE IF @LOG_PRG_NAME IS NULL OR @LOG_PRG_NAME = ''
        BEGIN
            SET @O_CODE = -106
            SET @O_MSG = '접근프로그램명은 필수 입력항목입니다.'
        END
    ELSE IF @LOG_FIELD IS NULL OR @LOG_FIELD = ''
        BEGIN
            SET @O_CODE = -107
            SET @O_MSG = '접근개인정보는 필수 입력항목입니다.'
        END
    ELSE IF @LOG_IP IS NULL OR @LOG_IP = ''
        BEGIN
            SET @O_CODE = -108
            SET @O_MSG = '접근IP는 필수 입력항목입니다.'
        END

    ELSE
        BEGIN
            /*
            BEGIN TRY
                SELECT CONVERT(DATETIME, @LOG_DATE)
            END TRY

            BEGIN CATCH
                SET @O_CODE = -202
                SET @O_MSG = '로그 일자(YYYYMMDD)를 다시 확인해 주세요.'
            END CATCH

            BEGIN TRY
                SELECT CONVERT(DATETIME, @LOG_TIME)
            END TRY

            BEGIN CATCH
                SET @O_CODE = -203
                SET @O_MSG = '로그 시간(HH24:MI:SS)를 다시 확인해 주세요.'
            END CATCH
            */
           
            INSERT INTO PIS_LEGACY (LOG_SYSTEM, LOG_DATE, LOG_TIME, LOG_MODE, LOG_USER, LOG_NAME, LOG_PRG, LOG_PRG_NAME, LOG_FIELD, LOG_IP, LOG_DATA)
            VALUES
            (@LOG_SYSTEM, CONVERT(VARCHAR, GETDATE(), 112), CONVERT(VARCHAR, GETDATE(), 108), @LOG_MODE, @LOG_USER, @LOG_NAME, @LOG_PRG, @LOG_PRG_NAME, @LOG_FIELD, @LOG_IP, @LOG_DATA)
        END
END 


프로시저 실행

DECLARE
@O_CODE1 INT,
@O_MSG1 VARCHAR(255)
BEGIN
    EXEC SP_LEGACY_INS 'PIS', 'C', 'kdn100', '홍길동', 'PID10', 'VPN - 신청', '1', '127.0.0.1', '123456-1234567', @O_CODE1 OUTPUT, @O_MSG1 OUTPUT
    SELECT @O_CODE1, @O_MSG1
END 


자바에서 프로시저 실행

import java.io.File;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Types;
import java.util.Properties;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ProcedureTest {

    private final static Logger log = LoggerFactory.getLogger(ProcedureTest.class);
    private Connection conn;
    
    private void conn() throws Exception {
        String filePath = getClass().getResource("db.properties").getPath();
        File file = new File(filePath);
        if(file.exists()) {
            Properties prop = new Properties();
            prop.load(LegacyBatchBackup.class.getResourceAsStream("db.properties"));
            Class.forName(prop.getProperty("ewppis.mssql.driver"));
            String url = prop.getProperty("ewppis.mssql.url") + ";user=" + prop.getProperty("ewppis.mssql.user") + ";password=" + prop.getProperty("ewppis.mssql.password") + ";";
            conn = DriverManager.getConnection(url);
        } else {
            log.error("DB 접속정보가 없습니다.");
        }
    }
    
    @Test
    public void test1() throws Exception {
        
        CallableStatement cstmt = null;
        try {
            conn();
            String sql = "{CALL SP_LEGACY_INS(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)}";
            cstmt = conn.prepareCall(sql);
            cstmt.setString(1, "PIS");
            cstmt.setString(2, "C");
            cstmt.setString(3, "admin");
            cstmt.setString(4, "이순신");
            cstmt.setString(5, "PID10");
            cstmt.setString(6, "VPN - 신청");
            cstmt.setString(7, "1");
            cstmt.setString(8, "127.0.0.1");
            cstmt.setString(9, "123456-1234567");
            cstmt.registerOutParameter(10, Types.INTEGER);
            cstmt.registerOutParameter(11, Types.VARCHAR);
            cstmt.executeUpdate();
            
            log.debug("return : {}", cstmt.getString(10));
            log.debug("return : {}", cstmt.getString(11));
            
        } catch (Exception e) {
            
        } finally {
            if(cstmt != null) cstmt.close();
            if(conn != null) conn.close();
        }
    }
}


Detect language » Korean


출처 : http://howtodoinjava.com/2012/10/22/singleton-design-pattern-in-java/

Singleton pattern is a design solution where an application wants to have one and only one instance of any class, in all possible scenarios without any exceptional condition. It has been debated long enough in java community regarding possible approaches to make any class singleton. Still, you will find people not satisfied with any solution you give. They can not be overruled either. In this post, we will discuss some good approaches and will work towards our best possible effort.

Sections in this post:

  • Eager initialization
  • Lazy initialization
  • Static block initialization
  • Bill pugh solution
  • Using Enum
  • Adding readResolve()
  • Adding serial version id
  • Conclusion

Singleton term is derived from its mathematical counterpart. It wants us, as said above, to have only one instance. Lets see the possible solutions:

Eager initialization

This is a design pattern where an instance of a class is created much before it is actually required. Mostly it is done on system start up. In singleton pattern, it refers to create the singleton instance irrespective of whether any other class actually asked for its instance or not. 

public class EagerSingleton {
 private static volatile EagerSingleton instance = new EagerSingleton();

 // private constructor
 private EagerSingleton() {
 }

 public synchronized static EagerSingleton getInstance() {
  if (instance == null) {
   instance = new EagerSingleton();
  }
  return instance;
 }
}
 

Above method works fine, but has one performance drawback. The getInstance() method is synchronized and each call will require extra locking/unlocking steps which are necessary only for first time, and never there after.

Lets solve above problem in next method.

Lazy initialization

In computer programming, lazy initialization is the tactic of delaying the creation of an object, the calculation of a value, or some other expensive process until the first time it is needed. In singleton pattern, it restricts the creation of instance until requested first time. Lets see in code: 

public final class LazySingleton {
 private static volatile LazySingleton instance = null;

 // private constructor
 private LazySingleton() {
 }

 public static LazySingleton getInstance() {
  if (instance == null) {
   synchronized (LazySingleton.class) {
    instance = new LazySingleton();
   }
  }
  return instance;
 }
}
 

On first invocation, above method will check if instance is already created using instance variable. If there is no instance i.e. instance is null, it will create an instance and will return its reference. If instance is already created, it will simply return the reference of instance.

But, this method also has its own drawbacks. Lets see how. Suppose there are two threads T1 and T2. Both comes to create instance and execute “instance==null”, now both threads have identified instance variable to null thus assume they must create an instance. They sequentially goes to synchronized block and create the instances. At the end, we have two instances in our application.

This error can be solved using double-checked locking. This principle tells us to recheck the instance variable again in synchronized block in given below way:

public class EagerSingleton {
 private static volatile EagerSingleton instance = null;

 // private constructor
 private EagerSingleton() {
 }

 public static EagerSingleton getInstance() {
  if (instance == null) {
   synchronized (EagerSingleton.class) {
    // Double check
    if (instance == null) {
     instance = new EagerSingleton();
    }
   }
  }
  return instance;
 }

 Above code is the correct implementation of singleton pattern.

Please ensure to use “volatile” keyword with instance variable otherwise you can run into out of order write error scenario, where reference of instance is returned before actually the object is constructed i.e. JVM has only allocated the memory and constructor code is still not executed. In this case, your other thread, which refer to uninitialized object may throw null pointer exception and can even crash the whole application.

Static block initialization

If you have little idea about class loading sequence, you can connect to the fact that static blocks are executed during the loading of class and even before the constructor is called. We can use this feature in our singleton pattern also like this:

public class StaticBlockSingleton {
 private static final StaticBlockSingleton INSTANCE;

 static {
  try {
   INSTANCE = new StaticBlockSingleton();
  } catch (Exception e) {
   throw new RuntimeException("Uffff, i was not expecting this!", e);
  }
 }

 public static StaticBlockSingleton getInstance() {
  return INSTANCE;
 }

 private StaticBlockSingleton() {
  // ...
 }

Above code has one drawback. Suppose there are 5 static fields in class and application code needs to access only 2 or 3, for which instance creation is not required at all. So, if we use this static initialization. we will have one instance created though we require it or not.

Next section will overcome this problem.

Bill pugh solution

Bill pugh was main force behind java memory model changes. His principle “Initialization-on-demand holder idiom” also uses static block but in different way. It suggest to use static inner class.

public class BillPughSingleton {
 private BillPughSingleton() {
 }

 private static class LazyHolder {
  private static final BillPughSingleton INSTANCE = new BillPughSingleton();
 }

 public static BillPughSingletongetInstance() {
  return LazyHolder.INSTANCE;
 }

 As you can see, until we need an instance, the LazyHolder class will not be initialized until required and you can still use other static members of BillPughSingleton class. This is the solution, i will recommend to use. I also use it in my all projects.

Using Enum

This type of implementation recommend the use of enum. Enum, as written in java docs, provide implicit support for thread safety and only one instance is guaranteed. This is also a good way to have singleton with minimum effort.

public enum EnumSingleton {
 INSTANCE;
 public void someMethod(String param) {
  // some class member
 }
}

 

Adding readResolve()

So, till now you must have taken your decision that how you would like to implement your singleton. Now lets see other problems that may arise even in interviews also.
Lets say your application is distributed and it frequently serialize the objects in file system, only to read them later when required. Please note that, de-serialization always creates a new instance. Lets understand using an example:

Our singleton class is: 

public class DemoSingleton implements Serializable {
 private volatile static DemoSingleton instance = null;

 public static DemoSingleton getInstance() {
  if (instance == null) {
   instance = new DemoSingleton();
  }
  return instance;
 }

 private int i = 10;

 public int getI() {
  return i;
 }

 public void setI(int i) {
  this.i = i;
 }
}
 

Lets serialize this class and de-serialize it after making some changes:

public class SerializationTest {
 static DemoSingleton instanceOne = DemoSingleton.getInstance();

 public static void main(String[] args) {
  try {
   // Serialize to a file
   ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
     "filename.ser"));
   out.writeObject(instanceOne);
   out.close();

   instanceOne.setI(20);

   // Serialize to a file
   ObjectInput in = new ObjectInputStream(new FileInputStream(
     "filename.ser"));
   DemoSingleton instanceTwo = (DemoSingleton) in.readObject();
   in.close();

   System.out.println(instanceOne.getI());
   System.out.println(instanceTwo.getI());

  } catch (IOException e) {
   e.printStackTrace();
  } catch (ClassNotFoundException e) {
   e.printStackTrace();
  }
 }
}

Output:
20
10
 

 Unfortunately, both variables have different value of variable “i”. Clearly, there are two instances of our class. So, again we are in same problem of multiple instances in application.
To solve this issue, we need to include readResolve() method in our DemoSingleton class. This method will be invoked when you will de-serialize the object. Inside this method, you must return the existing instance to ensure single instance application wide. 

public class DemoSingleton implements Serializable {
 private volatile static DemoSingleton instance = null;

 public static DemoSingleton getInstance() {
  if (instance == null) {
   instance = new DemoSingleton();
  }
  return instance;
 }

 protected Object readResolve() {
  return instance;
 }

 private int i = 10;

 public int getI() {
  return i;
 }

 public void setI(int i) {
  this.i = i;
 }
}
 

Now when you execute the class SerializationTest, it will give you correct output. 

20
20 

 

Adding serial version id

So far so good. Till now, we have solved the problem of synchronization and serialization both. Now, we are just one step behind our correct and complete implementation. And missing part is serial version id.

This is required in condition when you class structure can change in between you serialize the instance and go again to de-serialize it. Changed structure of class will cause JVM to give exception while de-serializing process.

java.io.InvalidClassException: singleton.DemoSingleton; local class incompatible: stream classdesc serialVersionUID = 5026910492258526905, local class serialVersionUID = 3597984220566440782
at java.io.ObjectStreamClass.initNonProxy(Unknown Source)
at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
at java.io.ObjectInputStream.readClassDesc(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at singleton.SerializationTest.main(SerializationTest.java:24) 

 This problem can be solved only by adding a unique serial version id to class. It will prevent the compiler to throw the exception by telling that both classes are same, and will load the available instance variables only.

Conclusion

After having discussed so many possible approaches and other possible error cases, i will recommend you below code template to design your singleton class which shall ensure only one instance of class in whole application in all above discussed scenarios.

public class DemoSingleton implements Serializable {
 private static final long serialVersionUID = 1L;

 private DemoSingleton() {
  // private constructor
 }

 private static class DemoSingletonHolder {
  public static final DemoSingleton INSTANCE = new DemoSingleton();
 }

 public static DemoSingleton getInstance() {
  return DemoSingletonHolder.INSTANCE;
 }

 protected Object readResolve() {
  return getInstance();
 }

I hope, this post has enough information to make you understand the most common approaches for singleton pattern. Let me know of you thoughts please.

Happy Learning !!

Update: I just thought to add some examples which can be referred for further study and mention in interviews:

========================================================================================================
========================================================================================================

Singleton Serializable

출처 : http://atin.tistory.com/355

자바 개발을 하면서 제일 많이 쓰는 패턴 중 하나가 싱글톤 패턴이다.
그리고 싱글톤 소스 또한 다양하게 작성한다.
Source2와 같은 경우는 다중 쓰레드 상에서 위험하다. Source3과 같은 경우는 안전하긴 하지만 성능상 Source1이 제일 좋다.

Source4와 같은 경우는 싱글톤에서 직렬화 처리를 해주기 위한 방법이다. Serializable 을 구현해주고 readResolve메소드를 구현하고 모든 인스턴스 필드를 transient 로 선언해준다.

Source5와 같은 경우는 enum을 통한 구현 방법이다. 직렬화가 자동으로 지원되고 인스턴스가 여러개 생기지 않도록 지원해준다.


public class Singleton {

private static final Singleton instance = new Singleton();

private Singleton(){}

public static final Singleton getInstance(){
return instance;
}
}

[Source. 1] 
 

class Singleton2 {
private static Singleton2 instance = null;

private Singleton2(){}

public static final Singleton2 getInstance(){
if(instance == null) instance = new Singleton2();
return instance;
}
}

[Source. 2] 


class Singleton3 {
private static Singleton3 instance = null;

private Singleton3(){}

public static final Singleton3 getInstance(){
if(instance == null) {
synchronized(Singleton3.class){
instance = new Singleton3();
}
}
return instance;
}
}

[Source. 3] 
 

class Singleton4 implements Serializable {
/**
*/
private static final long serialVersionUID = 6209266452691660409L;
private static final transient Singleton4 instance = new Singleton4();

private Singleton4(){}

public static final Singleton4 getInstance(){
return instance;
}
private Object readResolve(){
return instance;
}
}

[Source. 4] 
 

enum Singleton5{
instance;
public void method(){}
}

[Source. 5] 

+ Recent posts