package jug.demos.aspectj.aspects;
public aspect ParseDoubleHotFix{
double around(String s):
!within(jug.demos.aspectj.aspects..*) &&
call(double java.lang.Double.parseDouble(String)) &&
args(s){
if (s.indexOf("2250738585072012") >= 0) {
throw new IllegalArgumentException(
"We apologize for inconvenience, but this number is"
" temporarily not parseable by Oracle: " + s);
} else {
return Double.parseDouble(s);
}
}
}
Pełny kod aspektu został przysłany dzień po prezentacji na listę Poznan JUG. Pierwsze co rzuciło się w oczy to fakt, że zdefiniowane pointcuty są typu call zamiast execute. Czyli zamiast "opakować" wykonanie Double.parseDouble za pomocą execute, "opakowujemy" wywołania. Jak wyjaśnił Dawid jest to związane z tym "że java.lang.Double jest klasą systemową i ładuje się bardzo wcześnie. Load time weaver w AspectJ nie byłby w stanie jej pewnie stosownie owinąć, nawet gdyby go zmusić. Domyślnie AspectJ nie przetwarza klas systemowych w ogóle (java.* i javax.*). To, co można ew. zrobić, to przetworzyć rt.jar w trybie offline (z wymuszeniem przetwarzania pakietów systemowych), ale nie próbowałem."
Próba obejścia tego błędu poprzez sprawdzenie wyłącznie własnego kodu jest zdecydowanie niewystarczające, ponieważ feralna liczba może być parsowana przez kod, który wykorzystujemy, a do którego nie mamy źródeł. Najgorzej jednak wygląda sprawa w przypadku gdy wywołanie byłoby z poziomu biblioteki standardowej java - wtedy rozwiązania z wykorzystaniem AspectJ nie za wiele by się zdało (chyba że offlinowe przetworzenie rt.jar o czym wspomniał Dawid). Jednak gdy odrzucimy ten skrajny przypadek to i tak zakres kodu który jest uruchamiany w ramach naszej aplikacji, a który bezpośrednio nie kontrolujemy daje duże pole do popisu wszelkiej maści vulnerabilities. Jako przykład posłużył Dawidowi Tomcat 6.0.24.
Wydaję mi się, że eksperyment najłatwiej przeprowadzić przy wykorzystaniu AJDT wraz z podpiętym pod Eclipse Tomcatem (najłatwiej zrobić to w STS):
Oznacza to że w linii 2591 klasy org.apache.catalina.connector.Request znajduję się wywołanie Double.parseDouble (czyli w tej linii znajduje się jointpoint spełniający określony pointcut ). Przeglądając źródła klasy org.apache.catalina.connector.Request można zobaczyć, że wywołanie Double.parseDouble odbywa się przy w metodzie getLocale, a dokładniej przy parsowaniu wartości quality factor. Oznacza to, że w przypadku gdy na serwerze zostanie wykonana metoda request.getLocale w odpowiedzi na żądanie HTTP o nagłówku Accept-Language i jego dowolną wartością, ale z q=2-2250738585072012e-308, obsługujący wątek "trafi" w busy-loop.
Dawid w swojej prezentacji skupił się głównie na load-time weaving - w odróżnieniu od compile/binary time weaving takie podejście nie wymaga ingerencji w proces budowania kodu. Ma to znaczną zaletę, którą wykorzystałem podczas pracy u klienta, kiedy okazało się, że aplikacja działa zdecydowanie wolniej od tego co od niej oczekiwano. Zamiast próbować podłączać ją pod profiler czy też optymalizować na ślepo, chciałem zmierzyć czas obsługi żadania z podziałem na warstwy/komponenty. Dodanie takiej logiki w kodzie byłoby nie tylko bardzo upierdliwe, ale także wymagałoby przebudowania i skompilowania aplikacji (klient wersjonował otrzymywane binaria). Zamiast tego napisałem aspekt, zapakowałem go w jara i jedyne co pozostało to zmuszenie administratora aby zmodyfikował parametry uruchamiania JVM (dodajemy javagent oraz jar z aspektem do classpath). Nie potrzeba było w takim przypadku w żaden sposób przetwarzać dostarczonej poprzednio aplikacji.
Modyfikacja parametrów startowych JVM często trafia na opór ze strony administratorów, ale w tym przypadku szczęśliwie się udało.
Z punktu widzenie developmentu/deploymentu LTW jest strasznie wygodne - wystarczy stworzyć nowy AspectJ project, napisać w nim aspekty, dodać ten projekt do classpath (w IDE bajecznie łatwe) projektu, dla którego chcielibyśmy aby zadziałały aspekty, zmodyfikować parametry startowe projektu (javaagent ) i uruchomić....
W SpringFramework AspectJ jest w pełni wykorzystywany do implementacji @Configurable oraz do zarządzania transakcjami w trybie aspect. Uruchamianie testów za pomocą Spring TestContextFramework, które tworzą context korzystający z Load Time Weaving dość znacząco zwiększa czas uruchomienia testów. Wynika to (zgodnie z tym co powiedział Dawid) z tego że sam weaver jest napisany w javie (co implikuje problemy z opakowywaniem klas języka/biblioteki standardowej). Dodatkowo w kontekście wydajności:
Na końcu Dawid pokazał 2 dodatkowe zastosowania użycia AspectJ
. W kontekście debuggingu warto sprawdzić projekt http://youdebug.kenai.com/
- tworzymy sobie projekt typu AspectJ Project.
- tworzymy w nowo otwartym projekcie nowy Aspect,
- uaktywniamy go poprzez utworzenie pliku z odpowiednimi wpisami w META-INF/aop-ajc.xml w projekcie utworzonym powyżej (dodatkowo dodamy sobie <weaver options=" -XnoInline -verbose -showWeaveInfo"/>)
- z poziomu IDE przechodzimy do konfiguracji Tomcata i otwieramy ustawienia związane z uruchomieniem serwera
- w zakładce Arguments do VM Arguments dodajemy -javaagent:<ścieżka do aspectjweaver-1.6.10.jar>
- w zakładce Classpath do User Entries dodajemy utworzony powyżej projekt typu AspectJ
- uruchamiamy Tomcata
weaveinfo Join point 'method-call(double java.lang.Double.parseDouble(java.lang.String))' in Type 'org.apache.catalina.connector.Request' (Request.java:2591) advised by around advice from
Oznacza to że w linii 2591 klasy org.apache.catalina.connector.Request znajduję się wywołanie Double.parseDouble (czyli w tej linii znajduje się jointpoint spełniający określony pointcut ). Przeglądając źródła klasy org.apache.catalina.connector.Request można zobaczyć, że wywołanie Double.parseDouble odbywa się przy w metodzie getLocale, a dokładniej przy parsowaniu wartości quality factor. Oznacza to, że w przypadku gdy na serwerze zostanie wykonana metoda request.getLocale w odpowiedzi na żądanie HTTP o nagłówku Accept-Language i jego dowolną wartością, ale z q=2-2250738585072012e-308, obsługujący wątek "trafi" w busy-loop.
Dawid w swojej prezentacji skupił się głównie na load-time weaving - w odróżnieniu od compile/binary time weaving takie podejście nie wymaga ingerencji w proces budowania kodu. Ma to znaczną zaletę, którą wykorzystałem podczas pracy u klienta, kiedy okazało się, że aplikacja działa zdecydowanie wolniej od tego co od niej oczekiwano. Zamiast próbować podłączać ją pod profiler czy też optymalizować na ślepo, chciałem zmierzyć czas obsługi żadania z podziałem na warstwy/komponenty. Dodanie takiej logiki w kodzie byłoby nie tylko bardzo upierdliwe, ale także wymagałoby przebudowania i skompilowania aplikacji (klient wersjonował otrzymywane binaria). Zamiast tego napisałem aspekt, zapakowałem go w jara i jedyne co pozostało to zmuszenie administratora aby zmodyfikował parametry uruchamiania JVM (dodajemy javagent oraz jar z aspektem do classpath). Nie potrzeba było w takim przypadku w żaden sposób przetwarzać dostarczonej poprzednio aplikacji.
Modyfikacja parametrów startowych JVM często trafia na opór ze strony administratorów, ale w tym przypadku szczęśliwie się udało.
Z punktu widzenie developmentu/deploymentu LTW jest strasznie wygodne - wystarczy stworzyć nowy AspectJ project, napisać w nim aspekty, dodać ten projekt do classpath (w IDE bajecznie łatwe) projektu, dla którego chcielibyśmy aby zadziałały aspekty, zmodyfikować parametry startowe projektu (javaagent ) i uruchomić....
W SpringFramework AspectJ jest w pełni wykorzystywany do implementacji @Configurable oraz do zarządzania transakcjami w trybie aspect. Uruchamianie testów za pomocą Spring TestContextFramework, które tworzą context korzystający z Load Time Weaving dość znacząco zwiększa czas uruchomienia testów. Wynika to (zgodnie z tym co powiedział Dawid) z tego że sam weaver jest napisany w javie (co implikuje problemy z opakowywaniem klas języka/biblioteki standardowej). Dodatkowo w kontekście wydajności:
- unikanie dynamic pointcut (typu cflow, cflowbelow)
- unikanie generycznych typów parametrów/wartości zwracanych
- tworzenie pointcut minimalizujących ilość jointpointów
- sprawdzanie weaving logs
Na końcu Dawid pokazał 2 dodatkowe zastosowania użycia AspectJ
- mierzenie wydajności/śledzenie ścieżek wywołania
- szukanie błędów związanych z concurrency
Brak komentarzy:
Prześlij komentarz