SpringBoot 最常用的50個(gè)注解,都是干貨!
兄弟們!今天咱不整那些虛頭巴腦的概念,直接上硬菜 ——SpringBoot 最常用的 50 個(gè)注解,全是干活,看完保準(zhǔn)你開(kāi)發(fā)效率能提一大截,再也不用對(duì)著代碼撓頭想 “這個(gè)場(chǎng)景該用啥注解來(lái)著”。
咱都知道,SpringBoot 的核心就是 “簡(jiǎn)化配置”,而注解就是實(shí)現(xiàn)這事兒的 “大功臣”。以前用 Spring 的時(shí)候,寫(xiě) XML 配置能寫(xiě)到手軟,現(xiàn)在一個(gè)注解就能搞定,簡(jiǎn)直是開(kāi)發(fā)者的 “救星”。但注解多了也容易懵,比如 @Controller 和 @RestController 到底啥區(qū)別?@Autowired 和 @Resource 該咋選?別慌,今天我就用最接地氣的話,把這些注解一個(gè)個(gè)掰扯明白,連新手都能看得明明白白。
一、SpringBoot 核心注解(3 個(gè))—— 啟動(dòng)項(xiàng)目全靠它們
這仨注解就像項(xiàng)目的 “發(fā)動(dòng)機(jī)”,沒(méi)有它們,SpringBoot 項(xiàng)目都沒(méi)法啟動(dòng),屬于 “必背必用” 級(jí)別的。
1. @SpringBootApplication
作用:這是個(gè) “大雜燴” 注解,把三個(gè)關(guān)鍵注解打包了 ——@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。加在啟動(dòng)類(lèi)上,SpringBoot 就知道 “哦,這是入口,我要從這開(kāi)始加載配置、掃描組件”。
使用場(chǎng)景:唯一的使用地方就是項(xiàng)目的啟動(dòng)類(lèi),沒(méi)啥可挑的,寫(xiě)啟動(dòng)類(lèi)就必須加它。
代碼示例:
// 這就是咱項(xiàng)目的啟動(dòng)類(lèi),@SpringBootApplication一貼,萬(wàn)事俱備
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
// 啟動(dòng)SpringBoot項(xiàng)目的固定寫(xiě)法,記不住就復(fù)制粘貼
SpringApplication.run(DemoApplication.class, args);
}
}小提醒:別瞎折騰,啟動(dòng)類(lèi)最好放在項(xiàng)目包的最外層,比如你的項(xiàng)目包是 com.demo,啟動(dòng)類(lèi)就放這下面,這樣 @ComponentScan 才能掃到里面所有的組件,不然還得手動(dòng)配置掃描路徑,多此一舉。
2. @SpringBootConfiguration
作用:其實(shí)就是個(gè) “偽裝版” 的 @Configuration,告訴 Spring“這個(gè)類(lèi)是配置類(lèi),里面可能有 @Bean 定義的 bean”。因?yàn)?@SpringBootApplication 已經(jīng)包含它了,所以平時(shí)很少單獨(dú)寫(xiě),但得知道它的存在。
使用場(chǎng)景:一般不單獨(dú)用,除非你想自定義一個(gè)額外的配置類(lèi),并且不想用 @Configuration(不過(guò)沒(méi)必要,直接用 @Configuration 更直觀)。
代碼示例:
// 單獨(dú)用的情況很少見(jiàn),一般這么寫(xiě)
@SpringBootConfiguration
public class CustomConfig {
// 里面可以定義@Bean
@Bean
public UserService userService() {
return new UserService();
}
}3. @EnableAutoConfiguration
作用:SpringBoot 的 “自動(dòng)配置” 核心就在這!它會(huì)根據(jù)你項(xiàng)目里引入的依賴,自動(dòng)幫你配置一些 bean。比如你引入了 spring-boot-starter-web 依賴,它就自動(dòng)配置 DispatcherServlet、Tomcat 這些 Web 相關(guān)的東西,不用你手動(dòng)寫(xiě)配置。
使用場(chǎng)景:同樣被 @SpringBootApplication 包含了,不用單獨(dú)加。但如果想排除某個(gè)自動(dòng)配置(比如不想用 SpringBoot 自帶的數(shù)據(jù)源配置),可以用它的 exclude 屬性。
代碼示例:
// 比如不想用默認(rèn)的數(shù)據(jù)源自動(dòng)配置,就這么排除
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}小提醒:自動(dòng)配置雖然方便,但有時(shí)候會(huì) “幫倒忙”,比如你自己配置了數(shù)據(jù)源,它還瞎自動(dòng)配置,這時(shí)候排除就很有用。
二、配置相關(guān)注解(7 個(gè))—— 管理配置不頭疼
項(xiàng)目里肯定有各種配置,比如數(shù)據(jù)庫(kù)地址、端口號(hào)、自定義參數(shù),這些注解能幫你輕松搞定配置注入,不用再寫(xiě)死在代碼里。
4. @Configuration
作用:標(biāo)記一個(gè)類(lèi)是 “配置類(lèi)”,相當(dāng)于以前的 XML 配置文件。里面可以用 @Bean 注解定義 bean,讓 Spring 管理這些 bean 的創(chuàng)建和生命周期。
使用場(chǎng)景:需要自定義 bean 的時(shí)候就用它,比如配置 RedisTemplate、RestTemplate 這些第三方組件的 bean。
代碼示例:
// 配置RestTemplate的例子,這是咱開(kāi)發(fā)中經(jīng)常寫(xiě)的
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
// 可以在這里配置超時(shí)時(shí)間、攔截器等
return builder
.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(5))
.build();
}
}小提醒:配置類(lèi)里的 @Bean 方法,參數(shù)如果是 Spring 管理的 bean,Spring 會(huì)自動(dòng)注入,比如上面的 RestTemplateBuilder,不用你手動(dòng) new。
5. @Bean
作用:放在配置類(lèi)的方法上,告訴 Spring“這個(gè)方法返回的對(duì)象,你幫我管理起來(lái),以后要用的時(shí)候直接拿”。相當(dāng)于以前 XML 里的標(biāo)簽。
使用場(chǎng)景:自定義 bean 的時(shí)候,比如上面配置 RestTemplate,還有配置線程池、數(shù)據(jù)源這些。
代碼示例:
@Configuration
public class ThreadPoolConfig {
// 定義一個(gè)線程池bean,名字就是方法名threadPoolTaskExecutor
@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5); // 核心線程數(shù)
executor.setMaxPoolSize(10); // 最大線程數(shù)
executor.setQueueCapacity(20); // 隊(duì)列容量
executor.initialize();
return executor;
}
}6. @Value
作用:從配置文件(比如 application.yml 或 application.properties)里讀取單個(gè)配置值,注入到變量里。支持字符串、數(shù)字、布爾值這些類(lèi)型,還能寫(xiě)點(diǎn)簡(jiǎn)單的表達(dá)式。
使用場(chǎng)景:需要單個(gè)配置值的時(shí)候,比如讀取端口號(hào)、數(shù)據(jù)庫(kù)用戶名。
代碼示例:
# application.yml里的配置
server:
port: 8080
custom:
name: 張三
age: 25
isVip: true
@RestController
public class ConfigController {
// 讀取server.port
@Value("${server.port}")
private Integer port;
// 讀取custom.name,還能加默認(rèn)值,要是配置里沒(méi)這個(gè)key,就用默認(rèn)值“李四”
@Value("${custom.name:李四}")
private String name;
// 讀取布爾值
@Value("${custom.isVip}")
private Boolean isVip;
@GetMapping("/config")
public String getConfig() {
return "端口:" + port + ",姓名:" + name + ",是否VIP:" + isVip;
}
}小提醒:@Value 只能讀單個(gè)值,要是配置項(xiàng)多了,一個(gè)個(gè)寫(xiě) @Value 就太麻煩了,這時(shí)候就該下面的 @ConfigurationProperties 出場(chǎng)了。
7. @ConfigurationProperties
作用:批量讀取配置文件里的屬性,綁定到一個(gè)實(shí)體類(lèi)上。比 @Value 方便多了,支持嵌套屬性,還能做數(shù)據(jù)校驗(yàn)。
使用場(chǎng)景:配置項(xiàng)比較多的時(shí)候,比如數(shù)據(jù)庫(kù)配置(url、username、password)、第三方接口配置(url、appKey、appSecret)。
代碼示例:
# application.yml里的數(shù)據(jù)庫(kù)配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo?useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 自定義的第三方接口配置
third:
api:
url: https://api.xxx.com
appKey: abc123
appSecret: xyz789
timeout: 5000
// 數(shù)據(jù)庫(kù)配置實(shí)體類(lèi)
@ConfigurationProperties(prefix = "spring.datasource") // prefix指定配置的前綴
@Component // 要讓Spring掃描到這個(gè)類(lèi),不然沒(méi)法綁定
public class DataSourceProperties {
private String url;
private String username;
private String password;
private String driverClassName; // 注意:配置里是driver-class-name,這里用駝峰命名也能對(duì)應(yīng)上
// getters和setters必須有,不然沒(méi)法注入值
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
// 其他getter和setter省略...
}
// 第三方接口配置實(shí)體類(lèi),還能加數(shù)據(jù)校驗(yàn)
@ConfigurationProperties(prefix = "third.api")
@Component
@Validated // 開(kāi)啟數(shù)據(jù)校驗(yàn)
public class ThirdApiProperties {
@NotBlank(message = "接口URL不能為空") // 校驗(yàn)url不能為null或空字符串
private String url;
@NotBlank(message = "appKey不能為空")
private String appKey;
@NotBlank(message = "appSecret不能為空")
private String appSecret;
@Min(value = 1000, message = "超時(shí)時(shí)間不能小于1000毫秒") // 校驗(yàn)timeout至少1000
private Integer timeout;
// getters和setters省略...
}小提醒:用 @ConfigurationProperties 記得加 @Component(或者在配置類(lèi)里用 @EnableConfigurationProperties 注解),不然 Spring 找不到這個(gè)類(lèi),沒(méi)法綁定配置。另外,getter 和 setter 必須寫(xiě),不然值注入不進(jìn)去。
8. @PropertySource
作用:指定讀取自定義的配置文件,比如不想把所有配置都放 application.yml 里,想單獨(dú)放一個(gè) custom.properties,就用它。
使用場(chǎng)景:配置文件拆分的時(shí)候,比如把自定義配置放 custom.properties,把日志配置放 log.properties。
代碼示例:
# 自定義的custom.properties文件,放在resources目錄下
custom.phone=13800138000
custom.email=zhangsan@xxx.com
@Component
@PropertySource(value = "classpath:custom.properties", encoding = "UTF-8") // 指定文件路徑和編碼
@ConfigurationProperties(prefix = "custom")
public class CustomProperties {
private String phone;
private String email;
// getters和setters省略...
}小提醒:如果是.yml 格式的自定義配置文件,@PropertySource 默認(rèn)不支持,得額外加依賴,所以一般自定義配置用.properties 更方便,或者直接把.yml 配置文件放 application.yml 里用 profiles 拆分。
9. @Profile
作用:指定配置類(lèi)或 bean 在哪個(gè)環(huán)境下生效。比如開(kāi)發(fā)環(huán)境用 dev,測(cè)試環(huán)境用 test,生產(chǎn)環(huán)境用 prod,用 @Profile 就能區(qū)分。
使用場(chǎng)景:多環(huán)境配置的時(shí)候,比如開(kāi)發(fā)環(huán)境用本地?cái)?shù)據(jù)庫(kù),生產(chǎn)環(huán)境用線上數(shù)據(jù)庫(kù)。
代碼示例:
// 開(kāi)發(fā)環(huán)境的數(shù)據(jù)源配置
@Configuration
@Profile("dev") // 只有當(dāng)激活dev環(huán)境時(shí),這個(gè)配置才生效
public class DevDataSourceConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/dev_db?useSSL=false");
config.setUsername("dev_root");
config.setPassword("dev_123456");
return new HikariDataSource(config);
}
}// 生產(chǎn)環(huán)境的數(shù)據(jù)源配置
@Configuration
@Profile("prod") // 激活prod環(huán)境時(shí)生效
public class ProdDataSourceConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://192.168.1.100:3306/prod_db?useSSL=true");
config.setUsername("prod_root");
config.setPassword("prod_xxxxxx");
return new HikariDataSource(config);
}
}激活環(huán)境的方式:在 application.yml 里加spring.profiles.active=dev,或者啟動(dòng)項(xiàng)目時(shí)加參數(shù)--spring.profiles.active=prod。
10. @Conditional
作用:根據(jù)條件判斷配置類(lèi)或 bean 是否生效。比如 “只有當(dāng)某個(gè)類(lèi)存在時(shí),這個(gè) bean 才創(chuàng)建”“只有當(dāng)某個(gè)配置項(xiàng)為 true 時(shí),這個(gè)配置類(lèi)才生效”。它是個(gè)父注解,SpringBoot 提供了很多子注解,比直接用 @Conditional 方便。
常用子注解:
- @ConditionalOnClass:某個(gè)類(lèi)存在時(shí)生效
- @ConditionalOnMissingClass:某個(gè)類(lèi)不存在時(shí)生效
- @ConditionalOnBean:某個(gè) bean 存在時(shí)生效
- @ConditionalOnMissingBean:某個(gè) bean 不存在時(shí)生效
- @ConditionalOnProperty:某個(gè)配置項(xiàng)滿足條件時(shí)生效
使用場(chǎng)景:按需創(chuàng)建 bean,比如 “如果項(xiàng)目里引入了 Redis 依賴,就配置 RedisTemplate;如果沒(méi)引入,就不配置”。
代碼示例:
@Configuration
publicclass RedisConfig {
// 只有當(dāng)RedisTemplate這個(gè)類(lèi)不存在時(shí),才創(chuàng)建這個(gè)自定義的RedisTemplate bean
@Bean
@ConditionalOnMissingBean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 配置序列化方式
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
template.setValueSerializer(serializer);
template.setKeySerializer(new StringRedisSerializer());
return template;
}
}三、Web 開(kāi)發(fā)相關(guān)注解(12 個(gè))—— 寫(xiě)接口全靠它們
Web 開(kāi)發(fā)是 SpringBoot 最常用的場(chǎng)景,這些注解幫你快速寫(xiě) Controller、接收參數(shù)、處理響應(yīng),不用再像以前那樣配置 Servlet 了。
11. @Controller
作用:標(biāo)記一個(gè)類(lèi)是 “控制器”,處理用戶的 HTTP 請(qǐng)求。一般和 @ResponseBody 一起用,返回 JSON 數(shù)據(jù);如果是返回頁(yè)面(比如 Thymeleaf),就不用加 @ResponseBody。
使用場(chǎng)景:所有處理 HTTP 請(qǐng)求的類(lèi)都要加,比如用戶管理 Controller、訂單 Controller。
12. @RestController
作用:@Controller + @ResponseBody 的組合體!加了這個(gè)注解,類(lèi)里所有方法返回的都是 JSON 數(shù)據(jù),不用再每個(gè)方法都加 @ResponseBody,省事兒。
使用場(chǎng)景:寫(xiě) API 接口的時(shí)候(比如前后端分離項(xiàng)目),幾乎全用這個(gè),不用考慮返回頁(yè)面。
代碼示例:
// 前后端分離項(xiàng)目的Controller,直接用@RestController
@RestController
@RequestMapping("/user") // 類(lèi)上的請(qǐng)求路徑前綴,所有方法都帶/user
publicclass UserController {
// 處理GET請(qǐng)求,路徑是/user/{id},{id}是路徑參數(shù)
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
// 模擬從數(shù)據(jù)庫(kù)查數(shù)據(jù)
User user = new User(id, "張三", 25);
return ResponseEntity.ok(user); // 返回200狀態(tài)碼和用戶數(shù)據(jù)
}
// 處理POST請(qǐng)求,路徑是/user,請(qǐng)求體是JSON格式的User對(duì)象
@PostMapping
public ResponseEntity<String> addUser(@RequestBody User user) {
// 模擬保存用戶
return ResponseEntity.status(HttpStatus.CREATED).body("用戶添加成功:" + user.getName());
}
}小提醒:如果你的項(xiàng)目是傳統(tǒng)的 JSP/Thymeleaf 項(xiàng)目,需要返回頁(yè)面,就用 @Controller,然后在方法上用 @ResponseBody 返回 JSON,不用 @ResponseBody 的方法返回頁(yè)面路徑。
13. @RequestMapping
作用:指定 Controller 類(lèi)或方法處理的 HTTP 請(qǐng)求路徑和請(qǐng)求方式(GET、POST、PUT、DELETE 等)。可以加在類(lèi)上(指定前綴),也可以加在方法上(指定具體路徑)。
使用場(chǎng)景:所有需要指定請(qǐng)求路徑的方法都要加,不過(guò)現(xiàn)在更多用下面的 @GetMapping、@PostMapping 這些更具體的注解。
代碼示例:
@RestController
@RequestMapping("/order") // 類(lèi)上的前綴,所有方法路徑都帶/order
publicclass OrderController {
// 處理GET請(qǐng)求,路徑是/order,相當(dāng)于@RequestMapping(value = "/", method = RequestMethod.GET)
@RequestMapping(value = "/", method = RequestMethod.GET)
public List<Order> getOrderList() {
// 模擬查訂單列表
List<Order> orders = Arrays.asList(
new Order(1L, "訂單1", 100.0),
new Order(2L, "訂單2", 200.0)
);
return orders;
}
// 處理POST請(qǐng)求,路徑是/order,相當(dāng)于@RequestMapping(value = "/", method = RequestMethod.POST)
@RequestMapping(value = "/", method = RequestMethod.POST)
public String createOrder() {
return"訂單創(chuàng)建成功";
}
}14. @GetMapping
作用:專(zhuān)門(mén)處理 GET 請(qǐng)求的注解,相當(dāng)于 @RequestMapping (method = RequestMethod.GET)。比 @RequestMapping 更簡(jiǎn)潔,一看就知道是處理 GET 請(qǐng)求的。
使用場(chǎng)景:查詢數(shù)據(jù)的時(shí)候,比如查用戶、查訂單、查列表。
15. @PostMapping
作用:專(zhuān)門(mén)處理 POST 請(qǐng)求的注解,相當(dāng)于 @RequestMapping (method = RequestMethod.POST)。
使用場(chǎng)景:創(chuàng)建數(shù)據(jù)的時(shí)候,比如添加用戶、創(chuàng)建訂單、提交表單。
16. @PutMapping
作用:專(zhuān)門(mén)處理 PUT 請(qǐng)求的注解,相當(dāng)于 @RequestMapping (method = RequestMethod.PUT)。
使用場(chǎng)景:更新數(shù)據(jù)的時(shí)候,比如全量更新用戶信息(修改姓名、年齡、手機(jī)號(hào)等所有字段)。
17. @DeleteMapping
作用:專(zhuān)門(mén)處理 DELETE 請(qǐng)求的注解,相當(dāng)于 @RequestMapping (method = RequestMethod.DELETE)。
使用場(chǎng)景:刪除數(shù)據(jù)的時(shí)候,比如刪除用戶、刪除訂單。
代碼示例(@PutMapping 和 @DeleteMapping):
@RestController
@RequestMapping("/user")
publicclass UserController {
// 處理PUT請(qǐng)求,路徑是/user/{id},全量更新用戶
@PutMapping("/{id}")
publicString updateUser(@PathVariable Long id, @RequestBody User user) {
// 模擬更新,id是路徑參數(shù),user是請(qǐng)求體里的更新數(shù)據(jù)
return"用戶" + id + "更新成功,新信息:" + user.getName();
}
// 處理DELETE請(qǐng)求,路徑是/user/{id},刪除用戶
@DeleteMapping("/{id}")
publicString deleteUser(@PathVariable Long id) {
// 模擬刪除
return"用戶" + id + "刪除成功";
}
}18. @PathVariable
作用:獲取 URL 路徑里的參數(shù),比如路徑是 /user/{id},{id} 就是路徑參數(shù),用 @PathVariable 就能拿到這個(gè) id 的值。
使用場(chǎng)景:路徑里帶參數(shù)的時(shí)候,比如根據(jù) id 查數(shù)據(jù)、根據(jù) id 更新 / 刪除數(shù)據(jù)。
代碼示例:
@GetMapping("/user/{id}/{name}") // 路徑里有兩個(gè)參數(shù):id和name
public String getUserInfo(
@PathVariable Long id, // 直接拿id,參數(shù)名和路徑里的{id}一致
@PathVariable("name") String userName // 路徑里是{name},變量名是userName,用value指定對(duì)應(yīng)關(guān)系
) {
return "id:" + id + ",姓名:" + userName;
}小提醒:如果路徑參數(shù)是可選的,可以加 required = false,比如@PathVariable(required = false) Long id,但路徑里要寫(xiě)成 /user/{id:.*}(適配空值),不然會(huì)報(bào) 404。
19. @RequestParam
作用:獲取 URL 查詢參數(shù)(就是?后面的參數(shù)),比如請(qǐng)求是 /user?page=1&size=10,page 和 size 就是查詢參數(shù),用 @RequestParam 就能拿到。
使用場(chǎng)景:分頁(yè)查詢、條件查詢的時(shí)候,比如查用戶列表,帶 page(頁(yè)碼)、size(每頁(yè)條數(shù))、keyword(關(guān)鍵詞)這些參數(shù)。
代碼示例:
// 請(qǐng)求路徑:/user/list?page=1&size=10&keyword=張
@GetMapping("/user/list")
public String getUserList(
@RequestParam(defaultValue = "1") Integer page, // 默認(rèn)值1,沒(méi)傳page就用1
@RequestParam(defaultValue = "10") Integer size, // 默認(rèn)值10
@RequestParam(required = false) String keyword // 可選參數(shù),沒(méi)傳就是null
) {
return "當(dāng)前頁(yè)碼:" + page + ",每頁(yè)條數(shù):" + size + ",查詢關(guān)鍵詞:" + keyword;
}小提醒:@RequestParam 的 required 默認(rèn)是 true,也就是說(shuō)如果沒(méi)傳這個(gè)參數(shù),會(huì)報(bào) 400 錯(cuò)誤;如果參數(shù)是可選的,一定要設(shè) required = false,或者給個(gè)默認(rèn)值(給了默認(rèn)值,required 會(huì)自動(dòng)變成 false)。
20. @RequestBody
作用:獲取 HTTP 請(qǐng)求體里的 JSON 數(shù)據(jù),把它轉(zhuǎn)換成 Java 對(duì)象。一般和 POST、PUT 請(qǐng)求一起用,因?yàn)?GET 請(qǐng)求沒(méi)有請(qǐng)求體。
使用場(chǎng)景:提交復(fù)雜數(shù)據(jù)的時(shí)候,比如創(chuàng)建用戶需要傳姓名、年齡、手機(jī)號(hào)等多個(gè)字段,就把這些字段封裝成 JSON,放在請(qǐng)求體里,用 @RequestBody 接收。
代碼示例:
// 請(qǐng)求體是JSON:{"name":"李四","age":30,"phone":"13900139000"}
@PostMapping("/user")
publicString addUser(@RequestBody@Valid User user) { // @Valid開(kāi)啟數(shù)據(jù)校驗(yàn)
return"添加用戶成功:" + user.getName() + ",手機(jī)號(hào):" + user.getPhone();
}
// User類(lèi),加了數(shù)據(jù)校驗(yàn)注解
publicclass User {
private Long id;
@NotBlank(message = "姓名不能為空") // 姓名不能為null或空字符串
privateString name;
@Min(value = 18, message = "年齡不能小于18歲") // 年齡至少18
private Integer age;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手機(jī)號(hào)格式不正確") // 手機(jī)號(hào)正則校驗(yàn)
privateString phone;
// getters和setters省略...
}小提醒:用 @RequestBody 的時(shí)候,前端傳的 JSON 字段名要和 Java 對(duì)象的屬性名一致(或者用 @JsonProperty 指定對(duì)應(yīng)關(guān)系),不然值注入不進(jìn)去。另外,加上 @Valid 可以做數(shù)據(jù)校驗(yàn),不符合規(guī)則會(huì)報(bào) 400 錯(cuò)誤。
21. @ResponseBody
作用:把方法的返回值轉(zhuǎn)換成 JSON(或其他格式),寫(xiě)入 HTTP 響應(yīng)體里。以前用 @Controller 的時(shí)候,每個(gè)要返回 JSON 的方法都要加這個(gè)注解,現(xiàn)在用 @RestController 就不用了。
使用場(chǎng)景:用 @Controller 返回 JSON 數(shù)據(jù)的時(shí)候,比如傳統(tǒng)項(xiàng)目里既有頁(yè)面又有 API 接口的情況。
代碼示例:
// 傳統(tǒng)項(xiàng)目,用@Controller,有的方法返回頁(yè)面,有的方法返回JSON
@Controller
@RequestMapping("/test")
publicclass TestController {
// 返回頁(yè)面,不用@ResponseBody,返回的是Thymeleaf模板名
@GetMapping("/page")
publicString getPage() {
return"index"; // 對(duì)應(yīng)resources/templates/index.html
}
// 返回JSON,需要加@ResponseBody
@GetMapping("/json")
@ResponseBody
public Map<String, String> getJson() {
Map<String, String> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
return map;
}
}22. @RequestHeader
作用:獲取 HTTP 請(qǐng)求頭里的信息,比如 User-Agent、Token、Content-Type 這些。
使用場(chǎng)景:需要驗(yàn)證 Token、獲取客戶端信息的時(shí)候,比如接口鑒權(quán),前端把 Token 放在請(qǐng)求頭里,后端用 @RequestHeader 拿出來(lái)驗(yàn)證。
代碼示例:
@GetMapping("/header")
public String getHeader(
@RequestHeader("User-Agent") String userAgent, // 獲取User-Agent(客戶端信息)
@RequestHeader(value = "Token", required = false) String token // 獲取Token,可選
) {
String result = "客戶端信息:" + userAgent;
if (token != null) {
result += ",Token:" + token;
}
return result;
}23. @CookieValue
作用:獲取 HTTP 請(qǐng)求里的 Cookie 值,比如前端設(shè)置的用戶登錄 Cookie。
使用場(chǎng)景:需要從 Cookie 里獲取信息的時(shí)候,比如記住登錄狀態(tài)。
代碼示例:
@GetMapping("/cookie")
public String getCookie(
@CookieValue(value = "username", required = false) String username, // 獲取username Cookie
@CookieValue(value = "sessionId", defaultValue = "unknown") String sessionId // 默認(rèn)值
) {
return "Cookie中的用戶名:" + username + ",SessionId:" + sessionId;
}四、組件掃描與注入相關(guān)注解(8 個(gè))——Spring 管理 Bean 的核心
Spring 的核心是 IOC(控制反轉(zhuǎn)),就是讓 Spring 幫你創(chuàng)建和管理 Bean,這些注解幫你把 Bean 交給 Spring 管理,以及從 Spring 里拿 Bean 來(lái)用。
24. @Component
作用:標(biāo)記一個(gè)類(lèi)是 “Spring 組件”,讓 Spring 在掃描的時(shí)候把它當(dāng)成 Bean 來(lái)管理。這是個(gè)通用注解,下面的 @Service、@Repository、@Controller 都是它的 “子類(lèi)”,只是語(yǔ)義不同。
使用場(chǎng)景:不確定這個(gè)類(lèi)屬于哪個(gè)層(服務(wù)層、數(shù)據(jù)層、控制層)的時(shí)候,就用 @Component。
代碼示例:
// 通用組件,用@Component
@Component
public class CommonUtils {
// 比如一個(gè)工具方法
public String formatDate(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
}25. @Service
作用:@Component 的 “子類(lèi)”,專(zhuān)門(mén)標(biāo)記 “服務(wù)層” 的類(lèi)(比如處理業(yè)務(wù)邏輯的類(lèi))。和 @Component 功能一樣,只是語(yǔ)義更清晰,一看就知道這個(gè)類(lèi)是服務(wù)層的。
使用場(chǎng)景:所有業(yè)務(wù)邏輯類(lèi)都用這個(gè),比如 UserService、OrderService、PayService。
代碼示例:
// 服務(wù)層類(lèi),用@Service
@Service
publicclass UserService {
// 業(yè)務(wù)邏輯方法:根據(jù)id查用戶
public User getUserById(Long id) {
// 這里實(shí)際會(huì)調(diào)用DAO層查數(shù)據(jù)庫(kù),現(xiàn)在模擬一下
if (id == 1L) {
returnnew User(1L, "張三", 25);
} else {
returnnull;
}
}
// 業(yè)務(wù)邏輯方法:添加用戶
public boolean addUser(User user) {
// 模擬保存邏輯
return user != null && user.getName() != null;
}
}26. @Repository
作用:@Component 的 “子類(lèi)”,專(zhuān)門(mén)標(biāo)記 “數(shù)據(jù)訪問(wèn)層” 的類(lèi)(比如 DAO 層、Mapper 層)。除了語(yǔ)義清晰,它還能讓 Spring 自動(dòng)轉(zhuǎn)換數(shù)據(jù)庫(kù)相關(guān)的異常(比如把 JDBC 的異常轉(zhuǎn)換成 Spring 的 DataAccessException)。
使用場(chǎng)景:DAO 層類(lèi),比如 UserDao、OrderDao,不過(guò)現(xiàn)在用 MyBatis 的話,更多用 @Mapper,@Repository 用得少了。
代碼示例:
// 數(shù)據(jù)訪問(wèn)層類(lèi),用@Repository
@Repository
public class UserDao {
// 模擬數(shù)據(jù)庫(kù)查詢
public User findById(Long id) {
return new User(id, "李四", 30);
}
}27. @Autowired
作用:從 Spring 容器里 “自動(dòng)注入” Bean,不用你手動(dòng) new 對(duì)象。默認(rèn)按 “類(lèi)型” 注入,如果同一個(gè)類(lèi)型有多個(gè) Bean,就按 “名稱(chēng)” 匹配(或者用 @Qualifier 指定名稱(chēng))。
使用場(chǎng)景:需要使用其他 Bean 的時(shí)候,比如 Controller 里用 Service,Service 里用 DAO/Mapper。
代碼示例:
@RestController
@RequestMapping("/user")
public class UserController {
// 自動(dòng)注入U(xiǎn)serService,不用new UserService()
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
// 直接用注入的userService調(diào)用方法
return userService.getUserById(id);
}
}@Service
publicclass UserService {
// 同一個(gè)類(lèi)型有多個(gè)Bean的情況,用@Qualifier指定Bean名稱(chēng)
@Autowired
@Qualifier("userDaoImpl1") // 指定注入名稱(chēng)為userDaoImpl1的Bean
private UserDao userDao;
public User getUserById(Long id) {
return userDao.findById(id);
}
}
// UserDao的實(shí)現(xiàn)類(lèi)1
@Repository("userDaoImpl1")
publicclass UserDaoImpl1 implements UserDao {
@Override
public User findById(Long id) {
returnnew User(id, "張三(來(lái)自Impl1)", 25);
}
}
// UserDao的實(shí)現(xiàn)類(lèi)2
@Repository("userDaoImpl2")
publicclass UserDaoImpl2 implements UserDao {
@Override
public User findById(Long id) {
returnnew User(id, "李四(來(lái)自Impl2)", 30);
}
}小提醒:@Autowired 默認(rèn)要求注入的 Bean 必須存在,不然會(huì)報(bào)錯(cuò)。如果允許 Bean 不存在,可以加 required = false,比如@Autowired(required = false) private UserService userService。
28. @Resource
作用:和 @Autowired 類(lèi)似,也是自動(dòng)注入 Bean,但它是 JDK 自帶的注解(javax.annotation.Resource),不是 Spring 的。默認(rèn)按 “名稱(chēng)” 注入,如果名稱(chēng)找不到,再按 “類(lèi)型” 注入。
使用場(chǎng)景:和 @Autowired 一樣,需要注入 Bean 的時(shí)候,有人習(xí)慣用 @Resource,有人習(xí)慣用 @Autowired,看個(gè)人喜好。
代碼示例:
@Service
public class OrderService {
// 按名稱(chēng)注入,name指定Bean名稱(chēng)
@Resource(name = "orderDao")
private OrderDao orderDao;
// 不指定name,默認(rèn)按變量名“userService”匹配Bean名稱(chēng)
@Resource
private UserService userService;
public Order getOrderById(Long id) {
return orderDao.findById(id);
}
}小提醒:@Resource 沒(méi)有 required 屬性,所以如果注入的 Bean 不存在,會(huì)直接報(bào)錯(cuò);而 @Autowired 有 required = false,可以避免這種情況。另外,@Resource 不能和 @Qualifier 一起用,要指定名稱(chēng)就用它自己的 name 屬性。
29. @Qualifier
作用:和 @Autowired 一起用,指定要注入的 Bean 的 “名稱(chēng)”。當(dāng)同一個(gè)類(lèi)型有多個(gè) Bean 的時(shí)候,@Autowired 按類(lèi)型找不到唯一的 Bean,就需要 @Qualifier 指定名稱(chēng)。
使用場(chǎng)景:同一個(gè)接口有多個(gè)實(shí)現(xiàn)類(lèi)的時(shí)候,比如 UserDao 有 UserDaoImpl1 和 UserDaoImpl2,就用 @Qualifier 指定注入哪個(gè)。
代碼示例:前面 @Autowired 的例子里已經(jīng)用過(guò)了,這里再簡(jiǎn)單來(lái)一個(gè):
@Service
publicclass PayService {
// 同一個(gè)PayDao有多個(gè)實(shí)現(xiàn)類(lèi),用@Qualifier指定注入alipayDao
@Autowired
@Qualifier("alipayDao")
private PayDao payDao;
public String pay(Double amount) {
return payDao.pay(amount);
}
}
@Repository("alipayDao")
publicclass AlipayDao implements PayDao {
@Override
public String pay(Double amount) {
return"支付寶支付:" + amount + "元";
}
}
@Repository("wechatPayDao")
publicclass WechatPayDao implements PayDao {
@Override
public String pay(Double amount) {
return"微信支付:" + amount + "元";
}
}30. @Primary
作用:當(dāng)同一個(gè)類(lèi)型有多個(gè) Bean 的時(shí)候,標(biāo)記哪個(gè) Bean 是 “首選” 的。如果用 @Autowired 注入,沒(méi)指定 @Qualifier,就會(huì)優(yōu)先注入加了 @Primary 的 Bean。
使用場(chǎng)景:同一個(gè)類(lèi)型有多個(gè) Bean,且有一個(gè)是默認(rèn)常用的,就用 @Primary 標(biāo)記它。
代碼示例:
@Service
publicclass UserService {
// 沒(méi)加@Qualifier,會(huì)優(yōu)先注入加了@Primary的UserDao
@Autowired
private UserDao userDao;
public User getUserById(Long id) {
return userDao.findById(id);
}
}
// 加了@Primary,是首選Bean
@Repository
@Primary
publicclass UserDaoImpl1 implements UserDao {
@Override
public User findById(Long id) {
returnnew User(id, "張三(首選Impl1)", 25);
}
}
// 沒(méi)加@Primary,不是首選
@Repository
publicclass UserDaoImpl2 implements UserDao {
@Override
public User findById(Long id) {
returnnew User(id, "李四(Impl2)", 30);
}
}31. @Lazy
作用:讓 Bean “延遲初始化”,也就是 Spring 啟動(dòng)的時(shí)候不創(chuàng)建這個(gè) Bean,等到第一次使用的時(shí)候再創(chuàng)建。默認(rèn)情況下,Spring 啟動(dòng)時(shí)會(huì)創(chuàng)建所有單例 Bean(餓漢式),加了 @Lazy 就是懶漢式。
使用場(chǎng)景:Bean 創(chuàng)建比較耗時(shí),或者啟動(dòng)時(shí)用不到的 Bean,比如某些定時(shí)任務(wù)的 Bean、某些只在特定條件下用的 Bean,用 @Lazy 可以加快項(xiàng)目啟動(dòng)速度。
代碼示例:
// 加了@Lazy,Spring啟動(dòng)時(shí)不創(chuàng)建這個(gè)Bean,第一次用的時(shí)候才創(chuàng)建
@Service
@Lazy
publicclass LazyService {
public LazyService() {
// 構(gòu)造方法,啟動(dòng)時(shí)如果沒(méi)創(chuàng)建,這個(gè)日志不會(huì)打印
System.out.println("LazyService被創(chuàng)建了");
}
public String doSomething() {
return"LazyService做事了";
}
}
@RestController
publicclass LazyController {
@Autowired
private LazyService lazyService;
// 第一次訪問(wèn)這個(gè)接口的時(shí)候,才會(huì)創(chuàng)建LazyService,打印構(gòu)造方法的日志
@GetMapping("/lazy")
public String testLazy() {
return lazyService.doSomething();
}
}五、AOP 相關(guān)注解(5 個(gè))—— 切面編程不用愁
AOP(面向切面編程)是 Spring 的核心特性之一,用來(lái)做日志記錄、性能監(jiān)控、權(quán)限校驗(yàn)、事務(wù)管理這些 “橫切關(guān)注點(diǎn)” 的功能。這些注解幫你快速實(shí)現(xiàn) AOP。
32. @Aspect
作用:標(biāo)記一個(gè)類(lèi)是 “切面類(lèi)”,里面定義了切入點(diǎn)(Pointcut)和通知(Advice)。
使用場(chǎng)景:所有需要實(shí)現(xiàn) AOP 的類(lèi)都要加這個(gè)注解,比如日志切面、權(quán)限切面、性能監(jiān)控切面。
小提醒:光加 @Aspect 還不夠,還得讓 Spring 掃描到這個(gè)類(lèi)(加 @Component),或者在配置類(lèi)里用 @EnableAspectJAutoProxy 開(kāi)啟 AOP 支持(不過(guò) SpringBoot 默認(rèn)已經(jīng)開(kāi)啟了,不用手動(dòng)加)。
33. @Pointcut
作用:定義 “切入點(diǎn)”,也就是指定 AOP 要作用在哪些方法上。用表達(dá)式來(lái)描述切入點(diǎn),比如 “所有 Service 層的方法”“某個(gè)包下的所有方法”“加了某個(gè)注解的方法”。
常用切入點(diǎn)表達(dá)式:
- execution:按方法簽名匹配,比如execution(* com.demo.service..(..))(com.demo.service 包下所有類(lèi)的所有方法)
- @annotation:按注解匹配,比如@annotation(com.demo.annotation.Log)(加了 @Log 注解的方法)
- within:按類(lèi)匹配,比如within(com.demo.service.*)(com.demo.service 包下所有類(lèi))
- this:按接口匹配,比如this(com.demo.service.UserService)(實(shí)現(xiàn)了 UserService 接口的類(lèi))
使用場(chǎng)景:定義 AOP 作用的范圍,比如想給所有 Service 方法加日志,就用 execution 表達(dá)式;想給特定方法加日志,就自定義一個(gè)注解,用 @annotation 匹配。
34. @Before
作用:“前置通知”,在切入點(diǎn)方法執(zhí)行之前執(zhí)行。比如在方法執(zhí)行前記錄日志、校驗(yàn)權(quán)限。
35. @After
作用:“后置通知”,在切入點(diǎn)方法執(zhí)行之后執(zhí)行(不管方法有沒(méi)有拋出異常,都會(huì)執(zhí)行)。比如在方法執(zhí)行后清理資源。
36. @AfterReturning
作用:“返回后通知”,在切入點(diǎn)方法正常返回之后執(zhí)行(如果方法拋出異常,就不會(huì)執(zhí)行)。比如在方法返回后記錄返回值。
37. @AfterThrowing
作用:“異常通知”,在切入點(diǎn)方法拋出異常之后執(zhí)行。比如在方法拋異常時(shí)記錄異常信息、發(fā)送告警。
38. @Around
作用:“環(huán)繞通知”,包裹住切入點(diǎn)方法,可以在方法執(zhí)行前、執(zhí)行后、返回后、拋異常后都做處理,功能最強(qiáng)大。比如做性能監(jiān)控(記錄方法執(zhí)行時(shí)間)。
代碼示例(完整 AOP 日志切面):
首先自定義一個(gè) @Log 注解,用來(lái)標(biāo)記需要記錄日志的方法:
// 自定義@Log注解
@Target(ElementType.METHOD) // 注解作用在方法上
@Retention(RetentionPolicy.RUNTIME) // 注解在運(yùn)行時(shí)生效
public @interface Log {
String value() default ""; // 注解的屬性,用來(lái)描述日志內(nèi)容
}然后寫(xiě)切面類(lèi):
@Component // 讓Spring掃描到
@Aspect// 標(biāo)記為切面類(lèi)
publicclass LogAspect {
privatestaticfinal Logger log = LoggerFactory.getLogger(LogAspect.class);
// 定義切入點(diǎn):加了@Log注解的方法
@Pointcut("@annotation(com.demo.annotation.Log)")
public void logPointcut() {}
// 前置通知:方法執(zhí)行前記錄請(qǐng)求信息
@Before("logPointcut() && @annotation(logAnnotation)") // 拿到@Log注解的屬性
public void beforeLog(JoinPoint joinPoint, Log logAnnotation) {
// 獲取請(qǐng)求信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 記錄日志
log.info("【前置通知】請(qǐng)求URL:{},請(qǐng)求方法:{},日志描述:{}",
request.getRequestURL(),
request.getMethod(),
logAnnotation.value());
}
// 返回后通知:方法正常返回后記錄返回值
@AfterReturning(pointcut = "logPointcut()", returning = "result") // returning指定返回值變量名
public void afterReturningLog(JoinPoint joinPoint, Object result) {
log.info("【返回后通知】方法返回值:{}", result);
}
// 異常通知:方法拋異常后記錄異常信息
@AfterThrowing(pointcut = "logPointcut()", throwing = "e") // throwing指定異常變量名
public void afterThrowingLog(JoinPoint joinPoint, Exception e) {
log.error("【異常通知】方法拋出異常:{},異常信息:{}",
e.getClass().getName(),
e.getMessage(),
e); // 打印異常棧
}
// 環(huán)繞通知:記錄方法執(zhí)行時(shí)間
@Around("logPointcut()")
public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis(); // 開(kāi)始時(shí)間
// 執(zhí)行切入點(diǎn)方法(也就是目標(biāo)方法)
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis(); // 結(jié)束時(shí)間
long costTime = endTime - startTime; // 執(zhí)行時(shí)間
log.info("【環(huán)繞通知】方法執(zhí)行時(shí)間:{}毫秒", costTime);
return result; // 返回目標(biāo)方法的返回值
}
}最后在需要記錄日志的方法上加 @Log 注解:
@RestController
@RequestMapping("/user")
publicclass UserController {
@Autowired
private UserService userService;
// 加@Log注解,描述日志內(nèi)容
@Log("根據(jù)ID查詢用戶")
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
@Log("添加用戶")
@PostMapping
publicString addUser(@RequestBody User user) {
boolean success = userService.addUser(user);
return success ? "添加成功" : "添加失敗";
}
}小提醒:環(huán)繞通知里一定要調(diào)用proceed()方法,不然目標(biāo)方法不會(huì)執(zhí)行;另外,proceed()方法可能會(huì)拋出異常,要處理或拋出。
六、事務(wù)管理相關(guān)注解(2 個(gè))—— 數(shù)據(jù)一致性有保障
事務(wù)管理是數(shù)據(jù)庫(kù)操作的核心,比如轉(zhuǎn)賬的時(shí)候,扣錢(qián)和加錢(qián)必須同時(shí)成功或同時(shí)失敗,不然就會(huì)出問(wèn)題。SpringBoot 用這些注解幫你輕松實(shí)現(xiàn)事務(wù)。
39. @Transactional
作用:標(biāo)記方法或類(lèi)需要 “事務(wù)支持”,Spring 會(huì)自動(dòng)管理事務(wù)的提交和回滾。如果方法里拋出未捕獲的異常(默認(rèn)是 RuntimeException 及其子類(lèi)),事務(wù)會(huì)回滾;如果方法正常執(zhí)行,事務(wù)會(huì)提交。
使用場(chǎng)景:所有涉及多步數(shù)據(jù)庫(kù)操作的方法,比如轉(zhuǎn)賬、下單(創(chuàng)建訂單 + 扣庫(kù)存)、批量修改數(shù)據(jù)。
常用屬性:
- propagation:事務(wù)傳播行為,比如 REQUIRED(默認(rèn),沒(méi)有事務(wù)就新建,有就加入)、REQUIRES_NEW(不管有沒(méi)有事務(wù),都新建一個(gè))
- isolation:事務(wù)隔離級(jí)別,比如 DEFAULT(默認(rèn),用數(shù)據(jù)庫(kù)的隔離級(jí)別)、READ_COMMITTED(讀已提交,避免臟讀)
- timeout:事務(wù)超時(shí)時(shí)間,單位秒,超過(guò)時(shí)間沒(méi)執(zhí)行完就回滾
- rollbackFor:指定哪些異常會(huì)觸發(fā)回滾,默認(rèn)是 RuntimeException
- noRollbackFor:指定哪些異常不會(huì)觸發(fā)回滾
代碼示例:
@Service
publicclass OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private StockDao stockDao;
// 下單方法,需要事務(wù)支持:創(chuàng)建訂單和扣庫(kù)存必須同時(shí)成功或失敗
@Transactional(
propagation = Propagation.REQUIRED, // 事務(wù)傳播行為:默認(rèn)
isolation = Isolation.READ_COMMITTED, // 隔離級(jí)別:讀已提交
timeout = 5, // 超時(shí)時(shí)間5秒
rollbackFor = Exception.class // 所有Exception都回滾(默認(rèn)只回滾RuntimeException)
)
public String createOrder(Long productId, Integer count) {
try {
// 1. 創(chuàng)建訂單
Order order = new Order();
order.setProductId(productId);
order.setCount(count);
order.setStatus(0); // 0:未支付
orderDao.insert(order);
log.info("創(chuàng)建訂單成功:{}", order.getId());
// 2. 扣庫(kù)存
int rows = stockDao.decreaseStock(productId, count);
if (rows == 0) {
// 庫(kù)存不足,拋出異常,觸發(fā)事務(wù)回滾
thrownew RuntimeException("商品" + productId + "庫(kù)存不足");
}
log.info("扣庫(kù)存成功:商品{},數(shù)量{}", productId, count);
return"下單成功,訂單ID:" + order.getId();
} catch (Exception e) {
log.error("下單失敗", e);
// 拋出異常,觸發(fā)回滾(如果是checked異常,要手動(dòng)拋,或者rollbackFor指定)
thrownew RuntimeException("下單失敗:" + e.getMessage());
}
}
}小提醒:
- @Transactional 只能用在 public 方法上,非 public 方法(private、protected)加了也沒(méi)用,事務(wù)不生效。
- 方法內(nèi)部調(diào)用帶 @Transactional 的方法,事務(wù)也不生效,比如:
@Service
public class TestService {
public void methodA() {
// 內(nèi)部調(diào)用methodB,methodB的事務(wù)不生效
methodB();
}
@Transactional
public void methodB() {
// 數(shù)據(jù)庫(kù)操作
}
}解決辦法:把 methodB 放到另一個(gè) Service 里,或者用 AOP 代理調(diào)用(比如自己注入自己)。
- 事務(wù)回滾只對(duì)數(shù)據(jù)庫(kù)操作有效,對(duì)非數(shù)據(jù)庫(kù)操作(比如修改緩存、發(fā)送消息)無(wú)效,需要自己處理。
40. @EnableTransactionManagement
作用:開(kāi)啟 Spring 的事務(wù)管理支持。不過(guò)在 SpringBoot 里,只要引入了 spring-boot-starter-jdbc 或 spring-boot-starter-data-jpa 這些依賴,SpringBoot 會(huì)自動(dòng)開(kāi)啟事務(wù)管理,所以不用手動(dòng)加這個(gè)注解。
使用場(chǎng)景:非 SpringBoot 項(xiàng)目(比如純 Spring 項(xiàng)目),需要手動(dòng)加這個(gè)注解開(kāi)啟事務(wù)管理。
代碼示例:
// 純Spring項(xiàng)目的配置類(lèi),需要加@EnableTransactionManagement
@Configuration
@EnableTransactionManagement
public class SpringConfig {
// 配置數(shù)據(jù)源、事務(wù)管理器等
@Bean
public DataSource dataSource() {
// 數(shù)據(jù)源配置
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
returnnewDataSourceTransactionManager(dataSource);
}
}七、異常處理相關(guān)注解(3 個(gè))—— 全局異常不用慌
項(xiàng)目里肯定會(huì)有各種異常,比如用戶輸入錯(cuò)誤、數(shù)據(jù)庫(kù)異常、接口調(diào)用異常,如果每個(gè)方法都寫(xiě) try-catch,太麻煩了。用這些注解可以實(shí)現(xiàn)全局異常處理,統(tǒng)一捕獲和處理異常。
41. @ControllerAdvice
作用:“控制器增強(qiáng)”,用來(lái)定義全局的異常處理、數(shù)據(jù)綁定、模型屬性等。和 @ExceptionHandler 一起用,就能實(shí)現(xiàn)全局異常處理。
使用場(chǎng)景:所有需要全局處理的情況,比如全局異常處理、全局?jǐn)?shù)據(jù)綁定。
42. @RestControllerAdvice
作用:@ControllerAdvice + @ResponseBody 的組合體!用來(lái)處理 REST 接口的全局異常,返回 JSON 格式的異常信息,不用再每個(gè)異常處理方法加 @ResponseBody。
使用場(chǎng)景:前后端分離項(xiàng)目的全局異常處理,幾乎全用這個(gè)。
43. @ExceptionHandler
作用:定義 “異常處理方法”,指定捕獲哪種異常。加在 @ControllerAdvice 或 @RestControllerAdvice 類(lèi)里,就能全局捕獲指定的異常。
使用場(chǎng)景:捕獲特定異常,比如自定義異常、參數(shù)校驗(yàn)異常、數(shù)據(jù)庫(kù)異常。
代碼示例(全局異常處理):
首先自定義一個(gè)業(yè)務(wù)異常類(lèi):
// 自定義業(yè)務(wù)異常
public class BusinessException extends RuntimeException {
private Integer code; // 錯(cuò)誤碼
// 構(gòu)造方法
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
// getter
public Integer getCode() {
return code;
}
}然后寫(xiě)全局異常處理類(lèi):
// 全局異常處理,返回JSON
@RestControllerAdvice
publicclass GlobalExceptionHandler {
// 捕獲自定義的BusinessException
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResult> handleBusinessException(BusinessException e) {
ErrorResult result = new ErrorResult(e.getCode(), e.getMessage());
// 返回400狀態(tài)碼和錯(cuò)誤信息
return ResponseEntity.badRequest().body(result);
}
// 捕獲參數(shù)校驗(yàn)異常(MethodArgumentNotValidException)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResult> handleValidException(MethodArgumentNotValidException e) {
// 獲取校驗(yàn)失敗的信息
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(","));
ErrorResult result = new ErrorResult(400, message);
return ResponseEntity.badRequest().body(result);
}
// 捕獲其他所有未處理的異常(兜底)
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResult> handleException(Exception e) {
log.error("系統(tǒng)異常", e); // 記錄異常棧
ErrorResult result = new ErrorResult(500, "系統(tǒng)繁忙,請(qǐng)稍后再試");
// 返回500狀態(tài)碼
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
// 錯(cuò)誤結(jié)果實(shí)體類(lèi)
@Data
staticclass ErrorResult {
private Integer code; // 錯(cuò)誤碼
private String message; // 錯(cuò)誤信息
public ErrorResult(Integer code, String message) {
this.code = code;
this.message = message;
}
}
}然后在業(yè)務(wù)代碼里拋?zhàn)远x異常:
@Service
publicclass UserService {
public User getUserById(Long id) {
if (id == null || id <= 0) {
// 拋?zhàn)远x異常,錯(cuò)誤碼400,提示信息
thrownew BusinessException(400, "用戶ID不能為空且必須大于0");
}
// 模擬查數(shù)據(jù)庫(kù)
if (id == 1L) {
returnnew User(1L, "張三", 25);
} else {
thrownew BusinessException(404, "用戶不存在");
}
}
}當(dāng)請(qǐng)求 /user/0 的時(shí)候,會(huì)拋 BusinessException,全局異常處理類(lèi)會(huì)捕獲它,返回 JSON:
{
"code": 400,
"message": "用戶ID不能為空且必須大于0"
}小提醒:@RestControllerAdvice 默認(rèn)會(huì)處理所有 Controller 的異常,如果想只處理某個(gè)包下的 Controller,可以加 basePackages 屬性,比如@RestControllerAdvice(basePackages = "com.demo.controller")。
八、其他常用注解(7 個(gè))—— 小注解大用處
這些注解雖然不是核心,但在開(kāi)發(fā)中經(jīng)常用到,能解決很多實(shí)際問(wèn)題。
44. @Scope
作用:指定 Bean 的 “作用域”,也就是 Bean 的創(chuàng)建方式和生命周期。
常用作用域:
- singleton:?jiǎn)卫J(rèn)),Spring 啟動(dòng)時(shí)創(chuàng)建,整個(gè)應(yīng)用只有一個(gè)實(shí)例,所有地方用的都是同一個(gè)。
- prototype:多例,每次獲取 Bean 的時(shí)候(比如 @Autowired 注入、getBean ())都會(huì)創(chuàng)建一個(gè)新的實(shí)例。
- request:請(qǐng)求作用域,每個(gè) HTTP 請(qǐng)求創(chuàng)建一個(gè)新的 Bean,請(qǐng)求結(jié)束后銷(xiāo)毀,只在 Web 項(xiàng)目里有用。
- session:會(huì)話作用域,每個(gè) HTTP Session 創(chuàng)建一個(gè)新的 Bean,會(huì)話結(jié)束后銷(xiāo)毀,只在 Web 項(xiàng)目里有用。
使用場(chǎng)景:
- 一般情況下用 singleton(默認(rèn)),因?yàn)閱卫阅芎谩?/span>
- 如果 Bean 里有狀態(tài)(比如有成員變量會(huì)被修改),就用 prototype,避免多線程安全問(wèn)題。
- Web 項(xiàng)目里,需要保存請(qǐng)求或會(huì)話相關(guān)數(shù)據(jù)的時(shí)候,用 request 或 session。
代碼示例:
// 多例Bean,每次注入或獲取都是新實(shí)例
@Component
@Scope("prototype")
publicclass PrototypeBean {
private Integer count = 0;
public void increment() {
count++;
}
public Integer getCount() {
return count;
}
}
// 測(cè)試多例Bean
@RestController
publicclass ScopeController {
@Autowired
private PrototypeBean prototypeBean1;
@Autowired
private PrototypeBean prototypeBean2;
@GetMapping("/prototype")
public String testPrototype() {
prototypeBean1.increment();
prototypeBean2.increment();
// 兩個(gè)Bean的count都是1,因?yàn)槭遣煌膶?shí)例
return"prototypeBean1 count:" + prototypeBean1.getCount() +
",prototypeBean2 count:" + prototypeBean2.getCount();
}
}45. @RequiredArgsConstructor
作用:Lombok 提供的注解,自動(dòng)生成 “帶所有 final 字段的構(gòu)造方法”。配合 @NonNull 注解,可以生成帶非空字段的構(gòu)造方法。用它可以替代 @Autowired 注入 Bean,讓代碼更簡(jiǎn)潔。
使用場(chǎng)景:需要注入多個(gè) Bean 的時(shí)候,不用寫(xiě)多個(gè) @Autowired,加個(gè) @RequiredArgsConstructor 就行。
代碼示例:
首先要引入 Lombok 依賴(SpringBoot 項(xiàng)目一般都會(huì)加):
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>然后用 @RequiredArgsConstructor 注入 Bean:
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor// 自動(dòng)生成帶final字段的構(gòu)造方法
publicclass UserController {
// 用final修飾,@RequiredArgsConstructor會(huì)生成帶這個(gè)字段的構(gòu)造方法
privatefinal UserService userService;
// 不用加@Autowired,Spring會(huì)通過(guò)構(gòu)造方法注入
privatefinal OrderService orderService;
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
@GetMapping("/order/{userId}")
public List<Order> getOrderByUserId(@PathVariable Long userId) {
return orderService.getOrderByUserId(userId);
}
}小提醒:@RequiredArgsConstructor 生成的是帶 final 字段的構(gòu)造方法,所以注入的 Bean 必須用 final 修飾;如果想注入非 final 的 Bean,可以用 @AllArgsConstructor(生成帶所有字段的構(gòu)造方法),但一般推薦用 final 修飾注入的 Bean,避免被修改。
46. @Slf4j
作用:Lombok 提供的注解,自動(dòng)生成日志對(duì)象(private static final Logger log = LoggerFactory.getLogger (當(dāng)前類(lèi).class)),不用再手動(dòng)寫(xiě)日志對(duì)象了。
使用場(chǎng)景:所有需要打日志的類(lèi),比如 Controller、Service、Dao,用 @Slf4j 能省不少代碼。
代碼示例:
@Service
@Slf4j // 自動(dòng)生成log對(duì)象
publicclass UserService {
public User getUserById(Long id) {
log.info("開(kāi)始查詢用戶,用戶ID:{}", id); // 用log.info打日志
try {
// 模擬查數(shù)據(jù)庫(kù)
if (id == 1L) {
User user = new User(1L, "張三", 25);
log.debug("查詢到用戶信息:{}", user); // debug日志,一般開(kāi)發(fā)環(huán)境開(kāi)啟
return user;
} else {
log.warn("用戶不存在,用戶ID:{}", id); // warn日志,警告信息
return null;
}
} catch (Exception e) {
log.error("查詢用戶失敗,用戶ID:{}", id, e); // error日志,異常信息
throw e;
}
}
}小提醒:日志級(jí)別從低到高是 trace < debug < info < warn < error,生產(chǎn)環(huán)境一般只開(kāi)啟 info 及以上級(jí)別,避免 debug 日志太多影響性能。
47. @NonNull
作用:Lombok 提供的注解,加在方法參數(shù)或字段上,會(huì)自動(dòng)生成非空校驗(yàn)。如果參數(shù)或字段為 null,會(huì)拋出 NullPointerException。
使用場(chǎng)景:需要校驗(yàn)參數(shù)或字段非空的時(shí)候,比如方法參數(shù)不能為 null,字段不能為 null,用 @NonNull 能省掉手動(dòng)寫(xiě)的 if (param == null) throw new NullPointerException () 代碼。
代碼示例:
@Service
publicclass UserService {
// 方法參數(shù)加@NonNull,自動(dòng)校驗(yàn)非空
public boolean addUser(@NonNull User user) {
if (user.getName() == null) {
returnfalse;
}
// 模擬保存
returntrue;
}
// 字段加@NonNull,Lombok會(huì)在構(gòu)造方法和setter里加非空校驗(yàn)
@Data
@AllArgsConstructor
publicstaticclass User {
private Long id;
@NonNull// 姓名非空
private String name;
private Integer age;
}
}
// 測(cè)試
@RestController
publicclass UserController {
@Autowired
private UserService userService;
@PostMapping("/user")
public String addUser(@RequestBody UserService.User user) {
// 如果user是null,調(diào)用addUser的時(shí)候會(huì)拋NullPointerException
boolean success = userService.addUser(user);
return success ? "添加成功" : "添加失敗";
}
}48. @Data
作用:Lombok 提供的注解,自動(dòng)生成類(lèi)的 getter、setter、toString、equals、hashCode 方法,不用再手動(dòng)寫(xiě)這些模板代碼了,能極大減少代碼量。
使用場(chǎng)景:所有實(shí)體類(lèi)(比如 User、Order、Product),用 @Data 能省掉大量的 getter 和 setter 代碼。
代碼示例:
// 用@Data,自動(dòng)生成getter、setter、toString、equals、hashCode
@Data
publicclassUser {
private Long id;
private String name;
private Integer age;
private String phone;
private Date createTime;
}
// 測(cè)試
publicclassTest {
public static void main(String[] args) {
User user = new User();
user.setId(1L); // 自動(dòng)生成的setter
user.setName("張三");
System.out.println(user.getName()); // 自動(dòng)生成的getter
System.out.println(user); // 自動(dòng)生成的toString
}
}小提醒:@Data 包含了 @Getter、@Setter、@ToString、@EqualsAndHashCode、@RequiredArgsConstructor,如果不想生成其中某個(gè)方法,可以單獨(dú)用對(duì)應(yīng)的注解,比如 @Getter、@Setter。
49. @NoArgsConstructor
作用:Lombok 提供的注解,自動(dòng)生成 “無(wú)參構(gòu)造方法”。
使用場(chǎng)景:實(shí)體類(lèi)需要無(wú)參構(gòu)造方法的時(shí)候,比如 Jackson 反序列化 JSON 的時(shí)候需要無(wú)參構(gòu)造,JPA 實(shí)體類(lèi)也需要無(wú)參構(gòu)造。
50. @AllArgsConstructor
作用:Lombok 提供的注解,自動(dòng)生成 “帶所有字段的構(gòu)造方法”。
使用場(chǎng)景:需要?jiǎng)?chuàng)建包含所有字段的對(duì)象的時(shí)候,比如用構(gòu)造方法注入 Bean(配合 @RequiredArgsConstructor),或者快速創(chuàng)建實(shí)體類(lèi)對(duì)象。
代碼示例(@NoArgsConstructor 和 @AllArgsConstructor):
@Data
@NoArgsConstructor// 無(wú)參構(gòu)造
@AllArgsConstructor// 全參構(gòu)造
publicclass Order {
private Long id;
private Long userId;
private Double amount;
private Integer status;
privateDate createTime;
}
// 測(cè)試
publicclass Test {
publicstaticvoid main(String[] args) {
// 無(wú)參構(gòu)造
Order order1 = new Order();
order1.setId(1L);
order1.setUserId(100L);
// 全參構(gòu)造
Order order2 = new Order(2L, 101L, 200.0, 0, newDate());
System.out.println(order2);
}
}總結(jié)
好了,以上就是 SpringBoot 最常用的 50 個(gè)注解,從核心啟動(dòng)、配置管理、Web 開(kāi)發(fā),到 Bean 注入、AOP、事務(wù)、異常處理,再到 Lombok 的實(shí)用注解,基本覆蓋了日常開(kāi)發(fā)的所有場(chǎng)景。
這些注解不是孤立的,比如寫(xiě)一個(gè)接口,會(huì)用到 @RestController、@GetMapping,注入 Service 會(huì)用到 @Autowired 或 @RequiredArgsConstructor,處理業(yè)務(wù)邏輯會(huì)用到 @Service,涉及數(shù)據(jù)庫(kù)操作會(huì)用到 @Transactional,打日志會(huì)用到 @Slf4j。把這些注解融會(huì)貫通,你的 SpringBoot 開(kāi)發(fā)效率會(huì)提升一大截,代碼也會(huì)更簡(jiǎn)潔、更規(guī)范。































