无需框架管理DI对象:Java 8+无依赖DI实现方法咨询
Absolutely! You don’t need heavy frameworks like Spring to implement dependency injection (DI) in Java 8+. There are several straightforward design patterns and coding practices you can use to achieve clean, maintainable DI without any external dependencies. Let’s break them down:
This is the most fundamental and recommended approach to DI—it aligns perfectly with the Dependency Inversion Principle and makes your dependencies explicit. By passing dependencies through a constructor, you ensure the object is fully initialized and ready to use, with no hidden dependencies.
// Define an abstraction for your dependency public interface UserRepository { User findById(Long id); } // Concrete implementation of the dependency public class JdbcUserRepository implements UserRepository { @Override public User findById(Long id) { // JDBC logic here return new User(id, "John Doe"); } } // Service class that receives dependencies via constructor public class UserService { private final UserRepository userRepository; // Constructor injection guarantees dependency is present public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public User getUser(Long id) { return userRepository.findById(id); } } // Assemble your objects in a "composition root" public class Main { public static void main(String[] args) { UserRepository repo = new JdbcUserRepository(); UserService service = new UserService(repo); User user = service.getUser(1L); System.out.println(user.getName()); } }
Pros: Dependencies are immutable (thanks to final), easy to test (swap in mock implementations), and your code remains transparent about what it needs to function.
Use this when dependencies are optional or might need to be changed after object initialization. It’s less strict than constructor injection, so be cautious with required dependencies (you’ll need to handle null checks).
public class UserService { private UserRepository userRepository; // Setter method for injecting the dependency public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; } public User getUser(Long id) { if (userRepository == null) { throw new IllegalStateException("UserRepository not initialized"); } return userRepository.findById(id); } } // Assembly UserService service = new UserService(); service.setUserRepository(new JdbcUserRepository());
When object creation logic gets complex (e.g., multiple dependencies, conditional instantiation), a factory class can encapsulate all assembly logic in one place. This keeps your business code clean and makes it easy to swap implementations.
public class ServiceFactory { // Create a fully configured UserService public static UserService createUserService() { UserRepository repo = new JdbcUserRepository(); return new UserService(repo); } // Overload for test scenarios (inject mock dependencies) public static UserService createTestUserService(UserRepository testRepo) { return new UserService(testRepo); } } // Usage UserService productionService = ServiceFactory.createUserService(); UserService testService = ServiceFactory.createTestUserService(new MockUserRepository());
If you’re working with legacy code or need to access dependencies in places where constructor/setter injection isn’t feasible, a service locator can provide global access to instances. However, avoid overusing it—it hides dependencies and makes testing harder.
import java.util.HashMap; import java.util.Map; public class ServiceLocator { private static final Map<Class<?>, Object> services = new HashMap<>(); // Initialize services once (composition root) static { services.put(UserRepository.class, new JdbcUserRepository()); services.put(UserService.class, new UserService((UserRepository) services.get(UserRepository.class))); } @SuppressWarnings("unchecked") public static <T> T getService(Class<T> serviceClass) { return (T) services.get(serviceClass); } } // Usage UserService service = ServiceLocator.getService(UserService.class);
Supplier) Java 8’s Supplier interface lets you delay dependency instantiation or create dynamic instances (useful for prototype-scoped objects).
import java.util.function.Supplier; public class UserService { private final Supplier<UserRepository> repoSupplier; // Inject a supplier instead of a direct instance public UserService(Supplier<UserRepository> repoSupplier) { this.repoSupplier = repoSupplier; } public User getUser(Long id) { // Create a new repository instance each time (prototype behavior) UserRepository repo = repoSupplier.get(); return repo.findById(id); } } // Usage: Pass a constructor reference as the supplier UserService service = new UserService(JdbcUserRepository::new);
- Depend on abstractions, not concretes: Use interfaces to decouple your code from specific implementations.
- Keep assembly logic centralized: Use a "composition root" (like your
Mainclass or a factory) to avoid scattering object creation across your codebase. - Prioritize constructor injection: It’s the most robust and testable approach for required dependencies.
内容的提问来源于stack exchange,提问作者Thamiar

