잊지 않겠습니다.

최신 Gradle 2.0으로 업데이트 한 내용이 존재합니다.


전에 정리한 적이 있는데, 조금 글을 다듬고 정리할 필요성이 있어서 다시 옮깁니다.
먼저, 개발되는 Project는 다음과 같은 구조를 갖습니다.

rootProject
-- domainSubProject (Spring Data JPA + queryDSL)
-- webAppSubProject (Spring Web MVC)

위와 같은 Project는 개발의 편의를 위해서 다음 두 조건을 만족해야지 됩니다.

  1. queryDSL을 사용하기 위한 Q-Entity를 생성이 compileJava task전에 수행되어야지 됩니다.
  2. web application 개발을 위해, tomcat을 실행시킬 수 있어야합니다.
  3. 개발된 web application을 test 환경 또는 product 환경에 배포가 가능해야지 됩니다.

root project

root project는 sub project들의 공통 설정이 필요합니다.

  1. build시에 필요한 plugin의 repository를 설정합니다.
  2. sub project들에서 사용될 공통 dependency들을 설정합니다. (sub project들의 build.gradle 이 너무 길어지는 것을 막을 수 있습니다.)
공통 plugin 추가
  • maven의 POM과 동일한 provided과 optional의 사용을 위해 spring prop plugin을 추가합니다.
  • subprojects들에 필요한 plugin들을 모두 추가합니다. (저는 java와 groovy, idea, eclipse 등을 추가했습니다.)
  • source java version과 target java version을 정해줍니다.
  • source code의 Encoding을 정해줍니다.
  • SubProject에서 기본적으로 사용될 dependency들을 모두 적어줍니다.

build.gradle (root project)

apply plugin: 'base'

// spring prop plugin 추가
buildscript {
    repositories {
        maven { url 'http://repo.springsource.org/plugins-release' }
    }
    dependencies {
        classpath 'org.springframework.build.gradle:propdeps-plugin:0.0.5'
    }
}

subprojects {
    // Plugin 설정, 만약에 code에 대한 static analysis가 필요한 경우에 이곳에 설정.
    apply plugin: 'java'
    apply plugin: 'eclipse'
    apply plugin: 'eclipse-wtp'
    apply plugin: 'idea'
    apply plugin: 'groovy'
    apply plugin: 'propdeps'
    apply plugin: 'propdeps-maven'
    apply plugin: 'propdeps-idea'
    apply plugin: 'propdeps-eclipse'

    // 기본적으로 사용할 repository들을 정의
    repositories {
        mavenCentral()
        maven { url "http://oss.sonatype.org/content/repositories/snapshots/" }
        maven { url "http://192.168.13.209:8080/nexus/content/repositories/releases" }
    }

    dependencies {
        def springVersion = "4.0.1.RELEASE"

        compile 'org.slf4j:slf4j-api:1.7.5'
        compile "org.springframework:spring-context:${springVersion}"
        compile "org.springframework:spring-aspects:${springVersion}"
        compile "org.springframework:spring-jdbc:${springVersion}"
        compile "org.springframework:spring-context-support:${springVersion}"

        compile 'mysql:mysql-connector-java:5.1.27'
        compile 'com.jolbox:bonecp:0.8.0.RELEASE'
        compile 'com.google.guava:guava:15.0'
        compile 'org.aspectj:aspectjrt:1.7.4'
        compile 'org.aspectj:aspectjtools:1.7.4'
        compile 'org.aspectj:aspectjweaver:1.7.4'

        testCompile 'org.springframework:spring-test:4.0.0.RELEASE'
        testCompile "junit:junit:4.11"

        groovy "org.codehaus.groovy:groovy-all:2.1.6"
        testCompile "org.spockframework:spock-core:1.0-groovy-2.0-SNAPSHOT"
        testCompile "org.spockframework:spock-spring:1.0-groovy-2.0-SNAPSHOT"
    }

    // source, target compiler version 결정
    sourceCompatibility = 1.8
    targetCompatibility = 1.8

    // source code encoding
    [compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
}
domain project
  • domain project는 queryDsl의 Q-Entity들의 생성이 필요합니다.
  • compileJava에 대한 Q-Entity 생성 Task의 Depend가 잡혀있으면 사용하기 편합니다.

build.gradle (domain project)

dependencies {
    def springVersion = "4.0.1.RELEASE"

    compile 'org.hibernate:hibernate-core:4.3.1.Final'
    compile 'org.hibernate:hibernate-entitymanager:4.3.1.Final'
    compile "org.springframework:spring-orm:${springVersion}"

    def queryDSL = '3.2.4'
    compile("com.mysema.querydsl:querydsl-core:$queryDSL")
    compile("com.mysema.querydsl:querydsl-jpa:$queryDSL")
    compile("com.mysema.querydsl:querydsl-sql:$queryDSL")
    provided("com.mysema.querydsl:querydsl-apt:$queryDSL") {
        exclude group: 'com.google.guava'
    }
    compile 'org.springframework.data:spring-data-jpa:1.5.0.RELEASE'
}

sourceSets {
    generated {
        java {
            srcDirs = ['src/main/generated']
        }
    }
}

// QEntity 생성 task
task generateQueryDSL(type: JavaCompile, group: 'build', description: 'Generates the QueryDSL query types') {
    source = sourceSets.main.java
    classpath = configurations.compile + configurations.provided
    options.compilerArgs = [
            "-proc:only",
            "-processor", "com.mysema.query.apt.jpa.JPAAnnotationProcessor"
    ]
    destinationDir = sourceSets.generated.java.srcDirs.iterator().next()
}

// compileJava task에 dependency를 걸어줍니다.
compileJava {
    dependsOn generateQueryDSL
    // compile target에 generated된 QClass들의 위치를 추가.
    source sourceSets.generated.java.srcDirs.iterator().next()
}

compileGeneratedJava {
    dependsOn generateQueryDSL
    options.warnings = false
    classpath += sourceSets.main.runtimeClasspath
}

clean {
    delete sourceSets.generated.java.srcDirs
}

idea {
    module {
        sourceDirs += file('src/main/generated')
    }
}
webapplication project

마지막으로 web application project입니다. 이는 조건이 조금 더 많습니다.

  • tomcat을 실행시켜 local 개발 환경에서 사용할 수 있어야지 됩니다.
  • 외부 테스트 또는 운영환경의 tomcat에 배포가 가능해야지 됩니다.

위 조건을 만족하기 위해서 gradle의 tomcat plugin과 cargo plugin을 이용합니다.

plugin에 대한 자료들은 다음 url에서 보다 많은 자료를 볼 수 있습니다.

tomcat plugin > https://github.com/bmuschko/gradle-tomcat-plugin
cargo plugin > https://github.com/bmuschko/gradle-cargo-plugin

build.gradle (webapp project)

apply plugin: 'war'
apply plugin: 'tomcat'
apply plugin: 'cargo'

// tomcat과 cargo plugin에 대한 repository 설정입니다.
buildscript {
    repositories {
        mavenCentral()
        jcenter()
    }

    dependencies {
        classpath 'org.gradle.api.plugins:gradle-tomcat-plugin:1.0'
        classpath 'org.gradle.api.plugins:gradle-cargo-plugin:1.4'
    }
}


dependencies {
    // tomcat plugin 설정입니다.
    String tomcatVersion = '7.0.47'
    tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}"
    tomcat "org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}"
    tomcat("org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}") {
        exclude group: 'org.eclipse.jdt.core.compiler', module: 'ecj'
    }
    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
    providedCompile 'javax.websocket:javax.websocket-api:1.0'
    providedCompile 'javax.servlet:jsp-api:2.0'
    providedCompile "org.apache.tomcat:tomcat-servlet-api:${tomcatVersion}"

    // cargo에 대한 설정입니다.
    def cargoVersion = '1.4.5'
    cargo "org.codehaus.cargo:cargo-core-uberjar:$cargoVersion",
            "org.codehaus.cargo:cargo-ant:$cargoVersion"


    def springVersion = "4.0.1.RELEASE"
    compile "org.springframework:spring-webmvc:${springVersion}"
    compile 'jstl:jstl:1.2'
    compile 'org.apache.tiles:tiles-jsp:3.0.3'

    compile 'org.slf4j:slf4j-api:1.7.6'
    compile 'org.slf4j:jcl-over-slf4j:1.7.6'

    compile 'ch.qos.logback:logback-classic:1.0.13'
    compile 'ch.qos.logback:logback-core:1.0.13'

    compile 'org.apache.velocity:velocity:1.7'
    compile 'org.freemarker:freemarker:2.3.20'
    compile 'com.ctlok:spring-webmvc-rythm:1.4.4'
    compile project(':bookstoreHibernate')
}

// tomcarRun을 실행시키기 위해서 war에 대한 dependency를 주입합니다.
tomcatRun {
    contextPath = ""
    URIEncoding = 'UTF-8'
    dependsOn war
}

tomcatRunWar {
    dependsOn war
}

// cargo를 이용한 배포를 위해서 war에 대한 dependency를 주입합니다.
cargoRedeployRemote {
    dependsOn war
}

cargoDeployRemote {
    dependsOn war
}

cargo {
    containerId = 'tomcat7x'
    port = 8080

    deployable {
        context = "${project.name}"
    }

    // remoteDeploy 되는 target의 tomcat 정보
    remote {
        hostname = '192.168.13.209'
        username = 'ykyoon'
        password = 'qwer12#$'
    }
}

bower를 이용한 javascript dependency

web application에서의 외부 javascript dependency를 사용하는 방법입니다. bower를 이용하는 경우, 외부에서 javascript에 대한 source code를 모두 다운받고 compile된 javascript를 dist에 저장하게 됩니다.

그런데, 우리의 web application은 dist에 저장된 특정 파일만을 사용하게 됩니다. 그럼 이 dist에 있는 file을 최종적으로 배포할 webapp folder에 넣어줘야지 됩니다. 이를 위해서 개발된 것이 bower-installer 입니다. 그런데 bower-installer의 경우에는 윈도우즈에서 동작이 정상적이지 않습니다. 아니 실행이 되지 않습니다.; 그래서 bower-installer와 동일한 동작을 하는 task를 만들어봤습니다.

먼저, bower-installer는 bower.json의 install property에 설정합니다. jquery와 bootstrap에 대한 dependency를 설정한 bower.json 입니다.

bower.json

{
    "name" : "bookstore-web",
    "version" : "0.0.0.1",
    "dependencies" : {
        "jquery" : "1.11.0",
        "bootstrap" : "3.1.1"
    },
    "install" : {
        "path" : {
            "css" : "src/main/webapp/lib/css",
            "js" : "src/main/webapp/lib/js",
            "eot" : "src/main/webapp/lib/fonts",
            "svg" : "src/main/webapp/lib/fonts",
            "ttf" : "src/main/webapp/lib/fonts",
            "woff" : "src/main/webapp/lib/fonts",
            "map" : "src/main/webapp/lib/js"
        },
        "sources" : {
            "jquery" : [
                    "bower_components/jquery/dist/jquery.min.js",
                    "bower_components/jquery/dist/jquery.min.map"
                ],
            "bootstrap" : [
                    "bower_components/bootstrap/dist/css/bootstrap.min.css",
                    "bower_components/bootstrap/dist/css/bootstrap-theme.min.css",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.eot",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.svg",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.woff",
                    "bower_components/bootstrap/dist/js/bootstrap.min.js"
                ]
        }
    }
}

위의 install property에 지정된 js와 css들을 옮기는 task는 다음과 같이 설정할 수 있습니다. war task에 dependency를 주입해서 위의 tomcatRun이나 cargoRedeployRemote 등에서도 사용할 수 있습니다.

import org.apache.tools.ant.taskdefs.condition.Os
import groovy.json.JsonSlurper
task bowerInstall(type:Exec, description : 'copy js files dependencies that is defined in Bower.js') {

    if(Os.isFamily(Os.FAMILY_WINDOWS)) {
        commandLine 'cmd', 'bower', 'install'
    } else {
        commandLine 'bower', 'install'
    }

    def jsonHandler = new JsonSlurper()
    def jsonFile = file("bower.json")
    def conf = jsonHandler.parseText(jsonFile.getText("UTF-8"))
    def pathMap = [:]

    conf.install.path.each {
        pathMap.put(it.key, it.value)
    }

    conf.install.sources.each {
        it.value.each { f ->
            def sourceFile = file(f)
            String sourceName = sourceFile.name
            int dotPos = sourceName.lastIndexOf(".")
            String ext = sourceName.substring(dotPos + 1, sourceName.length())
            if(pathMap.containsKey(ext)) {
                copy {
                    from f
                    into pathMap.get(ext)
                }
            }
        }
    }
}

war {
    dependsOn bowerInstall
}


위 정리된 내용을 github에 공유합니다. 아래 주소에서 git clone 하시면 됩니다. ^^

 

 https://github.com/xyzlast/study-spring-bookstore.git



Posted by Y2K
,

Java VM - YoungGen space의 동작

번역(http://javaeesupportpatterns.blogspot.ie/2013/11/java-vm-beware-of-younggen-space.html) 입니다. 약간의 의역과 오역이 들어가있을수 있습니다. ^^;

정상적인 JVM 상태는 application의 성능 및 안정성에 매우 중요한 목표라고 할 수 있다. 이러한 health 평가는 GC의 major collection이나 memory leak에 촛점이 맞춰져 있다. Young Generation space 또는 shot lived object의 경우에는 크기나 저장 공간이 어떻게 될까?

이 article은 우리의 client의 최신 장애 경험에 의거한 이야기이다. 샘플 application을 통해, 이 현상을 보여줄 것이며, 메모리 저장소와 minor collection이 Old Generation collection 또는 tenured space와 비슷한 정도의 성능저하를 나타낼 수 있음을 보여줄 것이다.

JVM Health diagnostic

만약에 당신이 JVM tunning의 신참이라면, 모든 application에 공통으로 사용할 수 있는 일반적인 솔루션은 존재하지 않는다는 것은 곧 알 수 있을 것이다. Web을 통한 여러 소스의 경로를 통해, 여러분들은 JVM GC pauses가 매우 섬세하며 이것을 이해하기 위해서는 매우 많은 노력을 해야지 된다는 것을 알 수 있다. (어떤 application은 JVM pause time을 1% 이하로 줄이길 요구한다.)

성능과 load testing을 같이 하는 Java profiling (메모리 leak detection을 포함)은 JVM runtime health와 당신의 application의 memory 저장공간에 대한 데이터와 시실을 모두 수집하기 위한 좋은 방법이 된다.

그럼, “Healthy” JVM 이란 무엇을 의미하는가? 이 질문에 대해서 당신의 최고의 지식을 가지고 다음 질문들을 대답해보길 바란다.

만약에 당신이 NO라고 대답한다면, 90%이상의 확신을 가진 대답을 해주면 된다. 또는 I DON’T Know 라고 해도 된다.

  • Application에서 Java heap 또는 OldGen space leaking over time이 발생하는가? (major collection이 발생한 경우)
  • Application에서 현재 크거나 잦은 JVM GC Collection이 발생하고 있는지?
  • JVM overall pause time이 5%보다 넘거나 이상적인 baseline보다 높은지?
  • Application의 response time이 JVM GC 행위에 영향을 받고 있는지?
  • 3개월 내로, java.lang.OutOfMemoryError error를 경험해본적이 있는지?
  • 3개월 내로, JVM의 crush 현상을 격어본적이 있는지?
  • 당신의 JVM 상태가 현제 불안정하거나 인위적인 개입을 받아야지 되는 상태에 있는지? (JVM reboot etc.. )

만약에 당신이 YES 또는 I don’t know라고 대답을 했다면, 당신의 production 성능을 높이기 위해서 tuning team이 현 JVM GC policy를 살펴서 해야지 될 일들이 남아있다는 것이다.

만약에 당신이 모든 질문에 NO라고 대답할 수 있다면, 당신은 매우 견고한 application과 JVM 안정성을 가지고 있는 것이다. 축하한다. 여전히 나는 당신에게 major release와 load forecasts에 대한 상황을 평가하는 것을 제안한다.

Young Generation : Stop-The-World

우리가 JVM Health 평가를 진행할 때, JVM overall pause time은 자주 언급되는 문제이다. JVM overall pause time은 JVM이 stop the world event가 발생할 때, 얼마나 많은 시간이 걸리는지를 측정하는 것이다. Event가 발생중에, application thead는 모두 중지되고, 어떠한 일도 동작하지 않으며, application의 응답시간은 증가되게 된다. 이 수치는 JVM의 불안정성 이나 예측 불가능한 응답시간에 대한 매우 중요한 지표로 사용된다.

최근 몇년을 거쳐 사람들이 많이 알고 있으나, 잘못된 정보는 YoungGen 또는 minor collection은 매우 명료하며, application response time에 영향을 주지 않는다.라는 선입관이다. 이 문장은 대부분의 경우에 참이지만, Java heap size가 작고 (YG space < 1GB), 생명주기가 매우 짧은 경우만을 한정해야지 된다. 위 시나리오는 minor collection 실행이 매우 빠르며 (< 20msec), 자주 일어나지 않으며 (every 30 seconds++), overall JVM pause 시간중 YoungGen space가 매우 작게 남아있는 경우이다(<< 1%). 그러나, YG memory 할당 빈도수는 매우 빠르게 증가되어 이러한 상황은 매우 쉽게 변화될 수 있다(traffic의 증가로 인하여 사용자가 증가하는 경우, YG의 Size 및 할당 빈도수는 매우 쉽게 증가한다).

다음 article에서 제안한 내용을 중심으로 YoungGen space와 concurrent collectors에 대한 정보를 좀 더 얻기를 바란다.

Oracle HotSpot mostly concurrent collectors: CMS vs. G1

http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html

Oracle HotSpot minor collections exhaustive coverage

http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html
http://blog.griddynamics.com/2011/06/understanding-gc-pauses-in-jvm-hotspots_02.html

이미 사용하고 있는 HotSpot GC 규정에 상관없이, 모든 collector들 CMS, G1, YoungGen space collection 모두는 “stop the world”를 발생시킬 수 있다. 우리의 지식대로, Azul Zing C4만이 실시간 compacting collectior인 것으로 알려지고 있다. 우리는 현 시점에서 이 collector를 사용할 수 있는 경험을 갖지 못했다. C4 tuning 경험이나 연구 결과가 있다면 많은 공유를 부탁드린다.

지금 우리는 약간의 이론을 소개하도록 한다. sample application을 통해, 성능 검사 결과는 다양한 YoungGen의 저장공간과 할당 빈도를 보여줄 것이다.

Sample application specification

YG 할당빈도수에 따른 JVM pause time %를 측정하기 위해 우리는 다음 sample application을 작성하였다.

// jvm uri를 갖는 REST WebService (JAX-RS)
@GET
@Path("/jvm")
@Produces(MediaType.APPLICATION_JSON)
public Integer jvm() {}

jvm method의 실행은 다음 로직을 따르게 된다.

이미 결정된 크기의 short-lived object를 할당한다. (fast Young GC의 요건에 적합하다.)

추가적으로, 1 GB의 CMS collector의 동작의 약간의 noise를 가할 목적의 long-lived objects의 저장소를 만들어준다(GC의 대상이 아니다.).

private final static int LONG_LIVED_OBJ_FOOTPRINT = (1024 * 1024 * 1024);
private final static int SHORT_LIVED_OBJ_FOOTPRINT = (100 * 1024 * 1024);

// 1 GB static memory footprint
private final static byte byteArrayLongLivedObj[] = new byte[LONG_LIVED_OBJ_FOOTPRINT];

// 100 MB memory allocation (waste) created per execution
public void generateShortLivedObj(String objId) {          
  byte byteArrayShortLivedObj[] = new byte[SHORT_LIVED_OBJ_FOOTPRINT];
}

마지막으로, 테스팅하는 환경 변수는 다음과 같다.

OS: Windows 7 @64-bit
Java EE container: WildFly 8 Beta1
JVM: Oracle HotSpot 1.7 @64-bit with a Java heap size of 5 GB (YG space: 1152 MB XX:NewRatio=3). GC collector: CMS
IDE: JBoss Developer Studio 7.0.0.GA
JVM monitoring: JVisualVM
JVM memory leak analyzer: Plumbr 3.0
JVM verbose:gc log analyzer: GCMV
Performance & load testing: Apache JMeter 2.9

Performance testing results and observations

Simulated된 성능 테스팅은 높은 JVM pause time을 가지고 있었으며, peak load하에서 잦은 성능저하를 보여줬다.

Baseline

  • 10개의 concurrent thread
  • JVM process당 100 MB의 short lived object.

short lived object memory 저장 공간은 매우 극적이지만, 처음에는 잘 운영되었다.

Result

Average response time: 140 ms
Throughput: 68 req / sec
JVM overall pause time: 25.8%
YG collection frequency: 7 collections per second
Rate of GC: 308,909 MB per minute

JvirtualVM에 따르면, 이 JVM은 정상적이다(memory leak이 없으며, 안정적이며, OldGen collect 횟수가 낮다). 그러나, verbose:gc log를 좀 더 깊게 바라보면, JVM pause time이 전체 runtime 시간의 25.8%가 되는 것을 알 수 있고, 모든 지연시간은 YG Collection에 의해서 발생됨을 알 수 있다. 이것은 명확하게 verbose:gc log vs JVM에 대한 검증된 기준과의 차이에 대한 적절한 분석이 필요함을 보이고 있다.

Test & tuning #1.

  • 10 concurrent threads
  • 50 MB of short lived objects / JVM process

(먼저번의 테스트와의 차이점은 short lived object의 크기가 다르다는 점이다.)

위 조건에서 application의 메모리 할당 용량은 100MB에서 50MB 사이에서 동작하고 있었다. 우리는 명확히 개선된 것을 모든 지표에서 볼 수 있었으며 request 당 할당되는 메모리 양의 감소가 처리양에 관련이 있는 것을 볼 수 있다.

Result

Average response time: 119 ms  -21
Throughput: 79 req / sec  +11
JVM overall pause time: 15.59%  -10.21
YG collection frequency: 3-4 collections per second  -3
Rate of GC: 164 950 MB per minute  -143 959

Test & tuning #2

  • 10 concurrent threads
  • 5 MB of short lived objects created per execution per JVM process

(Test 1에 비하여 request 당 생성되는 short lived object의 크기가 5M로 반으로 줄었다.)

Result

Average response time: 107 ms  -33
Throughput: 90 req / sec  +22
JVM overall pause time: 1.9%  -23.9
YG collection frequency: 1 collection every 2-3 seconds * significant reduction
Rate of GC: 15 841 MB per minute  -293 068

보는것과 같이, application footprint와 memory 할당에 대한 최종 개선은 의미있는 매우 적절한 1.9%정도인 JVM pause time을 보여주고 있다.3번의 테스트를 통해서 매우 중요한 점을 알 수 있는데, OldGen footprint와 CMS 동작은 JVM pause time에 전혀 영향을 주지 않고 있으며, 성능 문제는 과도한 동작과 YG Collection에 연결된 매우 큰 크기의 stop the world event와 연결되어 있음을 알 수 있다.

Solution & recommandations

이 문제 케이스에서 우리는 JVM pause time을 줄이기 위해서는 연관된 과도한 YG collection 활동에 대한 tuning과 application request에 의한 memory footprint를 제거시켜주는것이 중요함을 알 수 있다. 따라서, application rate와 YG GC frequency를 줄이는 것이 중요하다.

그러나, 이러한 tuning 전략은 단기간에 가능하지 않는데, 다른 솔루션들을 분석하는 것이 더욱더 가치가 있기 때문이다. 이와 유사한 결과는 어쩌면 다음과 같은 허용량 개선 전략으로도 성취할 수도 있다.

  • Horizontal & Vertical scaling : traffic을 증가된 숫자의 JVM process로 나누면 메모리 할당 빈도와 YG Collection 횟수를 줄일 수 있다. 이는 근본적인 원인을 hardware로 넘기는 방법이다. 나의 추천사항은 언제나 당신의 application memory footprint를 먼저 tunning하고, 다른 scaling option을 찾는 것이다.
  • Java heap size & YG ratio tuning : YG 영역의 크기의 증가는 틀림없이 YG Collections에 의한 frequency를 줄일 수 있다. 그러나 매우 주의해야지 된다. 절대로 OldGen space를 굶주리게 만들어서는 안된다 그렇지 않으면 당신은 매우 간단히 문제를 JVM thrashing과 OOM events쪽으로 옮겨버리게 된다.

Final words

JVM YG collections의 성능 영향에 대해서 보다 나은 이해가 있기를 바란다. 나는 이 article을 읽은 후에 다음과 같은 실습을 해보기를 권합니다.

  • 당신이 맡고 있는 가장 바쁘게 돌아가는 application을 선택합니다.
  • verbose:gc log를 살펴보고, JVM pause time과 GCMV를 결정해보세요.
  • YG collection에 의한 영향을 판단하고, tunning 기회를 알아보시길 바랍니다.

당신의 comment와 JVM tuning 경험 기회를 공유할 수 있기를 바랍니다.


Posted by Y2K
,

(원문 : http://javaeesupportpatterns.blogspot.ie/2013/02/java-8-from-permgen-to-metaspace.html)

Java8에서 가장 큰 변화라고 개인적으로 생각하고 있는 Metaspace에 대한 post를 한번 번역해서 소개합니다.

JDK7까지 사용된 Permanent General (PermGen) space가 Oracle의 주도하에 제거된 것이 큰 특징중 하나입니다. 예를 들어, 내부 문자열의 경우, JDK7에서 이미 제거되어있었으나, JDK8에서 최종적으로 폐기되게 되었습니다.

이 문서는 PermGen의 후계자라고 할 수 있는 Metaspace에 대한 내용입니다. 또한 우리는 HotSpot 1.7 vs HotSpot 1.8 간의 leaking class meta object의 동작에 대해서 알아보도록 하겠습니다.

Metaspace : 새로운 메모리 저장소의 탄생

JDK8 HostSpot VM의 경우, 재사용성을 위한 객체에 대한 metadata를 Metaspace라고 불리우는 native memory에 저장하게 됩니다. 마치 Oracle JRockit 또는 IBM JVM과 같은 동작으로 구성되게 됩니다.

좋은 뉴스는 이제는 더이상 java.lang.OutOfMemoryError : PermGen space와 같은 문제를 더이상 야기하지 않는다는 것과 더이상 tunning과 monitoring을 통해서 이러한 memory space를 조절할 필요가 없다는 것입니다. 그렇지만, 이러한 변화는 기본적으로 안보이게 설정이 되기 때문에 우리는 이 새로운 meta memory에 대한 족적을 여전히 살펴볼 필요성은 존재하게 됩니다. 이러한 변화가 마법과 같이 class의 메모리 공간을 줄여주거나 class loader의 memory leak을 줄여주지는 못하는 것을 인지해야지 됩니다. 우리는 새로운 naming convention하에서 다른 시각으로 이러한 문제를 바라보는 것이 필요합니다.

Summary

PermGen space situation

  • memory space는 완벽하게 제거됩니다.
  • PermSize와 MaxPermSize JVM argument는 무시되며, 시작될때 경고를 표시합니다.

Metaspace memory allocation model

  • class의 metadata는 거의 대부분 native memory에 저장됩니다.
  • class의 metadata를 사용하기 위한 klasses들은 모두 제거됩니다.

Metaspace capacity

  • 기본적으로 metaspace는 native memory를 사용하기 때문에, OS에서 사용 가능한 memory 영역 모두를 사용 가능합니다.
  • 새로운 Flag(MaxMetaSpaceSize)가 가능합니다. 이는 class metadata를 저장하기 위한 최대한의 memory size를 정하는 것이 가능합니다. 만약에 Flag를 설정하지 않는다면, Metaspace는 application의 동작에 따라 가변적으로 동작하게 됩니다.

Metaspace garbage collection

  • dead class와 classloader의 GC는 MaxMetaspaceSize에 도달하면 발생합니다.
  • Metaspace의 tuning과 monitoring은 GC의 횟수와 지역을 제한하기 위해 필요한 작업입니다. 과도한 Metaspace GC는 classloader의 memory leak 또는 적합하지 않은 Application의 memory size를 초래합니다.

Java heap space impact

  • 여러 다양한 데이터들은 Java Heap space로 저장 영역이 이동되었습니다. 결국 기존 JDK7에서 JDK8로 업그레이드를 하는 경우, Java Heap memory를 증가시킬 필요가 있습니다.

Metaspace monitoring

  • metaspace 사용량은 GC log output을 통해서 확인 가능합니다.
  • Jstat * JVirtualVM 은 아직 metaspace를 monitoring 하지 못합니다. 아직 예전 PermGen space reference를 확인하도록 되어 있습니다.

PremGen vs. Metaspace runtime comparison

새로운 Metaspace memory space의 동작을 이해하기 위해서, 우리는 metadata leaking java program을 준비했습니다.

주어진 시나리오에 따라 다음과 같이 테스트를 진행했습니다.

  • JDK 1.7에서 PermGen memory space를 128M로 설정 후, PermGen memory의 감소를 관찰합니다
  • JDK 1.8에서 Metaspace memory space의 GC와 dynamic increase를 monitoring 합니다.
  • JDK 1.8에서 MaxMetaspaceSize를 128M로 설정 후, Memory의 감소를 관찰합니다.

JDK 17 - PermGen depletion

  • Java program with 50K configured iterations
  • Java heap space는 1024 MB
  • Java PermGen space 는 128MB

JVirtualVM의 경우, PermGen depletion은 약 30K+개의 class가 class loader에 로딩되었을 때, 발생합니다. GC output을 통해 다음 log를 확인할 수 있습니다.

Class metadata leak simulator
Author: Pierre-Hugues Charbonneau
http://javaeesupportpatterns.blogspot.com
ERROR: java.lang.OutOfMemoryError: PermGen space

JDK 1.8 - Metaspace dynamic re-size

  • Java program with 50K configured iterations
  • Java heap space는 1024 MB
  • Java metaspace space : 지정하지 않음 (default)


GC output에서 볼 수 있듯이, JVM Metaspace는 20MB에서 320MB로 dynamic resizing이 이루어진 것을 볼 수 있습니다. 또한 JVM의 GC 시도를 GC output을 통해서 확인 할 수 있습니다. 우리는 memory leak이 발생하는 application을 구성하고 있기 때문에, JVM은 dynamic expand 밖에 할 수 없습니다.

이 프로그램은 50K 정도의 OOM event와 50K+ 정도의 classes들을 load 시킬 수 있습니다.

JDK 1.8 - Metaspace depletion

  • Java program with 50K configured iterations
  • Java heap space는 1024 MB
  • Java metaspace space : 128M (-XX : MaxMetasapceSize=128M)

JVirtualVM에서 보듯이, metaspace 감소는 약 30K+ 개의 class들이 감소한 후 부터 발생하고 있습니다. 마치 JDK 1.7과 동작이 비슷합니다. 이러한 현상을 GC output을 통해서 확인할 수 있습니다. 또다른 흥미로운 현상은 native memory footprint를 보면 알 수 있듯이, native memory는 MaxMetaspaceSize의 2배를 할당하고 있다는 점입니다. 이것은 native memory 낭비를 줄일, Metaspace의 재할당 정책에 대한 좋은 tunning point입니다.

이제 java program output을 통해 Exception을 확인해보도록 하겠습니다.

Class metadata leak simulator
Author: Pierre-Hugues Charbonneau
http://javaeesupportpatterns.blogspot.com
ERROR: java.lang.OutOfMemoryError: Metadata space
Done!

예상과 같이, Metaspace가 128MB가 되었을 때, 마치 JDK 1.7과 거의 유사한 base line을 갖는 50 K 정도의 객체를 생성할 때 Exception이 발생합니다. 새로운 OOM(Out Of Memory) error는 JVM에 의해서 발생되게 됩니다. OOM event는 memory allocation failuire에 의해 발생되게 됩니다.

metaspace.cpp 

Final words

새로운 Java 8 Metaspace에 대해서, 빠른 분석과 설명을 알아보길 바랍니다. 현 분석대로라면 마지막 실험에서 보셨던 것과 같이, Metaspace GC 또는 OOM 조건이 발생하는 것에 대한 monitoring과 tuning이 필요한 것을 보여줍니다. 다음 article에서는 새로운 기능에 연관된 performance 향샹에 대한 비교를 포함할 수 있었으면 좋겠습니다.

Posted by Y2K
,

java8이 나오고 빠르게 한번 환경을 변경시켜보고 난 후, 문제점 상황을 정리해봤습니다.

jacoco 버젼 문제

jacoco를 최신 버젼으로 해줄 필요가 있습니다. jacoco는 최신 버젼이 0.7.0.201403182114 입니다. 버젼뒤의 날짜를 보시면 아시겠지만, java8 발표 일자와 완전히 동일합니다. 기존 jacoco에서는 byte code exception이 발생하기 때문에 반드시 업그레이드 할 필요가 있습니다.

tomcat 8

gradle tomcat plugin이 아직 tomcat8을 지원하지 못합니다. gradle을 이용한 build의 경우에는 java 8을 사용하시는 것을 고민해주시는 것이 좋습니다. 물론 java 8에서 tomcat7을 사용하는 것은 아무런 문제가 발생되지 않습니다.

SonarQube

sonarQube에서 아직 java 8을 지원하지 못하고 있습니다. 3월말에 최신 업데이트가 될 것이라는 이야기가 아래 링크에 있는데…… java8은 작년 여름에 release 될 예정이였지 않나요. 이걸 믿어야지 되는지 고민하고 있습니다.

http://sonarqube.15.x6.nabble.com/Sonar-Support-for-JDK-8-td5019488.html

무엇보다 위 링크에 이야기되고 있는 JIRA Issue는 이미 resolved된 상태입니다. 일단 stackoverflower에 질문을 올려두긴 했는데.. 메일링 리스트에서 물어보세요. 라는 답변을 받아서 상처받고 메일링 리스트에 가입해서 다시 물어봤습니다. ㅠ-ㅠ 답변을 기다려야지요.

FindBugs

sonarQube에서 지금 지원을 못하는 가장 결정적 이유가 바로 FindBug가 Java8을 지원하지 못하는 이슈가 있기 때문입니다. StringSequence class를 비롯해서 몇몇 Class에 대한 오류를 발생시키고 있습니다. Java8에서 가장 큰 이슈중 하나네요.

Java8

개인적으로는 이번 java8을 매우 기다리고 있습니다. PermGemSize에 대한 GC의 개선과 더불어 lamda expression으로 대표되는 java 언어의 확장성. 그리고 지겹고 지겨운 Date 객체 추가.

개인적으로는 이 3가지때문에 java8을 기다리고 있는데. 다른 분들은 어떠실지 모르겠네요.

Posted by Y2K
,

매번 코드를 작성할 때마다 까먹어서 정리합니다. 기억력이 감퇴가 된것 같네요.

  1. MessageSource를 선언. refresh를 위해 ReloadableResourceBundleMessageSource를 사용하는 것이 정신건강상 좋다.
  2. LocaleChangeInterceptor를 선언
  3. addInterceptors method를 override 시켜, interceptor를 추가
  4. AcceptHeaderLocaleResolver, CookieLocaleResolver, SessionLocaleResolver 중 하나를 선택하여 LocaleResolver를 구현

AcceptHandlerLocaleResolver

기본적인 Http Header인 Accept-Language를 이용하는 방법입니다. 기본값이 AcceptHeaderLocaleResolver로 되어 있습니다. 가장 구현이 간단하나, parameter를 통해서 동적으로 Locale을 변경하지 못합니다. UnsupportedOperationException이 발생합니다. 이를 테스트 하기 위해서는 Browser의 설정중에 언어 셋을 변경시켜야지 됩니다.

CokieLocaleResolver

Cookie를 이용하는 방식으로, Cookie name, expired time 등을 설정 가능합니다.

SessionLocaleResolver

Session값을 이용하는 방법으로 서버의 부하측면이 적다면 가장 좋은 방법인것 같습니다.

@Configuration code는 다음과 같습니다.

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {
        "me.xyzlast.web.controllers"
})
public class ControllerConfiguration extends WebMvcConfigurerAdapter {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/lib/**").addResourceLocations("/lib/**");
        super.addResourceHandlers(registry);
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() throws IOException {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
        localeChangeInterceptor.setParamName("lang");
        return localeChangeInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
        super.addInterceptors(registry);
    }

    @Bean
    public AcceptHeaderLocaleResolver acceptHeaderLocaleResolver() {
        return new AcceptHeaderLocaleResolver();
    }

    @Bean
    public ReloadableResourceBundleMessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setDefaultEncoding("UTF-8");
        messageSource.setBasename("classpath:messages");
        messageSource.setFallbackToSystemLocale(false);
        return messageSource;
    }
}


Posted by Y2K
,

국내 SI 개발환경에서의 거의 100이면 99정도 myBatis를 이용한 DB Handling을 하고 있습니다. 개인적으로는 상당한 ORM 빠돌이라서… 왜 ORM을 사용하지 않지? 라는 의문을 항시 가지고 있다가 다른 여러 사람들 및 개인적인 생각을 정리해서 한번 올려봅니다.

ORM에 대한 learning curve

먼저, 개발자들에 대한 개인 책임이 어느정도 있다고 생각합니다. 근처의 여러 사람들을 만나보면, 개발자들중 3년이 지나고 나서 공부를 꾸준히하는 개발자들을 찾기 힘들다는 이야기를 자주 듣습니다. 현업에서 주로 사용되고 있는 myBatis 이외에 자신이 찾아서 공부를 하는 사람들의 수가 의외로 적은 것이 한 이유가 아닐까 생각됩니다.

이는 철저히 개발자들의 문제점이 아닐까 라는 생각을 가지고 있습니다. 한해한해 바뀌어가는 개발환경에 대해서 자기 자신을 꾸준히 업그레이드해두지 않으면 자연스럽게 도태가 되어버리는 것이 현 상황인데, 한가지 기술로 너무나 오랫동안 욹어먹는 것 역시 현 IT 환경에서의 죄악 이지 아닐까 싶습니다.

수주에 따른 SI 개발이기 때문에 초기 Reference가 없다.

공적인 분야에서의 대규모 SI를 토대로 성장한 국내 IT 개발환경은 초기 Reference가 매우 중요합니다. 대부분 SQL을 그대로 사용하는 개발환경이 지금까지 이어져왔고, 이러한 개발환경에 대한 reference를 요구하기 때문에, 어쩔수 없이 myBatis를 이용하게 되는 것이 당연하게 이어져오고 있습니다.

이건 어떻게 하면 깰수 있을까요? 솔찍히 이 부분이 개인적으로는 가장 답이 안나옵니다. 공공부분 SI의 경우, reference를 요구하는 것이 당연하다고 생각됩니다. 그렇지만, 이러한 reference 요구에 의해서 신기술의 발전 및 보다 나은 기술의 채택부분이 막힌다면 이거 역시 문제가 아닐까 싶습니다. 점진적으로 해결해 나간다면, 개발자들이 최대한 ORM을 이용해서 공공부분 SI 이외의 현장에서 reference를 쌓는 것이 필요하다고 생각합니다. 지금까지 시장에셔와는 다르게 외부에서의 reference를 이용해서 공공부분에 들어가보는 것 이외에는 조금 답이 안나오는 것 아닌가 싶습니다.

기존 BL이 SQL로 구성되어 있기 때문에

가장 막막하고, 답답한 부분입니다. 기존의 로직이 SQL로 구성되어 있기 때문에 그 SQL을 사용하기 위해서 myBatis를 이용해야지 된다. 라는 의견입니다. 이에 대해서는 전 조금 다른 관점을 보고 싶습니다.

국내는 유독 차세대라는 개발 Project들이 많습니다. 해외에는 차세대라는 말을 붙일 수 있을 정도로 모든 시스템을 갈아엎는 Project들이 별로 없습니다.

그 이유가 무엇일까요?
해외는 대부분 점진적인 개량을 통해서 시스템을 항상 최신으로 유지를 시키거나, 계속적인 성능 개선을 해오고 있기 때문이라고 합니다. 국내의 사정은 어떤가요? 국내는 대부분 SI/SM으로 개발자의 직군이 나뉘게 됩니다. 전자는 주로 개발을, 후자는 주로 운영업무를 하게 됩니다. 그런데, 이 SM 업무의 경우에 대부분 신규로 무언가 개발을 하는 일을 막아버립니다. 업무로 인해서요. 개발이 필요한 상황이 있으면 개발팀에. 라는 것이 일반적인 상황이지요. 실리콘밸리의 새로운 trend라고 불리우고 있는 devOps의 경우, 이러한 용어로 만들어진 것은 얼마 안되었지만 기존까지 이런 식의 조직운영은 꾸준히 계속되어가고 있던것으로 알고 있습니다. 운영팀에서 보다 나은 개발방향을 위해서 시스템을 점진적으로 업그레이드 하고 변경시키는 과정을 이미 행하고 있는겁니다. 그렇지만, 국내의 SM환경은 대부분 어떻습니까? 대부분이 업무처리에 대한 전화상담 및 그에 따른 DB rollback, sql query문 작성에 대부분의 시간을 보내게 되는 것이 사실입니다.

이 문제는 제 개인적으로는 다음 문제와도 연결이 되고 있다고 생각합니다.

개발에 대한 사회적인 인식 문제

현 상황에서 우리 사회는 개발을 한번에 큰 돈을 들여서 하고, 그 다음에는 돈을 안쓰는 것이라고 인식하고 있습니다. 물론 개발에는 초창기 큰 돈이 듭니다. 그렇지만, 유지보수에 더욱더 많은 돈과 시간이 들게 되는 것또한 사실이지만, 국내에는 전혀 이런 사실이 먹히지 않고 있습니다. 유지보수 비용측정만봐도 알 수 있지요.

위 인식때문에, 개발을 할때 대부분이 외주개발자 또는 프리랜서들을 사용해서 대규모 프로젝트를 수행합니다. 그리고, 개발이 마쳐지면 이 사람들이 모두 이 업무에서 손을 떼게됩니다. 개발에 대한 내부적인 역량자체가 거의 없어지는거지요. 그리고 큰 돈을 들인 Application이 정상적으로 돌아가기 위한 산소호흡기만 붙인 상태로 유지를 시켜나가게 되는거지요. 점진적인 개선같은 것은 꿈도 못 꾸는 경우가 많습니다.

거기에다 우리나라는 자산에 대한 선호도가 매우 강합니다. 매우 잘 정제되고 훌륭한 Application이 아닌 시스템과 DB, 즉 자산에 해당되는 곳에는 매우 큰 돈을 쓰지만, 무형자산이라고 할 수 있는 Application에 대한 홀대는 매우 심각한 편입니다. 다른 이야기를 한다면, 이런 이유로 인하여 국내 기업환경에서 내부 데이터에 대한 Cloud는 매우 험난한 길을 가야지 될 것같다는 예상을 합니다. 국내 SI 개발환경에서 개발자들에게 투자한다는 이야기는 정말로 들어본적이 없습니다. 대부분 서버나 DB를 얼마나 비싼것을 샀는지를 자랑하는 기사들을 많이 봤지요.

솔찍히 이 문제는 국내의 천박한 자본주의를 보여주는 것이 아닌가도 싶습니다. 개발을 하는 사람들과 그 사람들의 노력이 귀한줄을 모르고 있는 것이 아닌가 싶어요.

마치면서…

ORM이 아닌 myBatis에 대한 의존 문제 자체가 개인적으로는 국내 IT 발전상황을 가로막는 일중 하나가 되지 않을까 생각하고 있습니다. 당장 open source로 무언가 새로운 web framework가 나오게 되면, 그 다음에 바로 나오는 것이 ORM입니다. DDD를 이용한 ORM modeling을 잘 할 줄 아는 사람들은 결국은 객체에 대한 이해가 좀더 나은 사람들이였던것이 제 개인적인 경험들이였습니다.

이제 ORM은 보다 더 나은 개발자가 되기 위한 조건이 아니라, 필수가 되어가고 있는 것 같은데… 국내 환경은 앞으로도 어떻게 되어갈까요.

Posted by Y2K
,

Bower는 maven, gradle을 이용한 java에서의 open source library의 버젼관리를 javascript client library에 옮겨온 개념입니다.

bower 소개

Bower는 maven, gradle과 비슷하게 bower.json과 .bowerrc 두개의 파일로 설정이 관리가 됩니다.

  • .bowerrc : Bower에서 source와 dist를 다운받을 위치를 지정합니다.
  • bower.json : Bower에서 다운받을 외부 component의 버젼 및 Project의 name, version을 지정합니다. 이는 maven, gradle에서 project와 비슷한 값을 지정하게 됩니다.

.bowerrc

{
  "directory": "components"
}

bower.json

{
  "name": "my-project",
  "version": "0.0.1",
  "dependencies": {
    "modernizr": "~2.6.2",
    "bootstrap": "~2.2.2",
    "angular-strap": "~0.7.0",
    "angular-ui" : "0.4.0",
    "ngInfiniteScroll" : "1.0.0",
    "angular-underscore" : "",
    "underscore" : "",
    "angular-bootstrap" : "~0.2.0",
    "font-awesome" : "3.0.2",
    "emoji" : "0.1.2"
  }
}

bower의 설치

bower는 node.js를 이용해서 작성되어있습니다. 먼저 node.js를 설치하는 것이 필요합니다. node.js의 설치방법은 너무나 많은 곳들에서 소개가 되고 있기 때문에 넘어가기로 하겠습니다.
Bower는 전역으로 사용되는 application이기 때문에, npm install -g를 이용해서 설치를 해야지 됩니다. 최종적으로 사용하기 위해서는 bower-installer 역시 같이 설치해주는 것이 좋습니다.

sudo npm install -g bower
sudo npm install -g bower-installer

bower의 활용

기본적으로 mvnrepository와 같이 사용할 library를 http://bower.io/search/ 에서 검색해서 찾는 것이 가능합니다. bower를 이용해서 library를 설치하기 위해서는 다음 command를 실행하면 됩니다.

bower install jquery

위는 jquery를 지정해서 설치하게 됩니다. 버젼이 따로 적혀있지 않는 경우, 가장 최신의 버젼을 다운받아 사용하게 됩니다. 특정하게 버젼을 지정하기 위해서는 다음과 같이 작성할 수 있습니다.

bower install jquery#1.11.0

설치와 bower.json 파일 기록을 동시에 할 수 있습니다.

bower install jquery#1.11.0 --save
bower install jquery#1.11.0 -s

bower-installer

bower를 통해서 component들을 설치하게 되면 source 코드와 min file 모두가 다운받게 됩니다. 그렇지만, 사용하게 될 component 들은 대다수 min file들만이 사용되게 됩니다. 이를 위해 설치한 component들의 min file 들만을 사용할 위치로 copy 해주는 tool이 bower-installer 입니다. bower-install는 bower.json에서 설정하며, 다음과 같이 설정할 수 있습니다.

    "install" : {
        "path" : {
            //"파일 확장자" : "copy될 위치"
            "css" : "src/main/webapp/lib/css",
            "js" : "src/main/webapp/lib/js",
            "woff" : "src/main/webapp/lib/fonts"
        },
        "sources" : {
            "component 이름" : [
                "copy 할 file 이름"
            ]
        }
    }

bower.json을 수정한 후, bower-installer를 실행하면 위치에 맞게 file이 모두 copy되는 것을 볼 수 있습니다.

bower와 gradle java war project의 연결

gradle을 이용한 war project의 경우, src/main/webapp에 사용되는 모든 code들과 javascript가 위치하게 됩니다. 여기 위치에 bower_component를 모두 다 다운받아서 처리하게 되면 url이 지저분해지는 경향이 있습니다. 이를 개발 방향에 맞게 정리해주는 것이 필요합니다. 이 때, bower-installer를 사용하면 원하는 directory 구조를 만들 수 있습니다.

제가 개인적으로 생각하는 좋은 구조는 다음과 같습니다.

project
- src
    - main
        - java
        - resources
        - webapp
    - test
        - java
        - resources
 .bowerrc
 .bower.json
 build.gradle
 setting.gradle

.bowerrc

{
    "directory" : "bower_components"
}

bower.json (bootstrap과 jquery를 설치한 상태입니다.)

{
    "name" : "bookstore-web",
    "version" : "0.0.0.1",
    "dependencies" : {
        "jquery" : "1.11.0",
        "bootstrap" : "3.1.1"
    },
    "install" : {
        "path" : {
            "css" : "src/main/webapp/lib/css",
            "js" : "src/main/webapp/lib/js",
            "eot" : "src/main/webapp/lib/fonts",
            "svg" : "src/main/webapp/lib/fonts",
            "ttf" : "src/main/webapp/lib/fonts",
            "woff" : "src/main/webapp/lib/fonts"
        },
        "sources" : {
            "jquery" : [
                    "bower_components/jquery/dist/jquery.min.js"
                ],
            "bootstrap" : [
                    "bower_components/bootstrap/dist/css/bootstrap.min.css",
                    "bower_components/bootstrap/dist/css/bootstrap-theme.min.css",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.eot",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.svg",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.woff",
                    "bower_components/bootstrap/dist/js/bootstrap.min.js"
                ]
        }
    }
}

위와 같이 설정해주면 bower-installer를 실행하면 src/main/webapp/lib 안에 모든 javascript component를 위치할 수 있게 됩니다. 그런데, 이 과정 역시 gradle에 추가하는 것이 보다 더 사용하기 편합니다. 다음과 같이 gradle task를 추가합니다.

import org.apache.tools.ant.taskdefs.condition.Os
task bowerInstaller(type:Exec) {
    if(Os.isFamily(Os.FAMILY_WINDOWS)) {
        commandLine 'cmd', 'bower-installer'
    } else {
        commandLine 'bower-installer'
    }
}

이제 gradle bowerInstaller commmand를 통해 bower-installer를 실행할 수 있습니다. bowerInstaller를 따로 실행시켜줘도 좋지만, war로 배포될 때 자동으로 포함될 수 있도록 war task에 dependency를 추가하도록 합니다.

war {
    dependsOn bowerInstaller
}

이제 gradle war를 통해서도 모든 component들을 같이 관리할 수 있습니다. gradle의 놀라운 확장성에 대해서 다시 한번 감탄하게 됩니다. ^^

마치면서

bower는 java에서의 maven, gradle과 같이 dependencies에 대한 관리 툴입니다. 따라서 기존 관행처럼 모든 javascript를 SCM에 올릴 필요가 더이상 없어집니다. 버젼 관리와 같이 개발되는 application에서 사용되고 있는 component에 대한 관리와 같이 maven, gradle을 사용할 때와 같은 매우 멋진 작업들이 가능하게 됩니다. 모두 bower를 한번 써봅시다. ^^

Posted by Y2K
,