views:

437

answers:

2

I have a strange behaviour when autowiring

I have a similar code like this one, and it works

@Controller
public class Class1 {
    @Autowired
    private Class2 object2;
    ...
}

@Service
@Transactional
public class Class2{
   ...
}

The problem is that I need that the Class2 implements an interface so I've only changed the Class2 so it's now like:

@Controller
public class Class1 {
    @Autowired
    private Class2 object2;
    ...
}

@Service
@Transactional
public class Class2 implements IServiceReference<Class3, Long>{
   ...
}

public interface IServiceReference<T, PK extends Serializable> {
    public T reference(PK id);
}

with this code I get a org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type for Class2. It seems that @Transitional annotation is not compatible with the interface because if I remove the @Transitional annotation or the implements IServiceReference<Class3, Long> the problem disapears and the bean is injected (though I need to have both in this class). It also happens if I put the annotation @Transitional in the methods instead of in the Class.

I use Spring 3.0.2 if this helps.

Is not compatible the interface with the transactional method? May it be a Spring bug?

+6  A: 

The problem is that your Class1 needs a reference to IServiceReference and not the concrete reference of Class2

@Controller
public class Class1 {
@Autowired
private IServiceReference object2;
    ...
}

The reason this is that Spring is creating a dynamic proxy for classes that you marked @Transactional. Thus when Class2 is created its wrapped in a Proxy object that is obviously not of type Class2 but is of type IServiceReference.

If you want the behavior of using Class2 with proxy support you will have to turn on CGLIB Read below:

From Springs Doc:

Spring AOP defaults to using standard J2SE dynamic proxies for AOP proxies. This enables any interface (or set of interfaces) to be proxied.

Spring AOP can also use CGLIB proxies. This is necessary to proxy classes, rather than interfaces. CGLIB is used by default if a business object does not implement an interface. As it is good practice to program to interfaces rather than classes, business classes normally will implement one or more business interfaces. It is possible to force the use of CGLIB, in those (hopefully rare) cases where you need to advise a method that is not declared on an interface, or where you need to pass a proxied object to a method as a concrete type.

It is important to grasp the fact that Spring AOP is proxy-based. See the section entitled Section 6.6.1, “Understanding AOP proxies” for a thorough examination of exactly what this implementation detail actually means.

Adam Gent
+4  A: 

The Transactional annotation instructs Spring to generate proxy objects around the annotated beans, to implement the transactional semantics. The generated proxy will implement the same interfaces as the target bean. So if your target bean implements IServiceReference, then so will the generated proxy.

If the target bean has no implemented interfaces, then the generated proxy will instead be a subclass of the target bean type.

In your original example, the transactional proxy will be a subclass of Class2, because Class2 implemented no interfaces. When you changed Class2 to implementIServiceReference, the generated proxy no longer extendedClass2, and instead implementedIServiceReference. This caused yourClassCastException`.

The best approach to this situation is to remove the reference from Class1 to Class2, and instead talk to Class2 purely through its interfaces. Class2 can implement as many interfaces as you like, the proxy will implement all of them.

You can force Spring to generate subclass proxies regardless of the interfaces, but it's additional complexity, and I'd recommend against it.

skaffman
Basically the same thing I said :)
Adam Gent