Spring:如何确保一个类只能由Spring实例化,而不能使用关键字new实例化

3

是否有可能确保只有Spring可以实例化一个类,而不是在编译时使用关键字“new”?(避免意外实例化)

谢谢!

7个回答

6
如果您希望在编译时检测它,则构造函数必须是非公共的。私有可能太严格了(它会使代码分析工具认为它永远不会被调用,甚至可能导致一些IDE发出警告),我认为默认值(无修饰符,包保护)最好。在某些情况下,您想允许其他包中的子类(但是这是不可能的,除非允许直接从该子类调用构造函数),则可以将其设置为受保护的。确保适当地注释构造函数,以便任何阅读代码的人都清楚为什么构造函数是这样的。
Spring将在没有任何问题的情况下调用此非公共构造函数(自Spring 1.1以来,SPR-174)。
然而,是否禁止其他任何东西调用您的构造函数,强制每个类的用户使用Spring依赖注入(所以这个问题的整个目标)是一个完全不同的问题。
如果这是在一个库/框架中(通常与Spring一起使用),限制它的使用方式可能不是一个好主意。但是,如果它是为您已知只会在您的封闭项目中使用的类而设计的,该项目已经强制使用Spring,则确实有意义。
或者,如果您真正的目标只是避免有人创建一个实例并没有初始化其依赖项,那么您可以使用构造函数依赖注入。
如果您的目标仅仅是防止开发人员意外使用构造函数(不知道该类应该由Spring初始化),但不想完全限制可能性,您可以将其设置为私有,但也添加静态工厂方法(使用显式名称,例如createManuallyInitializedInstance或类似的名称)。
不好的想法:另一个可能的替代方案是使构造函数公开,但将其弃用。这样它仍然可以被使用(而不需要使用反射等黑客技巧),但任何意外的使用都会给出警告。但这并不真正干净:这不是弃用的意思。

谢谢,那是非常好的回答,正是我想知道的 :) - Michael Bavin

5
我能想到的唯一明显的方法是通过一个巨大的hack在运行时来完成。毕竟,Spring是使用普通Java工作的(即,在Spring中可以完成的任何事情都可以通过标准Java完成)。因此,要作为编译时检查是不可能实现的。所以这里是这个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;
    }
}

我真的不建议这样做


“任何可以在 Spring 中完成的事情,都可以通过标准 Java 完成。” 是正确的,但并不是 Spring 能完成的所有事情都可以使用“new”关键字来完成。 - Wouter Coekaerts
@Wouter - 但是任何可以在Spring中运行的解决方法(使用private构造函数、使用静态工厂)也可以从Java中使用。因此,请求的目的(使某些东西无法在Spring容器之外工作)将无法实现。 - oxbow_lakes
2
哇,这是“永远不要这样做”类别中最聪明的Spring技巧之一 :) 为此鼓掌! - Michael Bavin
目的不是完全防止直接实例化类。这确实是不可能的,但这不是这个问题的重点。它关于拥有一些适当的封装,以便任何破坏封装的代码看起来显然很糟糕(通过使用反射等手段)。 - Wouter Coekaerts
1
除了显然不好,而且不是问题的答案,这个方法并不能起作用。 某些Spring类在堆栈中出现,并不意味着是Spring实例化了该类。 它可能正在初始化使用某些类直接调用此构造函数的类。或者它可能正在使用spring-mvc处理http请求(然后实现为调用此构造函数的某些内容),或者...... 事实上,在大量使用Spring的应用程序中,几乎总会在堆栈中的某个地方有一些Spring类,因此检查始终通过。 - Wouter Coekaerts

1

虽然我能理解你为什么想要确保一个类只能由Spring实例化,但这实际上并不是一个好主意。依赖注入的目的之一是在测试期间轻松模拟一个类。因此,在单元测试期间手动实例化各种依赖项和模拟依赖项应该是可能的。依赖注入的存在是为了让生活更轻松,所以通常最好使用DI进行实例化,但也有一些情况下使用new是完全合理的,人们应该小心不要将任何设计模式或习惯用法推向极端。如果你担心你的开发人员会在应该使用DI的地方使用new,那么最好的解决方案就是建立代码审查。


1
我告诉你为什么不要这样做 - 你将无法模拟你的类,因此你将无法进行单元测试。

0

我不确定Spring是否支持这个功能,因为我没有尝试过,并且已经有一段时间没有使用Spring了。然而,在另一个IOC容器中,我曾经采取的一个巧妙的方法是将希望作为注入接口返回的类作为抽象类,让IOC容器将其作为派生类实例返回。这样,没有人可以创建该类的实例(因为它是抽象的),并且容器可以返回此类的派生类。

容器本身将生成派生类的定义,因此不必担心有人尝试构造其中之一。


0

在构造函数调用周围编写一个方面,并且如果不是通过Spring,则中止


-1

虽然不知道Spring,但如果你想在创建新实例时有一些控制权,应将构造函数设为私有,并在类内部创建public static YourClass getInstance()方法来处理检查并返回该对象的新实例。然后,您可以创建具有调用getInstance()的构造函数的新类... 并将该类交给Spring。很快您就会发现,在Spring之外有'非法'调用的地方...


这也会违背依赖注入的初衷。 - Michael Aaron Safyan
他只是想抛出异常,以便找到那个在“spring之外”实例化的地方... - ante.sabo
如果您拥有一个工厂类,Spring将调用'newInstance'而不是构造函数。也就是说,您需要创建一个YourClassFactory类,该类具有方法'newInstance()',而不是在YourClass本身上的静态方法。然后,您可以使用各种技巧来解决构造函数问题,例如,您的工厂类实际上可以是YourClass的公共静态内部类,以便它能够看到后者的私有构造函数。 - bgiles

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接