[an error occurred while processing this directive] IT • archiv :: Print

IT • archiv


[an error occurred while processing this directive]

[an error occurred while processing this directive]

Проверка данных на чистом Java

[an error occurred while processing this directive](none) [an error occurred while processing this directive](none)[an error occurred while processing this directive] ::
[an error occurred while processing this directive](none)
[an error occurred while processing this directive]([an error occurred while processing this directive] Виктор Окунев [an error occurred while processing this directive])

[an error occurred while processing this directive](none)

Строим основательную среду проверки правильности ввода данных на базовом API Java.

Data Validation
PDF versionPDF версия
Обзор
Нельзя преувеличить важность использования хорошей среды проверки правильности данных. В конечном счете, возможно, кто-то будет многократно использовать классы, которые мы создаем. Если наши объекты сопровождаются встроенным, устойчивым механизмом проверки правильности данных, который не требует большого обслуживания, необходимо будет уведомить такого пользователя относительно диапазонов достоверных данных, воспринимаемых этими объектами. Ядро API Java имеет все, что должно решить эту проблему самым лучшим способом. В этой статье, мы построим основу для среды проверки правильности данных, на основе которой можно будет успешно реализовывать крупномасштабные коммерческие проекты. (1500 слов)

Идея контролируемых свойств в Java не нова. Со времен JDK 1.1, легионы разработчиков JavaBeans использовали мощную структуру, содержащуюся в библиотеке java.beans, чтобы сформулировать правила проверки правильности данных. К сожалению, те из нас, кто не занимаются напрямую созданием "программных компонентов многократного использования, которыми можно визуально управлять в средах разработки", зачастую пытаются повторно изобретать колесо, отказываясь от основного подхода Java в пользу различных частных решений. В области реализации правил проверки правильности данных можно бесконечно реализовывать творческий потенциал. Интересный подход, основанный на применении XML был недавно предложен в серии статей "Проверка XML на Java с использованием схем" в JavaWorld. Восхищаясь технологией XML, автор все же полагает, что Java имеет все необходимое, чтобы решить проблему более изящным способом. В качестве доказательства рассмотрим некоторые из подлинно драгоценных камней, которые можно найти в библиотеке java.beans. Они помогут нам создать несколько полезных классов и изучить пару интересных хитростей.

За контролируемыми свойствами Java стоит весьма простой механизм. Перед принятием нового значения данных, объект (владелец контролируемого свойства) проверяет, что оно принято всеми заинтересованными сторонами, которые могут накладывать вето на его изменение. Такой "запрос на одобрение" направляется в каждый зарегистрированный java.beans.VetoableChangeListener в форме объекта java.beans.PropertyChangeEvent. Если получен один или более запретов, предложенное значение данных отклоняется. Запрет представляется объектом java.beans.PropertyVetoException. Вообще говоря, сложность правил, предписанных обработчиками изменений, не имеет ограничений и зависит только от требований проекта и изобретательности разработчика. Цель настоящего упражнения состоит в том, чтобы научиться работать с наиболее общими критериями проверки правильности данных, а затем применять изученные методики к большинству объектов.

Во-первых, создадим класс BusinessObject с простым численным свойством. Затем, мы покажем, как сделать свойство управляемым, и увидим, как оно отличается от простого.

public class BusinessObject {

   private int numericValue;

   public void setNumericValue(int newNumericValue) {
     numericValue = newNumericValue;
   }

   public int getNumericValue() {
     return numericValue;
   }
 }

Заметим, что единственное свойство этого класса беспрепятственно примет любое значение надлежащего типа. Чтобы сделать его управляемым, необходимо сгенерировать и отправить обработчикам запрещаемое событие. Вспомогательный класс, называемый java.beans.VetoableChangeSupport, специально предназначен для того, чтобы выполнять этот набор обязанностей. Новый класс, называемый ConstrainedObject, будет управлять всеми возможностями этого сервисного класса, являясь таким образом хорошей оболочкой для BusinessObject. Вспоминая о потребителях, мы должны создать другой класс, называемый Validator, который выполняет универсальные алгоритмы проверки правильности данных. Он станет единственным потребителем события запрета в нашей среде. Этот упрощенный подход отклоняется от правил работы с JavaBeans (см. подробности в спецификации API JavaBeans), которые требуют представления каждого управляемого свойства как привязанного и обеспечения поддержки для множественных обработчиков событий. Это отклонение вполне приемлемо, так как здесь мы разрабатываем не JavaBean, но мы все же должны помнить об этом.

import java.beans.*;

 /**
  * ConstrainedObject отвечает за предоставление прав проверки
  * данных классу {@link Validator} и обеспечения подклассов
  * для создания прозрачного интерфейса к этому классу.
  */
 public class ConstrainedObject {

   private VetoableChangeSupport vetoableSupport = 
                                        new VetoableChangeSupport(this);

  /**
   * Создать новый объект с общими методами проверки свойств
   */
  public ConstrainedObject() {
   vetoableSupport.addVetoableChangeListener(new Validator());
  }

  /**
   * Этот метод будет использоваться подклассами для проверки нового
   * значения управляемого свойства типа int. Его можно легко
   * изменить для работы с другими примитивными типами.
   * @param propertyName Планируемое имя свойства, 
                                       которое запросило изменения.
   * @param oldValue Старое значение свойства.
   * @param newValue Новое значение свойства.
   */
   protected void validate(String propertyName, int oldValue, int newValue) 
                          throws PropertyVetoException {
      vetoableSupport.fireVetoableChange(propertyName, 
                                         new Integer(oldValue), 
                                         new Integer(newValue));
   }
}

Чтобы обеспечить компактность кода, ConstrainedObject содержит метод, проверяющий только свойства типа int. Этот метод легко изменяется для работы с другими типами. При выполнении изменений, следует помнить, что надо все примитивные типы помещать в соответствующие им объекты, так как PropertyChangeEvent передает старое и новое значения по ссылке. Теперь можно произвести второе приближение BusinessObject:

import java.beans.*;

 /**
  * В этом примере мы создадим класс, который практически использует
  * возможности проверки данных, заложенные в данной среде.
  */
 public class BusinessObject extends ConstrainedObject {

   private int numericValue;

   public void setNumericValue(int newNumericValue) 
                                        throws PropertyVetoException {
     // проверить предложенное значение
     validate("numericValue", numericValue, newNumericValue);
     // если новое значение одобрено, поэтому исключение не сгенерировано
     numericValue = newNumericValue;
   }

   public int getNumericValue() {
     return numericValue;
   }
 }

Мы видим, что метод установки теперь выглядит немного сложнее, чем прежде. Это та своего рода расплата за получение управляемого свойства. Теперь следует сформировать Validator, который является наиболее интересной частью этого упражнения. При этом мы изучим, как оформить правила проверки правильности данных, используя только чистый Java.

import java.beans.*;

 public class Validator implements VetoableChangeListener {

   public void vetoableChange(PropertyChangeEvent evt) 
                                        throws PropertyVetoException {
     // здесь выполнить проверку
   }
 }

Созданный скелет кода является лишь отправной точкой, но уже демонстрирует доступные в начале процесса проверки правильности возможности. Он содержит на удивление много информации. Из PropertyChangeEvent можно узнать объект-источник контролируемого свойства, непосредственное имя свойства, и его существующее и предложенное значения. Эту информацию можно использовать при выполнении самоанализа , чтобы запросить дополнительную информацию о свойстве. Какая информация потребуется? Конечно, данные для проверки правильности! Класс java.beans.Introspector разработан исключительно с целью сбора дополнительной информации относительно целевого класса. Информация предоставляется в форме объекта java.beans.BeanInfo, к которому в этом примере можно обратиться простым запросом вроде следующего:

BeanInfo info = Introspector.getBeanInfo(evt.getSource().getClass());

Introspector может собирать BeanInfo двумя способами. Во-первых, попытаться получить явную информацию от целевого класса поиском класса с тем же именем с добавленным суффиксом BeanInfo. В нашем случае, будет выполнен поиск класса BusinessObjectBeanInfo. Если такой класс не был найден, Introspector пытается построить динамический объект BeanInfo путем низкоуровневого отражения.

Хотя неявный самоанализ представляет собой очень красивый механизм, очевидно не следует полагаться на него в наших поисках правил проверки правильности. Класс BeanInfo полезен своим четким отношением к целевому классу. Отсутствие ссылок на целевой класса делает эту связь очень свободной. При изменении правил проверки правильности, следует лишь модифицировать и перетранслировать класс BeanInfo без воздействия на целевой класс. Ясно определенные правила именования, которые установлены в базовом API Java, делают возможной надежную компиляцию. В этом смысле подход на основе внешних правил с применением BeanInfo убедительно превосходит подходы на базе файлов свойств.

Перед тем, как начать строить явные классы BeanInfo, следует упомянуть, что можно загружать их различными описателями самого класса или его методов и событий, а не только свойствами. Благодаря создателям Java, не надо обеспечивать его всей этой информацией. Introspector выбирает то, что доступно явно и затем исполняет свою неявную магию анализа. Поэтому можно сконцентрироваться на одних только описателях свойств.

Для определения правил проверки данных, следует использовать класс java.beans.PropertyDescriptor. В документации можно обнаружить, что этот класс имеет средства обеспечения любой возможной информации о свойстве, включая правила проверки данных! Метод setValue используется для правил как набора именованных атрибутов. Ниже показано, как назначить ограничения числовому свойству numericValue:

PropertyDescriptor _numericValue = 
                           new PropertyDescriptor("numericValue", 
                                                  targetClass, 
                                                  "getNumericValue", 
                                                  "setNumericValue");
 _numericValue.setValue("maxVal", new Integer(100));
 _numericValue.setValue("minVal", new Integer(-100));
 

Единственным слабым местом, является невозможность проверки компилятором Java имен атрибутов. С этим можно справиться, используя предопределенный набор ограничений. Класс Validator хорошо подходит для управления такими ограничениями:

 public static final String MAX_VALUE = "maxValue";
 public static final String MIN_VALUE = "minValue";
 

Теперь можно более надежно переписать правила. Построим окончательную версию класса BeanInfo:

import java.beans.*;

 public class BusinessObjectBeanInfo extends SimpleBeanInfo {

   Class targetClass = BusinessObject.class;

   public PropertyDescriptor[] getPropertyDescriptors() {
     try {
       PropertyDescriptor numericValue = 
                          new PropertyDescriptor("numericValue", 
                                                 targetClass, 
                                                 "getNumericValue", 
                                                 "setNumericValue");
       numericValue.setValue(Validator.MAX_VALUE, new Integer(100));
       numericValue.setValue(Validator.MIN_VALUE, new Integer(-100));

       PropertyDescriptor[] pds = new PropertyDescriptor[] {numericValue};
       return pds;

     } catch(IntrospectionException ex) {
       ex.printStackTrace();
       return null;
     }
   }
 }
 

В заключение, напишем Validator таким образом, чтобы он извлекал и анализировал информацию. Он будет использовать соответствующий метод для извлечения PropertyDescriptor из BeanInfo по заданному имени свойства. В случае отсутствия имени свойства по какой-либо причине, метод известит вызывающий класс самообъясняющим исключением.

import java.beans.*;

 /**
  * This class implements generic data-validation mechanism 
  * that is based on the external
  * rules defined in the BeanInfo class.
  */
 public class Validator implements VetoableChangeListener {

   public static final String MAX_VALUE = "maxValue";
   public static final String MIN_VALUE = "minValue";

   /**
    * The only method required by {@link VetoableChangeListener} interface.
    */
   public void vetoableChange(PropertyChangeEvent evt) 
                              throws PropertyVetoException {

     try {

      // here we hope to retrieve explicitly defined 
      // additional information about the object-source 
      // of the constrained property
      BeanInfo info = Introspector.getBeanInfo(evt.getSource().getClass());

      // find a property descriptor by given property name
      PropertyDescriptor descriptor = getDescriptor(evt.getPropertyName(), 
                                                    info);

      Integer max = (Integer) descriptor.getValue(MAX_VALUE);

      // check if new value is greater than allowed
      if( max != null && max.compareTo(evt.getNewValue()) < 0 ) {
        // complain!
        throw new PropertyVetoException("Value " + evt.getNewValue() 
                                + " is greater than maximum allowed " 
                                + max, evt);
      }

      Integer min = (Integer) descriptor.getValue(MIN_VALUE);

      // check if new value is less than allowed
      if( min != null && min.compareTo(evt.getNewValue()) > 0 ) {
        // complain!
        throw new PropertyVetoException("Value " + evt.getNewValue() 
                                 + " is less than minimum allowed " 
                                 + min, evt);
      }

     } catch (IntrospectionException ex) {
       ex.printStackTrace();
     }
   }

   /**
    * This utility method tries to fetch a PropertyDescriptor 
    * from BeanInfo object by given property name.
    * @param name the programmatic name of the property
    * @param info the bean info object that will be searched 
    *                      for the property descriptor.
    * @throws IllegalArgumentException if a property with 
    *              given name does not exist in the given BeanInfo object.
    */
   private PropertyDescriptor getDescriptor(String name, BeanInfo info) 
                                         throws IllegalArgumentException {

     PropertyDescriptor[] pds = info.getPropertyDescriptors();

     for( int i=0; i<PDS.LENGTH;
       if( pds[i].getName().equals(name) ) {
         return pds[i];
       }
     }

    throw new IllegalArgumentException("Property " + name 
                                       + " not found.");
   }
 }
 

Теперь мы можем протестироват проверку данных с помощью простой процедуры:

/**
  * A simple application that tries to cause data-validation exception
  * condition.
  */
 public class Driver {

   public static void main(String[] args) {

     try {
       BusinessObject source = new BusinessObject();
       source.setNumericValue(10);
       System.out.println("Value accepted: " + source.getNumericValue());
       source.setNumericValue(-10);
       System.out.println("Value accepted: " + source.getNumericValue());
       source.setNumericValue(101);
       // we should never reach this line
       System.out.println("Value accepted: " + source.getNumericValue());
     } catch (Exception ex) {
       ex.printStackTrace();
     }
   }
 }

При запуске приложения строка source.setNumericValue(101) вызовет PropertyVetoException с сообщением : "Значение 101 больше максимального разрешенного 100."

Заключение

В этой короткой статье, мы показали, что базовое API Java имеет все возможности для построения простого и надежного механизма проверки правильности данных. Кроме того, мы рассмотрели, как встроить этот механизм в среду, которая на практике была реализована в большом коммерческом проекте. Единственная необходимая библиотека для среды — java.beans.

Об авторе

Sun Certified Java Programmer Victor Okunev is a design architect of the R&D team for Plexus Systems Design of Vancouver. With an M.S. in computer science from Moscow State Institute of Radio Engineering, Electronics, and Automation, Okunev has more than 10 years' experience in software development, primarily on enterprise-scale applications. He also teaches Java classes for Learning Tree International, snowboards, and enjoys sea and whitewater kayaking.

Ресурсы

Reprinted with permission from the December 2000 edition of JavaWorld magazine. Copyright © ITworld.com, Inc., an IDG Communications company.
View the original article at: http://www.javaworld.com/ jw-12-2000/jw-1229-valframe.html

[an error occurred while processing this directive]
[an error occurred while processing this directive] Перевод на русский © Андрей Ковалев, к.т.н., доцент МИЭТ, 2001
< Вернуться на caйт :: Copyright © 1999 — 2010, IT • archiv.