Category Archives: spring

Die Response Falle – HTTP Response Status in Spring MVC Controllern

Heutzutage ist doch alles REST, auch Sachen die gar nicht Restful sind.. aber das ist eine andere Diskussion.

Rest-Architekturen basieren auf dem HTTP Protokoll. Ein besonders wichtiger Aspekt des Protokolls sind HTTP Status Codes. Über Status-Codes teilt der Server einem Client mit, ob eine Operation erfolgreich war (Status Code 200), ob ein Fehler aufgetreten ist  (u.a. Status Code 500) oder ob die Parameter nicht den erwarteten entsprechen (Code 412).

Es ist also sehr wichtig, dem Aufrufer den richtigen Status-Code zurückzuliefern, damit dieser ggf. darauf reagieren kann.

Das Beispiel aus dem letzten Artikel führen wir einfach ein wenig weiter.

Wir haben einen sehr einfachen MVC Controller definiert.

@Controller
@EnableAutoConfiguration
public class SimpleSpringController {

    public SimpleSpringController() {
        System.out.println("Log");
    }

    @RequestMapping(
        method = RequestMethod.GET,
        value = "/hello/{helloValue}",
        produces = MediaType.APPLICATION_XML_VALUE
    )
    @ResponseBody
    public Hello sayHello(@PathVariable(value = "helloValue") String hello){
          //assume some persistence is going on here
          return new Hello(hello);
    }

    public static void main( String[] args )
    {
        SpringApplication.run(SimpleSpringController.class, args);
    }
}

Die Methode sayHello liefert ein Hello-Objekt zurück und lässt sich über das URL Pattern “/hello/{helloValue}” ansprechen.

Per Default liefert ein Controller den Status-Code 200 zurück, was als “Alles OK” interpretiert werden kann. Nehmen wir an, dass wir beim Aufruf nicht nur ein neues Objekt zurückgeben, sondern dieses auch persistieren möchten.

Man könnte die Methode dann so modellieren.

@RequestMapping(
        method = RequestMethod.PUT,
        value = "/hello/{helloValue}",
        produces = MediaType.APPLICATION_XML_VALUE
    )
    @ResponseBody
    public Hello sayHello(@PathVariable(value = "helloValue") String hello){
          //assume some persistence is going on here
          return new Hello(hello);
    }

Wir ändern die Methode auf PUT und möchten zusätzlich den Status Code 201 zurückliefern. Überfliegt man die Dokumentation, so könnte man annehmen, dass folgender Ansatz vielversprechend wäre.

@RequestMapping(
        method = RequestMethod.GET,
        value = "/hello/{helloValue}",
        produces = MediaType.APPLICATION_XML_VALUE
    )
    @ResponseBody
    @ResponseStatus(value = HttpStatus.CREATED)
    public Hello sayHello(@PathVariable(value = "helloValue") String hello){
        //do some fancy persistence stuff
        return new Hello(hello);
    }

Methoden lassen sich mit @ResponseStatus annotieren und liefern den richtigen Status-Code zurück.

Das ist aber eigentlich falsch, denn @ResponseStatus ist für ExceptionHandling gedacht. Mit dieser Annotation lassen sich sowohl Exceptions annotieren als auch ExceptionHandler. Warum aber nicht Controller? Der obige Fall würde funktionieren und die Methode liefert den Status-Code 201 zurück.

Das lässt sich auch sehr einfach mit einem kleinen Testcase beweisen.

@Test
public void testControllerResponseStatus() throws Exception
{
	MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new SimpleSpring                         Controller()).build();
	mockMvc.perform(get("/hello/test").accept(
                 MediaType.APPLICATION_XML)).
            andExpect(status().isCreated());
}

Wo liegt also das Problem?

Die Annotation hat einen zweiten Parameter reason.

    @ResponseStatus(value = HttpStatus.CREATED, reason = "some reason")

Das große Problem ist, wird reason gesetzt ruft Spring automatisch die HttpServletResponse.sendError Methode auf. In diesem Fall liefert die Response einen leeren Body, egal was der eigentliche Return-Value ist.

Extrem gefährlich und tritt nur zur Laufzeit auf.

Wir sehen es noch nicht mal in unserem obigen Testcase, denn der Call ist nach wie vor erfolgreich und liefert den Status Code 201 Created zurück. Erweitern wir den Testfall um ein Unmarshalling via JAXB.

 @Test
	public void testControllerResponseStatus() throws Exception
	{
        MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new SimpleSpringController()).build();
        mockMvc.perform(get("/hello/test").accept(MediaType.APPLICATION_XML)).andExpect(
            new ResultMatcher()
            {
                @Override
                public void match(MvcResult result) throws Exception
                {
                    JAXBContext context = JAXBContext.newInstance(Hello.class);
                    Hello hello = (Hello)context.createUnmarshaller().unmarshal(
                        new ByteArrayInputStream(result.getResponse()
                            .getContentAsByteArray()));
                    assertEquals("test", hello.getHello());
                }
            });
	}

Jetzt endlich schlägt der Test fehl mit folgender Fehlermeldung.

javax.xml.bind.UnmarshalException
 - with linked exception:
[org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Vorzeitiges Dateiende.]

Der ResponseBody ist leer, was er nicht sein sollte und deswegen beschwert sich JAXB zurecht.

Da ich selbst über diesen Fehler gestolpert bin dient dieser Blogeintrag als Reminder (hauptsächlich für mich).

Links

Spring MVC Testing Tutorial von Petri Kainulainen

Pre-Spring 3.2.x MVC Testing

Spring Boot, Rest, MVC – wenns mal schneller gehen muss

Hallo zusammen,

seit dem letzten Blogeintrag hat sich einiges im Spring Ökosystem getan. Beispielsweise wurde das Spring-IO Projekt gegründet, was quasi eine Art Dachprojekt ist und die verschiedenen Spring-Projekte zusammenbringt.

Mir persönlich gefällt der Ansatz von Spring Boot sehr gut mit Ihrem Leitspruch “Simplifying Spring for everyone“. Nicht unbedingt für Enterprise Projekte, sondern für Rapid Prototyping – schnell mal was zusammenklimpern ohne das Ganze drumherum (was bei Spring zwar besser geworden ist aber immer noch viel ist).

Spring Boot – wenns schnell gehen muss.

Testen wir das Ganze doch einmal und machen einen Jumpstart.

Aktuell gibt es noch keinen Archetype für Spring Boot Projekte. Das Ganze ist wohl doch noch zu frisch.

Setup

Eine taufrische pom für ein Mavenprojekt findet sich hier.

Wir brauchen zunächst das neue spring.io Repository.

<repository>
            <id>spring-snapshots</id>
            <url>http://repo.spring.io/libs-snapshot</url>
            <snapshots><enabled>true</enabled></snapshots>
</repository>

Und eine Abhängigkeit auf das boot-starter-web Modul.

<dependency>  
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>0.5.0.M6</version>
</dependency>

Einen schnellen Überblick über die verfügbaren Versionen kann man sich übrigens spontan hier verschaffen.

Testlauf

Wir implementieren ein einfaches Modell.

@XmlRootElement(name = "hello")
public class Hello {

    @XmlElement(name = "howToSayHello")
    private String hello;

    public Hello() {
    }

    public Hello(String hello) {
        this.hello = hello;
    }

    public String getHello() {
        return hello;
    }
}

Ausserdem einen einfachen Controller.

@Controller
@EnableAutoConfiguration
public class SimpleSpringController {

    public SimpleSpringController() {
        System.out.println("Log");
    }

    @RequestMapping(
        method = RequestMethod.GET,
        value = "/hello/{helloValue}",
        produces = MediaType.APPLICATION_XML_VALUE
    )
    @ResponseBody
    public Hello sayHello(@PathVariable(value = "helloValue") String hello){
        return new Hello(hello);
    }

    public static void main( String[] args )
    {
        SpringApplication.run(SimpleSpringController.class, args);
    }
}

Im Großen und Ganzen haben wir es hier mit einem Standard-Spring MVC Controller zu tun.

Einige Besonderheiten haben wir aber doch.

Eine Spring-Boot Applikation lässt sich sehr einfach starten.

 public static void main( String[] args )
    {
        SpringApplication.run(SimpleSpringController.class, args);
    }

ACHTUNG – SpringApplication.run() erwartet als ersten Paramter ein Object. Man neigt dazu, eine Instanz des Controllers zu übergeben.

Der Code in BeanDefinitionLoader sagt aber etwas anderes.

                if (source instanceof Class<?>) {
			return load((Class<?>) source);
		}
		if (source instanceof Resource) {
			return load((Resource) source);
		}
		if (source instanceof Package) {
			return load((Package) source);
		}
		if (source instanceof CharSequence) {
			return load((CharSequence) source);
		}
		throw new IllegalArgumentException("Invalid source type " + source.getClass());

Was man dann von Spring Boot bekommt ist die Fehlermeldung : Invalid source type SimpleSpringController. Damit kann man leider nicht viel anfangen.

Zurück zum Controller.

@EnableAutoConfiguration

Diese Annotation ist neu und kommt mit Spring-Boot. Was wird alles “Auto-Konfiguriert”?

Antwort – sehr viel. Das ist genau der Grund, wieso ich Spring Boot niemals für Projekte einsetzen würde (im jetztigen Stand). Es lassen sich Einschränkungen vornehmen, aber Spring-Boot ist einfach ideal für Prototyping Projekte geeignet. Was an Code notwendig ist für
die AutoConfiguration kann man sich hier ganz schön anschauen.

Testing

Die Anwendung lässt sich sehr einfach starten, lässt sie sich aber auch testen?

Mit Spring 3.2.x haben wir ein sehr schönes Testing-Framework für Spring-MVC Controller bekommen. Lässt sich das mit Spring Boot vereinbaren?

Hierfür brauchen wir zumindest die Spring Test Abhängigkeit.

 <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.0.0.RC1</version>
            <scope>test</scope>
 </dependency>

Spring Boot kommt per Default bereits mit Spring 4.0, deswegen müssen wir die Testabhängigkeit auch mit 4.0.0 deklarieren (führt zu Runtime-Exceptions, wenn nicht:().

Ein einfacher Test könnte so aussehen.

@Test
	public void testController() throws Exception
	{
		MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new SimpleSpringController()).build();
		mockMvc.perform(get("/hello/test").accept(MediaType.APPLICATION_XML)).andExpect(
				new ResultMatcher()
				{
					@Override
					public void match(MvcResult result) throws Exception
					{
						JAXBContext context = JAXBContext.newInstance(Hello.class);
						Hello hello = (Hello)context.createUnmarshaller().unmarshal(
								new ByteArrayInputStream(result.getResponse()
										.getContentAsByteArray()));
						assertEquals("test", hello.getHello());
					}
				});
	}

Auch das funktioniert sehr einfach.

Wir setzen einen Rest-Call ab gegen den Controller und der richtigen URL.Testweise deserialisieren wir das XML was wir zurückbekommen mittels JAXB.

Eine Abhängigkeit auf Spring 4?

Spring 4 ist zwar nett und immerhin bereits der RC1, aber eigentlich noch ein wenig experimentell. Muss ich, wenn ich mit Spring Boot arbeite mit Spring 4 arbeiten?

Antwort – erstmal ja – ich kenne zumindest keinen einfachen Weg auf eine Stable-Version zurückzugehen.

Fazit

Spring Boot ist einfach und schnell. Ich brauche keine speziellen Dependencies, es funktioniert alles direkt. Um auf die Schnelle Dinge auszuprobieren ideal. Ich werde Spring Boot definitiv zukünftig für kleine Prototyping Projekte einsetzen.

Source-Code

Wicket 6 und CDI – es kann so schön sein

Wicket 6.7.0 ist released, und das ist wie immer ein guter Zeitpunkt um sich einige Features  anzuschauen.

Heute geht es um Wicket in Verbindung mit CDI. Ein äusserst spannendes und praktisches Thema das bisher in diesem Blog sträflich vernachlässigt wurde. Am besten, wir fangen direkt an.

Ein neues Projekt – Wicket Archetype

Zunächst erzeugen wir uns ein neues Projekt mit Hilfe des Wicket-Maven-Archetypes.

[code lang=”bash” inline=”yes”]
mvn archetype:generate -DarchetypeGroupId=org.apache.wicket -DarchetypeArtifactId=wicket-archetype-quickstart -DarchetypeVersion=6.7.0 -DgroupId=de.effectivetrainings -DartifactId=wicket-cdi -DarchetypeRepository=https://repository.apache.org/ -DinteractiveMode=false
[/code]

Dieses Projekt lässt sich sehr einfach starten.

Um mit CDI arbeiten zu können deklarieren wir zusätzlich eine Depdendency in der pom auf das Wicket-CDI Modul, das mittlerweile nativ mit Wicket mitkommt.

[code lang=”xml” inline=”yes”]
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-cdi</artifactId>
<version>${wicket.version}</version>
</dependency>[/code]

Zusätzlich brauchen wir unbedingt eine Implementierung der CDI-Spezifikation. Natürlich verwenden wir hierfür Weld – die Referenzimplementierung.

Hierfür deklarieren wir zusätzlich folgende Abhängigkeiten in unserer Projekt-Pom.

[code lang=”xml”]

   &lt;dependency&gt;
            &lt;groupId&gt;org.jboss.weld&lt;/groupId&gt;
            &lt;artifactId&gt;weld-core&lt;/artifactId&gt;
            &lt;version&gt;1.1.11.Final&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;!-- needed for conversation scope propagation --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.jboss.seam.conversation&lt;/groupId&gt;
            &lt;artifactId&gt;seam-conversation-weld&lt;/artifactId&gt;
            &lt;version&gt;3.0.0.CR2&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.jboss.weld.servlet&lt;/groupId&gt;
            &lt;artifactId&gt;weld-servlet-core&lt;/artifactId&gt;
            &lt;version&gt;1.1.11.Final&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;javax.el&lt;/groupId&gt;
            &lt;artifactId&gt;javax.el-api&lt;/artifactId&gt;
            &lt;version&gt;2.2.1&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;javax.servlet.jsp&lt;/groupId&gt;
            &lt;artifactId&gt;javax.servlet.jsp-api&lt;/artifactId&gt;
            &lt;version&gt;2.2.1&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
            &lt;artifactId&gt;slf4j-ext&lt;/artifactId&gt;
            &lt;version&gt;1.6.2&lt;/version&gt;
        &lt;/dependency&gt;

[/code]

Zuletzt müssen wir noch das Jboss-Repository (oder müsste das mittlerweile Wildfly-Repository:) heissen?) integrieren, da die Weld-Ressourcen aus einem mir nicht erfindlichen Grund nicht im öffentlichen Maven-Repository hinterlegt sind.

[code lang=”xml”]
<repository>
<id>jboss</id>
<url>https://repository.jboss.org/nexus/content/groups/public/</url>
</repository>
[/code]

Damit Weld / CDI mit unseren Container (Jetty) startet deklarieren wir noch einen ServletContextListener in der web.xml

[code lang=”xml”]
<listener>
<listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
</listener>
[/code]

<em>Hinweis - das initiale Projekt-Setup mit allen Abhängigkeiten findet man im Commit 62479a3888ca93dc6d9095bafb4858df014ad8d1</em>

Immer auf den Scope fokussieren – CDI Scopes in Wicket

Beim Start der Anwendung sieht man sofort, ob CDI mit dem Container startet, wenn man folgende Meldung in der Konsole sieht.

INFO  - Version                    - WELD-000900 1.1.11 (Final)

Jetzt sind wir bereit, uns so richtig schön mit Scopes, Conversations und Dependency Injection zu beschäftigen.

Wicket mit CDI verheiraten

Das Projekt ist soweit vorbereitet und Wicket möchte gerne eine gepflegte Konversation mit CDI führen. Hierfür müssen wir noch einige kleine Schritte vornehmen. Zunächst brauchen wir den BeanManager, der uns vom zuvor konfigurierten ServletContextListener bereitgestellt wird.

Hierzu definieren wir eine neue Methode initCDI() in unserer Application-Klasse und rufen diese Methode aus der bekannten init()-Methode auf.

[code lang=”java”]

   @Override
   public void init()
   {
        super.init();
        initCDI();
   }

    private void initCDI(){
        BeanManager manager = (BeanManager)getServletContext().getAttribute(
            Listener.BEAN_MANAGER_ATTRIBUTE_NAME);
        new CdiConfiguration(manager).configure(this);
    }

[/code]

Den BeanManager können wir uns direkt aus dem ServletContext laden und übergeben diesen in eine neue Instanz der CdiConfiguration (hier sind wir bereits in der Wicket-Welt). Mit Hilfe der Methode configure(..) binden wir die CdiConfiguration an unsere Application und wir sind bereit für CDI und Wicket.

Mehr ist übrigens nicht zu tun. Die CdiConfiguration kümmert sich bereits um die Registrierung der entsprechenden RequestCycle- und ComponentInstantiationListener. Sehr schön gemacht.

<em>Hinweis - Die Konfiguration für CDI findet man im Commit 6eb7a81549a9138f586fc49b13e434457e77f033</em>

Bitte injezieren Sie..

Um CDI testen zu können bauen wir uns zunächst mal eine kleine Mini-Domäne. Wir bauen einen Miniatur-Online-Shop und brauchen hierfür einige Produkte.

[code lang=”java”]

public class Product implements Serializable{

    private String name;
    private Double prize;

    public Product(String name, Double prize) {
        this.name = name;
        this.prize = prize;
    }

    public String getName() {
        return name;
    }

    public Double getPrize() {
        return prize;
    }
}

[/code]

Und einen Provider für unsere Schuhe.

[code lang=”java”]

@ApplicationScoped
public class ProductProvider {

    public List&lt;Product&gt; productList(){

        Product samba = new Product("Adidas Samba",49.95);
        Product air = new Product("Nike Air",79.95);
        Product balerina = new Product("Balerinas",9.95);

        return Arrays.asList(samba, air, balerina);

    }
}

[/code]

Der Provider liegt im ApplicationScope, da er von allen Benutzern unserer Webseite verwendet wird.

Injezieren können wir in alles, was von Wicket gemanaged wird und die ComponentInstantiationListeneres notifiziert – also Session, Komponenten, die Application und Behaviors.

Wir löschen zunächst alles aus der Homepage und dem zugehörigen Markup, injezieren uns den Provider und zeigen alle Produkte in einem ListView an.

[code lang=”java”]

public class HomePage extends WebPage {
	private static final long serialVersionUID = 1L;

    @Inject
    ProductProvider provider;

    public HomePage(final PageParameters parameters) {
		super(parameters);
                add(new ListView&lt;Product&gt;("productsList", provider.productsList()) {
                     @Override
                     protected void populateItem(ListItem&lt;Product&gt; item) {
                          item.add(new Label("name", new PropertyModel&lt;String&gt;(item.getModel(),"name")));
                          item.add(new Label("prize", new PropertyModel&lt;Double&gt;(item.getModel(),"prize")));
                     }
                });
    }
}

[/code]

und das zugehörige Markup.

[code lang=”html”]

&lt;!DOCTYPE html&gt;
&lt;html xmlns:wicket="http://wicket.apache.org"&gt;
	&lt;body&gt;
        &lt;div wicket:id="productsList"&gt;
            &lt;span wicket:id="name"&gt;&lt;/span&gt;
            &lt;span wicket:id="prize"&gt;&lt;/span&gt;
        &lt;/div&gt;
	&lt;/body&gt;
&lt;/html&gt;

[/code]

Die beans.xml wird immer vergessen…

Beim Zugriff auf die Seite bekommen wir als allererstes eine Unsatisfied-Dependency Exception. CDI ist zwar konfiguriert, aber inaktiv, denn das scannen nach Beans und Abhängigkeiten passiert erst, wenn wir die so gehasste beans.xml im Verzeichnis src/main/resources/META-INF/beans.xml anlegen mit folgendem Inhalt.

[code lang=”xml”]

&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd"&gt;
&lt;/beans&gt;

[/code]

Endlich ist es soweit

wicket cdi und weld in aktion

Unser ProductProvider wird injeziert und funktioniert. Ideal ist das aber nicht, was wir hier machen. Denn eigentlich sollte die Liste von verfügbaren Produkten nicht direkt beim ProductProvider angefragt werden, sondern über ein entsprechendes LoadableDetachableModel. Machen wir das doch einfach mal.

Injezieren in Non-Managed Klassen

Hierfür bauen wir uns folgendes Model.

[code lang=”java”]

public class ProductsLoadableDetachableModel extends LoadableDetachableModel&lt;List&lt;Product&gt;&gt; {

    @Inject
    ProductProvider provider;

    @Override
    protected List&lt;Product&gt; load() {
        return provider.productList();
    }
}

[/code]

Funktioniert das? NEIN! Denn Models sind nicht unter Wicket-Kontrolle – hier haben wir per Definition keine Möglichkeit, uns Abhängigkeiten über CDI injezieren zu lassen… Oder?

Das Prinzip ist immer dasselbe, ob wir jetzt mit Spring, Guice oder CDI arbeiten. Für die Kombination mit Spring würde man das allseits bekannte Injector.get().inject(this) verwenden.

Das CDI-Pendant hierfür heisst  CdiContainer.get().getNonContextualManager().inject(this);

Die korrekte Model-Implementierung sieht also so aus.

[code lang=”java”]

public class ProductsLoadableDetachableModel extends LoadableDetachableModel&lt;List&lt;Product&gt;&gt; {

    @Inject
    ProductProvider provider;

    public ProductsLoadableDetachableModel() {
        CdiContainer.get().getNonContextualManager().inject(this);
    }

    @Override
    protected List&lt;Product&gt; load() {
        return provider.productList();
    }
}

[/code]

Fahren wir die Anwendung hoch, funktioniert das Ganze natürlich.

Einige CDI-Spielereien

Jetzt können wir uns noch einige Spezialitäten von CDI anschauen, wir können beispielsweise statt uns den ProductProvider direkt in das Model zu injezieren mit @Produces arbeiten.

[code lang=”java”]

@ApplicationScoped
public class ProductProvider {

    public ProductProvider(){}

    @Produces
    public List&lt;Product&gt; productList(){

        Product samba = new Product("Adidas Samba",49.95);
        Product air = new Product("Nike Air",79.95);
        Product balerina = new Product("Balerinas",9.95);

        return Arrays.asList(samba, air, balerina);

    }
}

[/code]

Wir deklarieren im ProductProvider dass dieser eine Liste von Produkten bereitstellt. Im Model sieht das Ganze dann so aus.

[code lang=”java”]

public class ProductsLoadableDetachableModel extends AbstractReadOnlyModel&lt;List&lt;Product&gt;&gt; {

    @Inject
    List&lt;Product&gt; productList;

    public ProductsLoadableDetachableModel() {
        CdiContainer.get().getNonContextualManager().inject(this);
    }

    @Override
    public List&lt;Product&gt; getObject() {
        return productList;
    }
}

[/code]

Achtung, durch das injezieren der Liste lösen wir zwar die Abhängigkeit zum ProductProvider auf, machen die Liste aber statisch. Wir würden keine Updates auf unsere Produktliste für diese Model-Instanz bekommen.

Kauf dir nen Schuh..

Zuletzt bauen wir uns noch die Möglichkeit, einen Schuh zu kaufen.

Hierfür bauen wir die Page folgendermaßen um.

[code lang=”java”]

public class HomePage extends WebPage {
	private static final long serialVersionUID = 1L;

    @Inject
    private Event&lt;Product&gt; productEvent;

	public HomePage(final PageParameters parameters) {
		super(parameters);

        ListView&lt;Product&gt; listView = new ListView&lt;Product&gt;("productsList", new ProductsLoadableDetachableModel()) {
            @Override
            protected void populateItem(ListItem&lt;Product&gt; item) {
                Form&lt;Product&gt; form = new Form&lt;Product&gt;("form", item.getModel()){
                    @Override
                    protected void onSubmit() {
                        super.onSubmit();
                        productEvent.fire(getModelObject());
                    }
                };
                item.add(form);
                form.add(new Label("name", new PropertyModel&lt;String&gt;(item.getModel(), "name")));
                form.add(new Label("prize", new PropertyModel&lt;Double&gt;(item.getModel(), "prize")));
            }
        };
        listView.setReuseItems(true);
        add(listView);
    }
}

[/code]

Und das entsprechende Markup.

[code lang=”html”]

&lt;!DOCTYPE html&gt;
&lt;html xmlns:wicket="http://wicket.apache.org"&gt;
&lt;body&gt;
&lt;div wicket:id="productsList"&gt;
    &lt;form wicket:id="form"&gt;
        &lt;span wicket:id="name"&gt;&lt;/span&gt;
        &lt;span wicket:id="prize"&gt;&lt;/span&gt;
        &lt;input type="submit" value="bestellen"&gt;
    &lt;/form&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;

[/code]

cdi-weld-order-by-event

Wir stellen also ein Formular bereit, über das unsere Schuhe bestellt werden können.

Interessant ist die neue Instanzvariable

[code lang=”java”]

 @Inject
 private Event&lt;Product&gt; productEvent;

[/code]

Wir verwenden den CDI-Eventmechanismus um Schuhe zu kaufen. Beim Klick auf den Submit-Button wird die onSubmit-Methode der Form-Klasse aufgerufen und hier feuern wir ein neues Event.

[code lang=”java”]

 super.onSubmit();
 productEvent.fire(getModelObject());

[/code]

Zuletzt bauen wir uns einen Miniatur-Product-Service, der auf dieses Event reagiert.

[code lang=”java”]

@ApplicationScoped
public class ProductService {

    public void order(@Observes Product product){
        System.out.println("Produkt " +   product.getName() + " bestellt");
    }
}

[/code]

Und bestellen wir beispielsweise den Nike-Schuh sehen wir in der Konsole wie erwartet.

Produkt Nike Air bestellt

Fazit

Die Integration von CDI und Wicket ist genauso einfach wie mit Spring oder Guice und sieht sehr vielversprechend aus. Ich hoffe, ich habe demnächst die Gelegenheit, dieses nette Duo mal in einem Projekt verwenden zu dürfen.

Links

Anleitung von Igor Vaynberg zu Wicket und CDI
Schöne Anleitung zu CDI zum Ausprobieren
API-Docs zu Wicket & CD
Sourcen auf Github

 

 

Hibernate – Collection-Size mit Lazy-Initialization

Es gibt einige klassische Probleme, die man mit JPA ständig zu lösen hat, für die aber scheinbar kaum jemand eine Sofort-Lösung parat hat.

Ein Klassiker dieser Gattung ist die Größe einer Collection zu erfragen, ohne diese vollständig zu initialisieren.

Nehmen wir das Standardbeispiel eines Blogs.

Ein Blog besteht aus Einträgen und Kommentaren. Die Entity für einen Blogeintrag kann beispielsweise so aussehen:

[code lang=”java” inline=”yes”]
@Entity
@Table(name=”POST”)
public class Post {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(name=”POST_ID”)
    Integer postId;

    @Column(name=”TITLE”)
    String title;

    @Column(name=”POST_DATE”)
    Date postDate;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name=”POST_ID”,referencedColumnName = “POST_ID”)
    private List<Comment> comments = new ArrayList<Comment>();

    public Integer getPostId() {
        return postId;
    }

    public void setPostId(Integer postId) {
        this.postId = postId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Date getPostDate() {
        return postDate;
    }

    public List<Comment> getComments(){
        return comments;
    }

    public void addComment(Comment comment){
        this.comments.add(comment);
    }

    @PrePersist
    public void setPostDate() {
        this.postDate = new Date();
    }

}
[/code]

Das Setup ist ein Standard-Hibernate/Spring/JPA Setup. Beispielsweise können wir mit Spring-Data für Posts folgendes Repository definieren, um auf die Einträge zuzugreifen.

[code lang=”java” inline=”yes”]

public interface PostRepository extends JpaRepository<Post, Integer> { }

[/code]

Um das Ganze zu testen definieren wir folgenden Test-Case

[code lang=”java” inline=”yes”]

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:META-INF/test-context.xml")
public class PostRepositoryTest {

    @Autowired
    PostRepository repository;

    @Test
    public void test() {
        Post post = new Post();
        Comment firstComment = new Comment("Kommentar Text1","Martin");
        post.addComment(firstComment);
        Comment secondComment = new Comment("Kommentar Text1","Martin");
        post.addComment(secondComment);
        post.setTitle("First Post");

        repository.save(post);

        Post dbpost = repository.findOne(post.getPostId());
        assertNotNull(dbpost);
        System.out.println(dbpost.getTitle());
    }

}

[/code]

Das generierte DDL-SQL von Hibernate sieht so aus

Hibernate: create table POST (POST_ID integer generated by default as identity, POST_DATE timestamp, TITLE varchar(255), primary key (POST_ID))
Hibernate: create table comments (id bigint generated by default as identity, author varchar(255), comment varchar(255), POST_ID integer, primary key (id))
Hibernate: alter table comments add constraint FKDC17DDF4D3BC1BD8 foreign key (POST_ID) references POST

Für die Inserts generiert Hibernate folgende Queries

Hibernate: insert into POST (POST_ID, POST_DATE, TITLE) values (null, ?, ?)
Hibernate: insert into comments (id, author, comment) values (null, ?, ?)
Hibernate: insert into comments (id, author, comment) values (null, ?, ?)
Hibernate: update comments set POST_ID=? where id=?
Hibernate: update comments set POST_ID=? where id=?

Soweit alles Standard. Jetzt wird es interessant. Wir möchten die Anzahl an Kommentaren ermitteln. Kein Problem oder?

Zunächst definieren wir eine neue Methode getCommentsCount() in der Post-Entity

 public Integer getCommentsCount(){
        return comments.size();
 }

Den Test ergänzen wir um einen einfachen Assert.

assertEquals(new Integer(2), post.getCommentsCount());

Das funktioniert nicht, aber nur deswegen, weil Hibernate die Collection nicht direkt beim Laden initialisiert (Lazy-Initialization). Das bedeutet, Kommentare werden nur geladen, wenn auch wirklich auf sie zugegriffen wird. Das Nachladen einer Lazy-Initialisierten Collection funktioniert aber leider nur im Kontext einer Transaktion und nicht mit einer Entity die bereits detached ist.
Der Test spiegelt das auch sofort wieder.

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: de.effectivetrainings.entities.Post.comments, could not initialize proxy - no Session

Welche Möglichkeiten haben wir jetzt?

Eager Initialization

Wir können Hibernate anweisen, die Collection Eager zu initialisieren, das bedeutet, immer wenn eine Post-Entity geladen wird, wird Hibernate auch gleich alle Kommentare laden.

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name="POST_ID",referencedColumnName = "POST_ID")
private List&lt;Comment&gt; comments = new ArrayList&lt;Comment&gt;();

Hibernate generiert uns folgendes SQL.

Hibernate: select post0_.POST_ID as POST1_0_0_, post0_.POST_DATE as POST2_0_0_, post0_.TITLE as TITLE0_0_ from POST post0_ where post0_.POST_ID=?
Hibernate: select comments0_.POST_ID as POST4_0_1_, comments0_.id as id1_1_, comments0_.id as id1_0_, comments0_.author as author1_0_, comments0_.comment as comment1_0_ from comments comments0_ where comments0_.POST_ID=?

Aus Performance-Gründen kann das nicht erwünscht sein, beispielsweise wenn wir nur eine Liste von Posts anzeigen möchten aber an den Kommentaren zunächst gar nicht interessiert sind. Jetzt haben wir ein Dilemma, aus dem es scheinbar keinen Ausweg gibt, oder? Dachte ich auch, bis ich auf ein spezielles Feature von Hibernate gestossen bin.

Extra-Lazy Initialization

Hibernate bietet ein sehr nettes Feature das sich Extra-Lazy Initialization nennt. Hier initialisiert Hibernate eine Collection teilweise, aber nur die Elemente, die tatsächlich gebraucht werden, was sogar einzelne Elemente in der Liste sein können.

Probieren wir es aus.

Wir ändern hierfür das Collection-Mapping für die Kommentare folgendermaßen.

 @OneToMany(cascade = CascadeType.ALL)
 @JoinColumn(name="POST_ID",referencedColumnName = "POST_ID")
 @LazyCollection(LazyCollectionOption.EXTRA)
 private List&lt;Comment&gt; comments = new ArrayList&lt;Comment&gt;();

Das allein macht unseren Test nicht grün. Zusätzlich fügen wir folgende private Methode in die Post-Klasse ein.

 @PostLoad
 private void initCommentCount(){
        comments.size();
 }

Jedesmal wenn ein Post geladen wird, fragen wir die Größe der Kommentar-Collection ab. Das SQL das jetzt von Hibernate generiert wird ist folgendes:

Hibernate: select post0_.POST_ID as POST1_1_0_, post0_.POST_DATE as POST2_1_0_, post0_.TITLE as TITLE1_0_ from POST post0_ where post0_.POST_ID=?
Hibernate: select count(id) from comments where POST_ID =?

Hibernate ist wirklich unglaublich klug und erkennt, dass nicht die Collection selbst sondern die die Größe benötigt wird. Deswegen wird auch kein Select auf alle Kommentare sondern nur ein select(count) ausgeführt, was aus Performance-Sicht optimal ist.Das lässt sich auch beweisen, in dem wir einfach mal auf ein Element in der Collection zugreifen.

 dbpost.getComments().get(0);

verursacht nach wie vor unsere

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: de.effectivetrainings.entities.Post.comments, could not initialize proxy - no Session

Fazit

Das ist ein sehr oft diskutiertes Problem, ich finde diese Lösung ziemlich elegant und beeindruckend. Ich hoffe, der/die Eine oder Andere kann damit was anfangen.

Der Code zu diesem Beispiel findet sich übrigens auf GitHub.

 

Effective Trainings Screencast – spring, cxf, rest, hsql,jpa,spring data,junit und jquery mit Ajax in einer knappen Stunde.


War dieser Blogeintrag für Sie interessant? Evtl. kann ich noch mehr für Sie tun.

Trainings & Know-How aus der Praxis zu

  • Apache Wicket 1.4.x, 1.5.x, 1.6.x
  • GIT – Best Practices, Einsatz, Methoden
  • Spring
  • Java
  • Scrum & Kanban
  • Agiles Arbeiten
Consulting & Softwareentwicklung

  • Requirements Engineering
  • Qualitätssicherung
  • Software-Entwicklung
  • Architektur
  • Scrum & Kanban

Spring Data und Queries – Elegantes Suchen und Filtern mit Spring und JPA

Da ich mich derzeit recht intensiv mit dem Thema Spring und JPA (und damit Gott sei Dank auch mit dem Thema Spring-Data) auseinandersetze, möchte ich hier für mich ein wenig dokumentieren, wie das doch nicht ganz einfach zu verstehende Konzept von Filtern und Suchen funktioniert.

Das Thema Spring-Data wird übrigens auch in meinem (im Entstehen begriffenen) Buch intensiv behandelt.

Das Projekt aufsetzen

Folgende pom deklariert alle notwendigen Dependencies inklusive Spring, Spring-Data, Hibernate und HsqlDB.

[code language=”xml”]
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org</groupId>
<artifactId>rest</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<properties>
<spring.version>3.1.0.RELEASE</spring.version>
<hibernate.version>3.5.6-Final</hibernate.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.0.3.RELEASE</version>
</dependency>

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>

<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.2.6</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
</dependency>

</dependencies>

<build>
<finalName>rest</finalName>

<plugins>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>

</plugins>

</build>

</project>

[/code]

Für die Spring-Konfiguration benötigen wir folgende Konfigurationsklasse.
Diese Klasse definiert den notwendige EntityManagerFactoryBean, den TransactionManager und den JpaVendorAdapter.
Leider brauchen wir für die Verwendung von spring-data immer noch eine Xml-Konfigurationsdatei (siehe hier)

[code language=”java”]
@Configuration
@ImportResource(value = "classpath:de/effective/spring-contextt.xml")
@ComponentScan(basePackages = "de.effective")
public class ApplicationConfig {

@Bean
public LocalContainerEntityManagerFactoryBean emf(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(dataSource);
emf.setJpaDialect(new HibernateJpaDialect());
emf.setJpaVendorAdapter(jpaVendorAdapter());
emf.setPersistenceUnitManager(null);
emf.setPackagesToScan("de.effective");
return emf;
}

@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setShowSql(true);
jpaVendorAdapter.setDatabase(Database.HSQL);
jpaVendorAdapter.setGenerateDdl(true);
return jpaVendorAdapter;
}

@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory emf){
return new JpaTransactionManager(emf);
}
}
[/code]

Folgende XML-Konfigurationsdatei definiert eine embedded-hsql-db sowie das Bootstrapping für Spring-Data.

[code language=”xml”]
<?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:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.0.xsd">
<jdbc:embedded-database id="embeddedDS" type="HSQL"/>
<jpa:repositories base-package="de.effective"/>
</beans>

[/code]

Über die folgende Zeile

[code language=”xml”]
<jpa:repositories base-package="de.effective"/>
[/code]

werden alle Spring-Data Repositories automatisch als Spring-Beans gewired.

Damit wir die Funktionalität von Spring-Data verwenden, definieren wir eine einfache Entity (als Beispiel implementieren
wir den bereits 100-fach implementieren BookStore:).

[code language=”java”]
@Entity
public class Book implements Serializable {

@Id
@Column(name="isbn")
private String isbn;
@Column(name="title")
private String title;
@Column(name="prize")
private double prize;

public Book(){}

public Book(String title, String isbn, double prize) {
this.title = title;
this.isbn = isbn;
this.prize = prize;
}

public String getTitle() {
return title;
}

public String getIsbn() {
return isbn;
}

public double getPrize() {
return prize;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

Book book = (Book) o;

if (Double.compare(book.prize, prize) != 0) return false;
if (isbn != null ? !isbn.equals(book.isbn) : book.isbn != null) return false;
if (title != null ? !title.equals(book.title) : book.title != null) return false;

return true;
}

@Override
public int hashCode() {
int result;
long temp;
result = title != null ? title.hashCode() : 0;
result = 31 * result + (isbn != null ? isbn.hashCode() : 0);
temp = prize != +0.0d ? Double.doubleToLongBits(prize) : 0L;
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}
[/code]

Und natürlich ein passenden Spring-Data-Jpa-Repository:

[code language=”java”]
public interface BookStoreRepository extends JpaRepository {
}
[/code]

Zuletzt definieren wir einen Testdall, der die Spring-Konfiguration testet.

[code language=”java”]
@ContextConfiguration(classes = {ApplicationConfig.class})
public class TestBootsTrap extends AbstractJUnit4SpringContextTests {

@Resource
private BookStoreRepository repository;

@Test
public void testBootstrap(){
assertNotNull(repository);
}
}
[/code]

Damit haben wir ein voll funktionsfähigen Beispiel, um mit Spring-Data loszulegen.

Spring Data Einführung

Das Ziel des Spring-Data Projektes ist es, die Arbeit mit JPA möglichst stark zu vereinfachen und den kompletten Boiler-Plate-Code zu vermeiden.

Für die Definition eines Repositories mit Spring-Data reicht es völlig aus, folgendes Interface zu definieren:

[code language=”java”]
public interface BookStoreRepository extends JpaRepository {
}
[/code]

In der xml-Konfiguration hatten wir folgende Zeile definiert:

[code language=”xml”]
<jpa:repositories base-package="de.effective"/>
[/code]

Alle Interfaces die eines der Spring-Data Interfaces erweitern (beispielsweise hier JpaRepository) werden automatisch
als Spring-Beans bereitgestellt und bieten u.a. bereits folgende Methoden, die automatisch funktionieren.

[code language=”java”]
repository.delete(Book);
repository.delete(String bookId);
repository.findAll();
repository.save(Book);
repository.count();
repository.exists(String bookId);
repository.findOne(String bookId)
[/code]

Damit lassen sich typische CRUD-Applikationen Out-of-the-Box umsetzen.

Testen wir das Ganze in unserem Testfall am beispiel von save / delete:

[code language=”java”]
@Test
public void saveAndDeleteBook(){
assertTrue(repository.findAll().isEmpty());
Book book = new Book("Domain Driven Design","abcdef",39.95);
repository.save(book);
assertFalse(repository.findAll().isEmpty());
assertTrue(repository.exists(book.getIsbn()));
repository.delete(book);
assertTrue(repository.findAll().isEmpty());
}
[/code]

Der Test ist direkt grün und funktioniert:).

Suchen mit Spring-Data und Queries

Oft möchte man auf spezielle Datensätze filtern.
Hierzu gibt es diverse Möglichkeiten, die einfachste (die aber meiner Ansicht nach eher unpraktikabel ist), ist die Definition von folgender Methode im Repository (wenn wir beispielsweise nach ISBN suchen möchten).

[code language=”java”]
public Book findByIsbn(String isbn)
[/code]

Es ist keine eigene Implementierung nötig, Spring Data erkennt bereits aus der Methodensignatur was gemacht werden soll.
Das Ganze testen wir in einem Testfall:

[code language=”java”]
@Test
public void findByIsbn(){
Book book = new Book("Domain Driven Design", "abcdef", 39.95);
repository.save(book);
assertNull(repository.findByIsbn("unknown_isbn"));
assertEquals(book, repository.findByIsbn(book.getIsbn()));
}
[/code]

Auch dieser Testfall ist direkt grün.

Diese Funktionalität hat übrigens zur Laufzeit kaum Auswirkung, da Spring-Data beim Aufbau des Spring-Kontextes NamedQueries aufbaut und somit zur Laufzeit alles schön schnell ist.

Es ist übrigens Vorsicht geboten, denn SPring-Data ist hier ein wenig extrik, denn es reagiert auf Camel-Case. Der folgende Fall zeigt zum Einen auf, dass Spring-Data die ganze Arbeit bereits beim Aufbau des Spring-Kontextes macht (auch das Parsen aller Methoden und Aufbau der Queries) als auch die Case-Sensitivität.

Hierzu benenne ich die Methode im Repository einfach um in findByIsBn(String isbn).

[code language=”java”]

@Test
public void findByIsbn() {
if (true)
throw new RuntimeException("Wird nicht auftreten, da schon in der setup Methode Exceptions fliegen");
Book book = new Book("Domain Driven Design", "abcdef", 39.95);
repository.save(book);
assertNull(repository.findByIsBn("unknown_isbn"));
assertEquals(book, repository.findByIsBn(book.getIsbn()));
}

[/code]

Schon in der 2. Zeile im Test würde eine Exception fliegen. Spring-Data bricht aber bereits früher ab, da bereits beim Aufbauen des Spring-Kontextes die entsprechende Exception fliegt.

[code]

Caused by: java.lang.IllegalArgumentException: No property is found for type class de.effective.Book

[/code]

Da Case-Sensitivität für mich hier etwas seltsam anmutet, bevorzuge ich die manuelle Definition von Queries (das ist natürlich rein subjektiv).

Folgendes Beispiel ist vollkommen äquivalent:

[code language=”java”]

@Query(value = "SELECT p from Book p where p.isbn=:isbn")
public Book manuallyFindByIsbn(@Param(value = "isbn") String isbn);

[/code]

Mit @Query kann man über jede beliebige Methode einen JPQL-Ausdruck setzen, mit @Param kann mit auf Named-Parameters zugreifen. Leider kann Spring-Data das nicht automatisch machen, da dies schon von der JVM nicht unterstützt wird:).

Hier ist der äquivalente Testfall:

[code language=”java”]

@Test
public void manuallyFindByIsbn() {
Book book = new Book("Domain Driven Design", "abcdef", 39.95);
repository.save(book);
assertNull(repository.manuallyFindByIsbn("unknown_isbn"));
assertEquals(book, repository.manuallyFindByIsbn(book.getIsbn()));
}

[/code]

Und wie sollte es anders sein, auch dieser Test ist sofort grün.

Paging

Bisher habe ich es nicht gebraucht, aber das Feature ist nett, deswegen will ich es hier kurz erwähnen. Pagination lässt sich extrem einfach realisieren.

[code language=”java”]

@Test
public void findInPages(){

for(int i = 20; i>0; i–){
repository.save(new Book("A Test Book",""+ i , i));
}
assertEquals(20, repository.findAll().size());

Page page = repository.findAll(new PageRequest(1, 3));
List books = page.getContent();
assertEquals(3, books.size());
}

[/code]

Der Test sollte recht einfach zu lesen sein, wir speichern 20 Bücher, wollen aber davon nur jeweils 3 laden.

Specifications

Der eigentliche Titel dieses Artikels lautat ja nicht Spring-Data (hierzu gibt es ja auch weiß Gott schon genügend Tutorials), sondern elegantes SUchen und Filtern mit Spring-Data.

Was Spring Data bietet ist das Konzept der Specifications.

[code language=”java”]
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}
[/code]

Das Konzept der Specifications finde ich ehrlich gesagt nicht besonders intuitiv (ich persönlich mag aber auch die JPA Criteria API überhaupt nicht).

Wer sich die Criteria-API von JPA2 anschaut wird früher oder später (eher früher) über genau die 3 Typen stossen, die die Specification in ihrer toPredicate-Methode übergeben bekommt.

  • Root -Das Root ist quasi die Wurzel einer Query, analog der Defintion “Book b” in der JPQL Abfrage “select from Book b”, die Identifikationsvariable.
  • CriteriaQuery – Entspricht der Query die aufgebaut wird
  • CriteriaBuilder -Wird verwendet, um eine Query selbst zu erzeugen, als auch die einzelnen Bestandteile der Query (where-Clause)

(Übrigens normalerweise bekommt man den CriteriaBuilder über den EntityManager mit der Methode “getCriteriaBuilder” und die CriteriaQuery über den CriteriaBuilder und die Methode “newQuery”. Es ist nicht immer so komfortabel wie hier in Spring-Data:), die CriteriaQuery wird dann an die createQuery-Methode des EntityManagers übergeben, um diese auszuführen).

Als Beispiel hier mal ein Testcase, der die Verwendung mit der Criteria-API zeigt.

[code language=”java”]

@Resource
private EntityManagerFactory em;

@Test
public void simpleCriteriaAPITest() {

Book ddd = new Book("Domain Driven Design", "1", 39.95);
//neu
Book tdd = new Book("Feature Driven Development", "2", 29.95);
//gebraucht mit anderer isbn:)
Book fdd = new Book("Feature Driven Development","3", 19.95);
repository.save(ddd);
repository.save(tdd);

EntityManager manager = em.createEntityManager();
CriteriaBuilder builder = manager.getCriteriaBuilder();
CriteriaQuery query = builder.createQuery(Book.class);
Root<Book> bookRoot = query.from(Book.class);
Predicate title = builder.equal(bookRoot.get("title"), "Feature Driven Development");

assertEquals(2, manager.createQuery(query).getResultList().size());

Predicate price = builder.greaterThan(bookRoot.get("prize").as(Double.class), 20d);

//override
query.where(title, price);
assertEquals(1, manager.createQuery(query).getResultList().size());

}

[/code]

Kommen wir zurück zum Thema

Specifications und Spring-Data.

Spring-Data kapselt die Verwendung der Criteria API in sogenannte Specifications (das Konzept ist im Buch DDD von Eric Evans erklärt).

Um mit Specifications arbeiten zu können muss unser Interface zusätzlich das Interface SpecificaionExecutor implementieren.

[code language=”java”]

public interface BookStoreRepository extends JpaRepository<Book, String>, JpaSpecificationExecutor<Book> {

public Book findByIsbn(String isbn);

@Query(value = "SELECT p from Book p where p.isbn=:isbn")
public Book manuallyFindByIsbn(@Param(value = "isbn") String isbn);
}

[/code]

Hierdurch erhält unser Repository folgende neuen Methoden, mit denen wir arbeiten können.

[code language=”java”]
T findOne(Specification<T> spec);
List<T> findAll(Specification<T> spec);
Page<T> findAll(Specification<T> spec, Pageable pageable);
List<T> findAll(Specification<T> spec, Sort sort);
long count(Specification<T> spec);

[/code]

Eine Specification kapselt nochmals viel vom ganzen Boilerplate-Code der JPA Criteria-API weg, im Prinzip kann man sich vorstellen, dass die Specifiation eine Prüfung darstellt und für jedes Buch hier im Beispiel entweder TRUE (=gehört in den Ergebnisraum) oder FALSE (=gehört nicht in den Ergebnisraum) liefert.

Das Ergebnis einer Specification sind alle Entities, die TRUE liefern.

Im Folgenden sieht man als Beispiel eine IsbnSpecification, die alle Bücher mit einer bestimmten ISBN filtert.

[code language=”java”]

public class IsbnSpecification implements Specification<Book> {

private String isbn;

public IsbnSpecification(String isbn){
this.isbn = isbn;
}

@Override
public Predicate toPredicate(Root<Book> bookRoot, CriteriaQuery<?> cq, CriteriaBuilder cb) {
return cb.equal(bookRoot.get("isbn"),isbn);
}
}

[/code]

Und hier der Testcase dazu:

[code language=”java”]

@Test
public void findByIsbnWithSpecification() {
Book ddd = new Book("Domain Driven Design", "abcdef", 39.95);
Book tdd = new Book("Test-Driven-Development", "abcdefg", 29.95);
repository.save(ddd);
repository.save(tdd);
IsbnSpecification specification = new IsbnSpecification(ddd.getIsbn());
List<Book> result = repository.findAll(specification);
assertEquals(1, result.size());
assertEquals(ddd, result.get(0));
}

[/code]

Einige Codebeispiele

Anbei folgen noch einige Code-Beispiele, die ich einfach nützlich finde und eine gute Referenz sind (aber gar nicht unbedingt was mit Spring-Data zu tun haben müssen):

Join-Table

Definieren wir eine Kategorie für Bücher.

[code language=”java”]
@Entity
@Table(name="CATEGORY")
public class Category implements Serializable {

@Id
@Column(name="CATEGORY")
private String category;

public Category(String category){
this.category = category;
}

public Category(){}

public String getCategory() {
return category;
}

public void setCategory(String category) {
this.category = category;
}
}
[/code]

Jedes Buch kann einer oder mehreren Kategorien zugeordnet sein.

[code language=”java”]
@OneToMany(fetch = FetchType.EAGER)
@JoinTable(name = "BOOK_CATEGORY", joinColumns = @JoinColumn(name="ISBN"),
inverseJoinColumns = @JoinColumn(name="CATEGORY"))
private List<Category> categories;

public Book(){}

public Book(String title, String isbn, double prize) {
this(title, isbn, prize, new ArrayList<Category>());
}

public Book(String title, String isbn, double prize, List<Category> categories){
this.title = title;
this.isbn = isbn;
this.prize = prize;
this.categories = categories;
}

[/code]
Das passende Mapping hierzu ist @JoinTable, da die Kategorien nichts von der Book-Entity wissen sollen.

Zusätzlich definieren wir uns ein CategoryRepository, um Kategorien speichern und laden zu können.
[code language=”java”]
public interface CategoryRepository extends JpaRepository<Category, String> {
}
[/code]

Der passende Testcase hierzu sieht so aus:
[code language=”java”]
@Test
public void saveWithSeveralCategories(){
Category cat1 = new Category("Krimi");
Category cat2 = new Category(("Sci-Fi"));

categoryRepository.save(cat1);
categoryRepository.save(cat2);

Book book = new Book("Analysis Patterns","abc", 39.95, Arrays.asList(new Category[]{cat1, cat2}));
repository.save(book);

Book persistendBook = repository.findByIsbn("abc");
assertEquals(2, persistendBook.getCategories().size());

}
[/code]

Was ich jetzt machen möchte ist, nach einem Buch mit einer bestimmten Kategorie zu suchen:
Nichts einfacher als das, und zwar im BookStoreRepository:

[code language=”java”]
public List<Book> findByCategories(Category cat);
[/code]

Und hier der passende Testcase dazu:

[code language=”java”]
@Test
public void findByCategory(){
Category cat1 = new Category("Krimi");
Category cat2 = new Category("Sci-Fi");
Category cat3 = new Category("Romanze");

categoryRepository.save(cat1);
categoryRepository.save(cat2);
categoryRepository.save(cat3);

Book book = new Book("Analysis Patterns","abc", 39.95, Arrays.asList(new Category[]{cat1, cat2}));
repository.save(book);

List<Book> krimis = repository.findByCategories(new Category("Krimi"));
assertEquals(1, krimis.size());

List<Book> romanzen = repository.findByCategories(new Category("Romanze"));
assertTrue(romanzen.isEmpty());

}
[/code]

Ok, wie aber würde das mit einer manuell generierten JPQL aussehen?

[code language=”java”]
@Query(value = "select p from Book p JOIN p.categories c where c=:cat ")
public List<Book> manuallyFindByCategory(@Param(value = "cat")Category cat);
[/code]

Und den Test erweitern wir einfach:

[code language=”java”]
@Test
public void findByCategory(){
Category cat1 = new Category("Krimi");
Category cat2 = new Category("Sci-Fi");
Category cat3 = new Category("Romanze");

categoryRepository.save(cat1);
categoryRepository.save(cat2);
categoryRepository.save(cat3);

Book book = new Book("Analysis Patterns","abc", 39.95, Arrays.asList(new Category[]{cat1, cat2}));
repository.save(book);
List<Book> krimis = repository.findByCategories(new Category("Krimi"));
assertEquals(1, krimis.size());

List<Book> manualKrimis = repository.manuallyFindByCategory(new Category("Krimi")) ;
assertEquals(1, manualKrimis.size());

List<Book> romanzen = repository.findByCategories(new Category("Romanze"));
assertTrue(romanzen.isEmpty());

List<Book> manualRomanzen = repository.manuallyFindByCategory(new Category("Romanze"));
assertTrue(manualRomanzen.isEmpty());

}
[/code]

Das generierte SQL ist identisch:
[code language=”sql”]
Hibernate: select book0_.isbn as isbn0_, book0_.prize as prize0_, book0_.title as title0_ from Book book0_ inner join BOOK_CATEGORY categories1_ on book0_.isbn=categories1_.ISBN inner join CATEGORY category2_ on categories1_.CATEGORY=category2_.CATEGORY where category2_.CATEGORY=?
[/code]
und
[code language=”sql”]
Hibernate: select book0_.isbn as isbn0_, book0_.prize as prize0_, book0_.title as title0_ from Book book0_ inner join BOOK_CATEGORY categories1_ on book0_.isbn=categories1_.ISBN inner join CATEGORY category2_ on categories1_.CATEGORY=category2_.CATEGORY where category2_.CATEGORY=?

[/code]

Der Sourcecode ist natürlich auf Github gehostet und zwar hier.

Sobald ich Zeit finde, werde ich dieses kleine Beispiel erweitern.

Anbei noch einige Links, die das Thema ebenfalls behandeln:

Spring-Data Tutorial zum Thema Queries

Referenz-Dokumentation zu Spring-Data

Spring Data API

Specifications in Spring Data

Spring-Data Java Konfiguration

Criteria-API Turorial


War dieser Blogeintrag für Sie interessant? Evtl. kann ich noch mehr für Sie tun.

Trainings & Know-How aus der Praxis zu

  • Apache Wicket 1.4.x, 1.5.x, 1.6.x
  • GIT – Best Practices, Einsatz, Methoden
  • Spring
  • Java
  • Scrum & Kanban
  • Agiles Arbeiten
Consulting & Softwareentwicklung

  • Requirements Engineering
  • Qualitätssicherung
  • Software-Entwicklung
  • Architektur
  • Scrum & Kanban

Spring, Wicket und die GAE

Im letzten Artikel haben wir ein initiales Setup einer Wicket-Applikation für die Google App Engine vorgenommen. Die Applikation ist nach wie vor hier erreichbar.

In diesem Artikel wollen wir die Applikation springifizieren, d.h. wir wollen das Springframework integrieren.

Zunächst definieren wir folgende pom im Unterverzeichnis “dependencies/spring”.

[code language=”xml”]
<?xml version=”1.0″ encoding=”UTF-8″?>
<project xmlns=”http://maven.apache.org/POM/4.0.0″
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=”http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd”>

<modelVersion>4.0.0</modelVersion>
<groupId>de.effective</groupId>
<artifactId>spring-dependencies</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<name>spring-dependencies</name>
<description>spring dependencies</description>

<properties>
<spring.version>3.1.1.RELEASE</spring.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</project>

[/code]

Spring definiert mittlerweile sehr viele Projekte, diese pom kann einfach importiert werden und theoretisch auch in anderen Projekten importiert werden.

Folgendes Fragment importiert die Spring-Abhängigkeiten (definiert in der ui-pom).

[code language=”xml”]

<dependency>
<groupId>de.effective</groupId>
<artifactId>spring-dependencies</artifactId>
<version>1.0-SNAPSHOT</version>
<type>pom</type>
</dependency>

[/code]

Wichtig ist hier das <type>pom</type>-Tag, denn hierdurch werden die in der spring-pom definierten Abhängigkeiten so aufgelöst als wären sie hier direkt in der pom definiert.

Zusätzlich deklarieren wir das Spring-Dependencies-pom im Application-Reactor-Pom:

[code]

<modules>
<!–adding–>
<module>wicketstuff/jdk-1.6-parent/gae-initializer-parent/gae-initializer</module>
<module>dependencies/spring</module>
<module>ui</module>
</modules>

[/code]

Um zu testen, ob die Spring-Integration funktioniert wollen wir jetzt einfach die Erzeugung der Wicket-Application-Instanz über Spring steuern.

Hierfür müssen wir die web.xml des Projektes anpassen. In dieser ist aktuell folgendes definiert.

[code language=”xml”]

<filter>
<filter-name>wicket.effectivetrainings.de</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
<init-param>
<param-name>applicationClassName</param-name>
<param-value>de.effective.EffectiveApplication</param-value>
</init-param>
</filter>

[/code]

Das muss abgeändert werden in:

[code language=”xml”]

<filter>
<filter-name>wicket.effectivetrainings.de</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
<init-param>
<param-name>applicationFactoryClassName</param-name>
<param-value>org.apache.wicket.spring.SpringWebApplicationFactory</param-value>
</init-param>
</filter>

[/code]

Wir registrieren also nicht die Applikation selbst, sondern eine Factory, die die Applikation über Spring auflöst. (Das ist eigentlich der Standardweg um damit zu arbeiten).

Zusätzlich müssen wir sicherstellen, dass der spring-context beim Starten der Applikation geladen wird.

Das kann auch in der web.xml passieren und zwar über den Standardmechnismus eines ContextLoaderListeners.

[code language=”xml”]

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

[/code]

Damit Spring weiß, wo es seinen Kontext findet, muss noch folgender Parameter definiert werden.

[code language=”xml”]
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-context.xml</param-value>
</context-param>
[/code]

Als nächstes definieren wir uns einen sehr einfachen Spring-Context:

[code language=”xml”]

<?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:context=”http://www.springframework.org/schema/context”
xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd”>
<context:component-scan base-package=”de.effective”/>
</beans>

[/code]

Spring sucht im Package de.effective und allen Unterverzeichnissen nach Annotierten Spring-Beans.

Zuletzt definieren wir unsere Wicket-Applikation als SpringBean.

[code language=”java”]

@Component
public class EffectiveApplication extends WebApplication
{

}

[/code]

Wir deployen die Applikation und greifen darauf zu.

 

 

 

 

 

 

Das Projekt ist weiterhin auf github gehostet. Der Commit der diese Anforderung implementiert ist hier zu finden. Das Projekt als Zip gibt es hier.

https://github.com/dilgerma/effectivetrainings/commit/3dc99d3c88cf343cfbfbcf5ad7b88069c036d4ad

MetadataMBeanInfoAssembler does not support JDK dynamic proxies – Eine der ekelhaftesten Spring Exceptions.

Das exportieren von MBeans mit Spring ist kinderleicht und macht Spaß!

Folgende Exception hat mich und auch einige Kollegen (hallo David!:)) kürzlich einige Nerven gekostet.

[code language=”java”]

Caused by: java.lang.IllegalArgumentException: MetadataMBeanInfoAssembler does not support JDK dynamic proxies – export the target beans directly or use CGLIB proxies instead
at org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler.checkManagedBean(MetadataMBeanInfoAssembler.java:105)
at org.springframework.jmx.export.assembler.AbstractMBeanInfoAssembler.getMBeanInfo(AbstractMBeanInfoAssembler.java:63)
at org.springframework.jmx.export.MBeanExporter.getMBeanInfo(MBeanExporter.java:819)
at org.springframework.jmx.export.MBeanExporter.createAndConfigureMBean(MBeanExporter.java:792)

[/code]

Um dies nachzustellen definieren wir folgenden Spring-Context:

[code language=”xml”]

<?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:context=”http://www.springframework.org/schema/context” xmlns:tx=”http://www.springframework.org/schema/tx”
xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd”>

<bean id=”transactionManager”>
<property name=”userTransaction” ref=”dummyTransaction”/>
</bean>
<tx:annotation-driven transaction-manager=”transactionManager”/>

<context:mbean-export/>
<context:mbean-server id=”mBeanServer”/>
<context:component-scan base-package=”de.effective”/>
</beans>

[/code]

Dieses Service-Interface:

[code language=”java”]

public interface MyService {
public void sagDochMal(String was);
}

[/code]

Und diese Implementierung:

[code language=”java”]

@Component
@ManagedResource(objectName=”effective:name=laberService”)
@Transactional
public class MyServiceImpl implements MyService {
@Override
@ManagedOperation()
public void sagDochMal(String was) {
System.out.println(“Ich sag ” + was);
}
}

[/code]

Startet man die Testapplikation

[code language=”java”]

/**
* @author martin.dilger
*/
public class SpringApplication {

public static void main(String[] args) throws Exception {

new Thread(new Runnable() {
@Override
public void run() {
ClassPathXmlApplicationContext appCtx = new ClassPathXmlApplicationContext(“de/effective/spring-context.xml”);

MyService myService = appCtx.getBean(MyService.class);
myService.sagDochMal(“Heureka, ich habs!”);
while(true)
try {
Thread.sleep(1000);
System.out.println(“still running”);
} catch (InterruptedException e) {
e.printStackTrace();
}

}
}).start();       ;

}
}

[/code]

kriegt man direkt eine Exception vor den Latz geknallt:

[code language=”java”]

Exception in thread “Thread-0″ org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘mbeanExporter': Invocation of init method failed; nested exception is org.springframework.jmx.export.UnableToRegisterMBeanException: Unable to register MBean [de.effective.MyServiceImpl@3adec164] with key ‘myServiceImpl'; nested exception is org.springframework.jmx.export.MBeanExportException: Could not create ModelMBean for managed resource [de.effective.MyServiceImpl@3adec164] with key ‘myServiceImpl'; nested exception is java.lang.IllegalArgumentException: MetadataMBeanInfoAssembler does not support JDK dynamic proxies – export the target beans directly or use CGLIB proxies instead
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1455)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:294)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:225)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:585)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:913)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:464)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
at de.effective.SpringApplication$1.run(SpringApplication.java:17)
at java.lang.Thread.run(Thread.java:722)
Caused by: org.springframework.jmx.export.UnableToRegisterMBeanException: Unable to register MBean [de.effective.MyServiceImpl@3adec164] with key ‘myServiceImpl'; nested exception is org.springframework.jmx.export.MBeanExportException: Could not create ModelMBean for managed resource [de.effective.MyServiceImpl@3adec164] with key ‘myServiceImpl'; nested exception is java.lang.IllegalArgumentException: MetadataMBeanInfoAssembler does not support JDK dynamic proxies – export the target beans directly or use CGLIB proxies instead
at org.springframework.jmx.export.MBeanExporter.registerBeanNameOrInstance(MBeanExporter.java:602)
at org.springframework.jmx.export.MBeanExporter.registerBeans(MBeanExporter.java:527)
at org.springframework.jmx.export.MBeanExporter.afterPropertiesSet(MBeanExporter.java:413)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1514)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1452)
… 13 more
Caused by: org.springframework.jmx.export.MBeanExportException: Could not create ModelMBean for managed resource [de.effective.MyServiceImpl@3adec164] with key ‘myServiceImpl'; nested exception is java.lang.IllegalArgumentException: MetadataMBeanInfoAssembler does not support JDK dynamic proxies – export the target beans directly or use CGLIB proxies instead
at org.springframework.jmx.export.MBeanExporter.createAndConfigureMBean(MBeanExporter.java:797)
at org.springframework.jmx.export.MBeanExporter.registerBeanInstance(MBeanExporter.java:654)
at org.springframework.jmx.export.MBeanExporter.registerBeanNameOrInstance(MBeanExporter.java:592)
… 17 more
Caused by: java.lang.IllegalArgumentException: MetadataMBeanInfoAssembler does not support JDK dynamic proxies – export the target beans directly or use CGLIB proxies instead
at org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler.checkManagedBean(MetadataMBeanInfoAssembler.java:105)
at org.springframework.jmx.export.assembler.AbstractMBeanInfoAssembler.getMBeanInfo(AbstractMBeanInfoAssembler.java:63)
at org.springframework.jmx.export.MBeanExporter.getMBeanInfo(MBeanExporter.java:819)
at org.springframework.jmx.export.MBeanExporter.createAndConfigureMBean(MBeanExporter.java:792)
… 19 more

[/code]

Quizfrage:

Was muss man tun, um die Applikation startbar zu machen und woran liegts?

Die Sourcen gibts übrigens auch auf github: git@github.com:dilgerma/playground.git im Unterverzeichnis spring-riddle.

Die Lösung ist sehr, sehr einfach, aber man muss drauf kommen.

Hier nochmals der spring-context:

[code language=”xml”]

<?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:context=”http://www.springframework.org/schema/context” xmlns:tx=”http://www.springframework.org/schema/tx”
xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd”>

<bean id=”transactionManager”>
<property name=”userTransaction” ref=”dummyTransaction”/>
</bean>
<tx:annotation-driven transaction-manager=”transactionManager”/>

<context:mbean-export/>
<context:mbean-server id=”mBeanServer”/>
<context:component-scan base-package=”de.effective”/>
</beans>

[/code]

Der Trick ist folgender:

Die beiden Direktiven <context:mbean-export/> und
<context:component-scan base-package=”de.effective”/> sind in der falschen Reihenfolge im spring-context deklariert.

Dadurch, dass die Beans am Transaktionsmanagement teilnehmen erzeugt Spring Proxy-Klassen für die Beans. Proxy-Klassen dürfen aber nicht
als MBeans registriert werden (warum genau weiß ich nicht).
Dreht man die Reihenfolge um, funktioniert das Ganze.

[code language=”xml”]

<?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:context=”http://www.springframework.org/schema/context” xmlns:tx=”http://www.springframework.org/schema/tx”
xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd”>

<bean id=”transactionManager”>
<property name=”userTransaction” ref=”dummyTransaction”/>
</bean>
<tx:annotation-driven transaction-manager=”transactionManager”/>
<context:component-scan base-package=”de.effective”/>
<context:mbean-export/>
<context:mbean-server id=”mBeanServer”/>
</beans>

[/code]

Die Reihenfolge im Spring-Context spielt also tatsächlich eine Rolle! MBean-Deklarationen sollten immer möglichst weit
unten im Spring-Context platziert werden.

Es gibt haufenweise Einträge mit diesem Problem und mindestens eben so viele Lösungsvorschläge:), beispielsweise hier:

http://forum.springsource.org/showthread.php?14349-My-JMX-beans-no-longer-work-in-RC2

http://nelz.net/2008/10/10/spring-jmx-challenges/


War dieser Blogeintrag für Sie interessant? Evtl. kann ich noch mehr für Sie tun.

Trainings & Know-How aus der Praxis zu

  • Apache Wicket 1.4.x, 1.5.x, 1.6.x
  • GIT – Best Practices, Einsatz, Methoden
  • Spring
  • Java
  • Scrum & Kanban
  • Agiles Arbeiten
Consulting & Softwareentwicklung

  • Requirements Engineering
  • Qualitätssicherung
  • Software-Entwicklung
  • Architektur
  • Scrum & Kanban