1. 经典三层架构(Controller-Service-DAO)

三层架构是Java EE(Jakarta EE)时代沉淀下来的经典分层模式,在Spring Boot中得到了完美的落地。其核心目的是实现 “高内聚、低耦合”。
- Controller(控制层/Web层):负责接收前端请求,解析参数,调用业务层,并返回响应数据(JSON/View)。职责:参数校验、数据封装、路由转发。
- Service(业务层):负责核心业务逻辑的处理(如计算、流程编排、事务控制)。职责:业务规则的实现,它是整个应用的核心。
- DAO/Repository(数据访问层):负责与数据库进行交互(CRUD)。职责:封装SQL语句或ORM映射。
最佳实践:面向接口编程(实现低耦合的关键)
每一层都定义接口(契约),通过接口进行交互,而不是直接依赖具体的实现类。这样,当底层实现改变时(如更换数据库),上层代码无需修改。
代码示例(模拟用户登录功能)
1. DAO 层(数据访问)
// 接口定义
public interface UserDao {
User selectUserByUsername(String username);
}
// 实现类,使用 @Repository 注解
@Repository
public class UserDaoImpl implements UserDao {
// 模拟数据库查询,实际中会用 MyBatis 或 JPA
@Override
public User selectUserByUsername(String username) {
System.out.println("DAO: 从数据库查询用户..." + username);
// 模拟返回实体
return new User(1L, username, "encrypted_password_123");
}
}
2. Service 层(业务逻辑)
// 接口定义
public interface UserService {
boolean login(String username, String password);
}
// 实现类,使用 @Service 注解
@Service
public class UserServiceImpl implements UserService {
// 依赖注入 DAO (面向接口编程,不依赖具体实现)
@Autowired
private UserDao userDao;
@Override
public boolean login(String username, String password) {
System.out.println("Service: 执行登录业务逻辑...");
// 1. 调用DAO查数据
User user = userDao.selectUserByUsername(username);
// 2. 业务校验(高内聚:密码比对逻辑封装在此处)
if (user != null && password.equals("123456")) { // 假设明文对比演示
return true;
}
return false;
}
}
3. Controller 层(请求控制)
@RestController
@RequestMapping("/api/users")
public class UserController {
// 依赖注入 Service
@Autowired
private UserService userService;
@PostMapping("/login")
public ResponseEntity<String> handleLogin(@RequestParam String username,
@RequestParam String password) {
System.out.println("Controller: 接收登录请求...");
boolean success = userService.login(username, password);
if (success) {
return ResponseEntity.ok("Login Success");
}
return ResponseEntity.status(401).body("Login Failed");
}
}
如何体现高内聚低耦合?
- 高内聚:Controller只管接收/返回,Service只管业务规则,DAO只管SQL,各司其职。
- 低耦合:Controller中只有
UserService接口引用,Service中只有UserDao接口引用。未来若将UserDaoImpl替换为NewUserDaoImpl,Service层代码无需任何修改(只要实现接口即可)。
2. IOC 控制反转(Inversion of Control)
传统开发:当Service需要使用DAO时,我们需要主动 new UserDaoImpl()。这是正转(主动创建依赖)。
IOC思想:将“创建对象”和“管理对象生命周期”的控制权反转交给Spring容器(IOC容器)。当需要使用对象时,直接从容器中“拿”即可,无需自己再去new。
在Spring Boot启动时,@Springbootapplication这个注解会扫描当前所在包及其子包带有特定注解的类,将它们实例化为Bean,并存放于IOC容器(本质是一个Map结构)中。
3. DI 依赖注入(Dependency Injection)
DI是IOC的具体实现方式。容器在创建Bean时,动态地将它所依赖的其他Bean注入进去。
Spring 支持三种注入方式(推荐使用构造器注入,尤其在Spring 4.3+后,若只有一个构造器可省略@Autowired):
- 构造器注入(推荐,不可变,支持强依赖性)
@Service public class UserServiceImpl implements UserService { private final UserDao userDao; // final 修饰,不可变// 只有一个构造器,可省略 @Autowired public UserServiceImpl(UserDao userDao) { this.userDao = userDao; }} - Setter注入(可选依赖)
@Service public class UserServiceImpl implements UserService { private UserDao userDao;@Autowired public void setUserDao(UserDao userDao) { this.userDao = userDao; }} - 字段注入(最常用但官方不推荐,因为破坏封装性)
java @Service public class UserServiceImpl implements UserService { @Autowired // 通过反射直接赋值,无视 private private UserDao userDao; }
4. Bean对象 与 核心注册注解
Bean是Spring容器管理的对象。将一个普通类交给Spring管理,最基础的是 @Component。
4.1 @Component 及其衍生注解(分层专用)
为了更清晰地标识各层组件,Spring提供了语义更强的衍生注解,它们本质上都是@Component。
| 注解 | 所属层 | 额外功能 |
|---|---|---|
@Controller | Web层 | 配合@RequestMapping处理HTTP请求 |
@RestController | Web层 | @RestController=@Controller + @ResponseBody,专门写API |
@Service | 业务层 | 标记业务逻辑类,支持事务注解@Transactional |
@Repository | 数据访问层(dao层) | 标记DAO类,Spring会自动转换SQL异常为DataAccessException |
4.2 Bean 的作用域(Scope)
默认情况下,Spring管理的Bean是单例(Singleton)的,即整个容器只有一个实例。
可以通过 @Scope 修改:
@Scope("prototype"):每次获取都生成一个新的实例。@Scope("request"):每个HTTP请求生成一个实例(仅Web环境)。
5. 依赖注入的精细控制注解(@Autowired+@Qualifier, @Primary, @Resource)
当容器中同一个接口有多个实现类时,注入就会产生歧义。以下注解用于解决冲突。
场景预设
假设有一个接口 PaymentService,有两个实现类:
@Component
public class AlipayService implements PaymentService { ... }
@Component
public class WechatPayService implements PaymentService { ... }
5.1 @Autowired(Spring原生,按类型注入)
默认根据类型(Type)去容器中寻找Bean。
- 如果容器中只有一个
PaymentService的实现,直接注入成功。 - 如果有两个(AlipayService, WechatPayService),会报错(NoUniqueBeanDefinitionException)。
5.2 @Primary(优先注入)
在多个实现类中,指定默认首选的那个Bean。
@Primary // 标记为默认首选
@Component
public class AlipayService implements PaymentService { ... }
此时,@Autowired 会优先注入 AlipayService。
5.3 @Qualifier("bean对象名")(精确名称注入)
显式指定Bean名称来注入,优先级高于@Primary。
注意:默认Bean名称是类名首字母小写(如 alipayService)。
@Autowired
@Qualifier("wechatPayService") // 明确指定注入微信支付
private PaymentService paymentService;
5.4 @Resource(Java标准,按名称注入)
这是JDK自带的注解(javax.annotation.Resource / jakarta.annotation.Resource),属于JSR-250规范。
- 默认按名称(Name)注入。如果找不到名称匹配的,则按类型匹配。
// 匹配名称为 "alipayService" 的 Bean
@Resource(name = "alipayService")
private PaymentService paymentService;
// 如果不写 name,默认取字段名 'wechatPayService'
@Resource
private PaymentService wechatPayService;
6. @Autowired 与 @Resource 核心区别(面试高频)
| 对比维度 | @Autowired | @Resource |
|---|---|---|
| 所属规范 | Spring 专用(org.springframework.beans.factory.annotation) | Java 标准(JSR-250,jakarta.annotation) |
| 默认注入策略 | ByType(按类型)。找不到类型则按名称回退。 | ByName(按名称)。找不到名称则按类型回退。 |
| 匹配优先级 | 配合 @Primary 或 @Qualifier("name") 解决歧义。 | 通过 name 属性指定,优先级最高。 |
| 支持注入位置 | 构造器、方法、参数、字段(支持构造器注入)。 | 字段、Setter方法(不支持构造器注入)。 |
| required属性 | 支持 required=false,允许注入为null。 | 不支持(必须存在,除非指定lookup)。 |
| 与JDK绑定 | 强耦合Spring,换IOC容器则失效。 | 弱耦合,属于Java标准,理论上可移植到其他非Spring容器。 |
实战建议
- 如果是自己写的业务代码,两者皆可。但为了保持技术栈统一,国内大厂多用
@Autowired+@Qualifier。 - 如果你希望代码脱离Spring框架(比如日后可能迁移到其他DI框架),可使用
@Resource。 - 终极推荐:在Spring Boot 项目中,优先使用构造器注入 +
@Autowired(省略写法),这样能保证依赖不可变且便于单元测试。若有歧义,配合@Qualifier。
// 终极推荐写法(Spring 4.3+ 推荐)
@RestController
public class OrderController {
private final PaymentService paymentService;
// 无需写 @Autowired(只有一个构造器时)
public OrderController(@Qualifier("alipayService") PaymentService paymentService) {
this.paymentService = paymentService;
}
}
7. 附:完整注解对照速查表
| 注解 | 用途 | 所属框架 |
|---|---|---|
@Component | 通用的Bean定义 | Spring |
@Controller / @RestController | Web层Bean定义 | Spring |
@Service | 业务层Bean定义 | Spring |
@Repository | DAO层Bean定义(转换异常) | Spring |
@Autowired | 按类型注入依赖(Spring原生) | Spring |
@Qualifier | 按名称指定注入哪个Bean(配合前者) | Spring |
@Primary | 设定Bean为首选候选者 | Spring |
@Resource | 按名称/类型注入依赖(Java标准) | Jakarta/JDK |
@Scope | 设置Bean作用域(单例/原型等) | Spring |
Comments NOTHING