Аспектно-ориентированное программирование (AOP) или почему мы отказались от использования самого маститого вендора AOP — PostSharp. Часть 3
Описание Fody и MethodDecorator
Введение
В предыдущей (второй) части статьи мы подробно рассмотрели те возможности PostSharp, которые используются в проектах нашей компании.
В этой части статьи мы подробно рассмотрим другой AOP-фреймворк — Fody, которым мы решили заменить PostSharp. И попробуем ответить на вопрос, что мы приобретаем и что теряем при этом переходе.
Одним из решающим для нас отличием от PostSharp было то, что Fody является бесплатным open source продуктом, использующим MIT-лицензию. Эта лицензия позволяет свободно использовать и модифицировать Fody и в закрытых коммерческих проектах.
Fody и его плагины
Fody, как и PostSharp, выполняет обработку сборок после их компиляции. Схематично, это изображено на рисунке ниже. Этот рисунок аналогичен рисунку для PostSharp, с тем лишь отличием, что PostSharp здесь заменен на Fody.
В отличие от PostSharp, который является монолитным продуктом со множеством реализованной функциональности, Fody является движком для постобработки сборок, так как сам по себе не реализует никакой логики, а лишь предоставляет API и инфраструктуру для её реализации.
Для реализации логики постобработки Fody использует плагины. Плагины, как правило, представляют собой отдельные проекты со своими лицензиями, и, как и Fody, выложенные на GitHub.
Перечислим наиболее примечательные, на наш взгляд, плагины:
· MethodDecorator, на котором мы подробно остановимся чуть позже;
· PropertyChanged, генерирует код для реализации интерфейса INotifyPropertyChanged;
· MethodCache, генерирует код, который кэширует результаты вызовов методов.
Помимо этих плагинов существует десятки других. Полный и актуальный список плагинов доступен на GitHub-странице Fody: https://github.com/Fody/Fody#addins-list
Использование Fody
Для того, чтобы использовать Fody в своём проекте достаточно:
1. Добавить в проект NuGet-пакет Fody.
2. Добавить в проект отдельные NuGet-пакеты необходимых плагинов.
3. Добавить в корневой каталог проекта или солюшена файл FodyWeavers.xml такого содержания:
Где WeaverName — это название плагина.
После этого достаточно применить в исходных кодах проекта кастомные атрибуты, которые предоставляют плагины, к своим классам, и при следующей компиляции эти классы будут обработаны Fody.
Возможности плагина MethodDecorator
Как уже было сказано выше, Fody сам по себе не реализует никакой логики постобработки, а только обеспечивает передачу управления в в свои плагины.
Поэтому при замене PostSharp недостаточно рассматривать только Fody в отдельности, а необходимо еще подобрать и плагины, реализующие необходимую функциональность.
MethodDecorator — плагин Fody, реализующий функциональность, аналогичную аспекту OnMethodBoundaryAspect у PostSharp, а именно — добавление своей логики в начало и конец метода.
Для примера, приведем код уже знакомого нам атрибута TraceAttribute, адаптированного под MethodDecorator:
Сразу можно отметить, что в новой реализации TraceAttribute сохранены названия методов OnEntry и OnExit, и оба эти метода сохранили свою семантику.
Но на этом сходства заканчиваются. Отметим следующие различия:
1. Для сохранения имени метода используется отдельное поле _methodName, значение которого задается в новом методе Init, специфичным для MethodDecorator.
2. В сигнатурах методов OnEntry, OnExit нет параметров.
А теперь рассмотрим, какой код генерирует MethodDecorator для этого атрибута:
Что можно отметить из приведенного фрагмента кода?
В целом, структура генерируемого кода внутри метода напоминает то, что генерирует PostSharp. Но важное отличие от PostSharp состоит в том, что на каждый вызов метода теперь будет создаваться отдельный экземпляр аспекта. При этом применяется механизм reflection, при использовании которого всегда возникают вопросы о производительности. В данном случае, если бы этот метод использовался внутри гигантского цикла, то по сравнению с PostSharp, его производительность ухудшилась бы на порядки.
Преимуществом такого подхода можно было бы назвать отсутствие необходимости думать о многопоточности при реализации аспекта. В частности, при реализации аспекта можно свободно использовать поля класса для сохранения состояния между вызовами методов Init, OnEntry, OnException. Но при переносе кода адаптированного под PostSharp, где проблемы с многопоточностью уже решены, это не является преимуществом.
Подводя итог, в целом, описанная выше функциональность и является единственной возможностью MethodDecorator. И хотя она позволяет реализовать аспекты, сделанные на основе OnMethodBoundaryAspect из PostSharp, при этом необходимо учитывать разницу в генерируемом коде и то, как она может сказаться на производительности приложения.
Другие возможности Fody+MethodDecorator?
Если вспомнить предыдущую часть статьи, помимо OnMethodBoundaryAspect в наших проектах используются и атрибуты, сделанные на основе аспекта MethodInterceptionAspect. Плюс код некоторых аспектов опирался на уникальную возможность PostSharp: инициализацию аспектов во время компиляции через метод.
Если подобная функциональность в плагинах Fody? Ответ: нет.
Для наглядности, приведем табличку, где приведем используемые нами возможности PostSharp и их аналоги в Fody (если они есть):
Возможности PostSharp и аналог в Fody
- Аспект OnMethodBoundaryAspect — Аналог в Fody: реализовано плагином MethodDecorator
2. Возможность инициализации на этапе билда (метод CompileTimeInitialize) — Аналога в Fody нет
3. Аспект MethodInterceptionAspect — Аналога в Fody нет
Поэтому при переходе на Fody встает вопрос и о том, каким образом заменить данную функциональность из PostSharp?
Что дальше?
При прочтении данной статьи может сложиться впечатление, что перспективы перехода с PostSharp на Fody выглядят сомнительными. Fody не только не покрывает используемые нами возможности PostSharp, но и делает это хуже для аналогичной функциональности.
Но, и Fody, и MethodDecorator являются продуктами с открытым исходным кодом, поэтому при необходимости их можно довести до ума своими руками, чем мы и занялись.
Перед нами, прежде всего, встали такие проблемы:
1. «Научить» MethodDecorator генерировать более оптимальный, с точки зрения производительности, код, аналогично PostSharp.
2. Придумать, как сделать альтернативу MethodInterceptionAspect для Fody.
В следующей части мы подробно рассмотрим, как реализованы плагины для Fody, заглянем под «капот» плагина MethodDecorator и продемонстрируем решение указанных выше проблем.
Ссылки
- Аспектно-ориентированное программирование (AOP) или почему мы отказались от использования самого маститого вендора AOP — PostSharp. Часть 1
- Аспектно-ориентированное программирование (AOP) или почему мы отказались от использования самого маститого вендора AOP — PostSharp. Часть 2
Автор статьи: Марат Вильданов, разработчик, ITA Labs