javaweb-Day06-三层架构&IOC&DI

TJCcc 发布于 20 天前 19 次阅读


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):

  1. 构造器注入(推荐,不可变,支持强依赖性) @Service public class UserServiceImpl implements UserService { private final UserDao userDao; // final 修饰,不可变// 只有一个构造器,可省略 @Autowired public UserServiceImpl(UserDao userDao) { this.userDao = userDao; }}
  2. Setter注入(可选依赖) @Service public class UserServiceImpl implements UserService { private UserDao userDao;@Autowired public void setUserDao(UserDao userDao) { this.userDao = userDao; }}
  3. 字段注入(最常用但官方不推荐,因为破坏封装性)
    java @Service public class UserServiceImpl implements UserService { @Autowired // 通过反射直接赋值,无视 private private UserDao userDao; }

4. Bean对象 与 核心注册注解

Bean是Spring容器管理的对象。将一个普通类交给Spring管理,最基础的是 @Component

4.1 @Component 及其衍生注解(分层专用)

为了更清晰地标识各层组件,Spring提供了语义更强的衍生注解,它们本质上都是@Component

注解所属层额外功能
@ControllerWeb层配合@RequestMapping处理HTTP请求
@RestControllerWeb层@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 / @RestControllerWeb层Bean定义Spring
@Service业务层Bean定义Spring
@RepositoryDAO层Bean定义(转换异常)Spring
@Autowired按类型注入依赖(Spring原生)Spring
@Qualifier按名称指定注入哪个Bean(配合前者)Spring
@Primary设定Bean为首选候选者Spring
@Resource按名称/类型注入依赖(Java标准)Jakarta/JDK
@Scope设置Bean作用域(单例/原型等)Spring
唯有极致沉淀,才能造就辉煌。
最后更新于 2026-08-05