Spring Boot 常用注解整理(最全收藏版)
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 的生命周期方法(類似于 InitializingBean
和 DisposableBean
接口或 @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.service
和 com.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)入了 SecurityConfig
和 DataConfig
兩個(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;
上面 conn1
和 conn2
將是不同的實(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。name
、apiUrl
會對應(yīng)賦值,嵌套的 pool.size
和 pool.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
非常靈活,其常用屬性:
value
或 path
:映射的 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
常用屬性:
value
或name
:參數(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í)間類型,還可指定 columnDefinition
或 Temporal
等,控制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
,或把Instant
、LocalDateTime
轉(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,會自動配置 DataSourceTransactionManager
或 JpaTransactionManager
,且默認(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é)果,只作附加操作。
使用示例: 參考前述 LoggingAspect
和 SecurityAspect
中的 @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)度異步方法。方法可以返回 void
或 Future
/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)
:
- Spring Boot 會僅啟動與
UserController
相關(guān)的 MVC 組件,如UserController
本身,MVC配置,序列化組件等。 UserService
因?yàn)椴皇茾Controller組件,不會自動加載。因此使用了@MockBean
注解(見后)創(chuàng)建一個(gè)模擬的UserService
Bean,將其注入到UserController
中,避免涉及真實(shí)的服務(wù)層邏輯。- 測試使用
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_USER
或ROLE_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)文章
Java多線程編程中使用Condition類操作鎖的方法詳解
Condition是java.util.concurrent.locks包下的類,提供了對線程鎖的更精細(xì)的控制方法,下面我們就來看一下Java多線程編程中使用Condition類操作鎖的方法詳解2016-07-07@RequestBody注解Ajax post json List集合數(shù)據(jù)請求400/41
這篇文章主要介紹了@RequestBody注解Ajax post json List集合數(shù)據(jù)請求400/415的處理方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10java?Date獲取本月的開始時(shí)間與結(jié)束時(shí)間
這篇文章主要為大家介紹了java?Date獲取本月的開始時(shí)間與結(jié)束時(shí)間示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2023-05-05使用@Transactional 設(shè)置嵌套事務(wù)不回滾
這篇文章主要介紹了使用@Transactional 設(shè)置嵌套事務(wù)不回滾問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07