Pokazywanie postów oznaczonych etykietą spring. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą spring. Pokaż wszystkie posty

2011-05-17

Jeszcze raz Groovy i Spring

Jakiś czas temu miałem problem z dynamiczną rekompilacją komponentów Spring napisanych w Groovy. Wtedy przyczyną był błąd w samym Groovy, szczegóły opisałem tutaj. Po aktualizacji Groovy wszystko było ok, ale niedawno stwierdziłem że mechanizm dynamicznego przeładowywania znowu przestał działać. Ponieważ w międzyczasie wykonałem spory refactoring aplikacji, to nie bardzo wiedziałem gdzie zacząć szukać sprawcy. Uznałem, że tym razem to nie Groovy - skoro raz zrobili błąd i go poprawili, to szansa na to że znowu się pojawił jest niewielka. Po włączeniu logów na poziomie DEBUG stwierdziłem, że Spring też jest niewinny - gdy plik źródłowy Groovy był zmieniany, w logach pojawiał się spodziewany komunikat: Target refreshed successfully ... czyli skrypt był ładowany od nowa. Pomimo tego w aplikacji dalej używana była pierwotna wersja. Nie będę zanudzał Czytelnika szczegółowym opisem trudności i niebezpieczeństw, którym musiałem stawić czoła aby odkryć istotę problemu i przejdę od razu do clou tego posta. Tym razem bruździł Tomcat, a dokładnie jego ClassLoader.
Jeżeli skrypt zostanie umieszczony w aplikacji webowej w katalogu WEB-INF/classes i będziemy się do niego odwoływać w Springu tak:
<lang:defaults refresh-check-delay="1000"/>
<lang:groovy id="myBean" source="classpath:Bean.groovy"/>
... to po pierwszym wczytaniu Tomcat umieści plik Bean.groovy w cache i do restartu go nie ruszy. Co ciekawe Spring potrafi zauważyć zmianę daty ostatniej modyfikacji pliku i próbuje go ponownie wczytać, ale ClassLoader cały czas zwraca pierwotnie wczytaną wersję. Wystarczy przenieść plik do WEB-INF i zmienić wpis w konfiguracji na:
<lang:defaults refresh-check-delay="1000"/>
<lang:groovy id="myBean" source="/WEB-INF/Bean.groovy"/>
... by wszystko wróciło do normy. Spring wczytuje wtedy plik za pośrednictwem ServletContextu a nie ClassLoadera i dostaje zaktualizowaną wersję. Howgh.
Share:

2010-08-26

Groovy 1.7.0 i Spring

W jednej z aplikacji które popełniłem, używam skryptów Groovy jak kontrolerów Spring MVC. Jest to wygodne w sytuacji, kiedy kod kontrolera często się zmienia a nie ma możliwości restartu całej aplikacji. Dzięki poniższej definicji:

komponent some.Controller będzie automatycznie przeładowywany (czyli skrypt będzie ponownie kompilowany) jeżeli plik źródłowy Script.groovy się zmieni (ale nie częściej niż podany czas - tutaj 1000 ms). Wszystko działało tak jak powinno do momentu upgrade'u Springa do wersji 3.0.0.RELEASE - wtedy aplikacja przestała zauważać zmiany w skryptach. Uznałem, że chwilowo mogę bez tego żyć a w wersji 3.0.1 pewnie poprawią. Kiedy jednak po kolejnych uaktualnieniach doszedłem do wersji 3.0.3 i dalej nie było ani trochę lepiej, zacząłem intensywniej szukać. Okazało się, że Spring jest niewinny a problemy sprawia sam Groovy, którego wersję podniosłem do 1.7.0 na fali ogólnego uaktualniania aplikacji (w zależnościach Springa jest 1.6.3). Aby dynamiczne przeładowywanie zaczęło znowu działać trzeba albo cofnąć się do wersji 1.6.x, albo użyć najnowszego Groovy (w tej chwili 1.7.4). Dla zainteresowanych zgłoszenie w JIR-ze GROOVY-3981.
Share:

2008-07-04

2008-06-06

2008-03-19

Komponent Spring jako ServletFilter

Nic odkrywczego, ale filtrów nie ma w przykładach zawartych w dystrybucji Spring i nie ma o nich słowa w Reference Documentation. Pamiętam że jakiś czas temu popełniłem takowy, ale parę dni temu chcąc znowu użyć tej samej konstrukcji nie pamiętałem szczegółów. Starzeję się. Dlatego, celem utrwalenia zdobytej (ponownie) wiedzy, powstał ten wpis.

Aby możliwe było użycie komponentu Spring jako filtru musi on implementować interfejs javax.servlet.Filter i to wszystko. Potem dopisujemy go w konfiguracji XMLowej lub adnotujemy jak każdy inny bean. Cała sztuczka jest w pliku web.xml aplikacji:

<filter>
<filter-name>nazwa</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

Klasa DelegatingFilterProxy będzie delegować wywołania metody doFilter(...) do beana o takiej samej nazwie jak nazwa filtra. Jeżeli chcemy, aby filtr miał inną nazwę, to trzeba dodać parametr targetBeanName:

<filter>
<filter-name>nazwaFiltra</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>nazwaBeana</param-value>
</init-param>
</filter>

Należy pamiętać, że pomimo tego iż bean implementuje interfejs Filter, to jego metody init(...) i destroy(...) domyślnie nie są wywoływane. Aby to zmienić musimy ustawić w konfiguracji kontekstu Spring atrybut targetFilterLifecycle na true:

<bean id="nazwaBeana"
class="klasaBeana"
p:targetFilterLifecycle="true">
...
</bean>
Share:

2008-03-09

CXF nie działa ze Spring 2.5.2

A konkretnie CXF 2.0.4-incubator nie działa ze Spring Framework 2.5.2 (w wersjach wcześniejszych jest w porządku). Powodem jest zmiana sposobu konfiguracji kontekstu Spring. Dotychczas w konstruktorze
ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
parametr configLocations mógł mieć wartość null - był w takiej sytuacji zastępowany pustą tablicą. W wersji 2.5.2 nie jest już zastępowany, a w metodzie setConfigLocations (wykorzystywanej przez powyższy konstruktor) następuje próba odczytania rozmiaru tablicy i rzucany jest wyjątek NullPointerException. Błąd został zgłoszony kilka dni temu (numer SPR-4531) i jak na razie nie ma reakcji ze strony autorów - pozostaje cofnięcie się do wersji 2.5.1.

[aktualizacja 2008.03.10]


Nieoceniony Juergen Hoeller zainteresował się tym bugiem - w dzisiejszym night buildzie wersji 2.5.3 ma być poprawiony. Rewelacja po raz drugi (pierwsza była tutaj).

[aktualizacja 2008.03.12]


Nie miałem czasu aby sprawdzić od razu, dopiero dzisiaj ściągnąłem najnowszy build. Oczywiście błąd jest poprawiony.

Share:

2008-02-18

Spring, CXF i Eclipse

Popełniłem pierwszy w życiu web service. Jak można przeczytać w tytule są to komponenty Springowe udostępniane jako usługi sieciowe za pomocą CXF. Projekt stworzony w Eclipse kompiluje się i działa bez problemu, ale Eclipse'owemu edytorowi XMLa nie podoba się definicja kontekstu Spring:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd"%gt;

<jaxws:endpoint id="webServiceEndpoint"
address="/test"
implementorClass="moja.klasa.WebService">

... implementacja usługi ...
</jaxws:endpoint>

</beans>
W zaznaczonym miejscu sygnalizowany jest błąd:
The matching wildcard is strict, but no declaration can be found for element 'jaxws:endpoint'.
Skoro Eclipse nie może znaleźć deklaracji, to trzeba mu powiedzieć gdzie jej szukać. Z menu wybieramy Window / Preferences, potem Web and XML / XML Catalog, zaznaczmy User Specified Entries, klikamy przycisk Add... i wpisujemy następujące wartości:

Location: jar:file:/lokalna/ścieżka/modules/cxf-rt-frontend-jaxws-<wersja>.jar!/schemas/jaxws.xsd
Key Type: Schema Location
Key: http://cxf.apache.org/schemas/jaxws.xsd

Wybranie wartości Schema Location w polu Key Type jest możliwe dopiero po wpisaniu poprawnej wartości w pole Location.

Po wykonaniu tej operacji dotychczasowy błąd zniknie, ale pojawi się nowy:
Referenced file contains errors (jar:file:/ścieżka/do/bibliotek/cxf-<wersja>.jar).
Problemem jest teraz brak definicji, która jest importowana przez jaxws.xsd. W taki sam sposób jak poprzednio dodajemy drugi wpis:

Location: jar:file:/lokalna/ścieżka/modules/cxf-common-utilities-<wersja>.jar!/schemas/configuration/cxf-beans.xsd
Key Type: Schema Location
Key: http://cxf.apache.org/schemas/configuration/cxf-beans.xsd

i tym razem jest to już wystarczające.

Opisany sposób jest oczywiście uniwersalny - można tak dodać w Eclipse dowolne brakujące definicje.
Share:

2008-02-10

Migracja z EJB 2.1 Entity Beans do Hibernate

Niedawno stanąłem przed koniecznością przepisania warstwy dostępu do danych z Entity Beans na Hibernate. Baza danych nie mogła być zmieniana, więc nowe klasy mapowane przez Hibernate musiały mieć dokładnie te same atrybuty i powiązania co dotychczasowe Entity Beans oraz musiały radzić sobie z typami danych w istniejących tabelach. Wszystko szło gładko do momentu kiedy trafiłem na atrybut mapowany na kolumnę typu BLOB (poniżej notacja XDocletowa):

/**
* @ejb.persistence column-name="NAZWA" jdbc-type="BLOB"
*/
public abstract String getNazwa();

Atrybut jest typu String, więc spodziewałem się że w bazie będą zapisane po prostu ciągi bajtów uzyskane metodą getBytes(). Jednak po sprawdzeniu istniejących wartości okazało się, że są to zserializowane obiekty i to nie klasy String, ale org.jboss.invocation.MarshalledValue (aplikacja działa na JBossie). Konieczne było więc napisanie odpowiedniej implementacji Hibernate'owego interfejsu UserType. Pomocny okazał się - jakże by inaczej - Spring, dostarczając bazowej klasy AbstractLobType:

public class MarshalledStringType extends AbstractLobType {
private static final int[] TYPES = { Types.BLOB };

protected Object nullSafeGetInternal(
ResultSet rs, String[] names,
Object owner, LobHandler lobHandler)
throws SQLException, IOException, HibernateException {

if (names.length != 1) {
throw new HibernateException("invalid column names");
}
Blob blob = rs.getBlob(names[0]);
try {
ObjectInputStream ois = new ObjectInputStream(blob.getBinaryStream());
MarshalledValue mv = (MarshalledValue) ois.readObject();
return mv.get();
} catch (IOException e) {
throw new HibernateException(e);
} catch (ClassNotFoundException e) {
throw new HibernateException(e);
}
}

protected void nullSafeSetInternal(
PreparedStatement ps, int index, Object value, LobCreator lobCreator)
throws SQLException, IOException, HibernateException {

MarshalledValue mv = new MarshalledValue(value);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(mv);
oos.close();
baos.close();
lobCreator.setBlobAsBytes(ps, index, baos.toByteArray());
}

public Class returnedClass() {
return String.class;
}

public int[] sqlTypes() {
return TYPES;
}
}

Aby nowy typ był widoczny dla Hibernate wystarczy jedna adnotacja, najlepiej w pliku package-info.java w pakiecie nadrzędnym dla klas modelu danych:

@org.hibernate.annotations.TypeDefs({
@org.hibernate.annotations.TypeDef(
name = "marshalledString",
typeClass = MarshalledStringType.class
)
})
package pakiet.nadrzedny.modelu.danych;

Od tego momentu marshalledString może być używany tak samo jak wszystkie standardowe typy dostępne w Hibernate:

@Basic
@Type(type = "marshalledString")
@Column(name = "NAZWA")
public String getNazwa() {
return nazwa;
}
Share:

2008-02-08

Problem z adnotacją @Autowire w Springu 2.5.1

Bug ujawnia się dość specyficznej sytuacji: jeżeli używamy automatycznego rozwiązywania zależności (autowiring) opartego na adnotacjach, mamy komponent nie będący singletonem i mający przynajmniej jeden atrybut będący kolekcją. W poniższym przykładzie:
public class TestBean {
   private List<TestDep> deps;

   @Autowired
   public void setTestDeps(List<TestDep> deps) {
       this.deps = deps;
   }
}


public class TestDep {
}


<beans>
   <context:annotation-config/>

   <bean id="testBean" class="test.TestBean" scope="prototype"/>

   <bean id="testDep" class="test.TestDep"/>
</beans>


public class Test {
   public static void main(String[] args) {
       ClassPathXmlApplicationContext ctx = ...
       ctx.getBean("testBean");
       ctx.getBean("testBean");
   }
}
pierwsze wywołanie ctx.getBean("testBean") zadziała poprawnie, ale drugie (i każde następne) zgłosi wyjątek IllegalArgumentException: argument type mismatch. Problemy powoduje kod odpowiedzialny za cache'owanie referencji do komponentów w klasie wewnętrznej AutowiredAnnotationBeanPostProcessor.AutowiredMethodElement. Wpisałem zgłoszenie do JIRY (numer SPR-4438) i po 39 minutach (!) błąd został poprawiony, a poprawka włączona do wersji 2.5.2. Rewelacja. [aktualizacja 2008.03.04] Wersja 2.5.2 została wydana wczoraj i AutowiredAnnotationBeanPostProcessor działa w niej poprawnie.
Share: