2022-02-16-TIL
2022-02-16-TIL
Today I Learned
jdk.incubator.httpclient -> java.net.http
자바 11버전 이후부터는 http 관련 모듈의 패키지 네이밍이 바뀌었다.
정확히는 JEP 321이 Java 11에서 HTTP Client API를 표준으로 만든 것이다. JDK 9(JEP 110)에서 인큐베이터 모듈로 들어와 JDK 10까지 시험되던 API가 java.net.http 모듈과 패키지로 정식 편입되었고, 인큐베이터 패키지 jdk.incubator.http는 제거되었다. 그래서 Java 9, 10 시절의 예제 코드를 Java 11 이상에서 컴파일하면 모듈을 찾을 수 없다는 오류가 난다. import를 java.net.http.HttpClient로 바꾸고, module-info.java가 있다면 requires java.net.http;로 고친다. JEP 문서는 표준화 과정에서 구현을 거의 새로 작성해 완전히 비동기로 동작하게 했고, CompletableFuture로 논블로킹 요청과 응답을 제공한다고 설명한다.
- https://stackoverflow.com/questions/50122696/the-jdk-incubator-httpclient-module-not-found-in-java11
- https://lts0606.tistory.com/455
JavaFX
오라클 JDK는 기본적으로 JavaFX를 내장하지 않는다. 따라서 외부 라이브러리로서 프로젝트에 직접 포함해주어야 한다.
JavaFX는 JDK 11부터 JDK에서 분리되어 OpenJFX라는 별도 프로젝트로 배포된다. OpenJFX 사이트의 소개에 따르면 JavaFX 런타임은 플랫폼별 SDK, jmod, Maven Central의 아티팩트로 제공된다. 그래서 Homebrew로 설치한 OpenJDK 17에서 JavaFX 프로그램이 동작하지 않는 문제(첫 번째 링크)는 JDK를 바꿔서가 아니라, Maven이나 Gradle에 org.openjfx 의존성과 플러그인을 추가하거나 SDK를 내려받아 --module-path로 지정해서 해결한다.
- https://stackoverflow.com/questions/69965469/why-does-javafx-not-work-under-openjdk-17-from-homebrew
- https://openjfx.io/openjfx-docs/#introduction
Validator
Bean Validation의 기본 애너테이션(@NotBlank, @Size, @Email)으로 표현할 수 없는 규칙은 사용자 정의 제약으로 만든다. 애너테이션을 정의하고 @Constraint(validatedBy = ...)로 검증 클래스를 연결한 뒤, 그 클래스가 ConstraintValidator<애너테이션, 타입>의 isValid()를 구현한다. 이후 DTO 필드에 애너테이션을 붙이고 컨트롤러 파라미터에 @Valid를 붙이면 검증이 실행된다.
- https://www.baeldung.com/spring-mvc-custom-validator
Helper Class
서비스 클래스가 커지면서 내부 로직을 헬퍼 클래스로 빼낼 때, 그 헬퍼를 의존성 주입으로 받을지 내부에서 직접 생성할지에 대한 자료다. 마크 시먼의 글은 생성자 파라미터가 지나치게 많아지는 것을 단일 책임 원칙 위반의 신호로 보고, 관련된 의존성들을 묶어 하나의 추상화(Facade Service)로 추출하는 리팩터링을 제안한다. 그러면 생성자 파라미터가 줄고, 숨어 있던 도메인 개념이 이름을 얻는다. 상태가 없고 바꿔 끼울 일이 없는 순수 계산 헬퍼라면 굳이 주입하지 않고 내부 구현으로 두어도 된다는 것이 Stack Overflow 질문에서 나오는 판단 기준이다.
- https://blog.ploeh.dk/2010/02/02/RefactoringtoAggregateServices/
- https://stackoverflow.com/questions/45208707/dependency-injection-and-internal-helper-classes
MyBatis 설정 파일
MyBatis 설정 파일(mybatis-config.xml)은 settings(예: mapUnderscoreToCamelCase로 user_name 컬럼을 userName 필드에 매핑), typeAliases(긴 클래스 이름 대신 별칭), environments(데이터 소스와 트랜잭션 매니저), mappers(매퍼 XML 위치)로 구성된다. Spring Boot에서는 같은 설정 대부분을 application.yml의 mybatis.* 속성으로 지정할 수 있다.
- https://atoz-develop.tistory.com/entry/MyBatis-%EC%84%A4%EC%A0%95-%ED%8C%8C%EC%9D%BC-%EC%9E%91%EC%84%B1-%EB%B0%A9%EB%B2%95
MyBatis Generic VO, DAO
테이블마다 비슷한 CRUD DAO를 반복해서 만들지 않도록, 제네릭 타입 파라미터를 가진 공통 DAO와 VO 상위 클래스를 두는 방법이다. 공통 코드는 줄지만, 매퍼 XML의 SQL은 여전히 테이블마다 작성해야 한다.
- https://srssee.tistory.com/11
java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for
MyBatis가 호출하려는 SQL 문(statement)을 찾지 못할 때 나는 오류다. 호출한 이름은 네임스페이스.id 형식인데, 매퍼 XML의 namespace가 매퍼 인터페이스의 정규화된 이름과 다르거나, id가 메서드 이름과 다르거나, 매퍼 XML 파일 자체가 설정의 mapper-locations에 포함되지 않아 로딩되지 않은 경우가 대부분이다.
- https://blog.naver.com/PostView.nhn?blogId=javaking75&logNo=220315971085
logback 과 log4j
SLF4J는 로깅 API(퍼사드)이고, Logback과 Log4j 2는 실제로 로그를 쓰는 구현체다. Logback은 SLF4J와 같은 개발자가 만든 구현체로 SLF4J API를 직접 구현하며, Spring Boot의 기본 로깅 구현이다. 애플리케이션 코드가 SLF4J에만 의존하면 구현체를 바꿔도 코드는 그대로다. 설정은 logback-spring.xml에서 appender(콘솔, 파일, 롤링 정책)와 패키지별 레벨을 정한다.
- https://primetime.tistory.com/entry/Spring-Boot-%EB%A1%9C%EA%B7%B8-%EA%B4%80%EB%A6%AC-logback%EA%B3%BC-Slf4j
log4j 취약점
2021년 12월 공개된 Log4Shell(CVE-2021-44228)이다. Apache Log4j 보안 페이지에 따르면 Log4j 2의 JNDI 조회 기능 때문에, 로그 메시지나 파라미터를 조작할 수 있는 공격자가 LDAP 서버에서 불러온 임의 코드를 실행할 수 있었다. 사용자 입력(HTTP 헤더 등)을 그대로 로그에 남기는 흔한 코드가 공격 경로가 되어 영향 범위가 컸다. 이 CVE는 Java 8 이상 기준 2.15.0에서 고쳐졌지만 직후 CVE-2021-45046 같은 관련 취약점이 이어 공개되었으므로 최신 2.x로 올리는 것이 맞다. Log4j 1.x는 JNDI를 설정에서 쓸 때만 영향을 받지만(CVE-2021-4104) 2015년에 지원이 끝나 패치가 없다. Spring 블로그는 Spring Boot 사용자는 기본 로깅 시스템을 Log4j 2로 바꾼 경우에만 영향을 받는다고 안내했다. 기본 구성인 Logback은 대상이 아니다.
- https://nhj12311.tistory.com/575
Immutable vs Unmodifiable
Collections.unmodifiableList(list)가 돌려주는 것은 원본에 대한 읽기 전용 뷰다. 뷰를 통해 수정하면 UnsupportedOperationException이 나지만, 원본 리스트를 바꾸면 그 변경이 뷰에도 그대로 보인다. 반면 List.of(), List.copyOf()(Java 9, 10)나 Guava의 ImmutableList는 원본과 연결되지 않은 불변 컬렉션이라 누구도 내용을 바꿀 수 없다. 방어적 복사가 목적이라면 unmodifiable 뷰가 아니라 불변 복사본을 만들어야 한다.
1
2
3
4
5
List<String> source = new ArrayList<>(List.of("a"));
List<String> view = Collections.unmodifiableList(source);
List<String> copy = List.copyOf(source);
source.add("b");
// view -> [a, b], copy -> [a]
- https://www.baeldung.com/java-immutable-maps
- https://stackoverflow.com/questions/8892350/immutable-vs-unmodifiable-collection
댓글
아직 댓글이 없습니다