Spring 依赖注入方式详解
一、Spring 依赖注入的多种实现方式
Spring 框架提供了灵活的依赖注入机制,主要包含以下几种方式,每种方式适用于不同的场景:
1. 构造器注入
通过类的构造函数完成依赖注入,具有以下优势:
- 保证依赖的不可变性和初始化时的完整性
- 支持依赖的强制注入(非空校验)
- 便于使用 Lombok 等工具简化代码
代码示例:
@Service
public class UserService {
private final UserRepository userRepository;
private final LogService logService;
// 构造器注入,使用 @Autowired 注解(Spring 4.3+ 可省略)
@Autowired
public UserService(UserRepository userRepository, LogService logService) {
this.userRepository = userRepository;
this.logService = logService;
}
// 使用 Lombok 简化构造器注入
@RequiredArgsConstructor
public class UserServiceLombok {
private final UserRepository userRepository;
private final LogService logService;
}
}
2. Setter 方法注入
通过 setter 方法完成依赖注入,适用于依赖关系可以后期修改的场景:
- 支持可选依赖(可设置默认值)
- 便于通过 XML 配置完成注入
代码示例:
@Service
public class OrderService {
private OrderRepository orderRepository;
// Setter 注入
@Autowired
public void setOrderRepository(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
// 业务逻辑...
}
3. 字段直接注入(注解方式)
通过注解直接在字段上完成注入,代码最为简洁,但存在一些局限性:
- 无法实现依赖的不可变性
- 不利于单元测试(需通过反射模拟依赖)
代码示例:
@Service
public class PaymentService {
// 字段注入,使用 @Autowired 或 @Inject
@Autowired
private PaymentGateway paymentGateway;
@Autowired
private NotificationService notificationService;
// 业务逻辑...
}
4. 接口注入(已过时)
早期 Spring 支持的方式,通过实现特定接口完成注入,目前已很少使用:
// 接口定义
public interface DependencyInjectionAware {
void setDependency(Dependency dependency);
}
// 实现类
public class LegacyService implements DependencyInjectionAware {
private Dependency dependency;
@Override
public void setDependency(Dependency dependency) {
this.dependency = dependency;
}
}
5. 基于 XML 的注入(传统方式)
在 XML 配置文件中显式定义依赖关系,适用于遗留系统:
<bean id="userService" class="com.example.UserService">
<!-- 构造器注入 -->
<constructor-arg ref="userRepository" />
<constructor-arg ref="logService" />
<!-- Setter 注入 -->
<property name="orderService" ref="orderService" />
</bean>
二、利用 Spring 特性减少 if-else 的实践方案
在业务开发中,大量 if-else 会导致代码臃肿、可维护性差。Spring 结合设计模式可有效优化这类场景:
1. 策略模式 + 依赖注入
通过定义策略接口,不同实现类通过注解注册为 Bean,再利用 Map 批量注入,避免条件判断。
实现步骤:
- 定义策略接口:
public interface PaymentStrategy {
String processPayment(PaymentRequest request);
String getPaymentType();
}
- 实现不同策略:
@Service
public class AlipayStrategy implements PaymentStrategy {
@Override
public String processPayment(PaymentRequest request) {
// 支付宝支付逻辑
return "支付宝支付成功,订单号:" + request.getOrderId();
}
@Override
public String getPaymentType() {
return "ALIPAY";
}
}
@Service
public class WechatPayStrategy implements PaymentStrategy {
@Override
public String processPayment(PaymentRequest request) {
// 微信支付逻辑
return "微信支付成功,订单号:" + request.getOrderId();
}
@Override
public String getPaymentType() {
return "WECHAT";
}
}
- 注入策略集合并使用:
@Service
public class PaymentService {
// 通过 Map 注入所有 PaymentStrategy 实现类,key 为 Bean 名称
private final Map<String, PaymentStrategy> strategyMap;
// 或通过类型注入,按支付类型分组
private final Map<String, PaymentStrategy> typeStrategyMap;
@Autowired
public PaymentService(Map<String, PaymentStrategy> strategyMap,
Map<String, PaymentStrategy> typeStrategyMap) {
this.strategyMap = strategyMap;
this.typeStrategyMap = typeStrategyMap;
}
public String process(PaymentRequest request) {
// 方式1:通过 Bean 名称获取策略
PaymentStrategy strategy = strategyMap.get(request.getStrategyName());
// 方式2:通过支付类型获取策略(更推荐)
PaymentStrategy strategy = typeStrategyMap.get(request.getPaymentType());
if (strategy == null) {
throw new IllegalArgumentException("不支持的支付类型:" + request.getPaymentType());
}
return strategy.processPayment(request);
}
}
- 配置支付类型与策略的映射(可选):
@Configuration
public class StrategyConfig {
@Bean
public Map<String, PaymentStrategy> typeStrategyMap(List<PaymentStrategy> strategies) {
return strategies.stream()
.collect(Collectors.toMap(PaymentStrategy::getPaymentType, strategy -> strategy));
}
}
2. 工厂模式 + Spring BeanFactory
通过工厂类管理 Bean 的创建,避免在代码中直接使用 if-else 实例化对象。
代码示例:
@Service
public class ProcessorFactory {
private final ApplicationContext applicationContext;
@Autowired
public ProcessorFactory(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public <T extends Processor> T getProcessor(Class<T> processorClass) {
return applicationContext.getBean(processorClass);
}
// 按名称获取处理器
public Processor getProcessorByName(String name) {
return applicationContext.getBean(name, Processor.class);
}
}
// 使用示例
public class TaskService {
private final ProcessorFactory factory;
@Autowired
public TaskService(ProcessorFactory factory) {
this.factory = factory;
}
public void executeTask(TaskType type) {
// 根据任务类型获取对应的处理器,无需 if-else
Processor processor = factory.getProcessorByName(type.name() + "Processor");
processor.process();
}
}
3. 注解驱动 + 自定义标签
通过自定义注解标记不同的处理逻辑,结合 Spring 的注解扫描机制实现动态分发。
实现步骤:
- 定义自定义注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Service
public @interface Handler {
String type();
}
- 实现处理器并标记注解:
@Handler(type = "USER")
public class UserHandler implements DataHandler {
@Override
public void handle(Data data) {
// 处理用户数据
}
}
@Handler(type = "ORDER")
public class OrderHandler implements DataHandler {
@Override
public void handle(Data data) {
// 处理订单数据
}
}
- 注入并分发处理:
@Service
public class DataService {
private final Map<String, DataHandler> handlerMap;
@Autowired
public DataService(Map<String, DataHandler> handlerMap) {
this.handlerMap = handlerMap;
}
public void processData(Data data) {
// 通过数据类型直接获取处理器
DataHandler handler = handlerMap.get(data.getType());
if (handler != null) {
handler.handle(data);
} else {
throw new UnsupportedOperationException("不支持的数据类型:" + data.getType());
}
}
}
4. Spring Expression Language (SpEL) 动态调用
通过 SpEL 表达式动态调用 Bean 方法,避免硬编码的条件判断。
代码示例:
@Service
public class DynamicService {
private final ApplicationContext applicationContext;
private final SpelExpressionParser parser = new SpelExpressionParser();
@Autowired
public DynamicService(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public Object execute(String beanName, String methodName, Object... args) {
// 动态获取 Bean 并调用方法
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new SpringBeanResolver(applicationContext));
Expression expression = parser.parseExpression(
"{" + beanName + "}." + methodName + "(" +
Arrays.stream(args).map(arg -> "'" + arg + "'").collect(Collectors.joining(",")) + ")"
);
return expression.getValue(context);
}
}
// 使用示例
public class Client {
@Autowired
private DynamicService dynamicService;
public void process(String type, Object data) {
// 动态调用对应类型的处理方法
dynamicService.execute(type + "Processor", "process", data);
}
}
三、不同方案的适用场景对比
| 优化方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 策略模式 + 依赖注入 | 代码结构清晰,扩展性强 | 需要定义接口和多个实现类 | 多分支业务逻辑,如支付、日志处理 |
| 工厂模式 + BeanFactory | 解耦对象创建与使用 | 依赖 Spring 容器接口 | 复杂对象的创建逻辑 |
| 注解驱动 + 自定义标签 | 声明式编程,代码简洁 | 配置成本较高 | 基于类型的处理器分发 |
| SpEL 动态调用 | 高度灵活,无需修改代码 | 性能开销较大,可读性较差 | 动态配置化的业务场景 |
四、最佳实践建议
- 优先使用构造器注入:保证依赖的不可变性,便于单元测试。
- 策略模式是减少 if-else 的首选:结合 Spring 的自动装配功能,实现“开闭原则”。
- 避免过度设计:对于简单的条件判断,直接使用 if-else 可能更高效。
- 结合 Lombok 简化代码:通过
@RequiredArgsConstructor自动生成构造器。 - 利用 Spring Boot 的自动配置:减少手动配置,让框架自动处理依赖关系。
通过合理运用 Spring 的依赖注入机制和设计模式,不仅能提升代码的可维护性和可扩展性,还能有效避免“面条式代码”,使系统架构更加清晰优雅。
1021

被折叠的 条评论
为什么被折叠?



