Spring:如何确保一个类仅由 spring 实例化,而不是由关键字 new 实例化
是否可以保证只有 spring可以实例化一个类,而不能在编译时通过关键字new
实例化? (避免意外实例化)
谢谢!
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!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
如果你想在编译时检测它,构造函数必须是非公共的。
Private 可能太严格了(它使代码分析工具假设它永远不会被调用,甚至可能在某些 IDE 中引起警告),我认为默认值(无修饰符,受包保护)是最好的。如果您希望允许其他包中的子类(但如果不允许直接从该子类调用构造函数,这是不可能的),您可以将其设置为受保护。
确保对构造函数进行适当的注释,以便任何阅读代码的人都清楚为什么构造函数是这样的。
Spring 将毫无问题地调用这个非公共构造函数(从 Spring 1.1 开始,SPR-174 )。
如果这不允许其他任何东西调用你的构造函数,那么强制类的每个用户使用 Spring 依赖注入的想法(所以这个问题的整个目标)是一个好主意还是不好,这是一个完全不同的问题。
如果这是在一个库/框架中(通常只是与 Spring 一起使用),那么限制它的使用方式可能不是一个好主意。但是,如果您知道这些类只会在您的封闭项目中使用,而该项目已经强制使用 Spring,那么它确实可能有意义。
或者,如果您的真正目标只是避免有人创建实例而不使用其依赖项初始化它的可能性,则可以仅使用构造函数依赖项注入。
如果您的目标只是防止意外使用构造函数(开发人员不知道该类应该由 Spring 初始化),但不想完全限制可能性,您可以它是私有的,但还添加静态工厂方法(具有显式名称,如 createManuallyInitializedInstance 或类似名称)。
坏主意:另一种可能的选择是使构造函数公开可用,但不推荐使用它。这样它仍然可以使用(无需诉诸像使用反射这样的黑客),但任何意外使用都会发出警告。但这并不是很干净:这不是弃用的目的。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.我能想到的唯一明显的方法是在运行时通过大规模黑客攻击;毕竟 Spring 可以与普通 Java 一起工作(即在 Spring 中可以完成的任何事情都必须可以通过标准 Java 完成 - 因此不可能作为编译时检查来实现)。所以这就是技巧:
我真的不建议这样做!
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:
I really would not advise doing this!
虽然我可以理解为什么您希望确保类仅由 Spring 实例化,但这实际上不是一个好主意。依赖注入的目的之一是能够在测试期间轻松模拟类。那么,在单元测试期间应该可以手动实例化各种依赖项和模拟依赖项。依赖注入的目的是让生活变得更轻松,因此使用 DI 实例化通常是好的,但在某些情况下使用 new 是完全明智的,并且应该小心不要将任何设计模式或习惯用法推向极端。如果您担心开发人员将在应该使用 DI 的地方使用新的,最好的解决方案是建立代码审查。
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.
我会告诉你为什么不这样做——你将无法模拟你的类。因此你将无法进行单元测试。
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.
不确定 Spring 是否支持这一点,因为我还没有尝试过,并且已经有一段时间没有使用 Spring 了,但是对于另一个 IOC 容器,我曾经采取的一个偷偷摸摸的路线是使一个希望作为注入接口返回的类成为一个抽象类,并让 IOC 容器将其作为派生类实例返回。这样,没有人可以创建该类的实例(因为它是抽象的),并且容器可以返回该类的派生类。
容器本身将生成派生类的定义,因此不必担心有人尝试构造其中之一
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
围绕构造函数的调用编写一个方面,如果不通过 Spring 则中止
Write an aspect around the call to the constructor and abort if not via Spring
不了解 spring,但如果你想对创建新实例有一些控制,你应该将构造函数设为私有,并在你的类中创建 public static YourClass getInstance() 方法,该方法将处理检查和返回该对象的新实例。然后,您可以使用构造函数创建新类,该类将调用 getInstance().. 并将该类交给 Spring。很快你就会发现在春天以外的地方有过“非法”电话......
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...