Spring & Spring Boot 常用注解整理
現代的 Spring 與 Spring Boot 應用大量使用注解來簡化配置、管理組件和實現各種框架功能。本文系統整理了常用的 Spring/Spring Boot 注解,按照功能分類進行介紹。每個注解都會涵蓋其含義、提供來源、應用場景以及代碼示例,幫助開發者深入理解和快速檢索。
一、Spring Boot 核心注解
@SpringBootApplication
簡介: @SpringBootApplication
是 Spring Boot 應用的主入口注解。它標注在啟動類上,表示這是一個 Spring Boot 應用。該注解由 Spring Boot 提供(位于 org.springframework.boot.autoconfigure
包),本質上是一個組合注解,包含了 Spring Framework 和 Spring Boot 的關鍵配置注解。
作用與場景: 使用 @SpringBootApplication
標記主類后,Spring Boot 會自動進行以下配置:
- 配置類聲明: 包含了
@SpringBootConfiguration
(其本身是@Configuration
的特化),因此該類被視為配置類,可定義 Bean。 - 組件掃描: 內含
@ComponentScan
,會自動掃描該類所在包及其子包下的組件(被諸如@Component
、@Service
、@Controller
等注解標記的類),將它們注冊為 Spring 容器中的 Bean。 - 自動配置: 內含
@EnableAutoConfiguration
,根據類路徑下依賴自動配置 Spring Boot 應用。例如,若 classpath 中存在 HSQLDB 數據庫依賴,則會自動配置內存數據庫等。開發者無需手動編寫大量配置即可啟動應用。
使用示例: 創建一個 Spring Boot 主啟動類,在類上添加 @SpringBootApplication
注解,并編寫 main
方法啟動應用:
@SpringBootApplication
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}
}
上述代碼中,MyApplication
類由 @SpringBootApplication
注解標記為應用入口。運行 SpringApplication.run
后,Spring Boot 將引導啟動內嵌服務器、初始化 Spring 容器,自動掃描組件并完成配置。
注: @SpringBootApplication
提供了屬性用于定制,如 exclude
可排除特定的自動配置類。如果需要禁用某些自動配置,可以使用例如 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
來排除。
二、Spring 容器與組件注冊注解
這一類注解用于將類注冊為 Spring 容器管理的組件或定義配置,以取代傳統的 XML 配置文件,實現注解驅動的裝配。
@Component
簡介: @Component
是一個通用的組件注解,由 Spring Framework 提供(org.springframework.stereotype.Component
)。它用于將一個普通的 Java 類標識為 Spring 容器中的 Bean。被標注的類在組件掃描時會被發現并實例化,由容器統一管理生命周期。
作用與場景: 當某個類不好歸類到特定層時,可以使用 @Component
進行標注。典型場景如工具類、通用邏輯處理類等。使用 @Component
后,無需在 XML 中聲明 bean,Spring 會根據配置的掃描路徑自動將其注冊。提供模塊: Spring Context 模塊提供對組件掃描和 @Component
注解的支持。
使用示例: 定義一個組件類并演示注入:
@Component
public class MessageUtil {public String getWelcomeMessage() {return "Welcome to Spring!";}
}// 使用組件
@Service
public class GreetingService {@Autowiredprivate MessageUtil messageUtil;public void greet() {System.out.println(messageUtil.getWelcomeMessage());}
}
上例中,MessageUtil
類通過 @Component
標記成為容器 Bean,GreetingService
中使用 @Autowired
(詳見后文)將其注入,最后調用其方法。
@Service
簡介: @Service
是 @Component
的一種特化,用于標注業務邏輯層的組件(Service層)。它位于 Spring 框架的 org.springframework.stereotype
包。
作用與場景: 在分層架構中,服務層類使用 @Service
注解,使代碼含義更語義化。盡管行為上和 @Component
相同(被掃描注冊為 Bean),@Service
強調該類承擔業務服務職責。提供模塊: Spring Context,同屬于組件模型的一部分。
使用示例:
@Service
public class OrderService {public void createOrder(Order order) {// 業務邏輯:創建訂單}
}
通過 @Service
,OrderService
會被自動掃描注冊。在需要使用它的地方,例如控制層或其他服務層,可以通過依賴注入獲取該 Bean 實例。
@Repository
簡介: @Repository
是 @Component
的特化注解之一,用于標注數據訪問層組件(DAO層,或倉庫類)。定義在 Spring Framework 的 org.springframework.stereotype.Repository
包中。
作用與場景: DAO 類(例如訪問數據庫的類)使用 @Repository
注解不僅可以被掃描為容器 Bean,還能啟用異常轉換功能。Spring DAO 層會捕獲底層數據訪問異常(如 JDBC 的 SQLException
或 JPA 的異常),將其翻譯為 Spring 統一的DataAccessException
體系,從而簡化異常處理。換句話說,如果一個類標注為 @Repository
,Spring 在為其創建代理時會自動處理持久化異常,將原始異常轉為 Spring 的非檢查型數據訪問異常,以提高健壯性。另外,標注了 @Repository
的類可以被 @Autowired
等注解自動裝配到其他地方。
使用示例:
@Repository
public class UserDao {@Autowiredprivate 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翻譯為DataAccessExceptionthrow e;}}
}
上例中,UserDao
使用 @Repository
注解,使其成為容器管理的DAO組件。Spring 自動為其提供異常轉換功能:如果 JDBC 操作拋出 SQLException
,會被翻譯為 DataAccessException
(RuntimeException),調用處可以統一處理。標注后也允許通過 @Autowired
注入到服務層使用。
@Controller
簡介: @Controller
是 Spring MVC 的控制層組件注解,同樣派生自 @Component
。由 Spring Web MVC 模塊提供(org.springframework.stereotype.Controller
),用于標識一個類是Web MVC 控制器,負責處理 HTTP 請求并返回視圖或響應。
作用與場景: 在 Web 應用程序中,@Controller
注解的類會被 DispatcherServlet 識別為控制器,用于映射請求URL、封裝模型數據并返回視圖名。通常配合視圖模板(如 Thymeleaf、JSP)返回頁面。如果需要直接返回 JSON 數據,可以配合 @ResponseBody
或直接使用 @RestController
(后者見下文)。
使用示例:
@Controller
public class HomeController {@RequestMapping("/home")public String homePage(Model model) {model.addAttribute("msg", "Hello Spring MVC");return "home"; // 返回視圖名,由視圖解析器解析為頁面}
}
上述 HomeController
使用 @Controller
標記,提供一個映射 “/home” 請求的處理方法。返回值 "home"
代表視圖邏輯名,框架會根據配置解析到具體的頁面(如 home.html
)。如果我們在類上使用了 @Controller
,框架在啟動時會自動注冊相應的映射。
@RestController
簡介: @RestController
是 Spring 提供的組合注解,等價于同時在類上使用 @Controller
和 @ResponseBody
。它主要由 Spring Web 模塊提供,用于RESTful Web服務的控制器。
作用與場景: 標注 @RestController
的類會被識別為控制器,并且其每個處理方法的返回值會直接作為 HTTP 響應體輸出,而不是作為視圖名稱解析。適用于需要返回 JSON、XML 等數據的場景,比如 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) {// 直接接收JSON反序列化為User對象,處理后返回return userService.save(user);}
}
UserApiController
使用 @RestController
注解,其方法返回字符串和對象將直接通過消息轉換器寫入響應(例如字符串作為純文本,User
對象會序列化為 JSON)。不需要再在每個方法上加 @ResponseBody
,使代碼更加簡潔。通常在開發 REST API 時,都使用 @RestController
來定義控制器。
@Configuration
簡介: @Configuration
用于聲明一個配置類,由 Spring Framework 提供(org.springframework.context.annotation.Configuration
)。配置類可以包含若干個帶有 @Bean
注解的方法,以定義 Bean 并交由 Spring 容器管理。@Configuration
本身也是 @Component
,因此配置類也會被組件掃描注冊。
作用與場景: 在 Java Config 風格的應用中,@Configuration
相當于傳統 XML 配置文件。用于定義 Beans、設置依賴注入規則等。Spring Boot 應用的某些自動配置也是以配置類形式存在。提供模塊: Spring Context。
使用示例:
@Configuration
public class AppConfig {@Beanpublic DataSource dataSource() {// 配置數據源 Bean,例如使用 HikariCP 數據源HikariDataSource ds = new HikariDataSource();ds.setJdbcUrl("jdbc:mysql://localhost:3306/test");ds.setUsername("root");ds.setPassword("123456");return ds;}@Beanpublic UserService userService() {// 將 UserService 注冊為 Bean,并注入依賴的數據源return new UserService(dataSource());}
}
上述 AppConfig
被 @Configuration
注解標識為配置類。方法 dataSource()
和 userService()
上的 @Bean
注解會使 Spring 將其返回值注冊為容器中的 Bean。其中 userService()
方法調用了 dataSource()
,Spring 會攔截并確保返回的是容器中單例的 DataSource Bean,而非每次調用重新實例化(即 CGLIB 增強 @Configuration
類確保 Bean 單例行為)。
@Bean
簡介: @Bean
注解用于定義一個 Bean。它標注在方法上,表示該方法返回的對象會注冊到 Spring 容器中。@Bean
通常配合 @Configuration
使用,由 Spring Context 模塊提供。
作用與場景: 當通過 JavaConfig 定義 Bean 時,用 @Bean
替代傳統 XML <bean>
聲明。例如整合第三方庫的 Bean、或需要在創建 Bean 時執行一些自定義邏輯等場景。@Bean
方法可以指定名稱(默認是方法名),還支持設置 initMethod
(初始化時回調方法)和 destroyMethod
(銷毀時回調方法)。
使用示例:
@Configuration
public class MyConfig {@Bean(name = "customBean", initMethod = "init", destroyMethod = "cleanup")public MyComponent customBean() {return new MyComponent();}
}
在上例中,@Bean
注解聲明了 customBean
這個 Bean。容器啟動時調用 customBean()
方法創建 MyComponent
實例,并以 "customBean"
名稱注冊。initMethod="init"
表示在 Bean 創建后自動調用其 init()
方法進行初始化;destroyMethod="cleanup"
表示容器銷毀該 Bean 時調用其 cleanup()
方法。通過這種方式可以管理 Bean 的生命周期方法(類似于 InitializingBean
和 DisposableBean
接口或 @PostConstruct
/@PreDestroy
,見后文)。
@ComponentScan
簡介: @ComponentScan
用于配置組件掃描路徑的注解。由 Spring Context 提供,通常與 @Configuration
一起使用。它的作用是指示 Spring 在指定的包路徑下搜索帶有組件注解的類,并注冊為 Bean。
作用與場景: 默認情況下,Spring Boot 的 @SpringBootApplication
已經隱含指定掃描其所在包及子包。如果需要自定義掃描范圍(例如掃描其他包的組件),可以使用 @ComponentScan
注解并提供 basePackages
等屬性。普通 Spring 應用(非 Boot)則經常需要在主配置類上顯式使用 @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
這兩個包及其子包,搜索所有標注了 @Component
/@Service
/@Controller
等的類并注冊。這樣可以將應用的組件按照包組織,而由配置集中管理掃描范圍。
@Import
簡介: @Import
注解用于導入額外的配置類或組件到 Spring 容器。它由 Spring Context 提供,可用在 @Configuration
類上,將一個或多個配置類合并進來。也可以用于引入第三方配置。
作用與場景: 當項目拆分成多個配置類時,可以通過 @Import
將它們組合。例如,將公共配置獨立出來,再在主配置中引入。Spring Boot 自動配置內部也大量使用了 @Import
來按條件加載配置類。提供模塊: Spring Context。
使用示例:
@Configuration
@Import({SecurityConfig.class, DataConfig.class})
public class MainConfig {// 主配置,導入了安全配置和數據配置
}
如上,MainConfig
通過 @Import
導入了 SecurityConfig
和 DataConfig
兩個配置類。這樣這兩個配置類中定義的 Bean 同樣會加載到容器中,相當于把多個配置模塊拼裝在一起。相比在 XML 里用 <import>
,注解方式更加直觀。
注: Spring Boot 提供的許多 @Enable...
注解(例如后文的 @EnableScheduling
等)內部也是通過 @Import
導入相應的配置實現啟用功能的。
三、依賴注入注解
依賴注入(DI)是 Spring 核心機制之一。以下注解用于在容器中進行 Bean 注入和裝配,解決 Bean 間的依賴關系。
@Autowired
簡介: @Autowired
是 Spring 提供的自動裝配注解(org.springframework.beans.factory.annotation.Autowired
),用于按類型自動注入依賴對象。它可作用于字段、setter方法或者構造函數上。由 Spring Context 模塊支持。
作用與場景: 標注了 @Autowired
的屬性或方法,Spring 會在容器啟動時自動尋找匹配的 Bean 注入。其中按類型匹配是默認行為。如果匹配到多個同類型 Bean,則需要結合 @Qualifier
或 @Primary
來消除歧義(見下文)。如果沒有找到匹配 Bean,默認會拋出異常。可通過設置 @Autowired(required=false)
來表示找不到 Bean 時跳過注入而不報錯。
使用示例:
@Component
public class UserService {@Autowired // 按類型自動裝配private UserRepository userRepository;// 或者構造函數注入// @Autowired // public UserService(UserRepository userRepository) { ... }public User findUser(Long id) {return userRepository.findById(id);}
}
上例中,UserService
有一個成員 userRepository
,使用 @Autowired
標注。容器會自動將類型為 UserRepository
的 Bean 注入進來(假設已有 @Repository
或 @Component
標記的 UserRepository
實現)。開發者可以通過構造器、setter 或字段注入的方式使用 @Autowired
。注意: Spring 4.3+ 如果類中只有一個構造器,且需要注入參數,可省略構造函數上的 @Autowired
,仍會自動注入。
@Qualifier
簡介: @Qualifier
注解與 @Autowired
配合使用,用于按照名稱或限定符進行依賴注入匹配。它由 Spring 提供(org.springframework.beans.factory.annotation.Qualifier
),可以解決當容器中存在多個同類型 Bean 時的沖突。
作用與場景: 默認按類型注入在有多于一個候選 Bean 時會無法確定注入哪個。例如有兩個實現類實現了同一接口,都被注冊為 Bean。這種情況下,可以在注入點使用 @Qualifier("beanName")
指定注入哪一個 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 的實現private UserRepository userRepository;// ...
}
如上,有兩個 UserRepository
實現 Bean,分別命名為 “mysqlRepo” 和 “oracleRepo”。在 UserService
中,通過 @Qualifier("mysqlRepo")
指定注入名為 mysqlRepo 的 Bean。這樣即使存在多個同類型 Bean,Spring 也能準確地注入所需的依賴。
@Primary
簡介: @Primary
注解用于標記一個 Bean 為主要候選者。當按類型注入出現多個 Bean 可選時,標有 @Primary
的 Bean 將優先被注入。它由 Spring 提供(org.springframework.context.annotation.Primary
),可作用于類或方法(例如 @Bean
方法)上。
作用與場景: 如果不方便在每個注入點都使用 @Qualifier
指定 Bean,另一種方式是在 Bean 定義處用 @Primary
聲明一個首選 Bean。當存在歧義時,容器會選擇標記了 @Primary
的 Bean 注入。注意,@Primary
只能有一個,否則仍然無法明確選擇。提供模塊: Spring Context。
使用示例:
@Configuration
public class RepoConfig {@Bean@Primary // 將這個Bean標記為首選public UserRepository mysqlUserRepository() {return new MySqlUserRepository();}@Beanpublic UserRepository oracleUserRepository() {return new OracleUserRepository();}
}@Service
public class UserService {@Autowiredprivate UserRepository userRepository;// 將自動注入 mysqlUserRepository,因為它被標記為 @Primary
}
在上例的配置中,我們定義了兩個 UserRepository
Bean,其中 MySQL 實現被標記為 @Primary
。因此在 UserService
中按類型注入 UserRepository
時,Spring 會注入標記了 @Primary
的 MySQL 實現。@Primary
提供了一個全局默認方案,簡化了注入點的選擇。
@Resource
簡介: @Resource
是來自 JSR-250 規范的注解(Javax/Jakarta Annotation),Spring 對其提供了支持,用于按名稱或按類型注入依賴。它通常位于 jakarta.annotation.Resource
(Java EE/Jakarta EE)包下。注意: 盡管不在 Spring 包中,Spring 容器能識別并處理它。
作用與場景: @Resource
可以看作功能類似于 @Autowired + @Qualifier
的組合。默認情況下按名稱注入:它首先按照屬性名或指定的名稱在容器中查找 Bean,找不到再按類型匹配。這在某些情況下很有用,例如需要與傳統 Java EE 代碼兼容時。在 Spring 應用中,也有開發者偏好使用 @Resource
進行依賴注入。提供模塊: 需要引入相應的 Jakarta Annotation API,但 Spring Framework 自身支持處理。
使用示例:
@Component("userRepo")
public class UserRepositoryImpl implements UserRepository { ... }@Service
public class UserService {@Resource(name = "userRepo") // 按名稱注入名為"userRepo"的Beanprivate UserRepository userRepo;// ...
}
這里,UserRepositoryImpl
組件被命名為 "userRepo"
。在 UserService
中,通過 @Resource(name = "userRepo")
來注入。如果省略 name
屬性,@Resource
默認以屬性名 userRepo
作為 Bean 名稱查找。與 @Autowired
不同,@Resource
不支持 required=false
屬性,但其異常信息可能更直觀(若找不到 Bean 則拋出 NoSuchBeanDefinitionException
)。值得一提的是,Spring 也支持 JSR-330 的 @Inject
(javax.inject.Inject)注解,其語義與 @Autowired
相同,也可用于構造函數注入等。在實際開發中,可根據團隊規范選擇使用 Spring 原生的 @Autowired
還是標準的 @Resource
/@Inject
。
@Value
簡介: @Value
注解用于將外部化配置中的屬性值注入到 Bean 的字段或參數中。它由 Spring 提供(org.springframework.beans.factory.annotation.Value
),常用于讀取 application.properties/yaml 配置文件或系統環境變量、JNDI等屬性。
作用與場景: 當需要在 Bean 中使用配置文件里的值時,可以使用 @Value("${property.name}")
注入。例如數據庫連接參數、服務端口號等。還支持設置默認值和 SpEL 表達式。提供模塊: Spring Context 環境抽象。
使用示例:
假設 application.properties 有如下內容:
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}") // 帶默認值,若配置缺失則使用0.0.1private String appVersion;// ...
}
上述 AppInfo
類中,@Value("${app.name}")
將把配置中的 app.name
值注入到 appName
字段。如果對應屬性不存在,會啟動失敗。而 appVersion
字段提供了默認值 0.0.1
,當配置文件未設置 app.version
時就會使用默認值。這樣,可以靈活地將外部配置與代碼解耦,使應用更易于調整參數而無需改動源碼。
@Scope
簡介: @Scope
注解用于指定 Bean 的作用域,由 Spring 提供(org.springframework.context.annotation.Scope
)。默認情況下,Spring 容器中的 Bean 都是單例(singleton)作用域。通過 @Scope
可以定義其他作用域,例如 prototype、request、session 等。
作用與場景: 常見的作用域:
singleton
(默認):容器中僅保持一個實例。prototype
:每次請求 Bean 時都會創建新實例。- Web相關的作用域(需要在 Web 容器環境下使用):如
request
(每個HTTP請求創建)、session
(每個會話創建)等。
在需要每次使用新對象的場景(如有狀態 Bean),可將 Bean 定義成 prototype;在 Web 應用中某些 Bean 希望隨請求或會話存續,可用相應作用域。提供模塊: Spring Context。
使用示例:
@Component
@Scope("prototype")
public class Connection {public Connection() {System.out.println("New Connection created.");}
}
將 Connection
Bean 聲明為 prototype,每次獲取都會創建新的實例:
@Autowired
private Connection conn1;
@Autowired
private Connection conn2;
上面 conn1
和 conn2
將是不同的實例,因為 Connection
定義為 prototype。日志會打印兩次 “New Connection created.”。若作用域是 singleton,則只創建一次實例并復用。需要注意,prototype Bean 的生命周期由使用方管理,Spring 只負責創建,不會自動調用其銷毀方法。
@Lazy
簡介: @Lazy
注解用于將 Bean 的初始化延遲到首次使用時(懶加載)。由 Spring 提供(org.springframework.context.annotation.Lazy
),可用于類級別或 @Bean
方法上。
作用與場景: 默認情況下,單例 Bean 在容器啟動時就會初始化。如果某些 Bean 的創建比較耗時或在應用運行期間可能不會被用到,可以標記為 @Lazy
,這樣只有在真正需要時才實例化,減少啟動時間和資源消耗。懶加載常用于:例如調試或在單元測試中減少不必要 Bean 創建,或避免循環依賴時暫緩 Bean 的注入初始化。對于 prototype Bean,Spring 始終延遲創建(因為本身就按需創建),@Lazy
主要針對單例 Bean。提供模塊: Spring Context。
使用示例:
@Service
@Lazy
public class HeavyService {public HeavyService() {// 構造函數可能進行大量初始化System.out.println("HeavyService initialized");}// ...
}@Controller
public class DemoController {@Autowiredprivate HeavyService heavyService; // 被@Lazy標記,不會在容器啟動時實例化// ...
}
如上,HeavyService
使用 @Lazy
注解標記為懶加載單例。啟動時不會打印 “HeavyService initialized”。當 DemoController
第一次實際調用 heavyService
的方法或訪問它時,Spring 才會創建 HeavyService
實例并注入。這對于優化啟動性能和按需加載組件很有幫助。但應謹慎使用懶加載,如果Bean在啟動后馬上就會用到,則不應延遲初始化,以免首次調用時產生延遲。
四、配置屬性注解
Spring 提供了將配置文件內容綁定到對象的機制,這類注解幫助管理應用的外部化配置和環境區分。
@ConfigurationProperties
簡介: @ConfigurationProperties
用于將一組配置屬性映射到一個 Java 類上。由 Spring Boot 提供(org.springframework.boot.context.properties.ConfigurationProperties
),通常配合 Bean 使用。通過前綴(prefix)來批量注入配置項到類的屬性中。
作用與場景: 當有多項相關配置需要使用時,比起逐個使用 @Value
,可以定義一個配置屬性類。例如應用配置、數據源配置等。在類上標注 @ConfigurationProperties(prefix="xxx")
后,該類的各屬性會根據前綴讀取配置文件中的對應項賦值。需要將該類注冊為 Bean(可以通過在類上加 @Component
或在配置類中用 @Bean
創建),Spring Boot 會自動將配置綁定到 Bean 實例上。
使用示例:
application.yml:
app:name: MyAppapiUrl: https://api.example.compool:size: 20enableLog: true
定義屬性綁定類:
@Component // 確保被掃描注冊為Bean
@ConfigurationProperties(prefix = "app")
public class AppProperties {private String name;private String apiUrl;private Pool pool;// 內部靜態類或普通類用于嵌套屬性public static class Pool {private int size;private boolean enableLog;// getters/setters ...}// getters/setters ...
}
將 AppProperties
注入使用:
@RestController
public class AppInfoController {@Autowiredprivate AppProperties appProperties;@GetMapping("/appInfo")public AppProperties getAppInfo() {// 返回整個配置對象,框架會序列化為JSONreturn appProperties;}
}
在這個例子中,@ConfigurationProperties(prefix="app")
使得 YAML 中 app
下的配置自動綁定到 AppProperties
Bean。name
、apiUrl
會對應賦值,嵌套的 pool.size
和 pool.enableLog
也會注入到 Pool
類中。這樣可以方便地管理和校驗成組的配置屬性。需要注意,綁定類必須有無參構造器,提供標準的 getter/setter。Spring Boot 還支持JSR-303校驗注解(如 @Validated)配合 @ConfigurationProperties
對配置進行格式校驗。
@EnableConfigurationProperties
簡介: @EnableConfigurationProperties
是 Spring Boot 用于啟用 @ConfigurationProperties
支持的注解。它通常加在主應用類或配置類上,用來將帶有 @ConfigurationProperties
注解的配置POJO注入到容器中。
作用與場景: 在 Spring Boot 2.x 以后,如果配置屬性類已經被聲明為 Bean(例如加了 @Component
),則無需顯式使用這個注解。@EnableConfigurationProperties
常用在需要將未被組件掃描的配置屬性類納入 Spring 管理時。例如定義了一個純 POJO 沒有用@Component,則可以在主類上通過此注解指定要啟用綁定的配置類列表。提供模塊: Spring Boot AutoConfigure。
使用示例:
@SpringBootApplication
@EnableConfigurationProperties(AppProperties.class)
public class MyApplication {// ...
}
上述在主啟動類上添加了 @EnableConfigurationProperties(AppProperties.class)
,顯式指定將 AppProperties
這個被 @ConfigurationProperties
注解的類納入配置屬性綁定并注冊為 Bean。這樣即使未加 @Component
,仍可使用 @Autowired
注入 AppProperties
實例。Spring Boot 自動配置模塊會掃描此注解并完成相應的綁定工作。
@Profile
簡介: @Profile
注解用于根據**環境(Profile)**加載 Bean。由 Spring 提供(org.springframework.context.annotation.Profile
)。可以標注在類或方法(Bean 方法)上,只有在激活的環境與指定 Profile 匹配時,該 Bean 才會注冊到容器。
作用與場景: 常用于區別開發、測試、生產環境的配置。例如開發環境使用嵌入式數據庫,而生產環境使用正式數據庫連接,就可以用 @Profile("dev")
和 @Profile("prod")
注解分別標注不同的配置類或 Bean。在運行應用時通過配置 spring.profiles.active
激活某個 Profile,則對應的 Bean 生效。提供模塊: Spring Context 環境管理。
使用示例:
@Configuration
public class DataSourceConfig {@Bean@Profile("dev")public DataSource memoryDataSource() {// 開發環境使用內存數據庫return new H2DataSource(...);}@Bean@Profile("prod")public DataSource mysqlDataSource() {// 生產環境使用MySQL數據源return new MySQLDataSource(...);}
}
當設置 spring.profiles.active=dev
時,應用啟動只會創建 memoryDataSource
Bean;設置為 prod
時只創建 mysqlDataSource
Bean。如果不激活任何 Profile,上述兩個 Bean 都不會加載(也可以用 @Profile("default")
指定默認配置)。使用 @Profile
實現了根據環境有條件地注冊 Bean,方便一套代碼多環境運行。
五、Bean 生命周期與作用域注解
Spring 管理的 Bean 具有完整的生命周期,包括初始化和銷毀過程。以下注解用于在生命周期特定階段執行方法,以及控制 Bean 的作用域與加載時機。
@PostConstruct
簡介: @PostConstruct
是一個來自 Java 標準(JSR-250)的注解(位于 jakarta.annotation.PostConstruct
)。Spring 容器在Bean初始化完依賴注入后,會調用被該注解標記的方法。常用于初始化邏輯。需要注意在 Spring Boot 3+ 中,@PostConstruct
等由 Jakarta 引入,需要相應依賴。
作用與場景: 當我們希望在 Bean 完成依賴注入后自動執行一些初始化代碼,可以在 Bean 的方法上加 @PostConstruct
。例如設置默認值、開啟定時器、檢查配置完整性等。在傳統 Spring 中,這相當于 <bean init-method="...">
或實現 InitializingBean
接口的 afterPropertiesSet
。提供模塊: JSR-250(Javax/Jakarta Annotation),由 Spring 容器支持調用。
使用示例:
@Component
public class CacheManager {private Map<String, Object> cache;@PostConstructpublic void init() {// 初始化緩存cache = new ConcurrentHashMap<>();System.out.println("CacheManager initialized");}
}
當 Spring 創建了 CacheManager
Bean 并注入完依賴后,會自動調用其 init()
方法,輸出 “CacheManager initialized” 并完成緩存容器初始化。這樣開發者無需手動調用初始化邏輯,容器托管完成。這對于單例Bean非常方便。
@PreDestroy
簡介: @PreDestroy
同樣來自 JSR-250 標準(jakarta.annotation.PreDestroy
),Spring 在 Bean 銷毀前(容器關閉或 Bean 移除前)調用標注該注解的方法。常用于資源釋放、保存狀態等操作。
作用與場景: 當應用結束或容器要銷毀 Bean 時,希望執行一些清理工作,例如關閉文件流、線程池、數據庫連接等,可以在方法上加 @PreDestroy
注解。相當于 XML 配置中的 <bean destroy-method="...">
或實現 DisposableBean
接口的 destroy
方法。提供模塊: JSR-250,由 Spring 容器負責調用。
使用示例:
@Component
public class ConnectionManager {private Connection connection;@PostConstructpublic void connect() {// 建立數據庫連接connection = DriverManager.getConnection(...);}@PreDestroypublic void disconnect() throws SQLException {// 關閉數據庫連接if(connection != null && !connection.isClosed()) {connection.close();System.out.println("Connection closed.");}}
}
在上例中,ConnectionManager
Bean 在初始化時建立數據庫連接,在容器銷毀時通過 @PreDestroy
標記的 disconnect()
方法關閉連接。Spring 在應用關閉時會調用該方法,確保資源釋放。這使得資源管理更加可靠,避免連接泄漏等問題。
@Scope
(作用域) – 見上文第三部分
(此處簡要說明:) 使用 @Scope
注解可以改變 Bean 的作用域,比如 "singleton"
、"prototype"
等。已在依賴注入部分詳細介紹其使用。
@Lazy
(懶加載) – 見上文第三部分
(此處簡要說明:) 使用 @Lazy
可以延遲 Bean 的初始化直至第一次使用。在某些場景下提高啟動性能或解決循環依賴。前文已介紹其概念和示例。
六、Web 開發注解
Spring MVC 框架提供了大量注解來簡化 Web 開發,包括請求映射、參數綁定、響應處理等。這些注解大多位于 org.springframework.web.bind.annotation
包中。
@RequestMapping
簡介: @RequestMapping
是最基本的請求映射注解,用于將 HTTP 請求URL路徑映射到對應的控制器類或處理方法上。由 Spring Web MVC 提供。可用于類和方法級別。
作用與場景: 在類上標注 @RequestMapping("basePath")
可以為該控制器指定一個基礎路徑,方法上的 @RequestMapping("subPath")
則在類路徑基礎上進一步細分。它支持設置請求方法(GET、POST等)、請求參數和請求頭等屬性,用于更精確地映射請求。例如只處理 GET 請求,或某個請求參數存在時才匹配。Spring MVC 啟動時會根據這些注解建立 URL 到方法的映射關系。
使用示例:
@Controller
@RequestMapping("/users")
public class UserController {@RequestMapping(value = "/{id}", method = RequestMethod.GET)public String getUserProfile(@PathVariable Long id, Model model) {// 根據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")
指定了基礎路徑“/users”。方法級注解:
getUserProfile
: 映射 GET 請求到 “/users/{id}”。使用method = RequestMethod.GET
限定請求方法為 GET,@PathVariable
獲取 URL 中的{id}
部分。返回視圖名 “profile” 供顯示用戶信息。registerUser
: 映射 POST 請求到 “/users”,并使用params="action=register"
進一步限定只有請求參數包含action=register
時才調用此方法。這是區分同一路徑不同操作的方式。處理完后重定向到用戶列表。
@RequestMapping
非常靈活,其常用屬性:
value
或path
:映射的 URL 路徑,可以是 Ant 風格模式(如/users/*
)。method
:限定 HTTP 方法,如RequestMethod.GET
等。params
:指定必須存在的參數或參數值,如"action=register"
或"!admin"
(必須不包含admin參數)。headers
:指定必須的請求頭,如"Content-Type=application/json"
。
@GetMapping
/ @PostMapping
等
簡介: @GetMapping
和 @PostMapping
是 @RequestMapping
的派生注解,專門用于簡化映射 GET 和 POST 請求。類似的還有 @PutMapping
、@DeleteMapping
、@PatchMapping
,分別對應 HTTP PUT/DELETE/PATCH 方法。它們由 Spring MVC 提供,從 Spring 4.3 開始引入。
作用與場景: 這些注解相當于 @RequestMapping(method = RequestMethod.X)
的快捷方式,使代碼更簡潔。尤其在定義 RESTful API 時,常用不同 HTTP 方法表示不同操作,用這些注解能直觀體現方法用途。例如 @GetMapping
表示獲取資源,@PostMapping
表示創建資源等。
使用示例:
@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})
等價于@RequestMapping(value="/{id}", method = RequestMethod.GET)
,用于獲取指定ID的 Item。@PostMapping("")
等價于類路徑/items
下的 POST 請求(創建新的 Item),請求體通過@RequestBody
解析為Item
對象。@DeleteMapping("/{id}")
處理刪除操作。
這些組合注解讓控制器方法定義更直觀,更符合 RESTful 風格。可以根據需要使用對應的 HTTP方法注解。未提供參數時,@GetMapping
等注解的路徑可以直接寫在注解括號內(如上 @PostMapping("")
指當前路徑)。
@PathVariable
簡介: @PathVariable
用于將 URL 路徑中的動態部分綁定到方法參數上。由 Spring MVC 提供。常與 @RequestMapping
或 @GetMapping
等一起使用,用于處理RESTful風格的URL。
作用與場景: 當URL中含有變量占位符(如 /users/{id}
)時,可通過在方法參數上加 @PathVariable
來獲取該占位符的值。可以指定名稱匹配占位符,或者不指定名稱則根據參數名自動推斷。適用于從路徑獲取資源標識(ID、name等)的場景。
使用示例:
@GetMapping("/orders/{orderId}/items/{itemId}")
public OrderItem getOrderItem(@PathVariable("orderId") Long orderId, @PathVariable("itemId") Long itemId) {return orderService.findItem(orderId, itemId);
}
當收到請求 /orders/123/items/456
時:
orderId
參數會被賦值為123
(Long 類型轉換),itemId
參數賦值為456
。
@PathVariable("orderId")
中指定名稱,與{orderId}
占位符對應。如果方法參數名與占位符名稱相同,可以簡寫為@PathVariable Long orderId
。
通過 @PathVariable
,我們無需從 HttpServletRequest
手動解析路徑,Spring MVC 自動完成轉換和注入,簡化了代碼。
@RequestParam
簡介: @RequestParam
用于綁定 HTTP 請求的查詢參數或表單數據到方法參數上。由 Spring MVC 提供。支持為參數設置默認值、是否必需等屬性。
作用與場景: 處理 GET 請求的查詢字符串參數(URL ?
后的參數)或 POST 表單提交的字段時,可以使用 @RequestParam
獲取。例如搜索接口的關鍵詞,分頁的頁碼和大小等。它可以將 String 類型的請求參數轉換為所需的目標類型(如 int、boolean),自動完成類型轉換和必要的校驗。
使用示例:
@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);
}
當請求 /search?keyword=phone&pageIndex=1
到達時:
keyword
參數綁定到方法的keyword
參數。如果未提供則使用默認值空字符串。pageIndex
綁定到整型參數,未提供則為默認0。pageSize
在此請求未提供,因此取默認值10。
@RequestParam
常用屬性:
value
或name
:參數名,對應URL中的參數名。required
:是否必須提供,默認 true(不提供會報錯)。上例中我們將 keyword 標記為 false 可選。defaultValue
:如果請求未包含該參數則使用默認值(注意即使標記 required=true,有 defaultValue 也不會報錯)。
通過 @RequestParam
,方法可以直接獲得解析后的參數值,無需自己從 request 獲取和轉換,大大簡化控制器代碼。
@RequestBody
簡介: @RequestBody
用于將 HTTP 請求報文體中的內容轉換為 Java 對象并綁定到方法參數上。常用于處理 JSON 或 XML 等請求體。由 Spring MVC 提供。
作用與場景: 在 RESTful API 中,POST/PUT 等請求通常會攜帶 JSON 格式的數據作為請求體。使用 @RequestBody
注解在方法參數(通常是自定義的 DTO 類)上,Spring MVC 會利用 HttpMessageConverter 將 JSON/XML 等按需轉換為對應的對象實例。適用于需要從請求正文獲取復雜對象的場景。與之對應,返回值或方法上使用 @ResponseBody
(或 @RestController
)可將對象序列化為響應。
使用示例:
@PostMapping("/users")
public ResponseEntity<String> addUser(@RequestBody UserDTO userDto) {// userDto 已自動綁定了請求JSON的數據userService.save(userDto);return ResponseEntity.ok("User added successfully");
}
假設客戶端發送 POST 請求至 /users
,請求體為:
{ "name": "Tom", "email": "tom@example.com" }
Spring MVC 會根據 @RequestBody UserDTO userDto
:
- 讀取請求體 JSON,
- 將其轉換為
UserDTO
對象(要求有適當的屬性和setter)。 - 然后傳遞給控制器方法使用。
方法處理后返回成功響應。使用 @RequestBody
,開發者無需手動解析 JSON,提高了開發效率并減少出錯。
注意: @RequestBody
默認要求請求體存在,否則報錯。如果希望在請求體為空時處理為 null,可以設置 required=false
。對于 GET 請求一般不使用 @RequestBody
(GET沒有主體或主體被忽略)。
@ResponseBody
簡介: @ResponseBody
注解用于將控制器方法的返回值直接作為 HTTP 響應內容輸出,而不是解析為視圖名稱。由 Spring MVC 提供。可以標注在方法上或(較少見)標注在類上(類上標注相當于對該類所有方法應用此行為)。
作用與場景: @ResponseBody
常用于 AJAX 接口或 RESTful 方法,需要返回 JSON、XML或純文本等給客戶端,而非頁面。當方法標注該注解后,Spring 會將返回對象通過合適的 HttpMessageConverter 轉換為 JSON/XML 或其他格式寫入響應流。例如返回一個對象會自動序列化為 JSON 字符串。@RestController
注解實際上已經包含了 @ResponseBody
效果,所以在使用 @RestController
時無需再標注此注解在每個方法上。
使用示例:
@Controller
public class StatusController {@GetMapping("/ping")@ResponseBodypublic String ping() {return "OK";}@GetMapping("/status")@ResponseBodypublic Map<String, Object> status() {Map<String, Object> info = new HashMap<>();info.put("status", "UP");info.put("timestamp", System.currentTimeMillis());return info;}
}
對于上例:
/ping
請求返回純文本 “OK” 給客戶端。/status
請求返回一個 Map,Spring 會將其轉換為 JSON,如:{"status":"UP","timestamp":1638346953000}
。
因為使用的是普通的 @Controller
類,所以需要在每個方法上添加 @ResponseBody
來指示直接返回內容。如果改用 @RestController
則可以省略這些注解。@ResponseBody
常用于快速測試接口或者在需要精確控制輸出內容時使用。
@CrossOrigin
簡介: @CrossOrigin
注解用于配置跨域資源共享 (CORS)。由 Spring Web 提供,可標注在類或方法上。它允許來自不同域名的客戶端訪問被標注的資源。
作用與場景: 當前端和后端分屬不同域(例如前端React開發服務器 http://localhost:3000,后端 http://localhost:8080)時,瀏覽器會攔截跨域請求。使用 @CrossOrigin
可以在服務端指定允許跨域的來源、方法、頭信息等,從而使瀏覽器允許調用。可以針對整個控制器類統一配置(類上標注)或針對特定方法(方法上標注)配置不同跨域策略。
使用示例:
@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) { ... }
}
類上標注的 @CrossOrigin(origins = "http://localhost:3000")
表示允許來自 http://localhost:3000
的跨域請求訪問該控制器的所有接口。submitData
方法上單獨標注了一個不同的 @CrossOrigin
,表示對于 /api/submit
接口,允許來自 http://example.com
的 POST 請求跨域訪問(不受類上通用配置限制)。@CrossOrigin
還可設置允許的請求頭、是否發送憑證等,通過參數如 allowedHeaders
, allowCredentials
等配置。使用這個注解,開發者不必在全局Web配置中配置 CorsRegistry
,可以就近管理跨域策略。
@ExceptionHandler
簡介: @ExceptionHandler
用于在控制器中定義異常處理方法的注解。由 Spring MVC 提供。通過指定要處理的異常類型,當控制器方法拋出該異常時,轉而由標注了 @ExceptionHandler
的方法來處理。
作用與場景: 為了避免將異常堆棧暴露給客戶端或者在每個控制器方法中編寫重復的 try-catch,可以使用 @ExceptionHandler
集中處理。例如處理表單校驗異常返回友好錯誤信息、處理全局異常返回統一格式響應等。@ExceptionHandler
通常與 @ControllerAdvice
(后述)配合,用于全局異常處理;也可以直接在本控制器內部定義專門的異常處理方法。
使用示例:
@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)
標注了 handleNotFound
方法來處理這種異常:當異常拋出后,控制器不會繼續執行原流程,而是進入該方法。方法可以接收異常對象,以及 Model
等參數,處理后返回一個視圖名 "orderError"
顯示錯誤信息。
通過 @ExceptionHandler
,控制器內部的異常處理邏輯與正常業務邏輯解耦,代碼清晰且易于維護。
@ControllerAdvice
簡介: @ControllerAdvice
是全局控制器增強注解。由 Spring MVC 提供,用于定義一個全局異常處理或全局數據綁定的切面類。標注該注解的類可以包含多個 @ExceptionHandler
方法,用于處理應用所有控制器拋出的異常;也可以包含 @ModelAttribute
或 @InitBinder
方法對所有控制器生效。
作用與場景: 當需要對所有控制器統一處理某些邏輯時,使用 @ControllerAdvice
非常方便。典型用法是結合 @ExceptionHandler
作為全局異常處理器,比如攔截所有 Exception
返回通用錯誤響應,或分類處理不同異常類型返回不同狀態碼。提供模塊: Spring MVC。
使用示例:
@ControllerAdvice
public class GlobalExceptionHandler {// 處理所有異常的fallback@ExceptionHandler(Exception.class)@ResponseBodypublic ResponseEntity<String> handleException(Exception ex) {// 記錄日志ex.printStackTrace();// 返回通用錯誤響應return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal Server Error: " + ex.getMessage());}// 處理特定異常@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseBodypublic 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 會將其中標注了 @ExceptionHandler
的方法應用到整個應用的控制器:
- 第一個方法捕獲所有未被其它更專門處理的異常,打印棧Trace并返回500錯誤提示。
- 第二個方法專門處理參數校驗失敗異常,提取錯誤信息列表并返回400狀態和錯誤列表。
此外,可以在 @ControllerAdvice
類中定義 @ModelAttribute
方法,為所有控制器請求添加模型數據(如公共下拉選項),或定義 @InitBinder
方法,注冊全局屬性編輯器等。@ControllerAdvice
可以通過屬性限制只應用于某些包或注解的控制器,但全局異常處理通常都是應用全局的。
通過 @ControllerAdvice
,我們實現了 AOP 式的全局控制器邏輯抽取,使各控制器關注自身業務,將通用邏輯集中處理,保持代碼整潔。
七、數據訪問與事務注解
在使用 Spring 管理數據持久化層時,會涉及到 JPA/Hibernate 等注解定義實體,以及 Spring 提供的事務管理注解等。
@Entity
簡介: @Entity
是 Java Persistence API (JPA) 的注解(jakarta.persistence.Entity
),用于將一個類聲明為 JPA 實體。Spring Boot 通常通過 JPA/Hibernate 來操作數據庫,因此定義模型時會用到它。@Entity
注解的類對應數據庫中的一張表。
作用與場景: 標記為 @Entity
的類將由 JPA 實現(例如 Hibernate)管理,其實例可映射到數據庫記錄。必須提供主鍵(用 @Id
標注),可選地用 @Table
指定表名,不指定則默認表名為類名。提供模塊: JPA 規范,由 Hibernate 等實現。在 Spring Boot 中,引入 spring-boot-starter-data-jpa
會自動掃描 @Entity
類并創建表結構(結合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
注解標記為持久化實體,對應數據庫表users
(由@Table指定,若不指定默認表名User
)。字段上:
id
用@Id
標識為主鍵,@GeneratedValue
指定主鍵生成策略(自增)。username
用@Column
細化映射:列名指定為username,長度50,非空且唯一。password
僅用了@Column(nullable=false)
,列名默認為屬性名。
定義好實體后,可以使用 Spring Data JPA 的倉庫接口來自動生成常用查詢(見下文 @Repository
和 @Query
)。Spring Boot 啟動時若開啟DDL-auto,會根據實體定義自動在數據庫創建或更新表結構。
@Table
簡介: @Table
是 JPA 注解(jakarta.persistence.Table
),配合 @Entity
使用,用于指定實體映射的數據庫表信息,如表名、schema、catalog等。
作用與場景: 默認情況下,實體類名即表名。若數據庫表名與類名不同,或者需要定義 schema,使用 @Table
注解非常必要。也能定義唯一約束等。提供模塊: JPA。
使用示例:
@Entity
@Table(name = "T_USER", schema = "public", uniqueConstraints = {@UniqueConstraint(columnNames = "email")
})
public class User {// ...
}
該實體指定映射到 public
模式下的 T_USER
表,并聲明 email
列上有唯一約束。@Table
的屬性:
name
:表名。schema
/catalog
:所屬 schema 或 catalog 名稱。uniqueConstraints
:唯一約束定義。
@Id
簡介: @Id
是 JPA 注解(jakarta.persistence.Id
),指定實體類的主鍵字段。每個 @Entity
必須有且只有一個屬性使用 @Id
注解。可配合 @GeneratedValue
一起使用定義主鍵生成策略。
作用與場景: 標記主鍵后,JPA 會將該字段作為數據庫表的主鍵列。支持基本類型或包裝類型,或 java.util.UUID
等。提供模塊: JPA。
使用示例:
@Entity
public class Product {@Id@GeneratedValue(strategy = GenerationType.AUTO)private Long id;// ... 其他字段
}
如上,id
字段為主鍵,使用自動生成策略。常見的 GenerationType
:
IDENTITY
:數據庫自增字段(MySQL的AUTO_INCREMENT等)。SEQUENCE
:使用數據庫序列(需要定義序列,Oracle等DB適用)。AUTO
:讓 JPA 自動選擇合適策略。TABLE
:使用一個數據庫表模擬序列。
@GeneratedValue
簡介: @GeneratedValue
是 JPA 注解(jakarta.persistence.GeneratedValue
),與 @Id
聯用,表示主鍵的生成方式。可指定策略 strategy
和生成器 generator
。
作用與場景: 根據數據庫和需求選擇主鍵生成策略。比如 MySQL 用 IDENTITY
讓數據庫自增,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” 的序列生成器,映射數據庫序列 “user_sequence”。@GeneratedValue
引用該生成器并使用 SEQUENCE 策略。每次插入User時會從序列獲取下一個值作為ID。
對于常用的 AUTO 或 IDENTITY 策略,在大多數情況下只需簡單標注 @GeneratedValue(strategy = GenerationType.IDENTITY)
等,無需額外生成器配置。
@Column
簡介: @Column
是 JPA 注解(jakarta.persistence.Column
),用于定義實體字段與數據庫表列的映射細節。可以不使用,如果不標注,JPA 默認按屬性名映射列名(可能做小寫下劃線轉換,視實現而定)。
作用與場景: @Column
可指定列名、數據類型長度、是否允許NULL、是否唯一等約束。對于日期時間類型,還可指定 columnDefinition
或 Temporal
等,控制SQL類型。提供模塊: JPA。
使用示例:
@Column(name = "email", length = 100, nullable = false, unique = true)
private String email;
如上,將字段 email
映射為名為 email 的列(其實和默認同名,但明確指出),長度100,非空且唯一。使用 @Column
可以清晰地將實體和數據庫字段對應起來。
@Repository
– 見上文第二部分
(此處補充:) 在數據訪問層,@Repository
標注的接口或類通常與 Spring Data JPA 搭配使用。如一個接口 UserRepository extends JpaRepository<User, Long>
上加 @Repository
(實際上 Spring Data JPA 的接口已經隱式有這個語義),Spring 會為其生成實現并交由容器管理。@Repository
除了提供組件掃描和異常轉換外,本身沒有其他方法屬性。
@Transactional
簡介: @Transactional
是 Spring 提供的聲明式事務管理注解(org.springframework.transaction.annotation.Transactional
)。可標注在類或方法上,表示其中的數據庫操作應當在一個事務中執行。Spring 將在運行時提供事務支持,如開始、提交或回滾事務。
作用與場景: 數據庫操作需要事務保障數據一致性,例如同時更新多張表,要么全部成功要么全部失敗。使用 @Transactional
可以在不手動編程式管理事務的情況下,由框架自動處理。典型應用:
- Service 層的方法需要原子性,則加上
@Transactional
,Spring會在進入方法時開啟事務,方法成功返回則提交,如有異常則回滾。 - 也可加在類上,表示類中所有公有方法都事務管理。
提供模塊: Spring ORM/Transaction 模塊,需要相應的事務管理器(DataSourceTransactionManager 或 JpaTransactionManager 等)配置。Spring Boot 自動根據數據源配置合適的事務管理器。
使用示例:
@Service
public class AccountService {@Autowiredprivate AccountRepository accountRepo;@Autowiredprivate AuditService auditService;@Transactionalpublic void transfer(Long fromId, Long toId, BigDecimal amount) {// 扣減轉出賬戶余額accountRepo.decreaseBalance(fromId, amount);// 增加轉入賬戶余額accountRepo.increaseBalance(toId, amount);// 記錄轉賬流水auditService.logTransfer(fromId, toId, amount);// 方法結束時,Spring自動提交事務。如發生運行時異常則自動回滾。}
}
transfer
方法標注了 @Transactional
,因此上述三個數據庫操作將處于同一個事務中:如果任何一步拋出未經捕獲的異常(默認僅RuntimeException和Error會導致回滾,可通過 rollbackFor
屬性更改回滾規則),所有已執行的數據庫更新都會回滾,保持數據一致性。如果全部成功,則提交事務,將更新真正持久化。事務傳播行為和隔離級別等也可以通過注解屬性配置,例如 @Transactional(propagation=Propagation.REQUIRES_NEW)
開啟新事務,@Transactional(isolation=Isolation.SERIALIZABLE)
設置高隔離級別等,視業務需求而定。
注意: 使用 @Transactional
時,需要確保啟用了 Spring 的事務支持(見下文 @EnableTransactionManagement
),Spring Boot 會自動在有數據源時啟用事務管理。所以在 Boot 場景下通常不需要額外配置即可使用。
@JsonFormat
簡介: @JsonFormat
是 Jackson 提供的序列化/反序列化格式化注解(com.fasterxml.jackson.annotation.JsonFormat
)。可作用在字段、方法(getter
/ setter
)或類型上,用于自定義日期-時間、數字、布爾等屬性在 JSON ←→ Java 轉換時的形態、時區與本地化設置。
作用與場景:
- 日期時間格式化:將
Date
/LocalDateTime
等類型格式化為固定字符串(例如yyyy-MM-dd HH:mm:ss
)并指定時區,避免前后端默認時區不一致導致時間偏移。 - 數字 / 布爾形態控制:可把布爾值序列化成
0/1
,或把Instant
、LocalDateTime
轉成數值時間戳(shape = NUMBER
)等。 - 與 Bean Validation 協同:在 DTO 中同時配合
@DateTimeFormat
/ 校驗注解,可保持前后端格式完全一致。 - 優先級:字段級
@JsonFormat
會覆蓋ObjectMapper
的全局日期格式配置,適用于單獨字段需要特殊格式的場景。
使用示例:
@Data
public class OrderDTO {private Long id;// 1. 指定日期-時間格式 + 時區@JsonFormat(shape = JsonFormat.Shape.STRING,pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")private LocalDateTime createdAt;// 2. 以秒級時間戳輸出@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 編寫:一個注解即可為所有字段(或單獨字段)生成讀取方法,保持類體簡潔。
- 與框架集成:Spring / Jackson / Hibernate 等框架依賴 getter 讀取屬性時可直接使用 Lombok 生成的方法。
使用示例:
@Getter // 為所有字段生成 getter
public class UserVO {private Long id;@Getter(AccessLevel.NONE) // 不生成該字段的 getterprivate String password;// 也可在字段級別加 @Getter 僅生成單個方法
}
依賴:開發環境需引入
lombok
依賴,并在 IDE 中安裝 Lombok 插件或開啟 Annotation Processing。
@Setter
簡介: @Setter
同樣由 Lombok 提供(lombok.Setter
),自動為類或字段生成 public setter 方法。
作用與場景:
- 可變對象賦值:在需要修改字段值、或框架反射注入時使用。
- 粒度控制:可通過
AccessLevel
設置方法可見性(如@Setter(AccessLevel.PROTECTED)
),或僅在特定字段上使用,避免暴露全部可寫接口。
使用示例:
@Getter
@Setter // 為所有字段生成 setter
public class ProductVO {private Long id;@Setter(AccessLevel.PRIVATE) // 僅類內部可修改private BigDecimal price;// price 的 setter 為 private,其余字段的 setter 為 public
}
@ToString
簡介: @ToString
亦由 Lombok 提供(lombok.ToString
)。在編譯期生成 toString()
方法,自動拼接字段名和值,支持包含/排除特定字段、隱藏敏感信息等。
作用與場景:
- 調試與日志:快速輸出對象內容而不必手寫
toString()
。 - 避免敏感字段泄漏:可用
@ToString.Exclude
排除字段,或在注解上設置callSuper = true
包含父類字段。 - 鏈式注解:常與
@Getter/@Setter/@EqualsAndHashCode
等一起使用,快速生成完整數據類。
使用示例:
@Getter
@Setter
@ToString(exclude = "password") // 排除 password
public class AccountVO {private String username;@ToString.Excludeprivate String password; // 或者字段級排除private LocalDateTime lastLogin;
}/*
輸出示例:
AccountVO(username=admin, lastLogin=2025-05-10T20:30:00)
*/
在生產日志中輸出對象時務必排除敏感信息;
@ToString
支持exclude
數組或字段級@ToString.Exclude
精細控制。
@EnableTransactionManagement
簡介: @EnableTransactionManagement
注解用于開啟 Spring 對 事務注解(如 @Transactional
)的支持。由 Spring 提供(org.springframework.transaction.annotation.EnableTransactionManagement
)。一般加在配置類上。
作用與場景: 在非 Spring Boot 場景下,使用 @Transactional
前通常需要在 Java 配置類上加此注解或在 XML 中配置 <tx:annotation-driven/>
來啟用事務 AOP。它會注冊事務管理相關的后置處理器,檢測 @Transactional
并在運行時生成代理。提供模塊: Spring Tx。
使用示例:
@Configuration
@EnableTransactionManagement
public class TxConfig {@Beanpublic PlatformTransactionManager txManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource); // 配置事務管理器}
}
上例通過 @EnableTransactionManagement
啟用了注解驅動的事務管理,并顯式聲明了數據源事務管理器 Bean。在 Spring Boot 中,如果引入了 spring-boot-starter-jdbc
或 JPA,會自動配置 DataSourceTransactionManager
或 JpaTransactionManager
,且默認啟用事務支持,無需手動添加該注解(Boot 會自動應用)。但在需要更細粒度控制事務行為時,了解此注解的作用仍然重要。
@Query
簡介: @Query
注解由 Spring Data JPA 提供(org.springframework.data.jpa.repository.Query
),用于在 Repository 方法上定義自定義的 JPQL或原生SQL 查詢。
作用與場景: 雖然 Spring Data JPA 可以通過解析方法名自動生成查詢,但是復雜或特殊的查詢可以用 @Query
手工編寫JPQL語句。還可以通過設置 nativeQuery=true
使用原生SQL。當自動生成無法滿足需求,或為了性能使用數據庫特定查詢時,用此注解非常有用。提供模塊: 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 查詢根據郵箱獲取用戶。?1
表示第一個參數。findTopByStatus
方法使用原生SQL查詢指定狀態的用戶若干條,使用命名參數:status
和:limit
。需要搭配@Param
注解綁定參數值。
Spring Data JPA 在運行時會解析這些注解并生成相應實現代碼執行查詢。@Query
能大大提升查詢的靈活性,但要注意JPQL語句的正確性以及原生SQL的可移植性。
八、面向切面編程(AOP)注解
Spring AOP 提供了強大的面向切面編程功能,可以通過注解定義橫切關注點,如日志記錄、性能監控、權限檢查等。主要的 AOP 注解包括:
@Aspect
簡介: @Aspect
注解用于將一個類聲明為切面類。由 AspectJ 提供(Spring AOP 使用 AspectJ 注解風格)。標記為 @Aspect
的類內部可以定義切點和通知,實現 AOP 功能。需要配合 Spring AOP 使用。
作用與場景: 切面類匯總了橫切邏輯,例如日志切面、安全切面等。一個切面類里通常包含若干通知方法(@Before、@After等)和切點定義(@Pointcut)。Spring 在運行時會根據切面定義生成代理對象,將橫切邏輯織入目標對象的方法調用。提供模塊: org.aspectj.lang.annotation.Aspect
(需要 spring-boot-starter-aop 或 Spring AOP 模塊依賴)。
使用示例:
@Aspect
@Component // 切面本身也需注冊為Bean
public class LoggingAspect {// 切點定義:匹配 service 包下所有類的公共方法@Pointcut("execution(public * com.example.service..*(..))")public void serviceMethods() {}// 前置通知:在滿足切點的方法執行之前執行@Before("serviceMethods()")public void logBefore(JoinPoint joinPoint) {System.out.println("Entering: " + joinPoint.getSignature());}// 后置通知:無論方法正常或異常結束都會執行@After("serviceMethods()")public void logAfter(JoinPoint joinPoint) {System.out.println("Exiting: " + joinPoint.getSignature());}
}
上面定義了一個日志切面類 LoggingAspect
:
- 使用
@Aspect
標記為切面類,結合@Component
使其成為 Spring Bean。 serviceMethods()
方法使用@Pointcut
定義了一個切點,表達式"execution(public * com.example.service..*(..))"
表示匹配com.example.service
包及子包下所有類的任意公共方法執行。切點方法本身沒有實現,僅作標識。logBefore
方法使用@Before("serviceMethods()")
注解,表示在執行匹配serviceMethods()
切點的任意方法之前,先執行該通知。通過JoinPoint
參數可以獲取被調用方法的信息。logAfter
方法使用@After("serviceMethods()")
,表示目標方法執行完成后(無論成功與否)執行。輸出方法簽名的退出日志。
為使上述 AOP 生效,需要啟用 Spring 對 AspectJ 切面的支持。Spring Boot 自動配置已經啟用了 AOP(如果引入了 starter-aop,默認會開啟 @AspectJ 支持),在非 Boot 環境可能需要在配置類上添加 @EnableAspectJAutoProxy
注解來開啟代理機制。如果未啟用,@Aspect
注解不會生效。總之,@Aspect
注解的類定義了 AOP 的橫切邏輯,是實現日志、事務、權限等橫切關注點的關鍵。
@Pointcut
簡介: @Pointcut
用于定義一個切點表達式,以命名方式重用切點。由 AspectJ 注解提供。通常是一個簽名為 void 且無實現的方法注解,用于給切點命名。
作用與場景: 切點定義了哪些連接點(Join Point)需要織入切面邏輯。通過@Pointcut
可以將復雜的切點表達式進行抽象,方便在多個通知上引用,避免重復書寫表達式。提供模塊: AspectJ。
使用示例:
@Aspect
@Component
public class SecurityAspect {// 切點: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() {// 執行權限驗證邏輯if (!SecurityContext.isAuthenticated()) {throw new SecurityException("User not authenticated");}}
}
此處,SecurityAspect
定義了一個切點 allGetEndpoints()
,通過 @Pointcut
注解的表達式指定:凡是標注了@RestController
的類中,標注了@GetMapping
的方法,都是切點。然后在 @Before("allGetEndpoints()")
通知中引用這個切點,執行權限檢查。如果當前用戶未認證則拋出異常阻止方法執行。
切點表達式語言十分豐富,可以基于執行方法簽名(execution)、注解(@annotation, within 等)、this/target對象等進行匹配組合。通過適當的切點定義,可以靈活地選擇哪些點應用橫切邏輯。
@Before
簡介: @Before
定義一個前置通知(Advice),即在目標方法執行之前執行的切面方法。它由 AspectJ 提供(org.aspectj.lang.annotation.Before
)。需要在 @Aspect
切面類中使用,注解的值是一個切點表達式或命名切點。
作用與場景: 前置通知通常用于在方法調用前執行一些檢查、日志或準備工作。例如權限驗證(見上例)、記錄方法開始日志、在方法執行前設置環境(如初始化 ThreadLocal)等。在目標方法之前執行,不影響目標方法參數和執行結果,只作附加操作。
使用示例: 參考前述 LoggingAspect
和 SecurityAspect
中的 @Before
用法。在 LoggingAspect
中:
@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) { ... }
這表示在匹配 serviceMethods()
切點的每個目標方法執行前調用 logBefore
方法。JoinPoint
參數可以獲取方法簽名、參數等信息,用于日志輸出。
@Before
通知不能阻止目標方法執行(除非拋出異常)。如果在通知中拋異常,目標方法將不會執行且異常向上拋出。因此一般前置通知不故意拋異常(權限驗證除外,驗證失敗則通過異常中斷執行)。
@After
簡介: @After
定義一個后置通知,即在目標方法執行結束后執行的切面方法,無論目標方法正常返回還是拋出異常都會執行(類似 finally block)。由 AspectJ 提供。
作用與場景: 常用于清理資源、記錄方法結束日志等操作。例如在方法完成后記錄執行時間(需要結合開始時間),或確保某些線程上下文數據被清除。不關心方法的結果,只要離開方法就執行通知。
使用示例: 參考 LoggingAspect
中:
@After("serviceMethods()")
public void logAfter(JoinPoint joinPoint) { ... }
不管 serviceMethods()
匹配的方法成功或異常返回,都會執行 logAfter
。可以用它來打印離開方法的日志。
如果需要根據方法是否拋異常做區分,可以使用 @AfterReturning
或 @AfterThrowing
(詳見下文)。@After
通常用來放置最終執行的操作,比如解鎖資源,不管成功失敗都要執行的。
@AfterReturning
簡介: @AfterReturning
定義一個返回通知,在目標方法成功返回后執行(未拋異常)。可以捕獲返回值。由 AspectJ 提供。
作用與場景: 當需要獲取目標方法的返回結果進行處理時,可使用 @AfterReturning
。例如日志中記錄返回值,或者根據返回值做后續動作。若目標方法拋異常則不會執行此通知。注解可指定 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
方法執行,如果其正常完成,則將返回值綁定到 result
形參并打印日志。比如 result 可能是訂單ID或確認對象。若 placeOrder
拋異常,則該通知不執行。
@AfterThrowing
簡介: @AfterThrowing
定義一個異常通知,在目標方法拋出指定異常后執行。由 AspectJ 提供。可捕獲異常對象。
作用與場景: 用于統一處理或記錄目標方法拋出的異常,例如記錄錯誤日志、發送告警等。可以指定 throwing
屬性將異常綁定到參數。只在有未捕獲異常時執行,正常返回不執行。
使用示例:
@AfterThrowing(pointcut = "execution(* com.example..*.*(..))", throwing = "ex")
public void logException(Exception ex) {System.err.println("Exception in method: " + ex.getMessage());
}
該切面方法會在應用中任何未捕獲的異常拋出時執行,打印異常信息。ex
參數即目標方法拋出的異常對象(可以指定具體異常類型過濾,如 throwing="ex" throwing=RuntimeException.class
)。
通過 @AfterThrowing
可以集中處理異常情況,例如對特定異常進行額外處理(如事務補償或資源回收),或統一記錄。
@Around
簡介: @Around
定義一個環繞通知,它包裹了目標方法的執行。由 AspectJ 提供。環繞通知最為強大,可以在方法執行前后都進行處理,并可決定是否、如何執行目標方法(通過 ProceedingJoinPoint
調用)。
作用與場景: 可以用來計算執行時間、控制方法執行(比如實現自定義注解的權限校驗并決定是否調用原方法)、修改方法的返回值甚至攔截異常。@Around
通知需要顯式調用 proceed()
才會執行目標方法,如果不調用則目標方法不執行。這讓我們有機會在調用前后插入邏輯,甚至改變執行流程。
使用示例:
@Around("execution(* com.example.service.*.*(..))")
public Object measureExecutionTime(ProceedingJoinPoint pjp) throws Throwable {long start = System.currentTimeMillis();Object result;try {result = pjp.proceed(); // 執行目標方法} finally {long end = System.currentTimeMillis();System.out.println(pjp.getSignature() + " executed in " + (end - start) + "ms");}return result;
}
這個環繞通知為 service 包下所有方法計算執行時間:
- 在調用目標方法前記錄開始時間。
- 通過
pjp.proceed()
執行目標方法,將返回結果保存。 - 方法執行后計算時間差并打印。
- 將目標方法的返回值返回,保證調用流程正常進行。
如果目標方法拋異常,proceed()
會拋出異常到外層(如上例沒有 catch,finally執行后異常繼續拋出)。也可以在環繞通知中捕獲異常并處理,甚至返回替代結果,從而吞掉異常(視業務需要謹慎處理)。
@Around
通知還可以實現諸如自定義注解攔截功能,例如檢查方法上是否有某注解,有則執行特殊邏輯等,靈活性最高。
@EnableAspectJAutoProxy
簡介: @EnableAspectJAutoProxy
是 Spring 提供的注解(org.springframework.context.annotation.EnableAspectJAutoProxy
),用于開啟基于注解的 AOP 支撐。它會啟用 AspectJ 注解的自動代理機制。
作用與場景: 在純 Spring 配置中,需要在配置類上添加此注解才能使前述 @Aspect
切面生效(等同于 XML 配置中的 <aop:aspectj-autoproxy/>
)。Spring Boot 在引入 AOP 起步依賴時,默認已經啟用了該功能 ,因此多數情況下無需顯式添加。但了解這個注解有助于在需要調整 AOP 代理選項時使用(比如 proxyTargetClass=true
強制使用CGLIB代理)。
使用示例:
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig {// 切面類Bean或通過@Component掃描切面類
}
這將在容器中搜索 @Aspect
注解的類,自動創建代理。proxyTargetClass=true
強制使用類代理而不是接口代理。默認為 false,即如果有實現接口則用JDK動態代理。這一點在需要代理沒有接口的類或者希望統一使用CGLIB代理時可以設置。
總結而言,Spring AOP 的注解允許我們以聲明方式實現橫切邏輯,將日志、性能監控、安全檢查等與業務代碼分離,提升模塊化和可維護性。
九、異步與定時任務注解
Spring 提供了對多線程異步任務和定時調度的支持,只需通過注解即可開啟這些功能。
@Async
簡介: @Async
注解用于將某個方法聲明為異步執行。由 Spring 提供(org.springframework.scheduling.annotation.Async
)。標注該注解的方法會在調用時由 Spring 異步執行,而不是同步阻塞當前線程。通常需要配合 @EnableAsync
一起使用。
作用與場景: 當某些操作不需要同步完成、可以在后臺線程執行時,用 @Async
能簡化并發編程。例如發送郵件、短信通知,執行耗時的計算而不阻塞主流程,或并行調用多個外部服務等。Spring 會基于 TaskExecutor
(默認SimpleAsyncTaskExecutor)調度異步方法。方法可以返回 void
或 Future
/CompletableFuture
以便獲取結果。
使用示例:
@Service
public class NotificationService {@Asyncpublic void sendEmail(String to, String content) {// 模擬發送郵件的耗時操作try {Thread.sleep(5000);System.out.println("Email sent to " + to);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
}@SpringBootApplication
@EnableAsync // 開啟異步支持
public class MyApp { ... }
在 NotificationService
中,sendEmail
標注了 @Async
,因此當它被調用時,Spring 會從線程池中拿出一個線程來異步執行該方法,原調用方線程不必等待5秒。需要在應用主類或配置類上添加 @EnableAsync
以激活異步處理能力。使用默認配置時,Spring 會使用一個簡單線程池執行,也可以通過定義 Executor
Bean 并加上 @Async("beanName")
來指定特定線程池。
調用異步方法示例:
@RestController
public class OrderController {@Autowiredprivate NotificationService notificationService;@PostMapping("/order")public ResponseEntity<String> placeOrder(@RequestBody Order order) {orderService.process(order);// 異步發送通知notificationService.sendEmail(order.getEmail(), "Your order is placed.");return ResponseEntity.ok("Order received");}
}
placeOrder
方法調用了 sendEmail
,因為后者是異步的,所以 placeOrder
在觸發郵件發送后會立即返回響應,郵件發送在另一個線程進行,不影響接口響應時間。異步調用的異常需特別處理,可以使用 AsyncUncaughtExceptionHandler
或返回 Future
在調用方監聽。總之,@Async
大大方便了將任務異步化。
@EnableAsync
簡介: @EnableAsync
是 Spring 提供的注解(org.springframework.scheduling.annotation.EnableAsync
),用于開啟對 @Async
注解的處理。加在配置類或主啟動類上,激活 Spring 異步方法執行的能力。
作用與場景: 類似于 @EnableAspectJAutoProxy
之于 AOP,對于異步也需要顯式開啟。Spring Boot 自動配置通常不會主動開啟異步,所以需要開發者添加此注解。提供模塊: Spring Context 調度任務支持。
使用示例: 見上方,將 @EnableAsync
放在 @SpringBootApplication
類上或獨立的配置類上均可。
啟用后,Spring 容器會搜索應用中標注了 @Async
的 Bean 方法,并通過代理的方式調用線程池執行它們。默認的執行器可以通過定義 TaskExecutor
Bean 來覆蓋。如:
@Bean(name = "taskExecutor")
public Executor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(10);executor.initialize();return executor;
}
定義名為 “taskExecutor” 的執行器后,Spring @Async
會自動使用它(因為默認執行器名稱就是 taskExecutor)。也可在 @Async
注解的參數中指定一個自定義執行器 Bean 名稱。
@Scheduled
簡介: @Scheduled
注解用于將方法標記為定時任務。由 Spring 提供(org.springframework.scheduling.annotation.Scheduled
)。可以通過 cron 表達式或固定間隔等配置何時運行該方法。需要配合 @EnableScheduling
開啟調度支持。
作用與場景: 當需要周期性地執行某段代碼時,例如每隔一段時間檢查庫存,每天夜間生成報表等,可以使用 @Scheduled
注解而不需要借助外部的調度框架。Spring 容器會在后臺線程按指定計劃調用這些方法。支持多種調度配置:
cron
表達式:通過 Cron 定義復雜時間計劃。- 固定速率
fixedRate
:以上一次開始時間為基準,間隔固定毫秒執行。 - 固定延遲
fixedDelay
:以上一次完成時間為基準,延遲固定毫秒執行。 - 可選屬性如
initialDelay
等設置啟動延遲。
使用示例:
@Component
public class ReportTask {@Scheduled(cron = "0 0 2 * * ?")public void generateDailyReport() {// 每天凌晨2點生成報告System.out.println("Generating daily report at " + LocalDate.now());// ... 報表生成邏輯}@Scheduled(fixedRate = 60000)public void checkSystemHealth() {// 每隔60秒檢查系統健康System.out.println("Health check at " + Instant.now());// ... 健康檢查邏輯}
}
這里,ReportTask
類中:
generateDailyReport()
使用cron="0 0 2 * * ?"
,表示每天2:00執行(Cron表達式:“秒 分 時 日 月 周”,“”表示不指定周幾)。這個方法將在主線程之外的調度線程按計劃調用。checkSystemHealth()
使用fixedRate=60000
表示每60秒執行一次,不論上次執行多長時間,都按固定頻率觸發。若上次尚未執行完,新周期到了默認不會并發執行(調度器會等待),但可以通過配置Scheduler
實現并發。
為了使 @Scheduled
生效,需要在配置類上添加 @EnableScheduling
(見下文)。Spring Boot 應用通常也需要手動加這一注解。定時任務執行由 Spring 的 TaskScheduler(默認SingleThreadScheduler)驅動,可能需要注意任務不應長時間阻塞,否則會影響后續任務調度。可自定義線程池 TaskScheduler 以提高并發度。
@EnableScheduling
簡介: @EnableScheduling
注解用于開啟 Spring 對定時任務調度的支持(org.springframework.scheduling.annotation.EnableScheduling
)。添加在配置類或主類上。
作用與場景: 沒有這個注解,@Scheduled
等注解不會被識別處理。啟用后,Spring 容器會啟動一個調度線程池,定時調用標記的方法。提供模塊: Spring Context 定時任務支持。
使用示例:
@SpringBootApplication
@EnableScheduling
public class Application { ... }
將 @EnableScheduling
放在啟動類上即可激活調度機制。然后所有 @Scheduled
注解的方法都會按照配置的計劃執行。Spring Boot 不會自動開啟定時任務支持,因為有的應用可能不需要調度功能,所以必須顯式聲明。
如果需要自定義調度器,可以定義 Scheduler
Bean 或 TaskScheduler
Bean。默認使用單線程執行所有定時任務,若多個任務需要并行,建議提供 ThreadPoolTaskScheduler
Bean。
通過 @Async
和 @Scheduled
這組注解,Spring 讓并發編程和任務調度變得非常容易,不再需要顯式創建線程或使用外部調度平臺,在應用內部即可完成這些邏輯。
十、緩存注解
Spring 提供了便捷的緩存機制,通過注解即可實現方法級緩存,把方法調用結果存儲起來,避免重復計算或數據庫查詢。
@EnableCaching
簡介: @EnableCaching
注解用于開啟 Spring 對緩存注解的支持(org.springframework.cache.annotation.EnableCaching
)。通常加在配置類或主類上,激活緩存管理能力。
作用與場景: 開啟后,Spring 會自動配置一個緩存管理器(可基于內存、EhCache、Redis等,取決于依賴配置),并掃描應用中的緩存注解(如 @Cacheable
等),在運行時用AOP代理實現緩存邏輯。提供模塊: Spring Cache。
使用示例:
@SpringBootApplication
@EnableCaching
public class Application { ... }
這樣,Spring Boot 就會自動根據 classpath 中的緩存庫選擇緩存實現(如有 spring-boot-starter-cache 默認用 ConcurrentMapCache 簡單實現;如果引入 spring-boot-starter-redis 則使用 RedisCacheManager 等)。確保在使用緩存注解前調用了 @EnableCaching
,否則緩存注解不會生效。
@Cacheable
簡介: @Cacheable
用于標記方法,將其返回結果緩存起來。由 Spring 提供(org.springframework.cache.annotation.Cacheable
)。再次調用該方法時,如果傳入參數相同且緩存中有結果,則直接返回緩存而不執行方法。
作用與場景: 典型用于讀取操作緩存,例如從數據庫查詢數據后緩存,下次查詢相同參數可以直接返回緩存值,提高性能。@Cacheable
需要指定緩存的名稱(cacheName)以及緩存鍵(key),可以是 SpEL 表達式。默認鍵根據所有參數自動生成(需參數可哈希)。提供模塊: Spring Cache。
使用示例:
@Service
public class ProductService {@Cacheable(value = "products", key = "#id")public Product getProductById(Long id) {// 假設這里有復雜計算或慢速數據庫查詢System.out.println("Loading product " + id + " from DB...");return productRepository.findById(id).orElse(null);}
}
配置 @Cacheable(value="products", key="#id")
:
value
指定緩存的名字叫 “products”(類似分類,可對應不同緩存存儲)。key="#id"
表示使用方法參數id
作為緩存鍵。
第一次調用 getProductById(1L)
時,會打印“Loading product 1 from DB…”并查詢數據庫,然后結果緩存到名為 “products” 的緩存中,鍵為 1
。第二次調用 getProductById(1L)
,Spring 檢測到相同鍵在緩存中有值,直接返回緩存,不執行方法主體,因此不會再打印那條日志。
@Cacheable
還有一些屬性:
condition
:滿足條件時才緩存或才查緩存,如condition="#id > 10"
.unless
:方法執行完后判斷,如果滿足條件則不緩存結果,如unless="#result == null"
.sync
:是否在并發場景下同步,只讓一個線程計算緩存,其它等待。
@CachePut
簡介: @CachePut
注解用于將方法返回值直接放入緩存,但與 @Cacheable
不同的是,它始終執行方法,不會跳過。它通常用于更新緩存數據。由 Spring 提供。
作用與場景: 當執行了修改操作后,希望緩存與數據庫同步更新,可使用 @CachePut
標記修改方法,使其結果及時寫入緩存。這樣后續再讀緩存可以得到最新值。提供模塊: Spring Cache。
使用示例:
@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {System.out.println("Updating product " + product.getId());return productRepository.save(product);
}
每次調用 updateProduct
,都會執行保存操作并返回更新后的 Product
。@CachePut
注解確保無論如何,這個返回的 Product
對象會以其 id
作為鍵,存入 “products” 緩存(覆蓋舊值)。因此,即便之前通過 @Cacheable
緩存過舊的 Product
數據,這里也會更新緩存,使之與數據庫一致。值得注意的是,@CachePut
不會影響方法執行(總會執行方法),它只是在返回后把結果寫緩存。
@CacheEvict
簡介: @CacheEvict
注解用于移除緩存。標記在方法上,可以在方法執行前或后將指定 key 或整個緩存清除。由 Spring 提供。
作用與場景: 當數據被刪除或改變且緩存不再有效時,需要清除緩存。例如刪除一個記錄后,需要把對應緩存刪掉;批量更新后,可以選擇清空整個緩存。@CacheEvict
支持指定 key
或設置 allEntries=true
清空整個命名緩存。提供模塊: Spring Cache。
使用示例:
@CacheEvict(value = "products", key = "#id")
public void deleteProduct(Long id) {System.out.println("Deleting product " + id);productRepository.deleteById(id);
}
調用 deleteProduct(5)
時,@CacheEvict
會使緩存 “products” 中鍵為5的條目無效(刪除)。默認地,它在方法成功執行后清除緩存。如果希望無論方法是否成功都清除,可設定 beforeInvocation=true
,那將在方法進入時就清除(防止方法拋異常緩存未清)。allEntries=true
則可以不顧鍵,直接清空整個緩存空間。例如:
@CacheEvict(value = "products", allEntries = true)
public void refreshAllProducts() { ... }
會清除 “products” 緩存的所有條目。
通過 @CacheEvict
與 @CachePut
,我們可以維護緩存與底層數據的一致性。
注意: 使用緩存注解要求配置正確的 CacheManager
和緩存存儲。Spring Boot 默認使用簡單的內存緩存(ConcurrentMapCacheManager)用于開發測試。生產中常結合 Redis、Ehcache等實現,更換實現通常無需改動注解,只需配置 CacheManager Bean。
十一、事件監聽注解
Spring 提供了應用內事件發布-訂閱機制,支持松耦合的消息通信。通過注解可以方便地訂閱事件。
@EventListener
簡介: @EventListener
是 Spring 4.2+ 引入的注解(org.springframework.context.event.EventListener
),用于將任意 Spring Bean 的方法標識為事件監聽器。當有匹配的事件發布時(實現 ApplicationEvent
或自定義事件對象),該方法會被調用。相比實現 ApplicationListener
接口,注解方式更簡潔。
作用與場景: 在應用內,不同組件之間可以通過發布事件進行解耦通訊。例如用戶注冊后發布一個 UserRegisteredEvent
,由其他監聽器監聽來發送歡迎郵件或統計指標。使用 @EventListener
,方法簽名定義了它感興趣的事件類型,也可以通過 condition
屬性設置過濾條件(比如只處理某字段滿足條件的事件)。提供模塊: Spring Context 事件機制。
使用示例:
// 定義事件類(可以繼承 ApplicationEvent 也可以是普通類)
public class UserRegisteredEvent /* extends ApplicationEvent */ {private final User user;public UserRegisteredEvent(User user) { this.user = user; }public User getUser() { return user; }
}// 發布事件的組件
@Service
public class UserService {@Autowired private ApplicationEventPublisher eventPublisher;public void register(User user) {// ... 保存用戶邏輯// 發布事件eventPublisher.publishEvent(new UserRegisteredEvent(user));}
}// 監聽事件的組件
@Component
public class WelcomeEmailListener {@EventListenerpublic void handleUserRegistered(UserRegisteredEvent event) {User newUser = event.getUser();// 發送歡迎郵件System.out.println("Sending welcome email to " + newUser.getEmail());// ... 實際發送郵件邏輯}
}
流程說明:
UserService.register
方法在新用戶注冊成功后,通過ApplicationEventPublisher
發布了一個UserRegisteredEvent
事件。Spring Boot 默認通過ApplicationEventPublisher
將事件發布到應用上下文。WelcomeEmailListener
是一個普通組件(被@Component
掃描)。其中方法handleUserRegistered
標注了@EventListener
,且參數是UserRegisteredEvent
。這表明它訂閱此類型事件。當事件被發布時,Spring 檢測到存在匹配的監聽方法,便調用該方法并將事件對象傳入。- 監聽方法運行,完成發送歡迎郵件的功能。
這樣,發送郵件的邏輯和用戶服務邏輯完全解耦,只通過事件聯系。如果以后不需要發送郵件,只需移除監聽器,而不影響用戶注冊流程。另外,可以很容易地新增其它監聽,如統計注冊用戶數的監聽器,而不需要修改 UserService。
@EventListener
還支持 condition
屬性使用 SpEL 表達式進行事件內容過濾。例如:
@EventListener(condition = "#event.user.vip")
public void handleVipUserRegistered(UserRegisteredEvent event) { ... }
僅當用戶是VIP時才處理。這種細粒度控制進一步增強了事件機制的靈活性。
需要注意,默認事件監聽器在發布線程內同步執行。如果想異步處理事件,可以結合 @Async
注解,將監聽方法異步執行(前提是已啟用 @EnableAsync
)。或者使用 Spring 5 提供的 ApplicationEventMulticaster
配置為異步模式。
ApplicationListener
接口 (替代方案)
說明: 在 @EventListener
出現之前,Spring 使用實現 ApplicationListener<E>
接口的方式來監聽事件。雖然這不是注解,但與事件注解結合使用時值得一提。任何 Spring Bean 實現了 ApplicationListener<MyEvent>
,當 MyEvent
發布時其 onApplicationEvent
方法會被調用。自 Spring 4.2 起推薦使用 @EventListener
代替,更加簡潔。
使用示例:
@Component
public class StatsListener implements ApplicationListener<UserRegisteredEvent> {@Overridepublic void onApplicationEvent(UserRegisteredEvent event) {// 統計用戶注冊metrics.increment("user.register.count");}
}
這個監聽器無須注解,Spring根據泛型自動注冊。但相比注解方式,它需要一個獨立的類實現接口,不如 @EventListener
可以直接用任意方法方便。而且一個類只能實現對一種事件的監聽,要監聽多種事件需要寫多個類或使用一些if判斷,不如注解靈活。因此現在開發中更多使用 @EventListener
。
綜上,Spring 的事件模型通過發布訂閱實現了應用內部的解耦協作。@EventListener
極大降低了使用門檻,使得監聽事件就像寫普通方法一樣便捷。配合異步能力,還能實現類似消息隊列的效果,用于不太關鍵的異步通知等場景。
十二、測試相關注解
Spring 為了方便編寫測試,特別是針對 Spring MVC 或 JPA等組件的測試,提供了一系列注解來簡化配置測試上下文。
@SpringBootTest
簡介: @SpringBootTest
是 Spring Boot 測試框架提供的注解(org.springframework.boot.test.context.SpringBootTest
),用于在測試類上,表示啟動一個完整的 Spring Boot 應用上下文進行集成測試。
作用與場景: 標注此注解的測試類在運行時會通過 Spring Boot 引導啟動應用(除非配置特定屬性使其部分引導),這意味著:
- 會掃描并創建所有 Bean,加載完整應用上下文。
- 提供對 Bean 的依賴注入支持,使測試類可以直接
@Autowired
需要的 Bean 進行集成測試。
它常用于需要測試多個層級協同工作的場景,例如驗證服務層和倉庫層交互,或者整個請求流程。
使用示例:
@SpringBootTest
class ApplicationTests {@Autowiredprivate UserService userService;@Testvoid testUserRegistration() {User user = new User("alice");userService.register(user);// 驗證注冊結果,比如檢查數據庫或事件發布效果assertNotNull(user.getId());}
}
這個測試類使用 @SpringBootTest
,則測試運行時 Spring Boot 會啟動應用上下文并注入 UserService
Bean,測試方法里可以直接調用業務代碼進行驗證。@SpringBootTest
還可以指定啟動端口、環境等參數,或通過 properties
覆蓋配置,比如:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
用于啟動嵌入式服務器在隨機端口,以進行 Web 集成測試。
@WebMvcTest
簡介: @WebMvcTest
是用于測試 Spring MVC 控制器的注解(org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
)。它會啟動一個精簡的 Spring MVC 環境,只包含Web相關的Bean,如@Controller、@RestController等,以及MVC配置,而不加載整個應用上下文。
作用與場景: 主要用于單元測試控制器。默認只掃描 @Controller
和 @RestController
等 Web 層組件,以及必要的配置(如 Jackson 轉換、Validator)。不會加載服務層、倉庫層Bean,除非通過配置指定。這樣測試運行速度快且聚焦于MVC層邏輯。常配合 MockMvc
(Spring提供的模擬MVC請求的工具)使用進行控制器的請求/響應測試。
使用示例:
@WebMvcTest(controllers = UserController.class)
class UserControllerTests {@Autowiredprivate MockMvc mockMvc;@MockBeanprivate UserService userService; // 將UserService模擬@Testvoid testGetUser() throws Exception {User user = new User(1L, "Bob");// 定義當userService.findById(1)被調用時返回user對象given(userService.findById(1L)).willReturn(user);mockMvc.perform(get("/users/1")).andExpect(status().isOk()).andExpect(jsonPath("$.name").value("Bob"));}
}
此測試類標注 @WebMvcTest(UserController.class)
:
- Spring Boot 會僅啟動與
UserController
相關的 MVC 組件,如UserController
本身,MVC配置,序列化組件等。 UserService
因為不是@Controller組件,不會自動加載。因此使用了@MockBean
注解(見后)創建一個模擬的UserService
Bean,將其注入到UserController
中,避免涉及真實的服務層邏輯。- 測試使用
MockMvc
發起GET請求到/users/1
,并斷言返回狀態200和返回JSON中的name字段為"Bob"。由于我們預先通過given(userService.findById(1L))
指定了模擬行為,所以控制器調用userService時會得到我們構造的user對象。
通過這種方式,不需要啟動整個應用,也不需要真實數據庫等,就能測試控制器映射、參數解析、返回結果等。@WebMvcTest
提供了對Spring MVC各方面的支持(如可以自動配置MockMvc)。
@DataJpaTest
簡介: @DataJpaTest
是用于測試 JPA 持久層的注解(org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
)。它會啟動一個只包含 JPA 相關組件的 Spring 應用上下文,例如實體管理器、Spring Data JPA 倉庫、嵌入式數據庫等。
作用與場景: 主要用于單元測試 Repository 層。它會:
- 配置嵌入式內存數據庫(如H2)用于測試,除非明確指定其他DataSource。
- 掃描
@Entity
實體和 Spring Data Repository 接口并注冊。 - 不加載 web、安全等其他非持久層Bean,以加快測試速度。
- 默認使用事務包裝每個測試,并在結束時回滾,保證測試隔離。
使用示例:
@DataJpaTest
class UserRepositoryTests {@Autowiredprivate UserRepository userRepository;@Testvoid testFindByEmail() {// 準備測試數據User user = new User();user.setEmail("test@example.com");user.setName("Test");userRepository.save(user);// 執行查詢方法User found = userRepository.findByEmail("test@example.com");assertEquals("Test", found.getName());}
}
這里 @DataJpaTest
將自動配置一個內存數據庫并初始化 JPA 環境。UserRepository
接口(假設繼承自 JpaRepository)會被加載為Bean。測試中先保存一個用戶,然后調用倉庫自定義方法 findByEmail
驗證結果。由于測試結束時事務會回滾,插入的測試數據不會污染下一個測試或實際數據庫。
@DataJpaTest
同樣可以與 @MockBean
配合如果需要模擬一些非JPA的Bean,但是通常持久層測試不需要。也可以通過 properties 指定連接真實數據庫進行集成測試,不過大多數情況下,使用內存數據庫足以測試Repository邏輯。
@MockBean
簡介: @MockBean
是 Spring Boot Test 提供的注解(org.springframework.boot.test.mock.mockito.MockBean
),用于在 Spring 測試上下文中添加一個由 Mockito 模擬的Bean,并替換掉容器中原本該類型的Bean(如果有)。常用于在 Web層或服務層測試中,模擬依賴的Bean行為。
作用與場景: 當測試的目標Bean有依賴,而我們不想測試依賴的真實邏輯(可能復雜或不確定),就可以用 @MockBean
來提供一個Mockito創建的模擬對象給容器。這比手工使用 Mockito.mock 然后手動注入更方便,因為 Spring 會自動把這個模擬Bean注入到需要它的地方。典型應用是在 @WebMvcTest
中模擬服務層Bean,在 @SpringBootTest
中模擬外部系統客戶端Bean等。
使用示例:
@MockBean
private WeatherService weatherService;
將 WeatherService
接口模擬為一個 Bean 注入容器。如果應用上下文本來有一個該類型的Bean(比如真實的實現),會被模擬對象替換。這使得我們可以用 given(weatherService.getTodayWeather())...
等來預設行為。這個注解可以用在測試類的字段上(如上)、也可以用在測試方法內參數上。
具體的用法在前面的 @WebMvcTest
示例已經體現。再比如,在一個服務層測試中:
@SpringBootTest
class OrderServiceTests {@Autowiredprivate OrderService orderService;@MockBeanprivate PaymentClient paymentClient; // 模擬外部支付服務客戶端@Testvoid testOrderPayment() {Order order = new Order(...);// 假定調用外部支付返回成功結果given(paymentClient.pay(order)).willReturn(new PaymentResult(true));boolean result = orderService.processPayment(order);assertTrue(result);// 驗證內部行為,如訂單狀態更新// ...}
}
這里 OrderService 依賴 PaymentClient,但我們不想真的調用外部服務,于是用 @MockBean 模擬它并規定返回 PaymentResult 成功。這樣 OrderService.processPayment 執行時實際上用的是假的 PaymentClient,但可以測試 OrderService 自身的邏輯是否正確處理了成功結果。注意: @MockBean
底層使用 Mockito,所以需要確保引入了 Mockito 相關依賴。
其他測試注解:
@SpringBootTest
和@WebMvcTest
,@DataJpaTest
是 Spring Boot Test 提供的測試切面注解,此外還有類似@WebFluxTest
(測試WebFlux控制器)、@JdbcTest
(測試JDBC)、@JsonTest
(測試JSON序列化)等,根據需要使用。- JUnit本身的注解如
@Test
,@BeforeEach
,@AfterEach
等也在測試中大量使用(如上示例已經用到 @Test)。雖然不屬于Spring范疇,但也算開發中常用的注解之一。
通過這些測試注解,開發者可以方便地編寫隔離的測試用例。例如只啟動Web層或持久層進行單元測試,大大提高測試執行速度和定位問題的精準度。Spring Boot 自動配置為測試裁剪了上下文,避免加載無關bean,使測試既保持類似生產環境的行為,又能高效運行。
十三、安全相關注解
Spring Security 框架提供了方法級安全控制的注解和配置注解,方便對控制器或服務方法實施權限檢查。此外還有開啟安全的配置注解等。
@EnableWebSecurity
簡介: @EnableWebSecurity
是用于開啟 Spring Security Web 安全支持的注解(org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
)。通常加在一個繼承 WebSecurityConfigurerAdapter(Spring Security 5.7 之前)的配置類上,或者加在包含 SecurityFilterChain Bean 的配置類上。它啟用了 Spring Security 的過濾器鏈。
作用與場景: 使用 Spring Security 時,需要此注解來加載 Web 安全配置,使應用受 Spring Security 管理。提供模塊: Spring Security Config。
使用示例:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN").anyRequest().authenticated().and().formLogin();}
}
上述經典用法在 Spring Security 5.7 之前通過繼承 WebSecurityConfigurerAdapter 來配置。@EnableWebSecurity
注解開啟安全功能。Spring Boot 自動配置也會在引入 starter-security 時添加該注解,因此有時無需手動添加;但當我們提供自定義安全配置類時,一般會注明此注解。注意: Spring Security 5.7 開始,官方更推薦不繼承類而是聲明 SecurityFilterChain Bean 配合 @EnableWebSecurity
使用,但注解作用相同。
@EnableGlobalMethodSecurity
(已過時) / @EnableMethodSecurity
簡介: @EnableGlobalMethodSecurity
用于開啟方法級安全注解的支持(如 @PreAuthorize
, @Secured
)。這是 Spring Security 舊版本使用的注解,位于 org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
。它已經在 Spring Security 6 被替換為新的 @EnableMethodSecurity
(@EnableGlobalMethodSecurity
標記為已棄用)。
作用與場景: 加在 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
(因為 prePostEnabled=true) 和 @Secured
注解(因為 securedEnabled=true)。在 Security 6 中,等價的做法是:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig { ... }
@EnableMethodSecurity
默認就啟用了 Pre/Post,所以可以不用顯式 prePostEnabled,secured和jsr250需要明確true。
總而言之,在當前的 Spring Boot 3 / Security 6 環境中,使用 @EnableMethodSecurity
取代 @EnableGlobalMethodSecurity
來開啟方法安全注解支持。
@PreAuthorize
簡介: @PreAuthorize
是 Spring Security 的方法級權限注解(org.springframework.security.access.prepost.PreAuthorize
)。它可以用在方法或類上,在方法調用之前基于給定的表達式進行權限驗證。需要啟用了全局方法安全后(如上),此注解才會生效。
作用與場景: @PreAuthorize
可以檢查當前認證用戶是否具備某權限或角色,或者滿足SpEL表達式定義的任意條件,然后才允許方法執行。常用于服務層或控制層方法,保護敏感操作。例如只有ADMIN角色能調用刪除用戶方法,或者只有資源擁有者才能訪問資源等。它比 @Secured
更強大,因為可以使用Spring EL編寫復雜的邏輯。
使用示例:
@Service
public class AccountService {@PreAuthorize("hasRole('ADMIN')")public void deleteAccount(Long accountId) {// 只有ADMIN角色用戶才能執行accountRepository.deleteById(accountId);}@PreAuthorize("#user.name == authentication.name or hasAuthority('SCOPE_profile')")public Profile getUserProfile(User user) {// 用戶本人或具有profile權限的可以查看return profileRepository.findByUser(user);}
}
deleteAccount
方法上,@PreAuthorize("hasRole('ADMIN')")
限制只有具有ROLE_ADMIN的用戶可以調用,否則會被拒絕(拋出AccessDeniedException
)。getUserProfile
方法上,使用了表達式:#user.name == authentication.name or hasAuthority('SCOPE_profile')
。authentication.name
代表當前登錄用戶名。如果傳入的user.name
等于當前用戶名(即查詢自己的資料),或當前主體具有SCOPE_profile
權限(例如 OAuth2 scope),則允許訪問。否則拒絕。可以看到PreAuthorize能夠引用方法參數(通過#參數名)和安全上下文信息(authentication對象)進行復雜判斷。
@PreAuthorize
非常靈活,也支持調用自定義權限評估方法等。但要注意權限表達式越復雜可能越難維護,需要在安全和可讀性之間平衡。Spring Security官方推薦使用PreAuthorize勝過Secured,因為其表達能力更強。
@Secured
簡介: @Secured
是較早的 Spring Security 提供的簡單方法安全注解(org.springframework.security.access.annotation.Secured
)。它指定一組允許的角色,調用該方法的用戶必須具備其中一個角色才行。需要在全局方法安全配置中啟用 securedEnabled=true
才生效。
作用與場景: 適用于簡單的基于角色的訪問控制。如果系統的授權模型主要基于角色,可以使用 @Secured("ROLE_X")
來保護方法。相對于 PreAuthorize,它不支持復雜表達式,只能指定角色列表。提供模塊: Spring Security(需要 @EnableMethodSecurity(securedEnabled=true)
或舊的相應配置)。
使用示例:
@Secured("ROLE_ADMIN")
public void createUser(User user) { ... }@Secured({"ROLE_USER", "ROLE_ADMIN"})
public Data getData() { ... }
createUser
方法要求調用者必須擁有ROLE_ADMIN
角色。getData
方法允許ROLE_USER
或ROLE_ADMIN
擁有者訪問(邏輯是OR的關系)。
如果不滿足要求,Spring Security同樣會拋出訪問拒絕異常。@Secured
內部實際上也是通過 AOP 攔截,與 @PreAuthorize
實現機制類似,但因為其功能有限,Spring官方更推薦使用Pre/PostAuthorize。
需要留意的是,@Secured
注解中的字符串需要包含完整的角色前綴(如默認前綴是 “ROLE_”)。如上必須寫 “ROLE_ADMIN” 而不是 “ADMIN”,除非通過配置修改了前綴策略。
@RolesAllowed
簡介: @RolesAllowed
來自 JSR-250(jakarta.annotation.security.RolesAllowed
),功能與 @Secured
類似,也是指定允許訪問方法的角色列表。Spring Security 支持它,需要 jsr250Enabled=true
。它和 Secured的區別主要在注解來源不同。
作用與場景: 可以作為 @Secured
的替代,用標準注解來聲明角色限制。在Spring環境下兩者效果一樣。提供模塊: JSR-250,Spring Security需啟用支持。
使用示例:
@RolesAllowed("ADMIN")
public void updateSettings(Settings settings) { ... }
這里假設已將角色前綴配置成無"ROLE_"前綴或 SecurityConfigurer里做了處理,否則 Spring Security會把 “ADMIN” 當作角色名直接匹配 GrantedAuthority “ADMIN”。一般Secured和RolesAllowed不能混用不同前綴,否則容易出錯。
綜上,Spring Security提供的這些注解允許我們無需在方法內部手動檢查權限,而由框架自動在調用前進行驗證,符合條件才執行。需要注意:
- 要在配置類上開啟相應支持(使用
@EnableMethodSecurity
或舊版@EnableGlobalMethodSecurity
)。 @PreAuthorize
/@PostAuthorize
功能最強,但稍復雜,@Secured
/@RolesAllowed
簡單直接,但只能基于角色判斷。- 這類注解只檢查Spring Security的上下文,對于未經過濾器鏈保護的方法調用(比如同類中自調用方法不會觸發注解檢查,或者在無Security環境下)就不起作用。這是常見陷阱——所以帶有安全注解的方法最好不要在內部直接調用,否則繞過了切面檢查。
十四、其他常用注解
除了上述類別,Spring & Spring Boot 中還有一些常用但未分類到的注解,例如:
- 參數校驗相關: Spring 對 JSR 303 Bean Validation 的支持,讓我們可以在模型上使用如
@NotNull
,@Size
,@Valid
等注解。其中在 Controller 方法參數上使用@Valid
可觸發校驗,并結合@ExceptionHandler
或@ControllerAdvice
統一處理校驗結果。 - JSON 序列化控制: 像
@JsonInclude
(來自 Jackson)可以注解類或屬性,控制JSON序列化包含規則,例如@JsonInclude(JsonInclude.Include.NON_NULL)
表示忽略null值字段。這在返回REST數據時很有用。 - 條件裝配注解: Spring Boot 提供了一系列
@ConditionalOn...
注解用于自動配置(如@ConditionalOnProperty
,@ConditionalOnClass
,@ConditionalOnMissingBean
等)來有條件地裝配Bean。這些主要在開發Spring Boot自動配置模塊時使用,在應用層較少直接用到,但理解它們有助于明白Boot的裝配機制。
以上羅列的注解涵蓋了Spring核心開發中最常用的部分。從應用啟動配置、Bean裝配,到Web層開發、數據持久化、AOP、異步、緩存、事件、測試、安全,各個方面都有簡潔的注解支持。掌握它們的用法能顯著提高開發效率,減少樣板代碼,讓我們更多關注業務邏輯實現。
總結: Spring & Spring Boot 常用注解極大地便利了開發,它們遵循“約定優于配置”的理念,通過簡單的注解聲明即可完成以前繁瑣的XML配置或手動編碼工作。在使用時要注意啟用相應功能的開關(如異步、事務、緩存等),理解注解背后機制(如AOP代理、運行時處理)以避免踩坑。熟練運用上述注解,能覆蓋大部分日常開發場景,實現優雅、高效和可維護的Spring應用。