1128 ~ 1211


# 1128 ~ 1211

# 1201 - Spring REST Docs + Rest Assured

[ build.gradle ]

plugins {
    ...
    // Asciidoctor ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ ์šฉ
    id "org.asciidoctor.jvm.convert" version "3.3.2"
    ...
}

configurations {
    ...
    // asciidoctorExtAsciidoctor๋ฅผ ํ™•์žฅํ•˜๋Š” ์ข…์†์„ฑ์— ๋Œ€ํ•œ ๊ตฌ์„ฑ์„ ์„ ์–ธ
    asciidoctorExt
    ...
}

dependencies {
    ...
    // asciidoctorExt : spring-restdocs-asciidoctor ๊ตฌ์„ฑ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€
    // snippets์„ .adoc๋กœ ๊ฐ€๋ฆฌํ‚ค๊ณ  build/generated-snippets ์ƒ์„ฑ๋˜๋„๋ก ์ž๋™์œผ๋กœ ๊ตฌ์„ฑ 
    asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
    // spring-restdocs-restassured ์˜์กด์„ฑ์„ ์ถ”๊ฐ€
    testImplementation 'org.springframework.restdocs:spring-restdocs-restassured'
    ...
}

ext {
    ...
    // ์ƒ์„ฑ๋œ ์Šค๋‹ˆํŽซ์˜ ์ถœ๋ ฅ ์œ„์น˜๋ฅผ ์ •์˜ํ•˜๋„๋ก ์†์„ฑ์„ ๊ตฌ์„ฑ
    set('snippetsDir', file("build/generated-snippets"))
    ...
}

tasks.named('test') {
    outputs.dir snippetsDir
    useJUnitPlatform()
}


tasks.named('asciidoctor') {
    inputs.dir snippetsDir
    // 	asciidoctorExtํ™•์žฅ ์— ๋Œ€ํ•œ ๊ตฌ์„ฑ ์‚ฌ์šฉ์„ ๊ตฌ์„ฑ
    configurations 'asciidoctorExt'
    dependsOn test
}

tasks.named('bootJar') {
    dependsOn asciidoctor
    // ์ƒ์„ฑ๋œ ๋ฌธ์„œ๋ฅผ jar static/docs๋””๋ ‰ํ† ๋ฆฌ์— ๋ณต์‚ฌ
    copy {
        from "${asciidoctor.outputDir}"
        into 'src/main/resources/static/docs'
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

[ ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค ]

  • AcceptanceTest
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith({RestDocumentationExtension.class, SpringExtension.class})
public class AcceptanceTest {

    @LocalServerPort
    int port;

    protected RequestSpecification spec;

    @BeforeEach
    public void setUp(RestDocumentationContextProvider restDocumentation) {
        RestAssured.port = port;
        this.spec = new RequestSpecBuilder()
                .addFilter(documentationConfiguration(restDocumentation))
                .build();
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • OrderRestControllerTest
class OrderRestControllerTest extends AcceptanceTest {

    @Test
    void postOrder() {
        // given
        String menuName = "๋ฉ”๋‰ด";

        // when
        ExtractableResponse<Response> response = ์ฃผ๋ฌธ_์ƒ์„ฑ_์š”์ฒญ(menuName, 2);
        OrderModel order = response.as(OrderModel.class);

        // then
        assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
        assertThat(order.getMenuName()).isEqualTo(menuName);
    }

    private ExtractableResponse<Response> ์ฃผ๋ฌธ_์ƒ์„ฑ_์š”์ฒญ(String menuName, Integer quantity) {
        OrderPostRequestDTO dto = new OrderPostRequestDTO(menuName, quantity);

        return RestAssured
                .given(this.spec).log().all()
                .accept(APPLICATION_JSON_VALUE)
                .filter(document("post-order",
                        requestFields(
                                fieldWithPath("order.menuName").type(STRING).description("order menu name"),
                                fieldWithPath("order.quantity").type(NUMBER).description("order quantity")),
                        responseFields(
                                fieldWithPath("order.menuName").type(STRING).description("order menu name"),
                                fieldWithPath("order.quantity").type(NUMBER).description("order quantity"))
                ))
                
                .body(dto)
                .contentType(APPLICATION_JSON_VALUE)
                .when().post("/orders")
                .then().log().all()
                .extract();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

[ adoc ๋ฌธ์„œ ์ž‘์„ฑ ]

  • common.adoc
[[index]]
=== link:./index-docs.html[HOME]

[[common]]
== Common API Docs

=== HTTP Status Code
[cols="2,3,5"]
|===
| Code | Description | Constraint

| 200
| Success
|

| 400
| Bad Request
|
|===
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • order.adoc
= Order API
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 3
:sectlinks:

include::{docfile}/../common.adoc[]

== Order API Docs

=== POST /orders
Curl Request
include::{snippets}/post-order/curl-request.adoc[]

Request Headers

Path Parameters

Request Parameters

Request Fields
include::{snippets}/post-order/request-fields.adoc[]

Response Fields
include::{snippets}/post-order/response-fields.adoc[]

HTTP Request
include::{snippets}/post-order/http-request.adoc[]

HTTP Response
include::{snippets}/post-order/http-response.adoc[]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 1205 - Caffeine Cache

# Caffeine vs EhCache

  • Ehcache๋Š” multi-level ์บ์‹œ, distributed ์บ์‹œ, ์บ์‹œ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๊ณผ ๊ฐ™์€ ๋งŽ์€ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•œ๋‹ค.
  • Caffeine์€ Ehcache๋ณด๋‹ค ์บ์‹œ์˜ ์„ฑ๋Šฅ์ด ๋†’์œผ๋ฉฐ, ์‹ค์ œ๋กœ ๋” ์šฐ์ˆ˜ํ•œ ์บ์‹œ ์ œ๊ฑฐ ์ „๋žต์„ ์‚ฌ์šฉํ•œ๋‹ค.(Window TinyLfu ํ‡ด์ถœ ์ •์ฑ…์„ ์‚ฌ์šฉ, ๊ฑฐ์˜ ์ตœ์ ์˜ ์ ์ค‘๋ฅ ์„ ์ œ๊ณต)

# Caffeine ๊ธฐ๋Šฅ

# [ Population Strategy ]

์บ์‹œ ์ถ”๊ฐ€ ์ „๋žต

Caffeine Cache๋Š” ์•„๋ž˜์˜ ์„ธ๊ฐ€์ง€ ํƒ€์ž…์˜ ์บ์‹œ๋กœ ์ œ์ž‘ํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  1. Manual

Cache<K, V> cache = Caffeine.newBuilder().build()

์—”ํŠธ๋ฆฌ๋ฅผ ์ž๋™ ๋กœ๋“œํ•˜๋Š” ์บ์‹œ๋ฅผ ์ƒ์„ฑ.

  1. Loading (Synchronously)

LoadingCache<K, V> cache = Caffeine.newBuilder().build(CacheLoader<> loader)

๋™๊ธฐ ๋ฐฉ์‹์œผ๋กœ loader๋ฅผ ํ†ตํ•ด ์บ์‹œ ์ƒ์„ฑํ•œ๋‹ค.

  1. Asynchronous Loading

AsyncLoadingCache<K, V> cache = Caffeine.newBuilder().buildAsync( ... );

๋น„๋™๊ธฐ ๋ฐฉ์‹์œผ๋กœ loader๋ฅผ ํ†ตํ•ด ์บ์‹œ ์ƒ์„ฑํ•œ๋‹ค.

# [ Eviction ]

Caffeine Cache๋Š” ์•„๋ž˜์˜ ์„ธ๊ฐ€์ง€ ํƒ€์ž…์œผ๋กœ ์บ์‹œ๋ฅผ Evictํ•˜๋Š” ์„ค์ •์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

  1. Size-based

Caffeine.newBuilder().maximumSize(long)

ํฌ๊ธฐ ๊ธฐ์ค€์œผ๋กœ ์บ์‹œ๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ๋ฐฉ์‹์€ ๊ฐœ๋ฐœ์ž๊ฐ€ ์„ค์ •ํ•œ ํŠน์ • ๊ฐ’์„ ๊ธฐ์ค€์œผ๋กœ, entries์˜ ํฌ๊ธฐ๊ฐ€ ๊ทธ ๊ฐ’์„ ๋„˜์„ ๋–„ entries์˜ ์ผ๋ถ€๋ถ„์„ ์ œ๊ฑฐํ•œ๋‹ค.(Window TinyLfu๋ฅผ ์ ์šฉํ•˜์—ฌ ๊ฐ€์žฅ ์ตœ๊ทผ์— ์‚ฌ์šฉ๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜, ์ž์ฃผ ์‚ฌ์šฉ๋˜์–ด์ง€์ง€ ์•Š์€ ๊ฒƒ์„ ์ œ๊ฑฐ)

  1. Time-based

Caffeine.newBuilder().expireAfterAccess(long)
Caffeine.newBuilder().expireAfterWrite(long[, TimeUnit])
Caffeine.newBuilder().expireAfter(Expiry)

  • expireAfterAccess : (์บ์‹œ ์ƒ์„ฑ ์ดํ›„) ํ•ด๋‹น ๊ฐ’์ด ๊ฐ€์žฅ ์ตœ๊ทผ์— ๋Œ€์ฒด๋˜๊ฑฐ๋‚˜ ๋งˆ์ง€๋ง‰์œผ๋กœ ์ฝ์€ ํ›„ ํŠน์ • ๊ธฐ๊ฐ„์ด ์ง€๋‚˜๋ฉด ๊ฐ ํ•ญ๋ชฉ์ด ์บ์‹œ์—์„œ ์ž๋™์œผ๋กœ ์ œ๊ฑฐ๋˜๋„๋ก ์ง€์ •ํ•œ๋‹ค.
  • expireAfterWrite : ์บ์‹œ ์ƒ์„ฑ ํ›„ ๋˜๋Š” ๊ฐ€์žฅ ์ตœ๊ทผ์— ๋ฐ”๋€ ํ›„ ํŠน์ • ๊ธฐ๊ฐ„์ด ์ง€๋‚˜๋ฉด ๊ฐ ํ•ญ๋ชฉ์ด ์บ์‹œ์—์„œ ์ž๋™์œผ๋กœ ์ œ๊ฑฐ๋˜๋„๋ก ์ง€์ •ํ•œ๋‹ค.
  • expireAfter : ์บ์‹œ๊ฐ€ ์ƒ์„ฑ๋˜๊ฑฐ๋‚˜ ๋งˆ์ง€๋ง‰์œผ๋กœ ์—…๋ฐ์ดํŠธ๋œ ํ›„ ์ง€์ •๋œ ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ์œผ๋กœ ์บ์‹œ๋ฅผ ์ƒˆ๋กœ ๊ณ ์นจํ•œ๋‹ค.
  1. Reference-based

Caffeine.newBuilder().weakKeys().weakValues()
Caffeine.newBuilder().softValues()

  • Caffeine.weakKeys(), Caffeine.weakValues() : Week References๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ‚ค๋ฅผ ์ €์žฅํ•œ๋‹ค. Week References๋Š” ํ‚ค์— ๋Œ€ํ•œ ๋‹ค๋ฅธ ๊ฐ•๋ ฅํ•œ ์ฐธ์กฐ(Strong References)๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ ๊ฐ€๋น„์ง€ ์ˆ˜์ง‘ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ฐ€๋น„์ง€ ์ˆ˜์ง‘์€ identity์—๋งŒ ์˜์กดํ•˜๋ฏ€๋กœ ์ „์ฒด ์บ์‹œ๊ฐ€ ๋™๋“ฑ์„ฑ(.equals()) ๋Œ€์‹  identity ๋™์ผ์„ฑ(==)์„ ์‚ฌ์šฉํ•˜์—ฌ ํ‚ค๋ฅผ ๋น„๊ตํ•œ๋‹ค.

  • Caffeine.softValues() : Soft References๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ’์„ ์ €์žฅํ•œ๋‹ค. Soft References ์˜ค๋ธŒ์ ํŠธ๋Š” ๋ฉ”๋ชจ๋ฆฌ ์ˆ˜์š”์— ๋”ฐ๋ผ least-recently-used ๋ฐฉ์‹์œผ๋กœ ๊ฐ€๋น„์ง€๊ฐ€ ์ˆ˜์ง‘๋œ๋‹ค. ์†Œํ”„ํŠธ ๋ ˆํผ๋Ÿฐ์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํผํฌ๋จผ์Šค๊ฐ€ ์˜ํ–ฅ์„ ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— ์ผ๋ฐ˜์ ์œผ๋กœ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ์ตœ๋Œ€ ์บ์‹œ ํฌ๊ธฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. softValues()๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Week References์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๊ฐ’์ด equals ๋Œ€์‹  identity(==) equality๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๊ต๋œ๋‹ค.

# [ Notification of Removal ]

์ œ๊ฑฐ๋˜๋Š” ์บ์‹œ ์—”ํŠธ๋ฆฌ์— ๋Œ€ํ•ด ๋ฆฌ์Šค๋„ˆ๋ฅผ ๋‹ฌ์•„ ์ถ”๊ฐ€์ ์ธ ์ž‘์—…์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

Caffeine.newBuilder()
.evictionListener((K, V, RemovalCause) -> { /* ... / })
.removalListener((K, V, RemovalCause) -> { /
... */ })
.build();

๋‘ ๋ฆฌ์Šค๋„ˆ์˜ ์ฐจ์ด๋Š” ์บ์‹œ ๊ด€๋ จ ์šฉ์–ด๋ฅผ ์ดํ•ดํ•˜๋ฉด ์ ์ ˆํžˆ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  • Eviction : ์บ์‹œ ์ •์ฑ… Policy์— ์˜ํ•œ ์‚ญ์ œ
  • Invalidation : Caller์— ์˜ํ•œ ์ˆ˜๋™ ์‚ญ์ œ
  • Removal : invalidation๊ณผ eviction, ๋‘ ๊ฐ€์ง€๋ฅผ ๋ชจ๋‘ ํฌํ•จ

ex)

Cache<Key, Graph> graphs = Caffeine.newBuilder()
    .evictionListener((Key key, Graph graph, RemovalCause cause) ->
        System.out.printf("Key %s was evicted (%s)%n", key, cause))
    .removalListener((Key key, Graph graph, RemovalCause cause) ->
        System.out.printf("Key %s was removed (%s)%n", key, cause))
    .build();
1
2
3
4
5
6

# [ Compute ]

์™ธ๋ถ€ ๋ฐ์ดํ„ฐ(๋ฆฌ์†Œ์Šค)์— ์ ‘๊ทผํ•˜๋ฉฐ ์บ์‹œ๋ฅผ ์“ธ ์ˆ˜ ์žˆ๋‹ค.

graphs.asMap().compute(key, (k, v) -> { /* ... */ });

๋ถˆ๋Ÿฌ์˜จ ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ Map์œผ๋กœ ๋ณ€๊ฒฝํ•œ ํ›„ ํ‚ค๋ฅผ ์ฐจ๋ก€๋กœ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค.

graphs.asMap().compute(key, (k, v) -> {
  Graph graph = createExpensiveGraph(key);
  ... // update a secondary store
  return graph;
});

map.compute(key, (k, v) -> (v == null) ? msg : v.concat(msg))
1
2
3
4
5
6
7

# [ Statistics ]

์บ์‹œ ์•ก์„ธ์Šค ํ†ต๊ณ„ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ํ†ต๊ณ„ ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Caffeine.recordStats()๋ฅผ ์„ค์ •ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

Cache.stats() ๋ฉ”์†Œ๋“œ๊ฐ€ CacheStats๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ค€๋‹ค. CacheStats๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๋‚ด์šฉ์„ ๊ฐ€์ง„๋‹ค.

public final class CacheStats {

    // ... 
    private final long hitCount;
    private final long missCount;
    private final long loadSuccessCount;
    private final long loadFailureCount;
    private final long totalLoadTime;
    private final long evictionCount;
    private final long evictionWeight;
    // ... 
    public double hitRate() {/* ... */}
    public long evictionCount() {/* ... */}
    public double averageLoadPenalty() {/* ... */}
    // ... 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Caffeine Configuration

  • dependency
<dependency>
  <groupId>com.github.ben-manes.caffeine</groupId>
  <artifactId>caffeine</artifactId>
  <version>3.0.6</version>
</dependency>
1
2
3
4
5
  • yml
spring:
  cache:
    caffeine:
      spec: maximumSize=500,expireAfterWrite=5s
    type: caffeine
    cache-names:
      - users
      - books
1
2
3
4
5
6
7
8
  • @configuration
@Configuration
@EnableCaching
public class CaffeineCacheConfig { /* ... */ }
1
2
3

ex)

@EnableCaching
@Configuration
public class CacheConfig {

    @Bean
    public Caffeine caffeineConfig() {
        return Caffeine
                .newBuilder()
                .maximumSize(10_000)
                .expireAfterWrite(60, TimeUnit.MINUTES);
    }

    @Bean
    public CacheManager cacheManager(Caffeine caffeine) {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(caffeine);
        return cacheManager;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 1209 - Java 17

  • 2021๋…„ 9์›” ์ถœ์‹œ
  • LTS(Long Term Support)
    • 2018๋…„์— ๋“ฑ์žฅํ•œ 11๋ฒ„์ „ ์ดํ›„์˜ ์ƒˆ๋กœ์šด LTS๋ฒ„์ „
      • (Java 8์˜ ์ข…๋ฃŒ ์ผ์ •(EOL, End of Life)์ด Java 11๋ณด๋‹ค ๋’ค๋‹ค)

# JDK 17 Release Notes

# [ JEP 356: Enhanced Pseudo-Random Number Generators ]

๊ธฐ๋ณธ ๋ ˆ๊ฑฐ์‹œ ๋žœ๋ค(Java.util.Random) ํด๋ž˜์Šค๋ฅผ ํ™•์žฅ, ๋ฆฌํŒฉํ† ๋งํ•œ RandomGenerator ๋‚œ์ˆ˜ ์ƒ์„ฑ API๊ฐ€ ์ถ”๊ฐ€ ๋˜์—ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์€ ์ฝ”๋“œ๋กœ ์ž๋ฐ” 17์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

RandomGeneratorFactory.all()
	.map(factory -> String.format("%s: %s", factory.group(), factory.name()))
	.sorted()
	.forEach(System.out::println);
1
2
3
4
LXM: L128X1024MixRandom
LXM: L128X128MixRandom
LXM: L128X256MixRandom
LXM: L32X64MixRandom
LXM: L64X1024MixRandom
LXM: L64X128MixRandom
LXM: L64X128StarStarRandom
LXM: L64X256MixRandom
Legacy: Random
Legacy: SecureRandom
Legacy: SplittableRandom
Xoroshiro: Xoroshiro128PlusPlus
Xoshiro: Xoshiro256PlusPlus
1
2
3
4
5
6
7
8
9
10
11
12
13

# [ JEP 382: New macOS Rendering Pipeline ]

์• ํ”Œ์‚ฌ์—์„œ ๊ธฐ์กด์˜ OpenGL์ด ์•„๋‹Œ Metal ์ด๋ผ๋Š” ์ƒˆ๋กœ์šด ๋ Œ๋”๋ง API๋กœ ๋Œ€์ฒดํ•˜๋Š” ์›€์ง์ž„์— ๋งž์ถ˜ ๋Œ€์‘์ด๋‹ค. ํ–ฅํ›„ MacOS ๋ฒ„์ „์—์„œ OpenGL API๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•˜๊ธฐ ์œ„ํ•จ์ด๋‹ค. ์ฝ”๋“œ ์‚ฌ์šฉ๊ณผ ์ง์ ‘์ ์ธ ๊ด€๋ จ์€ ์—†๊ฒ ์œผ๋‚˜, MacOS์—์„œ ์ธํ…”๋ฆฌ์ œ์ด IDE์˜ ๋ Œ๋”๋ง์ด ๊ฐœ์„ ๋˜์ง€ ์•Š์„๊นŒ ํ•˜๋Š” ๊ธฐ๋Œ€๊ฐ€ ์žˆ๋‹ค.

# [ JEP 398: Deprecate the Applet API for Removal ]

์• ํ”Œ๋ฆฟ(Applet) API๋Š” ์ž๋ฐ” 9์—์„œ๋ถ€ํ„ฐ ์ด๋ฏธ @Deprecated ์„ ์–ธ๋˜์—ˆ๋Š”๋ฐ, ์ด๋ฒˆ ๋ฒ„์ „์—์„œ๋Š” forRemoval ํƒœ๊น…๋„ ์„ ์–ธ๋˜์—ˆ๋‹ค.

@Deprecated(since = "9", forRemoval = true)
@SuppressWarnings("removal")
public class Applet extends Panel {
	// ...
}
1
2
3
4
5

# [ JEP 403: Strongly Encapsulate JDK Internals ]

์ค‘์š”ํ•œ ๋‚ด๋ถ€ API๋ฅผ ์ œ์™ธํ•˜๊ณ ๋Š” JDK์˜ ๋ชจ๋“  ๋‚ด๋ถ€ ์š”์†Œ๋ฅผ ๊ฐ•๋ ฅํ•˜๊ฒŒ ์บก์Šํ™”ํ•œ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฆฌํ”Œ๋ ‰์…˜ ์ฝ”๋“œ๊ฐ€ ๋” ์ด์ƒ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค.

var ks = java.security.KeyStore.getInstance("jceks");
var f = ks.getClass().getDeclaredField("keyStoreSpi");
f.setAccessible(true);
1
2
3

# [ JEP 406: Pattern Matching for switch (Preview) ]

์ž๋ฐ” 14๋ถ€ํ„ฐ ๋“ฑ์žฅํ–ˆ๋˜ ๋‚ด์šฉ์ธ๋ฐ ์•„์ง๊นŒ์ง€ ํ”„๋ฆฌ๋ทฐ(Preview) ๊ธฐ๋Šฅ์ด๋‹ค. ์‹คํ–‰ํ•˜๋ ค๋ฉด ๋ช…๋ น์–ด ์˜ต์…˜ ์ถ”๊ฐ€๊ฐ€ ํ•„์š”ํ•˜๋‹ค. (Intellij์—์„œ๋Š” Language Level ์„ค์ •์„ ๋ณ€๊ฒฝํ•˜๋ฉด ๋œ๋‹ค.)

์•„๋ž˜ ์˜ˆ์‹œ์ฒ˜๋Ÿผ instanceof๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์บ์ŠคํŒ…ํ•˜๋Š” ๋‹จ๊ณ„๋ฅผ ๊ฐ„์†Œํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค.

// AS-IS: ๊ธฐ์กด์˜ instanceof ์—ฐ์‚ฐ์ž ์‚ฌ์šฉ. ์บ์ŠคํŒ…์ด ๋“ค์–ด๊ฐ„๋‹ค.
if (o instanceof String) {
    String s = (String) str;
    // ... ๋ณ€์ˆ˜ s๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ
}

// TO-BE: ํ˜•๋ณ€ํ™˜ ๊ณผ์ •์„ ์—†์• ๊ณ , ๊ทธ ๋ณ€์ˆ˜('s')๋ฅผ ๋‹ด์„ ์ˆ˜ ์žˆ๋‹ค.
if (o instanceof String s) {
    // ... ๋ณ€์ˆ˜ s๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ
}
1
2
3
4
5
6
7
8
9
10

๊ทธ๋ฆฌ๊ณ  null์— ๋Œ€ํ•ด์„œ๋„ ์กฐ๊ธˆ ๋” ๊ฐ„ํŽธํ•˜๊ฒŒ ํ•ธ๋“ค๋งํ•  ์ˆ˜ ์žˆ๋‹ค.

// ๊ธฐ์กด
static void someMethod(String s) {
    if (s == null) {
        System.out.println("null!");
        return;
    }

    switch (s) {
        case "kim", "taeng" -> System.out.println("Hello~");
        default -> System.out.println("Wow!");
    }
}

// ๋ณ€๊ฒฝ
static void someMethod(String s) {
    switch (s) {
        case null -> System.out.println("null!");
        case "kim", "taeng" -> System.out.println("Hello~");
        default -> System.out.println("Wow!");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

๋” ๋‚˜์•„๊ฐ€ switch๋ฌธ์„ ์œ„ํ•œ ํŒจํ„ด ๋งค์นญ์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์€ ํ˜•ํƒœ์˜ switch์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ํŒŒ๋ผ๋ฏธํ„ฐ o๊ฐ’์€ Long l์— ๋งค์นญ๋˜์–ด ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋œ๋‹ค.

Object o = 123L;
String formatted = switch (o) {
    case Integer i -> String.format("int %d", i);
    case Long l -> String.format("long %d", l);
    case Double d -> String.format("double %f", d);
    case String s -> String.format("String %s", s);
    default -> o.toString();
};
1
2
3
4
5
6
7
8

# [ JEP 407: Remove RMI Activation ]

RMI ์ผ๋ถ€ ๊ธฐ๋Šฅ(java.rmi.activation ํŒจํ‚ค์ง€)์ด ์ œ๊ฑฐ๋จ.

# [ JEP 409: Sealed Classes ]

์ž๋ฐ”์—์„œ๋Š” ์ƒ์†์„ ํ†ตํ•ด์„œ ์ฝ”๋“œ๋ฅผ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ฐŒ๋งŒ ๋„ˆ๋ฌด ๋ถ€๋ถ„๋ณ„ํ•œ ์ƒ์†์€ ์ฝ”๋“œ๋ฅผ ์ดํ•ดํ•˜๋Š”๋ฐ ๋” ์–ด๋ ค์›€์„ ์ค„ ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ƒ์†์„ ์ผ๋ถ€ ์ œํ•œํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์ œ์•ˆ๋˜์—ˆ๋‹ค. JEP ์ŠคํŽ™ ๋ฌธ์„œ๋ฅผ ๋ณด๋ฉด ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด ์ƒˆ๋กœ์šด ํŒจ๋Ÿฌ๋‹ค์ž„์ด๊ธฐ ๋•Œ๋ฌธ์— ์กฐ๊ธˆ ๋ณต์žกํ•˜๋‹ค. ๋‚ด์šฉ ๋˜ํ•œ ์—ฌ๋Ÿฌ ๋ฒ„์ „์— ๊ฑฐ์ณ์„œ Preview๋กœ ์†Œ๊ฐœ ๋˜์—ˆ๋‹ค,

ํ•ต์‹ฌ์€ "ํ™•์žฅ(extends) ํ•˜๊ฑฐ๋‚˜ ๊ตฌํ˜„(implements) ํ•  ์ˆ˜ ์žˆ๋Š” ํด๋ž˜์Šค ๋˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œํ•œํ•œ๋‹ค."๋ผ๊ณ  ์ƒ๊ฐ ํ•˜๋ฉด ๋œ๋‹ค. ๋ณดํ†ต ์šฐ๋ฆฌ ๋ง๋กœ๋Š” ๋ด‰์ธ ํด๋ž˜์Šค ๋˜๋Š” ๋ด‰์ธ๋œ ํด๋ž˜์Šค๋กœ ํ‘œ๊ธฐํ•œ๋‹ค. ์ด์ œ ์ƒ์†, ๊ตฌํ˜„์— ์ด์€ ๋ด‰์ธ์ด๋ผ๋Š” ๋‹จ์–ด๋„ ์‚ฌ์šฉ๋ ๊ฑฐ๋ผ ์˜ˆ์ธก.

๊ธฐ์กด์—๋Š” ์ƒ์†์„ ์ œํ•œํ•˜๋Š” ๋ฐฉ๋ฒ•์€ final ํ‚ค์›Œ๋“œ๋ฅผ ํ†ตํ•ด์„œ ์‚ฌ์šฉ.

class Person {
}

// `Developer` ํด๋ž˜์Šค๋Š” ํ™•์žฅ/์ƒ์†(extends)ํ•  ์ˆ˜ ์—†๋‹ค.
final class Developer extends Person {
}

// `Designer` ํด๋ž˜์Šค๋Š” ํ™•์žฅ/์ƒ์†(extends)ํ•  ์ˆ˜ ์—†๋‹ค.
final class Designer extends Person {
}
1
2
3
4
5
6
7
8
9
10

์ด๋ฒˆ์—๋Š” ๋‹ค๋ฅธ ์ปจ์…‰์œผ๋กœ, ํŠน์ • ์„œ๋ธŒ ํด๋ž˜์Šค์—๊ฒŒ๋งŒ ํ™•์žฅ์„ ํ—ˆ์šฉํ•˜๊ณ  ๋‹ค๋ฅธ ํด๋ž˜์Šค์—๋Š” ๋ด‰์ธ(sealed)ํ•˜๋Š” ๋ฐฉ๋ฒ•.

// `Person`๋Š” ํ—ˆ์šฉ๋œ(permits) ์„œ๋ธŒ ํด๋ž˜์Šค๋งŒ ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.
sealed class Person
    permits Developer, Designer {
}

// `Developer` ํด๋ž˜์Šค๋Š” ๋ด‰์ธ์ด ํ•ด์ œ๋˜์—ˆ๋‹ค.
non-sealed class Developer extends Person {

}

// ๋ด‰์ธ์ด ํ•ด์ œ๋œ `Student` ํด๋ž˜์Šค๋Š” ๋‹ค๋ฅธ ์„œ๋ธŒ ํด๋ž˜์Šค์—์„œ ํ™•์žฅ ๊ฐ€๋Šฅํ•˜๋‹ค.
// ๊ทธ๋ฆฌ๊ณ  ์ž๊ธฐ ์ž์‹ ์„ Developer ๋ด‰์ธ(sealed)ํ•  ์ˆ˜ ์žˆ๋‹ค. 
sealed class Student extends Developer 
    permits HighSchoolStudent, MiddleSchoolStudent {
    // ์ด ํด๋ž˜์Šค๋Š” `HighSchoolStudent`, `MiddleSchoolStudent` ํด๋ž˜์Šค๋งŒ ํ™•์žฅ ๊ฐ€๋Šฅํ•˜๋‹ค.
}

// permitted ์„œ๋ธŒ ํด๋ž˜์Šค๋Š” ํ™•์žฅ์„ ๋ชปํ•˜๊ฒŒ ํ•˜๊ฑฐ๋‚˜(final),
// ์„œ๋ธŒ ํด๋ž˜์Šค๋ฅผ ๊ฐ€์ง„์ฑ„๋กœ ์ž์‹ ์„ ๋ด‰์ธํ•˜๊ฑฐ๋‚˜(sealed), ๋ด‰์ธ์„ ํ•ด์ œ(non-sealed)ํ•ด์•ผ๋งŒ ํ•œ๋‹ค.
final class HighSchoolStudent extends Student {

}

non-sealed class MiddleSchoolStudent extends Student {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

๋ด‰์ธ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ๋ช‡๊ฐ€์ง€ ๊ทœ์น™์ด ์žˆ๋‹ค.

  • sealed ํด๋ž˜์Šค์™€ permitted๋œ ์„œ๋ธŒ ํด๋ž˜์Šค์™€ ๋™์ผํ•œ ๋ชจ๋“ˆ ๋˜๋Š” ํŒจํ‚ค์ง€์— ์†ํ•ด์•ผํ•œ๋‹ค.
  • ๋ชจ๋“  permitted ์„œ๋ธŒ ํด๋ž˜์Šค๋Š” sealed ํด๋ž˜์Šค๋ฅผ ํ™•์žฅ(extends)ํ•ด์•ผ ํ•œ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์ปดํŒŒ์ผ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
  • ๋ชจ๋“  permitted ์„œ๋ธŒ ํด๋ž˜์Šค๋Š” ์ˆ˜ํผ ํด๋ž˜์Šค์— ์˜ํ•ด ์‹œ์ž‘๋œ ๋ด‰์ธ์„ ๊ณ„์†ํ• ์ง€ ๋ง์ง€ ์„ ์–ธํ•ด์•ผํ•œ๋‹ค.
    • ๋”์ด์ƒ ํ™•์žฅ๋˜์ง€ ์•Š๋„๋ก final์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
    • non-sealed๋กœ ์„ ์–ธํ•˜์—ฌ ๋‹ค๋ฅธ ํด๋ž˜์Šค๊ฐ€ ํ™•์žฅํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ์ž๊ธฐ ์ž์‹ ๋„ ๋ด‰์ธ(sealed) ํด๋ž˜์Šค๋กœ ์„ ์–ธ๋  ์ˆ˜ ์žˆ๋‹ค.

# [ JEP 410: Remove the Experimental AOT and JIT Compiler ]

AOT(Ahead-Of-Time), JIT(Just-In-Time) ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์ œ๊ฑฐ๋˜์—ˆ๋‹ค. ๋Œ€์ƒ์€ jdk.aot, jdk.internal.vm.compilerm, jdk.internal.vm.compiler.management ์ด๋‹ค.

# [ JEP 411: Deprecate the Security Manager for Removal ]

java.lang.SecurityManager์™€ ์ผ๋ถ€ ํด๋ž˜์Šค์— @Deprecated(forRemoval=true)๊ฐ€ ์„ ์–ธ๋จ.

์ž๋ฐ” 14๋ฒ„์ „ Preview ์ค‘ ๋ช‡ ๊ฐ€์ง€๊ฐ€ ์ •์‹ ๋ฐ˜์˜

# [ JEP 359: Records ]

record๋Š” ์ž๋ฐ” 16์—์„œ ์ŠคํŽ™์ด ํ™•์ •๋˜์–ด ์ •์‹ ์ถ”๊ฐ€๋จ.

record RecordPoint(int x, int y) {
    // ์ƒ์† ๋ถˆ๊ฐ€(final ํด๋ž˜์Šค)

    // ๊ฐ ํ•„๋“œ๋Š” private final. ์ˆ˜์ • ๋ถˆ๊ฐ€
    // x = 5;

    // serialize ํ•  ๋•Œ๋Š”? ํ•„๋“œ์— `@JsonProperty`๋ฅผ ๋ถ™์—ฌ์ค€๋‹ค.

    // static ํ•„๋“œ์™€ ๋ฉ”์„œ๋“œ ์†Œ์œ  ๊ฐ€๋Šฅ
    static int MAX_LENGTH = 25;

    public static int getMaxLength() {
        return MAX_LENGTH;
    }
}

// ์‚ฌ์šฉํ•  ๋•Œ๋Š”? ํด๋ž˜์Šค์™€ ๋™์ผํ•˜๊ฒŒ `new` ์—ฐ์‚ฐ์ž๋กœ ์ธ์Šคํ„ด์Šคํ™”ํ•œ๋‹ค.
RecordPoint recordPoint = new RecordPoint(2, 3);

// ๊ทธ๋Ÿฐ๋ฐ getter๊ฐ€ ์ž๋™ ์ƒ์„ฑ!
recordPoint.x();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# [ JEP 378: Text Blocks ]

JDK 15์— ์ •์‹ ํฌํ•จ.

private void runJEP368() {
    String html = """
            {
                "list": [
                    {
                        "title": "hello, taeng",
                        "author": "taeng"
                    },
                    {
                        "title": "hello, news",
                        "author": "taeng"
                    }
                ]
            }
            """.indent(2);
    // indent ๋Š” ์ž๋ฐ” 12์—์„œ ์ถ”๊ฐ€๋œ ๊ฒƒ์ธ๋ฐ, ๋ฌธ์ž์—ด ๊ฐ ๋งจ ์•ž ํ–‰์„ n๋งŒํผ ๋„์šด๋‹ค.
    System.out.println(html);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • ๋ณ€์ˆ˜ ๋งคํ•‘
String textBlock = """
        {
            "title": %s,
            "author": %s,
            "id": %d
        }
        """.formatted("hi", "taeng", 2);

System.out.println(textBlock);
1
2
3
4
5
6
7
8
9
Last update: December 15, 2022 00:52
Contributors: jaesungahn91