精品欧美一区二区三区在线观看 _久久久久国色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)

久久日文中文字幕乱码| 成人免费影院| 成人深夜在线观看| 欧美一级片免费在线| 免费观看a级片| 精品一区二区三区四区五区| 五月婷婷欧美视频| 图片区小说区区亚洲五月| 99久久精品国产一区二区成人| 欧美午夜在线| 国产一区二区三区日韩欧美| 亚洲三级在线观看视频| 日本不卡免费高清视频在线| 中文字幕日本乱码精品影院| 久久久久一区二区三区| 色综合色综合色综合| 先锋影音男人资源| 欧美zozo| 国产91综合网| 国产免费一区视频观看免费| 九热这里只有精品| 97视频精品| 亚洲精品自拍第一页| 日韩成人av免费| 日韩av中字| 亚洲国产一区二区a毛片| 亚洲欧美日韩在线综合 | 欧美mv日韩mv亚洲| 欧美自拍小视频| 国产高清视频色在线www| 中文字幕在线观看不卡| 久久久综合香蕉尹人综合网| 国产suv一区二区| 日本最新不卡在线| 欧美在线一级视频| 动漫精品一区一码二码三码四码| 天天揉久久久久亚洲精品| 亚洲欧美国产精品va在线观看| 性色av浪潮av| 亚洲精品tv| 欧美影视一区在线| 男女高潮又爽又黄又无遮挡| 超清av在线| 亚洲欧美国产高清| 国产免费色视频| yes4444视频在线观看| 2023国产精品视频| 精品久久久久久一区| 亚洲黄色在线免费观看| 国产一区二区网址| 91久久嫩草影院一区二区| 乱子伦一区二区三区| 亚洲一区二区伦理| 国产91精品高潮白浆喷水| 黄色小视频在线免费看| 欧美阿v一级看视频| 美乳少妇欧美精品| 亚洲熟女www一区二区三区| 97国产成人高清在线观看| 色一区av在线| 国产麻豆a毛片| 国产精品88久久久久久| 久久精品小视频| 内射一区二区三区| 欧美在线黄色| 欧美激情网友自拍| 日韩精品一区二区av| 亚洲美女网站| 日本精品久久久久影院| 欧美日韩 一区二区三区| 青青国产91久久久久久| 国产欧美日韩中文字幕| 国产精品久久久久久久成人午夜| 国产一区91精品张津瑜| 7777精品久久久大香线蕉小说 | 亚洲高清一区二| 日本人添下边视频免费| 欧美一区二区三区红桃小说| 日韩精品在线免费| av在线播放中文字幕| 性欧美欧美巨大69| 欧美激情亚洲国产| 久久久久久久久久久久久av| 日日夜夜精品免费视频| 91精品久久久久久久久中文字幕| 国产激情视频在线播放| av高清久久久| 日韩精品久久久| 久久精品视频免费看| 一区二区三区成人| 无码人妻h动漫| 欧美jizz18| 精品久久久久一区| 国产一区二区三区精品在线| 欧美黄色一级视频| 欧美一级大片在线观看| 亚洲一区中文字幕永久在线| 成人禁用看黄a在线| 欧美在线视频一区二区三区| www在线视频| 岛国av在线不卡| 国内自拍第二页| 久草在线综合| 爱福利视频一区| 国产性猛交╳xxx乱大交| 韩国视频一区二区| 欧美激情论坛| 91麻豆免费在线视频| 欧美日韩亚洲激情| 亚洲一区二区三区四区精品| 久久不卡国产精品一区二区| 美日韩丰满少妇在线观看| 日本免费在线观看视频| 国产九色精品成人porny| 欧美精品久久久| 男插女视频久久久| 欧美在线|欧美| 久久人妻一区二区| 亚洲成人免费| 国产精品aaa| 手机在线观看免费av| 亚洲欧美在线高清| 粉嫩虎白女毛片人体| 白白在线精品| 久久香蕉频线观| 日批视频免费观看| 91片黄在线观看| 日韩精品一区二区在线视频| 国产亚洲精彩久久| 亚洲人成电影在线观看天堂色 | 一本久久综合亚洲鲁鲁五月天| 成人不卡免费视频| 精品国产91久久久久久浪潮蜜月| 午夜精品蜜臀一区二区三区免费| 国产乱叫456在线| 国产欧美一区二区精品性| 精品一区二区中文字幕| 国产精品毛片久久久| 欧美不卡视频一区发布| 亚洲午夜激情视频| 欧美国产成人精品| www.四虎成人| 色天下一区二区三区| 国自产精品手机在线观看视频| 99久久精品无免国产免费| 亚洲欧洲另类国产综合| 性chinese极品按摩| 国产一区二区电影在线观看| 欧洲永久精品大片ww免费漫画| 无码精品视频一区二区三区| 亚洲一区二区欧美| 天堂www中文在线资源| 欧美午夜在线视频| 国产伦视频一区二区三区| 七七久久电影网| 日韩欧美电影在线| 麻豆视频在线观看| 国产福利精品一区二区| 日本福利视频网站| 成人三级av在线| 久久男人资源视频| 免费a视频在线观看| 亚洲线精品一区二区三区八戒| 亚洲麻豆一区二区三区| 亚洲精品社区| 欧美一区二区三区精美影视| 日本一区免费网站| 日韩一中文字幕| 亚洲精品久久久狠狠狠爱| 亚洲国产精品欧美一二99| 日本黄色免费观看| 日日夜夜精品视频免费| 中文精品一区二区三区| 亚洲精品国产九九九| 国语自产精品视频在线看一大j8| 欧美孕妇孕交| 欧美午夜一区二区三区免费大片| 国产小视频你懂的| 国产成人啪午夜精品网站男同| 99热亚洲精品| 国产精品片aa在线观看| 国产日韩欧美综合| 国产美女福利在线观看| 亚洲美女中文字幕| 一女二男一黄一片| 亚洲国产精品一区二区www| 黄色aaa视频| 国产剧情在线观看一区二区 | 蜜桃av噜噜一区| 黄色污污在线观看| 欧美sss在线视频| 国产精品视频一| 国产桃色电影在线播放| 原创国产精品91| 精品久久久无码中文字幕| 欧美视频在线免费看| 国产激情无码一区二区三区| 99re这里只有精品6| 少妇网站在线观看| 尤物在线精品| 亚洲国产精品一区在线观看不卡 | 免费在线看电影| 一区二区三区亚洲| 亚洲av永久纯肉无码精品动漫| 色天使久久综合网天天| 男人的天堂久久久| 国产欧美一区二区精品秋霞影院 | 97久久综合区小说区图片区 | 国产激情一区| 奇米影视亚洲狠狠色| 97超碰在线公开在线看免费| 亚洲天堂免费在线| 网站黄在线观看| 3atv一区二区三区| 中文字幕视频免费观看| 欧美日韩国产精品一区二区不卡中文| 免费成人深夜夜行网站| 国产日韩三级在线| 成人免费无码大片a毛片| 久久精品国产亚洲a| 日韩在线一级片| 午夜日本精品| 欧美亚洲视频一区| 日韩成人综合| 日韩免费电影一区二区三区| 人人精品亚洲| 国产伦精品一区二区三区照片91| 亚洲天堂网站| 人人做人人澡人人爽欧美| 国产桃色电影在线播放| 九九热这里只有精品免费看| 色影视在线观看| 日韩精品极品视频免费观看| 亚洲欧美国产高清va在线播放| 9191成人精品久久| 一级片视频网站| 欧美日韩一二三| 亚洲成人av网址| 日本韩国欧美国产| 亚洲自拍一区在线观看| 色综合久久综合| 久久久久女人精品毛片九一| 福利微拍一区二区| 久久久久久久久久久久久久av| 亚洲国产aⅴ天堂久久| 欧美精品一区二区蜜桃| 一区二区三区四区五区视频在线观看| 中文字幕无码日韩专区免费| 中文字幕一区二区三区在线不卡 | 香蕉视频999| 久久超碰97中文字幕| 亚洲18在线看污www麻豆| 精品亚洲成av人在线观看| 一区二区三区 欧美| 蜜臀av亚洲一区中文字幕| 日本www.色| 毛片av中文字幕一区二区| 最新国产黄色网址| 国产精品一区二区三区乱码| 亚洲女人在线观看| 国产91精品一区二区麻豆亚洲| 麻豆tv在线观看| 成人97人人超碰人人99| 丰满少妇在线观看资源站| 久久久国产午夜精品| 欧美18—19性高清hd4k| 国产精品麻豆网站| 亚洲色婷婷一区二区三区| 亚洲综合男人的天堂| www.av麻豆| 在线这里只有精品| 国产理论片在线观看| 精品精品国产高清a毛片牛牛 | 大地资源网在线观看免费官网 | 国产精品99久久免费黑人人妻| 日韩av在线播放中文字幕| 91欧美视频在线| 国产精品69毛片高清亚洲| 91精品啪在线观看国产| 久久精品欧美一区二区三区不卡| 国产wwwwxxxx| 亚洲成人动漫在线观看| 一二三区免费视频| 91精品国产欧美一区二区成人| 韩国av免费在线观看| 亚洲欧美中文另类| 久久99精品久久| 性欧美激情精品| 国产精品.xx视频.xxtv| 国产精品国产三级国产专区53 | 国产一区二区三区丝袜| a级影片在线观看| 人人做人人澡人人爽欧美| 国产精品美女久久久久| 国产尤物91| 999久久久91| 男人日女人bb视频| 国产精品一区二区三区四区| 中文字幕第4页| 亚洲美女屁股眼交| 在线观看日本网站| 日韩欧美在线不卡| 福利片在线观看| 97色在线观看免费视频| 成年永久一区二区三区免费视频| 久久av一区二区三区漫画| 久久中文字幕二区| 久久久久久久久久久视频| 国产精品一卡二卡| 久久丫精品忘忧草西安产品| 亚洲成人午夜影院| 亚洲天堂中文网| 亚洲欧美成人在线| 18aaaa精品欧美大片h| 成人欧美一区二区三区黑人| 亚洲日本三级| 僵尸世界大战2 在线播放| 经典三级在线一区| 女女互磨互喷水高潮les呻吟| 亚洲第一成年网| 亚洲av永久无码国产精品久久| 日韩中文娱乐网| 深夜成人影院| 久久久久国产精品视频| 欧美日韩精品| 青娱乐精品在线| 国产精品对白交换视频| 欧美人一级淫片a免费播放| 亚洲精品久久久一区二区三区 | 国产乱码精品一区二区三区四区| 国产成人一区二区三区别| 韩国女主播成人在线观看| 影音先锋男人在线| 91黄色免费看| 免费a在线观看| 日本精品视频在线播放| 青青草原在线亚洲| 精品无码国模私拍视频| 从欧美一区二区三区| 唐朝av高清盛宴| 日韩欧美国产一区在线观看| 成人看av片| 3d精品h动漫啪啪一区二区| 在线观看免费一区二区| 91欧美一区二区三区| 成人欧美一区二区三区白人| 国产精品久久免费| 久久精品国产一区二区三区| 欧美亚洲综合视频| 在线国产伦理一区| 国产毛片精品一区| 免费中文字幕在线观看| 精品国产成人系列| 第一av在线| 精品午夜一区二区三区| 国产精品久久久久毛片大屁完整版| 日韩av手机在线播放| 精品日韩视频在线观看| 国内av一区二区三区| 国产精品色悠悠| 亚洲欧美偷拍自拍| 中文字幕视频观看| 天天色综合成人网| 国产玉足榨精视频在线观看| 国产精品久久久久久婷婷天堂| 日韩精品中文字幕第1页| 亚洲欧美手机在线| 亚洲国产视频a| 日韩a在线观看| 国产精品电影一区| 亚洲女同中文字幕| 国产精品麻豆入口| 色综合久久九月婷婷色综合| www.在线播放| 91九色对白| 性欧美videos另类喷潮| 貂蝉被到爽流白浆在线观看 | 日韩高清在线一区二区| 亚洲伊人伊色伊影伊综合网| 亚洲日本国产精品| 国产日韩欧美一二三区| 欧美天天视频| 黄免费在线观看| 日韩欧美资源站| 日本成人三级电影| 最新av在线免费观看| 91在线码无精品| 一区二区三区播放| 91国产中文字幕| 999精品视频| 亚洲综合自拍网| 欧美精品电影在线播放| 2020国产在线| 亚洲一区尤物| 99久久伊人久久99| 国产探花精品一区二区| 青青精品视频播放| 欧美搞黄网站| 亚欧精品视频一区二区三区| 欧美精品一区二区蜜臀亚洲|