아파치 그루비
1. 개요
아파치 그루비는 자바 가상 머신(JVM)에서 실행되는 동적 타이핑 프로그래밍 언어이다. 2002년 제임스 스트라찬에 의해 개발되었으며, 자바의 강점과 파이썬, 루비, 스몰토크 등의 특징을 결합했다. 그루비는 자바 코드와 호환되며, 간결한 문법과 다양한 기능을 제공하여 웹 애플리케이션 개발, 빌드 자동화, 지속적 통합, 테스트 코드 작성 등 다양한 분야에서 활용된다. 특히, 그레일스 프레임워크와 Gradle 빌드 도구를 통해 한국에서 널리 알려졌으며, 젠킨스 파이프라인 스크립트 작성에도 사용된다.
-
2003년 도입 -
게임보이 어드밴스 SP
닌텐도가 2003년에 출시한 게임보이 어드밴스 SP는 휴대성을 높인 접이식 디자인과 프론트라이트 또는 백라이트 액정을 특징으로 하며 게임보이, 게임보이 컬러, 게임보이 어드밴스 게임을 지원하는 휴대용 게임기이다. -
2003년 도입 -
빅 립
빅 립은 팬텀 에너지가 주도하는 우주의 가속 팽창으로 인해 모든 물질이 붕괴되는 종말 시나리오로, 우주 팽창 속도 증가에 따라 상호작용이 불가능해지는 현상을 예측하며, 암흑 에너지 상태 방정식 매개변수 'w' 값에 따라 발생 시점이 결정되지만 현재 관측 데이터로는 불확실하다. -
2003년 개발된 프로그래밍 언어 -
스칼라 (프로그래밍 언어)
스칼라는 마틴 오더스키가 설계한 객체 지향 및 함수형 프로그래밍 언어이며, 자바 플랫폼에서 실행되고 자바 코드와 상호 운용이 가능하며, 아파치 스파크 등 다양한 곳에서 활용된다. -
2003년 개발된 프로그래밍 언어 -
스크래치 (프로그래밍 언어)
스크래치는 MIT 미디어 연구소에서 개발한 어린이 및 입문자를 위한 시각적 프로그래밍 언어이며, 블록 기반 방식을 통해 애니메이션, 게임, 스토리 등을 만들 수 있도록 설계되었고, 코드 공유 및 재사용을 장려하며 다양한 교육 환경에서 활용된다. -
자바 프로그래밍 언어 계열 -
코틀린 (프로그래밍 언어)
코틀린은 젯브레인즈에서 개발한 정적 타입 언어로, 자바 가상 머신에서 동작하며 자바와의 호환성을 갖고, 안드로이드 공식 지원 언어로 채택되어 다양한 분야에서 활용되고 있으며, 이름은 러시아의 코틀린 섬에서 유래되었다. -
자바 프로그래밍 언어 계열 -
스칼라 (프로그래밍 언어)
스칼라는 마틴 오더스키가 설계한 객체 지향 및 함수형 프로그래밍 언어이며, 자바 플랫폼에서 실행되고 자바 코드와 상호 운용이 가능하며, 아파치 스파크 등 다양한 곳에서 활용된다.
2. 역사
2002년 영국 출신의 프로그래머 제임스 스트라칸이 그루비를 창시했으며, 현재 [http://www.apache.org/licenses/LICENSE-2.0 아파치 사용 허가서 2.0] 아래에 소스와 바이너리가 배포, 공개되어 있다.
제임스 스트라찬(James Strachan)은 2003년 8월 자신의 블로그에서 그루비 개발에 대해 처음 언급했다. 2004년 3월 29일 JSR 241로 JCP에 제출되어 승인되었다. 2004년부터 2006년까지 여러 버전이 출시되었다. JCP 표준화 작업이 시작된 후 버전 번호가 변경되었고, 2007년 1월 2일 "1.0" 버전이 출시되었다. 이후 1.1 버전의 베타 및 릴리스 후보를 거쳐 2007년 12월 7일 그루비 1.1 Final이 출시되었으나, 변경 사항을 반영하여 즉시 그루비 1.5로 번호가 변경되었다.
2007년 그루비는 JAX 2007 혁신상을 수상했다. 2008년에는 그루비 웹 프레임워크인 그레일스가 JAX 2008 혁신상 2위를 수상했다.
2008년 11월, 스프링소스(SpringSource)는 그루비 및 그레일스 회사(G2One)를 인수했고, 2009년 8월 VMware가 스프링소스를 인수했다.
2012년 4월, 8년 동안 활동이 중단된 후 JSR 241은 휴면 상태로 변경되었다. 스트라찬은 2007년 그루비 1.0 릴리스 1년 전에 프로젝트를 떠났다. 2016년 10월, 스트라찬은 "나는 여전히 그루비(젠킨스 파이프라인은 매우 그루비합니다!), 자바, 고, 타입스크립트 및 코틀린을 사랑합니다"라고 언급했다.
2012년 7월 2일, 그루비 2.0이 출시되었으며, 정적 컴파일 및 정적 타입 검사 기능이 추가되었다.
2013년 4월, EMC Corporation (EMC)와 VMware가 합작하여 피보탈 소프트웨어(Pivotal Software)를 분사했을 때, 그루비와 그레일스는 제품 포트폴리오의 일부를 형성했다. 2015년 4월부터 피보탈은 그루비와 그레일스에 대한 후원을 중단했다. 같은 달, 그루비는 관리 구조를 Codehaus 저장소에서 아파치 소프트웨어 재단(Apache Software Foundation)의 인큐베이터를 거쳐 프로젝트 관리 위원회(PMC)로 변경했다. 2015년 11월 그루비는 아파치 인큐베이터를 졸업하고 최상위 수준의 프로젝트가 되었다.
2020년 2월 7일, 그루비 3.0이 출시되었다. 2022년 1월 25일에는 버전 4.0이 출시되었다.
2.1. 한국에서의 그루비
그루비는 대한민국에서는 주로 Grails 프레임워크와 Gradle 빌드 도구를 통해 알려지기 시작했다. 링크드인, Sky.com 등 해외 기업뿐만 아니라 국내 기업에서도 점차 그루비와 그레일스를 도입하는 사례가 늘고 있다. 특히, 젠킨스 파이프라인 스크립트를 그루비로 작성할 수 있게 되면서, 데브옵스(DevOps) 환경에서의 활용도가 높아지고 있다.
3. 특징
* 자바 가상 머신(JVM)에서 작동하는 동적 타이핑 프로그래밍 언어이다.
* 자바의 장점을 기반으로 파이썬, 루비, 스몰토크 등의 프로그래밍 언어에서 영향을 받은 특징들을 추가하였다.
* 자바 프로그래머들이 큰 학습 없이도 최신 프로그래밍 기법을 사용할 수 있도록 돕는다.
* 도메인 전문 언어와 단순화된 문법을 지원하여 코드 가독성과 유지 보수성을 향상시킨다.
* LISP 언어의 클로저를 도입하여 메타프로그래밍을 지원한다.
* 자바 가상 머신(JVM)에서 동작하는 동적 스크립팅 언어인 Jython, JRuby 등과 비교해도 손색이 없으며, 짧은 역사에도 불구하고 빠르게 발전하고 있다.
* 대부분의 유효한 자바 파일은 유효한 그루비 파일이기도 하다. 그루비 코드는 자바보다 더 간결하게 작성할 수 있다. 이는 자바 프로그래머가 익숙한 자바 구문으로 시작하여 점진적으로 그루비를 배울 수 있게 해준다.
* 자바에 없는 그루비 기능은 다음과 같다.
* 정적 및 동적 타이핑 ( `def` 키워드 사용)
* 연산자 오버로딩
* 리스트 및 연관 배열(맵)을 위한 기본 구문
* 정규 표현식 기본 지원
* 다형성 반복
* 문자열 인터폴레이션
* 추가 헬퍼 메서드
* 널 포인터 자동 확인 세이프 내비게이션 연산자 `?.` (예: `variable?.method()`)
* 버전 2부터 모듈성, 타입 검사, 정적 컴파일, 프로젝트 코인 구문 개선, 멀티캐치 블록, Java 7 `invokedynamic` 명령어를 사용한 성능 향상을 지원한다.
* XML 및 HTML 등 다양한 마크업 언어에 대한 기본 지원을 제공한다.
* 그루비는 객체 지향 프로그래밍 언어이지만, 함수형 프로그래밍 기능도 제공한다.
* 자바와의 직접적인 연계를 특징으로 한다. 예를 들어 그루비에서 모든 Java SE API 및 자바로 작성된 임의의 서드 파티 컴파일된 라이브러리 등을 호출할 수 있다.
* 그루비는 동적인 언어이며, 스크립트를 바로 실행할 수 있다.
* 그루비는 자바와 매우 친화성이 높고, 자바 기술로 축적된 개발 인프라 및 라이브러리, 노하우, 도구, JVM 최적화 기술 등의 많은 부분을 그대로 활용할 수 있다.
그루비(Groovy) 언어의 특징은 다음과 같다.
| 특징 | 설명 |
|---|---|
| 변수 타입 선언 불필요 | 일반적인 경우 `Object` 형으로 취급되며, 메서드 호출은 동적 디스패치로 해결. (정적/동적 스타일 선택 가능) |
| 메서드 호출 괄호 생략 가능 | 메서드 호출 시 괄호를 생략할 수 있다. |
| 행 끝 세미콜론 생략 가능 | 행 끝의 세미콜론을 생략할 수 있다. |
| 리스트/맵 초기화 내장 구문 | 리스트나 맵 초기화를 위한 내장 구문을 제공한다. |
| 연산자 오버로딩 정의 가능 | 사용자 정의 연산자를 정의할 수 있다. |
| 검사 예외 처리 간소화 | 검사 예외가 `throws`로 선언된 메서드를 호출할 때, `try`-`catch`로 감싸거나 호출 측 메서드를 `throws`로 선언할 필요가 없다. |
| 원시 타입 참조 타입 취급 | 원시 타입을 참조 타입처럼 취급할 수 있다. (명시적 변환 불필요) |
| 조건절 간소화 | if 문, while 문, 삼항 연산자 조건절에서 0이나 null 값은 거짓으로 취급. |
| 정규 표현식 처리 내장 연산자 | J2SE 정규 표현식 클래스 처리를 위한 내장 연산자(`=~`, `==~` 등) 제공. |
| 문자열 내 식 삽입 | 문자열 상수 안에 임의의 Groovy 식을 삽입 가능 (GString, `${}` 표기법). |
| 이름 지정 인자 사용 | 이름 지정 인자를 사용한 메서드 호출이 가능하다. |
| 접근 제한자 기본값 | 접근 제한자의 기본값은 `public`이다. |
| 패키지 자동 임포트 | `java.lang`, `java.io`, `java.math`, `java.net`, `java.util`, `groovy.lang`, `groovy.util` 등 주요 패키지는 자동 임포트된다. |
| `GroovyObject` 인터페이스 구현 | groovy 파일에서 정의된 클래스는 `GroovyObject` 인터페이스를 자동 구현. |
클로저 사양 및 표기, 예약어 등에서 루비의 영향을 확인할 수 있다. 파이썬, 딜런, 스몰토크 등에서도 영향을 받았다.
4. 철학
제임스 스트라칸은 그루비가 유연하고 간결한 동적 프로그래밍 언어라고 강조한다.
5. 자바와의 비교
대부분의 자바 코드는 그루비 코드와 호환된다. 그루비는 자바에 비해 더 간결하고 표현력이 풍부한 코드를 작성할 수 있다. 예를 들어, 세이프 네비게이션 연산자(?.)를 통해 널 포인터 예외(NullPointerException)를 방지할 수 있다.
표준 Java 5+의 예제는 다음과 같다.
public class StdJava
{
public static void main(String argv[])
{
for (String it : new String [] {"Rod", "Carlos", "Chris"})
if (it.length() <= 4)
System.out.println(it);
}
}
그루비를 사용하면 아래처럼 간단하게 표현할 수 있다.
["Rod", "Carlos", "Chris"].findAll{it.size() <= 4}.each{println it}
대부분의 유효한 자바 파일은 유효한 그루비 파일이기도 하다. 두 언어는 유사하지만, 그루비 코드는 자바가 필요로 하는 모든 요소를 필요로 하지 않기 때문에 더 간결할 수 있다. 이는 자바 프로그래머가 익숙한 자바 구문으로 시작하여 더 많은 그루비 프로그래밍 관용구를 습득함으로써 점진적으로 그루비를 배울 수 있게 해준다.
자바에는 없는 그루비의 기능은 다음과 같다.
* 정적 및 동적 타이핑 (def 키워드 사용)
* 연산자 오버로딩
* 리스트 및 연관 배열(맵)을 위한 기본 구문
* 정규 표현식에 대한 기본 지원
* 다형성 반복
* 문자열 인터폴레이션
* 추가된 헬퍼 메서드
* 널 포인터를 자동으로 확인하는 세이프 내비게이션 연산자 ?. (예: variable?.method() 또는 variable?.field())
버전 2부터 그루비는 모듈성, 타입 검사, 정적 컴파일, 프로젝트 코인 구문 개선, 멀티캐치 블록, Java 7에 도입된 `invokedynamic` 명령어를 사용한 지속적인 성능 향상을 지원한다.
그루비는 같은 런타임 시스템을 공유하는 자바 코드의 다른 표기법이라고 생각할 수도 있다.
6. 문법
그루비의 문법은 자바를 기반으로 하며, 자바 프로그래머에게 친숙하다. 하지만 그루비는 스크립트 언어로서 더욱 간결한 표현을 허용한다. 다음은 그루비 언어의 주요 특징들이다.
* 변수 타입 선언은 필수가 아니다.
* 선언하지 않으면, `Object` 형으로 취급되며, 메서드 호출은 동적 디스패치로 처리된다. (변수 타입 선언도 가능하며, 정적/동적 스타일에 따라 적절히 사용 가능). 메서드 인자나 반환 값 타입 선언도 마찬가지이다.
* `@TypeChecked` 사용 시: 변수 타입 미지정 시 타입 추론되며, 타입 정확성이 확인된다. 메서드 호출은 여전히 동적 디스패치로 처리된다.
* `@CompileStatic` 사용 시: 위 경우에 더해, 메서드 호출이 정적으로 처리된다. `@ToString`과 같은 컴파일 시점 메서드 생성도 정상적으로 사용 가능하다. 이 세 가지 중 가장 빠르다.
* 메서드 호출 시 괄호를 생략할 수 있다.
* 행 끝의 세미콜론(;)은 생략 가능하다.
* 리스트 및 맵 초기화를 위한 내장 구문을 제공한다.
* 연산자 오버로딩 정의 (사용자 정의 연산자)가 가능하다.
* 내장형으로 리스트나 맵을 처리할 수 있으며, 이를 위한 리터럴 표기법과 연산자가 정의되어 있다.
* `BigDecimal`, `BigInteger` 형 등에 대한 사칙 연산이 오버로딩되어 있다.
* 검사 예외가 `throws`로 선언된 메서드 호출 시, `try`-`catch`로 감싸거나 호출 측 메서드를 `throws`로 선언할 필요가 없다.
* 원시 타입은 참조 타입처럼 취급 가능하다 (명시적 변환 불필요).
* if 문, while 문, 삼항 연산자 (`c?x:y`)의 조건절에서 0이나 null 값은 거짓으로 간주된다 ( `boolean` 형일 필요 없음).
* J2SE 정규 표현식 클래스 처리를 위한 내장 연산자 (`=~`, `==~` 등)가 제공된다. 구문상으로도 특별 취급되어 Perl, Ruby와 유사하게 사용 가능하다.
* 문자열 상수 내에 임의의 Groovy 식을 삽입할 수 있다. `${}` 표기법을 사용하며, 이를 GString이라 부른다. 변수명인 경우 중괄호도 생략 가능하며, `"$변수명"` 형식으로 변수 값을 문자열에 삽입할 수 있다.
* 이름 지정 인자를 사용한 메서드 호출이 가능하다.
* 접근 제한자의 기본값은 `public`이다.
* `java.lang`, `java.io`, `java.math`, `java.net`, `java.util`, `groovy.lang`, `groovy.util`은 명시적으로 지정하지 않아도 암묵적으로 임포트된다.
* groovy 파일에서 정의된 클래스는 `GroovyObject` 인터페이스를 암묵적으로 구현하며, 클래스 외부에서 정의된 필드나 메서드는 `Script` 추상 클래스 구현 클래스의 필드나 메서드로 간주된다.
* switch 문은 임의 타입에 대해 분기 가능하도록 확장되었다 (타입 스위치).
다음은 GString을 사용한 문자열 처리 예시이다.
```groovy
BigDecimal account = 10.0
def text = "계좌 잔액은 현재 $account입니다"
assert text == "계좌 잔액은 현재 10.0입니다"
```
변수와 표현식을 포함하는 GString은 큰따옴표를 사용하여 선언해야 한다. 복잡한 표현식은 중괄호로 묶어야 한다.
```groovy
BigDecimal minus = 4.0
text = "계좌 잔액은 현재 ${account - minus}입니다"
assert text == "계좌 잔액은 현재 6.0입니다"
// 표현식을 격리하기 위해 중괄호가 없으면 다음과 같은 결과가 발생합니다:
text = "계좌 잔액은 현재 $account - minus입니다"
assert text == "계좌 잔액은 현재 10.0 - minus입니다"
6.1. 제어 구조
그루비는 `if`, `while`, `for` 등의 제어문을 사용할 수 있다. `switch` 문은 임의의 타입에 대해 분기할 수 있도록 확장되었다. 클로저와 반복자를 활용하여 반복 처리를 간결하게 표현할 수 있다.
groovy> if ("fablic".length() > 3)
groovy> println 'ya'
groovy> else
groovy> println 'nop'
ya
groovy> def n = 0
groovy> while (n < 3){
groovy> println n++
groovy> }
0
1
2
일부 제어구조는 반복자(iterator)로 대신할 수 있다.
* 리스트의 각 요소로 반복 처리
* 클로저를 사용하지 않는 경우
groovy> def list = [1, 2, 5, 13, 21]
groovy> def n = 0
groovy> while (n < list.size()) {
groovy> list[n++] *= 2
groovy> }
groovy> list
Result: [2, 4, 10, 26, 42]
* 클로저를 사용한 경우
groovy> def list = [1, 2, 5, 13, 21]
groovy> list.collect { it*2 }
Result: [2, 4, 10, 26, 42]
* 지정된 횟수 만큼 반복 처리
groovy> 3.times { println( 'foobar' ) }
foobar
foobar
foobar
switch 문은 임의의 타입에 대해 분기할 수 있도록 확장되었다(타입 스위치)。
switch (value) {
case "Hello":
println "value == 'Hello'"
break
case String:
println "value는 String 타입"
break
case 1..12:
println "value는 1부터 12 사이"
break
default:
println "그 외"
}
일반적인 for 문과 for in이 있다. 둘 다 break 문이나 continue 문을 사용할 수 있다. `@CompileStatic`을 붙인 상태에서는 C 언어 스타일의 for 루프이며, 루프 변수에 타입을 지정한 상태가 가장 빠르며, 자바 언어와 동등한 속도로 작동한다. `each`나 `times`는 클로저 호출에 시간이 걸린다.
for (int i = 0; i < 3; i++) { println "$i: Hello" }
for (i in 1..3) { println "$i: Hello" }
(1..3).each { println "$it: Hello" }
3.times { println "$it: Hello" }
6.2. 클래스
groovy
class Pojo {
def name
}
def pojo = new Pojo(name:"이름")
println pojo.getName() // getName()이 생성됨
println pojo.name // getName()이 호출됨
```
Groovy 코드는 클래스 정의 안에 있을 필요 없이, 클래스 정의 외부(최상위 레벨)에서 메서드를 정의하거나 실행문을 작성하는 것이 가능하다.
예시: 파일명이 HelloTest.groovy인 경우
```groovy
println "Hello, World!"
```
위 코드는 아래와 같은 의미이다.
```groovy
class HelloTest {
public HelloTest() {
println "Hello, World!"
}
public static void main(String[] args) {
new HelloTest()
}
}
```
Getter 및 Setter 메서드는 자동으로 생성된다. 필드 접근 표기법으로 Getter, Setter 메서드를 호출할 수 있다.
기본 인수 (메서드/생성자 호출 체인의 자동 생성) 예시:
```groovy
def greet(mess = "Hello World") {
println mess
}
greet()
greet("foo")
```
출력:
```text
Hello World
foo
6.3. MOP (Meta Object Protocol)
`ExpandoMetaClass`를 사용하면 Ruby의 오픈 클래스 개념과 유사하게, 실행 시간에 동적으로 메서드나 속성을 추가할 수 있다. 이는 도메인 특화 언어 (DSL)를 쉽게 만들 수 있게 해준다.
```groovy
Number.metaClass {
sqrt = { Math.sqrt(delegate) }
}
assert 9.sqrt() == 3
assert 4.sqrt() == 2
```
`Expando`를 사용하면 구현되지 않은 필드 참조, 대입, 메서드 호출을 처리할 수 있다.
```groovy
def obj = new Expando()
obj.greetingMessage = "Hello World"
obj.greet = { println greetingMessage }
obj.greet()
obj.message = "foo"
println obj.message
```
`use` 블록을 사용하면 특정 클래스의 클래스 메서드로 처리를 위임할 수 있다.
```groovy
import groovy.inspect.Inspector
use (Category.class) {
def obj = "Hoge"
println obj.getShortClassName()
println obj.toString()
}
// 이름은 자유.
class Category {
// 첫 번째 인수는, 메서드가 호출된 인스턴스의 참조 복사.
static getShortClassName(obj) {
Inspector.shortName(obj.getClass())
}
// 구현 메서드와 중복되는 경우, Groovy는 카테고리보다 구현 메서드를 우선시한다.
static String toString(Object obj) {
"Hello World"
}
}
```
위 코드는 다음과 같이 출력된다.
```text
String
Hoge
6.4. GroovyMarkup
그루비는 인라인 문서 객체 모델 (DOM) 구문을 통해 XML 및 HTML과 같은 다양한 마크업 언어에 대한 기본 지원을 제공한다. 이 기능을 통해 많은 유형의 이종 데이터 자산을 통일되고 간결한 구문과 프로그래밍 방법론으로 정의하고 조작할 수 있다.
빌더 패턴을 사용하여 데이터 구조를 덜 장황하게 생성할 수 있다. 예를 들어, 다음 XML은
```xml
```
다음 그루비 코드를 통해 생성할 수 있다.
```groovy
def writer = new StringWriter()
def builder = new groovy.xml.MarkupBuilder(writer)
builder.languages {
language(year: 1995) {
name "Java"
paradigm "object oriented"
typing "static"
}
language (year: 1995) {
name "Ruby"
paradigm "functional, object oriented"
typing "duck typing, dynamic"
}
language (year: 2003) {
name "Groovy"
paradigm "functional, object oriented"
typing "duck typing, dynamic, static"
}
}
```
또한 `StreamingMarkupBuilder`를 통해 스트리밍 방식으로 처리할 수 있다. 구현을 JSON으로 변경하려면 `MarkupBuilder`를 `JsonBuilder`로 바꿀 수 있다.
기능적 언어를 구문 분석하고 검색하기 위해, 그루비의 `findAll` 메서드를 사용할 수 있다.
```groovy
def languages = new XmlSlurper().parseText writer.toString()
// 여기서는 매처(=~)에 대한 그루비의 정규식 구문이 사용되며, 이는
// boolean 값으로 강제 변환됩니다. 즉, 값이 문자열을 포함하면 true, 그렇지 않으면 false입니다.
def functional = languages.language.findAll { it.paradigm =~ "functional" }
assert functional.collect { it.name } == ["Groovy", "Ruby"]
```
Groovy 코드를 사용하여 Groovy의 기능(클로저 및 동적 메서드 추가)을 활용하여 트리 데이터 구조를 구축한다. 구체적으로, 새 노드 추가를 메서드 호출로 정의하고, 해당 새 노드의 자식 노드 그룹의 설명을 메서드로 전달하는 클로저로 정의한다. 해당 클로저에는 해당 자식 노드를 위한 일련의 노드 추가 메서드 호출을 포함할 수 있다. 이처럼 재귀적으로 작성하며, 이때 Groovy의 루프문이나 if문 등의 제어 구조를 포함한 모든 Groovy 언어 기능을 사용할 수 있다.
GroovyMarkup은 직관적으로는 XML만큼 정적이지는 않지만, 순수한 프로그램 코드 열보다는 선언적인, "다소 선언적인 데이터 설명"이라고 할 수 있다.
GroovyMarkup은 기본적인 기능이며, GroovyMarkup을 사용한 구체적인 라이브러리로는, Swing의 GUI 컴포넌트의 조립을 수행하는 SwingBuilder, DOM과 같은 XML 데이터 구조를 조립하는 MarkupBuilder 등이 있다.
```groovy
import groovy.xml.MarkupBuilder
class Main {
static void main(array) {
Writer writer = new StringWriter()
writer.println("")
writer.println()
def builder = new MarkupBuilder(writer)
/*
이름이 루트 태그 이름인 메서드
인수가 맵인 경우 태그의 속성
인수가 문자열인 경우 텍스트 노드의 내용이며 HTML 이스케이프된다.
미구현 메서드를 처리하는 GroovyObject#invokeMethod(String methodName, Object methodParameter)를 이용
메서드의 괄호가 생략되었다.
*/
builder.html(xmlns:"http://www.w3.org/1999/xhtml", "xml:lang":"ja") { //이후는 이름이 태그 이름인 클로저
/*
인수가 클로저인 경우 이름이 태그 이름
인수가 맵인 경우 태그의 속성
인수가 문자열인 경우 텍스트 노드의 내용이며 HTML 이스케이프된다.
*/
head() {
}
body() {
div("1행目");
div("2행目");
//히어 도큐먼트 구문
String string = """
"""
pre(string) {
}
}
}
println writer.toString()
/*
표준 출력 결과
<div id='3'>3행目</div>
<div id='4'>4행目</div>
*/
}
}