Статьи

Код безопасности: шаблон проектирования Null Safe


Содержание:

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

10.
http://commons.apache.org/beanutils/apidocs/org/apache/commons/beanutils/package-summary.html#package_description

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

17.
http://static.springsource.org/spring/docs/3.2 .x / spring-framework-reference / html / aop.html # aop-aj-ltw

18.
https://sourceforge.net/projects/nullsafe/files/

(* все ссылки, приведенные выше, были доступны при написании статьи)