PDA

View Full Version : Found a problem in Spring AOP / IoC container implementation?


Oliver Paulus
04-03-2007, 02:44 PM
Hello,

here is my configuration:

Dao.xml:

<?xml version="1.0"?>
<objects xmlns="http://www.springframework.net">
<object id="userDaoEntity" type="SpringSample.Dao.UserDao, SpringSample" singleton="true">

</object>
</objects>


Pages.xml:

<?xml version="1.0"?>
<objects xmlns="http://www.springframework.net">
<object type="UserList.aspx">
<property name="UserService" ref="userServiceEntity"/>
</object>
</objects>


Services-Aop.xml:

<?xml version="1.0"?>
<objects xmlns="http://www.springframework.net">
<object id="loggingAdvice" type="SpringSample.Service.AOP.LoggingAdvice, SpringSample"/>

<object id="settersAndAbsquatulateAdvisor" type="Spring.Aop.Support.RegularExpressionMethodPointcut Advisor, Spring.Aop">
<property name="advice">
<ref local="loggingAdvice"/>
</property>
<property name="patterns">
<list>
<value>.*</value>
</list>
</property>
</object>

<object id="ProxyCreator" type="Spring.Aop.Framework.AutoProxy.DefaultAdvisorAutoP roxyCreator, Spring.Aop"/>
</objects>


Services.xml:

<?xml version="1.0"?>
<objects xmlns="http://www.springframework.net">
<object id="userServiceEntity" type="SpringSample.Service.UserService, SpringSample" singleton="true">
<constructor-arg name="userDao" ref="userDaoEntity"/>
<property name="MyValue" value="100"/>
</object>
</objects>


Here is the code:

UserService:

public class UserService
{
private UserDao userDao;
private int myvalue;

public int MyValue
{
set
{
this.myvalue = value;
}
}

public UserService(UserDao userDao)
{
this.userDao = userDao;
}

public IList<User> getAll()
{
return this.userDao.getAll();
}
}


Page (UserList.aspx):

public partial class UserList : System.Web.UI.Page
{
private UserService userService;

public UserService UserService
{
get
{
return userService;
}
set
{
userService = value;
}
}

protected void Page_Load(object sender, EventArgs e)
{
userService.getAll();
}
}


The problem is the following: If I use the AOP configuration the injected UserService into UserList.aspx does has the private field UserDao == null. If I do not use the AOP configuration everything works expected. The same problem occurs with the MyValue Property of UserService: the injected entity of this type has MyValue == 0 if I use the AOP configuration. That is very confusing and seems to be a bug in Spring AOP with IoC container to me. It seems that singleton="true" does not work correctly too - because the setter of MyValue (e.g) will be called - I get it in Debug mode. But the entity injected into the ASPX page does not have the value in the field.

Here is the code for the Advice:

public class LoggingAdvice : IMethodInterceptor
{
public object Invoke(IMethodInvocation invocation)
{
Console.WriteLine("Before: invocation=[{0}]", invocation);
object rval = invocation.Proceed();
Console.WriteLine("Invocation returned");
return rval;
}
}


Any ideas?

Mark Pollack
04-03-2007, 09:38 PM
Hi Oliver,

Thanks for a detailed report. I will try to reproduce. In the meantime, the pattern you are using will match all methods/properties of all objects defined in the context, the problem may lie there. Can you change this to


<property name="patterns">
<list>
<value>.*get.*</value>
</list>
</property>


which in the current case will apply the logging interceptor to just the service method IList<User> getAll(). (I smell some Java there.... ;) )

Cheers,
Mark

Bruno Baia
04-03-2007, 11:57 PM
Hi,

a reason why userDao private fields returns null is that UserDao class does not implement any interface and "getAll" is not virtual, so the class cannot be dynamically proxied.

2 solutions :
- Implement an IUserDao interface
or
- Make getAll method virtual (in Java, methods are virtual by default, this is not the case with C#)


HTH,
Bruno

Oliver Paulus
04-04-2007, 06:56 AM
I have made an interface for UserService which is used in the ASPX page. It is working now - but why do I need an interface for that? Normally I use Spring for Java - and there I do not need to create an interface to have the generated proxy working. I tried to make the Properties of UserService virtual - but nothing changed. I had tried to make getAll virtual - also no result.

Bruno Baia
04-04-2007, 10:56 PM
Hi,

Spring.Net works like the Java version, there is 2 ways to proxy a class :
- Inheriting from it and override target methods.
(The class can't be sealed, and methods needs to be virtual. In Java, methods are virtual by default unlike C#)
- Implementing interfaces the class implements, and delegate calls to the target object.


public class UserDao
{
public virtual IList getAll()
{
...
}
}

OR

public interface IUserDao
{
IList getAll;
}

public class UserDao : IUserDao
{
public IList getAll()
{
...
}
}



What I forgot to say in the previous post is that the first way is only supported since 1.1 Preview 3.


Bruno

Oliver Paulus
04-05-2007, 06:48 AM
Ok. I have made some tests. The problem is that your first solution with "virtual" does not work in my case. This is the Spring.Net version I am using: Spring.NET-20070401-2216.zip. I need an Interface to get the injection work correctly. If I do not use an interface for the service class I get a service instance injected into the page which has "null" value for dao instance within it.

Bruno Baia
04-05-2007, 07:12 AM
Hi,

The DefaultAdvisorAutoProxyCreator will also proxy the UserService class, have you changed the class UserService too ?

public class UserService
{
private UserDao userDao;
private int myvalue;

public virtual int MyValue
{
set
{
this.myvalue = value;
}
}

public UserService(UserDao userDao)
{
this.userDao = userDao;
}

public virtual IList<User> getAll()
{
return this.userDao.getAll();
}
}


If it still not works, I'll try to reproduce it.

Bruno

Oliver Paulus
04-05-2007, 08:49 AM
I have tried exactly this code in UserService class. I am using this AOP configuration:


<object id="loggingAdvice" type="SpringSample.Service.AOP.LoggingAdvice, SpringSample"/>

<object id="aroundAdvisor" type="Spring.Aop.Support.NameMatchMethodPointcutAdvisor, Spring.Aop">
<property name="Advice">
<ref local="loggingAdvice"/>
</property>
<property name="MappedNames">
<list>
<value>*</value>
</list>
</property>
</object>

<object id="ProxyCreator" type="Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxy Creator, Spring.Aop">
<property name="ObjectNames">
<list>
<value>*Service*</value>
</list>
</property>
<property name="InterceptorNames">
<list>
<value>aroundAdvisor</value>
</list>
</property>
</object>

Bruno Baia
04-05-2007, 10:50 PM
Hi,

I wrote a test application, and I made it work (see attached file).

But I got a stackoverflow exception when I replace "my*" by "*".
I applied a fix, and it will be avalaible tomorrow with the nightly build.


- Bruno

Oliver Paulus
04-06-2007, 09:21 AM
Thank you for the test! I will try it soon. I am using this in a web application - can it be a problem with the Spring "WebContextHandler" - because your code is very similar to mine? The problem is only in the page property - where UserService will be injected into. If I call userService.get... the UserDao within this UserService instance is null - MyValue is 0.

Bruno Baia
04-06-2007, 06:45 PM
Hi,

I got it working in a Web Application too, see attached file.

But now, I know what you mean by :
"the UserDao within this UserService instance is null - MyValue is 0."

Here is the proxy generated by Spring when your class does not implement any interfaces or when you set "ExposeTargetType=true" :

public class DecoratorProxy_GUID : UserService
{
private UserService __target;

public DecoratorProxy_GUID(UserService target)
{
__target = target;
}

public override int MyValue
{
set
{
__target.MyValue = value;
}
}

public override IList<User> getAll()
{
return __target.getAll();
}
}


The proxy instance it self is not valid, so in debug mode you can see that userdao and myValue are not set, but if you call getAll method or MyValue property, it will delegate the call to target object and you'll get expected values.
That's how the Spring Java version works too (see cglib proxies).


HTH,
Bruno

Oliver Paulus
04-07-2007, 11:18 AM
In my case I got an "Object reference not set to an instance of an object" exception within UserService. But I will take a look into your solution. Thank you for now - I will test it.

Mark Pollack
04-07-2007, 09:12 PM
Hi Oliver,

You may find it helpful to take a look at the 'TxQuickStart' application in the distribution. It is very similar to what you are doing, i.e. an AccountManager that calls DAOs.

Cheers,
Mark

Oliver Paulus
04-10-2007, 07:40 AM
I have made some tests now. I do not need an interface anymore - I have to use virtual - of course. I missed it sometimes during my tests - and I was confused by VS Debugger output. Thank you for your help.