Friday, November 21, 2008

[Spring 2.5] context:component-scan does not with @autowired in JUnit test.

A problem exists in Spring 2.5.1 (Maybe also later versions up until 2.5.4?) that cause context:component-scan not to detect the classes you wish to inject in your test classes. Instead you have to add a bean tag defining the bean that you wish to inject in your JUnit test case.

Example

applicationContext.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:aop="http://www.springframework.org/schema/aop"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
 <context:component-scan base-package="com.maaloe.spring" />
 <context:annotation-config/>
</beans>

TestCase:
package com.maaloe.spring;

import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

import dk.telia.cas.command.CommandException;
import dk.telia.cas.dataobject.CPRInfo;
import dk.telia.cas2.test.AbstractSpringTest;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:applicationContext.xml"})
public class MyServiceTest {

    @Autowired
    private MyTestService myTestService;

    @SuppressWarnings("unchecked")
    @Test
    public void test() {
        System.out.println("Test service class: " + myTestService.myToString());
    }
}

Service interface:
package com.maaloe.spring;

public interface MyTestService {
    String myToString();
}

Service Implementation:
package com.maaloe.spring;

import org.springframework.stereotype.Component;

@Component
public class MyTestServiceImpl implements MyTestService {
    public String myToString() {
        return "Ping...";
    }
}

The above does not work until you add:

<bean id="myTestService" class="com.maaloe.spring.MyTestServiceImpl"/>


to your applicationContext.xml file if you are running Spring 2.5.1.

Anyways... Simply fix it by upgrading Spring to 2.5.4.

Enjoy!

Wednesday, November 19, 2008

[Spring] @Autowired doesn't work in an abstract class

If your @AutoWired doesn't seem to work in an abstract class, it could be because you need to add @Component to the solid class that extends the abstract class.

So if you have an abstract class:
package com.maaloe.test;

import org.springframework.beans.factory.annotation.Autowired;

public abstract class MyAbstractClass {
  @Autowired
  TestService testService;
  public void test() {
    System.out.println("TestService: " + testService.myToString());
  }
}

and a solid class:
package com.maaloe.test;

import org.springframework.stereotype.Component;

@Component
public class MySolidClass extends MyAbstractClass {
  public void mytest() {
    super.test();
  }
}

Note that this abstract class does NOT have a @Component tag, as this is never used as a solid class, so even though it uses @Autowired for injecting its member variables, only the class extending the MyAbstractClass need to have the @Component tag.

I found this rather confusing... but that's apparently the way it works. :)

Heres' the TestService and the TestServiceImpl:
package com.maaloe.services;

public interface TestService {
  String myToString();
}

package com.maaloe.services.impl;

import org.springframework.stereotype.Component;

@Component
public class TestServiceImpl implements TestService {
  public String myToString() {
    return "TestService ping...";
  }
}

and finally, the applicationContext must contain:

<context:component-scan base-package="com.maaloe" />
<context:annotation-config/>


Enjoy...

Note that none of the above have been compiled, it's just written from my memory, so forgive me if it contains minor errors.

Please comment if you have anything to add!

Thursday, October 30, 2008

[JSF] How to get rid of "JSF1054: (Phase ID: RESTORE_VIEW 1, View ID: )" on session timeout

While upgrading JSF from 1.1 to 1.2 (1.2_09) I found a lot of the following exceptions in tomcats catalina.out and localhost.log.

com.sun.faces.lifecycle.Phase doPhase SEVERE: JSF1054: (Phase ID: RESTORE_VIEW 1, View ID: ) Exception thrown during phase execution: javax.faces.event.PhaseEvent[source=com.sun.faces.lifecycle.LifecycleImpl@55d7b]

SEVERE: Servlet.service() for servlet Faces Servlet threw exception
javax.faces.application.ViewExpiredException: viewId:/xxxx.jsf - View /xxxx.jsf could not be restored.
        at com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:186)
        at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:100)

Turns out that this usually happens if you have a request that refers to JSF ViewIds (the structure of the jsf page) that doesn't exist in the session on the server. For example, a users session could be timed out on the server, so when the user makes a request to the server with the ViewId, it no longer exist and you will most likely receive the above exception.
The reason why this suddenly shows up is that JSF 1.1 and 1.2 handles the session timeout differently. Now you actually have a chance to react upon the thrown exception whereas previously it was just ignored and the view tree was just regenerated from scratch.

Anyways... What you can do to avoid this is two things:
  • Handle the exception, for example by adding a filter that shows a nice page to the user
  • Insert the following in your web.xml:
      
        com.sun.faces.enableRestoreView11Compatibility    true
      
    
    This will cause JSF to behave as it did in 1.1