.NET 에서도 Reflection 의 성능때문에 Dynamic Method 라는 기법을 이용하여 동적으로 ILCode 를 생성하고, 그것을 실행하면 기존 Reflection 보다 4~100배 이상으로 빨라집니다.
이런 좋은 기능이 있는데, 안쓰면 바보겠죠? Java로 넘어 온 후, 실제 이런 기능의 필요성을 많이 못 느낄 정도로 iBatis 나 Hibernate 만을 사용했지만, 점점 JDBC 로우 레벨로 내려가다 보니, 꼭 필요하게 되는군요...
예를들어 객체 정보를 Map 으로 표현하고, Map으로 표현된 정보를 다른 객체 정보에 설정하려고 하는 기능 ( Mapper ) 는 상당히 많이 사용되기도 합니다. Model Mapper 라는 훌륭한 라이브러리가 있지만, 모든 것을 다 제공하는 게 아니고, 변형해서 쓰고자 하는 경우가 있어, 찾아 봤습니다.
여러가지 라이브러리가 있었지만, 작고 심플한 라이브러리를 찾다가 reflectasm 이란 놈을 발견했습니다.
소스를 보니 제가 원하는 딱 그 것이였습니다. byte code generation 을 통해 성능을 향상시킨다^^ 캬... Good
라이브러리를 가지고, 기존 .NET 코드와 유사하게 골격을 갖춰 봤습니다.
DynamicAccessor 라고 수형정보만 제공하면, 객체의 생성, 필드 정보 조회/수정, 메소드 실행 등을 할 수 있습니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 동적으로 객체의 속성, 메소드에 접근할 수 있는 접근자입니다. | |
* User: sunghyouk.bae@gmail.com | |
* Date: 13. 1. 21 | |
*/ | |
@Slf4j | |
public class DynamicAccessor<T> { | |
private final Class<T> targetType; | |
private final ConstructorAccess<T> ctorAccessor; | |
private final FieldAccess fieldAccessor; | |
private final MethodAccess methodAccessor; | |
private final List<String> fieldNames; | |
private final List<String> methodNames; | |
public DynamicAccessor(Class<T> targetType) { | |
Guard.shouldNotBeNull(targetType, "targetType"); | |
if (log.isDebugEnabled()) | |
log.debug(""); | |
this.targetType = targetType; | |
this.ctorAccessor = ConstructorAccess.get(this.targetType); | |
this.fieldAccessor = FieldAccess.get(this.targetType); | |
this.methodAccessor = MethodAccess.get(this.targetType); | |
this.fieldNames = Lists.newArrayList(fieldAccessor.getFieldNames()); | |
this.methodNames = Lists.newArrayList(methodAccessor.getMethodNames()); | |
} | |
@SuppressWarnings("unchecked") | |
public <T> T newInstance() { | |
return (T) ctorAccessor.newInstance(); | |
} | |
@SuppressWarnings("unchecked") | |
public <T> T newInstance(Object enclosingInstance) { | |
return (T) ctorAccessor.newInstance(enclosingInstance); | |
} | |
public String[] getFieldNames() { | |
return fieldAccessor.getFieldNames(); | |
} | |
public String[] getMethodNames() { | |
return methodAccessor.getMethodNames(); | |
} | |
public Object getField(Object instance, String fieldName) { | |
return fieldAccessor.get(instance, fieldName); | |
} | |
public void setField(Object instance, String fieldName, Object nv) { | |
fieldAccessor.set(instance, fieldName, nv); | |
} | |
public void setFieldBoolean(Object instance, String fieldName, boolean nv) { | |
fieldAccessor.setBoolean(instance, fieldAccessor.getIndex(fieldName), nv); | |
} | |
public void setFieldByte(Object instance, String fieldName, byte nv) { | |
fieldAccessor.setByte(instance, fieldAccessor.getIndex(fieldName), nv); | |
} | |
public void setFieldChar(Object instance, String fieldName, char nv) { | |
fieldAccessor.setChar(instance, fieldAccessor.getIndex(fieldName), nv); | |
} | |
public void setFieldDouble(Object instance, String fieldName, double nv) { | |
fieldAccessor.setDouble(instance, fieldAccessor.getIndex(fieldName), nv); | |
} | |
public void setFieldFloat(Object instance, String fieldName, float nv) { | |
fieldAccessor.setFloat(instance, fieldAccessor.getIndex(fieldName), nv); | |
} | |
public void setFieldInt(Object instance, String fieldName, int nv) { | |
fieldAccessor.setInt(instance, fieldAccessor.getIndex(fieldName), nv); | |
} | |
public void setFieldLong(Object instance, String fieldName, long nv) { | |
fieldAccessor.setLong(instance, fieldAccessor.getIndex(fieldName), nv); | |
} | |
public void setFieldShort(Object instance, String fieldName, short nv) { | |
fieldAccessor.setShort(instance, fieldAccessor.getIndex(fieldName), nv); | |
} | |
public Object getProperty(Object instance, String fieldName) { | |
String methodName = | |
(methodNames.contains(fieldName)) ? fieldName : "get" + getPropertyName(fieldName); | |
return invoke(instance, methodName); | |
} | |
public void setProperty(Object instance, String fieldName, Object nv) { | |
String methodName = | |
(methodNames.contains(fieldName)) ? fieldName : "set" + getPropertyName(fieldName); | |
invoke(instance, methodName, nv); | |
} | |
public Object invoke(Object instance, String methodName, Object... args) { | |
return methodAccessor.invoke(instance, methodName, args); | |
} | |
private static final String getPropertyName(String filedName) { | |
return filedName.substring(0, 1).toUpperCase() + filedName.substring(1); | |
} | |
} |
동적으로 특정 수형의 속성이나 메소드 실행하는 것은 환경설정이나 사용자 매크로 등을 파싱하여 실제 클래스를 수행하게 할 때 아주 유용합니다.
다음 코드는 DynamicAccessor 를 생성해주는 Factory입니다. 굳이 factory를 만든 이유는 DynamicAccessor 생성 비용이 일반 클래스의 생성 비용에 비하여 상당한 비용이 들어가므로, Cache 를 이용하여, 재활용하자는 의미가 큽니다. Cache는 google guava의 LoadingCache 를 사용한 이유는 Cache 자체적으로 항목들을 관리할 수 있어, 코드량 및 실수가 적어지는 것이 장점입니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* {@link DynamicAccessor} 의 생성자입니다. | |
* User: sunghyouk.bae@gmail.com | |
* Date: 13. 1. 21 | |
*/ | |
@Slf4j | |
public class DynamicAccessorFactory { | |
private static final CacheLoader<Class<?>, DynamicAccessor> loader; | |
private static final LoadingCache<Class<?>, DynamicAccessor> cache; | |
static { | |
loader = new CacheLoader<Class<?>, DynamicAccessor>() { | |
@Override | |
@SuppressWarnings("unchecked") | |
public DynamicAccessor<?> load(Class<?> type) throws Exception { | |
return new DynamicAccessor(type); | |
} | |
}; | |
cache = CacheBuilder.newBuilder().build(loader); | |
} | |
@SuppressWarnings("unchecked") | |
public static <T> DynamicAccessor<T> create(Class<T> targetType) { | |
try { | |
return (DynamicAccessor<T>) cache.get(targetType); | |
} catch (ExecutionException e) { | |
if (log.isErrorEnabled()) | |
log.error("DynamicAccessor 를 생성하는데 실패했습니다. targetType=" + targetType.getName(), e); | |
return null; | |
} | |
} | |
public static void clear() { | |
synchronized (cache) { | |
cache.cleanUp(); | |
} | |
} | |
} |
마지막으로 DynamicAccessor 를 테스트하는 코드입니다. (reflectasm 에 성능 측정 코드가 있어 굳이 만들지 않았습니다)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Slf4j | |
public class DynamicAccessorTest { | |
@lombok.Getter | |
@lombok.Setter | |
static class User { | |
private String email; | |
private Double age; | |
public User() {} | |
public void includeAge(int delta) { | |
age += delta; | |
} | |
} | |
@Test | |
public void dynamicInstancing() { | |
DynamicAccessor<User> userAccessor = DynamicAccessorFactory.create(User.class); | |
Assert.assertNotNull(userAccessor); | |
Object user = userAccessor.newInstance(); | |
userAccessor.setProperty(user, "email", "sunghyouk.bae@gmail.com"); | |
userAccessor.setProperty(user, "age", 110.0); | |
Assert.assertEquals("sunghyouk.bae@gmail.com", userAccessor.getProperty(user, "email")); | |
Assert.assertEquals(110.0, userAccessor.getProperty(user, "age")); | |
} | |
} |
댓글 없음:
댓글 쓰기