为什么service层需要声明service接口,然后再去实现该接口
面向接口编程
符合设计原则
便于测试
多实现类支持
代码可读性和维护性
解耦和灵活性
便于测试
符合设计模式
多实现支持
代码清晰
增加代码量
开发效率降低
过度设计
学习成本
适合使用接口的场景
不适合使用接口的场景
示例
public interface UserService {
User getUserById(Long id);
void saveUser(User user);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User getUserById(Long id) {
return userMapper.selectById(id);
}
@Override
public void saveUser(User user) {
userMapper.insert(user);
}
}
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUserById(id);
}
}
方面 | 使用接口 | 不使用接口 |
---|---|---|
代码结构 | 更清晰,符合设计原则 | 更简单,适合小型项目 |
扩展性 | 支持多实现,易于扩展 | 扩展性较差 |
测试 | 便于Mock测试 | 测试时需要依赖具体实现 |
代码量 | 增加接口文件,代码量较多 | 代码量较少 |
适用场景 | 大型项目、复杂业务、需要多实现的场景 | 小型项目、简单业务、不需要多实现的场景 |
在实际开发中,是否使用接口需要根据项目规模、业务复杂度和团队习惯来决定。对于大多数Spring Boot项目,使用接口是一种良好的实践,尤其是在需要扩展性和可测试性的场景下。
在Spring Boot中,如果一个接口有多个实现类(例如WxPayService
和AliPayService
都实现了PayService
接口),直接使用@Autowired
自动注入会导致Spring无法确定应该注入哪个实现类,从而抛出NoUniqueBeanDefinitionException
异常
为了解决这个问题,Spring提供了多种方式来明确指定使用哪个实现类。
@Qualifier
注解可以明确指定要注入的Bean名称。
示例:
@Service("wxPayService")
public class WxPayService implements PayService {
// 微信支付实现
}
@Service("aliPayService")
public class AliPayService implements PayService {
// 支付宝支付实现
}
在Controller中使用@Qualifier指定具体的实现类:
@RestController
@RequestMapping("/pay")
public class PayController {
@Autowired
@Qualifier("wxPayService") // 指定注入微信支付实现
private PayService wxPayService;
@Autowired
@Qualifier("aliPayService") // 指定注入支付宝支付实现
private PayService aliPayService;
@PostMapping("/wx")
public String wxPay() {
return wxPayService.pay();
}
@PostMapping("/ali")
public String aliPay() {
return aliPayService.pay();
}
}
@Primary
注解可以指定一个默认的实现类。当有多个实现类时,Spring会优先注入被标记为@Primary
的Bean。
示例:
@Service
@Primary // 标记为默认实现
public class WxPayService implements PayService {
// 微信支付实现
}
@Service
public class AliPayService implements PayService {
// 支付宝支付实现
}
在Controller中直接使用@Autowired
时,会默认注入WxPayService
:
@RestController
@RequestMapping("/pay")
public class PayController {
@Autowired // 默认注入WxPayService
private PayService payService;
@PostMapping("/pay")
public String pay() {
return payService.pay();
}
}
如果需要使用AliPayService
,仍然可以通过@Qualifier
指定:
@Autowired
@Qualifier("aliPayService")
private PayService aliPayService;
@Resource
是Java标准注解,可以通过name属性指定具体的Bean名称。
示例:
@Service("wxPayService")
public class WxPayService implements PayService {
// 微信支付实现
}
@Service("aliPayService")
public class AliPayService implements PayService {
// 支付宝支付实现
}
在Controller中使用@Resource
:
@RestController
@RequestMapping("/pay")
public class PayController {
@Resource(name = "wxPayService") // 指定注入微信支付实现
private PayService wxPayService;
@Resource(name = "aliPayService") // 指定注入支付宝支付实现
private PayService aliPayService;
@PostMapping("/wx")
public String wxPay() {
return wxPayService.pay();
}
@PostMapping("/ali")
public String aliPay() {
return aliPayService.pay();
}
}
如果希望根据某些条件动态选择实现类,可以使用@Conditional
注解或自定义条件注解。
示例:
@Service
@ConditionalOnProperty(name = "payment.type", havingValue = "wx")
public class WxPayService implements PayService {
// 微信支付实现
}
@Service
@ConditionalOnProperty(name = "payment.type", havingValue = "ali")
public class AliPayService implements PayService {
// 支付宝支付实现
}
在application.properties中配置:
payment.type=wx
在Controller中直接注入:
@RestController
@RequestMapping("/pay")
public class PayController {
@Autowired
private PayService payService; // 根据配置动态注入WxPayService或AliPayService
@PostMapping("/pay")
public String pay() {
return payService.pay();
}
}
如果支付方式需要动态切换(例如根据用户选择),可以使用工厂模式来管理不同的实现类。
示例:
@Service
public class PayServiceFactory {
@Autowired
private Map<String, PayService> payServiceMap; // 所有PayService实现类会自动注入
public PayService getPayService(String type) {
return payServiceMap.get(type);
}
}
在Controller中使用工厂:
@RestController
@RequestMapping("/pay")
public class PayController {
@Autowired
private PayServiceFactory payServiceFactory;
@PostMapping("/pay")
public String pay(@RequestParam String type) {
PayService payService = payServiceFactory.getPayService(type + "PayService");
return payService.pay();
}
}
方式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
@Qualifier | 明确指定实现类,简单直接 | 需要手动指定Bean名称 | 实现类较少且固定的场景 |
@Primary | 默认注入,减少配置 | 只能指定一个默认实现 | 有默认实现类的场景 |
@Resource | Java标准注解,支持按名称注入 | 功能与@Qualifier类似 | 需要按名称注入的场景 |
@Conditional | 动态选择实现类,灵活 | 需要额外配置 | 根据条件动态选择实现类的场景 |
工厂模式 | 动态切换实现类,扩展性强 | 需要额外编写工厂类 | 实现类较多且需要动态切换的场景 |
根据你的具体需求选择合适的方式。如果支付方式是固定的,推荐使用@Qualifier
或@Primary
;如果需要动态切换,推荐使用工厂模式。