Lombok @Getter(Class Level) Ignore Stragegy - 오버라이드 된 메소드와 대소문자를 구분하지 않고 중복 비교
상황은 이러하다.
User 클래스에 userName 이라는 이름의 private 인스턴스 멤버를 선언하고 해당 클래스 레벨에 @Getter를 적용하였다.
(필드 레벨에는 이러한 현상이 발생하지 않는다.)
또한 해당 User클래스는 Security에서 지원하는 UserDetails 인터페이스를 구현하였고 getUsername() 메소드를 오버라이딩 하였다.
@Getter
@Setter
public class User implements UserDetails {
private String userName; // 롬복의 @Getter에 의해 getUserName() 생성
@Override
public String getUsername() {
return this.userName;
}
}
이후 특정 클래스를 생성하여 해당 User 인스턴스 객체로부터 UserDetails를 구현하여 오버라이딩 한 getUsername() 메소드와 Lombok에 의해 생성된 User의 인스턴스 필드의 getter인 getUserName()을 각각 호출하였다.
public class Example {
public void access() {
User user = new User();
user.getUsername(); // 정상
user.getUserName(); // IDE에서 메소드를 호출하지 못한다 (롬복에서 무시됨)
}
}
여기서 발생한 오류는 인터페이스를 구현한 Overriding된 getUsername() 메소드는 정상적으로 호출되었으나, User의 인스턴스 멤버인 userName에 대한 Lombok이 적용된 getter 메소드 getUserName()은 호출되지 않았다. (정의되지 않음..)
이에 다음 두가지 유추를 해봤다.
- Spring Security에 의해 UserDetails를 구현한 클래스의 username 필드만 제한적으로 처리되는가?
- Reflection에 의해 컴파일 시점에 메소드의 대소문자를 구분하여 중복을 체크하는가?
조금 더 자세히 알아보려고 노력했더니, 두번째 유추와 근접한 내용을 알게 되었다.
Lombok은 내부적으로 @Getter가 적용된 클래스의 인스턴스 필드의 getter 메소드를 생성할 때 구현한 인터페이스의 모든 메소드를 기준으로 @Getter가 적용된 모든 필드의 getter메소드의 메소드명을
대/소문자 구분 없이 비교하여 Overriding되어 있다면 Lombok에서 무시한다는 사실을 알게 되었다.
1.handle() : lombok/src/core/lombok/javac/handlers/HandleGetter.java [Line127]
- 153Line의 createGetterForFields() 호출
2. createGetterForField() : lombok/src/core/lombok/javac/handlers/HandleGetter.java [Line162]
- Line202에서 methodExists() 메소드를 호출
3. methodExists() : lombok/src/core/lombok/javac/handlers/EclipseHandlerUtil.java [Line1889]
- Line1900에서 equalsIgnoreCase 메소드를 호출
- 해당 메소드에 의해 대소문자를 구분하지 않고 정의되어있는지 체킹한다.
- https://github.com/projectlombok/lombok/blob/master/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java#L1900
public static MemberExistsResult methodExists(String methodName, EclipseNode node, boolean caseSensitive, int params) {
while (node != null && !(node.get() instanceof TypeDeclaration)) {
node = node.up();
}
if (node != null && node.get() instanceof TypeDeclaration) {
TypeDeclaration typeDecl = (TypeDeclaration)node.get();
if (typeDecl.methods != null) top: for (AbstractMethodDeclaration def : typeDecl.methods) {
if (def instanceof MethodDeclaration) {
char[] mName = def.selector;
if (mName == null) continue;
boolean nameEquals = caseSensitive ? methodName.equals(new String(mName)) : methodName.equalsIgnoreCase(new String(mName));
if (nameEquals) {
if (params > -1) {
int minArgs = 0;
int maxArgs = 0;
if (def.arguments != null && def.arguments.length > 0) {
minArgs = def.arguments.length;
if ((def.arguments[def.arguments.length - 1].type.bits & ASTNode.IsVarArgs) != 0) {
minArgs--;
maxArgs = Integer.MAX_VALUE;
} else {
maxArgs = minArgs;
}
}
if (params < minArgs || params > maxArgs) continue;
}
if (isTolerate(node, def)) continue top;
return getGeneratedBy(def) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK;
}
}
}
}
return MemberExistsResult.NOT_EXISTS;
}
}
boolean nameEquals = caseSensitive ? methodName.equals(new String(mName)) : methodName.equalsIgnoreCase(new String(mName));
if (nameEquals) {
if (params > -1) {
int minArgs = 0;
int maxArgs = 0;
if (def.arguments != null && def.arguments.length > 0) {
minArgs = def.arguments.length;
if ((def.arguments[def.arguments.length - 1].type.bits & ASTNode.IsVarArgs) != 0) {
minArgs--;
maxArgs = Integer.MAX_VALUE;
} else {
maxArgs = minArgs;
}
}
if (params < minArgs || params > maxArgs) continue;
}
if (isTolerate(node, def)) continue top;
return getGeneratedBy(def) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK;
}
이러한 현상은 Abstract 추상 클래스의 추상메소드 또한 동일하게 적용된다