René's URL Explorer Experiment


Title: 아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라 · Issue #206 · Study-2-Effective-Java/Effective-Java · GitHub

Open Graph Title: 아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라 · Issue #206 · Study-2-Effective-Java/Effective-Java

X Title: 아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라 · Issue #206 · Study-2-Effective-Java/Effective-Java

Description: Discussed in https://github.com/orgs/Study-2-Effective-Java/discussions/203 Originally posted by JoisFe April 9, 2023 아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라 문제점 Serializable을 구현하기로 결정한 순간 언어의 정상 메커니즘인 생성자 이외의 방법으로 인스턴스를 생성할 수 있음 버그와 보안 문제...

Open Graph Description: Discussed in https://github.com/orgs/Study-2-Effective-Java/discussions/203 Originally posted by JoisFe April 9, 2023 아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라 문제점 Serializable을 구현하기로 결정한 순간 언어의 정상 메커니...

X Description: Discussed in https://github.com/orgs/Study-2-Effective-Java/discussions/203 Originally posted by JoisFe April 9, 2023 아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라 문제점 Serializable을 구현하기로 결정한 순간 언어의 정상 메커니...

Opengraph URL: https://github.com/Study-2-Effective-Java/Effective-Java/issues/206

X: @github

direct link

Domain: patch-diff.githubusercontent.com


Hey, it has json ld scripts:
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라","articleBody":"### Discussed in https://github.com/orgs/Study-2-Effective-Java/discussions/203\r\n\r\n\u003cdiv type='discussions-op-text'\u003e\r\n\r\n\u003csup\u003eOriginally posted by **JoisFe** April  9, 2023\u003c/sup\u003e\r\n# 아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라\r\n## 문제점\r\n- Serializable을 구현하기로 결정한 순간 언어의 정상 메커니즘인 생성자 이외의 방법으로 인스턴스를 생성할 수 있음\r\n### 버그와 보안 문제가 일어날 가능성이 커짐\r\n\r\n## 해결책\r\n### 직렬화 프록시 패턴 (serialization proxy pattern)\r\n- 먼저 바깥 클래스의 논리적 상태를 정밀하게 표현하는 중첩 클래스를 설계해 private static으로 선언\r\n- 해당 중첩 클래스가 바로 바깥 클래스의 직렬화 프록시\r\n- 중첩 클래스의 생성자는 단 하나여야 하며 바깥 클래스를 매개변수로 받아야 한다.\r\n- 이 생성자는 단순히 인수로 넘어온 인스턴스의 데이터를 복사\r\n- 일관성 검사나 방어적 복사도 필요 없음!\r\n- 설계상 직렬화 프록시의 기본 직렬화 형태는 바깥 클래스의 직렬화 형태로 쓰기에 이상적\r\n- 그리고 바깥 클래스와 직렬화 프록시 모두 Serializable을 구현한다고 선언해야 함\r\n\r\n``` java\r\npublic final class Period {\r\n\r\n    private final Date start;\r\n    private final Date end;\r\n\r\n    Period(Date start, Date end) {\r\n        if (start.compareTo(end) \u003e 0) {\r\n            throw new IllegalArgumentException(start + \"가 \" + end + \"보다 늦다.\");\r\n        }\r\n        \r\n        this.start = start;\r\n        this.end = end;\r\n    }\r\n    \r\n    public Date start() {\r\n        return this.start;\r\n    }\r\n    \r\n    public Date end() {\r\n        return this.end;\r\n    }\r\n\r\n    private static class SerializationProxy implements Serializable {\r\n        private final Date start;\r\n        private final Date end;\r\n\r\n        public SerializationProxy(Period p) {\r\n            this.start = p.start;\r\n            this.end = p.end;\r\n        }\r\n\r\n        // Period.SerializationProxy 용 readResolve 메서드\r\n        private Object readResolve() {\r\n            return new Period(this.start, this.end);\r\n        }\r\n    }\r\n    \r\n    private static final long serialVersionUID = 453452354;\r\n    \r\n     private static final long serialVersionUID = 453452354;\r\n\r\n    // 직렬화 프록시 패턴용 writeReplace 메서드\r\n    private Object writeReplace() {\r\n        return new SerializationProxy(this);\r\n    }\r\n\r\n    // 직렬화 프록시 패턴용 readObject 메서드\r\n    private Object readObject(ObjectInputStream stream) throws InvalidObjectException {\r\n        throw new InvalidObjectException(\"프록시가 필요합니다.\");\r\n    }\r\n}\r\n```\r\n\r\n- SerializationProxy 클래스는 Period 클래스의 직렬화 프록시이다.\r\n\r\n``` java\r\n // 직렬화 프록시 패턴용 writeReplace 메서드\r\n    private Object writeReplace() {\r\n        return new SerializationProxy(this);\r\n    }\r\n```\r\n- 바깥 클래스에 다음의 writeReplace 메서드를 추가\r\n- 해당 메서드는 범용적이므로 직렬화 프록시를 사용하는 모든 클래스에 그대로 복사해 쓰면 됨\r\n- 이 메서드는 자바의 직렬화 시스템이 바깥 클래스의 인스턴스 대신 SerializationProxy 인스턴스를 반환하게 하는 역할을 함\r\n- 달리 말하면 직렬화가 이뤄지기 전에 바깥 클래스의 인스턴스를 직렬화 프록시로 변환해줌\r\n- writeReplace 덕분에 직렬화 시스템은 결코 바깥 클래스의 직렬화된 인스턴스를 생성해낼 수 없음\r\n- 하지만 공격자는 불변식을 훼손하고자 이런 시도를 해볼 수 있음\r\n\r\n``` java\r\n  // 직렬화 프록시 패턴용 readObject 메서드\r\n    private Object readObject(ObjectInputStream stream) throws InvalidObjectException {\r\n        throw new InvalidObjectException(\"프록시가 필요합니다.\");\r\n    }\r\n```\r\n\r\n- readObject 메서드를 바깥 클래스에 추가하면 이 공격을 가볍게 막아낼 수있음\r\n\r\n``` java\r\n   // Period.SerializationProxy 용 readResolve 메서드\r\n    private Object readResolve() {\r\n        return new Period(this.start, this.end);\r\n    }\r\n```\r\n\r\n- 바깥 클래스와 논리적으로 동일한 인스턴스를 반환하는 readResolve 메서드를 SerializationProxy 클래스에 추가\r\n- 이 메서드는 역직렬화 시에 직렬화 시스템이 직렬화 프록시를 다시 바깥 클래스의 인스턴스로 변환하게 해줌\r\n- readResolve 메서드는 공개된 API 만을 사용해 바깥 클래스의 인스턴스를 생성하는데 이 패턴이 아름다운 이유가 여기 있음\r\n- 직렬화는 생성자를 이용하지 않고도 인스턴스를 생성하는 기능을 제공하는 이 패턴은 직렬화의 이런 언어도단적 특성을 상당 부분 제거\r\n- 즉 일반 인스턴스를 만들 때와 똑같은 생성자, 정적 팩터리 혹은 다른 메서드를 사용해 역질렬화된 인스턴스를 생성하는 것\r\n- 따라서 역직렬화된 인스턴스가 해당 클래스의 불변식을 만족하는지 검사할 또 다른 수단을 강구하지 않아도 됨\r\n- 그 클래스의 정적 팩터리나 생성자가 불변식을 확인해주고 인스턴스 메서드들이 불변식을 잘 지켜준다면 따로 더 해줘야 할 일이 없음\r\n\r\n## 프록시 패턴 vs 방어적 복사\r\n- 방어적 복사처럼 직렬화 프록시 패턴은 가짜 바이트 스트림 공격과 내부 필드 탈취 공격을 프록시 수준에서 차단해줌\r\n- 방어적 복사 방식과 달리 직렬화 프록시는 Period의 필드를 final로 선언해도 되므로 Period 클래스를 진정한 불변으로 만들 수 있음\r\n- 또한 이리저리 고민할 거리도 없어짐\r\n- 어떤 필드가 기만적인 직렬화 공격의 목표가 될지 고민하지 않아도 되며 역직렬화 때 유효성 검사를 수행하지 않아도 됨\r\n\r\n### 직렬화 프록시 패턴이 readObject 에서의 방어적 복사보다 강력한 경우\r\n- 직렬화 프록시 패턴은 역직렬화환 인스턴스와 원래의 직렬화된 인스턴스의 클래스가 달라도 정상 동작\r\n- 실전에서 크게 쓸모가 없어 보이나 쓸모가 있음\r\n\r\n### EX) EnumSet (#91)\r\n- 해당 클래스는 public 생성자 없이 정적 팩터리들만 제공\r\n- 클라이언트 입장에서 이 팩터리들이 EnumSet 인스턴스를 반환하는 걸로 보이지만 현재의 OpenJDK를 보면 열거 타입의 크기에 따라 두 하위 클래스 중 하나의 인스턴스를 반환\r\n- 열거 타입의 원소가 64개 이하이면 RegularEnumSet을 사용하고 그보다 크면 JumboEnumSet을 사용\r\n\r\n### EnumSet 직렬화 프록시 패턴\r\n- 원소 64개 짜리 열거 타입을 가진 EnumSet을 직렬화 한 다음 원소 5개를 추가하고 역직렬화하면 어떤 일이 벌어질지 알아보자\r\n- 처음 직렬화된 것은 RegularEnumSet 인스턴스\r\n- 하지만 역직렬화는 JumboEnumSet 인스턴스로 하면 좋을 것\r\n- 그리고 EnumSet은 직렬화 프록시 패턴을 사용해서 실제로도 아래와 같이 동작\r\n\r\n``` java\r\nprivate static class SerializationProxy \u003cE extends Enum\u003cE\u003e\u003e implements Serializable {\r\n\r\n        // 이 EnumSet의 원소 타입\r\n        private final Class\u003cE\u003e elementType;\r\n\r\n        // 이 EnumSet 안의 원소들\r\n        private final Enum\u003c?\u003e[] elements;\r\n\r\n        SerializationProxy(Enum\u003cE\u003e set) {\r\n            this.elementType = set.elementType;\r\n            this.elements = set.toArray(new Enum\u003c?\u003e[0]);\r\n        }\r\n\r\n        private Object readResolve() {\r\n            EnumSet\u003cE\u003e result = EnumSet.noneOf(this.elementType);\r\n\r\n            for (Enum\u003c?\u003e e : this.elements) {\r\n                result.add((E) e);\r\n            }\r\n\r\n            return result;\r\n        }\r\n\r\n        private static final long serialVersionUID = 23542435L;\r\n    }\r\n```\r\n\r\n## 직렬화 프록시 패턴의 한계\r\n### 1. 클라이언트가 멋대로 확장할 수 있는 클래스에는 적용할 수 없음\r\n### 2. 객체 그래프에 순환이 있는 클래스에도 적용할 수 없음\r\n- 이러한 객체의 메서드를 직렬화 프록시의 readResolve 안에서 호출하려 하면 ClassCastException 발생\r\n- 직렬화 프록시만 가졌을 뿐 실제 객체는 아직 만들어진 것이 아니기 때문\r\n\r\n## 직렬화 프록시 패턴이 주는 대가\r\n### 직렬화 프록시 패턴은 강력함과 안정성을 주지만 그만한 대가가 따름\r\n- 위 Period 코드가 방어적 복사에 비해 14% 정도 느려짐\r\n\r\n## 정리\r\n### 제 3자가 확장할 수 없는 클래스라면 가능한 한 직렬화 프록시 패턴을 사용하자!\r\n- 중요한 불변식을 안정적으로 직렬화해주는 가장 쉬운 방법 중 하나일 것","author":{"url":"https://github.com/JoisFe","@type":"Person","name":"JoisFe"},"datePublished":"2023-04-10T14:38:47.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":0},"url":"https://github.com/206/Effective-Java/issues/206"}

route-pattern/_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format)
route-controllervoltron_issues_fragments
route-actionissue_layout
fetch-noncev2:76c9c892-2da6-462c-7a4f-5f6697c87295
current-catalog-service-hash81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114
request-idD604:3F983D:8EE5EFF:BD53CA1:696DFF67
html-safe-noncebe0bb8efb952c7555aaac0fbe2192dc25f22d1a1797ac32e47586ee4c0156600
visitor-payloadeyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJENjA0OjNGOTgzRDo4RUU1RUZGOkJENTNDQTE6Njk2REZGNjciLCJ2aXNpdG9yX2lkIjoiMTc4ODI4MjI2NzY1MDM1OTE0MyIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9
visitor-hmacbac8f2b6fec8629b9ecec85ae00c4093375bf6ea667bd6e867644a84b402899c
hovercard-subject-tagissue:1660884578
github-keyboard-shortcutsrepository,issues,copilot
google-site-verificationApib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I
octolytics-urlhttps://collector.github.com/github/collect
analytics-location///voltron/issues_fragments/issue_layout
fb:app_id1401488693436528
apple-itunes-appapp-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/Study-2-Effective-Java/Effective-Java/206/issue_layout
twitter:imagehttps://opengraph.githubassets.com/a0083439b1ad90cbec313ebe9cfba53629980bb5c2e887d642d37a9d7a764d02/Study-2-Effective-Java/Effective-Java/issues/206
twitter:cardsummary_large_image
og:imagehttps://opengraph.githubassets.com/a0083439b1ad90cbec313ebe9cfba53629980bb5c2e887d642d37a9d7a764d02/Study-2-Effective-Java/Effective-Java/issues/206
og:image:altDiscussed in https://github.com/orgs/Study-2-Effective-Java/discussions/203 Originally posted by JoisFe April 9, 2023 아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라 문제점 Serializable을 구현하기로 결정한 순간 언어의 정상 메커니...
og:image:width1200
og:image:height600
og:site_nameGitHub
og:typeobject
og:author:usernameJoisFe
hostnamegithub.com
expected-hostnamegithub.com
None4922b452d03cd8dbce479d866a11bc25b59ef6ee2da23aa9b0ddefa6bd4d0064
turbo-cache-controlno-preview
go-importgithub.com/Study-2-Effective-Java/Effective-Java git https://github.com/Study-2-Effective-Java/Effective-Java.git
octolytics-dimension-user_id120388640
octolytics-dimension-user_loginStudy-2-Effective-Java
octolytics-dimension-repository_id577325341
octolytics-dimension-repository_nwoStudy-2-Effective-Java/Effective-Java
octolytics-dimension-repository_publictrue
octolytics-dimension-repository_is_forkfalse
octolytics-dimension-repository_network_root_id577325341
octolytics-dimension-repository_network_root_nwoStudy-2-Effective-Java/Effective-Java
turbo-body-classeslogged-out env-production page-responsive
disable-turbofalse
browser-stats-urlhttps://api.github.com/_private/browser/stats
browser-errors-urlhttps://api.github.com/_private/browser/errors
release7e5ae23c70136152637ceee8d6faceb35596ec46
ui-targetfull
theme-color#1e2327
color-schemelight dark

Links:

Skip to contenthttps://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/issues/206#start-of-content
https://patch-diff.githubusercontent.com/
Sign in https://patch-diff.githubusercontent.com/login?return_to=https%3A%2F%2Fgithub.com%2FStudy-2-Effective-Java%2FEffective-Java%2Fissues%2F206
GitHub CopilotWrite better code with AIhttps://github.com/features/copilot
GitHub SparkBuild and deploy intelligent appshttps://github.com/features/spark
GitHub ModelsManage and compare promptshttps://github.com/features/models
MCP RegistryNewIntegrate external toolshttps://github.com/mcp
ActionsAutomate any workflowhttps://github.com/features/actions
CodespacesInstant dev environmentshttps://github.com/features/codespaces
IssuesPlan and track workhttps://github.com/features/issues
Code ReviewManage code changeshttps://github.com/features/code-review
GitHub Advanced SecurityFind and fix vulnerabilitieshttps://github.com/security/advanced-security
Code securitySecure your code as you buildhttps://github.com/security/advanced-security/code-security
Secret protectionStop leaks before they starthttps://github.com/security/advanced-security/secret-protection
Why GitHubhttps://github.com/why-github
Documentationhttps://docs.github.com
Bloghttps://github.blog
Changeloghttps://github.blog/changelog
Marketplacehttps://github.com/marketplace
View all featureshttps://github.com/features
Enterpriseshttps://github.com/enterprise
Small and medium teamshttps://github.com/team
Startupshttps://github.com/enterprise/startups
Nonprofitshttps://github.com/solutions/industry/nonprofits
App Modernizationhttps://github.com/solutions/use-case/app-modernization
DevSecOpshttps://github.com/solutions/use-case/devsecops
DevOpshttps://github.com/solutions/use-case/devops
CI/CDhttps://github.com/solutions/use-case/ci-cd
View all use caseshttps://github.com/solutions/use-case
Healthcarehttps://github.com/solutions/industry/healthcare
Financial serviceshttps://github.com/solutions/industry/financial-services
Manufacturinghttps://github.com/solutions/industry/manufacturing
Governmenthttps://github.com/solutions/industry/government
View all industrieshttps://github.com/solutions/industry
View all solutionshttps://github.com/solutions
AIhttps://github.com/resources/articles?topic=ai
Software Developmenthttps://github.com/resources/articles?topic=software-development
DevOpshttps://github.com/resources/articles?topic=devops
Securityhttps://github.com/resources/articles?topic=security
View all topicshttps://github.com/resources/articles
Customer storieshttps://github.com/customer-stories
Events & webinarshttps://github.com/resources/events
Ebooks & reportshttps://github.com/resources/whitepapers
Business insightshttps://github.com/solutions/executive-insights
GitHub Skillshttps://skills.github.com
Documentationhttps://docs.github.com
Customer supporthttps://support.github.com
Community forumhttps://github.com/orgs/community/discussions
Trust centerhttps://github.com/trust-center
Partnershttps://github.com/partners
GitHub SponsorsFund open source developershttps://github.com/sponsors
Security Labhttps://securitylab.github.com
Maintainer Communityhttps://maintainers.github.com
Acceleratorhttps://github.com/accelerator
Archive Programhttps://archiveprogram.github.com
Topicshttps://github.com/topics
Trendinghttps://github.com/trending
Collectionshttps://github.com/collections
Enterprise platformAI-powered developer platformhttps://github.com/enterprise
GitHub Advanced SecurityEnterprise-grade security featureshttps://github.com/security/advanced-security
Copilot for BusinessEnterprise-grade AI featureshttps://github.com/features/copilot/copilot-business
Premium SupportEnterprise-grade 24/7 supporthttps://github.com/premium-support
Pricinghttps://github.com/pricing
Search syntax tipshttps://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax
documentationhttps://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax
Sign in https://patch-diff.githubusercontent.com/login?return_to=https%3A%2F%2Fgithub.com%2FStudy-2-Effective-Java%2FEffective-Java%2Fissues%2F206
Sign up https://patch-diff.githubusercontent.com/signup?ref_cta=Sign+up&ref_loc=header+logged+out&ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fvoltron%2Fissues_fragments%2Fissue_layout&source=header-repo&source_repo=Study-2-Effective-Java%2FEffective-Java
Reloadhttps://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/issues/206
Reloadhttps://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/issues/206
Reloadhttps://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/issues/206
Study-2-Effective-Java https://patch-diff.githubusercontent.com/Study-2-Effective-Java
Effective-Javahttps://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java
Notifications https://patch-diff.githubusercontent.com/login?return_to=%2FStudy-2-Effective-Java%2FEffective-Java
Fork 3 https://patch-diff.githubusercontent.com/login?return_to=%2FStudy-2-Effective-Java%2FEffective-Java
Star 8 https://patch-diff.githubusercontent.com/login?return_to=%2FStudy-2-Effective-Java%2FEffective-Java
Code https://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java
Issues 59 https://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/issues
Pull requests 0 https://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/pulls
Discussions https://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/discussions
Actions https://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/actions
Projects 0 https://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/projects
Security Uh oh! There was an error while loading. Please reload this page. https://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/security
Please reload this pagehttps://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/issues/206
Insights https://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/pulse
Code https://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java
Issues https://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/issues
Pull requests https://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/pulls
Discussions https://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/discussions
Actions https://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/actions
Projects https://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/projects
Security https://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/security
Insights https://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/pulse
New issuehttps://patch-diff.githubusercontent.com/login?return_to=https://github.com/Study-2-Effective-Java/Effective-Java/issues/206
New issuehttps://patch-diff.githubusercontent.com/login?return_to=https://github.com/Study-2-Effective-Java/Effective-Java/issues/206
아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라https://patch-diff.githubusercontent.com/Study-2-Effective-Java/Effective-Java/issues/206#top
https://patch-diff.githubusercontent.com/JoisFe
12장 직렬화이펙티브 자바 12장 (직렬화)https://github.com/Study-2-Effective-Java/Effective-Java/issues?q=state%3Aopen%20label%3A%2212%EC%9E%A5%20%EC%A7%81%EB%A0%AC%ED%99%94%22
https://github.com/JoisFe
https://github.com/JoisFe
JoisFehttps://github.com/JoisFe
on Apr 10, 2023https://github.com/Study-2-Effective-Java/Effective-Java/issues/206#issue-1660884578
https://github.com/orgs/Study-2-Effective-Java/discussions/203https://github.com/orgs/Study-2-Effective-Java/discussions/203
#91https://github.com/orgs/Study-2-Effective-Java/discussions/91
JoisFehttps://patch-diff.githubusercontent.com/JoisFe
12장 직렬화이펙티브 자바 12장 (직렬화)https://github.com/Study-2-Effective-Java/Effective-Java/issues?q=state%3Aopen%20label%3A%2212%EC%9E%A5%20%EC%A7%81%EB%A0%AC%ED%99%94%22
https://github.com
Termshttps://docs.github.com/site-policy/github-terms/github-terms-of-service
Privacyhttps://docs.github.com/site-policy/privacy-policies/github-privacy-statement
Securityhttps://github.com/security
Statushttps://www.githubstatus.com/
Communityhttps://github.community/
Docshttps://docs.github.com/
Contacthttps://support.github.com?tags=dotcom-footer

Viewport: width=device-width


URLs of crawlers that visited me.