PDA

View Full Version : Using internal classes with Spring


maggyb
04-22-2005, 06:51 PM
We are using Spring.Net for dependency injection at my company, DigitalFocus. It has worked out very well for us and aided us greatly in unit testing. There has only been one major snag, and it involves trying to use Spring.Net to inject internal classes. Internal classes are a good place to put plumbing that needs to be used inside an assembly that shouldn't be accessed directly from outside the assembly (such as low level database access code).

In order to inject an internal class into a public class, the property used for injection must also be internal (or else the compiler complains - inconsistent accessibility). This also is true for constructor or factory method injection. The problem is that Spring.Net does not use the NonPublic binding flag when looking for the property, constructor, or factory method to use for injection. I have been able to modify the Spring.Net code to make this work, but would like to know the intent in not allowing non-public members to participate in injection.

For reference, here are the 3 changes I made:

Spring.Objects.ObjectWrapper, changed a constant on line 110 (added BindingFlags.NonPublic):

private const BindingFlags PropertyResolutionFlags =
BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.SetProperty
| BindingFlags.Static
| BindingFlags.Instance
| BindingFlags.IgnoreCase;

Spring.Objects.Factory.Support.AbstractAutowireCap ableObjectFactory, changed line 971 in method: protected IObjectWrapper AutowireConstructor (string objectName, RootObjectDefinition definition)

ConstructorInfo[] constructors = definition.ObjectClass.GetConstructors(BindingFlag s.Public | BindingFlags.NonPublic | BindingFlags.Instance);

Spring.Objects.Factory.Support.AbstractAutowireCap ableObjectFactory, changed line 781 in method: protected virtual IObjectWrapper InstantiateUsingFactoryMethod (string objectName, RootObjectDefinition definition, object [] arguments)

BindingFlags methodFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase;

maggyb
04-22-2005, 08:26 PM
The version of Spring that I changed was actually release candidate 1. For anyone interested, only 2 changes had to be made for release candidate 3 (the most recent as of this writing):

Spring.Objects.ObjectWrapper, line 114 changed constant:

private const BindingFlags PropertyResolutionFlags =
BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.SetProperty
| BindingFlags.Static
| BindingFlags.Instance
| BindingFlags.IgnoreCase;

Spring.Objects.Factory.Support.AbstractAutowireCap ableObjectFactory, line 809 of method: protected virtual IObjectWrapper InstantiateUsingFactoryMethod(string name, RootObjectDefinition definition, object[] arguments):

BindingFlags methodFlags = BindingFlags.Public | BindingFlags.NonPublic| BindingFlags.IgnoreCase;

Mark Pollack
04-24-2005, 06:08 PM
Hi Maggy,

I'd be inclined to add this - though DI is normally about injection of public properties there are valid reasons for performing DI on non public properties in certain cases. It might be a good idea to disable this behavior by adding a top level property to the context - much like auto-wire so that public property DI purists can have a measure of control over this behavior. I'll run it by the other developers and also the Spring.Java guys. Thanks for such a detailed posting!

Cheers,
Mark

Aleks Seovic
04-25-2005, 01:10 AM
I'm inclined to allow this as well. In some cases I'd like to be able to make injected properties private, so they are not (as easily) accessible from the client code.

I don't fully understand the reason for making them internal, though. If the only classes you can assign to them are internal to the assembly, why use DI at all -- you could simply hardcode which class to use. Unless you are packaging both production classes and their corresponding test stubs into the same assembly, of course.

In that case, you could also make the property public, use the appropriate public interface as its type, and make internal class implement that same public interface. That way you get the ability to inject an instance of the internal class while being able to replace it with other, external implementation during testing.

I guess it boils down to how you want to ship your test stubs. My personal preference is to have them in a separate assembly, but I can see the value of keeping them in the same assembly with the classes they replace -- for one, it makes them much easier to find when you need them :wink:

- Aleks

maggyb
04-25-2005, 02:49 PM
Thanks for your quick replies. Here's some more in depth info on our situation:

We don't want to hardcode the references to our internal classes for testing reasons - using DI makes testing each layer separately a breeze. Our specific case for wanting internal classes is that we have public Manager classes that are responsible for security, business logic, etc. We also have Dao classes that simply interface with the DB. The Daos should not be accessed directly outside the assembly, but we do want to use DI to inject them into the managers to make testing cleaner (we frequently use NMock to mock out injected classes).

Using a public interface with an internal class does work, but we have no need to make the interface to the dao public (and would prefer not to simply to accommodate our chosen frameworks). All our tests are contained inside the assemblies they test, with different build scripts including or excluding them as appropriate for each environment.

I noticed that with Spring.Net release candidate 3 non-public constructor injection is supported already. It would be great if property and factory method injection could also support non-public injection. If it were configurable all the better.

Thanks very much!

Aleks Seovic
04-25-2005, 03:26 PM
Yeah, exactly the scenario I had in mind...

I'd say we should support non-public injection. It allows people to choose property and class visibility based on their needs instead of framework requirements. I really don't see the need to impose artifical requirements such as this one on the users.

The only issue I see with it is that it makes application somewhat tied to Spring because it's not possible to inject collaborators directly by simply setting a property -- you'd have to use reflection. But then again, tying or not tying application to Spring is a decision users should be allowed to make.

- Aleks