Bean类内部实例化其他类的可行性、机制、优劣及优化方案探讨
Hey there! Let's tackle your questions step by step—they're all centered around Spring Bean management, which is a common pain point when you're getting comfortable with dependency injection. Let's break it down:
Absolutely, you can manually instantiate another class inside a Spring Bean (like using new MyOtherClass() directly). But this defeats most of the benefits of using Spring's dependency injection (DI) container. The manually created object won't be managed by Spring—no lifecycle hooks, no dependency injection for it, no adherence to Spring's scoping rules (like singleton), etc.
To avoid this practice (and follow Spring best practices), here's what you should do:
- Inject the dependency instead: Register the target class (
MyOtherClassin your example) as a Spring Bean using annotations like@Component,@Service, or@Repository, or by defining an explicit@Beanmethod in a configuration class. Then use constructor injection (preferred) or@Autowiredto get that managed Bean into your original class. - Use Spring-managed factories if dynamic creation is needed: If you need to create instances on the fly, use a Spring-managed factory Bean instead of manual
newcalls. This keeps Spring in control of object lifecycle. - Avoid hardcoded
newcalls for managed objects: Any class that needs dependencies, lifecycle management, or to be swapped out (like for testing) should be handled by Spring's container.
Let's unpack the scenario you described: Your main class is registered as a Spring Bean, but it internally instantiates another class (MyOtherClass) manually. Here's the breakdown:
How does this mechanism work?
When Spring creates your registered Bean (e.g., MyBean), it calls its constructor (or uses a factory method). Inside that constructor, your manual new MyOtherClass() creates an instance completely outside Spring's oversight:
- Spring has no idea this
MyOtherClassinstance exists, so it won't inject any dependencies thatMyOtherClassmight require. - Spring won't apply lifecycle callbacks like
@PostConstructor@PreDestroyto this instance. - If
MyBeanis prototype-scoped, every newMyBeaninstance will get a brand newMyOtherClassinstance; ifMyBeanis singleton, you'll have oneMyOtherClassinstance tied to that singleton. But none of this follows Spring's built-in scoping rules.
Is it safe to instantiate new objects during Bean injection?
Technically, it's "safe" in that it won't crash your application—but it's a bad practice with significant downsides. The risks include:
- Unmanaged dependencies: If
MyOtherClassneeds its own dependencies (like a database repository), you'll have to instantiate those manually too, leading to tight coupling and messy, hard-to-maintain code. - Poor testability: You can't easily mock
MyOtherClassin unit tests forMyBean, since the instance is hardcoded withnew. - No lifecycle control: You can't leverage Spring's features to initialize or clean up
MyOtherClassinstances. - Scoping mismatches: If
MyOtherClassshould be a singleton butMyBeanis prototype, you'll end up with multipleMyOtherClassinstances which might not align with your intended behavior.
Pros and cons of this manual instantiation approach
Pros
- Quick and trivial for simple classes with no dependencies or lifecycle requirements.
- No need to register the secondary class as a Bean (though this is a short-term convenience that leads to long-term technical debt).
Cons
- Tight coupling:
MyBeanis directly tied toMyOtherClass's implementation, making it impossible to swapMyOtherClassfor another implementation (like a mock) without modifyingMyBean's code. - Unmanaged objects: As mentioned, no Spring DI, lifecycle management, or scoping for the manually created instance.
- Maintainability issues: As your application grows, manually managing object creation becomes error-prone and difficult to debug.
Is registering MyOtherClass as a Bean and using @Autowired feasible? (And is @Autowired + getMyclass() redundant?)
Absolutely—this is the standard, recommended way to handle dependencies in Spring. Let's assume your revised code looks something like this:
// MyOtherClass now registered as a Bean @Component public class MyOtherClass { // ... class logic ... } // MyBean with constructor injection (preferred approach) @Component public class MyBean { private final MyOtherClass myOtherClass; public MyBean(MyOtherClass myOtherClass) { this.myOtherClass = myOtherClass; } // Getter method public MyOtherClass getMyclass() { return myOtherClass; } }
Or if you're using field injection (less preferred, but still valid):
@Component public class MyBean { @Autowired private MyOtherClass myOtherClass; public MyOtherClass getMyclass() { return myOtherClass; } }
Now, about redundancy:
@Autowired(or constructor injection) is how you tell Spring to inject the managedMyOtherClassBean intoMyBean. It's essential for letting Spring handle the dependency.- The
getMyclass()method is only necessary if other classes need to accessmyOtherClassthroughMyBean. IfMyBeandoesn't need to expose this dependency to the outside, the getter is redundant and can be removed. - A quick best practice: Constructor injection is always better than field injection. It makes dependencies explicit, allows the field to be
final(for immutability), and makes unit testing easier (you can pass a mockMyOtherClassdirectly in the constructor without needing Spring's container).
内容的提问来源于stack exchange,提问作者user6219476

