views:

272

answers:

7

Hi,

Is it possible to assure that only spring can instantiate a class, and not by the keyword new at compile time? (To avoid instantiating by accident)

Thank you!

A: 

don't know about spring, but if you want to have some control on creating new instances, you should make constructor private, and create public static YourClass getInstance() method inside your class which will handle checks and return new instance of that object. You can then create new class with constructor whichi will call getInstance().. and hand that class to Spring. Soon you will discover places where you had that 'illegal' calls outside spring...

ante.sabo
That would also defeat the purpose of dependency injection.
Michael Aaron Safyan
just wanted exception to be thrown so he can find where that instantiation comes 'oustide spring'...
ante.sabo
Spring will call 'newInstance', not the constructor, if you have a factory class. That is, you'll need to create a YourClassFactory class that has the method 'newInstance()', not a static method on YourClass itself. There are all sorts of tricks you can use to get around the constructor issue then, e.g., your factory class could actually be a public static inner class to YourClass so it can see a private constructor for the latter.
bgiles
+4  A: 

The only obvious way I can think of doing this is at Runtime via a massive hack; Spring works with normal Java after all (i.e. anything that can be accomplished in Spring must be accomplishable via standard Java - it's therefore impossible to achieve as a compile time check). So here's the hack:

//CONSTRUCTOR
public MyClass() {
    try {
         throw new RuntimeException("Must be instantiated from with Spring container!");
    }
    catch (RuntimeException e) {
        StackTraceElement[] els  = e.getStackTrace();
        //NOW WALK UP STACK AND re-throw if you don't find a springframework package
        boolean foundSpring = false;
        for (StackTraceElements el : els) {
            if (el.getDeclaringClass().startsWith("org.springframework")) {
                foundSpring = true; break;
            }
        }
        if (!foundSpring) throw e;
    }
}

I really would not advise doing this!

oxbow_lakes
"anything that can be accomplished in Spring must be accomplishable via standard Java". True, but not everything Spring can accomplish can be done using the *new* keyword.
Wouter Coekaerts
+1 for the clever hack, and for advising against it.
Michael Aaron Safyan
@Wouter - but any workaround (using `private` constructor, using static factory) which will work in Spring is also available from Java. Hence the *purpose* of the request (making something unable to work outside a Spring container) will not be met
oxbow_lakes
Wow, that's one of the smartest spring hacks in the category "never do this" :) Kudos for that!
Michael Bavin
The purpose is not to make it *entirely* impossible to instantiate the class directly. That's indeed impossible, but that's not what this question is about. It's about having some proper encapsulation, so that any code that breaks the encapsulation looks obviously bad (by having to resort reflection,...)
Wouter Coekaerts
Besides being obviously bad, and not an answer to the question, this doesn't work.Some Spring class being somewhere on the stack doesn't mean that it is Spring who is instantiating this class.It could be initializing some class that uses some class that calls this constructor directly. Or it could be handling an http request using spring-mvc (which is then implemented as something calling this constructor directly), or...In fact, in an application that heavily uses Spring, there is practically always some Spring class somewhere on the stack, so the check always passes.
Wouter Coekaerts
A: 

Not sure if Spring supports this as I haven't tried, and haven't used Spring in quite awhile, however with another IOC container a sneaky route I once took was to make the class one wishes to be returned as your injected interface an abstract class, and have the IOC container return that as a derived class instance. This way no-one can create an instance of the class (as it's abstract) and the container can return a derived class of this.

The container itself will generate the definition of the derived class so there's no worry of someone trying to construct one of these

saret
+3  A: 

If you want to detect it at compile time, the constructor must be non-public. Private is probably too strict (it makes code analysis tools assume it will never be called, and may even cause warnings in some IDEs), I'd say the default (no modifier, package protected) is best there. In cases you want to allow subclasses in other packages (but that's impossible without allowing calling the constructor directly from that subclass) you can make it protected. Make sure to comment the constructor appropriately, so it is clear to anyone reading the code why the constructor is like that.

Spring will call this non-public constructor without any problems (since Spring 1.1, SPR-174).

The question if this not allowing anything else to call your constructor, the idea of forcing every user of a class to use the Spring dependency injection (so the whole goal of this question) is a good idea or not, is a whole different matter though.

If this is in a library/framework (that is just usually used with Spring), limiting the way it may be used might not be such a good idea. But if it's for classes that you know will only be used in your closed project, which already forces the use of Spring, it might make sense indeed.

Alternatively, if your real goal is just to avoid the possibility of someone creating an instance and not initializing it with its dependencies, you can just use constructor dependency injection.

And if your goal is only to prevent accidental usage of the constructor (by developers not aware that the class was supposed to be initialized by Spring), but don't want to totally limit possibilities, you can make it private but also add static factory method (with an explicit name like createManuallyInitializedInstance or something like that).

Bad idea: Another possible alternative is to make the constructor publicly available, but deprecate it. This way it can still be used (without resorting to hacks like using reflection) but any accidental usage will give a warning. But this isn't really clean: it is not what deprecation is meant for.

Wouter Coekaerts
Thank you, that was a very good reply and just what i wanted to know:)
Michael Bavin
A: 

Write an aspect around the call to the constructor and abort if not via Spring

Paul McKenzie
+1  A: 

While I can understand why you would want to ensure that a class is instantiated only by Spring, this is actually not a good idea. One of the purposes of dependency injection is to be able to easily mock out a class during testing. It should be possible, then, during unit tests to manually instantiate the various dependencies and mock dependencies. Dependency injection is there to make life easier, and so it is usually good to instantiate with DI, but there are cases where using new is perfectly sensible and one should be careful not to take any design pattern or idiom to the extreme. If you are concerned that your developers are going to use new where they should use DI, the best solution for that is to establish code reviews.

Michael Aaron Safyan
A: 

I'll tell you why not to do this - you won't be able to mock your classes. And thus you won't be able to make unit-tests.

Bozho