|
Персональные инструменты |
|||
|
Статически проверяемые ссылки на свойства Java-биновМатериал из CustisWikiВерсия от 16:34, 20 апреля 2015; KseniyaKirillova (обсуждение) (Новая страница: «<blockquote>''<span style="color:#0645AD">Сергей Кошель</span>, ведущий разработчик, опубликовал в нашем корпо…») Это снимок страницы. Он включает старые, но не удалённые версии шаблонов и изображений. Сергей Кошель, ведущий разработчик, опубликовал в нашем корпоративном блоге на «Хабрахабре» статью «Статически проверяемые ссылки на свойства Java-бинов». В ней он рассказал о недостатках синтаксиса Java, связанных с невозможностью сослаться на свойство бина, о конкретных рабочих задачах, при работе над которыми необходимо было устранить данные недостатки, а также представил разработанное в компании решение и аспекты его использования. Когда долго и серьезно используешь какой-либо инструмент, неминуемо возникают претензии к нему — неудобства, с которыми сперва миришься, но в какой-то момент понимаешь, что проще один раз исправить, чем все время страдать. Хорош тот инструмент, который позволяет допилить сам себя. Java — хороший инструмент, поэтому об одном таком неудобстве и о том, как мы его исправляли, и пойдет речь. Итак, неудобствоВ Java нет синтаксиса, позволяющего сослаться на свойство бина. Проще пояснить на примере. Допустим, есть public class Account { public Customer getCustomer() { ... } } public class Customer { public String getName() { ... } } И есть Как сказать, что мы хотим показать TableBuilder<Account> tableBuilder = TableBuilder.of(Account.class); … tableBuilder.addColumn(«customer.name»); Недостаток этого способа в том, что компилятор не знает, что это не просто строка и не может проверить ее корректность, а, значит, все опечатки обернутся ошибками только во время исполнения. По той же причине среда разработки не сможет подсказать нам, какие свойства есть у Есть здесь еще одно, менее очевидное, неудобство: типизация Хочется, чтобы компилятор всё проверил, среда подсказала, а рефакторинг не сломал. Не так уж это и много, учитывая, что вся необходимая для этого информация уже есть. Казалось бы, задача — нерешаемая: в Java действительно нет синтаксической конструкции, позволяющей сослаться на член класса без обращения к нему. Однако, это уже давно не мешает mock-фрэймворкам изящно и строго выражать «когда будет вызван метод…», как например умеет Mockito: Account account = mock(Account.class); when(account.getCustomer()).thenReturn(…); Метод Можно использовать такой же трюк для решения нашей задачи: Account account = root(Account.class); tableBuilder.addColumn($(account.gertCustomer().getName()));
$(account.gertCustomer().getName()).toDotDelimitedString() => «customer.name»
public <T> ColumnBuilder<T> addColumn(BaenPath<T> path) {…} Вот такой небольшой фрэймворк мы написали в CUSTIS, немного попользовались им сами, а теперь выложили на GitHub. Аспекты использованияРеализация через динамическое проксирование налагает следующие ограничения. Во-первых, «корень» и незамыкающие свойства в цепочке не могут быть $(account.getCustomer().getName().length()) // => NPX! Тем не менее, замыкать цепочку может свойство любого типа: и Во-вторых, геттеры должны быть видимы для фрэймворка, то есть не должны быть А вот конструктора по умолчанию и вообще публичного конструктора может и не быть — прокси инстанцируется в обход конструктора. Поскольку, законным способом это сделать нельзя, используется проприетарный для HotSpot JVM интринзик «Руты» можно и нужно переиспользовать, они не содержат состояния: Account account = root(Account.class); tableBuilder.addColumn($(account.getCustomer().getName())); tableBuilder.addColumn($(account.getNumber())); tableBuilder.addColumn($(account.getOpenDate())); Методы public class BeanPathMagicAlias { public static <T> BeanPath<T> path(T callChain) { return BeanPathMagic.$(callChain); } } Можно это использовать для переименования методов в угоду вкусу или чтобы создать полезный шорткат. В частности, один такой уже объявлен в public static String $$(Object callChain) { return $(callChain).toDotDelimitedString(); } Пригодится для использования Инстанс BeanPath<String> bp1 = $(account.getCustomer().getName()); BeanPath<String> bp2 = BaenPath.root(Account.class) .append("customer", Customer.class) .append("name", String.class); bp1.equals(bp2) // => true Это может пригодиться, чтобы обойти упомянутые выше ограничения (если в цепочке оказался Планы на будущееСейчас Потом заменить использование Ну и совсем на перспективу, подойти к решению задачи с другого края: использовать статическую кодогенерацию, а-ля
|
||