You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无需框架管理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:

1. Constructor Injection (The Gold Standard)

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.

2. Setter Injection

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());
3. Factory Pattern

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());
4. Service Locator Pattern (Use Sparingly)

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);
5. Leverage Java 8+ Features (e.g., 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);
Key Principles to Follow
  • Depend on abstractions, not concretes: Use interfaces to decouple your code from specific implementations.
  • Keep assembly logic centralized: Use a "composition root" (like your Main class 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:43:03