核心原则(先看这段)
-
把行为(业务)放在 Service 层,事务/缓存/异步等注解一般放在 Service 层,不要把复杂逻辑放到 Controller。
-
首选构造器注入(constructor injection),比字段注入更利于测试与不可变性。
-
注解很多都是"语义化的标记 + 框架在运行时的处理",理解背后的代理/生命周期很重要(例如 @Transactional 基于 AOP 代理,自调用不会触发代理逻辑)。
核心/启动与配置注解
@SpringBootApplication // 等价于 @Configuration + @EnableAutoConfiguration + @ComponentScan(默认扫描启动类包及子包)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
-
@Configuration:Java 配置类(替代 XML)。
-
@Bean:方法级别把返回对象注册为 Spring Bean(可设置 init/destroy)。
-
@ComponentScan:定制扫描包路径(启动类包位置很关键,建议把主类放在根包)。
-
@EnableConfigurationProperties:启用 @ConfigurationProperties 绑定(在 Spring Boot 中,直接将 @ConfigurationProperties 标注成 @Component 也可被扫描到)。
-
@ConfigurationProperties(prefix="..."):批量强类型注入配置(推荐用于复杂/层级配置,支持校验 @Validated)。
示例(YAML + 配置类):
# application.yml
app:
name: demo
timeout: 30
nested:
enabled: true
@Component
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {
@NotBlank
private String name;
private int timeout;
private Nested nested = new Nested();
// getters/setters
public static class Nested { private boolean enabled; /* getter/setter */ }
}
组件(stereotype)注解与区别
-
@Component:通用组件(任何层通用)。
-
@Service:业务层语义化(仅为语义,便于识别)。
-
@Repository:持久层语义化,且启用 异常翻译(把 JPA/Hibernate 的异常翻译为 Spring 的 DataAccessException)。
-
@Controller:MVC 控制器,通常返回视图(与 @ResponseBody 组合可返回 JSON)。
-
@RestController:@Controller + @ResponseBody,更常用于 REST API。
何时用?按层次用对应注解(可读性和工具/监控的好处)。
依赖注入 / Bean 选择 / Scope / 生命周期
-
注入方式对比(推荐):
-
构造器注入(推荐):支持 final,利于单元测试。
-
Setter 注入:有状态或可选依赖时可用。
-
字段注入(@Autowired 在字段上):不推荐(难测、不可 final)。
@Service
public class MyService {
private final UserRepository repo;
public MyService(UserRepository repo) { this.repo = repo; } // 自动注入(Spring 4.3+)
}
-
-
@Autowired(按类型注入),@Qualifier("name") 或 @Primary(用于同类型多个 Bean);
-
@Resource(JSR-250,按名称优先);
-
@Value("${xxx}")(注入单个配置值);
-
@ConfigurationProperties(批量注入复杂配置);
Bean scope:
-
@Scope("singleton")(默认)
-
@Scope("prototype")(每次请求新实例)
-
还有 request、session(web 场景)等
生命周期:
-
@PostConstruct、@PreDestroy(JSR)
-
或在 @Bean(initMethod="...", destroyMethod="...") 指定
-
InitializingBean / DisposableBean(接口)
示例:
@PostConstruct
public void init(){ /* init work / }
@PreDestroy
public void cleanup(){ / release */ }
Web / Controller 相关注解(常用)
-
路由映射:@RequestMapping、@GetMapping、@PostMapping、@PutMapping、@DeleteMapping。
-
参数绑定:@PathVariable、@RequestParam、@RequestBody、@RequestHeader、@CookieValue。
-
返回:@ResponseBody(将返回对象序列化为 JSON) → @RestController 更常用。
-
状态码:@ResponseStatus(HttpStatus.CREATED)。
-
验证:@Valid(与 Bean Validation 联动) + DTO 上加注解 @NotNull/@Size...。
-
异常处理:@ControllerAdvice + @ExceptionHandler(全局异常/统一返回)或 @RestControllerAdvice。
示例:
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public UserDto create(@Valid @RequestBody CreateUserDto dto) { ... }
}
数据访问与事务(重要)
-
@Repository(DAO层) + JPA @Entity / MyBatis 接口等。
-
@Transactional(关键点):
-
放在 Service 层方法上最合适(控制事务边界)。
-
常用属性:propagation、isolation、readOnly、rollbackFor。
-
注意:@Transactional 基于代理(AOP),内部自调用不会触发事务(self-invocation)。
@Service
public class OrderService {
@Transactional(rollbackFor = Exception.class)
public void placeOrder(OrderDto dto){ ... }
}
-
常见坑:
- 在同一个类里面 public methodA() 调 private methodB() 并给 methodB 标注 @Transactional,事务不会生效(因为代理不会拦截内部调用)。解决:把方法拆到另一个 Bean,或使用 AopContext.currentProxy() (不常用)。
AOP(切面)相关注解
-
@Aspect(切面类) + @Before / @After / @AfterReturning / @Around / @Pointcut。
-
典型用途:日志、监控、权限校验、限流、重试等。
@Aspect
@Component
public class LoggingAspect {
@Pointcut("execution(* com.example.service..*(..))")
public void serviceMethods() {}@Around("serviceMethods()") public Object log(ProceedingJoinPoint pjp) throws Throwable { long t = System.currentTimeMillis(); Object ret = pjp.proceed(); System.out.println("took " + (System.currentTimeMillis() - t)); return ret; }
}
异步 / 调度 / 缓存 / 安全(常见功能注解)
-
异步:@EnableAsync(配置类) + @Async(方法) → 返回 void / Future / CompletableFuture。注意自调用失效。
-
调度:@EnableScheduling + @Scheduled(cron = "...")。
-
缓存:@EnableCaching + @Cacheable / @CacheEvict / @CachePut。
-
方法级安全:@PreAuthorize("hasRole('ADMIN')") / @Secured,配合 @EnableMethodSecurity(或旧版 @EnableGlobalMethodSecurity)。
示例(异步):
@Configuration
@EnableAsync
public class AsyncConfig {}
@Service
public class MailService {
@Async
public CompletableFuture<Void> sendAsync(...) { ... }
}
条件化 & 自动配置注解(Spring Boot 自动化)
-
@ConditionalOnProperty(name="...", havingValue="..."):基于配置决定是否装配某 Bean。
-
@ConditionalOnClass:当类在 classpath 上时才生效(常用在 starter/自动配置中)。
-
@ConditionalOnMissingBean:容器中没有指定类型的 Bean 时才创建默认 bean。
-
@Profile("dev"):按 profile 加载(spring.profiles.active=dev)。
示例(有默认实现但允许覆盖):
@Configuration
@ConditionalOnClass(SomeClient.class)
public class AutoConfig {
@Bean
@ConditionalOnMissingBean
public MyClient myClient(){ return new DefaultMyClient(); }
}
测试相关注解(常见)
-
@SpringBootTest:集成测试,启动整个上下文(慢)。
-
@WebMvcTest(controllers = ...):只加载 Web 层(适合 Controller 测试)。
-
@DataJpaTest:只加载 JPA 相关组件(内存数据库)。
-
@MockBean:在 Spring 容器中替换某个 Bean 为 mock。
-
@TestConfiguration:测试专用配置类。
常见坑、注意事项与最佳实践(实战派)
-
优先构造器注入(可用 final,更安全)。
-
@Transactional 放在 public 方法上且不要依赖自调用来触发事务。
-
Controller 层只做参数校验/返回,不做事务处理和复杂业务(业务放 Service)。
-
使用 @ConfigurationProperties 管理复杂配置,@Value 仅用于单值注入。
-
@Repository 会做异常翻译(推荐持久层使用)。
-
@RestControllerAdvice + @ExceptionHandler 做统一异常处理和统一响应结构。
-
生产环境建议对定时任务、异步线程池、缓存策略做合理配置与监控。
-
用 @Profile 管理 dev/test/prod 配置差异。
-
注意 @Async / @Transactional/@Cacheable 等基于代理的注解,均受自调用影响。
速查小表(快速记忆)
-
启动:@SpringBootApplication
-
配置:@Configuration / @Bean / @ConfigurationProperties
-
组件:@Component / @Service / @Repository / @Controller / @RestController
-
注入:@Autowired / @Qualifier / @Value / 构造器注入
-
Web:@GetMapping / @PostMapping / @RequestBody / @PathVariable
-
事务:@Transactional(放 Service)
-
AOP:@Aspect / @Around / @Pointcut
-
异步:@EnableAsync + @Async
-
调度:@EnableScheduling + @Scheduled
-
缓存:@EnableCaching + @Cacheable
-
条件/自动配置:@ConditionalOnProperty / @ConditionalOnClass / @ConditionalOnMissingBean
-
测试:@SpringBootTest / @WebMvcTest / @MockBean
代码示例:Controller → Service → Repository + 配置属性(可拷贝运行)
// Application.java
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
// AppProperties.java
@Component
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {
@NotBlank private String name;
private int timeout;
// getters/setters
}
// UserEntity.java
@Entity
@Table(name = "users")
public class User {
@Id @GeneratedValue private Long id;
private String username;
// getters/setters
}
// UserRepository.java (Spring Data JPA example)
@Repository
public interface UserRepository extends JpaRepository<User, Long> {}
// UserService.java
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) { this.userRepository = userRepository; }
@Transactional
public User createUser(String username) {
User u = new User(); u.setUsername(username);
return userRepository.save(u);
}
@Transactional(readOnly = true)
public User getUser(Long id){ return userRepository.findById(id).orElse(null); }
}
// UserController.java
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
private final AppProperties props;
public UserController(UserService userService, AppProperties props){
this.userService = userService; this.props = props;
}
@PostMapping
public ResponseEntity<User> create(@RequestParam String name){
User u = userService.createUser(name);
return ResponseEntity.status(HttpStatus.CREATED).body(u);
}
@GetMapping("/{id}")
public ResponseEntity<User> get(@PathVariable Long id){
return ResponseEntity.of(Optional.ofNullable(userService.getUser(id)));
}
}