getBytes(); } } Przekazując obiekt CacheResponseWrapper do następnego filtra w łańcuchu, możemy umieścić dane w tablicy bajtowej, która następnie zostanie zbuforowana w pamięci lub na dysku. Działanie samego filtra buforującego jest dość proste. Po odebraniu żądania najpierw sprawdza on, czy strona może być buforowana. Jeśli tak, to filtr kontroluje, czy strona ta znajduje się już w buforze i zwraca wersję z bufora. W przeciwnym razie tworzy nową stronę i umieszcza ją w buforze. Kod filtra buforującego przedstawiony został na listingu 5.4. Listing 5.4. Klasa CacheFilter public class CacheFilter implements Filter { private FilterConfig filterConfig; // bufor danych private HashMap cache; public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; // klucz tworzony jest następująco: URI + łańcuch zapytania String key = req.getRequestURI() + "?" + req.getQueryString(); // buforuje jedynie te żądania GET, które zawierają dane // nadające się do buforowania if (req.getMethod().equalsIgnoreCase("get") && isCacheable(key)) { // próbuje pobrać dane z bufora byte[] data = (byte[]) cache.get(key); // jeśli danych nie ma w buforze, to generuje wynik w zwykły sposób // i dodaje go do bufora if (data == null) { CacheResponseWrapper crw = new CacheResponseWrapper(res); chain.doFilter(request, crw); data = crw.getBytes(); cache.put(key, data); } // jeśli dane zostały odnalezione lub dodane do bufora, // to używa ich do wygenerowania wyniku if (data != null) { res.setContentType("text/html"); res.setContentLength(data.length); 124 Rozdział 5. Skalowalność warstwy prezentacji try { OutputStream os = res.getOutputStream(); os.write(data); os.flush(); os.close(); } catch(Exception ex) { ... } } } // generuje dane w zwykły sposób // w pozostałych przypadkach chain.doFilter(request, response); } // sprawdza, czy dane nadają się do buforowania private boolean isCacheable(String key) { ... } // inicjuje bufor public void init(FilterConfig filterConfig) { this.filterConfig = filterConfig; cache = new HashMap(); } } Zauważmy, że nasz bufor nie jest zmienną statyczną. Zgodnie ze specyfikacją filtrów dla każdego elementu filter umieszczonego w deskryptorze instalacji zostaje utworzona tylko jedna instancja filtra. W każdym z filtrów możemy więc używać osobnego bufora, umożliwiając tym samym istnienie wielu buforów w różnych punktach łańcucha filtrów. Nasz prosty filtr pomija dwa trudne aspekty buforowania. Pierwszy polega na ustaleniu, czy strona może być buforowana. W większości środowisk występują strony nadające się do buforowania, jak i strony, które nie mogą być buforowane. W naszym przykładzie sprzedaży samochodów do buforowania nadają się strony opisujące różne konfiguracje aut, ale z pewnością nie może być buforowana strona zawierająca informacje o karcie kre-dytowej klienta. Typowe rozwiązanie tego problemu polega na stworzeniu odwzorowania w języku XML opisującego strony, które mogą być buforowane (podobnego do przed-stawionych wcześniej odwzorowań serwletów bądź filtrów). Druga trudność polega na zapewnieniu aktualności bufora. W naszym przykładzie założyli- śmy, że generowane dynamicznie strony nie zmieniają swej zawartości. Jeśli cena pewnego elementu wyposażenia ulegnie zmianie, to klientowi nadal prezentowana będzie strona z bufora zawierająca poprzednią, nieaktualną już cenę. Istnieje wiele strategii zachowania aktualności stron w buforze zależnych od natury generowanych stron. Najprostsze rozwiązanie polega oczywiście na przeterminowaniu zawartości bufora co pewien określony okres czasu. W przeciwnym razie nie tylko mogą się pojawić w buforze nieaktualne dane, ale jego zawartość może wzrastać w nieograniczony sposób, prowadząc do sytuacji opi-sanej w rozdziale 12. podczas omawiania antywzorca wycieku z kolekcji. Pule zasobów 125 Pule zasobów Przez pulę zasobów rozumiemy kolekcję utworzonych wcześniej obiektów, które mogą być wielokrotnie wynajmowane z puli, co pozwala uniknąć kosztów tworzenia tych obiektów za każdym razem od nowa. W środowisku J2EE pule zasobów są wszechobecne. Na przykład, gdy pojawia się nowe połączenie, to do obsługi żądania pobierany jest wątek z puli wątków. Jeśli przetwarzanie wymaga komponentu EJB, to może on zostać przydzielony z puli komponentów EJB, a jeśli komponent taki wymaga dostępu do bazy danych, to uzyska połączenie z bazą — niespodzianka! — z puli połączeń.
|