Java에서 클래스가 Serializable을 올바르게 구현하는지 테스트하는 방법 (단지 Serializable의 인스턴스가 아님)
Serializable 클래스를 구현하고 있습니다 (따라서 RMI와 함께 사용하는 값 객체입니다). 그러나 나는 그것을 테스트해야한다. 쉽게 할 수있는 방법이 있습니까?
설명 : 클래스를 구현하고 있으므로 클래스 정의에 Serializable을 고수하는 것은 간단합니다. 작동하는지 확인하려면 수동으로 직렬화 / 역 직렬화해야합니다.
이 C # 질문을 찾았 습니다. Java에 대한 유사한 답변이 있습니까?
쉬운 방법은 객체가 java.io.Serializable
또는 의 인스턴스인지 확인하는 java.io.Externalizable
것이지만, 실제로 객체가 실제로 직렬화 가능하다는 것을 증명하지는 않습니다.
확실하게하는 유일한 방법은 실제로 시도하는 것입니다. 가장 간단한 테스트는 다음과 같습니다.
new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(myObject);
예외가 발생하지 않는지 확인하십시오.
Apache Commons Lang 은보다 간단한 버전을 제공합니다.
SerializationUtils.serialize(myObject);
다시 예외를 확인하십시오.
여전히 더 엄격 할 수 있으며 원본과 동일한 것으로 역 직렬화되는지 확인할 수 있습니다.
Serializable original = ...
Serializable copy = SerializationUtils.clone(original);
assertEquals(original, copy);
등등.
skaffman의 답변을 기반으로 한 유틸리티 방법 :
private static <T extends Serializable> byte[] pickle(T obj)
throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
return baos.toByteArray();
}
private static <T extends Serializable> T unpickle(byte[] b, Class<T> cl)
throws IOException, ClassNotFoundException
{
ByteArrayInputStream bais = new ByteArrayInputStream(b);
ObjectInputStream ois = new ObjectInputStream(bais);
Object o = ois.readObject();
return cl.cast(o);
}
짧은 대답은 몇 가지 후보 객체를 생각해 내고 실제로 선택한 메커니즘을 사용하여 직렬화를 시도 할 수 있다는 것입니다. 여기서 테스트는 마샬링 / 마샬링 해제 중에 오류가 발생하지 않으며 결과 "재수 화"개체가 원본과 동일하다는 것입니다.
또는 후보 객체가없는 경우 클래스의 (비 정적, 비 일시적) 필드를 조사하여 직렬화 가능 여부를 확인하는 반사 기반 테스트를 구현할 수 있습니다. 경험으로 말하면, 이것은 놀랍도록 빠르게 복잡해 지지만 합리적인 범위에서 할 수 있습니다.
이 후자의 접근 방식의 단점은 필드가 예 List<String>
이면 엄격하게 직렬화 할 수 있는 필드가 없기 때문에 클래스를 실패하거나 단순히 List의 직렬화 구현이 사용된다고 가정 할 수 있다는 것입니다. 둘 다 완벽하지 않습니다. (후자의 문제는 예제에도 존재합니다. 테스트에 사용 된 모든 예제가 직렬화 가능한 목록을 사용하는 경우 실제로 다른 코드에서 직렬화 불가능한 버전을 사용하는 것을 막을 수있는 방법은 없습니다).
이 코드는 그것을해야합니다 ...
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
public class Main
{
public static void main(String[] args)
{
System.out.println(isSerializable("Hello"));
System.out.println(isSerializable(new Main()));
}
public static boolean isSerializable(final Object o)
{
final boolean retVal;
if(implementsInterface(o))
{
retVal = attemptToSerialize(o);
}
else
{
retVal = false;
}
return (retVal);
}
private static boolean implementsInterface(final Object o)
{
final boolean retVal;
retVal = ((o instanceof Serializable) || (o instanceof Externalizable));
return (retVal);
}
private static boolean attemptToSerialize(final Object o)
{
final OutputStream sink;
ObjectOutputStream stream;
stream = null;
try
{
sink = new ByteArrayOutputStream();
stream = new ObjectOutputStream(sink);
stream.writeObject(o);
// could also re-serilalize at this point too
}
catch(final IOException ex)
{
return (false);
}
finally
{
if(stream != null)
{
try
{
stream.close();
}
catch(final IOException ex)
{
// should not be able to happen
}
}
}
return (true);
}
}
다음 테스트를 수행 할 수 있습니다.
- 객체를 파일로 직렬화하고 예외가 발생하지 않는지 확인합니다.
- 또한 개체를 역 직렬화하고 원래 개체와 비교합니다.
다음은 객체를 파일로 직렬화 및 역 직렬화하는 예입니다.
http://www.rgagnon.com/javadetails/java-0075.html
http://www.javapractices.com/topic/TopicAction.do?Id=57
이것은 완전히 채워진 객체에 대해서만 작동합니다. 최상위 객체에서 구성된 객체도 직렬화 가능해야하는 경우 직렬화 / 역 직렬화가 널 객체를 건너 뛰기 때문에이 테스트가 유효하려면 널이 될 수 없습니다.
RMI와 함께 사용하기 위해 주어진 인터페이스가 실제로 완전히 직렬화 가능한지 확인할 수있는 단위 테스트 (Spock을 사용하는 Groovy에서)를 작성하려고했습니다. 모든 매개 변수, 예외 및 메서드에 정의 된 유형의 가능한 구현입니다.
지금까지는 저에게 효과가있는 것 같지만,이 작업은 조금 어리 석고 이것이 다루지 않는 경우가있을 수 있으므로 위험을 감수하고 사용하십시오!
예제 인터페이스 Notification
등을 자신의 것으로 교체해야합니다 . 이 예제에는 직렬화 할 수없는 하나의 필드가 그림으로 포함되어 있습니다.
package example
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import spock.lang.Specification
import java.lang.reflect.*
import java.rmi.Remote
import java.rmi.RemoteException
/** This checks that the a remoting API NotifierServer is safe
*
* It attempts to flush out any parameter classes which are
* not Serializable. This isn't checked at compile time!
*
*/
@CompileStatic
class RemotableInterfaceTest extends Specification {
static class NotificationException extends RuntimeException {
Object unserializable
}
static interface Notification {
String getMessage()
Date getDate()
}
static interface Notifier extends Remote {
void accept(Notification notification) throws RemoteException, NotificationException
}
static interface NotifierServer extends Remote {
void subscribe(Notification notifier) throws RemoteException
void notify(Notification message) throws RemoteException
}
// From https://www.javaworld.com/article/2077477/learn-java/java-tip-113--identify-subclasses-at-runtime.html
/**
* Scans all classes accessible from the context class loader which belong to the given package and subpackages.
*
* @param packageName The base package
* @return The classes
* @throws ClassNotFoundException
* @throws IOException
*/
static Class[] getClasses(String packageName)
throws ClassNotFoundException, IOException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader()
assert classLoader != null
String path = packageName.replace('.', '/')
Enumeration resources = classLoader.getResources(path)
List<File> dirs = new ArrayList()
while (resources.hasMoreElements()) {
URL resource = resources.nextElement()
dirs.add(new File(resource.getFile()))
}
ArrayList classes = new ArrayList()
for (File directory : dirs) {
classes.addAll(findClasses(directory, packageName))
}
return classes.toArray(new Class[classes.size()])
}
/**
* Recursive method used to find all classes in a given directory and subdirs.
*
* @param directory The base directory
* @param packageName The package name for classes found inside the base directory
* @return The classes
* @throws ClassNotFoundException
*/
static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
List<Class> classes = new ArrayList()
if (!directory.exists()) {
return classes
}
File[] files = directory.listFiles()
for (File file : files) {
if (file.isDirectory()) {
//assert !file.getName().contains(".");
classes.addAll(findClasses(file, packageName + "." + file.getName()))
} else if (file.getName().endsWith(".class")) {
classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)))
}
}
return classes
}
/** Finds all known subclasses of a class */
@CompileDynamic
static List<Class> getSubclasses(Class type) {
allClasses
.findAll { Class it ->
!Modifier.isAbstract(it.modifiers) &&
it != type &&
type.isAssignableFrom(it)
}
}
/** Checks if a type is nominally serializable or remotable.
*
* Notes:
* <ul>
* <li> primitives are implicitly serializable
* <li> interfaces are serializable or remotable by themselves, but we
* assume that since #getSerializedTypes checks derived types of interfaces,
* we can safely assume that all implementations will be checked
*</ul>
*
* @param it
* @return
*/
static boolean isSerializableOrRemotable(Class<?> it) {
return it.primitive || it.interface || Serializable.isAssignableFrom(it) || Remote.isAssignableFrom(it)
}
/** Recursively finds all (new) types associated with a given type
* which need to be serialized because they are fields, parameterized
* types, implementations, etc. */
static void getSerializedTypes(final Set<Class<?>> types, Type... it) {
for(Type type in it) {
println "type: $type.typeName"
if (type instanceof GenericArrayType) {
type = ((GenericArrayType)type).genericComponentType
}
if (type instanceof ParameterizedType) {
ParameterizedType ptype = (ParameterizedType)type
getSerializedTypes(types, ptype.actualTypeArguments)
break
}
if (type instanceof Class) {
Class ctype = (Class)type
if (ctype == Object)
break
if (types.contains(type))
break
types << ctype
for (Field field : ctype.declaredFields) {
println "${ctype.simpleName}.${field.name}: ${field.type.simpleName}"
if (Modifier.isVolatile(field.modifiers) ||
Modifier.isTransient(field.modifiers) ||
Modifier.isStatic(field.modifiers))
continue
Class<?> fieldType = field.type
if (fieldType.array)
fieldType = fieldType.componentType
if (types.contains(fieldType))
continue
types << fieldType
if (!fieldType.primitive)
getSerializedTypes(types, fieldType)
}
if (ctype.genericSuperclass) {
getSerializedTypes(types, ctype.genericSuperclass)
}
getSubclasses(ctype).each { Class c -> getSerializedTypes(types, c) }
break
}
}
}
/** Recursively checks a type's methods for related classes which
* need to be serializable if the type is remoted */
static Set<Class<?>> getMethodTypes(Class<?> it) {
Set<Class<?>> types = []
for(Method method: it.methods) {
println "method: ${it.simpleName}.$method.name"
getSerializedTypes(types, method.genericParameterTypes)
getSerializedTypes(types, method.genericReturnType)
getSerializedTypes(types, method.genericExceptionTypes)
}
return types
}
/** All the known defined classes */
static List<Class> allClasses = Package.packages.collectMany { Package p -> getClasses(p.name) as Collection<Class> }
@CompileDynamic
def "NotifierServer interface should only expose serializable or remotable types"() {
given:
Set<Class> types = getMethodTypes(NotifierServer)
Set<Class> nonSerializableTypes = types.findAll { !isSerializableOrRemotable(it) }
expect:
nonSerializableTypes.empty
}
}
'Programing' 카테고리의 다른 글
Carthage로 Cartfile에서 하나의 라이브러리 만 업데이트하는 방법은 무엇입니까? (0) | 2020.11.01 |
---|---|
JS 객체에 대한 JSON 문자열 (0) | 2020.11.01 |
리눅스 정규식 찾기 (0) | 2020.11.01 |
숫자 만 허용하도록 QLineEdit 설정 (0) | 2020.11.01 |
신속하게 터치하거나 클릭 한 tableView 셀을 감지하는 방법 (0) | 2020.11.01 |