Example code in GitHub
 
lyhcode.infoCodeData
http://www.codedata.com.tw/tag/groovy/
html.html {
    head {
        title("Groovy Servlet")
    }
    body {
        ul {
            ['Java', 'Groovy', 'Scala'].each {
                li("Hello, ${request.remoteHost}")
            }
        }
    }
}<html>
<head>
    <title>GSP</title>
</head>
<body>
    <ul>
    <% ['Java', 'Groovy', 'Scala'].each { %>
        <li>Hello, ${it}</li>
    <% } %>
    </ul>
</body>
</html> 
in
Groovy programming language
for
Java developers
Don't Reinvent the Wheel
(Grails 2.4.3)
Model
View Decorator
 
http://localhost:8080/user/forgetPassword/103
class UserController {
    def forgetPassword(User user) {
        sendMail {
            to user.email
            subject "Forget Your Password?"
            body "Your password is ${user.password}"
        }
        respond user
    }
}domain-specific runtime environments

使用 GVM 管理開發環境
http://gvmtool.net
# Install GVM
curl -s get.gvmtool.net | bash
# Install Grails
gvm install grailsgrails create-app realestategrails-appweb-appwrappersrctestscriptslibapplication.propertiesgrails run-app
Server running. Browse to
http://localhost:8080/realestate
房屋 {
    縣市;
    鄉鎮區;
    地址;
    坪數;
    售價;
    完成日期;
}grails create-domain-class houseDomain Class
grails-app/domain/realestate/House.groovy
Unit Test
test/unit/realestate/HouseSpec.groovy
class House {
    String city
    String region
    String address
    float feet
    int price
    Date buildDate
}@TestFor(House)
class HouseSpec extends Specification {
    void "create a house"() {
        given:
        def house = new House(
                city: 'Taichung',
                region: 'Taiping',
                address: 'Yu Cai Rd. No. 440',
                feet: 45,
                price: 960,
                buildDate: new Date()
        )
        
        when:
        house.save(flush: true)
        
        then:
        House.countByCity('Taichung') > 0
    }
}grails test-appBrowse to
target/test-reports/html/index.html
grails shell$ grails generate-all realestate.House$ grails run-appBrowse to
http://localhost:8080/realestate/house
grails-app/conf/UrlMappings.groovy
class UrlMappings {
    static mappings = {
        "/$controller/$action?/$id?(.$format)?"{
            constraints {
                // apply constraints here
            }
        }
    }
}http://localhost:8080/realestate/house/index.json
http://localhost:8080/realestate/house/index.xml
http://localhost:8080/realestate/house/show/1.json
http://localhost:8080/realestate/house/show/1.xml
a framework that
focuses on providing both authentication and authorization
to Java applications.
plugins {
    // Spring Security Core Plugin
    compile ":spring-security-core:2.0-SNAPSHOT"
}grails s2-quickstart realestate User Role// Added by the Spring Security Core plugin:
grails.plugin.springsecurity.userLookup.userDomainClassName = 'realestate.User'
grails.plugin.springsecurity.userLookup.authorityJoinClassName = 'realestate.UserRole'
grails.plugin.springsecurity.authority.className = 'realestate.Role'
grails.plugin.springsecurity.controllerAnnotations.staticRules = [
    '/**':                            ['permitAll'],
    '/index':                         ['permitAll'],
    '/index.gsp':                     ['permitAll'],
    '/assets/**':                     ['permitAll'],
    '/**/js/**':                      ['permitAll'],
    '/**/css/**':                     ['permitAll'],
    '/**/images/**':                  ['permitAll'],
    '/**/favicon.ico':                ['permitAll']
]def roleUser = new Role(authority: 'ROLE_USER')
def roleAdmin = new Role(authority: 'ROLE_ADMIN')
roleUser.save flush: true
roleAdmin.save flush: true
def admin = new User(
        username: 'admin',
        password: 'admin',
        enabled: true,
        accountExpired: false,
        accountLocked: false,
        passwordExpired: false
)
admin.save flush: true
UserRole.create(admin, roleUser, true)
UserRole.create(admin, roleAdmin, true)grails-app/conf/BootStrap.groovy
class BootStrap {
    def init = { servletContext ->
        //...
    }
    def destroy = {
    }
}grails-app/conf/DataSource.groovy
dialect = realestate.ImprovedH2Dialectsrc/groovy/realestate/ImprovedH2Dialect.groovy
public class ImprovedH2Dialect extends H2Dialect {
    @Override
    public String getDropSequenceString(String sequenceName) {
        return "drop sequence if exists " + sequenceName;
    }
    @Override
    public boolean dropConstraints() {
        return false;
    }
}<sec:ifLoggedIn>...</sec:ifLoggedIn><sec:username />grails-app/views/index.gsp
<sec:ifLoggedIn>
    <g:link uri="/j_spring_security_logout">Logout</g:link>
    <h1>Hello, <sec:username />. Welcome to Grails</h1>
</sec:ifLoggedIn>
<sec:ifNotLoggedIn>
    <g:link controller="login" action="auth">Login</g:link>
    <h1>Welcome to Grails</h1>
</sec:ifNotLoggedIn>for controller or actions
@Secured(["ROLE_ADMIN"])grails-app/conf/BuildConfig.groovy
    dependencies {
        // OAuth required scribe framework
        runtime "org.scribe:scribe:1.3.5"
    }
    plugins {
        // Spring Security Core Plugin
        compile ":spring-security-core:2.0-SNAPSHOT"
        // Spring Security OAuth2
        compile (":oauth:2.5") { exclude "scribe" }
        compile ":spring-security-oauth:2.1.0-RC4"
        compile ":spring-security-oauth-facebook:0.2"
    }grails s2-init-oauth realestate OAuthIDAdd relationship to User domain class.
static hasMany = [oAuthIDs: OAuthID]grails-app/conf/Config.groovy
// OAuth Facebook
oauth {
    providers {
        facebook {
            key = '363532783795859'
            secret = '----App Secret----'
        }
    }
}~/.grails/realestate-config.groovy
oauth.providers.facebook.secret = '....'grails.serverURL = "http://dev.teamcollab.org:8080/realestate"grails-app/views/index.gsp
<oauth:connect provider="facebook">Sign-in with Facebook</oauth:connect>plugins {
    // Twitter Bootstrap
    runtime ":twitter-bootstrap:3.2.0.2"
}plugins {
    compile ":asset-pipeline:1.9.6"
    // Uncomment these to enable additional asset-pipeline capabilities
    //compile ":sass-asset-pipeline:1.9.0"
    //compile ":less-asset-pipeline:1.10.0"
    //compile ":coffee-asset-pipeline:1.8.0"
    //compile ":handlebars-asset-pipeline:1.3.0.3"
}grails-app/assets/javascripts/application.js
//= require bootstrapgrails-app/assets/stylesheets/application.css
/*
*= require bootstrap
*/Apply a "layout" for views.
<html>
<head>
    <meta name="layout" content="main" />
    <title>Page Title</title><!DOCTYPE html>
<html>
<head>
    <title><g:layoutTitle default="Untitled" /></title>
    <g:layoutHead/>
</head>
<body>
    <g:layoutBody/>
</body>
</html>class BootstrapTagLib {
    static namespace = "bs3"
    def alert = { attrs, body ->
        out << "<div class=\"alert alert-${attrs.type?:'success'}\">"
        out << body()
        out << "</div>"
    }
}<bs3:alert><b>Well done!</b> You successfully ...</bs3:alert>plugins {
    // Elasticsearch
    compile ":elasticsearch:0.0.3.3"
}elasticSearch {
    datastoreImpl = "hibernateDatastore"
    date.formats = [
            "yyyy-MM-dd'T'HH:mm:ss'Z'"
    ]
    client.hosts = [
            [host: 'localhost', port: 9300]
    ]
    defaultExcludedProperties = ["password"]
    disableAutoIndex = false
    bulkIndexOnStartup = true
    maxBulkRequest = 500
    searchableProperty.name = 'searchable'
}environments {
    development {
        elasticSearch.client.mode = 'local'
    }
    test {
        elasticSearch {
            client.mode = 'local'
            index.store.type = 'memory'
        }
    }
    production {
        elasticSearch.client.mode = 'node'
        //elasticSearch.client.mode = 'local'
    }
}class House {
    static searchable = true
    //...
}def search() {
    render House.search(params.q) as JSON
}/house/search?q=KEYWORDS
dependencies {
    test "org.seleniumhq.selenium:selenium-firefox-driver:2.42.2"
    test "org.gebish:geb-spock:0.9.3"
}plugins {
    test ":geb:0.9.3"
}grails test-app functional:spockgrails war