В современном мире IT-разработки существует довольно большое множество различных подходов к написанию программ. Так, например, кому-то нравиться представлять программу в виде последовательности действий, а кто-то считает, что программа должна представлять собой множество объектов, общающихся друг с другом. Совокупности этих идей и понятий образуют своего рода стиль написания программы, который принято назвать – парадигма программирования.
объект (объектно-ориентированное программирование, С++/Java),
факт (логическое программирование, PROLOG).
В этой статье я хочу рассказать о сравнительно молодой, но крайне, на мой взгляд, полезной парадигме программирования – аспектно-ориентированном программировании.
Основы АОП
Рассмотри некоторую сферическую службу в вакууме (например, web-сервис), реализующую следующий метод:
public BookDTO getBook(Integer bookId) BookDTO book = bookDAO.readBook(bookId); return book; >
Метод довольно прост и очевиден: чтение информации о некоторой книге по её идентификатору. Но давайте подумаем, чего тут не хватает? Первым делом нам стоит задуматься о логировании – без него, как вы сами понимаете, в web-службе никуда:
public BookDTO getBook(Integer bookId) LOG.debug( «Call method getBook with id » + bookId);
BookDTO book = bookDAO.readBook(bookId);
LOG.debug( «Book info is: » + book.toString()); return book; >
Далее необходимо реализовать обработку исключений (сделать так, что бы слой служб возвращал соответствующие ему исключения, скрывая исключения нижележащих слоёв):
public BookDTO getBook(Integer bookId) throws ServiceException LOG.debug( «Call method getBook with id » + bookId); BookDTO book = null ;
try book = bookDAO.readBook(bookId); > catch(SQLException e) throw new ServiceException(e); >
LOG.debug( «Book info is: » + book.toString()); return book; >
Так же не стоит забывать о проверке прав доступа:
public BookDTO getBook(Integer bookId) throws ServiceException, AuthExceptionif (!SecurityContext.getUser().hasRight(«GetBook»)) throw new AuthException(«Permission Denied»);
LOG.debug( «Call method getBook with id » + bookId); BookDTO book = null ;
try book = bookDAO.readBook(bookId); > catch (SQLException e) throw new ServiceException(e); >
LOG.debug( «Book info is: » + book.toString()); return book; >
Кроме того имеет смысл кешировать результат работы:
public BookDTO getBook(Integer bookId) throws ServiceException, AuthException if (!SecurityContext.getUser().hasRight( «GetBook» )) throw new AuthException( «Permission Denied» );
LOG.debug( «Call method getBook with id » + bookId); BookDTO book = null ; String cacheKey = «getBook:» + bookId;
try if (cache.contains(cacheKey)) book = (BookDTO) cache.get(cacheKey); > else book = bookDAO.readBook(bookId); cache.put(cacheKey, book); > > catch (SQLException e) throw new ServiceException(e); >
LOG.debug( «Book info is: » + book.toString()); return book; >
Можно продолжать совершенствовать данный метод, но для начала — достаточно. В ходе наших доработок мы получили метод в 10 раз (с 2 до 20 LOC) превышающий исходный размер. Самое интересное, что объём бизнес-логики в нём не изменился – это всё та же 1 строка. Остальной код реализует некоторую общую служебную функциональность приложения: логирование, обработку ошибок, проверку прав доступа, кеширование и так далее.
логирование,
обработка транзакций,
обработка ошибок,
авторизация и проверка прав,
кэширование,
элементы контрактного программирования.
аспект (aspect) – модуль или класс, реализующий сквозную функциональность. Аспект изменяет поведение остального кода, применяя совет в точках соединения, определённых некоторым срезом. Так же аспект может использоваться для внедрения функциональности;
совет (advice) – дополнительная логика — код, который должен быть вызван из точки соединения. Совет может быть выполнен до, после или вместо точки соединения;
точка соединения (join point) — точка в выполняемой программе (вызов метода, создание объекта, обращение к переменной), где следует применить совет ;
срез (pointcut) — набор точек соединения. Срез определяет, подходит ли данная точка соединения к заданному совету;
внедрение (introduction) — изменение структуры класса и/или изменение иерархии наследования для добавления функциональности аспекта в инородный код;
цель (target) – объект, к которому будут применяться советы;
переплетение (weaving) – связывание объектов с соответствующими аспектами (возможно на этапе компиляции, загрузки или выполнения программы).
Пример использования (AspectJ)
AspectJ является аспектно-ориентированным расширением/framework’ом для языка Java. На данный момент это, пожалуй, самый популярный и развивающийся АОП движок.
Рассмотрим реализацию аспекта логирования с его помощью:
@Aspect public class WebServiceLogger private final static Logger LOG = Logger.getLogger(WebServiceLogger. class );
@Pointcut( «execution(* example.WebService.*(..))» ) public void webServiceMethod()
@Pointcut( «@annotation(example.Loggable)» ) public void loggableMethod()
Первым делом создаётся аспект логирования методов сервисов – класс WebServiceLogger, помеченный аннотацией Aspect. Далее определяются два среза точек соединения: webServiceMethod (вызов метода, принадлежащего классу WebService) и loggableMethod (вызов метода, помеченного аннотацией @Loggable). В завершении объявляется совет (метод logWebServiceCall), который выполняется вместо (аннотация Around) точек соединения, удовлетворяющих срезу («webServiceMethod() && loggableMethod()»).
В коде совета происходит получение информации о текущем методе (точке соединения), логирование начала выполнения метода, непосредственный вызов запрошенного метода, логирование и возвращение результата работы.
execution(static * com.xyz..*.*(..)) – выполнение кода любого статического метода в пакете com.xyz;
call(void MyInterface.*(..)) – вызов любого метода, возвращающего void, интерфейса MyInterface;
initialization(MyClass || MyOtherClass) – инициализация класса MyClass или MyOtherClass;
staticinitialization(MyClass+ && !MyClass) – статическая инициализация класса, имя которого начинается на MyClass, но не сам MyClass;
handler(ArrayOutOfBoundsException) – выполнение обработчика исключения ArrayOutOfBoundsException;
get/set(static int MyClass.x) — чтение / запись свойства x класса MyClass;
this/target(MyClass) – выполнение точки соединения, соответствующей объекту типа MyClass;
args(Integer) – выполнение точки соединения, в которой доступен аргумент типа Integer;
if(thisJoinPoint.getKind().equals(«call»)) – совпадает со всеми точками соединения, в которых заданное выражение истинно;
within/withincode(MyClass) — совпадает со всеми точками соединения, встречающимися в коде заданного класса;
cflow/cflowbelow(call(void MyClass.test())) – совпадает со всеми точками соединения, встречающимися в потоке выполнения заданного среза;
@annotation(MyAnnotation) – выполнение точки соединения, цель которой помечена аннотацией @MyAnnotation.
before – запуск совета до выполнения точки соединения,
after returning — запуск совета после нормального выполнения точки соединения,
after throwing — запуск совета после выброса исключения в процессе выполнения точки соединения,
after — запуск совета после любого варианта выполнения точки соединения,
around – запуск совета вместо выполнения точки соединения (выполнение точки соединения может быть вызвано внутри совета).
Для того, что бы использовать аспекты AspectJ их придётся скомпилировать и «вшить» в основные классы с помощью специального компилятора AJC.
Продукт бесплатный. Распространяется под Eclipse License.
Пример использования (PostSharp)
PostSharp является аспектно-ориентированным framework’ом для платформы .NET. Существуют и другие реализации АОП для .NET, однако, судя по сравнениям с сайта PostSharp, лидирующую позицию занимает именно он.
Рассмотрим, как с помощью него описать аспект обработки исключений. Первым делом необходимо создать класс, расширяющий соответствующий аспект:
public class ExceptionDialogAttribute : OnExceptionAspect public override void OnException(MethodExecutionEventArgs eventArgs) string message = eventArgs.Exception.Message; Window window = Window.GetWindow((DependencyObject)eventArgs.Instance); MessageBox.Show(window, message, «Exception» ); eventArgs.FlowBehavior = FlowBehavior.Continue; > >
Строго говоря, аспекты в терминологии PostSharp – это, как мы можем видеть, аспект и совет в терминологии АОП.
Для того, что бы указать срез точек пересечения для данного аспекта необходимо в файл настроек сборки (AssemblyInfo.cs) добавить следующую строку:
Или же явно пометить интересующие вас методы атрибутом ExceptionDialog:
[ExceptionDialog] public BookDTO GetBook(Integer bookId)
Вот собственно и всё: теперь все выброшенные в соответствующих методах исключения будут обрабатываться созданным аспектом.
OnMethodBoundary/OnMethodInvocation – обращение к методу (начало, конец, выход, выход с исключением);
OnFieldAccess – обращение к свойству;
OnException – обработка исключения;
Composition – внедрение кода;
Продукт платный. Есть Community Edition.
От теории к практике
И так, мы только что увидели, как красиво и эффективно можно решить проблему «выноса за скобки» сквозного функционала в вашем приложении. Однако, это всё теория. На практике всё, естественно, немного иначе 🙂
Прежде всего, в обоих случаях для компиляции и «вшивания» (weaving) аспектов придётся использовать специальный компилятор и тащить вместе с проектом дополнительные библиотеки. Вроде бы, это не проблема: компилятор легко скачивается и интегрируется в среду (например, при использовании maven’a задача сведётся всего лишь к добавлению плагина aspectj-maven-plugin), а множество зависимостей – обычное дело, по крайней мере для Java-приложений (решаемая с помощью того же maven’a). Однако, необходимость включения в проект чего-то, что требует отдельной компиляции, да ещё и не имеет широкого распространения, зачастую отпугивает разработчиков, не смотря на все потенциальные плюсы.
В данном случае решением проблемы может стать Spring Framework [1,2]. Данный фреймворк имеет много достоинств, однако в рамках данной статьи нас интересует его AOP-составляющая. Spring Framework реализует ограниченную AOP-функциональность на чистом Java (C#) без использования сторонних библиотек с помощью создания прокси-объектов (JDK Dynamic Proxy, CGLIB). Другими словами в Spring AOP можно использовать только точки соединения типа «выполнение метода». Однако, как показывает практика, данное ограничение не играет значительной роли, так как для решения большинства задач, требуется точки соединения именно этого типа.
Кроме того, Spring Framework поддерживает конфигурирование приложений c помощью @AspectJ аннотаций, а так же интеграцию аспектов скомпилированных непосредственно с помощью AspectJ.
У себя в компании мы используем именно Spring AOP. Учитывая прочие заслуги Spring Framework, на мой взгляд, он является самой доступной и удобной площадкой для работы с AOP, внося значительный вклад в его популяризацию и развитие.
Резюме
Основная цель АОП — выноса «общей» (сквозной) функциональности «за скобки» (модуляризация сквозной функциональности);
Для Java AOP доступен через проект AspectJ, для .NET – через PostSharp;
Наиболее простая и проверенная реализация AOP – Spring AOP.
Аспектно-ориентированное программирование: изучи и сделай сам!
Статья родилась из того, что мне потребовался удобный и простой механизм перехвата для некоторых задач, который легко реализуется техниками АОП. Существует довольно много перехватчиков (Casle.net, Spring.net, LinFu и т.д.), требующих внедрять динамические дочерние классы в IL-код во время исполнения и приводящих практически всегда к одним и тем же ограничениям, накладываемым на перехватываемые классы: не статические, не запечатанные, методы и свойства должны быть виртуальными и т.д…
Другие механизмы перехвата требовали изменения процесс сборки или покупки лицензии. Ни то ни другое я себе позволить не мог…
Введение
АОП – Аспектно-Ориентированное Программирование. Думаю, все знакомы с тем, что значит ОП, потому нужно разобраться, что же такое Аспект. Не волнуйтесь, об этом в статье позже.
Я постараюсь писать эту статью на уровне новичка. Единственное требование для дальнейшего чтения – знание концепции Объектно-Ориентированного Программирования.
Мне кажется, что понимание концепции позволит вам лучше понять реализацию. Но я также понимаю, что статья довольно большая, потому если вам стало скучно или вы утомились, всё-таки посмотрите часть с реализацией. К теории всегда можно вернуться.
Опытные разработчики, прочтите!
Если вы уже знакомы с АОП, не уходите! Давайте я прямо здесь и сейчас раскрою, что же в этой статье…
Я расскажу о технике перехвата, которая позволит перехватывать: • Любой класс (включая запечатанные, статичные, значимого типа) • Конструкторы • Инициализаторы типов • Методы, свойства и события экземпляров (даже если они не помечены как виртуальные) • Статичные методы, свойства и события
Без: • Перекройки вашего кода и сборок • Внедрения в IL-код • Динамического создания чего-либо • Изменения целевых классов • Необходимости виверами (weaver) реализовывать что-либо (например MarshalByRef)
В целом, разговор пойдет о чистом управляемом коде, который можно запустить и на .Net 1.1 (вообще, я использую немного Linq, но вы можете от него легко избавиться) и перехватывать всё, что взбредет вам в голову.
Проясним окончательно. С помощью этой техники: • Вы сможете перехватывать такие конструкции как System.DateTime.Now и System.IO.File! • У вас не будет обычных ограничений характерных для всех популярных библиотек перехвата. Всё еще сомневаетесь? Тогда читаем дальше!
Принципы АОП
Вводная
Некоторые могут подумать, что их путь познания Объектно-Ориентированного Программирования ещё не завершён, и есть ли смысл переключаться с ООП на АОП, отказываясь от всех практик изученных за годы тяжкого обучения? Ответ прост: не надо переключаться. Нет противостояния ООП и АОП! АОП – концепция, чьё название, по-моему, вводит в заблуждения. Понимание принципов АОП требует от вас работы с классами, объектами, наследованием, полиморфизмом, абстракциями и т.п., потому у вас никак не получится потерять всё то, чем вы пользуетесь в ООП.
В стародавние времена, когда интернет состоял из желтых страниц, досок объявлений и юзнета, лучше всего было читать книжки, чтобы изучить что-то (ремарка для нового поколения: книга – штука, состоящая из сшитых вместе бумажных листов, содержащих текст). И практически все эти книги постоянно напоминали вам о следующих правилах ООП: • Правило №1: необходима инкапсуляция данных и кода • Правило №2: никогда не нарушай Правило №1
Инкапсуляция была основной целью, ради которой появилось ООП в языках 3-го поколения (3GL).
Инкапсуляция используется для поддержки двух схожих, но различных, требований и, иногда, к их комбинации: • Ограничение доступа к некоторым компонентам объекта. • Языковая конструкция, облегчающая объединение данных с методами (или другими функциями), работающими с этими данными.
АОП, в целом, утверждает, что иногда требуется языковая конструкция, облегчающая объединение методов (или других функций), работающих с инкапсулированными данными, без этих данных.
Уловили? Собственно, это всё что вам требовалось из теории. Отлично!
Теперь, перейдем к двум появившимся вопросам: • Когда это «иногда»? • Зачем это нужно?
О пользе АОП… некоторые сценарии
Сценарий А
Вы разработчик в банке. В банке прекрасно работает система. Бизнес стабилен. Правительство издает указ, требующий от банков большей прозрачности. При любом движении средств в банк или из него, это действие должно быть запротоколировано. Так же в правительстве говорят, что это только первый шаг на пути к прозрачности, будут еще.
Сценарий Б
Ваше веб-приложение выдано команде тестеров. Все функциональные тесты прошли, а приложение сломалось на нагрузочном тесте. Имеется нефункциональное требование, говорящее, что никакая страница не может обрабатываться на сервере более 500 мс. Проведя анализ, вы обнаружили десятки запросов к базе, которых можно избежать кэшируя результаты.
Сценарий В
Вы два года собирали доменную модель в идеальную библиотеку, содержащую 200+ классов. Позже вы узнаете, что для приложения пишется новый фронт-енд и необходимо все ваши объекты связать с UI. Но для решения задачи необходимо, чтобы все классы реализовывали INotifyPropertyChanged.
Приведенные примеры демонстрируют, где АОП может быть спасением. Все эти сценарии похожи в следующем:
Внедряемые действия (cross-cutting concern)
Когда это «иногда»?
Некоторым классам (банковской системы, сервисов доступа к данным, доменной-модели) необходимо получить функциональность, которая, в общем, не является «их делом».
• Дело банковской системы – переводить деньги. Протоколирование операций хочет правительство. • Сервис доступа к данным нужен для получения данных. Кэширование данных – нефункциональное требование. • Классы доменной модели реализуют бизнес-логику вашей компании. Оповещение UI о изменении свойства требуется только UI.
В целом, речь идёт о ситуациях, когда требуется написать код для различных классов для решения задач неприсущих этим классам. Говоря на диалекте АОП – нужны внедряемые действия.
Понимание внедряемых действий является ключевым для АОП. Нет внедряемых действий – нет надобности в АОП.
Зачем это нужно? Давайте рассмотрим детально Сценарий В.
Проблема в том, что у вас, допустим, в среднем 5 свойств в каждом классе. Имея 200+ классов, вам придется реализовать (скопипастить) более 1000 шаблонных кусков кода, для превращения чего-то типа этого:
public class Customer < public string Name < get; set; >>
Во что-то типа такого:
public class Customer : INotifyPropertyChanged < public event PropertyChangedEventHandler PropertyChanged; private string _Name; public string Name < get < return _Name; >set < if (value != _Name) < _Name = value; SignalPropertyChanged("Name"); >> > void SignalPropertyChanged(string propertyName) < var pcEvent = this.PropertyChanged; if (pcEvent != null) pcEvent(this, new PropertyChangedEventArgs(propertyName)); >>
Ух! А это только одно свойство! Кстати, вы в курсе? Стажер уволился пару дней назад
Нужно «объединение методов (или других функций), работающих с инкапсулированными данными, без этих данных». Другими словами: внедрить реализацию действий INotifyPropertyChanged не меняя или с минимальными изменениями в классы доменной модели типа Customer.
Если мы можем это реализовать, то на выходе получим: • Четкое разделение действий • Избежим повторения кода и ускорим разработку • Не будем прятать доменные классы под тоннами шаблонного кода
Здорово. Но как всё это сделать?
Теоретический ответ
У нас есть внедряемое действие, которое должно выполняться в нескольких классах (далее по тексту – цели). Реализация (код который реализует протоколирование, кэширование или еще что-то) называется в АОП действие.
Далее нам нужно прикрепить (внедрить, встроить, выберите своё слово) действие (я повторю, потому, что это важно: действие – это реализация внедряемого действия) в любое нужное нам место цели. И мы должны иметь возможность выбрать любое из следующих мест цели для внедрения действия: • Статические инициализаторы • Конструкторы • Чтение и запись статических свойств • Чтение и запись свойств экземпляров • Статические методы • Методы экземпляра • Деструкторы
В идеальном АОП мы должны иметь возможность внедрить действие в любой строчке кода цели. Отлично, но если нам нужно прикрепить действие, необходим перехватчик в цели? Да, КЭП!
В АОП описание перехватчика (места, в котором действие будет выполняться) имеет название: срез (pointcut). А место, в котором код фактически привяжется: точка соединения.
Понятно? Возможно нет… Вот немного псевдокода, который, надеюсь, даст пояснения:
// Цель class BankAccount < public string AccountNumber public int Balance void Withdraw(int AmountToWithdraw) < :public pointcut1; // срез (представьте себе, что это что-то вроде метки в Бейсике) Balance -= AmountToWithdraw; >> // Действие протоколирования concern LoggingConcern < void LogWithdraw(int AmountToWithdraw) < // Тут предстаьте себе, что происходит некое волшебство // и 'this' – это экземпляр класса BankAccount. Console.WriteLine(this.AccountNumber + " withdrawal on-going. "); >> class Program < void Main() < // получим ссылку на маркер среза через рефлексию pointcut = typeof(Bank).GetPointcut("pointcut1"); // а это точка соединения LoggingConcern.Join(cutpoint, LogWithdraw); // После точки соединения среда исполнениея будет иметь запись, // которая требует выполнить LoggingConcern в срезе pointcut1 класса >>
Было бы круто иметь такой механизм в С# прямо «из коробки».
Еще несколько понятий
До того как мы двинемся далее к нашей реализации, приведем еще несколько понятий…
Что такое Аспект? Это совокупность действия, среза и точки соединения.
Поразмышляйте над этим минуту, и, я надеюсь, всё встанет на свои места: имеется механизм протоколирования (действие), который регистрируется свой метод протоколирования для исполнения (точка соединения) в указанном месте (срезе) моего приложения. Всё это вместе является аспектом моего приложения.
Но минутку… Что должен и может делать действие после внедрения?
Действия разделяются на две категории:
Побочные эффекты. Побочный эффект – действие, которое не изменяет действия кода в срезе. Побочный эффект, просто добавляет некую команду для исполнения. Действие протоколирования – хороший пример побочного эффекта. Когда среда выполняет целевой метод (например, Bank.Withdraw(int Amount)) исполнится метод LoggingConcern.LogWithdraw(int Amount) и метод Bank.Withdraw(int Amount) продолжит своё исполнение.
Советы. Советы – действия, которые могут изменить ввод/вывод метода. Действие кэширование – прекрасный пример. Когда выполняется целевой метод (например, CustomerService.GetById(int Id)) исполняется метод CachingConcern.TryGetCustomerById(int Id) и возвращает значение, найденное в кэше, или продолжает исполнение при его отсутствие.
Советам можно: • Проверять параметры в срезе цели и возможность менять их при необходимости • Отменять выполнение целевых методов и замещать их другой реализацией • Проверять возвращаемое значение целевого метода и изменять или замещать их
Вы всё еще читаете? Браво! Parce que vous le valez bien…
На этом заканчиваем с концепций и понятиями АОП. Давайте познакомимся с ним поближе на C#.
Наша реализация
Покажи мне код u je tue le chien!
Действия (concern)
Действие должно реализовывать волшебство this, которое относится к типу нашей цели. No problemo!
public interface IConcern < T This < get; >// вообще читерство, но кого волнует? >
Срезы (pointcut)
Нет простого способа получить срезы для каждой строчки кода. Но по одной мы всё-таки можем получить и это довольно просто используя System.Reflection.MethodBase класс. MSDN о нём не многословен: Предоставляет сведения о методах и конструкторах.
Между нами, использование MethodBase для получения ссылок на срезы – наиболее мощное из возможных средств в нашей задаче. Можно получить доступ к срезам конструкторов, методов, свойств и событий, потому что практически все что вы объявляете в .Net в конечном итоге сводится к методу…
public class Customer < public event EventHandlerNameChanged; public string Name < get; private set; >public void ChangeName(string newName) < Name = newName; NameChanged(this, EventArgs.Empty); >> class Program < static void Main(string[] args) < var t = typeof(Customer); // Конструктор (и неограничваясь пустым) var pointcut1 = t.GetConstructor(new Type[] < >); // Метод ChangeName var pointcut2 = t.GetMethod("ChangeName"); // Свойство Name var nameProperty = t.GetProperty("Name"); var pointcut3 = nameProperty.GetGetMethod(); var pointcut4 = nameProperty.GetSetMethod(); // Всё связанное с событием NameChanged var NameChangedEvent = t.GetEvent("NameChanged"); var pointcut5 = NameChangedEvent.GetRaiseMethod(); var pointcut6 = NameChangedEvent.GetAddMethod(); var pointcut7 = NameChangedEvent.GetRemoveMethod(); > >
Точки соединения (joinpoint)
Писать код для соединения реально просто. Посмотрите на этот код:
Мы можем добавить его во что-то вроде реестра, который сделаем позже, и можем начинать писать код вроде этого.
public class Customer < public string Name < get; set;>public void DoYourOwnBusiness() < System.Diagnostics.Trace.WriteLine(Name + " занят своим делом"); >> public class LoggingConcern : IConcern < public Customer This < get; set; >public void DoSomething() < System.Diagnostics.Trace.WriteLine(This.Name + " собирается заняться своим делом"); This.DoYourOwnBusiness(); System.Diagnostics.Trace.WriteLine(This.Name + " закончил заниматься своим делом"); >> class Program < static void Main(string[] args)h < // получить срез в Customer.DoSomething(); var pointcut1 = typeof(Customer).GetMethod("DoSomething"); var concernMethod = typeof(LoggingConcern).GetMethod("DoSomething"); // Соединить их AOP.Registry.Join(pointcut1, concernMethod); >>
Далеко ли мы ушли от нашего псевдокода? На мой взгляд, не очень… Так что дальше?
Собираем всё вместе…
Вот тут одновременно начинаются проблемы и веселье! Но начнем с простого
Реестр
Реестр будет хранить записи о наших точках соединения. Берем список-синглтон для точек соединения. Точка соединения – простая структура:
public struct Joinpoint < internal MethodBase PointcutMethod; internal MethodBase ConcernMethod; private Joinpoint(MethodBase pointcutMethod, MethodBase concernMethod) < PointcutMethod = pointcutMethod; ConcernMethod = concernMethod; >// служебный метод для создания точек соединения public static Joinpoint Create(MethodBase pointcutMethod, MethodBase concernMethod) < return new Joinpoint (pointcutMethod, concernMethod); >>
Ничего особенного… Еще ему нужно реализовывать IEquatable, но, чтобы сделать код короче, я его убрал.
И реестр. Класс называется AOP и является синглтоном. Он предоставляет доступ к своему уникальному экземпляру через статическое свойство названое Registry:
public class AOP : List < static readonly AOP _registry; static AOP() < _registry = new AOP(); >private AOP() < >public static AOP Registry < get < return _registry; >> [MethodImpl(MethodImplOptions.Synchronized)] public void Join(MethodBase pointcutMethod, MethodBase concernMethod) < var joinPoint = Joinpoint.Create(pointcutMethod, concernMethod); if (!this.Contains(joinPoint)) this.Add(joinPoint); >>
С помощью класса AOP можно написать такую конструкцию:
AOP.Registry.Join(pointcut, concernMethod);
Хьюстон, у нас проблемы
Здесь мы столкнулись с очевидной и большой проблемой, с которой надо что-то делать. Если разработчик напишет вот так
var customer = new Customer ; customer.DoYourOwnBusiness();
то нет причин, по которым нужно обращаться к нашему реестру, и наш метод LoggingConcern.DoSomething() не запустится.
Беда в том, что .Net не предоставляет нам простого способа перехватить такие вызовы. Раз нет встроенного механизма, нужно сделать свой. Возможности вашего механизма будут определять возможности вашей реализации АОП. Цель данной статьи – не обсуждение всех возможных техник перехвата. Просто обратите внимание, что способ перехвата является ключевым отличием реализаций АОП.
Сайт SharpCrafters (владельцы PostSharp) приводят некоторую четкую информацию по двум основным техникам: • Встраивание на этапе компиляции • Встраивание во время исполнения
Наша реализация – Прокси
В общем, не секрет, что для осуществления перехвата есть три варианта: • Создать свой язык и компилятор для получения сборок .net: при компиляции можно внедрить что угодно куда угодно. • Реализовать решение, изменяющее поведение сборок при исполнении. • Поставить между клиентом и целью прокси, осуществляющее перехват вызовов.
Замечание для продвинутых парней: я нарочно не рассматриваю возможности API отладчика и профилировщика, так как их использование нежизнеспособно в продакшене.
Замечание для самых продвинутых: гибрид первого и второго варианта, используемый в Raslyn API, может быть реализован, но, как я знаю, пока ещё только делается. A bon entendeur…
Тем более, если вам не нужно иметь возможность делать срезы в любой строчке кода, первые два варианта чересчур сложны.
Перейдем к третьему варианту. Есть две новости по поводу прокси: хорошая и плохая. Плохая – во время исполнения нужно подменять цель на экземпляр прокладки. Если хочется перехватывать конструкторы, то придется делегировать создание экземпляров целевых классов фабрике, такое встраивание действий эта реализация не может. Если существует экземпляр целевого класса необходимо явно запросить замену. Для мастеров инверсии управления и внедрения зависимостей – это даже не задача. Для остальных же это значит, что придется использовать фабрику для обеспечения всех возможностей в нашей технике перехвата. Но не волнуйтесь, мы построим эту фабрику.
Хорошая новость – для реализации прокси ничего делать не надо. Класс System.Runtime.Remoting.Proxies.RealProxy построит его оптимальным способом. По-моему, название класса не отражает его назначения. Этот класс – не прокси, а перехватчик. Тем не менее, он сделает нам прокси вызовом его метода GetTransparentProxy(), а это то, что нам, собственно, от него и нужно.
Вот рыба перехватчика:
public class Interceptor : RealProxy, IRemotingTypeInfo < object theTarget < get; set; >public Interceptor(object target) : base(typeof(MarshalByRefObject)) < theTarget = target; >public override System.Runtime.Remoting.Messaging.IMessage Invoke(System.Runtime.Remoting.Messaging.IMessage msg) < IMethodCallMessage methodMessage = (IMethodCallMessage) msg; MethodBase method = methodMessage.MethodBase; object[] arguments = methodMessage.Args; object returnValue = null; // TODO: // реализация подмены метода для случая, когда AOP.Registry // уже содержит точку соединения MethodBase, содержащуюся в переменной"method". // если в реестре нет точки соединения, просто искать соответствующий метод // в объекте "theTarget" и вызвать его. ;-) return new ReturnMessage(returnValue, methodMessage.Args, methodMessage.ArgCount, methodMessage.LogicalCallContext, methodMessage); >#region IRemotingTypeInfo public string TypeName < get; set; >public bool CanCastTo(Type fromType, object o) < return true; >#endregion >
Некоторые пояснения, т.к. мы забрались в самое сердце реализации…
Класс RealProxy создан для перехвата вызовов от удалённых объектов и упорядочиванию целевых объектов. Под удалёнными следует понимать по-настоящему удаленные: объекты из другого приложения, другого домена приложений, другого сервера и т.д.). Не углубляясь в детали, имеется два способа упорядочивания удалённых объектов в инфраструктуре .Net: по ссылке и по значению. Поэтому упорядочить удалённые объекты можно только, если они наследуют MarshalByRef или реализуют ISerializable. Мы не собираемся использовать возможности удаленных объектов, но тем не менее нам необходимо, чтобы класс RealProxy думал, что цель поддерживает удаленное управление. Из-за этого мы передаем typeof(MarshalByRef) в конструктор RealProxy.
RealProxy получает все вызовы через прозрачный прокси с помощью метода Invoke(System.Runtime.Remoting.Messaging.IMessage msg). Именно здесь мы реализуем суть подмены методов. Смотри комментарии в коде выше.
Касательно реализации IRemotingTypeInfo: в реальной удаленной среде, клиент запросит у сервера объект. Клиентское приложение может вообще ничего не знать о типе получаемого объекта. Соответственно, когда клиентское приложение вызывает public object GetTransparentProxy() среда может ли возвращаемый объект (прозрачный прокси) соответствовать контракту приложения. Реализуя IRemotingTypeInfo мы даем подсказку среде клиента какое приведение допустимо, а какое нет.
А теперь дивитесь, какой трюк мы здесь используем.
public bool CanCastTo(Type fromType, object o)
Вся наша реализация АОП возможна исключительно благодаря возможности написать в для удаленного объекта эти два слова: return true. Которые означают, что что мы можем привести объект возвращаемый GetTransparentProxy() к какому угодно интерфейсу без какой-либо проверки средой.
Среда просто дает нам добро на любые действия! Можно поправить этот код и возвращать что-то более разумное, чем true для любого типа… Но можно также, представить себе как извлечь пользу из поведения предоставляемым Несуществующим Метода или перехватить весь интерфейс… В общем, появляется довольно много пространства для фантазии…
Сейчас у нас уже есть достойный механизм перехвата для нашего целевого экземпляра. Но у нас всё еще нет перехвата конструкторов и прозрачного прокси. Это задача для фабрики…
Фабрика
Сказать особо нечего. Вот рыба класса.
public static class Factory < public static object Create(params object[] constructorArgs) < T target; // TODO: // Основываясь на typeof(T) и списке constructorArgs (кол-ву и их типах) // мы можем спросить реестр, есть ли в нём точка соединения для конструктора // и вызвать его, а если нет - найти соответствующий и вызвать // Присвоить результат конструктора переменной “target” (цель) и передать // её методу GetProxy return GetProxyFor(target); > public static object GetProxyFor(object target = null) < // Здесь мы перехватываем вызовы к существующему экземпляру объекта // (может мы его и создали, но необязательно) // Просто создайте перехватчик и верните прозрачный прокси return new Interceptor(target).GetTransparentProxy(); >>
Обратите внимание, что класс Factory всегда возвращает объект типа объект. Мы не можем вернуть объект типа Т просто потому, что прозрачный прокси не типа Т, он типа System.Runtime.Remoting.Proxies.__TransparentProxy. Но помните о разрешении данном нам средой, мы можем привести возвращаемый объект к любому интерфейсу без проверки!
Поселим класс Factory в наш класс AOP с надеждой, что мы передадим нашим заказчикам опрятный код. На это можно посмотреть в разделе Использование.
Последние замечания по реализации
Если вы дочитали до этой точки, то вы – герой! Bravissimo! Kudos!
Чтобы сохранить статью краткой и понятной (и чего смешного?), я не стану вдаваться в скучные рассуждения о деталях реализации получения и переключения методов. В этом нет ничего интересного. Но если вам всё-таки интересно: качайте код и смотрите – он полностью рабочий! Названия классов и методов, могут немного отличаться, т.к. я правил его параллельно, но особых изменений быть не должно.
Внимание! Перед использованием кода в вашем проекте, внимательно прочитайте paenultimus. А если не знаете что значит paenultimus, кликайте по ссылке.
Использование
Я уже много чего написал, но до сих пор не показал, что же со всем этим делать. И вот он момент истины!
Что в архиве (качать)
Архив включает в себя 5 примеров, демонстрирующих внедрение следующих аспектов: • Перехват конструктора • Перехват методов и свойств • Перехват событий • Перехват инициализации типов • Перехват File.ReadAllText(string path)
А здесь я продемонстрирую два из пяти: наиболее и наименее очевидный.
Перехват методов и свойств
Первое, нам нужна доменная модель. Ничего особенного.
public interface IActor < string Name < get; set; >void Act(); > public class Actor : IActor < public string Name < get; set; >public void Act() < Console.WriteLine("My name is ''. I am such a good actor!", Name); > >
Теперь нам нужно действие
public class TheConcern : IConcern < public Actor This < get; set; >public string Name < set < This.Name = value + ". Hi, " + value + " you've been hacked"; >> public void Act() < This.Act(); Console.WriteLine("You think so. "); >>
При инициализации приложения мы сообщаем реестру о наших точках соединения
// Weave the Name property setter AOP.Registry.Join ( typeof(Actor).GetProperty("Name").GetSetMethod(), typeof(TheConcern).GetProperty("Name").GetSetMethod() ); // Weave the Act method AOP.Registry.Join ( typeof(Actor).GetMethod("Act"), typeof(TheConcern).GetMethod("Act") );
И, наконец, мы создаем объект в фабрике
var actor1 = (IActor) AOP.Factory.Create(); actor1.Name = "the Dude"; actor1.Act();
Обратите внимание, что мы запросили создание класса Actor, но мы можем привести результат к интерфейсу, потому будем приводить к IActor, т.к. класс его реализует.
Если запустить всё это в консольном приложении, получим: My name is ‘the Dude. Hi, the Dude you’ve been hacked’. I am such a good actor! You think so.
Перехват File.ReadAllText(string path)
Здесь две небольшие проблемы: • Класс File статичный • и не реализует никакие интерфейсы
Помните о «добро»? Среда не проверяет тип возвращаемый прокси и соответствие интерфейсу. Значит мы можем создать любой интерфейс. Никто его не реализует ни цель, ни действие. В общем, мы используем интерфейс как контракт. Давайте создадим интерфейс прикидывающийся статичным классом File.
public interface IFile
public class TheConcern < public static string[] ReadAllLines(string path) < return File.ReadAllLines(path).Select(x =>x + " hacked. ").ToArray(); > >
var path = Path.Combine(Environment.CurrentDirectory, "Examples", "data.txt"); var file = (IFile) AOP.Factory.Create(typeof(File)); foreach (string s in file.ReadAllLines(path)) Console.WriteLine(s);
В этом примере, обратите внимание, что мы не можем использовать метод Factory.Create, т.к. статические типы не могут использоваться как аргументы.
Ссылки
Что дальше?
Мы смогли добиться основной цели АОП: реализовать аспект и зарегистрировать его для исполнения. TinyAOP появился на свет. Но ваш путь в землях АОП ещё не закончен и, возможно, вы захотите углубиться.
Причина 1: Кому охота регистрировать точки соединения так, как мы это делаем сейчас? Точно не мне! Немного анализа и можно сделать всё более практичным и похожим на настоящую АОП-библиотеку. АОП нужно для упрощения жизни, а не создания головной боли.
Причина 2: Темы примесей и агрегирования вообще не раскрыты, а от них можно ожидать много чего хорошего.
Причина 3: Нам нужна производительность и стабильность. Сейчас код всего лишь доказательство работоспособности концепции. Он довольно медленный и его можно сделать очень быстрым. Проверка ошибок тоже не помешает.
Причина 4: Мы перехватываем почти все классы, но как быть с перехватом интерфейсов?
Причина 5: Вам еще мало причин?
Заключение
У нас есть хороший и компактный прототип, демонстрирующий техническую возможность реализации АОП в чистом управляемом коде без вшивания и т.п.
Теперь вы знаете об АОП, можете написать собственную реализацию.
Тут внедряется переводчик и начинает нести отсебятину
Хотя и полностью согласен с автором по поводу этого раздела. Сайты вроде habahabr.ru, codeproject.com и им подобные существуют только потому, что разные люди публикуют на них статьи. Неважно почему они это делают, но эта работа делается. Пожалуйста, не пренебрегайте авторами. Если вам не понравилась статья, объясните в комментариях, что не так. Если вы будете просто минусовать, никто не узнает, как сделать лучше. Если понравилась, тоже не молчите!
А теперь точно отсебятина переводчика
Вообще, к моему удивлению вменяемо перевести многие слова из АОП не получилось, потому предлагаю в комментах предложить хорошие и понятные русские слова для следующих терминов: • Aspect (тут вроде понятно, но всё же) • Concern • Joinpoint • Cross-cutting • Weaving (вивер — ничего, но лучше бы русское слово найти)
.NET
Проектирование и рефакторинг
C#
Что такое АОП? Основы аспектно-ориентированного программирования
Hello, guys! Без понимания основных концепций довольно сложно вникнуть во фреймворки и подходы к построению функционала. Так что сегодня поговорим об одной из таких концепций — АОП, или аспектно-ориентированное программирование .Это тема не из легких и нечасто применяется напрямую, но во многих фреймворках и технологиях она используется под капотом. Ну и конечно, иногда на собеседованиях вас могут попросить рассказать в общих чертах, что это за зверь такой и где его можно применить. Поэтому давайте рассмотрим основные концепции и несколько несложных примеров AOП на Java .Итак, АОП — аспектно-ориентированное программирование — это парадигма, направленная на повышение модульности различных частей приложения за счет разделения сквозных задач. Для этого к уже существующему коду добавляется дополнительного поведение, без изменений в изначальном коде. Иными словами, мы как бы навешиваем сверху на методы и классы дополнительную функциональность, не внося поправки в модифицируемый код. Зачем это нужно? Рано или поздно мы приходим к тому, что обычный объектно-ориентированный подход не всегда может эффективно решить те или иные задачи. В такой момент на помощь приходит АОП и дает нам дополнительные инструменты для постройки приложения. А дополнительные инструменты — это увеличение гибкости при разработке, благодаря которой появляется больше вариантов решения той или иной задачи.
Применение АОП
Аспектно-ориентированное программирование предназначено для решения сквозных задач, которые могут представлять собой любой код, многократно повторяющийся разными методами, который нельзя полностью структурировать в отдельный модуль. Соответственно, с помощью АОП мы можем оставить это за пределами основного кода и определить его по вертикали. В качестве примера можно привести применение политики безопасности в каком-либо приложении. Как правило безопасность проходит сквозь многие элементы приложения. Тем более, политика безопасности приложения должна применяться одинаково ко всем существующим и новым частям приложения. При этом используемая политика безопасности может и сама развиваться. Вот тут нам отлично может пригодится использование АОП . Также в качестве еще одного примера можно привести логирование. У использования АОП подхода к логированию есть несколько преимуществ по сравнению с ручной вставкой логирования:
Код для логирования легко внедрять и удалять: всего-то нужно добавить или удалить пару конфигураций некоторого аспекта.
Весь исходный код для логирования хранится в одном месте и не нужно находить вручную все места использования.
Код, предназначенный для логирования, можно добавить в любое место, будь то уже написанные методы и классы или же новый функционал. Это уменьшает количество ошибок разработчика. Также при удалении аспекта из конфигурации конструкции можно быть абсолютно уверенным, что весь код трассировки удален и ничего не пропущено.
Аспекты — это вынесенный отдельно код, который можно многократно переиспользовать и улучшать.
Также АОП используется для обработки исключений, кеширования, выноса некоторого функционала, чтобы сделать его переиспользуемым.
Основные понятия АОП
Перед (Before) — советы данного типа запускаются перед выполнением целевых методов — точек соединения. При использовании аспектов в виде классов мы берем @Before аннотацию, чтобы пометить тип совета как идущий перед. При использовании аспектов в виде файлов .aj это будет метод before() .
После (After) — советы, которые выполняются после завершения выполнения методов — точек соединения, как в обычных случаях, так и при бросании исключения. При использовании аспектов в виде классов мы можем использовать @After аннотацию для указания, что это совет, идущий после. При использовании аспектов в виде файлов .aj это будет метод after() .
После возврата (After Returning) — данные советы выполняются только в том случае, когда целевой метод отрабатывает нормально, без ошибок. Когда аспекты представлены в виде классов, мы можем использовать аннотацию @AfterReturning , чтобы пометить совет как выполняемый после успешного завершения. При использовании аспектов в виде файлов .aj это будет метод after() returning (Object obj) .
После бросания (After Throwing) — данный вид советов предназначен для тех случаев, когда метод, то есть точка соединения выдает исключение. Мы можем использовать этот совет для некой обработки неудачного выполнения (к примеру, для отката всей транзакции или логирования с необходимым уровнем трассировки). Для аспектов-классов аннотация @AfterThrowing используется, чтобы указать, что этот совет используется при после броска исключения. При использовании аспектов в виде файлов .aj это будет метод — after() throwing (Exception e) .
Вокруг (Around) — пожалуй, один из самых важных видов советов, который окружает метод, то есть — точку соединения, с помощью которого мы можем, к примеру, выбрать, выполнять данный метод точки соединения или нет. Можно написать код совета, который будет выполняться до и после выполнения метода точки соединения. В обязанности around advice входит вызов метода точки соединения и возвращение значений, если метод что-то возвращает. То есть в этом совете можно попросту сымитировать работу целевого метода, не вызывая его, и в качестве результата вернуть что-то свое. При аспектах в виде классов используем @Around аннотацию для создания советов, оборачивающих точку соединения. При использовании аспектов в виде файлов .aj это будет метод around() .
плетение во время компиляции — если у вас есть исходный код аспекта и код, в котором вы используете аспекты, вы можете скомпилировать исходный код и аспект напрямую с помощью компилятора AspectJ;
посткомпиляционное плетение (бинарное плетение) — если вы не можете или не хотите использовать преобразования исходного кода для вплетения аспектов в код, вы можете взять уже скомпилированные классы или jar-файлы и внедрить аспекты;
плетение во время загрузки — это просто бинарное плетение, отложенное до момента, когда загрузчик классов загрузит файл класса и определит класс для JVM. Для поддержки этого требуется один или несколько «загрузчиков классов плетения». Они либо явно предоставляются средой выполнения, либо активируются с помощью «агента плетения.
Примеры в Java
Далее для большего понимания АОП мы рассмотрим небольшие примеры уровня Hello World.Сразу отмечу, что в наших примерах будем использовать плетение во время компиляции . Сперва нам нужно прописать следующую зависимость в нашем pom.xml :
org.aspectjaspectjrt1.9.5
Как правило для использования аспектов применяют особый компилятор Ajs . В IntelliJ IDEA по умолчанию его нет, поэтому при выборе его как компилятора приложения нужно указать путь к дистрибутиву AspectJ . Подробнее о способе выбора Ajs как компилятора можно почитать на этой странице. Это был первый способ, а второй (которым я и воспользовался) — прописать следующий плагин в pom.xml :
После этого желательно сделать реимпорт у Мавена и запустить mvn clean compile . А теперь перейдём непосредственно к примерам.
Пример №1
Давайте создадим класс Main . В нем у нас будет точка запуска и метод, который печатает в консоли переданные ему имена:
public class Main < public static void main(String[] args) < printName("Толя"); printName("Вова"); printName("Саша"); >public static void printName(String name) < System.out.println(name); >>
Ничего сложного: передали имя — вывели его в консоли. Если мы сейчас запустим, в консоли будет выведено:
Толя Вова Саша
Что ж, пришло время воспользоваться возможностями АОП. Сейчас нам нужно создать файл — аспект . Они бывают двух видов: первый — файл с расширением .aj , второй — обычный класс, который реализует возможности АОП при помощи аннотаций. Давайте сперва рассмотрим файл с расширением .aj :
Данный файл чем-то похож на класс. Разберемся, что здесь происходит: pointcut — срез или набор точек соединения; greeting() — название данного среза; : execution — при выполнении * — всех, вызов — Main.printName(..) — данного метода. Далее идёт конкретный совет — before() — который выполняется до вызова целевого метода, : greeting() — срез, на который данный совет реагирует, ну а ниже мы видим само тело метода, которое написано на понятном нам языке Java. При запуске main с наличием данного аспекта мы получим вывод в консоль:
Привет Толя Привет Вова Привет Саша
Мы видим, что каждый вызов метода printName был модифицирован при помощи аспекта. А теперь давайте взглянем, как будет выглядеть аспект, но уже как класс Java с аннотациями:
@Aspect public class GreetingAspect < @Pointcut("execution(* Main.printName(String))") public void greeting() < >@Before("greeting()") public void beforeAdvice() < System.out.print("Привет "); >>
@Aspect обозначает, что данный класс является аспектом; @Pointcut(«execution(* Main.printName(String))») — точка среза, которая срабатывает на все вызовы Main.printName с входящим аргументом типа String ;
@Before(«greeting()») — совет, который применяется до вызова кода описанного в точке среза greeting() .
Привет Толя Привет Вова Привет Саша
Пример №2
Допустим, у нас есть некоторый метод который осуществляет некоторые операции для клиентов и вызов этого метода из main :
public class Main < public static void main(String[] args) < makeSomeOperation("Толя"); >public static void makeSomeOperation(String clientName) < System.out.println("Выполнение некоторых операций для клиента - " + clientName); >>
С помощью аннотации @Around сделаем что-то типа “псевдотранзакции”:
@Aspect public class TransactionAspect < @Pointcut("execution(* Main.makeSomeOperation(String))") public void executeOperation() < >@Around(value = "executeOperation()") public void beforeAdvice(ProceedingJoinPoint joinPoint) < System.out.println("Открытие транзакции. "); try < joinPoint.proceed(); System.out.println("Закрытие транзакции. "); >catch (Throwable throwable) < System.out.println("Операция не удалась, откат транзакции. "); >> >
С помощью метода proceed объекта ProceedingJoinPoint мы вызываем оборачиваемый метод, чтобы определить его место в совете и, соответственно, код в методе, который выше joinPoint.proceed(); — это Before , который ниже — After . Если мы запустим main , в консоли мы получим:
Открытие транзакции. Выполнение некоторых операций для клиента — Толя Закрытие транзакции.
Если же мы добавим бросок исключения в наш метод (вдруг выполнение операции дало сбой):
public static void makeSomeOperation(String clientName)throws Exception
То мы получим вывод в консоли:
Открытие транзакции. Выполнение некоторых операций для клиента — Толя Операция не удалась, откат транзакции.
Получилась такая себе псевдообработка неудачи.
Пример №3
В качестве следующего примера сделаем что-то типа логирования в консоли. Для начала посмотрим в Main , где у нас происходит псевдо бизнес-логика:
public class Main < private String value; public static void main(String[] args) throws Exception < Main main = new Main(); main.setValue(""); String valueForCheck = main.getValue(); main.checkValue(valueForCheck); > public void setValue(String value) < this.value = value; >public String getValue() < return this.value; >public void checkValue(String value) throws Exception < if (value.length() >10) < throw new Exception(); >> >
В main с помощью setValue мы зададим значение внутренней переменной — value , далее с помощью getValue возьмём это значение и в checkValue проверим, длиннее ли это значение 10 символов. Если да, будет брошено исключение. Теперь посмотрим на аспект, с помощью которого мы будем логировать работу методов:
@Aspect public class LogAspect < @Pointcut("execution(* *(..))") public void methodExecuting() < >@AfterReturning(value = "methodExecuting()", returning = "returningValue") public void recordSuccessfulExecution(JoinPoint joinPoint, Object returningValue) < if (returningValue != null) < System.out.printf("Успешно выполнен метод - %s, класса- %s, с результатом выполнения - %s\n", joinPoint.getSignature().getName(), joinPoint.getSourceLocation().getWithinType().getName(), returningValue); >else < System.out.printf("Успешно выполнен метод - %s, класса- %s\n", joinPoint.getSignature().getName(), joinPoint.getSourceLocation().getWithinType().getName()); >> @AfterThrowing(value = "methodExecuting()", throwing = "exception") public void recordFailedExecution(JoinPoint joinPoint, Exception exception) < System.out.printf("Метод - %s, класса- %s, был аварийно завершен с исключением - %s\n", joinPoint.getSignature().getName(), joinPoint.getSourceLocation().getWithinType().getName(), exception); >>
Когда у метода есть возвращаемое значение if (returningValue != null)
Когда возвращаемого значения нет else
Успешно выполнен метод — setValue, класса- Main Успешно выполнен метод — getValue, класса- Main, с результатом выполнения — <некоторое значение>Метод — checkValue, класса- Main, был аварийно завершен с исключением — java.lang.Exception Метод — main, класса- Main, был аварийно завершен с исключением — java.lang.Exceptionнекоторое>
Ну и так как мы не обработали исключения, еще получим его стектрейс:Почитать об исключениях и их обработке можно в этих статьях: Исключения в Java и Исключения и их обработка. На этом у меня сегодня всё. Сегодня мы познакомились с АОП , и вы смогли увидеть, что сей зверь не так страшен, как его рисуют. Goodbye everyone!
ГАУ АОП
ГОСУДАРСТВЕННОЕ АВТОНОМНОЕ УЧРЕЖДЕНИЕ ГОРОДА МОСКВЫ «МОСКОВСКОЕ АГЕНТСТВО РЕАЛИЗАЦИИ ОБЩЕСТВЕННЫХ ПРОЕКТОВ»
Действующая организация
ОГРН 1057747228703 от 14 июня 2005 г. ИНН/КПП 7718550691 771801001
Дата регистрации 14.06.2005
Все реквизиты (ФНС / ПФР / ФСС / РОССТАТ)
Юридический адрес 107392 , город Москва , Малая Черкизовская ул, д. 22 Еще 3 организации по этому адресу
Руководитель Директор Луценко Сергей Витальевич с 4 июля 2017 г.
Среднесписочная численность нет данных Специальный налоговый режим ░░░░░░░░░░
Реестр МСП не входит
Правопредшественник
Подробнее в выписке из ЕГРЮЛ
Основной вид деятельности Государственное регулирование деятельности в области здравоохранения, образования, социально-культурного развития и других социальных услуг, кроме социального обеспечения (84.12) Все виды деятельности (57)
Налоговый орган Инспекция ФНС России № 18 по г.Москве с 14 июня 2005 г.
Коды статистики ОКПО 77510651 ОКАТО 45263594000 ОКТМО 45316000000 ОКФС 13 Собственность субъектов Российской Федерации ОКОГУ 2300231 — культуры ОКОПФ 75201 Государственные автономные учреждения субъектов Российской Федерации
Телефон +7 (░░░) ░░░-░░-░░ +7 (░░░) ░░░-░░-░░
Электронная почта ░░░░░░░░░░@░░░░░░.ru ░░░░@░░░░░░.ru
Сайт ░░░░░░░░░░.ru ░░░░░░.ru
Для просмотра контактов оформите профессиональный доступ
По организации доступны исторические сведения (488 изменений).
Следить за организацией
Как это работает и зачем нужно?
Это ваша компания?
Актуально на 29.01.2024
Директор ГАУ АОП — Луценко Сергей Витальевич (ИНН 370200674602). Организации присвоен ИНН 7718550691, ОГРН 1057747228703. Подробное описание >
Юридическое лицо зарегистрировано 14.06.2005 по адресу 107392, город Москва, Малая Черкизовская ул, д. 22. Статус: действующая с 14.06.2005. ОКПО 77510651.
До Луценко Сергей Витальевич, руководителем «ГАУ АОП» являлись: Бесполденов Александр Владимирович (ИНН 622802450102), Гудыма Георгий Валерьевич (ИНН 772904966356), Игнатова Екатерина Анатольевна (ИНН 773606771709), Давлеткалиев Денис Куанышевич (ИНН 263204175738). Ранее ГАУ АОП находилось по адресу: 107392, город Москва, улица Черкизовская М., 22.
Компания работает 18 лет 7 месяцев, с 14 июня 2005 по настоящее время. В выписке ЕГРЮЛ учредителем указана 1 государственная структура. Основной вид деятельности «ГАУ АОП» — Государственное регулирование деятельности в области здравоохранения, образования, социально-культурного развития и других социальных услуг, кроме социального обеспечения и 56 дополнительных видов.
Состоит на учете в налоговом органе Инспекция ФНС России № 18 по г.Москве с 14 июня 2005 г., присвоен КПП 771801001. Регистрационный номер ПФР 087407002348, ФСС 772402386677381. < Свернуть