精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

SpringBoot 最常用的50個(gè)注解,都是干貨!

開(kāi)發(fā) 前端
咱都知道,SpringBoot 的核心就是 “簡(jiǎn)化配置”,而注解就是實(shí)現(xiàn)這事兒的 “大功臣”。以前用 Spring 的時(shí)候,寫(xiě) XML 配置能寫(xiě)到手軟,現(xiàn)在一個(gè)注解就能搞定,簡(jiǎn)直是開(kāi)發(fā)者的 “救星”。

兄弟們!今天咱不整那些虛頭巴腦的概念,直接上硬菜 ——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());
        }
    }
}

小提醒

  1. @Transactional 只能用在 public 方法上,非 public 方法(private、protected)加了也沒(méi)用,事務(wù)不生效。
  2. 方法內(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)用(比如自己注入自己)。

  1. 事務(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ī)范。

責(zé)任編輯:姜華 來(lái)源: 石杉的架構(gòu)筆記
相關(guān)推薦

2023-11-10 08:56:49

Springboot常用的注解

2020-09-24 10:00:50

SpringBoo

2020-03-31 14:00:29

Python 開(kāi)發(fā)工具

2011-07-14 17:58:11

編程語(yǔ)言

2024-11-07 16:39:42

SpringBoo常用注解Bean

2009-06-10 21:58:51

Javascript常

2024-03-18 15:04:02

物聯(lián)網(wǎng)通信協(xié)議IOT

2024-02-26 00:00:00

stage函數(shù)進(jìn)度

2022-01-06 09:41:45

區(qū)塊鏈比特幣技術(shù)

2020-06-18 09:55:50

Windows微軟工具

2022-06-15 21:16:49

Java

2023-09-26 12:28:49

IDEA導(dǎo)航

2020-04-26 12:05:53

機(jī)器學(xué)習(xí)工具人工智能

2023-11-27 13:57:00

Linux用法

2023-12-31 12:05:42

Markdown語(yǔ)法鏈接

2024-01-29 18:02:46

2023-09-24 23:26:23

IDE代碼導(dǎo)航

2025-07-21 07:20:11

2024-01-24 13:14:00

Python內(nèi)置函數(shù)工具

2021-09-27 18:07:06

物聯(lián)網(wǎng)協(xié)議物聯(lián)網(wǎng)IOT
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

一区二区三区四区视频在线| 久久久久久成人| 五月婷婷六月合| 日本欧美在线视频免费观看| 国产剧情一区在线| 97国产真实伦对白精彩视频8| 亚洲精品理论片| 国产一区二区三区四区五区3d| 18欧美乱大交hd1984| 国产精品制服诱惑| 进去里视频在线观看| 伊人成综合网| 亚洲色图欧美制服丝袜另类第一页| 中国黄色片一级| 三级在线看中文字幕完整版| 国产精品久久久久影院| 国产一区二区在线网站| 亚洲天堂一二三| 国产精品久久久久久模特| 自拍视频国产精品| 加勒比精品视频| av国产精品| 色综合 综合色| 国产a级黄色大片| 国产高清免费在线播放| av激情综合网| 96pao国产成视频永久免费| 日韩视频在线观看一区| 欧美日本在线| 色婷婷久久av| 中文字幕一区二区人妻在线不卡| 欧美2区3区4区| 欧美色综合网站| 精品这里只有精品| 色综合999| 国产精品护士白丝一区av| 久草一区二区| 丰满肥臀噗嗤啊x99av| 久久精品国产精品亚洲精品| 欧美在线亚洲一区| 精品无码久久久久久久| 影音先锋日韩精品| 日韩在线视频二区| 69精品无码成人久久久久久| 日韩av不卡一区| 欧美mv日韩mv国产网站| 91精产国品一二三产区别沈先生| 欧美精品日日操| 精品国产91久久久久久| 国产一区二区四区| 污视频网站在线免费| 亚洲欧洲另类国产综合| 亚洲精品乱码视频| 国产高清在线看| 久久精品免费在线观看| 欧美日韩高清免费| 你懂的在线网址| 26uuu成人网一区二区三区| 国产欧美亚洲日本| 天天综合网在线观看| 国产成人免费在线| 国产精品对白刺激久久久| 国产三级第一页| 国产原创一区二区三区| 91久热免费在线视频| 国产精品日韩无码| 国产精品一二三| 69174成人网| 午夜精品久久久久久久99| 高清成人免费视频| 久久超碰亚洲| 国内在线免费高清视频| 欧美国产成人精品| 一区二区三区四区不卡| 大片免费在线观看| 亚洲综合一二区| 91成人在线观看喷潮教学| 忘忧草在线影院两性视频| 日本韩国一区二区三区视频| 日本新janpanese乱熟| 久久伊人国产| 日韩欧美在线123| 伊人久久一区二区三区| 性人久久久久| 最新中文字幕亚洲| 精品欧美一区二区久久久久 | 制服丝袜一区二区三区| 日日夜夜精品视频免费观看| 成人香蕉社区| 亚洲视频视频在线| 777777国产7777777| 欧美视频在线观看| 日韩美女写真福利在线观看| 亚洲在线精品视频| 国产福利电影一区二区三区| 精品国产91亚洲一区二区三区www| 飘雪影院手机免费高清版在线观看| 国产精品丝袜久久久久久app| 中文字幕色呦呦| 韩日成人影院| 欧美一二区视频| 亚洲av片不卡无码久久| 91av精品| 欧美在线视频一区二区| 黑人巨大精品欧美| 久久婷婷一区二区| 国产高清视频在线| 天堂影院一区二区| 久久人人爽国产| 中文字幕一区二区人妻电影| 老牛精品亚洲成av人片| 日韩av中文字幕在线| 男人j进女人j| av在线加勒比| 久久久久99精品一区| 精品视频一区二区| 黄网页在线观看| 色综合婷婷久久| www.成人av| 97人人在线| 麻豆精品视频在线| 99在线观看| 亚洲热在线视频| 岛国视频免费在线观看| 99久久久久免费精品国产| 91国产视频在线播放| 亚洲毛片一区二区三区| 久久视频在线观看| 亚洲国产精品影院| 久久这里只有精品99| 久久久久久午夜| 精品无人国产偷自产在线| 国产精品免费在线免费| 人妻精品一区二区三区| 国产嫩草影院久久久久| 国产二级片在线观看| 亚洲一二三区视频| 久久成人18免费网站| 在线观看国产精品视频| 91小视频在线观看| 国产亚洲黄色片| 亚洲精品一二三**| 欧美成人激情视频免费观看| 亚洲无码久久久久| 国产精品久久午夜| 冲田杏梨av在线| 神马影视一区二区| 日本欧美一二三区| 欧美婷婷久久五月精品三区| 亚洲成人综合视频| 国产美女视频免费观看下载软件| 极品av少妇一区二区| 国产成人精品福利一区二区三区| av片在线观看永久免费| 欧美一级高清大全免费观看| 欧美日韩在线观看成人| 国产精品一区久久久久| 神马午夜伦理影院| 一区二区在线视频观看| 欧美国产一区二区三区| 丰满人妻一区二区三区免费视频 | 精品伦精品一区二区三区视频密桃 | 国产一区二区视频在线播放| 在线观看免费91| 精品中文在线| 国产69精品久久久久99| 亚洲 精品 综合 精品 自拍| 91久久精品午夜一区二区| 五月天精品在线| 精品系列免费在线观看| avav在线播放| 日本天堂一区| 国产精品久久久久久久久久久久 | 久久成人18免费网站| av免费在线不卡| 亚洲成在线观看| 国产精品亚洲无码| 免费成人av在线播放| 午夜啪啪福利视频| 久久综合五月婷婷| 国产成人精品电影| 亚洲图区一区| 日韩电影大全免费观看2023年上| 国产污视频网站| 中文字幕综合网| 国产视频精品视频| 日韩精品免费专区| 米仓穗香在线观看| 亚洲人成亚洲精品| 91久久精品美女高潮| 黄在线观看免费网站ktv| 中文字幕欧美视频在线| 亚洲精品成av人片天堂无码| 日韩欧美国产黄色| 伊人在线视频观看| 久久蜜桃av一区精品变态类天堂| 日韩va在线观看| 亚洲在线网站| 欧美 国产 精品| 精品久久美女| 粉嫩av四季av绯色av第一区| 日韩不卡在线| 91精品国产高清久久久久久91| 福利在线播放| 精品粉嫩aⅴ一区二区三区四区 | 久久不射网站| 国产911在线观看| 奇米色欧美一区二区三区| 999国产视频| 91精品国产经典在线观看| 久久久久久久久久久久久久久久久久av | 欧美高清视频www夜色资源网| 五月综合色婷婷| 26uuu精品一区二区三区四区在线| 精品国产乱码久久久久久蜜臀| 成人性视频免费看| www.久久精品| 欧美日韩一区二区三区69堂| 在线播放一区| 亚洲AV无码成人精品一区| 亚洲小说图片视频| 91成人伦理在线电影| 亚洲成a人片| 欧美激情亚洲国产| 日韩精品毛片| 亚洲人成伊人成综合网久久久| 国产丝袜视频在线观看| 欧美中文字幕一区二区三区| 天天操天天摸天天干| 综合分类小说区另类春色亚洲小说欧美 | 91沈先生播放一区二区| 成人黄页网站视频| 欧美自拍大量在线观看| 日本小视频在线免费观看| 一本大道亚洲视频| 亚洲av激情无码专区在线播放| 欧美老女人第四色| 免费精品一区二区| 亚洲成人综合视频| 暗呦丨小u女国产精品| 国产精品拍天天在线| 亚洲AV无码国产成人久久| 9久草视频在线视频精品| 国产精品日日摸夜夜爽| 国产伦精品一区二区三区免费| 美女黄色片视频| 视频一区二区不卡| 无遮挡又爽又刺激的视频 | 久久久久国产精品一区| 成人看av片| 日韩专区在线观看| h网站在线免费观看| 国产一区二区动漫| 国产视频第一区| 亚洲欧美变态国产另类| 嫩草在线播放| 亚洲精品丝袜日韩| 日产精品久久久久久久性色| 日韩精品欧美国产精品忘忧草 | 国产一级片毛片| 色综合激情久久| 中文精品久久久久人妻不卡| 欧美日韩精品三区| 国产精品欧美激情在线| 日韩午夜三级在线| 内射后入在线观看一区| 亚洲国产天堂网精品网站| 天天色综合av| 亚洲精品日韩久久久| 久草福利在线| 这里只有精品在线播放| 国产素人视频在线观看| 欧美大片欧美激情性色a∨久久| 美足av综合网| 欧美亚洲在线视频| 另类中文字幕国产精品| 91网站在线免费观看| 亚洲精品一二三**| 欧美精品在线一区| 婷婷激情图片久久| 成人免费观看在线| 午夜一区不卡| 无需播放器的av| 久久97超碰国产精品超碰| 中文字幕无码毛片免费看| 91在线一区二区三区| 中文字幕第4页| 综合久久国产九一剧情麻豆| 国产精品99无码一区二区| 欧亚一区二区三区| www.五月婷| 亚洲人永久免费| a篇片在线观看网站| 91精品国产91久久久久久吃药| 国产91亚洲精品久久久| 国产成人精品日本亚洲11| 久久99性xxx老妇胖精品| 综合网五月天| 国产日韩一区| 亚洲 国产 图片| 99国产精品国产精品毛片| 精品在线观看一区| 午夜精品久久久久久久久| 中文字幕 自拍偷拍| 亚洲精品在线电影| 在线观看国产原创自拍视频| 久久全国免费视频| 另类一区二区三区| 久久青青草原| 亚洲午夜一级| 一区二区三区韩国| 97精品久久久午夜一区二区三区| 久草福利资源在线| 日韩欧美国产骚| 精品二区在线观看| 视频在线一区二区| 性感女国产在线| 99精品国产一区二区| 久久国产成人精品| 50路60路老熟妇啪啪| 成人免费高清在线观看| 久久av红桃一区二区禁漫| 色一情一乱一乱一91av| 欧美 日韩 国产 在线| www.日韩av.com| 91精品影视| 久久av一区二区三区亚洲| 亚洲视频精品| 中国男女全黄大片| 亚洲免费看黄网站| 97国产精品久久久| 神马国产精品影院av| 成人不卡视频| 欧美在线3区| 亚洲综合精品| 精品夜夜澡人妻无码av| 亚洲成人精品在线观看| www.激情五月.com| 欧美另类老女人| 成人短视频软件网站大全app| 亚洲国产精品综合| 奇米色一区二区| 天堂资源在线视频| 欧美亚洲日本一区| 成a人片在线观看www视频| 国产成人精品久久| 红桃成人av在线播放| 狠狠热免费视频| 欧美高清在线一区| 中文字幕一区二区在线视频| 这里只有视频精品| 97色婷婷成人综合在线观看| 中文字幕成人一区| 国产精品一色哟哟哟| 精品无码久久久久久久| 亚洲第一区第一页| 亚洲欧美小说色综合小说一区| 欧美久久在线| 日韩福利电影在线观看| 精品人妻中文无码av在线| 在线观看视频一区二区欧美日韩| 国产黄色片在线观看| 成人久久一区二区三区| 欧美一区91| 69亚洲乱人伦| 色94色欧美sute亚洲线路一ni| yourporn在线观看中文站| 成人国产在线视频| 一区免费视频| 中文字幕一区二区三区人妻| 91福利视频久久久久| 97视频在线观看网站| 91传媒视频免费| av成人黄色| 免费黄色片网站| 6080国产精品一区二区| 国产网红在线观看| 日本在线播放不卡| 精品一区二区在线观看| 国产一级做a爰片在线看免费| 日韩激情av在线免费观看| 成人黄色毛片| 日韩黄色片在线| 久久久久青草大香线综合精品| 一级黄色短视频| 久久久天堂国产精品女人| 亚洲自拍电影| 婷婷中文字幕在线观看| 五月天亚洲婷婷| wwwww在线观看免费视频| 99久久伊人精品影院| 美女91精品| 欧洲猛交xxxx乱大交3| 日韩精品中文字幕在线| 亚洲欧美专区| 免费在线观看的av网站| 亚洲人成精品久久久久久 | 欧美最新精品| 久久久久久久久影视| 国产日韩精品一区二区三区在线|