학습 계기
최근 업무중에 만난 코드에서 궁금증이 생겼었습니다.
Static Nested Class에 @Service 애너테이션이 적용되어 있고,
해당 클래스 내부에 @Transactional이 적용되어 있어서,
왜 중첩클래스? 왜 static 선언? 이게 프록시가 적용되나? 등의 의문이 있었는데
마침 토비의 스프링 채널에 업로드된 Nested Class 관련 영상이 있어서 학습하고 정리해봤습니다.
용어 정리
- Top-level Class vs Nested Class
- Static Nested Class vs Non-static Nested Class (inner class)
- Local Class
- Anonymous Class
이러한 용어들에 대해 아직 정확히 구분하지 못하고 있었다는 걸 배웠네요.
관련하여 쉐도잉에 대해 표현만 들어보고 무슨 개념인지 모르고 있었는데 이번에 정확히 이해했습니다.
토비님이 추천해주신 오라클 튜토리얼이 확실히 도움이 된다는 걸 느꼈습니다.
주절주절 글로 나열하기 보다 코드로 보는 게 나을 것 같아 코드와 주석으로 내용을 남깁니다.
/**
* Top-level Class
* - 다른 클래스나 인터페이스 내부에 존재하지 않는다면 Top-level 클래스이다
* - public 또는 default 접근제한자만 가능하다
* - 자바 소스 파일 당 하나의 public top-level 클래스만 선언 가능하다
* - default top-level 클래스는 여러 개 선언 가능하다
* - static 선언이 불가하다
*/
/**
* protected, private 접근제한자는 top-level 클래스의 접근제한자로 사용될 수 없다
*/
//protected class ProtectedTopLevelClass{}
//private class ProtectedTopLevelClass{}
/**
자바 소스파일과 이름이 다른 public top-level 클래스는 컴파일 에러
*/
//public class SecondPublicTopLevelCalss {}
/**
접근제한자가 default인 top-level 클래스는 다수 선언 가능
*/
class DefaultTopLevelClass{}
class SecondDefaultTopLevelClass{}
/**
inside TopLevelClass.java
public Top-level class는 자바 소스파일 당 1개만 선언 가능
*/
public class TopLevelClass {
private final AtomicLong id = new AtomicLong(0);
private final String city = "seoul";
/**
Nested Class
- 다른 클래스 내부에 선언되는 클래스를 Nested Class, 중첩 클래스 라고 한다
- Nested 클래스는 이를 감싸는 바깥쪽 클래스의 멤버이다
- Nested 클래스는 static nested class와 non-static nested class로 나뉜다
- non-static nested class 는 inner class 라고도 부른다
- top-level 클래스와 달리, protected, private 접근제한자도 선언 가능하다
- 가독성, 유지보수성, 캡슐화, 한 곳에서만 사용되는 클래스들의 논리적 그룹화 등의 목적으로 사용할 수 있다
*/
protected static class StaticNestedClass {
/**
Static Nested Class
- static nested class는 바깥쪽 클래스의 멤버에 접근권한이 없다
- 패키징 편의를 위해 다른 클래스 내부에 선언되었을 뿐, Top-level 클래스의 동일하게 인스턴스를 생성할 수 있다
- 즉, non-static nested class와 달리, Top-level 클래스 인스턴스를 통하지 않고 직접 인스턴스를 생성할 수 있다
- final var staticNestedClassInstance = new StaticNestedClass();
- 바깥쪽 클래스의 non-static 필드에 static 참조는 불가하지만, object 참조는 가능하다.
*/
// void printIdByStaticReference() {
// System.out.println(id.getAndIncrement());
// }
void printIdByObjectReference(final TopLevelClass topLevelClass) {
System.out.println("topLevelClass.id.getAndIncrement() = " + topLevelClass.id.getAndIncrement());
}
}
private class NonStaticNestedClassWhichIsInnerClass{
/**
Non-Static Nested Class ( == Inner Class )
- non-static nested class(inner class)는 바깥쪽 클래스의 멤버에 접근권한이 있다
- JDK 16부터 non-static nested class에 static 멤버를 선언할 수 있게 됐다
- Non-Static Nested Class 인스턴스를 생성하려면, 바깥쪽 클래스 인스턴스를 통해야 한다
- final var innerClassInstance = new TopLevelClass().new NonStaticNestedClassWhichIsInnerClass();
- non-static nested class(inner class)에는 Serialization을 사용하지 않는 게 강력히 권고된다
*/
static String NAME = "Richard"; // static member in inner class is available since JDK 16
private final String city = "busan";
void printId() {
System.out.println(id.getAndIncrement()); // inner class has access to its outer class' member
}
/**
shadowing
- 위에서 아래로 순서대로 각각 전달된 파라미터, inner class의 필드, enclosing class의 필드를 출력한다
- 불필요한 혼란을 야기할 수 있으니 피하자
*/
void printCity(final String city) {
System.out.println("city = " + city);
System.out.println("city = " + this.city);
System.out.println("city = " + TopLevelClass.this.city);
}
}
}
스프링 빈 등록 여부
public class Outer {
@Service
public static class PublicStaticNestedClass { // Case 1
@Service
public class InnerClassInPublicStaticNestedClass {} // Case 2
}
@Service
static class DefaultStaticNestedClass { // Case 3
@Service
public class InnerClassInDefaultStaticNestedClass {} // Case4
}
@Service
public class PublicInnerClass{} // Case 5
@Service
class DefaultInnerClass {} // Case 6
}
@Service
class DefaultTopLevelClass {} // Case7
@Service
class PublicTopLevelClass {} // Case8
이하는 Outer 클래스가 스프링 빈으로 등록되지 않는 상황을 전제했습니다.
- Case 1 : Public Static Nested Class : 스프링 빈으로 등록됩니다.
- Case 2 : Case 1 내부에 선언된 Non-Static Nested Class, 즉 Inner Class 입니다. 스프링 빈으로 등록됩니다.
- Case 3,4 : Case 1에서 public 선언만 default로 변경된 케이스입니다. 둘 다 스프링 빈으로 등록됩니다.
- Case 5,6 : Inner class, 즉 non-static nested class 입니다. 스프링 빈으로 등록되지 않습니다.
- Outer Class의 접근제한자가 public임에도, 스프링 빈이 아니므로 등록되지 않습니다.
- Case 7,8 : 자바 소스파일과 이름이 다른 Top-level class입니다. 스프링 빈으로 등록됩니다.
사실 지금도 위 내용이 명쾌하게 해석이되진 않습니다만, 나름대로 해석해보자면 아래와 같습니다.
- Case 1과 3은 static nested class 이므로, 별도의 자바 소스파일에 선언한 것과 동일하다
- 따라서 그 자신도 스프링 빈으로 등록되고, 뒤이어 Case 2,4도 스프링 빈으로 등록된다.
- Case 5,6은 Outer Class가 스프링 빈으로 등록되지 않았기에 스프링 빈으로 등록되지 않는다.
- Outer가 public이기에 외부에서 Case 5,6 모두 접근 및 인스턴스 생성이 가능하다.
- 그러나 Outer가 스프링 빈으로 등록되지 않는다면 Case 5,6도 등록되지 않는다.
- Outer에 @Service 선언 등을 사용해 스프링 빈으로 등록한다면, Case 5,6도 등록된다.
- Case 7,8은 외부 자바 소스파일에 선언한 것과 동일하다. 스프링 빈으로 등록된다.
컴파일 된 결과를 보면, 자바 소스파일과 이름이 다른 Top-level 클래스는
별도의 class 파일로 생성되었음을 알 수 있습니다.
정리
- 자바 클래스는 Top-level 클래스와 Nested 클래스로 나뉜다
- Nested 클래스에는 static nested class와 non-static nested class, local class, anonymouse class가 있다.
- non-static nested class를 inner class라고도 한다
- nested 클래스를 사용하는 이유는 캡슐화, 논리적 그룹화, 유지보수성, 가독성 등의 이유가 있다.
- nested 클래스를 사용하면 변수명이 일치할 경우 쉐도잉이 발생하므로 유의해야 한다.
- static nested 클래스, 자바 소스파일과 이름이 다른 top-level 클래스는 별도의 파일에 선언한 것과 거의 동일하다.
- 따라서 @Service 등의 애너테이션 사용시, Outer 클래스나 자바 소스파일과 이름이 같은 top-level 클래스와 무관하게 스프링 빈으로 등록된다.
- inner class, 즉 non-static nested class는 바깥쪽 클래스가 스프링 빈으로 등록되지 않으면 스프링 빈으로 등록되지 않는다.
학습 출처
자바의 내부 클래스는 스프링 빈이 될 수 있을까? by 토비의 스프링
Nested Classes by Oracle Java Tutorial
틀렸거나 보완할 내용이 있다면 알려주세요! 감사합니다.
'Java & Spring' 카테고리의 다른 글
Spring Cache Abstraction (0) | 2022.11.28 |
---|---|
응답 DTO, N+1, Open Session In View (2) | 2022.11.27 |
Spring 외부 설정 파일 import 및 프로퍼티 리팩터링 (2) | 2022.11.17 |
🙌 쓰레드풀을 이용해 두 비동기 작업 처리 완료 후 후속 처리하기 (0) | 2022.09.29 |
🥄 쓰레드풀 한 스푼 (2) | 2022.09.29 |