Содержание:
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/
(* все ссылки, приведенные выше, были доступны при написании статьи)