欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring Boot 常用注解整理(最全收藏版)

 更新時(shí)間:2025年05月14日 11:31:13   作者:Gurucyy  
本文系統(tǒng)整理了常用的 Spring/Spring Boot 注解,按照功能分類進(jìn)行介紹,每個(gè)注解都會涵蓋其含義、提供來源、應(yīng)用場景以及代碼示例,幫助開發(fā)者深入理解和快速檢索,感興趣的朋友跟隨小編一起看看吧

Spring & Spring Boot 常用注解整理

現(xiàn)代的 Spring 與 Spring Boot 應(yīng)用大量使用注解來簡化配置、管理組件和實(shí)現(xiàn)各種框架功能。本文系統(tǒng)整理了常用的 Spring/Spring Boot 注解,按照功能分類進(jìn)行介紹。每個(gè)注解都會涵蓋其含義、提供來源、應(yīng)用場景以及代碼示例,幫助開發(fā)者深入理解和快速檢索。

一、Spring Boot 核心注解

@SpringBootApplication

簡介: @SpringBootApplication 是 Spring Boot 應(yīng)用的主入口注解。它標(biāo)注在啟動類上,表示這是一個(gè) Spring Boot 應(yīng)用。該注解由 Spring Boot 提供(位于 org.springframework.boot.autoconfigure 包),本質(zhì)上是一個(gè)組合注解,包含了 Spring Framework 和 Spring Boot 的關(guān)鍵配置注解。

作用與場景: 使用 @SpringBootApplication 標(biāo)記主類后,Spring Boot 會自動進(jìn)行以下配置:

  • 配置類聲明: 包含了 @SpringBootConfiguration(其本身是 @Configuration 的特化),因此該類被視為配置類,可定義 Bean。
  • 組件掃描: 內(nèi)含 @ComponentScan,會自動掃描該類所在包及其子包下的組件(被諸如 @Component、@Service、@Controller 等注解標(biāo)記的類),將它們注冊為 Spring 容器中的 Bean。
  • 自動配置: 內(nèi)含 @EnableAutoConfiguration,根據(jù)類路徑下依賴自動配置 Spring Boot 應(yīng)用。例如,若 classpath 中存在 HSQLDB 數(shù)據(jù)庫依賴,則會自動配置內(nèi)存數(shù)據(jù)庫等。開發(fā)者無需手動編寫大量配置即可啟動應(yīng)用。

使用示例: 創(chuàng)建一個(gè) Spring Boot 主啟動類,在類上添加 @SpringBootApplication 注解,并編寫 main 方法啟動應(yīng)用:

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

上述代碼中,MyApplication 類由 @SpringBootApplication 注解標(biāo)記為應(yīng)用入口。運(yùn)行 SpringApplication.run 后,Spring Boot 將引導(dǎo)啟動內(nèi)嵌服務(wù)器、初始化 Spring 容器,自動掃描組件并完成配置。

注: @SpringBootApplication 提供了屬性用于定制,如 exclude 可排除特定的自動配置類。如果需要禁用某些自動配置,可以使用例如 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) 來排除。

二、Spring 容器與組件注冊注解

這一類注解用于將類注冊為 Spring 容器管理的組件或定義配置,以取代傳統(tǒng)的 XML 配置文件,實(shí)現(xiàn)注解驅(qū)動的裝配。

@Component

簡介: @Component 是一個(gè)通用的組件注解,由 Spring Framework 提供(org.springframework.stereotype.Component)。它用于將一個(gè)普通的 Java 類標(biāo)識為 Spring 容器中的 Bean。被標(biāo)注的類在組件掃描時(shí)會被發(fā)現(xiàn)并實(shí)例化,由容器統(tǒng)一管理生命周期。

作用與場景: 當(dāng)某個(gè)類不好歸類到特定層時(shí),可以使用 @Component 進(jìn)行標(biāo)注。典型場景如工具類、通用邏輯處理類等。使用 @Component 后,無需在 XML 中聲明 bean,Spring 會根據(jù)配置的掃描路徑自動將其注冊。提供模塊: Spring Context 模塊提供對組件掃描和 @Component 注解的支持。

使用示例: 定義一個(gè)組件類并演示注入:

@Component
public class MessageUtil {
    public String getWelcomeMessage() {
        return "Welcome to Spring!";
    }
}
// 使用組件
@Service
public class GreetingService {
    @Autowired
    private MessageUtil messageUtil;
    public void greet() {
        System.out.println(messageUtil.getWelcomeMessage());
    }
}

上例中,MessageUtil 類通過 @Component 標(biāo)記成為容器 Bean,GreetingService 中使用 @Autowired(詳見后文)將其注入,最后調(diào)用其方法。

@Service

簡介: @Service@Component 的一種特化,用于標(biāo)注業(yè)務(wù)邏輯層的組件(Service層)。它位于 Spring 框架的 org.springframework.stereotype 包。

作用與場景: 在分層架構(gòu)中,服務(wù)層類使用 @Service 注解,使代碼含義更語義化。盡管行為上和 @Component 相同(被掃描注冊為 Bean),@Service 強(qiáng)調(diào)該類承擔(dān)業(yè)務(wù)服務(wù)職責(zé)。提供模塊: Spring Context,同屬于組件模型的一部分。

使用示例:

@Service
public class OrderService {
    public void createOrder(Order order) {
        // 業(yè)務(wù)邏輯:創(chuàng)建訂單
    }
}

通過 @Service,OrderService 會被自動掃描注冊。在需要使用它的地方,例如控制層或其他服務(wù)層,可以通過依賴注入獲取該 Bean 實(shí)例。

@Repository

簡介: @Repository@Component 的特化注解之一,用于標(biāo)注數(shù)據(jù)訪問層組件(DAO層,或倉庫類)。定義在 Spring Framework 的 org.springframework.stereotype.Repository 包中。

作用與場景: DAO 類(例如訪問數(shù)據(jù)庫的類)使用 @Repository 注解不僅可以被掃描為容器 Bean,還能啟用異常轉(zhuǎn)換功能。Spring DAO 層會捕獲底層數(shù)據(jù)訪問異常(如 JDBC 的 SQLException 或 JPA 的異常),將其翻譯為 Spring 統(tǒng)一的DataAccessException體系,從而簡化異常處理。換句話說,如果一個(gè)類標(biāo)注為 @Repository,Spring 在為其創(chuàng)建代理時(shí)會自動處理持久化異常,將原始異常轉(zhuǎn)為 Spring 的非檢查型數(shù)據(jù)訪問異常,以提高健壯性。另外,標(biāo)注了 @Repository 的類可以被 @Autowired 等注解自動裝配到其他地方。

使用示例:

@Repository
public class UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    public User findById(Long id) {
        try {
            return jdbcTemplate.queryForObject("SELECT * FROM users WHERE id=?", 
                                               new BeanPropertyRowMapper<>(User.class), id);
        } catch (DataAccessException e) {
            // Spring 已將底層SQLException翻譯為DataAccessException
            throw e;
        }
    }
}

上例中,UserDao 使用 @Repository 注解,使其成為容器管理的DAO組件。Spring 自動為其提供異常轉(zhuǎn)換功能:如果 JDBC 操作拋出 SQLException,會被翻譯為 DataAccessException(RuntimeException),調(diào)用處可以統(tǒng)一處理。標(biāo)注后也允許通過 @Autowired 注入到服務(wù)層使用。

@Controller

簡介: @Controller 是 Spring MVC 的控制層組件注解,同樣派生自 @Component。由 Spring Web MVC 模塊提供(org.springframework.stereotype.Controller),用于標(biāo)識一個(gè)類是Web MVC 控制器,負(fù)責(zé)處理 HTTP 請求并返回視圖或響應(yīng)。

作用與場景: 在 Web 應(yīng)用程序中,@Controller 注解的類會被 DispatcherServlet 識別為控制器,用于映射請求URL、封裝模型數(shù)據(jù)并返回視圖名。通常配合視圖模板(如 Thymeleaf、JSP)返回頁面。如果需要直接返回 JSON 數(shù)據(jù),可以配合 @ResponseBody 或直接使用 @RestController(后者見下文)。

使用示例:

@Controller
public class HomeController {
    @RequestMapping("/home")
    public String homePage(Model model) {
        model.addAttribute("msg", "Hello Spring MVC");
        return "home"; // 返回視圖名,由視圖解析器解析為頁面
    }
}

上述 HomeController 使用 @Controller 標(biāo)記,提供一個(gè)映射 “/home” 請求的處理方法。返回值 "home" 代表視圖邏輯名,框架會根據(jù)配置解析到具體的頁面(如 home.html)。如果我們在類上使用了 @Controller,框架在啟動時(shí)會自動注冊相應(yīng)的映射。

@RestController

簡介: @RestController 是 Spring 提供的組合注解,等價(jià)于同時(shí)在類上使用 @Controller@ResponseBody。它主要由 Spring Web 模塊提供,用于RESTful Web服務(wù)的控制器。

作用與場景: 標(biāo)注 @RestController 的類會被識別為控制器,并且其每個(gè)處理方法的返回值會直接作為 HTTP 響應(yīng)體輸出,而不是作為視圖名稱解析。適用于需要返回 JSON、XML 等數(shù)據(jù)的場景,比如 Web API 接口。模塊提供: Spring Web(Spring MVC)。

使用示例:

@RestController
@RequestMapping("/api")
public class UserApiController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello, RESTful";
    }
    @PostMapping("/users")
    public User createUser(@RequestBody User user) {
        // 直接接收J(rèn)SON反序列化為User對象,處理后返回
        return userService.save(user);
    }
}

UserApiController 使用 @RestController 注解,其方法返回字符串和對象將直接通過消息轉(zhuǎn)換器寫入響應(yīng)(例如字符串作為純文本,User 對象會序列化為 JSON)。不需要再在每個(gè)方法上加 @ResponseBody,使代碼更加簡潔。通常在開發(fā) REST API 時(shí),都使用 @RestController 來定義控制器。

@Configuration

簡介: @Configuration 用于聲明一個(gè)配置類,由 Spring Framework 提供(org.springframework.context.annotation.Configuration)。配置類可以包含若干個(gè)帶有 @Bean 注解的方法,以定義 Bean 并交由 Spring 容器管理。@Configuration 本身也是 @Component,因此配置類也會被組件掃描注冊。

作用與場景: 在 Java Config 風(fēng)格的應(yīng)用中,@Configuration 相當(dāng)于傳統(tǒng) XML 配置文件。用于定義 Beans、設(shè)置依賴注入規(guī)則等。Spring Boot 應(yīng)用的某些自動配置也是以配置類形式存在。提供模塊: Spring Context。

使用示例:

@Configuration
public class AppConfig {
    @Bean
    public DataSource dataSource() {
        // 配置數(shù)據(jù)源 Bean,例如使用 HikariCP 數(shù)據(jù)源
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        ds.setUsername("root");
        ds.setPassword("123456");
        return ds;
    }
    @Bean
    public UserService userService() {
        // 將 UserService 注冊為 Bean,并注入依賴的數(shù)據(jù)源
        return new UserService(dataSource());
    }
}

上述 AppConfig@Configuration 注解標(biāo)識為配置類。方法 dataSource()userService() 上的 @Bean 注解會使 Spring 將其返回值注冊為容器中的 Bean。其中 userService() 方法調(diào)用了 dataSource(),Spring 會攔截并確保返回的是容器中單例的 DataSource Bean,而非每次調(diào)用重新實(shí)例化(即 CGLIB 增強(qiáng) @Configuration 類確保 Bean 單例行為)。

@Bean

簡介: @Bean 注解用于定義一個(gè) Bean。它標(biāo)注在方法上,表示該方法返回的對象會注冊到 Spring 容器中。@Bean 通常配合 @Configuration 使用,由 Spring Context 模塊提供。

作用與場景: 當(dāng)通過 JavaConfig 定義 Bean 時(shí),用 @Bean 替代傳統(tǒng) XML <bean> 聲明。例如整合第三方庫的 Bean、或需要在創(chuàng)建 Bean 時(shí)執(zhí)行一些自定義邏輯等場景。@Bean 方法可以指定名稱(默認(rèn)是方法名),還支持設(shè)置 initMethod(初始化時(shí)回調(diào)方法)和 destroyMethod(銷毀時(shí)回調(diào)方法)。

使用示例:

@Configuration
public class MyConfig {
    @Bean(name = "customBean", initMethod = "init", destroyMethod = "cleanup")
    public MyComponent customBean() {
        return new MyComponent();
    }
}

在上例中,@Bean 注解聲明了 customBean 這個(gè) Bean。容器啟動時(shí)調(diào)用 customBean() 方法創(chuàng)建 MyComponent 實(shí)例,并以 "customBean" 名稱注冊。initMethod="init" 表示在 Bean 創(chuàng)建后自動調(diào)用其 init() 方法進(jìn)行初始化;destroyMethod="cleanup" 表示容器銷毀該 Bean 時(shí)調(diào)用其 cleanup() 方法。通過這種方式可以管理 Bean 的生命周期方法(類似于 InitializingBeanDisposableBean 接口或 @PostConstruct/@PreDestroy,見后文)。

@ComponentScan

簡介: @ComponentScan 用于配置組件掃描路徑的注解。由 Spring Context 提供,通常與 @Configuration 一起使用。它的作用是指示 Spring 在指定的包路徑下搜索帶有組件注解的類,并注冊為 Bean。

作用與場景: 默認(rèn)情況下,Spring Boot 的 @SpringBootApplication 已經(jīng)隱含指定掃描其所在包及子包。如果需要自定義掃描范圍(例如掃描其他包的組件),可以使用 @ComponentScan 注解并提供 basePackages 等屬性。普通 Spring 應(yīng)用(非 Boot)則經(jīng)常需要在主配置類上顯式使用 @ComponentScan 指定根包。提供模塊: Spring Context。

使用示例:

@Configuration
@ComponentScan(basePackages = {"com.example.service", "com.example.dao"})
public class AppConfig {
    // ... Bean definitions
}

上述配置類通過 @ComponentScan 指定 Spring 將掃描 com.example.servicecom.example.dao 這兩個(gè)包及其子包,搜索所有標(biāo)注了 @Component/@Service/@Controller 等的類并注冊。這樣可以將應(yīng)用的組件按照包組織,而由配置集中管理掃描范圍。

@Import

簡介: @Import 注解用于導(dǎo)入額外的配置類或組件到 Spring 容器。它由 Spring Context 提供,可用在 @Configuration 類上,將一個(gè)或多個(gè)配置類合并進(jìn)來。也可以用于引入第三方配置。

作用與場景: 當(dāng)項(xiàng)目拆分成多個(gè)配置類時(shí),可以通過 @Import 將它們組合。例如,將公共配置獨(dú)立出來,再在主配置中引入。Spring Boot 自動配置內(nèi)部也大量使用了 @Import 來按條件加載配置類。提供模塊: Spring Context。

使用示例:

@Configuration
@Import({SecurityConfig.class, DataConfig.class})
public class MainConfig {
    // 主配置,導(dǎo)入了安全配置和數(shù)據(jù)配置
}

如上,MainConfig 通過 @Import 導(dǎo)入了 SecurityConfigDataConfig 兩個(gè)配置類。這樣這兩個(gè)配置類中定義的 Bean 同樣會加載到容器中,相當(dāng)于把多個(gè)配置模塊拼裝在一起。相比在 XML 里用 <import>,注解方式更加直觀。

注: Spring Boot 提供的許多 @Enable... 注解(例如后文的 @EnableScheduling 等)內(nèi)部也是通過 @Import 導(dǎo)入相應(yīng)的配置實(shí)現(xiàn)啟用功能的。

三、依賴注入注解

依賴注入(DI)是 Spring 核心機(jī)制之一。以下注解用于在容器中進(jìn)行 Bean 注入和裝配,解決 Bean 間的依賴關(guān)系。

@Autowired

簡介: @Autowired 是 Spring 提供的自動裝配注解(org.springframework.beans.factory.annotation.Autowired),用于按類型自動注入依賴對象。它可作用于字段、setter方法或者構(gòu)造函數(shù)上。由 Spring Context 模塊支持。

作用與場景: 標(biāo)注了 @Autowired 的屬性或方法,Spring 會在容器啟動時(shí)自動尋找匹配的 Bean 注入。其中按類型匹配是默認(rèn)行為。如果匹配到多個(gè)同類型 Bean,則需要結(jié)合 @Qualifier@Primary 來消除歧義(見下文)。如果沒有找到匹配 Bean,默認(rèn)會拋出異常。可通過設(shè)置 @Autowired(required=false) 來表示找不到 Bean 時(shí)跳過注入而不報(bào)錯(cuò)。

使用示例:

@Component
public class UserService {
    @Autowired  // 按類型自動裝配
    private UserRepository userRepository;
    // 或者構(gòu)造函數(shù)注入
    // @Autowired 
    // public UserService(UserRepository userRepository) { ... }
    public User findUser(Long id) {
        return userRepository.findById(id);
    }
}

上例中,UserService 有一個(gè)成員 userRepository,使用 @Autowired 標(biāo)注。容器會自動將類型為 UserRepository 的 Bean 注入進(jìn)來(假設(shè)已有 @Repository@Component 標(biāo)記的 UserRepository 實(shí)現(xiàn))。開發(fā)者可以通過構(gòu)造器、setter 或字段注入的方式使用 @Autowired注意: Spring 4.3+ 如果類中只有一個(gè)構(gòu)造器,且需要注入?yún)?shù),可省略構(gòu)造函數(shù)上的 @Autowired,仍會自動注入。

@Qualifier

簡介: @Qualifier 注解與 @Autowired 配合使用,用于按照名稱或限定符進(jìn)行依賴注入匹配。它由 Spring 提供(org.springframework.beans.factory.annotation.Qualifier),可以解決當(dāng)容器中存在多個(gè)同類型 Bean 時(shí)的沖突。

作用與場景: 默認(rèn)按類型注入在有多于一個(gè)候選 Bean 時(shí)會無法確定注入哪個(gè)。例如有兩個(gè)實(shí)現(xiàn)類實(shí)現(xiàn)了同一接口,都被注冊為 Bean。這種情況下,可以在注入點(diǎn)使用 @Qualifier("beanName") 指定注入哪一個(gè) Bean,或在 Bean 定義處使用 @Component("name") 為 Bean 命名,然后在注入處引用同名限定符。提供模塊: Spring Context/Beans。

使用示例:

@Component("mysqlRepo")
public class MySqlUserRepository implements UserRepository { ... }
@Component("oracleRepo")
public class OracleUserRepository implements UserRepository { ... }
@Service
public class UserService {
    @Autowired
    @Qualifier("mysqlRepo")  // 指定注入名稱為 mysqlRepo 的實(shí)現(xiàn)
    private UserRepository userRepository;
    // ...
}

如上,有兩個(gè) UserRepository 實(shí)現(xiàn) Bean,分別命名為 “mysqlRepo” 和 “oracleRepo”。在 UserService 中,通過 @Qualifier("mysqlRepo") 指定注入名為 mysqlRepo 的 Bean。這樣即使存在多個(gè)同類型 Bean,Spring 也能準(zhǔn)確地注入所需的依賴。

@Primary

簡介: @Primary 注解用于標(biāo)記一個(gè) Bean 為主要候選者。當(dāng)按類型注入出現(xiàn)多個(gè) Bean 可選時(shí),標(biāo)有 @Primary 的 Bean 將優(yōu)先被注入。它由 Spring 提供(org.springframework.context.annotation.Primary),可作用于類或方法(例如 @Bean 方法)上。

作用與場景: 如果不方便在每個(gè)注入點(diǎn)都使用 @Qualifier 指定 Bean,另一種方式是在 Bean 定義處用 @Primary 聲明一個(gè)首選 Bean。當(dāng)存在歧義時(shí),容器會選擇標(biāo)記了 @Primary 的 Bean 注入。注意,@Primary 只能有一個(gè),否則仍然無法明確選擇。提供模塊: Spring Context。

使用示例:

@Configuration
public class RepoConfig {
    @Bean
    @Primary  // 將這個(gè)Bean標(biāo)記為首選
    public UserRepository mysqlUserRepository() {
        return new MySqlUserRepository();
    }
    @Bean
    public UserRepository oracleUserRepository() {
        return new OracleUserRepository();
    }
}
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    // 將自動注入 mysqlUserRepository,因?yàn)樗粯?biāo)記為 @Primary
}

在上例的配置中,我們定義了兩個(gè) UserRepository Bean,其中 MySQL 實(shí)現(xiàn)被標(biāo)記為 @Primary。因此在 UserService 中按類型注入 UserRepository 時(shí),Spring 會注入標(biāo)記了 @Primary 的 MySQL 實(shí)現(xiàn)。@Primary 提供了一個(gè)全局默認(rèn)方案,簡化了注入點(diǎn)的選擇。

@Resource

簡介: @Resource 是來自 JSR-250 規(guī)范的注解(Javax/Jakarta Annotation),Spring 對其提供了支持,用于按名稱或按類型注入依賴。它通常位于 jakarta.annotation.Resource(Java EE/Jakarta EE)包下。注意: 盡管不在 Spring 包中,Spring 容器能識別并處理它。

作用與場景: @Resource 可以看作功能類似于 @Autowired + @Qualifier 的組合。默認(rèn)情況下按名稱注入:它首先按照屬性名或指定的名稱在容器中查找 Bean,找不到再按類型匹配。這在某些情況下很有用,例如需要與傳統(tǒng) Java EE 代碼兼容時(shí)。在 Spring 應(yīng)用中,也有開發(fā)者偏好使用 @Resource 進(jìn)行依賴注入。提供模塊: 需要引入相應(yīng)的 Jakarta Annotation API,但 Spring Framework 自身支持處理。

使用示例:

@Component("userRepo")
public class UserRepositoryImpl implements UserRepository { ... }
@Service
public class UserService {
    @Resource(name = "userRepo")  // 按名稱注入名為"userRepo"的Bean
    private UserRepository userRepo;
    // ...
}

這里,UserRepositoryImpl 組件被命名為 "userRepo"。在 UserService 中,通過 @Resource(name = "userRepo") 來注入。如果省略 name 屬性,@Resource 默認(rèn)以屬性名 userRepo 作為 Bean 名稱查找。與 @Autowired 不同,@Resource 不支持 required=false 屬性,但其異常信息可能更直觀(若找不到 Bean 則拋出 NoSuchBeanDefinitionException)。值得一提的是,Spring 也支持 JSR-330 的 @Inject(javax.inject.Inject)注解,其語義與 @Autowired 相同,也可用于構(gòu)造函數(shù)注入等。在實(shí)際開發(fā)中,可根據(jù)團(tuán)隊(duì)規(guī)范選擇使用 Spring 原生的 @Autowired 還是標(biāo)準(zhǔn)的 @Resource/@Inject。

@Value

簡介: @Value 注解用于將外部化配置中的屬性值注入到 Bean 的字段或參數(shù)中。它由 Spring 提供(org.springframework.beans.factory.annotation.Value),常用于讀取 application.properties/yaml 配置文件或系統(tǒng)環(huán)境變量、JNDI等屬性。

作用與場景: 當(dāng)需要在 Bean 中使用配置文件里的值時(shí),可以使用 @Value("${property.name}") 注入。例如數(shù)據(jù)庫連接參數(shù)、服務(wù)端口號等。還支持設(shè)置默認(rèn)值和 SpEL 表達(dá)式。提供模塊: Spring Context 環(huán)境抽象。

使用示例:
假設(shè) application.properties 有如下內(nèi)容:

app.name=MySpringApp
app.version=1.0.0

Java 類使用 @Value 注入:

@Component
public class AppInfo {
    @Value("${app.name}")
    private String appName;
    @Value("${app.version:0.0.1}")  // 帶默認(rèn)值,若配置缺失則使用0.0.1
    private String appVersion;
    // ...
}

上述 AppInfo 類中,@Value("${app.name}") 將把配置中的 app.name 值注入到 appName 字段。如果對應(yīng)屬性不存在,會啟動失敗。而 appVersion 字段提供了默認(rèn)值 0.0.1,當(dāng)配置文件未設(shè)置 app.version 時(shí)就會使用默認(rèn)值。這樣,可以靈活地將外部配置與代碼解耦,使應(yīng)用更易于調(diào)整參數(shù)而無需改動源碼。

@Scope

簡介: @Scope 注解用于指定 Bean 的作用域,由 Spring 提供(org.springframework.context.annotation.Scope)。默認(rèn)情況下,Spring 容器中的 Bean 都是單例(singleton)作用域。通過 @Scope 可以定義其他作用域,例如 prototype、request、session 等。

作用與場景: 常見的作用域:

  • singleton(默認(rèn)):容器中僅保持一個(gè)實(shí)例。
  • prototype:每次請求 Bean 時(shí)都會創(chuàng)建新實(shí)例。
  • Web相關(guān)的作用域(需要在 Web 容器環(huán)境下使用):如 request(每個(gè)HTTP請求創(chuàng)建)、session(每個(gè)會話創(chuàng)建)等。

在需要每次使用新對象的場景(如有狀態(tài) Bean),可將 Bean 定義成 prototype;在 Web 應(yīng)用中某些 Bean 希望隨請求或會話存續(xù),可用相應(yīng)作用域。提供模塊: Spring Context。

使用示例:

@Component
@Scope("prototype")
public class Connection {
    public Connection() {
        System.out.println("New Connection created.");
    }
}

Connection Bean 聲明為 prototype,每次獲取都會創(chuàng)建新的實(shí)例:

@Autowired
private Connection conn1;
@Autowired
private Connection conn2;

上面 conn1conn2 將是不同的實(shí)例,因?yàn)?Connection 定義為 prototype。日志會打印兩次 “New Connection created.”。若作用域是 singleton,則只創(chuàng)建一次實(shí)例并復(fù)用。需要注意,prototype Bean 的生命周期由使用方管理,Spring 只負(fù)責(zé)創(chuàng)建,不會自動調(diào)用其銷毀方法。

@Lazy

簡介: @Lazy 注解用于將 Bean 的初始化延遲到首次使用時(shí)(懶加載)。由 Spring 提供(org.springframework.context.annotation.Lazy),可用于類級別或 @Bean 方法上。

作用與場景: 默認(rèn)情況下,單例 Bean 在容器啟動時(shí)就會初始化。如果某些 Bean 的創(chuàng)建比較耗時(shí)或在應(yīng)用運(yùn)行期間可能不會被用到,可以標(biāo)記為 @Lazy,這樣只有在真正需要時(shí)才實(shí)例化,減少啟動時(shí)間和資源消耗。懶加載常用于:例如調(diào)試或在單元測試中減少不必要 Bean 創(chuàng)建,或避免循環(huán)依賴時(shí)暫緩 Bean 的注入初始化。對于 prototype Bean,Spring 始終延遲創(chuàng)建(因?yàn)楸旧砭桶葱鑴?chuàng)建),@Lazy主要針對單例 Bean。提供模塊: Spring Context。

使用示例:

@Service
@Lazy
public class HeavyService {
    public HeavyService() {
        // 構(gòu)造函數(shù)可能進(jìn)行大量初始化
        System.out.println("HeavyService initialized");
    }
    // ...
}
@Controller
public class DemoController {
    @Autowired
    private HeavyService heavyService; // 被@Lazy標(biāo)記,不會在容器啟動時(shí)實(shí)例化
    // ...
}

如上,HeavyService 使用 @Lazy 注解標(biāo)記為懶加載單例。啟動時(shí)不會打印 “HeavyService initialized”。當(dāng) DemoController 第一次實(shí)際調(diào)用 heavyService 的方法或訪問它時(shí),Spring 才會創(chuàng)建 HeavyService 實(shí)例并注入。這對于優(yōu)化啟動性能和按需加載組件很有幫助。但應(yīng)謹(jǐn)慎使用懶加載,如果Bean在啟動后馬上就會用到,則不應(yīng)延遲初始化,以免首次調(diào)用時(shí)產(chǎn)生延遲。

四、配置屬性注解

Spring 提供了將配置文件內(nèi)容綁定到對象的機(jī)制,這類注解幫助管理應(yīng)用的外部化配置和環(huán)境區(qū)分。

@ConfigurationProperties

簡介: @ConfigurationProperties 用于將一組配置屬性映射到一個(gè) Java 類上。由 Spring Boot 提供(org.springframework.boot.context.properties.ConfigurationProperties),通常配合 Bean 使用。通過前綴(prefix)來批量注入配置項(xiàng)到類的屬性中。

作用與場景: 當(dāng)有多項(xiàng)相關(guān)配置需要使用時(shí),比起逐個(gè)使用 @Value,可以定義一個(gè)配置屬性類。例如應(yīng)用配置、數(shù)據(jù)源配置等。在類上標(biāo)注 @ConfigurationProperties(prefix="xxx") 后,該類的各屬性會根據(jù)前綴讀取配置文件中的對應(yīng)項(xiàng)賦值。需要將該類注冊為 Bean(可以通過在類上加 @Component 或在配置類中用 @Bean 創(chuàng)建),Spring Boot 會自動將配置綁定到 Bean 實(shí)例上。

使用示例:
application.yml:

app:
  name: MyApp
  apiUrl: https://api.example.com
  pool:
    size: 20
    enableLog: true

定義屬性綁定類:

@Component  // 確保被掃描注冊為Bean
@ConfigurationProperties(prefix = "app")
public class AppProperties {
    private String name;
    private String apiUrl;
    private Pool pool;
    // 內(nèi)部靜態(tài)類或普通類用于嵌套屬性
    public static class Pool {
        private int size;
        private boolean enableLog;
        // getters/setters ...
    }
    // getters/setters ...
}

AppProperties 注入使用:

@RestController
public class AppInfoController {
    @Autowired
    private AppProperties appProperties;
    @GetMapping("/appInfo")
    public AppProperties getAppInfo() {
        // 返回整個(gè)配置對象,框架會序列化為JSON
        return appProperties;
    }
}

在這個(gè)例子中,@ConfigurationProperties(prefix="app") 使得 YAML 中 app 下的配置自動綁定到 AppProperties Bean。nameapiUrl 會對應(yīng)賦值,嵌套的 pool.sizepool.enableLog 也會注入到 Pool 類中。這樣可以方便地管理和校驗(yàn)成組的配置屬性。需要注意,綁定類必須有無參構(gòu)造器,提供標(biāo)準(zhǔn)的 getter/setter。Spring Boot 還支持JSR-303校驗(yàn)注解(如 @Validated)配合 @ConfigurationProperties 對配置進(jìn)行格式校驗(yàn)。

@EnableConfigurationProperties

簡介: @EnableConfigurationProperties 是 Spring Boot 用于啟用 @ConfigurationProperties 支持的注解。它通常加在主應(yīng)用類或配置類上,用來將帶有 @ConfigurationProperties 注解的配置POJO注入到容器中。

作用與場景: 在 Spring Boot 2.x 以后,如果配置屬性類已經(jīng)被聲明為 Bean(例如加了 @Component),則無需顯式使用這個(gè)注解。@EnableConfigurationProperties 常用在需要將未被組件掃描的配置屬性類納入 Spring 管理時(shí)。例如定義了一個(gè)純 POJO 沒有用@Component,則可以在主類上通過此注解指定要啟用綁定的配置類列表。提供模塊: Spring Boot AutoConfigure。

使用示例:

@SpringBootApplication
@EnableConfigurationProperties(AppProperties.class)
public class MyApplication {
    // ...
}

上述在主啟動類上添加了 @EnableConfigurationProperties(AppProperties.class),顯式指定將 AppProperties 這個(gè)被 @ConfigurationProperties 注解的類納入配置屬性綁定并注冊為 Bean。這樣即使未加 @Component,仍可使用 @Autowired 注入 AppProperties 實(shí)例。Spring Boot 自動配置模塊會掃描此注解并完成相應(yīng)的綁定工作。

@Profile

簡介: @Profile 注解用于根據(jù)**環(huán)境(Profile)**加載 Bean。由 Spring 提供(org.springframework.context.annotation.Profile)??梢詷?biāo)注在類或方法(Bean 方法)上,只有在激活的環(huán)境與指定 Profile 匹配時(shí),該 Bean 才會注冊到容器。

作用與場景: 常用于區(qū)別開發(fā)、測試、生產(chǎn)環(huán)境的配置。例如開發(fā)環(huán)境使用嵌入式數(shù)據(jù)庫,而生產(chǎn)環(huán)境使用正式數(shù)據(jù)庫連接,就可以用 @Profile("dev")@Profile("prod") 注解分別標(biāo)注不同的配置類或 Bean。在運(yùn)行應(yīng)用時(shí)通過配置 spring.profiles.active 激活某個(gè) Profile,則對應(yīng)的 Bean 生效。提供模塊: Spring Context 環(huán)境管理。

使用示例:

@Configuration
public class DataSourceConfig {
    @Bean
    @Profile("dev")
    public DataSource memoryDataSource() {
        // 開發(fā)環(huán)境使用內(nèi)存數(shù)據(jù)庫
        return new H2DataSource(...);
    }
    @Bean
    @Profile("prod")
    public DataSource mysqlDataSource() {
        // 生產(chǎn)環(huán)境使用MySQL數(shù)據(jù)源
        return new MySQLDataSource(...);
    }
}

當(dāng)設(shè)置 spring.profiles.active=dev 時(shí),應(yīng)用啟動只會創(chuàng)建 memoryDataSource Bean;設(shè)置為 prod 時(shí)只創(chuàng)建 mysqlDataSource Bean。如果不激活任何 Profile,上述兩個(gè) Bean 都不會加載(也可以用 @Profile("default") 指定默認(rèn)配置)。使用 @Profile 實(shí)現(xiàn)了根據(jù)環(huán)境有條件地注冊 Bean,方便一套代碼多環(huán)境運(yùn)行。

五、Bean 生命周期與作用域注解

Spring 管理的 Bean 具有完整的生命周期,包括初始化和銷毀過程。以下注解用于在生命周期特定階段執(zhí)行方法,以及控制 Bean 的作用域與加載時(shí)機(jī)。

@PostConstruct

簡介: @PostConstruct 是一個(gè)來自 Java 標(biāo)準(zhǔn)(JSR-250)的注解(位于 jakarta.annotation.PostConstruct)。Spring 容器在Bean初始化完依賴注入后,會調(diào)用被該注解標(biāo)記的方法。常用于初始化邏輯。需要注意在 Spring Boot 3+ 中,@PostConstruct 等由 Jakarta 引入,需要相應(yīng)依賴。

作用與場景: 當(dāng)我們希望在 Bean 完成依賴注入后自動執(zhí)行一些初始化代碼,可以在 Bean 的方法上加 @PostConstruct。例如設(shè)置默認(rèn)值、開啟定時(shí)器、檢查配置完整性等。在傳統(tǒng) Spring 中,這相當(dāng)于 <bean init-method="..."> 或?qū)崿F(xiàn) InitializingBean 接口的 afterPropertiesSet。提供模塊: JSR-250(Javax/Jakarta Annotation),由 Spring 容器支持調(diào)用。

使用示例:

@Component
public class CacheManager {
    private Map<String, Object> cache;
    @PostConstruct
    public void init() {
        // 初始化緩存
        cache = new ConcurrentHashMap<>();
        System.out.println("CacheManager initialized");
    }
}

當(dāng) Spring 創(chuàng)建了 CacheManager Bean 并注入完依賴后,會自動調(diào)用其 init() 方法,輸出 “CacheManager initialized” 并完成緩存容器初始化。這樣開發(fā)者無需手動調(diào)用初始化邏輯,容器托管完成。這對于單例Bean非常方便。

@PreDestroy

簡介: @PreDestroy 同樣來自 JSR-250 標(biāo)準(zhǔn)(jakarta.annotation.PreDestroy),Spring 在 Bean 銷毀前(容器關(guān)閉或 Bean 移除前)調(diào)用標(biāo)注該注解的方法。常用于資源釋放、保存狀態(tài)等操作。

作用與場景: 當(dāng)應(yīng)用結(jié)束或容器要銷毀 Bean 時(shí),希望執(zhí)行一些清理工作,例如關(guān)閉文件流、線程池、數(shù)據(jù)庫連接等,可以在方法上加 @PreDestroy 注解。相當(dāng)于 XML 配置中的 <bean destroy-method="..."> 或?qū)崿F(xiàn) DisposableBean 接口的 destroy 方法。提供模塊: JSR-250,由 Spring 容器負(fù)責(zé)調(diào)用。

使用示例:

@Component
public class ConnectionManager {
    private Connection connection;
    @PostConstruct
    public void connect() {
        // 建立數(shù)據(jù)庫連接
        connection = DriverManager.getConnection(...);
    }
    @PreDestroy
    public void disconnect() throws SQLException {
        // 關(guān)閉數(shù)據(jù)庫連接
        if(connection != null && !connection.isClosed()) {
            connection.close();
            System.out.println("Connection closed.");
        }
    }
}

在上例中,ConnectionManager Bean 在初始化時(shí)建立數(shù)據(jù)庫連接,在容器銷毀時(shí)通過 @PreDestroy 標(biāo)記的 disconnect() 方法關(guān)閉連接。Spring 在應(yīng)用關(guān)閉時(shí)會調(diào)用該方法,確保資源釋放。這使得資源管理更加可靠,避免連接泄漏等問題。

@Scope (作用域) – 見上文第三部分

(此處簡要說明:) 使用 @Scope 注解可以改變 Bean 的作用域,比如 "singleton"、"prototype" 等。已在依賴注入部分詳細(xì)介紹其使用。

@Lazy (懶加載) – 見上文第三部分

(此處簡要說明:) 使用 @Lazy 可以延遲 Bean 的初始化直至第一次使用。在某些場景下提高啟動性能或解決循環(huán)依賴。前文已介紹其概念和示例。

六、Web 開發(fā)注解

Spring MVC 框架提供了大量注解來簡化 Web 開發(fā),包括請求映射、參數(shù)綁定、響應(yīng)處理等。這些注解大多位于 org.springframework.web.bind.annotation 包中。

@RequestMapping

簡介: @RequestMapping 是最基本的請求映射注解,用于將 HTTP 請求URL路徑映射到對應(yīng)的控制器類或處理方法上。由 Spring Web MVC 提供??捎糜陬惡头椒墑e。

作用與場景: 在類上標(biāo)注 @RequestMapping("basePath") 可以為該控制器指定一個(gè)基礎(chǔ)路徑,方法上的 @RequestMapping("subPath") 則在類路徑基礎(chǔ)上進(jìn)一步細(xì)分。它支持設(shè)置請求方法(GET、POST等)、請求參數(shù)和請求頭等屬性,用于更精確地映射請求。例如只處理 GET 請求,或某個(gè)請求參數(shù)存在時(shí)才匹配。Spring MVC 啟動時(shí)會根據(jù)這些注解建立 URL 到方法的映射關(guān)系。

使用示例:

@Controller
@RequestMapping("/users")
public class UserController {
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String getUserProfile(@PathVariable Long id, Model model) {
        // 根據(jù)id查詢用戶...
        model.addAttribute("user", userService.findById(id));
        return "profile";
    }
    @RequestMapping(value = "", method = RequestMethod.POST, params = "action=register")
    public String registerUser(UserForm form) {
        // 處理用戶注冊
        userService.register(form);
        return "redirect:/users";
    }
}

UserController 類上的 @RequestMapping("/users") 指定了基礎(chǔ)路徑“/users”。方法級注解:

  • getUserProfile: 映射 GET 請求到 “/users/{id}”。使用 method = RequestMethod.GET 限定請求方法為 GET,@PathVariable 獲取 URL 中的 {id} 部分。返回視圖名 “profile” 供顯示用戶信息。
  • registerUser: 映射 POST 請求到 “/users”,并使用 params="action=register" 進(jìn)一步限定只有請求參數(shù)包含 action=register 時(shí)才調(diào)用此方法。這是區(qū)分同一路徑不同操作的方式。處理完后重定向到用戶列表。

@RequestMapping 非常靈活,其常用屬性:

valuepath:映射的 URL 路徑,可以是 Ant 風(fēng)格模式(如 /users/*)。method:限定 HTTP 方法,如 RequestMethod.GET 等。params:指定必須存在的參數(shù)或參數(shù)值,如 "action=register""!admin"(必須不包含admin參數(shù))。headers:指定必須的請求頭,如 "Content-Type=application/json"。 @GetMapping / @PostMapping

簡介: @GetMapping@PostMapping@RequestMapping 的派生注解,專門用于簡化映射 GET 和 POST 請求。類似的還有 @PutMapping@DeleteMapping、@PatchMapping,分別對應(yīng) HTTP PUT/DELETE/PATCH 方法。它們由 Spring MVC 提供,從 Spring 4.3 開始引入。

作用與場景: 這些注解相當(dāng)于 @RequestMapping(method = RequestMethod.X) 的快捷方式,使代碼更簡潔。尤其在定義 RESTful API 時(shí),常用不同 HTTP 方法表示不同操作,用這些注解能直觀體現(xiàn)方法用途。例如 @GetMapping 表示獲取資源,@PostMapping 表示創(chuàng)建資源等。

使用示例:

@RestController
@RequestMapping("/items")
public class ItemController {
    @GetMapping("/{id}")
    public Item getItem(@PathVariable Long id) {
        return itemService.findById(id);
    }
    @PostMapping("")
    public Item createItem(@RequestBody Item item) {
        return itemService.save(item);
    }
    @DeleteMapping("/{id}")
    public void deleteItem(@PathVariable Long id) {
        itemService.delete(id);
    }
}

上例中:

  • @GetMapping("/{id}) 等價(jià)于 @RequestMapping(value="/{id}", method = RequestMethod.GET),用于獲取指定ID的 Item。
  • @PostMapping("") 等價(jià)于類路徑/items下的 POST 請求(創(chuàng)建新的 Item),請求體通過 @RequestBody 解析為 Item 對象。
  • @DeleteMapping("/{id}") 處理刪除操作。

這些組合注解讓控制器方法定義更直觀,更符合 RESTful 風(fēng)格。可以根據(jù)需要使用對應(yīng)的 HTTP方法注解。未提供參數(shù)時(shí),@GetMapping 等注解的路徑可以直接寫在注解括號內(nèi)(如上 @PostMapping("") 指當(dāng)前路徑)。

@PathVariable

簡介: @PathVariable 用于將 URL 路徑中的動態(tài)部分綁定到方法參數(shù)上。由 Spring MVC 提供。常與 @RequestMapping@GetMapping 等一起使用,用于處理RESTful風(fēng)格的URL。

作用與場景: 當(dāng)URL中含有變量占位符(如 /users/{id})時(shí),可通過在方法參數(shù)上加 @PathVariable 來獲取該占位符的值。可以指定名稱匹配占位符,或者不指定名稱則根據(jù)參數(shù)名自動推斷。適用于從路徑獲取資源標(biāo)識(ID、name等)的場景。

使用示例:

@GetMapping("/orders/{orderId}/items/{itemId}")
public OrderItem getOrderItem(
        @PathVariable("orderId") Long orderId, 
        @PathVariable("itemId") Long itemId) {
    return orderService.findItem(orderId, itemId);
}

當(dāng)收到請求 /orders/123/items/456 時(shí):

  • orderId 參數(shù)會被賦值為 123(Long 類型轉(zhuǎn)換),
  • itemId 參數(shù)賦值為 456。

@PathVariable("orderId") 中指定名稱,與 {orderId} 占位符對應(yīng)。如果方法參數(shù)名與占位符名稱相同,可以簡寫為 @PathVariable Long orderId

通過 @PathVariable,我們無需從 HttpServletRequest 手動解析路徑,Spring MVC 自動完成轉(zhuǎn)換和注入,簡化了代碼。

@RequestParam

簡介: @RequestParam 用于綁定 HTTP 請求的查詢參數(shù)或表單數(shù)據(jù)到方法參數(shù)上。由 Spring MVC 提供。支持為參數(shù)設(shè)置默認(rèn)值、是否必需等屬性。

作用與場景: 處理 GET 請求的查詢字符串參數(shù)(URL ? 后的參數(shù))或 POST 表單提交的字段時(shí),可以使用 @RequestParam 獲取。例如搜索接口的關(guān)鍵詞,分頁的頁碼和大小等。它可以將 String 類型的請求參數(shù)轉(zhuǎn)換為所需的目標(biāo)類型(如 int、boolean),自動完成類型轉(zhuǎn)換和必要的校驗(yàn)。

使用示例:

@GetMapping("/search")
public List<Product> searchProducts(
        @RequestParam(name="keyword", required=false, defaultValue="") String keyword,
        @RequestParam(defaultValue="0") int pageIndex,
        @RequestParam(defaultValue="10") int pageSize) {
    return productService.search(keyword, pageIndex, pageSize);
}

當(dāng)請求 /search?keyword=phone&pageIndex=1 到達(dá)時(shí):

  • keyword 參數(shù)綁定到方法的 keyword 參數(shù)。如果未提供則使用默認(rèn)值空字符串。
  • pageIndex 綁定到整型參數(shù),未提供則為默認(rèn)0。
  • pageSize 在此請求未提供,因此取默認(rèn)值10。

@RequestParam 常用屬性:

  • valuename:參數(shù)名,對應(yīng)URL中的參數(shù)名。
  • required:是否必須提供,默認(rèn) true(不提供會報(bào)錯(cuò))。上例中我們將 keyword 標(biāo)記為 false 可選。
  • defaultValue:如果請求未包含該參數(shù)則使用默認(rèn)值(注意即使標(biāo)記 required=true,有 defaultValue 也不會報(bào)錯(cuò))。

通過 @RequestParam,方法可以直接獲得解析后的參數(shù)值,無需自己從 request 獲取和轉(zhuǎn)換,大大簡化控制器代碼。

@RequestBody

簡介: @RequestBody 用于將 HTTP 請求報(bào)文體中的內(nèi)容轉(zhuǎn)換為 Java 對象并綁定到方法參數(shù)上。常用于處理 JSON 或 XML 等請求體。由 Spring MVC 提供。

作用與場景: 在 RESTful API 中,POST/PUT 等請求通常會攜帶 JSON 格式的數(shù)據(jù)作為請求體。使用 @RequestBody 注解在方法參數(shù)(通常是自定義的 DTO 類)上,Spring MVC 會利用 HttpMessageConverter 將 JSON/XML 等按需轉(zhuǎn)換為對應(yīng)的對象實(shí)例。適用于需要從請求正文獲取復(fù)雜對象的場景。與之對應(yīng),返回值或方法上使用 @ResponseBody(或 @RestController)可將對象序列化為響應(yīng)。

使用示例:

@PostMapping("/users")
public ResponseEntity<String> addUser(@RequestBody UserDTO userDto) {
    // userDto 已自動綁定了請求JSON的數(shù)據(jù)
    userService.save(userDto);
    return ResponseEntity.ok("User added successfully");
}

假設(shè)客戶端發(fā)送 POST 請求至 /users,請求體為:

{ "name": "Tom", "email": "tom@example.com" }

Spring MVC 會根據(jù) @RequestBody UserDTO userDto

  • 讀取請求體 JSON,
  • 將其轉(zhuǎn)換為 UserDTO 對象(要求有適當(dāng)?shù)膶傩院蛃etter)。
  • 然后傳遞給控制器方法使用。

方法處理后返回成功響應(yīng)。使用 @RequestBody,開發(fā)者無需手動解析 JSON,提高了開發(fā)效率并減少出錯(cuò)。

注意: @RequestBody 默認(rèn)要求請求體存在,否則報(bào)錯(cuò)。如果希望在請求體為空時(shí)處理為 null,可以設(shè)置 required=false。對于 GET 請求一般不使用 @RequestBody(GET沒有主體或主體被忽略)。

@ResponseBody

簡介: @ResponseBody 注解用于將控制器方法的返回值直接作為 HTTP 響應(yīng)內(nèi)容輸出,而不是解析為視圖名稱。由 Spring MVC 提供??梢詷?biāo)注在方法上或(較少見)標(biāo)注在類上(類上標(biāo)注相當(dāng)于對該類所有方法應(yīng)用此行為)。

作用與場景: @ResponseBody 常用于 AJAX 接口或 RESTful 方法,需要返回 JSON、XML或純文本等給客戶端,而非頁面。當(dāng)方法標(biāo)注該注解后,Spring 會將返回對象通過合適的 HttpMessageConverter 轉(zhuǎn)換為 JSON/XML 或其他格式寫入響應(yīng)流。例如返回一個(gè)對象會自動序列化為 JSON 字符串。@RestController 注解實(shí)際上已經(jīng)包含了 @ResponseBody 效果,所以在使用 @RestController 時(shí)無需再標(biāo)注此注解在每個(gè)方法上。

使用示例:

@Controller
public class StatusController {
    @GetMapping("/ping")
    @ResponseBody
    public String ping() {
        return "OK";
    }
    @GetMapping("/status")
    @ResponseBody
    public Map<String, Object> status() {
        Map<String, Object> info = new HashMap<>();
        info.put("status", "UP");
        info.put("timestamp", System.currentTimeMillis());
        return info;
    }
}

對于上例:

  • /ping 請求返回純文本 “OK” 給客戶端。
  • /status 請求返回一個(gè) Map,Spring 會將其轉(zhuǎn)換為 JSON,如:{"status":"UP","timestamp":1638346953000}。

因?yàn)槭褂玫氖瞧胀ǖ?@Controller 類,所以需要在每個(gè)方法上添加 @ResponseBody 來指示直接返回內(nèi)容。如果改用 @RestController 則可以省略這些注解。@ResponseBody 常用于快速測試接口或者在需要精確控制輸出內(nèi)容時(shí)使用。

@CrossOrigin

簡介: @CrossOrigin 注解用于配置跨域資源共享 (CORS)。由 Spring Web 提供,可標(biāo)注在類或方法上。它允許來自不同域名的客戶端訪問被標(biāo)注的資源。

作用與場景: 當(dāng)前端和后端分屬不同域(例如前端React開發(fā)服務(wù)器 http://localhost:3000,后端 http://localhost:8080)時(shí),瀏覽器會攔截跨域請求。使用 @CrossOrigin 可以在服務(wù)端指定允許跨域的來源、方法、頭信息等,從而使瀏覽器允許調(diào)用??梢葬槍φ麄€(gè)控制器類統(tǒng)一配置(類上標(biāo)注)或針對特定方法(方法上標(biāo)注)配置不同跨域策略。

使用示例:

@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "http://localhost:3000")
public class ApiController {
    @GetMapping("/data")
    public Data getData() { ... }
    @PostMapping("/submit")
    @CrossOrigin(origins = "http://example.com", methods = RequestMethod.POST)
    public void submitData(@RequestBody Data data) { ... }
}

類上標(biāo)注的 @CrossOrigin(origins = "http://localhost:3000") 表示允許來自 http://localhost:3000 的跨域請求訪問該控制器的所有接口。submitData 方法上單獨(dú)標(biāo)注了一個(gè)不同的 @CrossOrigin,表示對于 /api/submit 接口,允許來自 http://example.com 的 POST 請求跨域訪問(不受類上通用配置限制)。@CrossOrigin 還可設(shè)置允許的請求頭、是否發(fā)送憑證等,通過參數(shù)如 allowedHeaders, allowCredentials 等配置。使用這個(gè)注解,開發(fā)者不必在全局Web配置中配置 CorsRegistry,可以就近管理跨域策略。

@ExceptionHandler

簡介: @ExceptionHandler 用于在控制器中定義異常處理方法的注解。由 Spring MVC 提供。通過指定要處理的異常類型,當(dāng)控制器方法拋出該異常時(shí),轉(zhuǎn)而由標(biāo)注了 @ExceptionHandler 的方法來處理。

作用與場景: 為了避免將異常堆棧暴露給客戶端或者在每個(gè)控制器方法中編寫重復(fù)的 try-catch,可以使用 @ExceptionHandler集中處理。例如處理表單校驗(yàn)異常返回友好錯(cuò)誤信息、處理全局異常返回統(tǒng)一格式響應(yīng)等。@ExceptionHandler 通常與 @ControllerAdvice(后述)配合,用于全局異常處理;也可以直接在本控制器內(nèi)部定義專門的異常處理方法。

使用示例:

@Controller
@RequestMapping("/orders")
public class OrderController {
    @GetMapping("/{id}")
    public String getOrder(@PathVariable Long id, Model model) {
        Order order = orderService.findById(id);
        if(order == null) {
            throw new OrderNotFoundException(id);
        }
        model.addAttribute("order", order);
        return "orderDetail";
    }
    // 本控制器專門處理 OrderNotFoundException
    @ExceptionHandler(OrderNotFoundException.class)
    public String handleNotFound(OrderNotFoundException ex, Model model) {
        model.addAttribute("error", "訂單不存在,ID=" + ex.getOrderId());
        return "orderError";
    }
}

OrderController 中,getOrder 方法如果找不到訂單,會拋出自定義的 OrderNotFoundException。下方用 @ExceptionHandler(OrderNotFoundException.class) 標(biāo)注了 handleNotFound 方法來處理這種異常:當(dāng)異常拋出后,控制器不會繼續(xù)執(zhí)行原流程,而是進(jìn)入該方法。方法可以接收異常對象,以及 Model 等參數(shù),處理后返回一個(gè)視圖名 "orderError" 顯示錯(cuò)誤信息。

通過 @ExceptionHandler,控制器內(nèi)部的異常處理邏輯與正常業(yè)務(wù)邏輯解耦,代碼清晰且易于維護(hù)。

@ControllerAdvice

簡介: @ControllerAdvice 是全局控制器增強(qiáng)注解。由 Spring MVC 提供,用于定義一個(gè)全局異常處理或全局?jǐn)?shù)據(jù)綁定的切面類。標(biāo)注該注解的類可以包含多個(gè) @ExceptionHandler 方法,用于處理應(yīng)用所有控制器拋出的異常;也可以包含 @ModelAttribute@InitBinder 方法對所有控制器生效。

作用與場景: 當(dāng)需要對所有控制器統(tǒng)一處理某些邏輯時(shí),使用 @ControllerAdvice 非常方便。典型用法是結(jié)合 @ExceptionHandler 作為全局異常處理器,比如攔截所有 Exception 返回通用錯(cuò)誤響應(yīng),或分類處理不同異常類型返回不同狀態(tài)碼。提供模塊: Spring MVC。

使用示例:

@ControllerAdvice
public class GlobalExceptionHandler {
    // 處理所有異常的fallback
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ResponseEntity<String> handleException(Exception ex) {
        // 記錄日志
        ex.printStackTrace();
        // 返回通用錯(cuò)誤響應(yīng)
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                             .body("Internal Server Error: " + ex.getMessage());
    }
    // 處理特定異常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public ResponseEntity<List<String>> handleValidationException(MethodArgumentNotValidException ex) {
        List<String> errors = ex.getBindingResult().getAllErrors()
                                .stream()
                                .map(ObjectError::getDefaultMessage)
                                .collect(Collectors.toList());
        return ResponseEntity.badRequest().body(errors);
    }
}

GlobalExceptionHandler 類使用 @ControllerAdvice 聲明后,Spring 會將其中標(biāo)注了 @ExceptionHandler 的方法應(yīng)用到整個(gè)應(yīng)用的控制器:

  • 第一個(gè)方法捕獲所有未被其它更專門處理的異常,打印棧Trace并返回500錯(cuò)誤提示。
  • 第二個(gè)方法專門處理參數(shù)校驗(yàn)失敗異常,提取錯(cuò)誤信息列表并返回400狀態(tài)和錯(cuò)誤列表。

此外,可以在 @ControllerAdvice 類中定義 @ModelAttribute 方法,為所有控制器請求添加模型數(shù)據(jù)(如公共下拉選項(xiàng)),或定義 @InitBinder 方法,注冊全局屬性編輯器等。@ControllerAdvice 可以通過屬性限制只應(yīng)用于某些包或注解的控制器,但全局異常處理通常都是應(yīng)用全局的。

通過 @ControllerAdvice,我們實(shí)現(xiàn)了 AOP 式的全局控制器邏輯抽取,使各控制器關(guān)注自身業(yè)務(wù),將通用邏輯集中處理,保持代碼整潔。

七、數(shù)據(jù)訪問與事務(wù)注解

在使用 Spring 管理數(shù)據(jù)持久化層時(shí),會涉及到 JPA/Hibernate 等注解定義實(shí)體,以及 Spring 提供的事務(wù)管理注解等。

@Entity

簡介: @Entity 是 Java Persistence API (JPA) 的注解(jakarta.persistence.Entity),用于將一個(gè)類聲明為 JPA 實(shí)體。Spring Boot 通常通過 JPA/Hibernate 來操作數(shù)據(jù)庫,因此定義模型時(shí)會用到它。@Entity 注解的類對應(yīng)數(shù)據(jù)庫中的一張表。

作用與場景: 標(biāo)記為 @Entity 的類將由 JPA 實(shí)現(xiàn)(例如 Hibernate)管理,其實(shí)例可映射到數(shù)據(jù)庫記錄。必須提供主鍵(用 @Id 標(biāo)注),可選地用 @Table 指定表名,不指定則默認(rèn)表名為類名。提供模塊: JPA 規(guī)范,由 Hibernate 等實(shí)現(xiàn)。在 Spring Boot 中,引入 spring-boot-starter-data-jpa 會自動掃描 @Entity 類并創(chuàng)建表結(jié)構(gòu)(結(jié)合DDL生成策略)。

使用示例:

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;              // 主鍵
    @Column(name = "username", length = 50, nullable = false, unique = true)
    private String username;      // 用戶名列
    @Column(nullable = false)
    private String password;      // 密碼列
    // getters & setters ...
}

User 類被 @Entity 注解標(biāo)記為持久化實(shí)體,對應(yīng)數(shù)據(jù)庫表users(由@Table指定,若不指定默認(rèn)表名User)。字段上:

  • id@Id 標(biāo)識為主鍵,@GeneratedValue 指定主鍵生成策略(自增)。
  • username@Column 細(xì)化映射:列名指定為username,長度50,非空且唯一。
  • password 僅用了 @Column(nullable=false),列名默認(rèn)為屬性名。

定義好實(shí)體后,可以使用 Spring Data JPA 的倉庫接口來自動生成常用查詢(見下文 @Repository@Query)。Spring Boot 啟動時(shí)若開啟DDL-auto,會根據(jù)實(shí)體定義自動在數(shù)據(jù)庫創(chuàng)建或更新表結(jié)構(gòu)。

@Table

簡介: @Table 是 JPA 注解(jakarta.persistence.Table),配合 @Entity 使用,用于指定實(shí)體映射的數(shù)據(jù)庫表信息,如表名、schema、catalog等。

作用與場景: 默認(rèn)情況下,實(shí)體類名即表名。若數(shù)據(jù)庫表名與類名不同,或者需要定義 schema,使用 @Table 注解非常必要。也能定義唯一約束等。提供模塊: JPA。

使用示例:

@Entity
@Table(name = "T_USER", schema = "public", uniqueConstraints = {
    @UniqueConstraint(columnNames = "email")
})
public class User {
    // ...
}

該實(shí)體指定映射到 public 模式下的 T_USER 表,并聲明 email 列上有唯一約束。@Table 的屬性:

  • name:表名。
  • schema/catalog:所屬 schema 或 catalog 名稱。
  • uniqueConstraints:唯一約束定義。

@Id

簡介: @Id 是 JPA 注解(jakarta.persistence.Id),指定實(shí)體類的主鍵字段。每個(gè) @Entity 必須有且只有一個(gè)屬性使用 @Id 注解。可配合 @GeneratedValue 一起使用定義主鍵生成策略。

作用與場景: 標(biāo)記主鍵后,JPA 會將該字段作為數(shù)據(jù)庫表的主鍵列。支持基本類型或包裝類型,或 java.util.UUID 等。提供模塊: JPA。

使用示例:

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    // ... 其他字段
}

如上,id 字段為主鍵,使用自動生成策略。常見的 GenerationType

  • IDENTITY:數(shù)據(jù)庫自增字段(MySQL的AUTO_INCREMENT等)。
  • SEQUENCE:使用數(shù)據(jù)庫序列(需要定義序列,Oracle等DB適用)。
  • AUTO:讓 JPA 自動選擇合適策略。
  • TABLE:使用一個(gè)數(shù)據(jù)庫表模擬序列。

@GeneratedValue

簡介: @GeneratedValue 是 JPA 注解(jakarta.persistence.GeneratedValue),與 @Id 聯(lián)用,表示主鍵的生成方式。可指定策略 strategy 和生成器 generator。

作用與場景: 根據(jù)數(shù)據(jù)庫和需求選擇主鍵生成策略。比如 MySQL 用 IDENTITY 讓數(shù)據(jù)庫自增,Oracle 用 SEQUENCE 指定序列名稱等。提供模塊: JPA。

使用示例:

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq")
@SequenceGenerator(name = "user_seq", sequenceName = "user_sequence", allocationSize = 1)
private Long id;

上例中,使用序列生成器:

  • @SequenceGenerator 定義名為 “user_seq” 的序列生成器,映射數(shù)據(jù)庫序列 “user_sequence”。
  • @GeneratedValue 引用該生成器并使用 SEQUENCE 策略。每次插入U(xiǎn)ser時(shí)會從序列獲取下一個(gè)值作為ID。

對于常用的 AUTO 或 IDENTITY 策略,在大多數(shù)情況下只需簡單標(biāo)注 @GeneratedValue(strategy = GenerationType.IDENTITY) 等,無需額外生成器配置。

@Column

簡介: @Column 是 JPA 注解(jakarta.persistence.Column),用于定義實(shí)體字段與數(shù)據(jù)庫表列的映射細(xì)節(jié)??梢圆皇褂?,如果不標(biāo)注,JPA 默認(rèn)按屬性名映射列名(可能做小寫下劃線轉(zhuǎn)換,視實(shí)現(xiàn)而定)。

作用與場景: @Column 可指定列名、數(shù)據(jù)類型長度、是否允許NULL、是否唯一等約束。對于日期時(shí)間類型,還可指定 columnDefinitionTemporal 等,控制SQL類型。提供模塊: JPA。

使用示例:

@Column(name = "email", length = 100, nullable = false, unique = true)
private String email;

如上,將字段 email 映射為名為 email 的列(其實(shí)和默認(rèn)同名,但明確指出),長度100,非空且唯一。使用 @Column 可以清晰地將實(shí)體和數(shù)據(jù)庫字段對應(yīng)起來。

@Repository – 見上文第二部分

(此處補(bǔ)充:) 在數(shù)據(jù)訪問層,@Repository 標(biāo)注的接口或類通常與 Spring Data JPA 搭配使用。如一個(gè)接口 UserRepository extends JpaRepository<User, Long> 上加 @Repository(實(shí)際上 Spring Data JPA 的接口已經(jīng)隱式有這個(gè)語義),Spring 會為其生成實(shí)現(xiàn)并交由容器管理。@Repository 除了提供組件掃描和異常轉(zhuǎn)換外,本身沒有其他方法屬性。

@Transactional

簡介: @Transactional 是 Spring 提供的聲明式事務(wù)管理注解(org.springframework.transaction.annotation.Transactional)??蓸?biāo)注在類或方法上,表示其中的數(shù)據(jù)庫操作應(yīng)當(dāng)在一個(gè)事務(wù)中執(zhí)行。Spring 將在運(yùn)行時(shí)提供事務(wù)支持,如開始、提交或回滾事務(wù)。

作用與場景: 數(shù)據(jù)庫操作需要事務(wù)保障數(shù)據(jù)一致性,例如同時(shí)更新多張表,要么全部成功要么全部失敗。使用 @Transactional 可以在不手動編程式管理事務(wù)的情況下,由框架自動處理。典型應(yīng)用:

  • Service 層的方法需要原子性,則加上 @Transactional,Spring會在進(jìn)入方法時(shí)開啟事務(wù),方法成功返回則提交,如有異常則回滾。
  • 也可加在類上,表示類中所有公有方法都事務(wù)管理。

提供模塊: Spring ORM/Transaction 模塊,需要相應(yīng)的事務(wù)管理器(DataSourceTransactionManager 或 JpaTransactionManager 等)配置。Spring Boot 自動根據(jù)數(shù)據(jù)源配置合適的事務(wù)管理器。

使用示例:

@Service
public class AccountService {
    @Autowired
    private AccountRepository accountRepo;
    @Autowired
    private AuditService auditService;
    @Transactional
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        // 扣減轉(zhuǎn)出賬戶余額
        accountRepo.decreaseBalance(fromId, amount);
        // 增加轉(zhuǎn)入賬戶余額
        accountRepo.increaseBalance(toId, amount);
        // 記錄轉(zhuǎn)賬流水
        auditService.logTransfer(fromId, toId, amount);
        // 方法結(jié)束時(shí),Spring自動提交事務(wù)。如發(fā)生運(yùn)行時(shí)異常則自動回滾。
    }
}

transfer 方法標(biāo)注了 @Transactional,因此上述三個(gè)數(shù)據(jù)庫操作將處于同一個(gè)事務(wù)中:如果任何一步拋出未經(jīng)捕獲的異常(默認(rèn)僅RuntimeException和Error會導(dǎo)致回滾,可通過 rollbackFor 屬性更改回滾規(guī)則),所有已執(zhí)行的數(shù)據(jù)庫更新都會回滾,保持?jǐn)?shù)據(jù)一致性。如果全部成功,則提交事務(wù),將更新真正持久化。事務(wù)傳播行為隔離級別等也可以通過注解屬性配置,例如 @Transactional(propagation=Propagation.REQUIRES_NEW) 開啟新事務(wù),@Transactional(isolation=Isolation.SERIALIZABLE) 設(shè)置高隔離級別等,視業(yè)務(wù)需求而定。

注意: 使用 @Transactional 時(shí),需要確保啟用了 Spring 的事務(wù)支持(見下文 @EnableTransactionManagement),Spring Boot 會自動在有數(shù)據(jù)源時(shí)啟用事務(wù)管理。所以在 Boot 場景下通常不需要額外配置即可使用。

@JsonFormat

簡介: @JsonFormat 是 Jackson 提供的序列化/反序列化格式化注解(com.fasterxml.jackson.annotation.JsonFormat)??勺饔迷谧侄?、方法(getter / setter)或類型上,用于自定義日期-時(shí)間、數(shù)字、布爾等屬性在 JSON ←→ Java 轉(zhuǎn)換時(shí)的形態(tài)、時(shí)區(qū)與本地化設(shè)置。

作用與場景:

  • 日期時(shí)間格式化:將 Date / LocalDateTime 等類型格式化為固定字符串(例如 yyyy-MM-dd HH:mm:ss)并指定時(shí)區(qū),避免前后端默認(rèn)時(shí)區(qū)不一致導(dǎo)致時(shí)間偏移。
  • 數(shù)字 / 布爾形態(tài)控制:可把布爾值序列化成 0/1,或把 InstantLocalDateTime 轉(zhuǎn)成數(shù)值時(shí)間戳(shape = NUMBER)等。
  • 與 Bean Validation 協(xié)同:在 DTO 中同時(shí)配合 @DateTimeFormat / 校驗(yàn)注解,可保持前后端格式完全一致。
  • 優(yōu)先級:字段級 @JsonFormat 會覆蓋 ObjectMapper 的全局日期格式配置,適用于單獨(dú)字段需要特殊格式的場景。

使用示例:

@Data
public class OrderDTO {
    private Long id;
    // 1. 指定日期-時(shí)間格式 + 時(shí)區(qū)
    @JsonFormat(shape = JsonFormat.Shape.STRING,
                pattern = "yyyy-MM-dd HH:mm:ss",
                timezone = "GMT+8")
    private LocalDateTime createdAt;
    // 2. 以秒級時(shí)間戳輸出
    @JsonFormat(shape = JsonFormat.Shape.NUMBER)
    private Instant eventTime;
    // 3. 布爾值改為 0 / 1
    @JsonFormat(shape = JsonFormat.Shape.NUMBER)
    private Boolean paid;
}

@Getter

簡介: @Getter 是 Lombok 提供的生成器注解(lombok.Getter)。編譯期自動為被注解的類或字段生成 public getter 方法,省去手寫樣板代碼。

作用與場景:

  • 簡化 POJO / DTO 編寫:一個(gè)注解即可為所有字段(或單獨(dú)字段)生成讀取方法,保持類體簡潔。
  • 與框架集成:Spring / Jackson / Hibernate 等框架依賴 getter 讀取屬性時(shí)可直接使用 Lombok 生成的方法。

使用示例:

@Getter          // 為所有字段生成 getter
public class UserVO {
    private Long id;
    @Getter(AccessLevel.NONE) // 不生成該字段的 getter
    private String password;
    // 也可在字段級別加 @Getter 僅生成單個(gè)方法
}

依賴:開發(fā)環(huán)境需引入 lombok 依賴,并在 IDE 中安裝 Lombok 插件或開啟 Annotation Processing。

@Setter

簡介: @Setter 同樣由 Lombok 提供(lombok.Setter),自動為類或字段生成 public setter 方法。

作用與場景:

  • 可變對象賦值:在需要修改字段值、或框架反射注入時(shí)使用。
  • 粒度控制:可通過 AccessLevel 設(shè)置方法可見性(如 @Setter(AccessLevel.PROTECTED)),或僅在特定字段上使用,避免暴露全部可寫接口。

使用示例:

@Getter
@Setter               // 為所有字段生成 setter
public class ProductVO {
    private Long id;
    @Setter(AccessLevel.PRIVATE) // 僅類內(nèi)部可修改
    private BigDecimal price;
    // price 的 setter 為 private,其余字段的 setter 為 public
}

@ToString

簡介: @ToString 亦由 Lombok 提供(lombok.ToString)。在編譯期生成 toString() 方法,自動拼接字段名和值,支持包含/排除特定字段、隱藏敏感信息等。

作用與場景:

  • 調(diào)試與日志:快速輸出對象內(nèi)容而不必手寫 toString()。
  • 避免敏感字段泄漏:可用 @ToString.Exclude 排除字段,或在注解上設(shè)置 callSuper = true 包含父類字段。
  • 鏈?zhǔn)阶⒔?/strong>:常與 @Getter/@Setter/@EqualsAndHashCode 等一起使用,快速生成完整數(shù)據(jù)類。

使用示例:

@Getter
@Setter
@ToString(exclude = "password")          // 排除 password
public class AccountVO {
    private String username;
    @ToString.Exclude
    private String password;             // 或者字段級排除
    private LocalDateTime lastLogin;
}
/*
輸出示例:
AccountVO(username=admin, lastLogin=2025-05-10T20:30:00)
*/

在生產(chǎn)日志中輸出對象時(shí)務(wù)必排除敏感信息;@ToString 支持 exclude 數(shù)組或字段級 @ToString.Exclude 精細(xì)控制。

@EnableTransactionManagement

簡介: @EnableTransactionManagement 注解用于開啟 Spring 對 事務(wù)注解(如 @Transactional)的支持。由 Spring 提供(org.springframework.transaction.annotation.EnableTransactionManagement)。一般加在配置類上。

作用與場景: 在非 Spring Boot 場景下,使用 @Transactional 前通常需要在 Java 配置類上加此注解或在 XML 中配置 <tx:annotation-driven/> 來啟用事務(wù) AOP。它會注冊事務(wù)管理相關(guān)的后置處理器,檢測 @Transactional 并在運(yùn)行時(shí)生成代理。提供模塊: Spring Tx。

使用示例:

@Configuration
@EnableTransactionManagement
public class TxConfig {
    @Bean
    public PlatformTransactionManager txManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource); // 配置事務(wù)管理器
    }
}

上例通過 @EnableTransactionManagement 啟用了注解驅(qū)動的事務(wù)管理,并顯式聲明了數(shù)據(jù)源事務(wù)管理器 Bean。在 Spring Boot 中,如果引入了 spring-boot-starter-jdbc 或 JPA,會自動配置 DataSourceTransactionManagerJpaTransactionManager,且默認(rèn)啟用事務(wù)支持,無需手動添加該注解(Boot 會自動應(yīng)用)。但在需要更細(xì)粒度控制事務(wù)行為時(shí),了解此注解的作用仍然重要。

@Query

簡介: @Query 注解由 Spring Data JPA 提供(org.springframework.data.jpa.repository.Query),用于在 Repository 方法上定義自定義的 JPQL或原生SQL 查詢。

作用與場景: 雖然 Spring Data JPA 可以通過解析方法名自動生成查詢,但是復(fù)雜或特殊的查詢可以用 @Query 手工編寫JPQL語句。還可以通過設(shè)置 nativeQuery=true 使用原生SQL。當(dāng)自動生成無法滿足需求,或?yàn)榱诵阅苁褂脭?shù)據(jù)庫特定查詢時(shí),用此注解非常有用。提供模塊: Spring Data JPA。

使用示例:

public interface UserRepository extends JpaRepository<User, Long> {
    // 使用JPQL查詢
    @Query("SELECT u FROM User u WHERE u.email = ?1")
    User findByEmail(String email);
    // 使用原生SQL查詢
    @Query(value = "SELECT * FROM users u WHERE u.status = :status LIMIT :limit", nativeQuery = true)
    List<User> findTopByStatus(@Param("status") int status, @Param("limit") int limit);
}

在上面的倉庫接口中:

  • findByEmail 方法使用 JPQL 查詢根據(jù)郵箱獲取用戶。?1 表示第一個(gè)參數(shù)。
  • findTopByStatus 方法使用原生SQL查詢指定狀態(tài)的用戶若干條,使用命名參數(shù) :status:limit。需要搭配 @Param 注解綁定參數(shù)值。

Spring Data JPA 在運(yùn)行時(shí)會解析這些注解并生成相應(yīng)實(shí)現(xiàn)代碼執(zhí)行查詢。@Query 能大大提升查詢的靈活性,但要注意JPQL語句的正確性以及原生SQL的可移植性。

八、面向切面編程(AOP)注解

Spring AOP 提供了強(qiáng)大的面向切面編程功能,可以通過注解定義橫切關(guān)注點(diǎn),如日志記錄、性能監(jiān)控、權(quán)限檢查等。主要的 AOP 注解包括:

@Aspect

簡介: @Aspect 注解用于將一個(gè)類聲明為切面類。由 AspectJ 提供(Spring AOP 使用 AspectJ 注解風(fēng)格)。標(biāo)記為 @Aspect 的類內(nèi)部可以定義切點(diǎn)和通知,實(shí)現(xiàn) AOP 功能。需要配合 Spring AOP 使用。

作用與場景: 切面類匯總了橫切邏輯,例如日志切面、安全切面等。一個(gè)切面類里通常包含若干通知方法(@Before、@After等)和切點(diǎn)定義(@Pointcut)。Spring 在運(yùn)行時(shí)會根據(jù)切面定義生成代理對象,將橫切邏輯織入目標(biāo)對象的方法調(diào)用。提供模塊: org.aspectj.lang.annotation.Aspect(需要 spring-boot-starter-aop 或 Spring AOP 模塊依賴)。

使用示例:

@Aspect
@Component  // 切面本身也需注冊為Bean
public class LoggingAspect {
    // 切點(diǎn)定義:匹配 service 包下所有類的公共方法
    @Pointcut("execution(public * com.example.service..*(..))")
    public void serviceMethods() {}
    // 前置通知:在滿足切點(diǎn)的方法執(zhí)行之前執(zhí)行
    @Before("serviceMethods()")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Entering: " + joinPoint.getSignature());
    }
    // 后置通知:無論方法正?;虍惓=Y(jié)束都會執(zhí)行
    @After("serviceMethods()")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("Exiting: " + joinPoint.getSignature());
    }
}

上面定義了一個(gè)日志切面類 LoggingAspect

  • 使用 @Aspect 標(biāo)記為切面類,結(jié)合 @Component 使其成為 Spring Bean。
  • serviceMethods() 方法使用 @Pointcut 定義了一個(gè)切點(diǎn),表達(dá)式 "execution(public * com.example.service..*(..))" 表示匹配 com.example.service 包及子包下所有類的任意公共方法執(zhí)行。切點(diǎn)方法本身沒有實(shí)現(xiàn),僅作標(biāo)識。
  • logBefore 方法使用 @Before("serviceMethods()") 注解,表示在執(zhí)行匹配 serviceMethods() 切點(diǎn)的任意方法之前,先執(zhí)行該通知。
  • 通過 JoinPoint 參數(shù)可以獲取被調(diào)用方法的信息。logAfter 方法使用 @After("serviceMethods()"),表示目標(biāo)方法執(zhí)行完成后(無論成功與否)執(zhí)行。輸出方法簽名的退出日志。

為使上述 AOP 生效,需要啟用 Spring 對 AspectJ 切面的支持。Spring Boot 自動配置已經(jīng)啟用了 AOP(如果引入了 starter-aop,默認(rèn)會開啟 @AspectJ 支持),在非 Boot 環(huán)境可能需要在配置類上添加 @EnableAspectJAutoProxy 注解來開啟代理機(jī)制。如果未啟用,@Aspect 注解不會生效??傊?,@Aspect 注解的類定義了 AOP 的橫切邏輯,是實(shí)現(xiàn)日志、事務(wù)、權(quán)限等橫切關(guān)注點(diǎn)的關(guān)鍵。

@Pointcut

簡介: @Pointcut 用于定義一個(gè)切點(diǎn)表達(dá)式,以命名方式重用切點(diǎn)。由 AspectJ 注解提供。通常是一個(gè)簽名為 void 且無實(shí)現(xiàn)的方法注解,用于給切點(diǎn)命名。

作用與場景: 切點(diǎn)定義了哪些連接點(diǎn)(Join Point)需要織入切面邏輯。通過@Pointcut可以將復(fù)雜的切點(diǎn)表達(dá)式進(jìn)行抽象,方便在多個(gè)通知上引用,避免重復(fù)書寫表達(dá)式。提供模塊: AspectJ。

使用示例:

@Aspect
@Component
public class SecurityAspect {
    // 切點(diǎn):controller 包下的所有含 @GetMapping 注解的方法
    @Pointcut("within(@org.springframework.web.bind.annotation.RestController *) && @annotation(org.springframework.web.bind.annotation.GetMapping)")
    public void allGetEndpoints() {}
    @Before("allGetEndpoints()")
    public void checkAuthentication() {
        // 執(zhí)行權(quán)限驗(yàn)證邏輯
        if (!SecurityContext.isAuthenticated()) {
            throw new SecurityException("User not authenticated");
        }
    }
}

此處,SecurityAspect 定義了一個(gè)切點(diǎn) allGetEndpoints(),通過 @Pointcut 注解的表達(dá)式指定:凡是標(biāo)注了@RestController的類中,標(biāo)注了@GetMapping的方法,都是切點(diǎn)。然后在 @Before("allGetEndpoints()") 通知中引用這個(gè)切點(diǎn),執(zhí)行權(quán)限檢查。如果當(dāng)前用戶未認(rèn)證則拋出異常阻止方法執(zhí)行。

切點(diǎn)表達(dá)式語言十分豐富,可以基于執(zhí)行方法簽名(execution)、注解(@annotation, within 等)、this/target對象等進(jìn)行匹配組合。通過適當(dāng)?shù)那悬c(diǎn)定義,可以靈活地選擇哪些點(diǎn)應(yīng)用橫切邏輯。

@Before

簡介: @Before 定義一個(gè)前置通知(Advice),即在目標(biāo)方法執(zhí)行之前執(zhí)行的切面方法。它由 AspectJ 提供(org.aspectj.lang.annotation.Before)。需要在 @Aspect 切面類中使用,注解的值是一個(gè)切點(diǎn)表達(dá)式或命名切點(diǎn)。

作用與場景: 前置通知通常用于在方法調(diào)用前執(zhí)行一些檢查、日志或準(zhǔn)備工作。例如權(quán)限驗(yàn)證(見上例)、記錄方法開始日志、在方法執(zhí)行前設(shè)置環(huán)境(如初始化 ThreadLocal)等。在目標(biāo)方法之前執(zhí)行,不影響目標(biāo)方法參數(shù)和執(zhí)行結(jié)果,只作附加操作。

使用示例: 參考前述 LoggingAspectSecurityAspect 中的 @Before 用法。在 LoggingAspect 中:

@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) { ... }

這表示在匹配 serviceMethods() 切點(diǎn)的每個(gè)目標(biāo)方法執(zhí)行前調(diào)用 logBefore 方法。JoinPoint 參數(shù)可以獲取方法簽名、參數(shù)等信息,用于日志輸出。

@Before 通知不能阻止目標(biāo)方法執(zhí)行(除非拋出異常)。如果在通知中拋異常,目標(biāo)方法將不會執(zhí)行且異常向上拋出。因此一般前置通知不故意拋異常(權(quán)限驗(yàn)證除外,驗(yàn)證失敗則通過異常中斷執(zhí)行)。

@After

簡介: @After 定義一個(gè)后置通知,即在目標(biāo)方法執(zhí)行結(jié)束后執(zhí)行的切面方法,無論目標(biāo)方法正常返回還是拋出異常都會執(zhí)行(類似 finally block)。由 AspectJ 提供。

作用與場景: 常用于清理資源、記錄方法結(jié)束日志等操作。例如在方法完成后記錄執(zhí)行時(shí)間(需要結(jié)合開始時(shí)間),或確保某些線程上下文數(shù)據(jù)被清除。不關(guān)心方法的結(jié)果,只要離開方法就執(zhí)行通知。

使用示例: 參考 LoggingAspect 中:

@After("serviceMethods()")
public void logAfter(JoinPoint joinPoint) { ... }

不管 serviceMethods() 匹配的方法成功或異常返回,都會執(zhí)行 logAfter。可以用它來打印離開方法的日志。

如果需要根據(jù)方法是否拋異常做區(qū)分,可以使用 @AfterReturning@AfterThrowing(詳見下文)。@After 通常用來放置最終執(zhí)行的操作,比如解鎖資源,不管成功失敗都要執(zhí)行的。

@AfterReturning

簡介: @AfterReturning 定義一個(gè)返回通知,在目標(biāo)方法成功返回后執(zhí)行(未拋異常)??梢圆东@返回值。由 AspectJ 提供。

作用與場景: 當(dāng)需要獲取目標(biāo)方法的返回結(jié)果進(jìn)行處理時(shí),可使用 @AfterReturning。例如日志中記錄返回值,或者根據(jù)返回值做后續(xù)動作。若目標(biāo)方法拋異常則不會執(zhí)行此通知。注解可指定 returning 屬性綁定返回值。

使用示例:

@AfterReturning(pointcut = "execution(* com.example.service.OrderService.placeOrder(..))", returning = "result")
public void logOrderResult(Object result) {
    System.out.println("Order placed result: " + result);
}

此通知針對 OrderService.placeOrder 方法執(zhí)行,如果其正常完成,則將返回值綁定到 result 形參并打印日志。比如 result 可能是訂單ID或確認(rèn)對象。若 placeOrder 拋異常,則該通知不執(zhí)行。

@AfterThrowing

簡介: @AfterThrowing 定義一個(gè)異常通知,在目標(biāo)方法拋出指定異常后執(zhí)行。由 AspectJ 提供。可捕獲異常對象。

作用與場景: 用于統(tǒng)一處理或記錄目標(biāo)方法拋出的異常,例如記錄錯(cuò)誤日志、發(fā)送告警等??梢灾付?throwing 屬性將異常綁定到參數(shù)。只在有未捕獲異常時(shí)執(zhí)行,正常返回不執(zhí)行。

使用示例:

@AfterThrowing(pointcut = "execution(* com.example..*.*(..))", throwing = "ex")
public void logException(Exception ex) {
    System.err.println("Exception in method: " + ex.getMessage());
}

該切面方法會在應(yīng)用中任何未捕獲的異常拋出時(shí)執(zhí)行,打印異常信息。ex 參數(shù)即目標(biāo)方法拋出的異常對象(可以指定具體異常類型過濾,如 throwing="ex" throwing=RuntimeException.class)。

通過 @AfterThrowing 可以集中處理異常情況,例如對特定異常進(jìn)行額外處理(如事務(wù)補(bǔ)償或資源回收),或統(tǒng)一記錄。

@Around

簡介: @Around 定義一個(gè)環(huán)繞通知,它包裹了目標(biāo)方法的執(zhí)行。由 AspectJ 提供。環(huán)繞通知最為強(qiáng)大,可以在方法執(zhí)行前后都進(jìn)行處理,并可決定是否、如何執(zhí)行目標(biāo)方法(通過 ProceedingJoinPoint 調(diào)用)。

作用與場景: 可以用來計(jì)算執(zhí)行時(shí)間、控制方法執(zhí)行(比如實(shí)現(xiàn)自定義注解的權(quán)限校驗(yàn)并決定是否調(diào)用原方法)、修改方法的返回值甚至攔截異常。@Around 通知需要顯式調(diào)用 proceed() 才會執(zhí)行目標(biāo)方法,如果不調(diào)用則目標(biāo)方法不執(zhí)行。這讓我們有機(jī)會在調(diào)用前后插入邏輯,甚至改變執(zhí)行流程。

使用示例:

@Around("execution(* com.example.service.*.*(..))")
public Object measureExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
    long start = System.currentTimeMillis();
    Object result;
    try {
        result = pjp.proceed(); // 執(zhí)行目標(biāo)方法
    } finally {
        long end = System.currentTimeMillis();
        System.out.println(pjp.getSignature() + " executed in " + (end - start) + "ms");
    }
    return result;
}

這個(gè)環(huán)繞通知為 service 包下所有方法計(jì)算執(zhí)行時(shí)間:

  • 在調(diào)用目標(biāo)方法前記錄開始時(shí)間。
  • 通過 pjp.proceed() 執(zhí)行目標(biāo)方法,將返回結(jié)果保存。
  • 方法執(zhí)行后計(jì)算時(shí)間差并打印。
  • 將目標(biāo)方法的返回值返回,保證調(diào)用流程正常進(jìn)行。

如果目標(biāo)方法拋異常,proceed() 會拋出異常到外層(如上例沒有 catch,finally執(zhí)行后異常繼續(xù)拋出)。也可以在環(huán)繞通知中捕獲異常并處理,甚至返回替代結(jié)果,從而吞掉異常(視業(yè)務(wù)需要謹(jǐn)慎處理)。

@Around 通知還可以實(shí)現(xiàn)諸如自定義注解攔截功能,例如檢查方法上是否有某注解,有則執(zhí)行特殊邏輯等,靈活性最高。

@EnableAspectJAutoProxy

簡介: @EnableAspectJAutoProxy 是 Spring 提供的注解(org.springframework.context.annotation.EnableAspectJAutoProxy),用于開啟基于注解的 AOP 支撐。它會啟用 AspectJ 注解的自動代理機(jī)制。

作用與場景: 在純 Spring 配置中,需要在配置類上添加此注解才能使前述 @Aspect 切面生效(等同于 XML 配置中的 <aop:aspectj-autoproxy/>)。Spring Boot 在引入 AOP 起步依賴時(shí),默認(rèn)已經(jīng)啟用了該功能 ,因此多數(shù)情況下無需顯式添加。但了解這個(gè)注解有助于在需要調(diào)整 AOP 代理選項(xiàng)時(shí)使用(比如 proxyTargetClass=true 強(qiáng)制使用CGLIB代理)。

使用示例:

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig {
    // 切面類Bean或通過@Component掃描切面類
}

這將在容器中搜索 @Aspect 注解的類,自動創(chuàng)建代理。proxyTargetClass=true 強(qiáng)制使用類代理而不是接口代理。默認(rèn)為 false,即如果有實(shí)現(xiàn)接口則用JDK動態(tài)代理。這一點(diǎn)在需要代理沒有接口的類或者希望統(tǒng)一使用CGLIB代理時(shí)可以設(shè)置。

總結(jié)而言,Spring AOP 的注解允許我們以聲明方式實(shí)現(xiàn)橫切邏輯,將日志、性能監(jiān)控、安全檢查等與業(yè)務(wù)代碼分離,提升模塊化和可維護(hù)性。

九、異步與定時(shí)任務(wù)注解

Spring 提供了對多線程異步任務(wù)和定時(shí)調(diào)度的支持,只需通過注解即可開啟這些功能。

@Async

簡介: @Async 注解用于將某個(gè)方法聲明為異步執(zhí)行。由 Spring 提供(org.springframework.scheduling.annotation.Async)。標(biāo)注該注解的方法會在調(diào)用時(shí)由 Spring 異步執(zhí)行,而不是同步阻塞當(dāng)前線程。通常需要配合 @EnableAsync 一起使用。

作用與場景: 當(dāng)某些操作不需要同步完成、可以在后臺線程執(zhí)行時(shí),用 @Async 能簡化并發(fā)編程。例如發(fā)送郵件、短信通知,執(zhí)行耗時(shí)的計(jì)算而不阻塞主流程,或并行調(diào)用多個(gè)外部服務(wù)等。Spring 會基于 TaskExecutor (默認(rèn)SimpleAsyncTaskExecutor)調(diào)度異步方法。方法可以返回 voidFuture/CompletableFuture 以便獲取結(jié)果。

使用示例:

@Service
public class NotificationService {
    @Async
    public void sendEmail(String to, String content) {
        // 模擬發(fā)送郵件的耗時(shí)操作
        try {
            Thread.sleep(5000);
            System.out.println("Email sent to " + to);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
@SpringBootApplication
@EnableAsync  // 開啟異步支持
public class MyApp { ... }

NotificationService 中,sendEmail 標(biāo)注了 @Async,因此當(dāng)它被調(diào)用時(shí),Spring 會從線程池中拿出一個(gè)線程來異步執(zhí)行該方法,原調(diào)用方線程不必等待5秒。需要在應(yīng)用主類或配置類上添加 @EnableAsync 以激活異步處理能力。使用默認(rèn)配置時(shí),Spring 會使用一個(gè)簡單線程池執(zhí)行,也可以通過定義 Executor Bean 并加上 @Async("beanName") 來指定特定線程池。

調(diào)用異步方法示例:

@RestController
public class OrderController {
    @Autowired
    private NotificationService notificationService;
    @PostMapping("/order")
    public ResponseEntity<String> placeOrder(@RequestBody Order order) {
        orderService.process(order);
        // 異步發(fā)送通知
        notificationService.sendEmail(order.getEmail(), "Your order is placed.");
        return ResponseEntity.ok("Order received");
    }
}

placeOrder 方法調(diào)用了 sendEmail,因?yàn)楹笳呤钱惒降?,所?placeOrder 在觸發(fā)郵件發(fā)送后會立即返回響應(yīng),郵件發(fā)送在另一個(gè)線程進(jìn)行,不影響接口響應(yīng)時(shí)間。異步調(diào)用的異常需特別處理,可以使用 AsyncUncaughtExceptionHandler 或返回 Future 在調(diào)用方監(jiān)聽。總之,@Async 大大方便了將任務(wù)異步化。

@EnableAsync

簡介: @EnableAsync 是 Spring 提供的注解(org.springframework.scheduling.annotation.EnableAsync),用于開啟對 @Async 注解的處理。加在配置類或主啟動類上,激活 Spring 異步方法執(zhí)行的能力。

作用與場景: 類似于 @EnableAspectJAutoProxy 之于 AOP,對于異步也需要顯式開啟。Spring Boot 自動配置通常不會主動開啟異步,所以需要開發(fā)者添加此注解。提供模塊: Spring Context 調(diào)度任務(wù)支持。

使用示例: 見上方,將 @EnableAsync 放在 @SpringBootApplication 類上或獨(dú)立的配置類上均可。

啟用后,Spring 容器會搜索應(yīng)用中標(biāo)注了 @Async 的 Bean 方法,并通過代理的方式調(diào)用線程池執(zhí)行它們。默認(rèn)的執(zhí)行器可以通過定義 TaskExecutor Bean 來覆蓋。如:

@Bean(name = "taskExecutor")
public Executor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(5);
    executor.setMaxPoolSize(10);
    executor.initialize();
    return executor;
}

定義名為 “taskExecutor” 的執(zhí)行器后,Spring @Async 會自動使用它(因?yàn)槟J(rèn)執(zhí)行器名稱就是 taskExecutor)。也可在 @Async 注解的參數(shù)中指定一個(gè)自定義執(zhí)行器 Bean 名稱。

@Scheduled

簡介: @Scheduled 注解用于將方法標(biāo)記為定時(shí)任務(wù)。由 Spring 提供(org.springframework.scheduling.annotation.Scheduled)??梢酝ㄟ^ cron 表達(dá)式或固定間隔等配置何時(shí)運(yùn)行該方法。需要配合 @EnableScheduling 開啟調(diào)度支持。

作用與場景: 當(dāng)需要周期性地執(zhí)行某段代碼時(shí),例如每隔一段時(shí)間檢查庫存,每天夜間生成報(bào)表等,可以使用 @Scheduled 注解而不需要借助外部的調(diào)度框架。Spring 容器會在后臺線程按指定計(jì)劃調(diào)用這些方法。支持多種調(diào)度配置:

  • cron 表達(dá)式:通過 Cron 定義復(fù)雜時(shí)間計(jì)劃。
  • 固定速率 fixedRate:以上一次開始時(shí)間為基準(zhǔn),間隔固定毫秒執(zhí)行。
  • 固定延遲 fixedDelay:以上一次完成時(shí)間為基準(zhǔn),延遲固定毫秒執(zhí)行。
  • 可選屬性如 initialDelay 等設(shè)置啟動延遲。

使用示例:

@Component
public class ReportTask {
    @Scheduled(cron = "0 0 2 * * ?")
    public void generateDailyReport() {
        // 每天凌晨2點(diǎn)生成報(bào)告
        System.out.println("Generating daily report at " + LocalDate.now());
        // ... 報(bào)表生成邏輯
    }
    @Scheduled(fixedRate = 60000)
    public void checkSystemHealth() {
        // 每隔60秒檢查系統(tǒng)健康
        System.out.println("Health check at " + Instant.now());
        // ... 健康檢查邏輯
    }
}

這里,ReportTask 類中:

  • generateDailyReport() 使用 cron="0 0 2 * * ?",表示每天2:00執(zhí)行(Cron表達(dá)式:“秒 分 時(shí) 日 月 周”,“?”表示不指定周幾)。這個(gè)方法將在主線程之外的調(diào)度線程按計(jì)劃調(diào)用。
  • checkSystemHealth() 使用 fixedRate=60000 表示每60秒執(zhí)行一次,不論上次執(zhí)行多長時(shí)間,都按固定頻率觸發(fā)。若上次尚未執(zhí)行完,新周期到了默認(rèn)不會并發(fā)執(zhí)行(調(diào)度器會等待),但可以通過配置 Scheduler 實(shí)現(xiàn)并發(fā)。

為了使 @Scheduled 生效,需要在配置類上添加 @EnableScheduling(見下文)。Spring Boot 應(yīng)用通常也需要手動加這一注解。定時(shí)任務(wù)執(zhí)行由 Spring 的 TaskScheduler(默認(rèn)SingleThreadScheduler)驅(qū)動,可能需要注意任務(wù)不應(yīng)長時(shí)間阻塞,否則會影響后續(xù)任務(wù)調(diào)度??勺远x線程池 TaskScheduler 以提高并發(fā)度。

@EnableScheduling

簡介: @EnableScheduling 注解用于開啟 Spring 對定時(shí)任務(wù)調(diào)度的支持(org.springframework.scheduling.annotation.EnableScheduling)。添加在配置類或主類上。

作用與場景: 沒有這個(gè)注解,@Scheduled 等注解不會被識別處理。啟用后,Spring 容器會啟動一個(gè)調(diào)度線程池,定時(shí)調(diào)用標(biāo)記的方法。提供模塊: Spring Context 定時(shí)任務(wù)支持。

使用示例:

@SpringBootApplication
@EnableScheduling
public class Application { ... }

@EnableScheduling 放在啟動類上即可激活調(diào)度機(jī)制。然后所有 @Scheduled 注解的方法都會按照配置的計(jì)劃執(zhí)行。Spring Boot 不會自動開啟定時(shí)任務(wù)支持,因?yàn)橛械膽?yīng)用可能不需要調(diào)度功能,所以必須顯式聲明。

如果需要自定義調(diào)度器,可以定義 Scheduler Bean 或 TaskScheduler Bean。默認(rèn)使用單線程執(zhí)行所有定時(shí)任務(wù),若多個(gè)任務(wù)需要并行,建議提供 ThreadPoolTaskScheduler Bean。

通過 @Async@Scheduled 這組注解,Spring 讓并發(fā)編程和任務(wù)調(diào)度變得非常容易,不再需要顯式創(chuàng)建線程或使用外部調(diào)度平臺,在應(yīng)用內(nèi)部即可完成這些邏輯。

十、緩存注解

Spring 提供了便捷的緩存機(jī)制,通過注解即可實(shí)現(xiàn)方法級緩存,把方法調(diào)用結(jié)果存儲起來,避免重復(fù)計(jì)算或數(shù)據(jù)庫查詢。

@EnableCaching

簡介: @EnableCaching 注解用于開啟 Spring 對緩存注解的支持(org.springframework.cache.annotation.EnableCaching)。通常加在配置類或主類上,激活緩存管理能力。

作用與場景: 開啟后,Spring 會自動配置一個(gè)緩存管理器(可基于內(nèi)存、EhCache、Redis等,取決于依賴配置),并掃描應(yīng)用中的緩存注解(如 @Cacheable 等),在運(yùn)行時(shí)用AOP代理實(shí)現(xiàn)緩存邏輯。提供模塊: Spring Cache。

使用示例:

@SpringBootApplication
@EnableCaching
public class Application { ... }

這樣,Spring Boot 就會自動根據(jù) classpath 中的緩存庫選擇緩存實(shí)現(xiàn)(如有 spring-boot-starter-cache 默認(rèn)用 ConcurrentMapCache 簡單實(shí)現(xiàn);如果引入 spring-boot-starter-redis 則使用 RedisCacheManager 等)。確保在使用緩存注解前調(diào)用了 @EnableCaching,否則緩存注解不會生效。

@Cacheable

簡介: @Cacheable 用于標(biāo)記方法,將其返回結(jié)果緩存起來。由 Spring 提供(org.springframework.cache.annotation.Cacheable)。再次調(diào)用該方法時(shí),如果傳入?yún)?shù)相同且緩存中有結(jié)果,則直接返回緩存而不執(zhí)行方法。

作用與場景: 典型用于讀取操作緩存,例如從數(shù)據(jù)庫查詢數(shù)據(jù)后緩存,下次查詢相同參數(shù)可以直接返回緩存值,提高性能。@Cacheable 需要指定緩存的名稱(cacheName)以及緩存鍵(key),可以是 SpEL 表達(dá)式。默認(rèn)鍵根據(jù)所有參數(shù)自動生成(需參數(shù)可哈希)。

提供模塊: Spring Cache。

使用示例:

@Service
public class ProductService {
    @Cacheable(value = "products", key = "#id")
    public Product getProductById(Long id) {
        // 假設(shè)這里有復(fù)雜計(jì)算或慢速數(shù)據(jù)庫查詢
        System.out.println("Loading product " + id + " from DB...");
        return productRepository.findById(id).orElse(null);
    }
}

配置 @Cacheable(value="products", key="#id")

  • value 指定緩存的名字叫 “products”(類似分類,可對應(yīng)不同緩存存儲)。
  • key="#id" 表示使用方法參數(shù) id 作為緩存鍵。

第一次調(diào)用 getProductById(1L) 時(shí),會打印“Loading product 1 from DB…”并查詢數(shù)據(jù)庫,然后結(jié)果緩存到名為 “products” 的緩存中,鍵為 1。第二次調(diào)用 getProductById(1L),Spring 檢測到相同鍵在緩存中有值,直接返回緩存,不執(zhí)行方法主體,因此不會再打印那條日志。

@Cacheable 還有一些屬性:

  • condition:滿足條件時(shí)才緩存或才查緩存,如 condition="#id > 10".
  • unless:方法執(zhí)行完后判斷,如果滿足條件則不緩存結(jié)果,如 unless="#result == null".
  • sync:是否在并發(fā)場景下同步,只讓一個(gè)線程計(jì)算緩存,其它等待。

@CachePut

簡介: @CachePut 注解用于將方法返回值直接放入緩存,但與 @Cacheable 不同的是,它始終執(zhí)行方法,不會跳過。它通常用于更新緩存數(shù)據(jù)。由 Spring 提供。

作用與場景: 當(dāng)執(zhí)行了修改操作后,希望緩存與數(shù)據(jù)庫同步更新,可使用 @CachePut 標(biāo)記修改方法,使其結(jié)果及時(shí)寫入緩存。這樣后續(xù)再讀緩存可以得到最新值。提供模塊: Spring Cache。

使用示例:

@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
    System.out.println("Updating product " + product.getId());
    return productRepository.save(product);
}

每次調(diào)用 updateProduct,都會執(zhí)行保存操作并返回更新后的 Product。@CachePut 注解確保無論如何,這個(gè)返回的 Product 對象會以其 id 作為鍵,存入 “products” 緩存(覆蓋舊值)。因此,即便之前通過 @Cacheable 緩存過舊的 Product 數(shù)據(jù),這里也會更新緩存,使之與數(shù)據(jù)庫一致。值得注意的是,@CachePut 不會影響方法執(zhí)行(總會執(zhí)行方法),它只是在返回后把結(jié)果寫緩存。

@CacheEvict

簡介: @CacheEvict 注解用于移除緩存。標(biāo)記在方法上,可以在方法執(zhí)行前或后將指定 key 或整個(gè)緩存清除。由 Spring 提供。

作用與場景: 當(dāng)數(shù)據(jù)被刪除或改變且緩存不再有效時(shí),需要清除緩存。例如刪除一個(gè)記錄后,需要把對應(yīng)緩存刪掉;批量更新后,可以選擇清空整個(gè)緩存。@CacheEvict 支持指定 key 或設(shè)置 allEntries=true 清空整個(gè)命名緩存。提供模塊: Spring Cache。

使用示例:

@CacheEvict(value = "products", key = "#id")
public void deleteProduct(Long id) {
    System.out.println("Deleting product " + id);
    productRepository.deleteById(id);
}

調(diào)用 deleteProduct(5) 時(shí),@CacheEvict 會使緩存 “products” 中鍵為5的條目無效(刪除)。默認(rèn)地,它在方法成功執(zhí)行后清除緩存。如果希望無論方法是否成功都清除,可設(shè)定 beforeInvocation=true,那將在方法進(jìn)入時(shí)就清除(防止方法拋異常緩存未清)。allEntries=true 則可以不顧鍵,直接清空整個(gè)緩存空間。例如:

@CacheEvict(value = "products", allEntries = true)
public void refreshAllProducts() { ... }

會清除 “products” 緩存的所有條目。

通過 @CacheEvict@CachePut,我們可以維護(hù)緩存與底層數(shù)據(jù)的一致性。

注意: 使用緩存注解要求配置正確的 CacheManager 和緩存存儲。Spring Boot 默認(rèn)使用簡單的內(nèi)存緩存(ConcurrentMapCacheManager)用于開發(fā)測試。生產(chǎn)中常結(jié)合 Redis、Ehcache等實(shí)現(xiàn),更換實(shí)現(xiàn)通常無需改動注解,只需配置 CacheManager Bean。

十一、事件監(jiān)聽注解

Spring 提供了應(yīng)用內(nèi)事件發(fā)布-訂閱機(jī)制,支持松耦合的消息通信。通過注解可以方便地訂閱事件。

@EventListener

簡介: @EventListener 是 Spring 4.2+ 引入的注解(org.springframework.context.event.EventListener),用于將任意 Spring Bean 的方法標(biāo)識為事件監(jiān)聽器。當(dāng)有匹配的事件發(fā)布時(shí)(實(shí)現(xiàn) ApplicationEvent 或自定義事件對象),該方法會被調(diào)用。相比實(shí)現(xiàn) ApplicationListener 接口,注解方式更簡潔。

作用與場景: 在應(yīng)用內(nèi),不同組件之間可以通過發(fā)布事件進(jìn)行解耦通訊。例如用戶注冊后發(fā)布一個(gè) UserRegisteredEvent,由其他監(jiān)聽器監(jiān)聽來發(fā)送歡迎郵件或統(tǒng)計(jì)指標(biāo)。使用 @EventListener,方法簽名定義了它感興趣的事件類型,也可以通過 condition 屬性設(shè)置過濾條件(比如只處理某字段滿足條件的事件)。提供模塊: Spring Context 事件機(jī)制。

使用示例:

// 定義事件類(可以繼承 ApplicationEvent 也可以是普通類)
public class UserRegisteredEvent /* extends ApplicationEvent */ {
    private final User user;
    public UserRegisteredEvent(User user) { this.user = user; }
    public User getUser() { return user; }
}
// 發(fā)布事件的組件
@Service
public class UserService {
    @Autowired 
    private ApplicationEventPublisher eventPublisher;
    public void register(User user) {
        // ... 保存用戶邏輯
        // 發(fā)布事件
        eventPublisher.publishEvent(new UserRegisteredEvent(user));
    }
}
// 監(jiān)聽事件的組件
@Component
public class WelcomeEmailListener {
    @EventListener
    public void handleUserRegistered(UserRegisteredEvent event) {
        User newUser = event.getUser();
        // 發(fā)送歡迎郵件
        System.out.println("Sending welcome email to " + newUser.getEmail());
        // ... 實(shí)際發(fā)送郵件邏輯
    }
}

流程說明:

  • UserService.register 方法在新用戶注冊成功后,通過 ApplicationEventPublisher 發(fā)布了一個(gè) UserRegisteredEvent 事件。Spring Boot 默認(rèn)通過 ApplicationEventPublisher 將事件發(fā)布到應(yīng)用上下文。
  • WelcomeEmailListener 是一個(gè)普通組件(被 @Component 掃描)。其中方法 handleUserRegistered 標(biāo)注了 @EventListener,且參數(shù)是 UserRegisteredEvent。這表明它訂閱此類型事件。當(dāng)事件被發(fā)布時(shí),Spring 檢測到存在匹配的監(jiān)聽方法,便調(diào)用該方法并將事件對象傳入。
  • 監(jiān)聽方法運(yùn)行,完成發(fā)送歡迎郵件的功能。

這樣,發(fā)送郵件的邏輯和用戶服務(wù)邏輯完全解耦,只通過事件聯(lián)系。如果以后不需要發(fā)送郵件,只需移除監(jiān)聽器,而不影響用戶注冊流程。另外,可以很容易地新增其它監(jiān)聽,如統(tǒng)計(jì)注冊用戶數(shù)的監(jiān)聽器,而不需要修改 UserService。

@EventListener 還支持 condition 屬性使用 SpEL 表達(dá)式進(jìn)行事件內(nèi)容過濾。例如:

@EventListener(condition = "#event.user.vip")
public void handleVipUserRegistered(UserRegisteredEvent event) { ... }

僅當(dāng)用戶是VIP時(shí)才處理。這種細(xì)粒度控制進(jìn)一步增強(qiáng)了事件機(jī)制的靈活性。

需要注意,默認(rèn)事件監(jiān)聽器在發(fā)布線程內(nèi)同步執(zhí)行。如果想異步處理事件,可以結(jié)合 @Async 注解,將監(jiān)聽方法異步執(zhí)行(前提是已啟用 @EnableAsync)?;蛘呤褂?Spring 5 提供的 ApplicationEventMulticaster 配置為異步模式。

ApplicationListener 接口 (替代方案)

說明:@EventListener 出現(xiàn)之前,Spring 使用實(shí)現(xiàn) ApplicationListener<E> 接口的方式來監(jiān)聽事件。雖然這不是注解,但與事件注解結(jié)合使用時(shí)值得一提。任何 Spring Bean 實(shí)現(xiàn)了 ApplicationListener<MyEvent>,當(dāng) MyEvent 發(fā)布時(shí)其 onApplicationEvent 方法會被調(diào)用。自 Spring 4.2 起推薦使用 @EventListener 代替,更加簡潔。

使用示例:

@Component
public class StatsListener implements ApplicationListener<UserRegisteredEvent> {
    @Override
    public void onApplicationEvent(UserRegisteredEvent event) {
        // 統(tǒng)計(jì)用戶注冊
        metrics.increment("user.register.count");
    }
}

這個(gè)監(jiān)聽器無須注解,Spring根據(jù)泛型自動注冊。但相比注解方式,它需要一個(gè)獨(dú)立的類實(shí)現(xiàn)接口,不如 @EventListener 可以直接用任意方法方便。而且一個(gè)類只能實(shí)現(xiàn)對一種事件的監(jiān)聽,要監(jiān)聽多種事件需要寫多個(gè)類或使用一些if判斷,不如注解靈活。因此現(xiàn)在開發(fā)中更多使用 @EventListener。

綜上,Spring 的事件模型通過發(fā)布訂閱實(shí)現(xiàn)了應(yīng)用內(nèi)部的解耦協(xié)作。@EventListener 極大降低了使用門檻,使得監(jiān)聽事件就像寫普通方法一樣便捷。配合異步能力,還能實(shí)現(xiàn)類似消息隊(duì)列的效果,用于不太關(guān)鍵的異步通知等場景。

十二、測試相關(guān)注解

Spring 為了方便編寫測試,特別是針對 Spring MVC 或 JPA等組件的測試,提供了一系列注解來簡化配置測試上下文。

@SpringBootTest

簡介: @SpringBootTest 是 Spring Boot 測試框架提供的注解(org.springframework.boot.test.context.SpringBootTest),用于在測試類上,表示啟動一個(gè)完整的 Spring Boot 應(yīng)用上下文進(jìn)行集成測試。

作用與場景: 標(biāo)注此注解的測試類在運(yùn)行時(shí)會通過 Spring Boot 引導(dǎo)啟動應(yīng)用(除非配置特定屬性使其部分引導(dǎo)),這意味著:

  • 會掃描并創(chuàng)建所有 Bean,加載完整應(yīng)用上下文。
  • 提供對 Bean 的依賴注入支持,使測試類可以直接 @Autowired 需要的 Bean 進(jìn)行集成測試。

它常用于需要測試多個(gè)層級協(xié)同工作的場景,例如驗(yàn)證服務(wù)層和倉庫層交互,或者整個(gè)請求流程。

使用示例:

@SpringBootTest
class ApplicationTests {
    @Autowired
    private UserService userService;
    @Test
    void testUserRegistration() {
        User user = new User("alice");
        userService.register(user);
        // 驗(yàn)證注冊結(jié)果,比如檢查數(shù)據(jù)庫或事件發(fā)布效果
        assertNotNull(user.getId());
    }
}

這個(gè)測試類使用 @SpringBootTest,則測試運(yùn)行時(shí) Spring Boot 會啟動應(yīng)用上下文并注入 UserService Bean,測試方法里可以直接調(diào)用業(yè)務(wù)代碼進(jìn)行驗(yàn)證。@SpringBootTest 還可以指定啟動端口、環(huán)境等參數(shù),或通過 properties 覆蓋配置,比如:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

用于啟動嵌入式服務(wù)器在隨機(jī)端口,以進(jìn)行 Web 集成測試。

@WebMvcTest

簡介: @WebMvcTest 是用于測試 Spring MVC 控制器的注解(org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest)。它會啟動一個(gè)精簡的 Spring MVC 環(huán)境,只包含Web相關(guān)的Bean,如@Controller、@RestController等,以及MVC配置,而不加載整個(gè)應(yīng)用上下文。

作用與場景: 主要用于單元測試控制器。默認(rèn)只掃描 @Controller@RestController 等 Web 層組件,以及必要的配置(如 Jackson 轉(zhuǎn)換、Validator)。不會加載服務(wù)層、倉庫層Bean,除非通過配置指定。這樣測試運(yùn)行速度快且聚焦于MVC層邏輯。常配合 MockMvc (Spring提供的模擬MVC請求的工具)使用進(jìn)行控制器的請求/響應(yīng)測試。

使用示例:

@WebMvcTest(controllers = UserController.class)
class UserControllerTests {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private UserService userService;  // 將UserService模擬
    @Test
    void testGetUser() throws Exception {
        User user = new User(1L, "Bob");
        // 定義當(dāng)userService.findById(1)被調(diào)用時(shí)返回user對象
        given(userService.findById(1L)).willReturn(user);
        mockMvc.perform(get("/users/1"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.name").value("Bob"));
    }
}

此測試類標(biāo)注 @WebMvcTest(UserController.class)

  1. Spring Boot 會僅啟動與 UserController 相關(guān)的 MVC 組件,如 UserController 本身,MVC配置,序列化組件等。
  2. UserService 因?yàn)椴皇茾Controller組件,不會自動加載。因此使用了 @MockBean 注解(見后)創(chuàng)建一個(gè)模擬的 UserService Bean,將其注入到 UserController 中,避免涉及真實(shí)的服務(wù)層邏輯。
  3. 測試使用 MockMvc 發(fā)起GET請求到 /users/1,并斷言返回狀態(tài)200和返回JSON中的name字段為"Bob"。由于我們預(yù)先通過 given(userService.findById(1L)) 指定了模擬行為,所以控制器調(diào)用userService時(shí)會得到我們構(gòu)造的user對象。

通過這種方式,不需要啟動整個(gè)應(yīng)用,也不需要真實(shí)數(shù)據(jù)庫等,就能測試控制器映射、參數(shù)解析、返回結(jié)果等。@WebMvcTest 提供了對Spring MVC各方面的支持(如可以自動配置MockMvc)。

@DataJpaTest

簡介: @DataJpaTest 是用于測試 JPA 持久層的注解(org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest)。它會啟動一個(gè)只包含 JPA 相關(guān)組件的 Spring 應(yīng)用上下文,例如實(shí)體管理器、Spring Data JPA 倉庫、嵌入式數(shù)據(jù)庫等。

作用與場景: 主要用于單元測試 Repository 層。它會:

  • 配置嵌入式內(nèi)存數(shù)據(jù)庫(如H2)用于測試,除非明確指定其他DataSource。
  • 掃描 @Entity 實(shí)體和 Spring Data Repository 接口并注冊。
  • 不加載 web、安全等其他非持久層Bean,以加快測試速度。
  • 默認(rèn)使用事務(wù)包裝每個(gè)測試,并在結(jié)束時(shí)回滾,保證測試隔離。

使用示例:

@DataJpaTest
class UserRepositoryTests {
    @Autowired
    private UserRepository userRepository;
    @Test
    void testFindByEmail() {
        // 準(zhǔn)備測試數(shù)據(jù)
        User user = new User();
        user.setEmail("test@example.com");
        user.setName("Test");
        userRepository.save(user);
        // 執(zhí)行查詢方法
        User found = userRepository.findByEmail("test@example.com");
        assertEquals("Test", found.getName());
    }
}

這里 @DataJpaTest 將自動配置一個(gè)內(nèi)存數(shù)據(jù)庫并初始化 JPA 環(huán)境。UserRepository 接口(假設(shè)繼承自 JpaRepository)會被加載為Bean。測試中先保存一個(gè)用戶,然后調(diào)用倉庫自定義方法 findByEmail 驗(yàn)證結(jié)果。由于測試結(jié)束時(shí)事務(wù)會回滾,插入的測試數(shù)據(jù)不會污染下一個(gè)測試或?qū)嶋H數(shù)據(jù)庫。

@DataJpaTest 同樣可以與 @MockBean 配合如果需要模擬一些非JPA的Bean,但是通常持久層測試不需要。也可以通過 properties 指定連接真實(shí)數(shù)據(jù)庫進(jìn)行集成測試,不過大多數(shù)情況下,使用內(nèi)存數(shù)據(jù)庫足以測試Repository邏輯。

@MockBean

簡介: @MockBean 是 Spring Boot Test 提供的注解(org.springframework.boot.test.mock.mockito.MockBean),用于在 Spring 測試上下文中添加一個(gè)由 Mockito 模擬的Bean,并替換掉容器中原本該類型的Bean(如果有)。常用于在 Web層或服務(wù)層測試中,模擬依賴的Bean行為。

作用與場景: 當(dāng)測試的目標(biāo)Bean有依賴,而我們不想測試依賴的真實(shí)邏輯(可能復(fù)雜或不確定),就可以用 @MockBean 來提供一個(gè)Mockito創(chuàng)建的模擬對象給容器。這比手工使用 Mockito.mock 然后手動注入更方便,因?yàn)?Spring 會自動把這個(gè)模擬Bean注入到需要它的地方。典型應(yīng)用是在 @WebMvcTest 中模擬服務(wù)層Bean,在 @SpringBootTest 中模擬外部系統(tǒng)客戶端Bean等。

使用示例:

@MockBean
private WeatherService weatherService;

WeatherService 接口模擬為一個(gè) Bean 注入容器。如果應(yīng)用上下文本來有一個(gè)該類型的Bean(比如真實(shí)的實(shí)現(xiàn)),會被模擬對象替換。這使得我們可以用 given(weatherService.getTodayWeather())... 等來預(yù)設(shè)行為。這個(gè)注解可以用在測試類的字段上(如上)、也可以用在測試方法內(nèi)參數(shù)上。

具體的用法在前面的 @WebMvcTest 示例已經(jīng)體現(xiàn)。再比如,在一個(gè)服務(wù)層測試中:

@SpringBootTest
class OrderServiceTests {
    @Autowired
    private OrderService orderService;
    @MockBean
    private PaymentClient paymentClient;  // 模擬外部支付服務(wù)客戶端
    @Test
    void testOrderPayment() {
        Order order = new Order(...);
        // 假定調(diào)用外部支付返回成功結(jié)果
        given(paymentClient.pay(order)).willReturn(new PaymentResult(true));
        boolean result = orderService.processPayment(order);
        assertTrue(result);
        // 驗(yàn)證內(nèi)部行為,如訂單狀態(tài)更新
        // ...
    }
}

這里 OrderService 依賴 PaymentClient,但我們不想真的調(diào)用外部服務(wù),于是用 @MockBean 模擬它并規(guī)定返回 PaymentResult 成功。這樣 OrderService.processPayment 執(zhí)行時(shí)實(shí)際上用的是假的 PaymentClient,但可以測試 OrderService 自身的邏輯是否正確處理了成功結(jié)果。注意: @MockBean 底層使用 Mockito,所以需要確保引入了 Mockito 相關(guān)依賴。

其他測試注解:

  • @SpringBootTest@WebMvcTest, @DataJpaTest 是 Spring Boot Test 提供的測試切面注解,此外還有類似 @WebFluxTest(測試WebFlux控制器)、@JdbcTest(測試JDBC)、@JsonTest(測試JSON序列化)等,根據(jù)需要使用。
  • JUnit本身的注解如 @Test, @BeforeEach, @AfterEach 等也在測試中大量使用(如上示例已經(jīng)用到 @Test)。雖然不屬于Spring范疇,但也算開發(fā)中常用的注解之一。

通過這些測試注解,開發(fā)者可以方便地編寫隔離的測試用例。例如只啟動Web層或持久層進(jìn)行單元測試,大大提高測試執(zhí)行速度和定位問題的精準(zhǔn)度。Spring Boot 自動配置為測試裁剪了上下文,避免加載無關(guān)bean,使測試既保持類似生產(chǎn)環(huán)境的行為,又能高效運(yùn)行。

十三、安全相關(guān)注解

Spring Security 框架提供了方法級安全控制的注解和配置注解,方便對控制器或服務(wù)方法實(shí)施權(quán)限檢查。此外還有開啟安全的配置注解等。

@EnableWebSecurity

簡介: @EnableWebSecurity 是用于開啟 Spring Security Web 安全支持的注解(org.springframework.security.config.annotation.web.configuration.EnableWebSecurity)。通常加在一個(gè)繼承 WebSecurityConfigurerAdapter(Spring Security 5.7 之前)的配置類上,或者加在包含 SecurityFilterChain Bean 的配置類上。它啟用了 Spring Security 的過濾器鏈。

作用與場景: 使用 Spring Security 時(shí),需要此注解來加載 Web 安全配置,使應(yīng)用受 Spring Security 管理。提供模塊: Spring Security Config。

使用示例:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated()
            .and().formLogin();
    }
}

上述經(jīng)典用法在 Spring Security 5.7 之前通過繼承 WebSecurityConfigurerAdapter 來配置。@EnableWebSecurity 注解開啟安全功能。Spring Boot 自動配置也會在引入 starter-security 時(shí)添加該注解,因此有時(shí)無需手動添加;但當(dāng)我們提供自定義安全配置類時(shí),一般會注明此注解。注意: Spring Security 5.7 開始,官方更推薦不繼承類而是聲明 SecurityFilterChain Bean 配合 @EnableWebSecurity 使用,但注解作用相同。

@EnableGlobalMethodSecurity (已過時(shí)) / @EnableMethodSecurity

簡介: @EnableGlobalMethodSecurity 用于開啟方法級安全注解的支持(如 @PreAuthorize, @Secured)。這是 Spring Security 舊版本使用的注解,位于 org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity。它已經(jīng)在 Spring Security 6 被替換為新的 @EnableMethodSecurity@EnableGlobalMethodSecurity 標(biāo)記為已棄用)。

作用與場景: 加在 Security 配置類上,啟用對 @PreAuthorize, @PostAuthorize, @Secured, @RolesAllowed 注解的識別??梢酝ㄟ^其屬性啟用各類注解,如 prePostEnabled=true 支持 Pre/PostAuthorize,securedEnabled=true 支持@Secured,jsr250Enabled=true 支持@RolesAllowed 等。提供模塊: Spring Security Config。

使用示例(舊):

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // ...
}

這將開啟 @PreAuthorize/@PostAuthorize(因?yàn)?prePostEnabled=true) 和 @Secured 注解(因?yàn)?securedEnabled=true)。在 Security 6 中,等價(jià)的做法是:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig { ... }

@EnableMethodSecurity 默認(rèn)就啟用了 Pre/Post,所以可以不用顯式 prePostEnabled,secured和jsr250需要明確true。

總而言之,在當(dāng)前的 Spring Boot 3 / Security 6 環(huán)境中,使用 @EnableMethodSecurity 取代 @EnableGlobalMethodSecurity 來開啟方法安全注解支持。

@PreAuthorize

簡介: @PreAuthorize 是 Spring Security 的方法級權(quán)限注解(org.springframework.security.access.prepost.PreAuthorize)。它可以用在方法或類上,在方法調(diào)用之前基于給定的表達(dá)式進(jìn)行權(quán)限驗(yàn)證。需要啟用了全局方法安全后(如上),此注解才會生效。

作用與場景: @PreAuthorize 可以檢查當(dāng)前認(rèn)證用戶是否具備某權(quán)限或角色,或者滿足SpEL表達(dá)式定義的任意條件,然后才允許方法執(zhí)行。常用于服務(wù)層或控制層方法,保護(hù)敏感操作。例如只有ADMIN角色能調(diào)用刪除用戶方法,或者只有資源擁有者才能訪問資源等。它比 @Secured 更強(qiáng)大,因?yàn)榭梢允褂肧pring EL編寫復(fù)雜的邏輯。

使用示例:

@Service
public class AccountService {
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteAccount(Long accountId) {
        // 只有ADMIN角色用戶才能執(zhí)行
        accountRepository.deleteById(accountId);
    }
    @PreAuthorize("#user.name == authentication.name or hasAuthority('SCOPE_profile')")
    public Profile getUserProfile(User user) {
        // 用戶本人或具有profile權(quán)限的可以查看
        return profileRepository.findByUser(user);
    }
}
  • deleteAccount 方法上,@PreAuthorize("hasRole('ADMIN')") 限制只有具有ROLE_ADMIN的用戶可以調(diào)用,否則會被拒絕(拋出 AccessDeniedException)。getUserProfile 方法上,使用了表達(dá)式:#user.name == authentication.name or hasAuthority('SCOPE_profile')。authentication.name 代表當(dāng)前登錄用戶名。如果傳入的 user.name 等于當(dāng)前用戶名(即查詢自己的資料),或當(dāng)前主體具有 SCOPE_profile 權(quán)限(例如 OAuth2 scope),則允許訪問。否則拒絕??梢钥吹絇reAuthorize能夠引用方法參數(shù)(通過#參數(shù)名)和安全上下文信息(authentication對象)進(jìn)行復(fù)雜判斷。

@PreAuthorize 非常靈活,也支持調(diào)用自定義權(quán)限評估方法等。但要注意權(quán)限表達(dá)式越復(fù)雜可能越難維護(hù),需要在安全和可讀性之間平衡。Spring Security官方推薦使用PreAuthorize勝過Secured,因?yàn)槠浔磉_(dá)能力更強(qiáng)。

@Secured

簡介: @Secured 是較早的 Spring Security 提供的簡單方法安全注解(org.springframework.security.access.annotation.Secured)。它指定一組允許的角色,調(diào)用該方法的用戶必須具備其中一個(gè)角色才行。需要在全局方法安全配置中啟用 securedEnabled=true 才生效。

作用與場景: 適用于簡單的基于角色的訪問控制。如果系統(tǒng)的授權(quán)模型主要基于角色,可以使用 @Secured("ROLE_X") 來保護(hù)方法。相對于 PreAuthorize,它不支持復(fù)雜表達(dá)式,只能指定角色列表。提供模塊: Spring Security(需要 @EnableMethodSecurity(securedEnabled=true) 或舊的相應(yīng)配置)。

使用示例:

@Secured("ROLE_ADMIN")
public void createUser(User user) { ... }
@Secured({"ROLE_USER", "ROLE_ADMIN"})
public Data getData() { ... }
  • createUser 方法要求調(diào)用者必須擁有 ROLE_ADMIN 角色。
  • getData 方法允許 ROLE_USERROLE_ADMIN 擁有者訪問(邏輯是OR的關(guān)系)。

如果不滿足要求,Spring Security同樣會拋出訪問拒絕異常。@Secured 內(nèi)部實(shí)際上也是通過 AOP 攔截,與 @PreAuthorize 實(shí)現(xiàn)機(jī)制類似,但因?yàn)槠涔δ苡邢?,Spring官方更推薦使用Pre/PostAuthorize。

需要留意的是,@Secured 注解中的字符串需要包含完整的角色前綴(如默認(rèn)前綴是 “ROLE_”)。如上必須寫 “ROLE_ADMIN” 而不是 “ADMIN”,除非通過配置修改了前綴策略。

@RolesAllowed

簡介: @RolesAllowed 來自 JSR-250(jakarta.annotation.security.RolesAllowed),功能與 @Secured 類似,也是指定允許訪問方法的角色列表。Spring Security 支持它,需要 jsr250Enabled=true。它和 Secured的區(qū)別主要在注解來源不同。

作用與場景: 可以作為 @Secured 的替代,用標(biāo)準(zhǔn)注解來聲明角色限制。在Spring環(huán)境下兩者效果一樣。提供模塊: JSR-250,Spring Security需啟用支持。

使用示例:

@RolesAllowed("ADMIN")
public void updateSettings(Settings settings) { ... }

這里假設(shè)已將角色前綴配置成無"ROLE_"前綴或 SecurityConfigurer里做了處理,否則 Spring Security會把 “ADMIN” 當(dāng)作角色名直接匹配 GrantedAuthority “ADMIN”。一般Secured和RolesAllowed不能混用不同前綴,否則容易出錯(cuò)。

綜上,Spring Security提供的這些注解允許我們無需在方法內(nèi)部手動檢查權(quán)限,而由框架自動在調(diào)用前進(jìn)行驗(yàn)證,符合條件才執(zhí)行。需要注意:

  • 要在配置類上開啟相應(yīng)支持(使用 @EnableMethodSecurity 或舊版 @EnableGlobalMethodSecurity)。
  • @PreAuthorize/@PostAuthorize 功能最強(qiáng),但稍復(fù)雜,@Secured/@RolesAllowed簡單直接,但只能基于角色判斷。
  • 這類注解只檢查Spring Security的上下文,對于未經(jīng)過濾器鏈保護(hù)的方法調(diào)用(比如同類中自調(diào)用方法不會觸發(fā)注解檢查,或者在無Security環(huán)境下)就不起作用。這是常見陷阱——所以帶有安全注解的方法最好不要在內(nèi)部直接調(diào)用,否則繞過了切面檢查。

十四、其他常用注解

除了上述類別,Spring & Spring Boot 中還有一些常用但未分類到的注解,例如:

  • 參數(shù)校驗(yàn)相關(guān): Spring 對 JSR 303 Bean Validation 的支持,讓我們可以在模型上使用如 @NotNull, @Size, @Valid 等注解。其中在 Controller 方法參數(shù)上使用 @Valid 可觸發(fā)校驗(yàn),并結(jié)合 @ExceptionHandler@ControllerAdvice 統(tǒng)一處理校驗(yàn)結(jié)果。
  • JSON 序列化控制:@JsonInclude(來自 Jackson)可以注解類或?qū)傩?,控制JSON序列化包含規(guī)則,例如 @JsonInclude(JsonInclude.Include.NON_NULL) 表示忽略null值字段。這在返回REST數(shù)據(jù)時(shí)很有用。
  • 條件裝配注解: Spring Boot 提供了一系列 @ConditionalOn... 注解用于自動配置(如 @ConditionalOnProperty, @ConditionalOnClass, @ConditionalOnMissingBean 等)來有條件地裝配Bean。這些主要在開發(fā)Spring Boot自動配置模塊時(shí)使用,在應(yīng)用層較少直接用到,但理解它們有助于明白Boot的裝配機(jī)制。

以上羅列的注解涵蓋了Spring核心開發(fā)中最常用的部分。從應(yīng)用啟動配置、Bean裝配,到Web層開發(fā)、數(shù)據(jù)持久化、AOP、異步、緩存、事件、測試、安全,各個(gè)方面都有簡潔的注解支持。掌握它們的用法能顯著提高開發(fā)效率,減少樣板代碼,讓我們更多關(guān)注業(yè)務(wù)邏輯實(shí)現(xiàn)。

總結(jié): Spring & Spring Boot 常用注解極大地便利了開發(fā),它們遵循“約定優(yōu)于配置”的理念,通過簡單的注解聲明即可完成以前繁瑣的XML配置或手動編碼工作。在使用時(shí)要注意啟用相應(yīng)功能的開關(guān)(如異步、事務(wù)、緩存等),理解注解背后機(jī)制(如AOP代理、運(yùn)行時(shí)處理)以避免踩坑。熟練運(yùn)用上述注解,能覆蓋大部分日常開發(fā)場景,實(shí)現(xiàn)優(yōu)雅、高效和可維護(hù)的Spring應(yīng)用。

到此這篇關(guān)于Spring Boot 常用注解整理的文章就介紹到這了,更多相關(guān)Spring Boot 常用注解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • springboot2.x集成swagger的方法示例

    springboot2.x集成swagger的方法示例

    這篇文章主要介紹了springboot2.x集成swagger的方法示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05
  • Mybatis-Plus條件構(gòu)造器的具體使用方法

    Mybatis-Plus條件構(gòu)造器的具體使用方法

    這篇文章主要介紹了Mybatis-Plus條件構(gòu)造器的具體使用方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • Java讀取DBF文件(GBK編碼)的方法

    Java讀取DBF文件(GBK編碼)的方法

    在Java開發(fā)中,有時(shí)需要讀取DBF(dBase文件)格式的數(shù)據(jù)文件,而這些文件通常采用GBK(簡體中文)編碼,本文將介紹如何使用Java讀取采用GBK編碼的DBF文件,需要的朋友可以參考下
    2024-11-11
  • idea查看class字節(jié)碼的示例代碼

    idea查看class字節(jié)碼的示例代碼

    本文給大家分享三種方法介紹idea查看class字節(jié)碼,每種方法給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧
    2025-04-04
  • spring cloud如何集成nacos配置中心

    spring cloud如何集成nacos配置中心

    這篇文章主要介紹了spring cloud如何集成nacos配置中心操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Java多線程編程中使用Condition類操作鎖的方法詳解

    Java多線程編程中使用Condition類操作鎖的方法詳解

    Condition是java.util.concurrent.locks包下的類,提供了對線程鎖的更精細(xì)的控制方法,下面我們就來看一下Java多線程編程中使用Condition類操作鎖的方法詳解
    2016-07-07
  • @RequestBody注解Ajax post json List集合數(shù)據(jù)請求400/415的處理

    @RequestBody注解Ajax post json List集合數(shù)據(jù)請求400/41

    這篇文章主要介紹了@RequestBody注解Ajax post json List集合數(shù)據(jù)請求400/415的處理方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • java?Date獲取本月的開始時(shí)間與結(jié)束時(shí)間

    java?Date獲取本月的開始時(shí)間與結(jié)束時(shí)間

    這篇文章主要為大家介紹了java?Date獲取本月的開始時(shí)間與結(jié)束時(shí)間示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2023-05-05
  • Spring?Bean后處理器詳細(xì)介紹

    Spring?Bean后處理器詳細(xì)介紹

    Bean后置處理器允許在調(diào)用初始化方法前后對Bean進(jìn)行額外的處理。可以在?Spring容器通過插入一個(gè)或多個(gè)BeanPostProcessor的實(shí)現(xiàn)來完成實(shí)例化,配置和初始化一個(gè)?bean?之后實(shí)現(xiàn)一些自定義邏輯回調(diào)方法
    2023-01-01
  • 使用@Transactional 設(shè)置嵌套事務(wù)不回滾

    使用@Transactional 設(shè)置嵌套事務(wù)不回滾

    這篇文章主要介紹了使用@Transactional 設(shè)置嵌套事務(wù)不回滾問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07

最新評論