Ответы на вопросы по ревью 4. Java io. Ключевым понятием здесь является понятие потока
Скачать 1.93 Mb.
|
Проверка десериализованного объекта, ObjectInputValidation и validateObjectЕсли есть необходимость выполнения контроля за значениями десериализованного/восстановленного объекта, то можно использовать интерфейс ObjectInputValidation с переопределением метода validateObject. В следующем листинге представлены изменения, которые следует внести в описание класса Person, чтобы контролировать возраст. // Добавление интерфейса ObjectInputValidation public class Person implements java.io.Serializable, java.io.ObjectInputValidation { ... @Override public void validateObject() throws InvalidObjectException { if ((age < 39) || (age > 60)) throw new InvalidObjectException("Invalid age"); } } Если вызвать метод validateObject после десериализации объекта, то будет вызвано исключение InvalidObjectException при значении возраста за пределами 39...60. Подписывание сериализованных данныхЧтобы убедиться, что данные не были изменены в файле или при пересылке по сети их можно «подписать». Несмотря на то, что управление подписями реализовать можно и с помощью методов writeObject и readObject, для этого есть более подходящий способ. Если требуется зашифровать и подписать объект, то проще всего поместить его в оберточный класс javax.crypto.SealedObject и/или java.security.SignedObject. Данные классы являются сериализуемыми, поэтому при оборачивании объекта в SealedObject создается подобие "подарочной упаковки" вокруг исходного объекта. Для шифрования необходимо создать симметричный ключ, управление которым должно осуществляться отдельно. Аналогично, для проверки данных можно использовать класс SignedObject, для работы с которым также нужен симметричный ключ, управляемый отдельно. Эти два объекта позволяют упаковывать и подписывать сериализованные данные, не отвлекаясь на детали проверки и шифрования цифровых подписей. Листинг теста подписи объекта@Test public void testSigning() { try { //Generate a 1024-bit Digital Signature Algorithm (DSA) key pair KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); keyPairGenerator.initialize(1024); KeyPair keyPair = keyPairGenerator.genKeyPair(); PrivateKey privateKey = keyPair.getPrivate(); PublicKey publicKey = keyPair.getPublic(); // Подписывать можно только сериализуемый объект String unsignedObject = alex.toString(); Signature signature = Signature.getInstance(privateKey.getAlgorithm()); SignedObject signedObject = new SignedObject(unsignedObject, privateKey, signature); // Verify the signed object signature = Signature.getInstance(publicKey.getAlgorithm()); boolean verified = signedObject.verify(publicKey, signature); assertTrue("Проверка 'подписанного' объекта", verified); // Retrieve the object unsignedObject = (String) signedObject.getObject(); assertEquals("Проверка описания 'подписанного' объекта", unsignedObject, alex.toString()); } catch (SignatureException e) { } catch (InvalidKeyException e) { } catch (NoSuchAlgorithmException e) { } catch (ClassNotFoundException e) { } catch (IOException e) { fail("Exception thrown during test: " + e.toString()); } } Прокси-класс для сериализацииИногда класс может включать элемент, который позволяет получить значения отдельных полей класса по определенному алгоритму. В этих случаях необязательно сериализовывать весь объект. Можно было бы пометить восстанавливаемые поля как «транзитные». Однако в классе всё равно требуется явно указывать код (определять метод), который при обращении к полю каждый раз проверял бы его инициализацию. Для этих целей лучше использовать специальный прокси-класс, из которого можно восстановить объект. Листинг прокси-классаВ прокси-классе определим метод readResolve, которой будет вызываться во время десериализации объекта, чтобы вернуть объект-замену. Конструктор прокси-класса будет упаковывать объект PersonY во внутреннее поле data. public class PersonProxy implements java.io.Serializable { private static final long serialVersionUID = 1L; public String data; public PersonProxy(PersonY original) { data = original.getFirstName() + "," + original.getLastName() + "," + String.valueOf(original.getAge()); if (original.getSpouse() != null) { PersonY spouse = original.getSpouse(); data = data + "," + spouse.getFirstName() + "," + spouse.getLastName() + "," + String.valueOf(spouse.getAge()); } } private Object readResolve() throws java.io.ObjectStreamException { String[] pieces = data.split(","); PersonY result = new PersonY(pieces[0], pieces[1], Integer.parseInt(pieces[2])); if (pieces.length > 3) { result.setSpouse(new PersonY(pieces[3], pieces[4], Integer.parseInt(pieces[5]))); result.getSpouse().setSpouse(result); } return result; } } Класс PersonY создадим на основе базового класса Person с добавлением метода writeReplace следующего вида : private Object writeReplace() throws java.io.ObjectStreamException { return new PersonProxy(this); } Вместе методы writeReplace и readResolve позволяют классу PersonY упаковывать все данные (или их наиболее важную часть) в объект класса PersonProxy, помещать его в поток и затем распаковать его при десериализации. Пример тестирования прокси-класса JUnitPersonProxypublic class JUnitPersonProxy { private static final String FILE_proxy = "proxy.ser"; private static final String FNAME_Alex = "Алексей" ; private static final String FNAME_Olga = "Ольга" ; private static final String LAST_NAME = "Иванов" ; private static final int AGE_Alex = 39 ; private static PersonY alex = null ; private static PersonY olga = null ; @BeforeClass public static void setUpBeforeClass() throws Exception { try { alex = new PersonY(FNAME_Alex, LAST_NAME, AGE_Alex); olga = new PersonY(FNAME_Olga, LAST_NAME, 38); alex.setSpouse(olga); olga.setSpouse(alex); // Сохранение сериализованных прокси-объектов PersonProxy proxy_alex = new PersonProxy(alex); PersonProxy proxy_olga = new PersonProxy(olga); FileOutputStream fos = new FileOutputStream(FILE_proxy); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(proxy_alex); oos.writeObject(proxy_olga); oos.close(); } catch (Exception e) { fail("Exception thrown during test: " + e.toString()); } } @AfterClass public static void tearDownAfterClass() throws Exception { // Удаление файла new File(FILE_proxy).delete(); } @Test public void testProxy() { try { FileInputStream fis = new FileInputStream(FILE_proxy); ObjectInputStream ois = new ObjectInputStream(fis); PersonY alex = (PersonY) ois.readObject(); PersonY olga = (PersonY) ois.readObject(); ois.close(); assertEquals(alex.getFirstName(), FNAME_Alex); assertEquals(alex.getLastName() , LAST_NAME); assertEquals(olga.getFirstName(), FNAME_Olga); assertEquals(olga.getFirstName(), FNAME_Olga); assertEquals(alex.getAge() , AGE_Alex); assertEquals(alex.getSpouse().getFirstName(), FNAME_Olga); // Описание объекта } catch (Exception e) { fail("Exception thrown during test: " + e.toString()); } } } |