乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 Spring概述与IoC容器

    • Spring概述与IoC容器
    • Spring Framework 概述
    • IoC 与 DI 核心概念
    • @Configuration 详解
    • @Component 详解
    • @ComponentScan 详解
    • @Import 详解
    • @Profile 详解
    • @PropertySource 详解
    • @Service 详解
    • @Repository 详解
  • 第2章 Bean的定义与依赖注入

    • Bean的定义与依赖注入
    • @Bean 详解
    • @Autowired 详解
    • @Qualifier 详解
    • @Primary 详解
    • @Resource 详解
    • @Inject 详解
    • @Named 详解
    • @Value 详解
    • @Scope 详解
    • @Lazy 详解
  • 第3章 Bean生命周期与作用域

    • Bean生命周期与作用域
    • Bean生命周期概述
    • @PostConstruct
    • @PreDestroy
    • InitializingBean
    • DisposableBean
    • BeanPostProcessor
    • BeanFactoryPostProcessor
  • 第4章 AOP面向切面编程

    • AOP面向切面编程
    • AOP核心概念
    • @EnableAspectJAutoProxy
    • @Aspect
    • @Pointcut
    • @Before
    • @After
    • @AfterReturning
    • @AfterThrowing
    • @Around
  • 第5章 数据访问与事务管理

    • 数据访问与事务管理
    • 数据访问概述
    • @EnableTransactionManagement
    • @Transactional
    • @Transactional 的传播行为
    • @Transactional 的隔离级别
    • @Transactional 的回滚规则
    • @Transactional 的超时与只读属性
    • @TransactionalEventListener
  • 第6章 Spring Boot自动配置基础

    • Spring Boot自动配置基础
    • @SpringBootApplication 注解
    • @EnableAutoConfiguration 注解
    • @ConfigurationProperties 注解
    • @ConditionalOnClass 注解
    • @ConditionalOnMissingBean 注解
    • @ConditionalOnProperty 注解
  • 第7章 从容器到Web: Spring MVC导引

    • Spring MVC 导引
  • 第8章 扩展阅读

    • 扩展阅读
    • Spring 事件机制 — ApplicationEvent / ApplicationListener
    • @EventListener
    • SpEL — Spring 表达式语言
    • 校验 Validation — JSR-303 / JSR-380 Bean Validation
    • 类型转换与数据绑定 — Converter / DataBinder
  • 附录

    • Spring Framework 专业术语
    • Spring 核心知识点
    • Spring 面试高频考点
    • Spring 核心注解速查表

@Service 详解

本章定位:深入理解业务层组件的语义化注解。@Service 与 @Component 功能等价,但通过名称明确传达"业务逻辑层"的意图,是团队代码规范的重要工具。


定义与作用

@Service 是 Spring 提供的业务层组件注解,用于标记包含业务逻辑的类。

解决的痛点:在大型项目中,如果所有层都使用 @Component,开发者无法从注解名称快速判断类的职责边界:

// 痛点:全部用 @Component,语义模糊
@Component
public class StudentDao { }      // 这是数据层还是工具类?

@Component
public class StudentService { }  // 这是业务层还是工具类?

@Component
public class StudentController { } // 这是 Web 层还是定时任务?

@Service 通过语义化命名,让代码的"分层意图"一目了然,配合 AOP 切点表达式时也能精确拦截业务层:

// 解决后:职责清晰
@Repository
public class StudentDao { }       // 数据访问层

@Service
public class StudentService { }   // 业务逻辑层

@Controller
public class StudentController { } // Web 控制层

与 @Component 的关系

@Service 的源码定义揭示了它的本质:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component  // <-- 元注解,功能完全继承
public @interface Service {
    @AliasFor(annotation = Component.class)
    String value() default "";
}

@Service 没有增加任何新功能,它的全部价值在于语义表达和架构规范。


适用位置与常用属性

适用位置

@Service 只能标注在类级别,通常用于 Service 层(业务逻辑层)的类。

@Service
public class StudentService {
    public void enrollStudent(Student student) {
        // 业务逻辑:入学审批、数据校验、通知发送
    }
}

常用属性

属性类型默认值说明
valueString""指定 Bean 的名称(id)。默认使用类名首字母小写
// 显式指定 Bean 名称
@Service("studentBizService")
public class StudentService {
}

核心原理:分层架构中的 AOP 拦截

@Service 的语义价值在 AOP 场景下尤为突出。由于它与 @Component 在扫描机制上等价,Spring 的组件扫描器同样会将其注册为 Bean。但 AOP 的切点表达式可以利用 @Service 的语义进行精准拦截:

切点表达式示例:

@Aspect
@Component
public class ServiceLayerAspect {

    // 拦截所有 @Service 标注的类的方法
    @Pointcut("within(@org.springframework.stereotype.Service *)")
    public void serviceLayer() {}

    @Around("serviceLayer()")
    public Object logServiceMethod(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        long duration = System.currentTimeMillis() - start;
        System.out.printf("[Service 层耗时] %s.%s: %dms%n",
            pjp.getTarget().getClass().getSimpleName(),
            pjp.getSignature().getName(),
            duration);
        return result;
    }
}

完整示例:飞翔科技公司的学生入学服务

场景简述

飞翔科技的学生管理系统中,后端开发小崔负责实现"学生入学"业务。该业务涉及多个步骤:参数校验、保存学生信息、发送通知、记录日志。架构师白歌要求将业务逻辑封装在 @Service 层,与数据访问和 Web 控制解耦。

操作前:业务逻辑散落在各处

// 操作前:StudentController.java —— 业务逻辑混在控制器中
public class StudentController {
    private StudentDao studentDao = new StudentDao();
    private EmailSender emailSender = new EmailSender();

    public void handleEnrollRequest(Student student) {
        // ① 参数校验(本应在 Service 层)
        if (student.getAge() < 18 || student.getAge() > 35) {
            throw new IllegalArgumentException("年龄必须在 18-35 岁之间");
        }
        if (student.getName() == null || student.getName().isEmpty()) {
            throw new IllegalArgumentException("姓名不能为空");
        }

        // ② 业务逻辑(本应在 Service 层)
        student.setStatus("ENROLLED");
        student.setEnrollDate(LocalDate.now());

        // ③ 数据访问(混在一起了)
        studentDao.save(student);

        // ④ 通知发送(混在一起了)
        emailSender.send(student.getEmail(), "入学成功", "欢迎加入飞翔科技学院");

        // ⑤ 日志记录(混在一起了)
        System.out.println("学生 " + student.getName() + " 已入学");
    }
}

痛点:

  1. 控制器既处理 HTTP 请求,又执行业务逻辑,违反单一职责原则
  2. 无法对"入学业务"进行单元测试(必须模拟 HTTP 环境)
  3. 事务边界不清晰:如果 save() 成功但 sendEmail() 失败,数据已写入但通知未发送

操作后:使用 @Service 封装业务逻辑

// 操作后:StudentService.java —— 纯业务层,无 Web 依赖
@Service
public class StudentService {
    private static final Logger logger = LoggerFactory.getLogger(StudentService.class);

    private final StudentDao studentDao;
    private final NotificationManager notificationManager;
    private final AuditLogService auditLogService;

    public StudentService(StudentDao studentDao,
                          NotificationManager notificationManager,
                          AuditLogService auditLogService) {
        this.studentDao = studentDao;
        this.notificationManager = notificationManager;
        this.auditLogService = auditLogService;
    }

    @Transactional
    public EnrollmentResult enrollStudent(Student student) {
        // ① 业务规则校验
        validateStudent(student);

        // ② 执行业务逻辑
        student.setStatus("ENROLLED");
        student.setEnrollDate(LocalDate.now());
        student.setStudentId(generateStudentId());

        // ③ 数据持久化
        studentDao.save(student);

        // ④ 发送通知
        notificationManager.notifyStudent(student,
            "欢迎加入飞翔科技学院,您的学号是:" + student.getStudentId());

        // ⑤ 记录审计日志
        auditLogService.record("ENROLL", student.getStudentId(),
            "学生 " + student.getName() + " 完成入学");

        logger.info("学生 {} 入学成功,学号:{}", student.getName(), student.getStudentId());

        return new EnrollmentResult(student.getStudentId(), "SUCCESS");
    }

    private void validateStudent(Student student) {
        if (student.getAge() < 18 || student.getAge() > 35) {
            throw new BusinessException("年龄必须在 18-35 岁之间");
        }
        if (student.getName() == null || student.getName().trim().isEmpty()) {
            throw new BusinessException("姓名不能为空");
        }
        if (studentDao.existsByName(student.getName())) {
            throw new BusinessException("该姓名已存在,请核实");
        }
    }

    private String generateStudentId() {
        return "FX" + System.currentTimeMillis();
    }
}
// 操作后:StudentController.java —— 只负责请求转发
@Controller
public class StudentController {
    private final StudentService studentService;

    public StudentController(StudentService studentService) {
        this.studentService = studentService;
    }

    @PostMapping("/students")
    public ResponseEntity<EnrollmentResult> enroll(@RequestBody Student student) {
        EnrollmentResult result = studentService.enrollStudent(student);
        return ResponseEntity.ok(result);
    }
}
// 操作后:AppConfig.java
@Configuration
@ComponentScan("com.feixiang.student")
@EnableTransactionManagement
public class AppConfig {
}

运行结果及分析

运行输出:
[main] INFO  o.s.c.a.AnnotationConfigApplicationContext - 
    Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6d06d69c
[main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - 
    Creating shared instance of singleton bean 'studentService'
[main] DEBUG o.s.j.d.DataSourceTransactionManager - 
    Acquired Connection [HikariProxyConnection@1234567890] for JDBC transaction
[main] DEBUG o.s.j.d.DataSourceTransactionManager - 
    Initiating transaction commit
[main] INFO  c.f.s.service.StudentService - 学生 小崔 入学成功,学号:FX1718001234567
[main] INFO  c.f.s.component.EmailSender - [邮件通知] 收件人:xiaocui@learnto.cn...
[main] INFO  c.f.s.component.SmsSender - [短信通知] 手机号:13800138000...
[main] INFO  c.f.s.service.AuditLogService - [审计日志] 操作:ENROLL,对象:FX1718001234567

改进点总结:

维度操作前(逻辑混杂)操作后(@Service 分层)
职责边界控制器既管 HTTP 又管业务控制器只管请求转发,@Service 管业务
可测试性必须模拟 HTTP 环境直接 new StudentService(mockDao, ...) 测试
事务管理无事务,数据可能不一致@Transactional 保证原子性
代码复用入学逻辑只能在控制器使用任何入口(Web、定时任务、MQ)都可调用 Service
AOP 拦截无法精确拦截业务层within(@Service *) 精确切中业务方法

易错场景与面试考点

反例一:在 Service 层直接操作数据库连接

小崔为了"省事",在 @Service 中直接写 JDBC:

// ❌ 错误:@Service 越权操作数据访问细节
@Service
public class StudentService {

    @Autowired
    private DataSource dataSource;  // 直接操作 DataSource

    public Student findById(int id) {
        try (Connection conn = dataSource.getConnection();
             PreparedStatement ps = conn.prepareStatement("SELECT * FROM students WHERE id = ?")) {
            ps.setInt(1, id);
            ResultSet rs = ps.executeQuery();
            // ... 手动映射结果集
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

问题:

  1. 违反分层原则:@Service 应该调用 @Repository,而不是自己写 SQL
  2. 异常处理混乱:原始 SQLException 直接暴露到业务层
  3. 无法利用 Spring 的声明式事务和异常转换机制

纠正:

// ✅ 正确:@Service 调用 @Repository,各司其职
@Service
public class StudentService {
    private final StudentDao studentDao;

    public StudentService(StudentDao studentDao) {
        this.studentDao = studentDao;
    }

    public Student findById(int id) {
        return studentDao.findById(id);  // 委托给数据层
    }
}

@Repository
public class StudentDao {
    private final JdbcTemplate jdbcTemplate;

    public Student findById(int id) {
        return jdbcTemplate.queryForObject(...);  // 数据层处理 SQL
    }
}

反例二:Service 层抛出受检异常导致事务不回滚

小崔在 @Service 方法中抛出了受检异常,发现事务没有回滚:

// ❌ 错误:默认只回滚 RuntimeException,受检异常不回滚
@Service
public class StudentService {

    @Transactional
    public void enrollStudent(Student student) throws IOException {
        studentDao.save(student);
        // 如果这里抛出 IOException,事务不会回滚!
        fileService.writeEnrollLog(student);
    }
}

原理:Spring 的 @Transactional 默认只对 RuntimeException 和 Error 回滚,受检异常(checked exception)默认不回滚。

纠正:显式配置回滚异常类型:

// ✅ 正确:显式声明回滚的异常类型
@Service
public class StudentService {

    @Transactional(rollbackFor = {RuntimeException.class, IOException.class})
    public void enrollStudent(Student student) throws IOException {
        studentDao.save(student);
        fileService.writeEnrollLog(student);
    }
}

反例三:Service 层方法命名不规范导致 AOP 切点失效

白歌配置了一个拦截所有 Service 层方法的日志切面,但发现某些方法没被拦截:

// ❌ 错误:切点表达式按包名匹配,但类没放在 service 包下
@Service
public class StudentHelper {  // 放在 util 包下,而非 service 包
    public void calculateScore() { }
}
@Aspect
@Component
public class LogAspect {
    // 只拦截 service 包下的类
    @Pointcut("execution(* com.feixiang.student.service.*.*(..))")
    public void serviceLayer() {}
}

纠正:@Service 类应统一放在 service 或 biz 包下,保持包结构与分层一致:

// ✅ 正确:包结构与分层一致
// 包路径:com.feixiang.student.service.StudentService
@Service
public class StudentService {
    public void enrollStudent() { }
}

// 包路径:com.feixiang.student.service.ScoreService
@Service
public class ScoreService {
    public void calculateScore() { }
}

面试高频题

Q1:@Service 和 @Component 有什么区别?

功能上完全等价,@Service 的源码上标注了 @Component 作为元注解。区别在于语义意图:@Service 明确标识该类属于业务逻辑层,有助于代码可读性和 AOP 切点表达式的精确匹配。在团队规范中,业务层类应统一使用 @Service 而非 @Component。

Q2:为什么 Service 层通常要声明 @Transactional?

业务方法往往涉及多个数据操作(如"扣减库存→创建订单→扣减余额"),需要保证原子性。@Transactional 声明在 @Service 层而非 @Repository 层,是因为事务边界应由业务操作定义,而非单个 SQL 操作定义。

Q3:@Service 类中可以直接使用 @Autowired 注入 @Repository 吗?

可以,且这是标准做法。但 Spring 官方推荐构造器注入而非字段注入。从 Spring 4.3 开始,如果 @Service 只有一个构造方法,可以省略 @Autowired 注解,Spring 会自动按类型注入构造器参数。

Q4:@Service 的 Bean 默认作用域是什么?

默认是 singleton。即整个 Spring 容器中只有一个 StudentService 实例。如果 StudentService 内部维护了可变状态(如计数器、缓存),需要考虑线程安全问题,或改用 @Scope("prototype")。

上一页
@PropertySource 详解
下一页
@Repository 详解