Содержание:
I. Описание проблемы
II. Обзор существующих решений
III. Решение без аспектов
IV. Решение с использованием AspectJ
V. Динамические аспекты
VI. Послесловие
VII. Ссылки и использованная документация
I. Описание проблемы
Программирование на Java (и некоторых других языках) программисты часто имеют дело со строковыми переменными и другими типами, которые представляют собой объектные блоки простых типов, например: Boolean, Integer, Double. Есть bean-компонент (здесь, в качестве bean-компонента, я имею в виду, что класс поддерживает рекомендации Java Beans):
public class SomeBean{ private String strField; public void setStrField(String strField){ this.strField=strField; } public String getStrField(){ return strField; } // further declarations are omitted }
В моей работе поля должны быть проверены на ноль каждый раз, используя этот компонент:
String s=someBean.getStrField(); if (s!=null){ if (!s.isEmpty()){ // s.length()>0 Java 5- doSomeThing(); } }
Я тоже с любым полем-объектом. Если я забыл это сделать, наше приложение рано или поздно будет закрыто из-за непроверенного исключения NullPointerException. Конечно, изменяя ошибки в системе, я могу срочно поймать это исключение, но это означает очень плохой код. Производительность с производительностью будет потрачена впустую, поскольку генерация исключений составляет несколько десятков простых команд. Такую «нулевую» проверку мы должны делать много раз в теле боба и вокруг него. Раньше bean-компонент используется, а приложение становится более сложным — чаще всего нам приходится выполнять проверку «null», и поэтому мы должны выполнять рутинную работу. Это означает большую вероятность чего-то упустить, в свою очередь это означает увеличение вероятности дисфункции. Как разработчик я сталкиваюсь много времени с такими вещами.Далее я представляю вашему вниманию брифинг о существующих мерах против нулевых зависимостей и решение об избежании этой проблемы, основанное на аспектах.
II. Обзор существующих решений
Я предположил, что кто-то делал это раньше и рассмотрел существующие решения.
1) аннотация @NotNull из JSR-305, подробно описанная в [1] и [2].
Он размещен перед полями и методами, возвращающими объекты. Это помогает определить IDE в количестве исходных кодов.
@NotNull public Color notNullMethod(){ return null; }
Недостатки: аннотация не решает проблему, и мы не должны забывать о ее сопоставлении во всех слабых местах кода, поскольку JSR-305 не подходит для анализа сложного кода.
2) Средство проверки JSR-308 описано в [3] [4].
Представляет более развитый метод по сравнению с JSR-305 из-за введения дополнительной проверки кода для анализа более сложного кода при проверке на «ноль» и предоставляет множество других полезных аннотаций для проверки параллелизма.
Преимущества: разработанный каркас.
Недостатки: аннотация @NotNull не дает решения, мы не должны забывать публиковать аннотации во всех точках веса.
3) Проверка аннотаций Java (JACK) [5] имеет те же достоинства и недостатки, что и JSR-305.
4) Project Coin, содержит множество рекомендаций по улучшению Java (под влиянием Groovy), подробности в [6] [7].
Давайте поговорим о функции:
public String getPostcode(Person person){ if (person != null){ Address address = person.getAddress(); if (address != null){ return address.getPostcode(); } } return null; }
Project Coin предложил переписать функцию с помощью NullSafe-навигации:
public String getPostcode(Person person){ return person?.getAddress()?.getPostcode(); }
К сожалению, Oracle не включил эти функции в Java 7, поэтому мы должны использовать другие решения для Java.
5) Использование Groovy. Собственно описанная выше NullSafe-навигация основана на Groove [8].
Преимущество: значительное упрощение кода.
Недостаток: мы должны помнить обо всех слабых местах.
6) Apache Commons, StringUtils [9] и PropertyUtils [10]:
StringUtils.isBlank(null)= true StringUtils.isBlank("")= true StringUtils.isBlank(" ")= true String firstName = (String) PropertyUtils.getSimpleProperty(employee, "firstName"); String city = (String)PropertyUtils.getNestedProperty(employee, "address(home).city");
Преимущества: упрощает процедуру проверки.
Недостатки: нужно проверять все вокруг. В этом случае нам понадобится дополнительный код для обработки данных исключений, которые были сгенерированы PropertyUtils.
7) Статические анализаторы кода FindBugs [11] и PMD [12] имеют плагины для большинства IDE.
Преимущества: очень мощные и полезные инструменты для анализа статического кода без предварительного добавления аннотаций.
Недостатки: показывает недельные баллы, не дает законченных решений для каждой ситуации.
III. Решение без использования Аспектов
Обычно чтение данных Null-проверки происходит чаще, чем процедура сохранения значения. Таким образом, операция чтения должна быть оптимизирована. В этом случае мы можем переписать поля взаимодействия бинов, как показано ниже:
public class SomeBean{ private String strField=""; public void setStrField(String strField){ if (strField!=null){ this.strField=strField; } } public String getStrField(){ return strField; } }
Таким образом, мы можем игнорировать нулевые значения. В этом случае поля следует принудительно игнорировать. Все эти факты вместе помогают нам создавать нулевой безопасный код, в котором нам не нужно многократно проверять нуль. Это упрощает создание кода и полностью снижает вероятность пропустить что-то.
Преимущества: нам не нужно заботиться о нулевой проверке полей. Это упростит код и увеличит производительность.
Недостаток: для каждого поля мы должны написать исходный код и обрезку нуля
Внутривенно Решение AspectJ
Чтобы не делать обрезку в каждом случае, мы используем аспекты, чтобы обеспечить полную функциональность. Очевидно, что нам нужно работать на уровне поля, операции с полями должны быть внутри bean-компонентов, и полная функциональность должна быть полной, иначе это не имеет смысла.
Для включения NullSafe в поля и классы введем аннотацию:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) public @interface NullSafe { boolean getter() default true; }
@NullSafe (getter = false) — нам нужно упростить и оптимизировать, если, конечно, у разработчика есть время и он не ленится для полей инициализации в bean-компонентах. В этом случае только запись в поле будет находиться под влиянием аспектов.
Для легкой будущей модификации аспект был написан с использованием AspectJ 5 (аннотированный), и для него требуется Java 5+.
Пожалуйста, смотрите ссылки [13], [14], [15].
@Aspect public class NullSafeAspect { @Pointcut(value="within(@NullSafe *)") // support annotation by class public void byClass() {} @Pointcut(value="!within(@NullSafe(getter=false) *)") public void byClassNoGetter() {} @Pointcut(value="get(@NullSafe Long||Integer||Double||Float||Short||Byte||java.math.BigDecimal *)") public void fieldGetNumerics() {} @Pointcut(value="!get(@NullSafe(getter=false) * *)") public void fieldNoGetter() {} @Pointcut(value="get(Long||Integer||Double||Float||Short||Byte||java.math.BigDecimal *)") public void classGetNumerics() {} @Pointcut(value="set(Long||Integer||Double||Float||Short||Byte||java.math.BigDecimal *)") public void classSetNumerics() {} @Pointcut(value="set(@NullSafe * *)") // all field-annotated public void fieldSetAllSupported() {} @Pointcut(value="classSetNumerics() || set(String||Boolean||Character *)") public void classSetAllSupported() {} @Pointcut(value="fieldGetNumerics() || get(@NullSafe String||Boolean||Character *)") public void fieldGetAllSupported() {} @Pointcut(value="classGetNumerics() || get(String||Boolean||Character *)") public void classGetAllSupported() {} @Around(value="(fieldSetAllSupported() || byClass() && classSetAllSupported()) && args(v)") public void aroundSet(ProceedingJoinPoint jp, Object v) throws Throwable{ toLogSetter(jp, v); if (v!=null){ jp.proceed();} } @Around(value="get(@NullSafe String *) && fieldNoGetter() || byClass() && get(String *) && byClassNoGetter()") public String aroundGetString(ProceedingJoinPoint jp) throws Throwable{ String v=(String)jp.proceed(); if (v==null){return "";} return v; } private Field getField(JoinPoint jp){ try{ Signature sig=jp.getStaticPart().getSignature(); Field field=sig.getDeclaringType().getDeclaredField(sig.getName()); field.setAccessible(true); return field; }catch(Exception e){ } return null; } private Object getBean(JoinPoint jp){ try { Field field=getField(jp); if (field==null){return null;} Object obj=field.getType().newInstance(); field.set(jp.getTarget(),obj); return obj; }catch(Exception e){ stackTraceToLog(e); } return null; } @Around(value="!fieldGetAllSupported() && get(@NullSafe * *) && fieldNoGetter() && byClassNoGetter()") public Object aroundGetBean(ProceedingJoinPoint jp) throws Throwable{ Object v=jp.proceed(); if (v==null){ return getBean(jp); } return v; } private Object getNumeric(JoinPoint jp){ try { Field field=getField(jp); if (field==null){return null;} Object obj=field.getType().getDeclaredConstructor(String.class).newInstance("0"); field.set(jp.getTarget(),obj); return obj; }catch(Exception e){ stackTraceToLog(e); } return null; } @Around(value="fieldGetNumerics() && fieldNoGetter() || byClass() && classGetNumerics() && byClassNoGetter()") public Object aroundGetNumerics(ProceedingJoinPoint jp) throws Throwable{ Object v=jp.proceed(); if (v==null){ return getNumeric(jp); } return v; } }
Пример использования аннотации:
@NullSafe public class SomeClassTestByClass extends SomeClass { ... } @NullSafe private String strField; @NullSafe private HashMap<Integer,HashMap<String , String>> map; @NullSafe(getter=false) private ArrayList<String> listString=new ArrayList<String>();
Дальнейшие объяснения даны об аспекте. Аннотация @Pointcut включает в себя общее описание Inside — означает действие внутри класса, в данном случае любой класс, отмеченный @NullSafe. Поддерживаются числовые типы Long, Integer, Double, Float, Short, Byte, BigDecimal, которые инициализируются одинаковым шаблоном. Также поддерживаются String, Boolean, Character. Все классы, упомянутые выше, включены в термин, которые подозреваются в классах аспекта.
Любой компонент или класс с конструктором без параметров может быть аннотирован через поля. Аннотация класса означает полную поддержку для всех видов полей. Все эти поля инициализируются отражением. Инициализированные в коде, наши поля помогают нам повысить производительность без использования геттеров только с @NullSafe (getter = false). Set () и get () — в точечных вырезах отвечают за записи и чтение полей компонента, включая операции внутри полей. @Advice отвечает за точечные действия. Я не верю, что использование @Before для set () является хорошим советом в отношении производительности кода. Чтобы предотвратить это, нам нужно вызвать исключение. Это неудобно для хорошей производительности.
По умолчанию аспект Singleton был создан. Вся обработка касается только одного метода в проекте для каждого типа. Полный пример с тестами находится в NullSafe-проекте в [18].
Преимущество: полная функциональность только в случае необходимости.
Недостатки: нам все еще нужны пост-аннотации в классах и полях, но тщательная функциональность для каждого класса без ограничений только с использованием @NullSafe является неправильным решением.
V. Динамические аспекты
Обычно Аспекты представляют модификацию кода, и она работает во время компиляции. Поэтому эффективность и производительность программы не могут быть под сложным описанием влияния точек разреза. AspectJ способен производить динамический модифицирующий байт-код с изменяющимся загрузчиком. Нам это нужно в случае использования аспектов для байт-кода, который не имеет источников. В этом случае необходимо избавиться от аннотаций и добавить упоминания о классах в аспекте NullSafeAspect (или создать новый аспект, который будет расширяться). Поскольку такая техника очень специфична для разных типов серверов и сред, я не буду подробно описывать эту статью. Подробнее об использовании динамических аспектов вы можете узнать из [16] [17].
VI. Послесловие
Спасибо за внимание. Эта статья была моей попыткой взглянуть на широко известную проблему с другой точки зрения. Возможно, использование нулевых значений необходимо для конкретного алгоритма или баз данных, но в этом случае, как для меня, часто возникает вопрос, оптимизирован ли алгоритм или база данных. Подход, описанный в моей статье, мог бы стать обычной мерой для создания более компактного и устойчивого к ошибкам кода и стать одним из шаблонов проектирования на Java.
VII. Ссылки и использованная документация *
1.
http://jcp.org/en/jsr/detail?id=305
2.
http://www.jetbrains.com/idea/documentation/howto.html
3.
http://jcp.org/en/ jsr / detail? id = 308
4.
http://types.cs.washington.edu/checker-framework/current/checkers-manual.html
5.
http://homepages.ecs.vuw.ac.nz/~djp / JACK /
6.
https://blogs.oracle.com/darcy/entry/project_coin_final_five
7.
http://metoojava.wordpress.com/2010/11/15/java-7-awesome-features/
8.
http: //groovy.codehaus.org/Operators
9.
http://commons.apache.org/lang/api-2.5/org/apache/commons/lang/StringUtils.html
11.
http://findbugs.sourceforge.net/
12.
http: //pmd.sourceforge .net /
13.
http://eclipse.org/aspectj/doc/released/adk15notebook/index.html
14.
http://eclipse.org/aspectj/doc/released/progguide/index.html
15. AspectJ в действии , Второе издание. ISBN 978-1-933988-05-4
16.
http://www.eclipse.org/aspectj/doc/next/devguide/ltw.html
18.
https://sourceforge.net/projects/nullsafe/files/
(* все ссылки, приведенные выше, были доступны при написании статьи)