Spring Boot 常用注解整理(最全收藏版)
Spring & Spring Boot 常用注解整理
現(xiàn)代的 Spring 與 Spring Boot 應用大量使用注解來簡化配置、管理組件和實現(xiàn)各種框架功能。本文系統(tǒng)整理了常用的 Spring/Spring Boot 注解,按照功能分類進行介紹。每個注解都會涵蓋其含義、提供來源、應用場景以及代碼示例,幫助開發(fā)者深入理解和快速檢索。
一、Spring Boot 核心注解
@SpringBootApplication
簡介: @SpringBootApplication 是 Spring Boot 應用的主入口注解。它標注在啟動類上,表示這是一個 Spring Boot 應用。該注解由 Spring Boot 提供(位于 org.springframework.boot.autoconfigure 包),本質(zhì)上是一個組合注解,包含了 Spring Framework 和 Spring Boot 的關(guān)鍵配置注解。
作用與場景: 使用 @SpringBootApplication 標記主類后,Spring Boot 會自動進行以下配置:
- 配置類聲明: 包含了
@SpringBootConfiguration(其本身是@Configuration的特化),因此該類被視為配置類,可定義 Bean。 - 組件掃描: 內(nèi)含
@ComponentScan,會自動掃描該類所在包及其子包下的組件(被諸如@Component、@Service、@Controller等注解標記的類),將它們注冊為 Spring 容器中的 Bean。 - 自動配置: 內(nèi)含
@EnableAutoConfiguration,根據(jù)類路徑下依賴自動配置 Spring Boot 應用。例如,若 classpath 中存在 HSQLDB 數(shù)據(jù)庫依賴,則會自動配置內(nèi)存數(shù)據(jù)庫等。開發(fā)者無需手動編寫大量配置即可啟動應用。
使用示例: 創(chuàng)建一個 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 將引導啟動內(nèi)嵌服務(wù)器、初始化 Spring 容器,自動掃描組件并完成配置。
注: @SpringBootApplication 提供了屬性用于定制,如 exclude 可排除特定的自動配置類。如果需要禁用某些自動配置,可以使用例如 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) 來排除。
二、Spring 容器與組件注冊注解
這一類注解用于將類注冊為 Spring 容器管理的組件或定義配置,以取代傳統(tǒng)的 XML 配置文件,實現(xiàn)注解驅(qū)動的裝配。
@Component
簡介: @Component 是一個通用的組件注解,由 Spring Framework 提供(org.springframework.stereotype.Component)。它用于將一個普通的 Java 類標識為 Spring 容器中的 Bean。被標注的類在組件掃描時會被發(fā)現(xiàn)并實例化,由容器統(tǒng)一管理生命周期。
作用與場景: 當某個類不好歸類到特定層時,可以使用 @Component 進行標注。典型場景如工具類、通用邏輯處理類等。使用 @Component 后,無需在 XML 中聲明 bean,Spring 會根據(jù)配置的掃描路徑自動將其注冊。提供模塊: Spring Context 模塊提供對組件掃描和 @Component 注解的支持。
使用示例: 定義一個組件類并演示注入:
@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 標記成為容器 Bean,GreetingService 中使用 @Autowired(詳見后文)將其注入,最后調(diào)用其方法。
@Service
簡介: @Service 是 @Component 的一種特化,用于標注業(yè)務(wù)邏輯層的組件(Service層)。它位于 Spring 框架的 org.springframework.stereotype 包。
作用與場景: 在分層架構(gòu)中,服務(wù)層類使用 @Service 注解,使代碼含義更語義化。盡管行為上和 @Component 相同(被掃描注冊為 Bean),@Service 強調(diào)該類承擔業(yè)務(wù)服務(wù)職責。提供模塊: Spring Context,同屬于組件模型的一部分。
使用示例:
@Service
public class OrderService {
public void createOrder(Order order) {
// 業(yè)務(wù)邏輯:創(chuàng)建訂單
}
}通過 @Service,OrderService 會被自動掃描注冊。在需要使用它的地方,例如控制層或其他服務(wù)層,可以通過依賴注入獲取該 Bean 實例。
@Repository
簡介: @Repository 是 @Component 的特化注解之一,用于標注數(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體系,從而簡化異常處理。換句話說,如果一個類標注為 @Repository,Spring 在為其創(chuàng)建代理時會自動處理持久化異常,將原始異常轉(zhuǎn)為 Spring 的非檢查型數(shù)據(jù)訪問異常,以提高健壯性。另外,標注了 @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)一處理。標注后也允許通過 @Autowired 注入到服務(wù)層使用。
@Controller
簡介: @Controller 是 Spring MVC 的控制層組件注解,同樣派生自 @Component。由 Spring Web MVC 模塊提供(org.springframework.stereotype.Controller),用于標識一個類是Web MVC 控制器,負責處理 HTTP 請求并返回視圖或響應。
作用與場景: 在 Web 應用程序中,@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 標記,提供一個映射 “/home” 請求的處理方法。返回值 "home" 代表視圖邏輯名,框架會根據(jù)配置解析到具體的頁面(如 home.html)。如果我們在類上使用了 @Controller,框架在啟動時會自動注冊相應的映射。
@RestController
簡介: @RestController 是 Spring 提供的組合注解,等價于同時在類上使用 @Controller 和 @ResponseBody。它主要由 Spring Web 模塊提供,用于RESTful Web服務(wù)的控制器。
作用與場景: 標注 @RestController 的類會被識別為控制器,并且其每個處理方法的返回值會直接作為 HTTP 響應體輸出,而不是作為視圖名稱解析。適用于需要返回 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) {
// 直接接收JSON反序列化為User對象,處理后返回
return userService.save(user);
}
}UserApiController 使用 @RestController 注解,其方法返回字符串和對象將直接通過消息轉(zhuǎn)換器寫入響應(例如字符串作為純文本,User 對象會序列化為 JSON)。不需要再在每個方法上加 @ResponseBody,使代碼更加簡潔。通常在開發(fā) REST API 時,都使用 @RestController 來定義控制器。
@Configuration
簡介: @Configuration 用于聲明一個配置類,由 Spring Framework 提供(org.springframework.context.annotation.Configuration)。配置類可以包含若干個帶有 @Bean 注解的方法,以定義 Bean 并交由 Spring 容器管理。@Configuration 本身也是 @Component,因此配置類也會被組件掃描注冊。
作用與場景: 在 Java Config 風格的應用中,@Configuration 相當于傳統(tǒng) XML 配置文件。用于定義 Beans、設(shè)置依賴注入規(guī)則等。Spring Boot 應用的某些自動配置也是以配置類形式存在。提供模塊: 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 注解標識為配置類。方法 dataSource() 和 userService() 上的 @Bean 注解會使 Spring 將其返回值注冊為容器中的 Bean。其中 userService() 方法調(diào)用了 dataSource(),Spring 會攔截并確保返回的是容器中單例的 DataSource Bean,而非每次調(diào)用重新實例化(即 CGLIB 增強 @Configuration 類確保 Bean 單例行為)。
@Bean
簡介: @Bean 注解用于定義一個 Bean。它標注在方法上,表示該方法返回的對象會注冊到 Spring 容器中。@Bean 通常配合 @Configuration 使用,由 Spring Context 模塊提供。
作用與場景: 當通過 JavaConfig 定義 Bean 時,用 @Bean 替代傳統(tǒng) XML <bean> 聲明。例如整合第三方庫的 Bean、或需要在創(chuàng)建 Bean 時執(zhí)行一些自定義邏輯等場景。@Bean 方法可以指定名稱(默認是方法名),還支持設(shè)置 initMethod(初始化時回調(diào)方法)和 destroyMethod(銷毀時回調(diào)方法)。
使用示例:
@Configuration
public class MyConfig {
@Bean(name = "customBean", initMethod = "init", destroyMethod = "cleanup")
public MyComponent customBean() {
return new MyComponent();
}
}在上例中,@Bean 注解聲明了 customBean 這個 Bean。容器啟動時調(diào)用 customBean() 方法創(chuàng)建 MyComponent 實例,并以 "customBean" 名稱注冊。initMethod="init" 表示在 Bean 創(chuàng)建后自動調(diào)用其 init() 方法進行初始化;destroyMethod="cleanup" 表示容器銷毀該 Bean 時調(diào)用其 cleanup() 方法。通過這種方式可以管理 Bean 的生命周期方法(類似于 InitializingBean 和 DisposableBean 接口或 @PostConstruct/@PreDestroy,見后文)。
@ComponentScan
簡介: @ComponentScan 用于配置組件掃描路徑的注解。由 Spring Context 提供,通常與 @Configuration 一起使用。它的作用是指示 Spring 在指定的包路徑下搜索帶有組件注解的類,并注冊為 Bean。
作用與場景: 默認情況下,Spring Boot 的 @SpringBootApplication 已經(jīng)隱含指定掃描其所在包及子包。如果需要自定義掃描范圍(例如掃描其他包的組件),可以使用 @ComponentScan 注解并提供 basePackages 等屬性。普通 Spring 應用(非 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 這兩個包及其子包,搜索所有標注了 @Component/@Service/@Controller 等的類并注冊。這樣可以將應用的組件按照包組織,而由配置集中管理掃描范圍。
@Import
簡介: @Import 注解用于導入額外的配置類或組件到 Spring 容器。它由 Spring Context 提供,可用在 @Configuration 類上,將一個或多個配置類合并進來。也可以用于引入第三方配置。
作用與場景: 當項目拆分成多個配置類時,可以通過 @Import 將它們組合。例如,將公共配置獨立出來,再在主配置中引入。Spring Boot 自動配置內(nèi)部也大量使用了 @Import 來按條件加載配置類。提供模塊: Spring Context。
使用示例:
@Configuration
@Import({SecurityConfig.class, DataConfig.class})
public class MainConfig {
// 主配置,導入了安全配置和數(shù)據(jù)配置
}如上,MainConfig 通過 @Import 導入了 SecurityConfig 和 DataConfig 兩個配置類。這樣這兩個配置類中定義的 Bean 同樣會加載到容器中,相當于把多個配置模塊拼裝在一起。相比在 XML 里用 <import>,注解方式更加直觀。
注: Spring Boot 提供的許多 @Enable... 注解(例如后文的 @EnableScheduling 等)內(nèi)部也是通過 @Import 導入相應的配置實現(xiàn)啟用功能的。
三、依賴注入注解
依賴注入(DI)是 Spring 核心機制之一。以下注解用于在容器中進行 Bean 注入和裝配,解決 Bean 間的依賴關(guān)系。
@Autowired
簡介: @Autowired 是 Spring 提供的自動裝配注解(org.springframework.beans.factory.annotation.Autowired),用于按類型自動注入依賴對象。它可作用于字段、setter方法或者構(gòu)造函數(shù)上。由 Spring Context 模塊支持。
作用與場景: 標注了 @Autowired 的屬性或方法,Spring 會在容器啟動時自動尋找匹配的 Bean 注入。其中按類型匹配是默認行為。如果匹配到多個同類型 Bean,則需要結(jié)合 @Qualifier 或 @Primary 來消除歧義(見下文)。如果沒有找到匹配 Bean,默認會拋出異常??赏ㄟ^設(shè)置 @Autowired(required=false) 來表示找不到 Bean 時跳過注入而不報錯。
使用示例:
@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 有一個成員 userRepository,使用 @Autowired 標注。容器會自動將類型為 UserRepository 的 Bean 注入進來(假設(shè)已有 @Repository 或 @Component 標記的 UserRepository 實現(xiàn))。開發(fā)者可以通過構(gòu)造器、setter 或字段注入的方式使用 @Autowired。注意: Spring 4.3+ 如果類中只有一個構(gòu)造器,且需要注入?yún)?shù),可省略構(gòu)造函數(shù)上的 @Autowired,仍會自動注入。
@Qualifier
簡介: @Qualifier 注解與 @Autowired 配合使用,用于按照名稱或限定符進行依賴注入匹配。它由 Spring 提供(org.springframework.beans.factory.annotation.Qualifier),可以解決當容器中存在多個同類型 Bean 時的沖突。
作用與場景: 默認按類型注入在有多于一個候選 Bean 時會無法確定注入哪個。例如有兩個實現(xiàn)類實現(xiàn)了同一接口,都被注冊為 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 的實現(xiàn)
private UserRepository userRepository;
// ...
}如上,有兩個 UserRepository 實現(xiàn) Bean,分別命名為 “mysqlRepo” 和 “oracleRepo”。在 UserService 中,通過 @Qualifier("mysqlRepo") 指定注入名為 mysqlRepo 的 Bean。這樣即使存在多個同類型 Bean,Spring 也能準確地注入所需的依賴。
@Primary
簡介: @Primary 注解用于標記一個 Bean 為主要候選者。當按類型注入出現(xiàn)多個 Bean 可選時,標有 @Primary 的 Bean 將優(yōu)先被注入。它由 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();
}
@Bean
public UserRepository oracleUserRepository() {
return new OracleUserRepository();
}
}
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 將自動注入 mysqlUserRepository,因為它被標記為 @Primary
}在上例的配置中,我們定義了兩個 UserRepository Bean,其中 MySQL 實現(xiàn)被標記為 @Primary。因此在 UserService 中按類型注入 UserRepository 時,Spring 會注入標記了 @Primary 的 MySQL 實現(xiàn)。@Primary 提供了一個全局默認方案,簡化了注入點的選擇。
@Resource
簡介: @Resource 是來自 JSR-250 規(guī)范的注解(Javax/Jakarta Annotation),Spring 對其提供了支持,用于按名稱或按類型注入依賴。它通常位于 jakarta.annotation.Resource(Java EE/Jakarta EE)包下。注意: 盡管不在 Spring 包中,Spring 容器能識別并處理它。
作用與場景: @Resource 可以看作功能類似于 @Autowired + @Qualifier 的組合。默認情況下按名稱注入:它首先按照屬性名或指定的名稱在容器中查找 Bean,找不到再按類型匹配。這在某些情況下很有用,例如需要與傳統(tǒng) Java EE 代碼兼容時。在 Spring 應用中,也有開發(fā)者偏好使用 @Resource 進行依賴注入。提供模塊: 需要引入相應的 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 默認以屬性名 userRepo 作為 Bean 名稱查找。與 @Autowired 不同,@Resource 不支持 required=false 屬性,但其異常信息可能更直觀(若找不到 Bean 則拋出 NoSuchBeanDefinitionException)。值得一提的是,Spring 也支持 JSR-330 的 @Inject(javax.inject.Inject)注解,其語義與 @Autowired 相同,也可用于構(gòu)造函數(shù)注入等。在實際開發(fā)中,可根據(jù)團隊規(guī)范選擇使用 Spring 原生的 @Autowired 還是標準的 @Resource/@Inject。
@Value
簡介: @Value 注解用于將外部化配置中的屬性值注入到 Bean 的字段或參數(shù)中。它由 Spring 提供(org.springframework.beans.factory.annotation.Value),常用于讀取 application.properties/yaml 配置文件或系統(tǒng)環(huán)境變量、JNDI等屬性。
作用與場景: 當需要在 Bean 中使用配置文件里的值時,可以使用 @Value("${property.name}") 注入。例如數(shù)據(jù)庫連接參數(shù)、服務(wù)端口號等。還支持設(shè)置默認值和 SpEL 表達式。提供模塊: 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}") // 帶默認值,若配置缺失則使用0.0.1
private String appVersion;
// ...
}上述 AppInfo 類中,@Value("${app.name}") 將把配置中的 app.name 值注入到 appName 字段。如果對應屬性不存在,會啟動失敗。而 appVersion 字段提供了默認值 0.0.1,當配置文件未設(shè)置 app.version 時就會使用默認值。這樣,可以靈活地將外部配置與代碼解耦,使應用更易于調(diào)整參數(shù)而無需改動源碼。
@Scope
簡介: @Scope 注解用于指定 Bean 的作用域,由 Spring 提供(org.springframework.context.annotation.Scope)。默認情況下,Spring 容器中的 Bean 都是單例(singleton)作用域。通過 @Scope 可以定義其他作用域,例如 prototype、request、session 等。
作用與場景: 常見的作用域:
singleton(默認):容器中僅保持一個實例。prototype:每次請求 Bean 時都會創(chuàng)建新實例。- Web相關(guān)的作用域(需要在 Web 容器環(huán)境下使用):如
request(每個HTTP請求創(chuàng)建)、session(每個會話創(chuàng)建)等。
在需要每次使用新對象的場景(如有狀態(tài) Bean),可將 Bean 定義成 prototype;在 Web 應用中某些 Bean 希望隨請求或會話存續(xù),可用相應作用域。提供模塊: Spring Context。
使用示例:
@Component
@Scope("prototype")
public class Connection {
public Connection() {
System.out.println("New Connection created.");
}
}將 Connection Bean 聲明為 prototype,每次獲取都會創(chuàng)建新的實例:
@Autowired private Connection conn1; @Autowired private Connection conn2;
上面 conn1 和 conn2 將是不同的實例,因為 Connection 定義為 prototype。日志會打印兩次 “New Connection created.”。若作用域是 singleton,則只創(chuàng)建一次實例并復用。需要注意,prototype Bean 的生命周期由使用方管理,Spring 只負責創(chuàng)建,不會自動調(diào)用其銷毀方法。
@Lazy
簡介: @Lazy 注解用于將 Bean 的初始化延遲到首次使用時(懶加載)。由 Spring 提供(org.springframework.context.annotation.Lazy),可用于類級別或 @Bean 方法上。
作用與場景: 默認情況下,單例 Bean 在容器啟動時就會初始化。如果某些 Bean 的創(chuàng)建比較耗時或在應用運行期間可能不會被用到,可以標記為 @Lazy,這樣只有在真正需要時才實例化,減少啟動時間和資源消耗。懶加載常用于:例如調(diào)試或在單元測試中減少不必要 Bean 創(chuàng)建,或避免循環(huán)依賴時暫緩 Bean 的注入初始化。對于 prototype Bean,Spring 始終延遲創(chuàng)建(因為本身就按需創(chuàng)建),@Lazy主要針對單例 Bean。提供模塊: Spring Context。
使用示例:
@Service
@Lazy
public class HeavyService {
public HeavyService() {
// 構(gòu)造函數(shù)可能進行大量初始化
System.out.println("HeavyService initialized");
}
// ...
}
@Controller
public class DemoController {
@Autowired
private HeavyService heavyService; // 被@Lazy標記,不會在容器啟動時實例化
// ...
}如上,HeavyService 使用 @Lazy 注解標記為懶加載單例。啟動時不會打印 “HeavyService initialized”。當 DemoController 第一次實際調(diào)用 heavyService 的方法或訪問它時,Spring 才會創(chuàng)建 HeavyService 實例并注入。這對于優(yōu)化啟動性能和按需加載組件很有幫助。但應謹慎使用懶加載,如果Bean在啟動后馬上就會用到,則不應延遲初始化,以免首次調(diào)用時產(chǎn)生延遲。
四、配置屬性注解
Spring 提供了將配置文件內(nèi)容綁定到對象的機制,這類注解幫助管理應用的外部化配置和環(huán)境區(qū)分。
@ConfigurationProperties
簡介: @ConfigurationProperties 用于將一組配置屬性映射到一個 Java 類上。由 Spring Boot 提供(org.springframework.boot.context.properties.ConfigurationProperties),通常配合 Bean 使用。通過前綴(prefix)來批量注入配置項到類的屬性中。
作用與場景: 當有多項相關(guān)配置需要使用時,比起逐個使用 @Value,可以定義一個配置屬性類。例如應用配置、數(shù)據(jù)源配置等。在類上標注 @ConfigurationProperties(prefix="xxx") 后,該類的各屬性會根據(jù)前綴讀取配置文件中的對應項賦值。需要將該類注冊為 Bean(可以通過在類上加 @Component 或在配置類中用 @Bean 創(chuàng)建),Spring Boot 會自動將配置綁定到 Bean 實例上。
使用示例:
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() {
// 返回整個配置對象,框架會序列化為JSON
return appProperties;
}
}在這個例子中,@ConfigurationProperties(prefix="app") 使得 YAML 中 app 下的配置自動綁定到 AppProperties Bean。name、apiUrl 會對應賦值,嵌套的 pool.size 和 pool.enableLog 也會注入到 Pool 類中。這樣可以方便地管理和校驗成組的配置屬性。需要注意,綁定類必須有無參構(gòu)造器,提供標準的 getter/setter。Spring Boot 還支持JSR-303校驗注解(如 @Validated)配合 @ConfigurationProperties 對配置進行格式校驗。
@EnableConfigurationProperties
簡介: @EnableConfigurationProperties 是 Spring Boot 用于啟用 @ConfigurationProperties 支持的注解。它通常加在主應用類或配置類上,用來將帶有 @ConfigurationProperties 注解的配置POJO注入到容器中。
作用與場景: 在 Spring Boot 2.x 以后,如果配置屬性類已經(jīng)被聲明為 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 注解用于根據(jù)**環(huán)境(Profile)**加載 Bean。由 Spring 提供(org.springframework.context.annotation.Profile)??梢詷俗⒃陬惢蚍椒ǎ˙ean 方法)上,只有在激活的環(huán)境與指定 Profile 匹配時,該 Bean 才會注冊到容器。
作用與場景: 常用于區(qū)別開發(fā)、測試、生產(chǎn)環(huán)境的配置。例如開發(fā)環(huán)境使用嵌入式數(shù)據(jù)庫,而生產(chǎn)環(huán)境使用正式數(shù)據(jù)庫連接,就可以用 @Profile("dev") 和 @Profile("prod") 注解分別標注不同的配置類或 Bean。在運行應用時通過配置 spring.profiles.active 激活某個 Profile,則對應的 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(...);
}
}當設(shè)置 spring.profiles.active=dev 時,應用啟動只會創(chuàng)建 memoryDataSource Bean;設(shè)置為 prod 時只創(chuàng)建 mysqlDataSource Bean。如果不激活任何 Profile,上述兩個 Bean 都不會加載(也可以用 @Profile("default") 指定默認配置)。使用 @Profile 實現(xiàn)了根據(jù)環(huán)境有條件地注冊 Bean,方便一套代碼多環(huán)境運行。
五、Bean 生命周期與作用域注解
Spring 管理的 Bean 具有完整的生命周期,包括初始化和銷毀過程。以下注解用于在生命周期特定階段執(zhí)行方法,以及控制 Bean 的作用域與加載時機。
@PostConstruct
簡介: @PostConstruct 是一個來自 Java 標準(JSR-250)的注解(位于 jakarta.annotation.PostConstruct)。Spring 容器在Bean初始化完依賴注入后,會調(diào)用被該注解標記的方法。常用于初始化邏輯。需要注意在 Spring Boot 3+ 中,@PostConstruct 等由 Jakarta 引入,需要相應依賴。
作用與場景: 當我們希望在 Bean 完成依賴注入后自動執(zhí)行一些初始化代碼,可以在 Bean 的方法上加 @PostConstruct。例如設(shè)置默認值、開啟定時器、檢查配置完整性等。在傳統(tǒng) Spring 中,這相當于 <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");
}
}當 Spring 創(chuàng)建了 CacheManager Bean 并注入完依賴后,會自動調(diào)用其 init() 方法,輸出 “CacheManager initialized” 并完成緩存容器初始化。這樣開發(fā)者無需手動調(diào)用初始化邏輯,容器托管完成。這對于單例Bean非常方便。
@PreDestroy
簡介: @PreDestroy 同樣來自 JSR-250 標準(jakarta.annotation.PreDestroy),Spring 在 Bean 銷毀前(容器關(guān)閉或 Bean 移除前)調(diào)用標注該注解的方法。常用于資源釋放、保存狀態(tài)等操作。
作用與場景: 當應用結(jié)束或容器要銷毀 Bean 時,希望執(zhí)行一些清理工作,例如關(guān)閉文件流、線程池、數(shù)據(jù)庫連接等,可以在方法上加 @PreDestroy 注解。相當于 XML 配置中的 <bean destroy-method="..."> 或?qū)崿F(xiàn) DisposableBean 接口的 destroy 方法。提供模塊: JSR-250,由 Spring 容器負責調(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ù)據(jù)庫連接,在容器銷毀時通過 @PreDestroy 標記的 disconnect() 方法關(guān)閉連接。Spring 在應用關(guān)閉時會調(diào)用該方法,確保資源釋放。這使得資源管理更加可靠,避免連接泄漏等問題。
@Scope (作用域) – 見上文第三部分
(此處簡要說明:) 使用 @Scope 注解可以改變 Bean 的作用域,比如 "singleton"、"prototype" 等。已在依賴注入部分詳細介紹其使用。
@Lazy (懶加載) – 見上文第三部分
(此處簡要說明:) 使用 @Lazy 可以延遲 Bean 的初始化直至第一次使用。在某些場景下提高啟動性能或解決循環(huán)依賴。前文已介紹其概念和示例。
六、Web 開發(fā)注解
Spring MVC 框架提供了大量注解來簡化 Web 開發(fā),包括請求映射、參數(shù)綁定、響應處理等。這些注解大多位于 org.springframework.web.bind.annotation 包中。
@RequestMapping
簡介: @RequestMapping 是最基本的請求映射注解,用于將 HTTP 請求URL路徑映射到對應的控制器類或處理方法上。由 Spring Web MVC 提供??捎糜陬惡头椒墑e。
作用與場景: 在類上標注 @RequestMapping("basePath") 可以為該控制器指定一個基礎(chǔ)路徑,方法上的 @RequestMapping("subPath") 則在類路徑基礎(chǔ)上進一步細分。它支持設(shè)置請求方法(GET、POST等)、請求參數(shù)和請求頭等屬性,用于更精確地映射請求。例如只處理 GET 請求,或某個請求參數(shù)存在時才匹配。Spring MVC 啟動時會根據(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"進一步限定只有請求參數(shù)包含action=register時才調(diào)用此方法。這是區(qū)分同一路徑不同操作的方式。處理完后重定向到用戶列表。
@RequestMapping 非常靈活,其常用屬性:
value 或 path:映射的 URL 路徑,可以是 Ant 風格模式(如 /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,分別對應 HTTP PUT/DELETE/PATCH 方法。它們由 Spring MVC 提供,從 Spring 4.3 開始引入。
作用與場景: 這些注解相當于 @RequestMapping(method = RequestMethod.X) 的快捷方式,使代碼更簡潔。尤其在定義 RESTful API 時,常用不同 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})等價于@RequestMapping(value="/{id}", method = RequestMethod.GET),用于獲取指定ID的 Item。@PostMapping("")等價于類路徑/items下的 POST 請求(創(chuàng)建新的 Item),請求體通過@RequestBody解析為Item對象。@DeleteMapping("/{id}")處理刪除操作。
這些組合注解讓控制器方法定義更直觀,更符合 RESTful 風格。可以根據(jù)需要使用對應的 HTTP方法注解。未提供參數(shù)時,@GetMapping 等注解的路徑可以直接寫在注解括號內(nèi)(如上 @PostMapping("") 指當前路徑)。
@PathVariable
簡介: @PathVariable 用于將 URL 路徑中的動態(tài)部分綁定到方法參數(shù)上。由 Spring MVC 提供。常與 @RequestMapping 或 @GetMapping 等一起使用,用于處理RESTful風格的URL。
作用與場景: 當URL中含有變量占位符(如 /users/{id})時,可通過在方法參數(shù)上加 @PathVariable 來獲取該占位符的值。可以指定名稱匹配占位符,或者不指定名稱則根據(jù)參數(shù)名自動推斷。適用于從路徑獲取資源標識(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參數(shù)會被賦值為123(Long 類型轉(zhuǎn)換),itemId參數(shù)賦值為456。
@PathVariable("orderId") 中指定名稱,與 {orderId} 占位符對應。如果方法參數(shù)名與占位符名稱相同,可以簡寫為 @PathVariable Long orderId。
通過 @PathVariable,我們無需從 HttpServletRequest 手動解析路徑,Spring MVC 自動完成轉(zhuǎn)換和注入,簡化了代碼。
@RequestParam
簡介: @RequestParam 用于綁定 HTTP 請求的查詢參數(shù)或表單數(shù)據(jù)到方法參數(shù)上。由 Spring MVC 提供。支持為參數(shù)設(shè)置默認值、是否必需等屬性。
作用與場景: 處理 GET 請求的查詢字符串參數(shù)(URL ? 后的參數(shù))或 POST 表單提交的字段時,可以使用 @RequestParam 獲取。例如搜索接口的關(guān)鍵詞,分頁的頁碼和大小等。它可以將 String 類型的請求參數(shù)轉(zhuǎn)換為所需的目標類型(如 int、boolean),自動完成類型轉(zhuǎ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);
}當請求 /search?keyword=phone&pageIndex=1 到達時:
keyword參數(shù)綁定到方法的keyword參數(shù)。如果未提供則使用默認值空字符串。pageIndex綁定到整型參數(shù),未提供則為默認0。pageSize在此請求未提供,因此取默認值10。
@RequestParam 常用屬性:
value或name:參數(shù)名,對應URL中的參數(shù)名。required:是否必須提供,默認 true(不提供會報錯)。上例中我們將 keyword 標記為 false 可選。defaultValue:如果請求未包含該參數(shù)則使用默認值(注意即使標記 required=true,有 defaultValue 也不會報錯)。
通過 @RequestParam,方法可以直接獲得解析后的參數(shù)值,無需自己從 request 獲取和轉(zhuǎn)換,大大簡化控制器代碼。
@RequestBody
簡介: @RequestBody 用于將 HTTP 請求報文體中的內(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)換為對應的對象實例。適用于需要從請求正文獲取復雜對象的場景。與之對應,返回值或方法上使用 @ResponseBody(或 @RestController)可將對象序列化為響應。
使用示例:
@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對象(要求有適當?shù)膶傩院蛃etter)。 - 然后傳遞給控制器方法使用。
方法處理后返回成功響應。使用 @RequestBody,開發(fā)者無需手動解析 JSON,提高了開發(fā)效率并減少出錯。
注意: @RequestBody 默認要求請求體存在,否則報錯。如果希望在請求體為空時處理為 null,可以設(shè)置 required=false。對于 GET 請求一般不使用 @RequestBody(GET沒有主體或主體被忽略)。
@ResponseBody
簡介: @ResponseBody 注解用于將控制器方法的返回值直接作為 HTTP 響應內(nèi)容輸出,而不是解析為視圖名稱。由 Spring MVC 提供??梢詷俗⒃诜椒ㄉ匣颍ㄝ^少見)標注在類上(類上標注相當于對該類所有方法應用此行為)。
作用與場景: @ResponseBody 常用于 AJAX 接口或 RESTful 方法,需要返回 JSON、XML或純文本等給客戶端,而非頁面。當方法標注該注解后,Spring 會將返回對象通過合適的 HttpMessageConverter 轉(zhuǎn)換為 JSON/XML 或其他格式寫入響應流。例如返回一個對象會自動序列化為 JSON 字符串。@RestController 注解實際上已經(jīng)包含了 @ResponseBody 效果,所以在使用 @RestController 時無需再標注此注解在每個方法上。
使用示例:
@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請求返回一個 Map,Spring 會將其轉(zhuǎn)換為 JSON,如:{"status":"UP","timestamp":1638346953000}。
因為使用的是普通的 @Controller 類,所以需要在每個方法上添加 @ResponseBody 來指示直接返回內(nèi)容。如果改用 @RestController 則可以省略這些注解。@ResponseBody 常用于快速測試接口或者在需要精確控制輸出內(nèi)容時使用。
@CrossOrigin
簡介: @CrossOrigin 注解用于配置跨域資源共享 (CORS)。由 Spring Web 提供,可標注在類或方法上。它允許來自不同域名的客戶端訪問被標注的資源。
作用與場景: 當前端和后端分屬不同域(例如前端React開發(fā)服務(wù)器 http://localhost:3000,后端 http://localhost:8080)時,瀏覽器會攔截跨域請求。使用 @CrossOrigin 可以在服務(wù)端指定允許跨域的來源、方法、頭信息等,從而使瀏覽器允許調(diào)用。可以針對整個控制器類統(tǒng)一配置(類上標注)或針對特定方法(方法上標注)配置不同跨域策略。
使用示例:
@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 還可設(shè)置允許的請求頭、是否發(fā)送憑證等,通過參數(shù)如 allowedHeaders, allowCredentials 等配置。使用這個注解,開發(fā)者不必在全局Web配置中配置 CorsRegistry,可以就近管理跨域策略。
@ExceptionHandler
簡介: @ExceptionHandler 用于在控制器中定義異常處理方法的注解。由 Spring MVC 提供。通過指定要處理的異常類型,當控制器方法拋出該異常時,轉(zhuǎn)而由標注了 @ExceptionHandler 的方法來處理。
作用與場景: 為了避免將異常堆棧暴露給客戶端或者在每個控制器方法中編寫重復的 try-catch,可以使用 @ExceptionHandler集中處理。例如處理表單校驗異常返回友好錯誤信息、處理全局異常返回統(tǒ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) 標注了 handleNotFound 方法來處理這種異常:當異常拋出后,控制器不會繼續(xù)執(zhí)行原流程,而是進入該方法。方法可以接收異常對象,以及 Model 等參數(shù),處理后返回一個視圖名 "orderError" 顯示錯誤信息。
通過 @ExceptionHandler,控制器內(nèi)部的異常處理邏輯與正常業(yè)務(wù)邏輯解耦,代碼清晰且易于維護。
@ControllerAdvice
簡介: @ControllerAdvice 是全局控制器增強注解。由 Spring MVC 提供,用于定義一個全局異常處理或全局數(shù)據(jù)綁定的切面類。標注該注解的類可以包含多個 @ExceptionHandler 方法,用于處理應用所有控制器拋出的異常;也可以包含 @ModelAttribute 或 @InitBinder 方法對所有控制器生效。
作用與場景: 當需要對所有控制器統(tǒng)一處理某些邏輯時,使用 @ControllerAdvice 非常方便。典型用法是結(jié)合 @ExceptionHandler 作為全局異常處理器,比如攔截所有 Exception 返回通用錯誤響應,或分類處理不同異常類型返回不同狀態(tài)碼。提供模塊: Spring MVC。
使用示例:
@ControllerAdvice
public class GlobalExceptionHandler {
// 處理所有異常的fallback
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseEntity<String> handleException(Exception ex) {
// 記錄日志
ex.printStackTrace();
// 返回通用錯誤響應
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 會將其中標注了 @ExceptionHandler 的方法應用到整個應用的控制器:
- 第一個方法捕獲所有未被其它更專門處理的異常,打印棧Trace并返回500錯誤提示。
- 第二個方法專門處理參數(shù)校驗失敗異常,提取錯誤信息列表并返回400狀態(tài)和錯誤列表。
此外,可以在 @ControllerAdvice 類中定義 @ModelAttribute 方法,為所有控制器請求添加模型數(shù)據(jù)(如公共下拉選項),或定義 @InitBinder 方法,注冊全局屬性編輯器等。@ControllerAdvice 可以通過屬性限制只應用于某些包或注解的控制器,但全局異常處理通常都是應用全局的。
通過 @ControllerAdvice,我們實現(xiàn)了 AOP 式的全局控制器邏輯抽取,使各控制器關(guān)注自身業(yè)務(wù),將通用邏輯集中處理,保持代碼整潔。
七、數(shù)據(jù)訪問與事務(wù)注解
在使用 Spring 管理數(shù)據(jù)持久化層時,會涉及到 JPA/Hibernate 等注解定義實體,以及 Spring 提供的事務(wù)管理注解等。
@Entity
簡介: @Entity 是 Java Persistence API (JPA) 的注解(jakarta.persistence.Entity),用于將一個類聲明為 JPA 實體。Spring Boot 通常通過 JPA/Hibernate 來操作數(shù)據(jù)庫,因此定義模型時會用到它。@Entity 注解的類對應數(shù)據(jù)庫中的一張表。
作用與場景: 標記為 @Entity 的類將由 JPA 實現(xiàn)(例如 Hibernate)管理,其實例可映射到數(shù)據(jù)庫記錄。必須提供主鍵(用 @Id 標注),可選地用 @Table 指定表名,不指定則默認表名為類名。提供模塊: JPA 規(guī)范,由 Hibernate 等實現(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 注解標記為持久化實體,對應數(shù)據(jù)庫表users(由@Table指定,若不指定默認表名User)。字段上:
id用@Id標識為主鍵,@GeneratedValue指定主鍵生成策略(自增)。username用@Column細化映射:列名指定為username,長度50,非空且唯一。password僅用了@Column(nullable=false),列名默認為屬性名。
定義好實體后,可以使用 Spring Data JPA 的倉庫接口來自動生成常用查詢(見下文 @Repository 和 @Query)。Spring Boot 啟動時若開啟DDL-auto,會根據(jù)實體定義自動在數(shù)據(jù)庫創(chuàng)建或更新表結(jié)構(gòu)。
@Table
簡介: @Table 是 JPA 注解(jakarta.persistence.Table),配合 @Entity 使用,用于指定實體映射的數(shù)據(jù)庫表信息,如表名、schema、catalog等。
作用與場景: 默認情況下,實體類名即表名。若數(shù)據(jù)庫表名與類名不同,或者需要定義 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 會將該字段作為數(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:使用一個數(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 策略。每次插入User時會從序列獲取下一個值作為ID。
對于常用的 AUTO 或 IDENTITY 策略,在大多數(shù)情況下只需簡單標注 @GeneratedValue(strategy = GenerationType.IDENTITY) 等,無需額外生成器配置。
@Column
簡介: @Column 是 JPA 注解(jakarta.persistence.Column),用于定義實體字段與數(shù)據(jù)庫表列的映射細節(jié)??梢圆皇褂?,如果不標注,JPA 默認按屬性名映射列名(可能做小寫下劃線轉(zhuǎn)換,視實現(xiàn)而定)。
作用與場景: @Column 可指定列名、數(shù)據(jù)類型長度、是否允許NULL、是否唯一等約束。對于日期時間類型,還可指定 columnDefinition 或 Temporal 等,控制SQL類型。提供模塊: JPA。
使用示例:
@Column(name = "email", length = 100, nullable = false, unique = true) private String email;
如上,將字段 email 映射為名為 email 的列(其實和默認同名,但明確指出),長度100,非空且唯一。使用 @Column 可以清晰地將實體和數(shù)據(jù)庫字段對應起來。
@Repository – 見上文第二部分
(此處補充:) 在數(shù)據(jù)訪問層,@Repository 標注的接口或類通常與 Spring Data JPA 搭配使用。如一個接口 UserRepository extends JpaRepository<User, Long> 上加 @Repository(實際上 Spring Data JPA 的接口已經(jīng)隱式有這個語義),Spring 會為其生成實現(xiàn)并交由容器管理。@Repository 除了提供組件掃描和異常轉(zhuǎn)換外,本身沒有其他方法屬性。
@Transactional
簡介: @Transactional 是 Spring 提供的聲明式事務(wù)管理注解(org.springframework.transaction.annotation.Transactional)??蓸俗⒃陬惢蚍椒ㄉ希硎酒渲械臄?shù)據(jù)庫操作應當在一個事務(wù)中執(zhí)行。Spring 將在運行時提供事務(wù)支持,如開始、提交或回滾事務(wù)。
作用與場景: 數(shù)據(jù)庫操作需要事務(wù)保障數(shù)據(jù)一致性,例如同時更新多張表,要么全部成功要么全部失敗。使用 @Transactional 可以在不手動編程式管理事務(wù)的情況下,由框架自動處理。典型應用:
- Service 層的方法需要原子性,則加上
@Transactional,Spring會在進入方法時開啟事務(wù),方法成功返回則提交,如有異常則回滾。 - 也可加在類上,表示類中所有公有方法都事務(wù)管理。
提供模塊: Spring ORM/Transaction 模塊,需要相應的事務(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é)束時,Spring自動提交事務(wù)。如發(fā)生運行時異常則自動回滾。
}
}transfer 方法標注了 @Transactional,因此上述三個數(shù)據(jù)庫操作將處于同一個事務(wù)中:如果任何一步拋出未經(jīng)捕獲的異常(默認僅RuntimeException和Error會導致回滾,可通過 rollbackFor 屬性更改回滾規(guī)則),所有已執(zhí)行的數(shù)據(jù)庫更新都會回滾,保持數(shù)據(jù)一致性。如果全部成功,則提交事務(wù),將更新真正持久化。事務(wù)傳播行為和隔離級別等也可以通過注解屬性配置,例如 @Transactional(propagation=Propagation.REQUIRES_NEW) 開啟新事務(wù),@Transactional(isolation=Isolation.SERIALIZABLE) 設(shè)置高隔離級別等,視業(yè)務(wù)需求而定。
注意: 使用 @Transactional 時,需要確保啟用了 Spring 的事務(wù)支持(見下文 @EnableTransactionManagement),Spring Boot 會自動在有數(shù)據(jù)源時啟用事務(wù)管理。所以在 Boot 場景下通常不需要額外配置即可使用。
@JsonFormat
簡介: @JsonFormat 是 Jackson 提供的序列化/反序列化格式化注解(com.fasterxml.jackson.annotation.JsonFormat)。可作用在字段、方法(getter / setter)或類型上,用于自定義日期-時間、數(shù)字、布爾等屬性在 JSON ←→ Java 轉(zhuǎn)換時的形態(tài)、時區(qū)與本地化設(shè)置。
作用與場景:
- 日期時間格式化:將
Date/LocalDateTime等類型格式化為固定字符串(例如yyyy-MM-dd HH:mm:ss)并指定時區(qū),避免前后端默認時區(qū)不一致導致時間偏移。 - 數(shù)字 / 布爾形態(tài)控制:可把布爾值序列化成
0/1,或把Instant、LocalDateTime轉(zhuǎn)成數(shù)值時間戳(shape = NUMBER)等。 - 與 Bean Validation 協(xié)同:在 DTO 中同時配合
@DateTimeFormat/ 校驗注解,可保持前后端格式完全一致。 - 優(yōu)先級:字段級
@JsonFormat會覆蓋ObjectMapper的全局日期格式配置,適用于單獨字段需要特殊格式的場景。
使用示例:
@Data
public class OrderDTO {
private Long id;
// 1. 指定日期-時間格式 + 時區(qū)
@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) // 不生成該字段的 getter
private String password;
// 也可在字段級別加 @Getter 僅生成單個方法
}依賴:開發(fā)環(huán)境需引入
lombok依賴,并在 IDE 中安裝 Lombok 插件或開啟 Annotation Processing。
@Setter
簡介: @Setter 同樣由 Lombok 提供(lombok.Setter),自動為類或字段生成 public setter 方法。
作用與場景:
- 可變對象賦值:在需要修改字段值、或框架反射注入時使用。
- 粒度控制:可通過
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包含父類字段。 - 鏈式注解:常與
@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)日志中輸出對象時務(wù)必排除敏感信息;
@ToString支持exclude數(shù)組或字段級@ToString.Exclude精細控制。
@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 并在運行時生成代理。提供模塊: 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,且默認啟用事務(wù)支持,無需手動添加該注解(Boot 會自動應用)。但在需要更細粒度控制事務(wù)行為時,了解此注解的作用仍然重要。
@Query
簡介: @Query 注解由 Spring Data JPA 提供(org.springframework.data.jpa.repository.Query),用于在 Repository 方法上定義自定義的 JPQL或原生SQL 查詢。
作用與場景: 雖然 Spring Data JPA 可以通過解析方法名自動生成查詢,但是復雜或特殊的查詢可以用 @Query 手工編寫JPQL語句。還可以通過設(shè)置 nativeQuery=true 使用原生SQL。當自動生成無法滿足需求,或為了性能使用數(shù)據(jù)庫特定查詢時,用此注解非常有用。提供模塊: 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表示第一個參數(shù)。findTopByStatus方法使用原生SQL查詢指定狀態(tài)的用戶若干條,使用命名參數(shù):status和:limit。需要搭配@Param注解綁定參數(shù)值。
Spring Data JPA 在運行時會解析這些注解并生成相應實現(xiàn)代碼執(zhí)行查詢。@Query 能大大提升查詢的靈活性,但要注意JPQL語句的正確性以及原生SQL的可移植性。
八、面向切面編程(AOP)注解
Spring AOP 提供了強大的面向切面編程功能,可以通過注解定義橫切關(guān)注點,如日志記錄、性能監(jiān)控、權(quán)限檢查等。主要的 AOP 注解包括:
@Aspect
簡介: @Aspect 注解用于將一個類聲明為切面類。由 AspectJ 提供(Spring AOP 使用 AspectJ 注解風格)。標記為 @Aspect 的類內(nèi)部可以定義切點和通知,實現(xiàn) AOP 功能。需要配合 Spring AOP 使用。
作用與場景: 切面類匯總了橫切邏輯,例如日志切面、安全切面等。一個切面類里通常包含若干通知方法(@Before、@After等)和切點定義(@Pointcut)。Spring 在運行時會根據(jù)切面定義生成代理對象,將橫切邏輯織入目標對象的方法調(diào)用。提供模塊: 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() {}
// 前置通知:在滿足切點的方法執(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());
}
}上面定義了一個日志切面類 LoggingAspect:
- 使用
@Aspect標記為切面類,結(jié)合@Component使其成為 Spring Bean。 serviceMethods()方法使用@Pointcut定義了一個切點,表達式"execution(public * com.example.service..*(..))"表示匹配com.example.service包及子包下所有類的任意公共方法執(zhí)行。切點方法本身沒有實現(xiàn),僅作標識。logBefore方法使用@Before("serviceMethods()")注解,表示在執(zhí)行匹配serviceMethods()切點的任意方法之前,先執(zhí)行該通知。- 通過
JoinPoint參數(shù)可以獲取被調(diào)用方法的信息。logAfter方法使用@After("serviceMethods()"),表示目標方法執(zhí)行完成后(無論成功與否)執(zhí)行。輸出方法簽名的退出日志。
為使上述 AOP 生效,需要啟用 Spring 對 AspectJ 切面的支持。Spring Boot 自動配置已經(jīng)啟用了 AOP(如果引入了 starter-aop,默認會開啟 @AspectJ 支持),在非 Boot 環(huán)境可能需要在配置類上添加 @EnableAspectJAutoProxy 注解來開啟代理機制。如果未啟用,@Aspect 注解不會生效??傊?,@Aspect 注解的類定義了 AOP 的橫切邏輯,是實現(xiàn)日志、事務(wù)、權(quán)限等橫切關(guān)注點的關(guān)鍵。
@Pointcut
簡介: @Pointcut 用于定義一個切點表達式,以命名方式重用切點。由 AspectJ 注解提供。通常是一個簽名為 void 且無實現(xiàn)的方法注解,用于給切點命名。
作用與場景: 切點定義了哪些連接點(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() {
// 執(zhí)行權(quán)限驗證邏輯
if (!SecurityContext.isAuthenticated()) {
throw new SecurityException("User not authenticated");
}
}
}此處,SecurityAspect 定義了一個切點 allGetEndpoints(),通過 @Pointcut 注解的表達式指定:凡是標注了@RestController的類中,標注了@GetMapping的方法,都是切點。然后在 @Before("allGetEndpoints()") 通知中引用這個切點,執(zhí)行權(quán)限檢查。如果當前用戶未認證則拋出異常阻止方法執(zhí)行。
切點表達式語言十分豐富,可以基于執(zhí)行方法簽名(execution)、注解(@annotation, within 等)、this/target對象等進行匹配組合。通過適當?shù)那悬c定義,可以靈活地選擇哪些點應用橫切邏輯。
@Before
簡介: @Before 定義一個前置通知(Advice),即在目標方法執(zhí)行之前執(zhí)行的切面方法。它由 AspectJ 提供(org.aspectj.lang.annotation.Before)。需要在 @Aspect 切面類中使用,注解的值是一個切點表達式或命名切點。
作用與場景: 前置通知通常用于在方法調(diào)用前執(zhí)行一些檢查、日志或準備工作。例如權(quán)限驗證(見上例)、記錄方法開始日志、在方法執(zhí)行前設(shè)置環(huán)境(如初始化 ThreadLocal)等。在目標方法之前執(zhí)行,不影響目標方法參數(shù)和執(zhí)行結(jié)果,只作附加操作。
使用示例: 參考前述 LoggingAspect 和 SecurityAspect 中的 @Before 用法。在 LoggingAspect 中:
@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) { ... }這表示在匹配 serviceMethods() 切點的每個目標方法執(zhí)行前調(diào)用 logBefore 方法。JoinPoint 參數(shù)可以獲取方法簽名、參數(shù)等信息,用于日志輸出。
@Before 通知不能阻止目標方法執(zhí)行(除非拋出異常)。如果在通知中拋異常,目標方法將不會執(zhí)行且異常向上拋出。因此一般前置通知不故意拋異常(權(quán)限驗證除外,驗證失敗則通過異常中斷執(zhí)行)。
@After
簡介: @After 定義一個后置通知,即在目標方法執(zhí)行結(jié)束后執(zhí)行的切面方法,無論目標方法正常返回還是拋出異常都會執(zhí)行(類似 finally block)。由 AspectJ 提供。
作用與場景: 常用于清理資源、記錄方法結(jié)束日志等操作。例如在方法完成后記錄執(zhí)行時間(需要結(jié)合開始時間),或確保某些線程上下文數(shù)據(jù)被清除。不關(guān)心方法的結(jié)果,只要離開方法就執(zhí)行通知。
使用示例: 參考 LoggingAspect 中:
@After("serviceMethods()")
public void logAfter(JoinPoint joinPoint) { ... }不管 serviceMethods() 匹配的方法成功或異常返回,都會執(zhí)行 logAfter??梢杂盟鼇泶蛴‰x開方法的日志。
如果需要根據(jù)方法是否拋異常做區(qū)分,可以使用 @AfterReturning 或 @AfterThrowing(詳見下文)。@After 通常用來放置最終執(zhí)行的操作,比如解鎖資源,不管成功失敗都要執(zhí)行的。
@AfterReturning
簡介: @AfterReturning 定義一個返回通知,在目標方法成功返回后執(zhí)行(未拋異常)??梢圆东@返回值。由 AspectJ 提供。
作用與場景: 當需要獲取目標方法的返回結(jié)果進行處理時,可使用 @AfterReturning。例如日志中記錄返回值,或者根據(jù)返回值做后續(xù)動作。若目標方法拋異常則不會執(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或確認對象。若 placeOrder 拋異常,則該通知不執(zhí)行。
@AfterThrowing
簡介: @AfterThrowing 定義一個異常通知,在目標方法拋出指定異常后執(zhí)行。由 AspectJ 提供??刹东@異常對象。
作用與場景: 用于統(tǒng)一處理或記錄目標方法拋出的異常,例如記錄錯誤日志、發(fā)送告警等??梢灾付?throwing 屬性將異常綁定到參數(shù)。只在有未捕獲異常時執(zhí)行,正常返回不執(zhí)行。
使用示例:
@AfterThrowing(pointcut = "execution(* com.example..*.*(..))", throwing = "ex")
public void logException(Exception ex) {
System.err.println("Exception in method: " + ex.getMessage());
}該切面方法會在應用中任何未捕獲的異常拋出時執(zhí)行,打印異常信息。ex 參數(shù)即目標方法拋出的異常對象(可以指定具體異常類型過濾,如 throwing="ex" throwing=RuntimeException.class)。
通過 @AfterThrowing 可以集中處理異常情況,例如對特定異常進行額外處理(如事務(wù)補償或資源回收),或統(tǒng)一記錄。
@Around
簡介: @Around 定義一個環(huán)繞通知,它包裹了目標方法的執(zhí)行。由 AspectJ 提供。環(huán)繞通知最為強大,可以在方法執(zhí)行前后都進行處理,并可決定是否、如何執(zhí)行目標方法(通過 ProceedingJoinPoint 調(diào)用)。
作用與場景: 可以用來計算執(zhí)行時間、控制方法執(zhí)行(比如實現(xiàn)自定義注解的權(quán)限校驗并決定是否調(diào)用原方法)、修改方法的返回值甚至攔截異常。@Around 通知需要顯式調(diào)用 proceed() 才會執(zhí)行目標方法,如果不調(diào)用則目標方法不執(zhí)行。這讓我們有機會在調(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í)行目標方法
} finally {
long end = System.currentTimeMillis();
System.out.println(pjp.getSignature() + " executed in " + (end - start) + "ms");
}
return result;
}這個環(huán)繞通知為 service 包下所有方法計算執(zhí)行時間:
- 在調(diào)用目標方法前記錄開始時間。
- 通過
pjp.proceed()執(zhí)行目標方法,將返回結(jié)果保存。 - 方法執(zhí)行后計算時間差并打印。
- 將目標方法的返回值返回,保證調(diào)用流程正常進行。
如果目標方法拋異常,proceed() 會拋出異常到外層(如上例沒有 catch,finally執(zhí)行后異常繼續(xù)拋出)。也可以在環(huán)繞通知中捕獲異常并處理,甚至返回替代結(jié)果,從而吞掉異常(視業(yè)務(wù)需要謹慎處理)。
@Around 通知還可以實現(xiàn)諸如自定義注解攔截功能,例如檢查方法上是否有某注解,有則執(zhí)行特殊邏輯等,靈活性最高。
@EnableAspectJAutoProxy
簡介: @EnableAspectJAutoProxy 是 Spring 提供的注解(org.springframework.context.annotation.EnableAspectJAutoProxy),用于開啟基于注解的 AOP 支撐。它會啟用 AspectJ 注解的自動代理機制。
作用與場景: 在純 Spring 配置中,需要在配置類上添加此注解才能使前述 @Aspect 切面生效(等同于 XML 配置中的 <aop:aspectj-autoproxy/>)。Spring Boot 在引入 AOP 起步依賴時,默認已經(jīng)啟用了該功能 ,因此多數(shù)情況下無需顯式添加。但了解這個注解有助于在需要調(diào)整 AOP 代理選項時使用(比如 proxyTargetClass=true 強制使用CGLIB代理)。
使用示例:
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig {
// 切面類Bean或通過@Component掃描切面類
}這將在容器中搜索 @Aspect 注解的類,自動創(chuàng)建代理。proxyTargetClass=true 強制使用類代理而不是接口代理。默認為 false,即如果有實現(xiàn)接口則用JDK動態(tài)代理。這一點在需要代理沒有接口的類或者希望統(tǒng)一使用CGLIB代理時可以設(shè)置。
總結(jié)而言,Spring AOP 的注解允許我們以聲明方式實現(xiàn)橫切邏輯,將日志、性能監(jiān)控、安全檢查等與業(yè)務(wù)代碼分離,提升模塊化和可維護性。
九、異步與定時任務(wù)注解
Spring 提供了對多線程異步任務(wù)和定時調(diào)度的支持,只需通過注解即可開啟這些功能。
@Async
簡介: @Async 注解用于將某個方法聲明為異步執(zhí)行。由 Spring 提供(org.springframework.scheduling.annotation.Async)。標注該注解的方法會在調(diào)用時由 Spring 異步執(zhí)行,而不是同步阻塞當前線程。通常需要配合 @EnableAsync 一起使用。
作用與場景: 當某些操作不需要同步完成、可以在后臺線程執(zhí)行時,用 @Async 能簡化并發(fā)編程。例如發(fā)送郵件、短信通知,執(zhí)行耗時的計算而不阻塞主流程,或并行調(diào)用多個外部服務(wù)等。Spring 會基于 TaskExecutor (默認SimpleAsyncTaskExecutor)調(diào)度異步方法。方法可以返回 void 或 Future/CompletableFuture 以便獲取結(jié)果。
使用示例:
@Service
public class NotificationService {
@Async
public void sendEmail(String to, String content) {
// 模擬發(fā)送郵件的耗時操作
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,因此當它被調(diào)用時,Spring 會從線程池中拿出一個線程來異步執(zhí)行該方法,原調(diào)用方線程不必等待5秒。需要在應用主類或配置類上添加 @EnableAsync 以激活異步處理能力。使用默認配置時,Spring 會使用一個簡單線程池執(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,因為后者是異步的,所以 placeOrder 在觸發(fā)郵件發(fā)送后會立即返回響應,郵件發(fā)送在另一個線程進行,不影響接口響應時間。異步調(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 類上或獨立的配置類上均可。
啟用后,Spring 容器會搜索應用中標注了 @Async 的 Bean 方法,并通過代理的方式調(diào)用線程池執(zhí)行它們。默認的執(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 會自動使用它(因為默認執(zhí)行器名稱就是 taskExecutor)。也可在 @Async 注解的參數(shù)中指定一個自定義執(zhí)行器 Bean 名稱。
@Scheduled
簡介: @Scheduled 注解用于將方法標記為定時任務(wù)。由 Spring 提供(org.springframework.scheduling.annotation.Scheduled)??梢酝ㄟ^ cron 表達式或固定間隔等配置何時運行該方法。需要配合 @EnableScheduling 開啟調(diào)度支持。
作用與場景: 當需要周期性地執(zhí)行某段代碼時,例如每隔一段時間檢查庫存,每天夜間生成報表等,可以使用 @Scheduled 注解而不需要借助外部的調(diào)度框架。Spring 容器會在后臺線程按指定計劃調(diào)用這些方法。支持多種調(diào)度配置:
cron表達式:通過 Cron 定義復雜時間計劃。- 固定速率
fixedRate:以上一次開始時間為基準,間隔固定毫秒執(zhí)行。 - 固定延遲
fixedDelay:以上一次完成時間為基準,延遲固定毫秒執(zhí)行。 - 可選屬性如
initialDelay等設(shè)置啟動延遲。
使用示例:
@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秒檢查系統(tǒng)健康
System.out.println("Health check at " + Instant.now());
// ... 健康檢查邏輯
}
}這里,ReportTask 類中:
generateDailyReport()使用cron="0 0 2 * * ?",表示每天2:00執(zhí)行(Cron表達式:“秒 分 時 日 月 周”,“?”表示不指定周幾)。這個方法將在主線程之外的調(diào)度線程按計劃調(diào)用。checkSystemHealth()使用fixedRate=60000表示每60秒執(zhí)行一次,不論上次執(zhí)行多長時間,都按固定頻率觸發(fā)。若上次尚未執(zhí)行完,新周期到了默認不會并發(fā)執(zhí)行(調(diào)度器會等待),但可以通過配置Scheduler實現(xiàn)并發(fā)。
為了使 @Scheduled 生效,需要在配置類上添加 @EnableScheduling(見下文)。Spring Boot 應用通常也需要手動加這一注解。定時任務(wù)執(zhí)行由 Spring 的 TaskScheduler(默認SingleThreadScheduler)驅(qū)動,可能需要注意任務(wù)不應長時間阻塞,否則會影響后續(xù)任務(wù)調(diào)度。可自定義線程池 TaskScheduler 以提高并發(fā)度。
@EnableScheduling
簡介: @EnableScheduling 注解用于開啟 Spring 對定時任務(wù)調(diào)度的支持(org.springframework.scheduling.annotation.EnableScheduling)。添加在配置類或主類上。
作用與場景: 沒有這個注解,@Scheduled 等注解不會被識別處理。啟用后,Spring 容器會啟動一個調(diào)度線程池,定時調(diào)用標記的方法。提供模塊: Spring Context 定時任務(wù)支持。
使用示例:
@SpringBootApplication
@EnableScheduling
public class Application { ... }將 @EnableScheduling 放在啟動類上即可激活調(diào)度機制。然后所有 @Scheduled 注解的方法都會按照配置的計劃執(zhí)行。Spring Boot 不會自動開啟定時任務(wù)支持,因為有的應用可能不需要調(diào)度功能,所以必須顯式聲明。
如果需要自定義調(diào)度器,可以定義 Scheduler Bean 或 TaskScheduler Bean。默認使用單線程執(zhí)行所有定時任務(wù),若多個任務(wù)需要并行,建議提供 ThreadPoolTaskScheduler Bean。
通過 @Async 和 @Scheduled 這組注解,Spring 讓并發(fā)編程和任務(wù)調(diào)度變得非常容易,不再需要顯式創(chuàng)建線程或使用外部調(diào)度平臺,在應用內(nèi)部即可完成這些邏輯。
十、緩存注解
Spring 提供了便捷的緩存機制,通過注解即可實現(xiàn)方法級緩存,把方法調(diào)用結(jié)果存儲起來,避免重復計算或數(shù)據(jù)庫查詢。
@EnableCaching
簡介: @EnableCaching 注解用于開啟 Spring 對緩存注解的支持(org.springframework.cache.annotation.EnableCaching)。通常加在配置類或主類上,激活緩存管理能力。
作用與場景: 開啟后,Spring 會自動配置一個緩存管理器(可基于內(nèi)存、EhCache、Redis等,取決于依賴配置),并掃描應用中的緩存注解(如 @Cacheable 等),在運行時用AOP代理實現(xiàn)緩存邏輯。提供模塊: Spring Cache。
使用示例:
@SpringBootApplication
@EnableCaching
public class Application { ... }這樣,Spring Boot 就會自動根據(jù) classpath 中的緩存庫選擇緩存實現(xiàn)(如有 spring-boot-starter-cache 默認用 ConcurrentMapCache 簡單實現(xiàn);如果引入 spring-boot-starter-redis 則使用 RedisCacheManager 等)。確保在使用緩存注解前調(diào)用了 @EnableCaching,否則緩存注解不會生效。
@Cacheable
簡介: @Cacheable 用于標記方法,將其返回結(jié)果緩存起來。由 Spring 提供(org.springframework.cache.annotation.Cacheable)。再次調(diào)用該方法時,如果傳入?yún)?shù)相同且緩存中有結(jié)果,則直接返回緩存而不執(zhí)行方法。
作用與場景: 典型用于讀取操作緩存,例如從數(shù)據(jù)庫查詢數(shù)據(jù)后緩存,下次查詢相同參數(shù)可以直接返回緩存值,提高性能。@Cacheable 需要指定緩存的名稱(cacheName)以及緩存鍵(key),可以是 SpEL 表達式。默認鍵根據(jù)所有參數(shù)自動生成(需參數(shù)可哈希)。
提供模塊: Spring Cache。
使用示例:
@Service
public class ProductService {
@Cacheable(value = "products", key = "#id")
public Product getProductById(Long id) {
// 假設(shè)這里有復雜計算或慢速數(shù)據(jù)庫查詢
System.out.println("Loading product " + id + " from DB...");
return productRepository.findById(id).orElse(null);
}
}配置 @Cacheable(value="products", key="#id"):
value指定緩存的名字叫 “products”(類似分類,可對應不同緩存存儲)。key="#id"表示使用方法參數(shù)id作為緩存鍵。
第一次調(diào)用 getProductById(1L) 時,會打印“Loading product 1 from DB…”并查詢數(shù)據(jù)庫,然后結(jié)果緩存到名為 “products” 的緩存中,鍵為 1。第二次調(diào)用 getProductById(1L),Spring 檢測到相同鍵在緩存中有值,直接返回緩存,不執(zhí)行方法主體,因此不會再打印那條日志。
@Cacheable 還有一些屬性:
condition:滿足條件時才緩存或才查緩存,如condition="#id > 10".unless:方法執(zhí)行完后判斷,如果滿足條件則不緩存結(jié)果,如unless="#result == null".sync:是否在并發(fā)場景下同步,只讓一個線程計算緩存,其它等待。
@CachePut
簡介: @CachePut 注解用于將方法返回值直接放入緩存,但與 @Cacheable 不同的是,它始終執(zhí)行方法,不會跳過。它通常用于更新緩存數(shù)據(jù)。由 Spring 提供。
作用與場景: 當執(zhí)行了修改操作后,希望緩存與數(shù)據(jù)庫同步更新,可使用 @CachePut 標記修改方法,使其結(jié)果及時寫入緩存。這樣后續(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 注解確保無論如何,這個返回的 Product 對象會以其 id 作為鍵,存入 “products” 緩存(覆蓋舊值)。因此,即便之前通過 @Cacheable 緩存過舊的 Product 數(shù)據(jù),這里也會更新緩存,使之與數(shù)據(jù)庫一致。值得注意的是,@CachePut 不會影響方法執(zhí)行(總會執(zhí)行方法),它只是在返回后把結(jié)果寫緩存。
@CacheEvict
簡介: @CacheEvict 注解用于移除緩存。標記在方法上,可以在方法執(zhí)行前或后將指定 key 或整個緩存清除。由 Spring 提供。
作用與場景: 當數(shù)據(jù)被刪除或改變且緩存不再有效時,需要清除緩存。例如刪除一個記錄后,需要把對應緩存刪掉;批量更新后,可以選擇清空整個緩存。@CacheEvict 支持指定 key 或設(shè)置 allEntries=true 清空整個命名緩存。提供模塊: 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) 時,@CacheEvict 會使緩存 “products” 中鍵為5的條目無效(刪除)。默認地,它在方法成功執(zhí)行后清除緩存。如果希望無論方法是否成功都清除,可設(shè)定 beforeInvocation=true,那將在方法進入時就清除(防止方法拋異常緩存未清)。allEntries=true 則可以不顧鍵,直接清空整個緩存空間。例如:
@CacheEvict(value = "products", allEntries = true)
public void refreshAllProducts() { ... }會清除 “products” 緩存的所有條目。
通過 @CacheEvict 與 @CachePut,我們可以維護緩存與底層數(shù)據(jù)的一致性。
注意: 使用緩存注解要求配置正確的 CacheManager 和緩存存儲。Spring Boot 默認使用簡單的內(nèi)存緩存(ConcurrentMapCacheManager)用于開發(fā)測試。生產(chǎn)中常結(jié)合 Redis、Ehcache等實現(xiàn),更換實現(xiàn)通常無需改動注解,只需配置 CacheManager Bean。
十一、事件監(jiān)聽注解
Spring 提供了應用內(nèi)事件發(fā)布-訂閱機制,支持松耦合的消息通信。通過注解可以方便地訂閱事件。
@EventListener
簡介: @EventListener 是 Spring 4.2+ 引入的注解(org.springframework.context.event.EventListener),用于將任意 Spring Bean 的方法標識為事件監(jiān)聽器。當有匹配的事件發(fā)布時(實現(xiàn) ApplicationEvent 或自定義事件對象),該方法會被調(diào)用。相比實現(xiàn) ApplicationListener 接口,注解方式更簡潔。
作用與場景: 在應用內(nèi),不同組件之間可以通過發(fā)布事件進行解耦通訊。例如用戶注冊后發(fā)布一個 UserRegisteredEvent,由其他監(jiān)聽器監(jiān)聽來發(fā)送歡迎郵件或統(tǒng)計指標。使用 @EventListener,方法簽名定義了它感興趣的事件類型,也可以通過 condition 屬性設(shè)置過濾條件(比如只處理某字段滿足條件的事件)。提供模塊: Spring Context 事件機制。
使用示例:
// 定義事件類(可以繼承 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());
// ... 實際發(fā)送郵件邏輯
}
}流程說明:
UserService.register方法在新用戶注冊成功后,通過ApplicationEventPublisher發(fā)布了一個UserRegisteredEvent事件。Spring Boot 默認通過ApplicationEventPublisher將事件發(fā)布到應用上下文。WelcomeEmailListener是一個普通組件(被@Component掃描)。其中方法handleUserRegistered標注了@EventListener,且參數(shù)是UserRegisteredEvent。這表明它訂閱此類型事件。當事件被發(fā)布時,Spring 檢測到存在匹配的監(jiān)聽方法,便調(diào)用該方法并將事件對象傳入。- 監(jiān)聽方法運行,完成發(fā)送歡迎郵件的功能。
這樣,發(fā)送郵件的邏輯和用戶服務(wù)邏輯完全解耦,只通過事件聯(lián)系。如果以后不需要發(fā)送郵件,只需移除監(jiān)聽器,而不影響用戶注冊流程。另外,可以很容易地新增其它監(jiān)聽,如統(tǒng)計注冊用戶數(shù)的監(jiān)聽器,而不需要修改 UserService。
@EventListener 還支持 condition 屬性使用 SpEL 表達式進行事件內(nèi)容過濾。例如:
@EventListener(condition = "#event.user.vip")
public void handleVipUserRegistered(UserRegisteredEvent event) { ... }僅當用戶是VIP時才處理。這種細粒度控制進一步增強了事件機制的靈活性。
需要注意,默認事件監(jiān)聽器在發(fā)布線程內(nèi)同步執(zhí)行。如果想異步處理事件,可以結(jié)合 @Async 注解,將監(jiān)聽方法異步執(zhí)行(前提是已啟用 @EnableAsync)。或者使用 Spring 5 提供的 ApplicationEventMulticaster 配置為異步模式。
ApplicationListener 接口 (替代方案)
說明: 在 @EventListener 出現(xiàn)之前,Spring 使用實現(xiàn) ApplicationListener<E> 接口的方式來監(jiān)聽事件。雖然這不是注解,但與事件注解結(jié)合使用時值得一提。任何 Spring Bean 實現(xiàn)了 ApplicationListener<MyEvent>,當 MyEvent 發(fā)布時其 onApplicationEvent 方法會被調(diào)用。自 Spring 4.2 起推薦使用 @EventListener 代替,更加簡潔。
使用示例:
@Component
public class StatsListener implements ApplicationListener<UserRegisteredEvent> {
@Override
public void onApplicationEvent(UserRegisteredEvent event) {
// 統(tǒng)計用戶注冊
metrics.increment("user.register.count");
}
}這個監(jiān)聽器無須注解,Spring根據(jù)泛型自動注冊。但相比注解方式,它需要一個獨立的類實現(xiàn)接口,不如 @EventListener 可以直接用任意方法方便。而且一個類只能實現(xiàn)對一種事件的監(jiān)聽,要監(jiān)聽多種事件需要寫多個類或使用一些if判斷,不如注解靈活。因此現(xiàn)在開發(fā)中更多使用 @EventListener。
綜上,Spring 的事件模型通過發(fā)布訂閱實現(xiàn)了應用內(nèi)部的解耦協(xié)作。@EventListener 極大降低了使用門檻,使得監(jiān)聽事件就像寫普通方法一樣便捷。配合異步能力,還能實現(xiàn)類似消息隊列的效果,用于不太關(guān)鍵的異步通知等場景。
十二、測試相關(guān)注解
Spring 為了方便編寫測試,特別是針對 Spring MVC 或 JPA等組件的測試,提供了一系列注解來簡化配置測試上下文。
@SpringBootTest
簡介: @SpringBootTest 是 Spring Boot 測試框架提供的注解(org.springframework.boot.test.context.SpringBootTest),用于在測試類上,表示啟動一個完整的 Spring Boot 應用上下文進行集成測試。
作用與場景: 標注此注解的測試類在運行時會通過 Spring Boot 引導啟動應用(除非配置特定屬性使其部分引導),這意味著:
- 會掃描并創(chuàng)建所有 Bean,加載完整應用上下文。
- 提供對 Bean 的依賴注入支持,使測試類可以直接
@Autowired需要的 Bean 進行集成測試。
它常用于需要測試多個層級協(xié)同工作的場景,例如驗證服務(wù)層和倉庫層交互,或者整個請求流程。
使用示例:
@SpringBootTest
class ApplicationTests {
@Autowired
private UserService userService;
@Test
void testUserRegistration() {
User user = new User("alice");
userService.register(user);
// 驗證注冊結(jié)果,比如檢查數(shù)據(jù)庫或事件發(fā)布效果
assertNotNull(user.getId());
}
}這個測試類使用 @SpringBootTest,則測試運行時 Spring Boot 會啟動應用上下文并注入 UserService Bean,測試方法里可以直接調(diào)用業(yè)務(wù)代碼進行驗證。@SpringBootTest 還可以指定啟動端口、環(huán)境等參數(shù),或通過 properties 覆蓋配置,比如:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
用于啟動嵌入式服務(wù)器在隨機端口,以進行 Web 集成測試。
@WebMvcTest
簡介: @WebMvcTest 是用于測試 Spring MVC 控制器的注解(org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest)。它會啟動一個精簡的 Spring MVC 環(huán)境,只包含Web相關(guān)的Bean,如@Controller、@RestController等,以及MVC配置,而不加載整個應用上下文。
作用與場景: 主要用于單元測試控制器。默認只掃描 @Controller 和 @RestController 等 Web 層組件,以及必要的配置(如 Jackson 轉(zhuǎn)換、Validator)。不會加載服務(wù)層、倉庫層Bean,除非通過配置指定。這樣測試運行速度快且聚焦于MVC層邏輯。常配合 MockMvc (Spring提供的模擬MVC請求的工具)使用進行控制器的請求/響應測試。
使用示例:
@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");
// 定義當userService.findById(1)被調(diào)用時返回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相關(guān)的 MVC 組件,如UserController本身,MVC配置,序列化組件等。 UserService因為不是@Controller組件,不會自動加載。因此使用了@MockBean注解(見后)創(chuàng)建一個模擬的UserServiceBean,將其注入到UserController中,避免涉及真實的服務(wù)層邏輯。- 測試使用
MockMvc發(fā)起GET請求到/users/1,并斷言返回狀態(tài)200和返回JSON中的name字段為"Bob"。由于我們預先通過given(userService.findById(1L))指定了模擬行為,所以控制器調(diào)用userService時會得到我們構(gòu)造的user對象。
通過這種方式,不需要啟動整個應用,也不需要真實數(shù)據(jù)庫等,就能測試控制器映射、參數(shù)解析、返回結(jié)果等。@WebMvcTest 提供了對Spring MVC各方面的支持(如可以自動配置MockMvc)。
@DataJpaTest
簡介: @DataJpaTest 是用于測試 JPA 持久層的注解(org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest)。它會啟動一個只包含 JPA 相關(guān)組件的 Spring 應用上下文,例如實體管理器、Spring Data JPA 倉庫、嵌入式數(shù)據(jù)庫等。
作用與場景: 主要用于單元測試 Repository 層。它會:
- 配置嵌入式內(nèi)存數(shù)據(jù)庫(如H2)用于測試,除非明確指定其他DataSource。
- 掃描
@Entity實體和 Spring Data Repository 接口并注冊。 - 不加載 web、安全等其他非持久層Bean,以加快測試速度。
- 默認使用事務(wù)包裝每個測試,并在結(jié)束時回滾,保證測試隔離。
使用示例:
@DataJpaTest
class UserRepositoryTests {
@Autowired
private UserRepository userRepository;
@Test
void testFindByEmail() {
// 準備測試數(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 將自動配置一個內(nèi)存數(shù)據(jù)庫并初始化 JPA 環(huán)境。UserRepository 接口(假設(shè)繼承自 JpaRepository)會被加載為Bean。測試中先保存一個用戶,然后調(diào)用倉庫自定義方法 findByEmail 驗證結(jié)果。由于測試結(jié)束時事務(wù)會回滾,插入的測試數(shù)據(jù)不會污染下一個測試或?qū)嶋H數(shù)據(jù)庫。
@DataJpaTest 同樣可以與 @MockBean 配合如果需要模擬一些非JPA的Bean,但是通常持久層測試不需要。也可以通過 properties 指定連接真實數(shù)據(jù)庫進行集成測試,不過大多數(shù)情況下,使用內(nèi)存數(shù)據(jù)庫足以測試Repository邏輯。
@MockBean
簡介: @MockBean 是 Spring Boot Test 提供的注解(org.springframework.boot.test.mock.mockito.MockBean),用于在 Spring 測試上下文中添加一個由 Mockito 模擬的Bean,并替換掉容器中原本該類型的Bean(如果有)。常用于在 Web層或服務(wù)層測試中,模擬依賴的Bean行為。
作用與場景: 當測試的目標Bean有依賴,而我們不想測試依賴的真實邏輯(可能復雜或不確定),就可以用 @MockBean 來提供一個Mockito創(chuàng)建的模擬對象給容器。這比手工使用 Mockito.mock 然后手動注入更方便,因為 Spring 會自動把這個模擬Bean注入到需要它的地方。典型應用是在 @WebMvcTest 中模擬服務(wù)層Bean,在 @SpringBootTest 中模擬外部系統(tǒng)客戶端Bean等。
使用示例:
@MockBean private WeatherService weatherService;
將 WeatherService 接口模擬為一個 Bean 注入容器。如果應用上下文本來有一個該類型的Bean(比如真實的實現(xiàn)),會被模擬對象替換。這使得我們可以用 given(weatherService.getTodayWeather())... 等來預設(shè)行為。這個注解可以用在測試類的字段上(如上)、也可以用在測試方法內(nèi)參數(shù)上。
具體的用法在前面的 @WebMvcTest 示例已經(jīng)體現(xiàn)。再比如,在一個服務(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);
// 驗證內(nèi)部行為,如訂單狀態(tài)更新
// ...
}
}這里 OrderService 依賴 PaymentClient,但我們不想真的調(diào)用外部服務(wù),于是用 @MockBean 模擬它并規(guī)定返回 PaymentResult 成功。這樣 OrderService.processPayment 執(zhí)行時實際上用的是假的 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層或持久層進行單元測試,大大提高測試執(zhí)行速度和定位問題的精準度。Spring Boot 自動配置為測試裁剪了上下文,避免加載無關(guān)bean,使測試既保持類似生產(chǎn)環(huán)境的行為,又能高效運行。
十三、安全相關(guān)注解
Spring Security 框架提供了方法級安全控制的注解和配置注解,方便對控制器或服務(wù)方法實施權(quán)限檢查。此外還有開啟安全的配置注解等。
@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 {
@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 時添加該注解,因此有時無需手動添加;但當我們提供自定義安全配置類時,一般會注明此注解。注意: Spring Security 5.7 開始,官方更推薦不繼承類而是聲明 SecurityFilterChain Bean 配合 @EnableWebSecurity 使用,但注解作用相同。
@EnableGlobalMethodSecurity (已過時) / @EnableMethodSecurity
簡介: @EnableGlobalMethodSecurity 用于開啟方法級安全注解的支持(如 @PreAuthorize, @Secured)。這是 Spring Security 舊版本使用的注解,位于 org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity。它已經(jīng)在 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 環(huán)境中,使用 @EnableMethodSecurity 取代 @EnableGlobalMethodSecurity 來開啟方法安全注解支持。
@PreAuthorize
簡介: @PreAuthorize 是 Spring Security 的方法級權(quán)限注解(org.springframework.security.access.prepost.PreAuthorize)。它可以用在方法或類上,在方法調(diào)用之前基于給定的表達式進行權(quán)限驗證。需要啟用了全局方法安全后(如上),此注解才會生效。
作用與場景: @PreAuthorize 可以檢查當前認證用戶是否具備某權(quán)限或角色,或者滿足SpEL表達式定義的任意條件,然后才允許方法執(zhí)行。常用于服務(wù)層或控制層方法,保護敏感操作。例如只有ADMIN角色能調(diào)用刪除用戶方法,或者只有資源擁有者才能訪問資源等。它比 @Secured 更強大,因為可以使用Spring EL編寫復雜的邏輯。
使用示例:
@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方法上,使用了表達式:#user.name == authentication.name or hasAuthority('SCOPE_profile')。authentication.name代表當前登錄用戶名。如果傳入的user.name等于當前用戶名(即查詢自己的資料),或當前主體具有SCOPE_profile權(quán)限(例如 OAuth2 scope),則允許訪問。否則拒絕??梢钥吹絇reAuthorize能夠引用方法參數(shù)(通過#參數(shù)名)和安全上下文信息(authentication對象)進行復雜判斷。
@PreAuthorize 非常靈活,也支持調(diào)用自定義權(quán)限評估方法等。但要注意權(quán)限表達式越復雜可能越難維護,需要在安全和可讀性之間平衡。Spring Security官方推薦使用PreAuthorize勝過Secured,因為其表達能力更強。
@Secured
簡介: @Secured 是較早的 Spring Security 提供的簡單方法安全注解(org.springframework.security.access.annotation.Secured)。它指定一組允許的角色,調(diào)用該方法的用戶必須具備其中一個角色才行。需要在全局方法安全配置中啟用 securedEnabled=true 才生效。
作用與場景: 適用于簡單的基于角色的訪問控制。如果系統(tǒng)的授權(quán)模型主要基于角色,可以使用 @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方法要求調(diào)用者必須擁有ROLE_ADMIN角色。getData方法允許ROLE_USER或ROLE_ADMIN擁有者訪問(邏輯是OR的關(guān)系)。
如果不滿足要求,Spring Security同樣會拋出訪問拒絕異常。@Secured 內(nèi)部實際上也是通過 AOP 攔截,與 @PreAuthorize 實現(xiàn)機制類似,但因為其功能有限,Spring官方更推薦使用Pre/PostAuthorize。
需要留意的是,@Secured 注解中的字符串需要包含完整的角色前綴(如默認前綴是 “ROLE_”)。如上必須寫 “ROLE_ADMIN” 而不是 “ADMIN”,除非通過配置修改了前綴策略。
@RolesAllowed
簡介: @RolesAllowed 來自 JSR-250(jakarta.annotation.security.RolesAllowed),功能與 @Secured 類似,也是指定允許訪問方法的角色列表。Spring Security 支持它,需要 jsr250Enabled=true。它和 Secured的區(qū)別主要在注解來源不同。
作用與場景: 可以作為 @Secured 的替代,用標準注解來聲明角色限制。在Spring環(huán)境下兩者效果一樣。提供模塊: JSR-250,Spring Security需啟用支持。
使用示例:
@RolesAllowed("ADMIN")
public void updateSettings(Settings settings) { ... }這里假設(shè)已將角色前綴配置成無"ROLE_"前綴或 SecurityConfigurer里做了處理,否則 Spring Security會把 “ADMIN” 當作角色名直接匹配 GrantedAuthority “ADMIN”。一般Secured和RolesAllowed不能混用不同前綴,否則容易出錯。
綜上,Spring Security提供的這些注解允許我們無需在方法內(nèi)部手動檢查權(quán)限,而由框架自動在調(diào)用前進行驗證,符合條件才執(zhí)行。需要注意:
- 要在配置類上開啟相應支持(使用
@EnableMethodSecurity或舊版@EnableGlobalMethodSecurity)。 @PreAuthorize/@PostAuthorize功能最強,但稍復雜,@Secured/@RolesAllowed簡單直接,但只能基于角色判斷。- 這類注解只檢查Spring Security的上下文,對于未經(jīng)過濾器鏈保護的方法調(diào)用(比如同類中自調(diào)用方法不會觸發(fā)注解檢查,或者在無Security環(huán)境下)就不起作用。這是常見陷阱——所以帶有安全注解的方法最好不要在內(nèi)部直接調(diào)用,否則繞過了切面檢查。
十四、其他常用注解
除了上述類別,Spring & Spring Boot 中還有一些常用但未分類到的注解,例如:
- 參數(shù)校驗相關(guān): Spring 對 JSR 303 Bean Validation 的支持,讓我們可以在模型上使用如
@NotNull,@Size,@Valid等注解。其中在 Controller 方法參數(shù)上使用@Valid可觸發(fā)校驗,并結(jié)合@ExceptionHandler或@ControllerAdvice統(tǒng)一處理校驗結(jié)果。 - JSON 序列化控制: 像
@JsonInclude(來自 Jackson)可以注解類或?qū)傩裕刂艼SON序列化包含規(guī)則,例如@JsonInclude(JsonInclude.Include.NON_NULL)表示忽略null值字段。這在返回REST數(shù)據(jù)時很有用。 - 條件裝配注解: Spring Boot 提供了一系列
@ConditionalOn...注解用于自動配置(如@ConditionalOnProperty,@ConditionalOnClass,@ConditionalOnMissingBean等)來有條件地裝配Bean。這些主要在開發(fā)Spring Boot自動配置模塊時使用,在應用層較少直接用到,但理解它們有助于明白Boot的裝配機制。
以上羅列的注解涵蓋了Spring核心開發(fā)中最常用的部分。從應用啟動配置、Bean裝配,到Web層開發(fā)、數(shù)據(jù)持久化、AOP、異步、緩存、事件、測試、安全,各個方面都有簡潔的注解支持。掌握它們的用法能顯著提高開發(fā)效率,減少樣板代碼,讓我們更多關(guān)注業(yè)務(wù)邏輯實現(xiàn)。
總結(jié): Spring & Spring Boot 常用注解極大地便利了開發(fā),它們遵循“約定優(yōu)于配置”的理念,通過簡單的注解聲明即可完成以前繁瑣的XML配置或手動編碼工作。在使用時要注意啟用相應功能的開關(guān)(如異步、事務(wù)、緩存等),理解注解背后機制(如AOP代理、運行時處理)以避免踩坑。熟練運用上述注解,能覆蓋大部分日常開發(fā)場景,實現(xiàn)優(yōu)雅、高效和可維護的Spring應用。
到此這篇關(guān)于Spring Boot 常用注解整理的文章就介紹到這了,更多相關(guān)Spring Boot 常用注解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java多線程編程中使用Condition類操作鎖的方法詳解
Condition是java.util.concurrent.locks包下的類,提供了對線程鎖的更精細的控制方法,下面我們就來看一下Java多線程編程中使用Condition類操作鎖的方法詳解2016-07-07
@RequestBody注解Ajax post json List集合數(shù)據(jù)請求400/41
這篇文章主要介紹了@RequestBody注解Ajax post json List集合數(shù)據(jù)請求400/415的處理方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10
使用@Transactional 設(shè)置嵌套事務(wù)不回滾
這篇文章主要介紹了使用@Transactional 設(shè)置嵌套事務(wù)不回滾問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07

